From b2606e3828cea0e7b98e74ae6df7f2e3d67bc12d Mon Sep 17 00:00:00 2001 From: Jay Lalwani Date: Sat, 7 Feb 2026 17:05:52 -0500 Subject: [PATCH 1/8] Replace legacy views and assets with v2 UI and ads (#1224) * v2 draft * Build v2 search results page * Implement v2 search results page * Clean up v2 course UI * Update review and card displays * Align course instructor v2 parity * Implement v2 UI cleanup plan * Revamp schedule builder V2 plan * Fix section display and scroll * Fix section display and scrolling * Fix sections scroll and messaging * Fix scheduling and messaging UI * Standardize message display sitewide * Update v2 layout and ads * remove v2 * remove old * lint and fixes * Fix review success message * fixes * Fix modal focus and switching --- doc/dev.md | 2 +- doc/parity_tracker.md | 56 ++ tcf_website/static/base/reset.css | 2 +- tcf_website/static/browse/browse.css | 99 -- tcf_website/static/club/mode_toggle.css | 153 --- tcf_website/static/club/mode_toggle.js | 61 -- tcf_website/static/common/form.js | 14 - tcf_website/static/common/rating_card.css | 22 - tcf_website/static/common/recently_viewed.js | 82 -- tcf_website/static/common/show-hide.css | 13 - .../static/course/course_professor.css | 524 ----------- .../static/course/javascript/course_data.js | 329 ------- tcf_website/static/css/site/base.css | 226 +++++ .../static/css/site/components/button.css | 222 +++++ .../static/css/site/components/calendar.css | 184 ++++ .../static/css/site/components/dropdown.css | 132 +++ .../static/css/site/components/footer.css | 230 +++++ .../static/css/site/components/header.css | 316 +++++++ .../static/css/site/components/input.css | 350 +++++++ .../static/css/site/components/misc.css | 428 +++++++++ .../static/css/site/components/modal.css | 295 ++++++ .../static/css/site/components/rating.css | 270 ++++++ .../static/css/site/components/search.css | 378 ++++++++ .../static/css/site/components/search_bar.css | 318 +++++++ tcf_website/static/css/site/main.css | 24 + tcf_website/static/css/site/pages/about.css | 349 +++++++ tcf_website/static/css/site/pages/browse.css | 331 +++++++ tcf_website/static/css/site/pages/club.css | 150 +++ .../static/css/site/pages/club_category.css | 108 +++ tcf_website/static/css/site/pages/course.css | 311 ++++++ .../css/site/pages/course_instructor.css | 683 ++++++++++++++ .../site/pages/course_instructor_grades.css | 83 ++ .../static/css/site/pages/department.css | 329 +++++++ .../static/css/site/pages/instructor.css | 277 ++++++ tcf_website/static/css/site/pages/landing.css | 350 +++++++ tcf_website/static/css/site/pages/legal.css | 61 ++ tcf_website/static/css/site/pages/profile.css | 260 ++++++ tcf_website/static/css/site/pages/review.css | 277 ++++++ tcf_website/static/css/site/pages/reviews.css | 126 +++ .../static/css/site/pages/schedule.css | 358 +++++++ tcf_website/static/css/site/pages/search.css | 271 ++++++ tcf_website/static/css/site/reset.css | 140 +++ tcf_website/static/css/site/tokens.css | 265 ++++++ tcf_website/static/css/site/utilities.css | 359 +++++++ tcf_website/static/department/department.css | 0 tcf_website/static/instructor/instructor.css | 13 - tcf_website/static/js/site/modal.js | 182 ++++ tcf_website/static/js/site/review_votes.js | 135 +++ tcf_website/static/js/site/search_bar.js | 135 +++ tcf_website/static/js/site/theme.js | 117 +++ .../static/landing/UVA_rotunda_logo.png | Bin 20529 -> 0 bytes .../static/landing/UVA_rotunda_logo_blue.png | Bin 8139 -> 0 bytes tcf_website/static/landing/Zelle_qr.jpg | Bin 290553 -> 0 bytes tcf_website/static/landing/dashboard.css | 11 - tcf_website/static/landing/feedbackform.js | 41 - tcf_website/static/landing/landing.css | 183 ---- tcf_website/static/login/login_button.css | 50 - tcf_website/static/profile/profile.css | 8 - tcf_website/static/qa/qa.css | 33 - tcf_website/static/qa/qa.js | 94 -- tcf_website/static/qa/sort_qa.js | 142 --- tcf_website/static/reviews/new_review.css | 149 --- tcf_website/static/reviews/pagination.css | 67 -- tcf_website/static/reviews/review.css | 43 - tcf_website/static/reviews/review.js | 89 -- tcf_website/static/reviews/review_stats.css | 13 - tcf_website/static/reviews/sort_reviews.js | 54 -- tcf_website/static/schedule/parse_time.js | 95 -- tcf_website/static/schedule/schedule.css | 62 -- .../static/schedule/schedule_editor.css | 62 -- .../static/schedule/schedule_select_script.js | 101 -- .../schedule/schedule_with_sections.css | 44 - tcf_website/static/search/filters.js | 380 -------- tcf_website/static/search/search.css | 16 - tcf_website/static/search/searchbar.css | 305 ------ tcf_website/templates/404.html | 26 +- tcf_website/templates/about/about.html | 109 --- .../privacy_content.html} | 18 - .../terms_content.html} | 20 +- tcf_website/templates/base/base.html | 93 -- tcf_website/templates/base/bugform.html | 82 -- tcf_website/templates/base/index.html | 101 -- tcf_website/templates/base/messages.html | 18 - tcf_website/templates/base/navbar.html | 59 -- tcf_website/templates/base/sidebar.html | 144 --- tcf_website/templates/browse/browse.html | 75 -- tcf_website/templates/browse/school.html | 22 - .../templates/club/browse_category.html | 22 - tcf_website/templates/club/category.html | 65 -- tcf_website/templates/club/club.html | 143 --- tcf_website/templates/club/mode_toggle.html | 25 - .../templates/common/adblock_modal.html | 15 - .../templates/common/application_banner.html | 21 - .../templates/common/coming_soon_modal.html | 23 - .../templates/common/donate_page_modal.html | 25 - .../templates/common/history_page_modal.html | 73 -- .../templates/common/leaderboard_ad.html | 9 - .../templates/common/login-fix-banner.html | 14 - .../common/marketing_application_banner.html | 20 - .../templates/common/notification.html | 17 - tcf_website/templates/common/pagination.html | 53 -- .../templates/common/qa_form_banner.html | 15 - tcf_website/templates/common/rating_card.html | 75 -- .../templates/common/review-drive-banner.html | 25 - tcf_website/templates/common/toolbar.html | 66 -- tcf_website/templates/course/course.html | 142 --- .../templates/course/course_professor.html | 364 -------- .../templates/department/department.html | 46 - .../department/department_course.html | 64 -- .../templates/instructor/instructor.html | 135 --- tcf_website/templates/landing/_faqs.json | 38 - tcf_website/templates/landing/dashboard.html | 23 - tcf_website/templates/landing/landing.html | 390 -------- tcf_website/templates/login/login_modal.html | 28 - .../profile/delete_confirm_modal.html | 25 - tcf_website/templates/profile/profile.html | 45 - .../qa/delete_answer_confirm_modal.html | 24 - .../qa/delete_question_confirm_modal.html | 24 - tcf_website/templates/qa/qa.html | 404 -------- .../reviews/delete_confirm_modal.html | 23 - .../reviews/duplicate_review_modal.html | 22 - tcf_website/templates/reviews/new_review.html | 240 ----- tcf_website/templates/reviews/review.html | 117 --- .../reviews/review_form_content.html | 225 ----- .../templates/reviews/review_stats.html | 16 - tcf_website/templates/reviews/reviews.html | 95 -- .../templates/reviews/user_reviews.html | 21 - .../templates/reviews/zero_hours_modal.html | 29 - .../templates/schedule/add_course_modal.html | 109 --- .../schedule/create_schedule_modal.html | 51 - .../schedule/delete_schedule_modal.html | 116 --- .../schedule/edit_schedule_modal.html | 119 --- tcf_website/templates/schedule/schedule.html | 335 ------- .../templates/schedule/schedule_builder.html | 22 - .../templates/schedule/schedule_editor.html | 71 -- .../schedule/schedule_with_sections.html | 96 -- .../schedule/select_schedule_modal.html | 61 -- .../templates/schedule/user_schedules.html | 58 -- tcf_website/templates/search/search.html | 264 ------ tcf_website/templates/search/searchbar.html | 210 ----- tcf_website/templates/site/base.html | 116 +++ .../site/components/_about_member_card.html | 27 + .../components/_about_member_section.html | 10 + .../site/components/_course_card.html | 41 + .../templates/site/components/_footer.html | 87 ++ .../templates/site/components/_header.html | 138 +++ .../site/components/_instructor_card.html | 35 + .../site/components/_leaderboard_ad.html | 14 + .../templates/site/components/_messages.html | 13 + .../site/components/_pagination.html | 48 + .../site/components/_school_section.html | 31 + .../site/components/_search_bar.html | 120 +++ .../site/components/_section_card.html | 35 + .../site/components/_weekly_calendar.html | 41 + .../templates/site/modals/_adblock.html | 26 + tcf_website/templates/site/modals/_login.html | 44 + tcf_website/templates/site/pages/about.html | 179 ++++ tcf_website/templates/site/pages/browse.html | 82 ++ tcf_website/templates/site/pages/club.html | 248 +++++ .../templates/site/pages/club_category.html | 55 ++ tcf_website/templates/site/pages/course.html | 124 +++ .../site/pages/course_instructor.html | 607 ++++++++++++ .../templates/site/pages/department.html | 138 +++ .../templates/site/pages/instructor.html | 131 +++ tcf_website/templates/site/pages/landing.html | 191 ++++ tcf_website/templates/site/pages/privacy.html | 18 + tcf_website/templates/site/pages/profile.html | 146 +++ tcf_website/templates/site/pages/review.html | 328 +++++++ tcf_website/templates/site/pages/reviews.html | 176 ++++ .../templates/site/pages/schedule.html | 173 ++++ .../site/pages/schedule_add_course.html | 128 +++ tcf_website/templates/site/pages/search.html | 274 ++++++ tcf_website/templates/site/pages/terms.html | 18 + .../site/partials/privacy_content.html | 1 + .../site/partials/terms_content.html | 1 + tcf_website/templatetags/custom_tags.py | 88 +- tcf_website/tests/test_browse.py | 65 +- tcf_website/urls.py | 40 +- tcf_website/views/__init__.py | 23 +- tcf_website/views/auth.py | 9 +- tcf_website/views/browse.py | 370 ++++---- tcf_website/views/index.py | 102 +- tcf_website/views/profile.py | 60 +- tcf_website/views/review.py | 297 +++--- tcf_website/views/schedule.py | 883 ++++++++++++------ tcf_website/views/search.py | 3 +- tcf_website/views/utils.py | 15 + 187 files changed, 14177 insertions(+), 9589 deletions(-) create mode 100644 doc/parity_tracker.md delete mode 100644 tcf_website/static/browse/browse.css delete mode 100644 tcf_website/static/club/mode_toggle.css delete mode 100644 tcf_website/static/club/mode_toggle.js delete mode 100644 tcf_website/static/common/form.js delete mode 100644 tcf_website/static/common/rating_card.css delete mode 100644 tcf_website/static/common/recently_viewed.js delete mode 100644 tcf_website/static/common/show-hide.css delete mode 100644 tcf_website/static/course/course_professor.css delete mode 100644 tcf_website/static/course/javascript/course_data.js create mode 100644 tcf_website/static/css/site/base.css create mode 100644 tcf_website/static/css/site/components/button.css create mode 100644 tcf_website/static/css/site/components/calendar.css create mode 100644 tcf_website/static/css/site/components/dropdown.css create mode 100644 tcf_website/static/css/site/components/footer.css create mode 100644 tcf_website/static/css/site/components/header.css create mode 100644 tcf_website/static/css/site/components/input.css create mode 100644 tcf_website/static/css/site/components/misc.css create mode 100644 tcf_website/static/css/site/components/modal.css create mode 100644 tcf_website/static/css/site/components/rating.css create mode 100644 tcf_website/static/css/site/components/search.css create mode 100644 tcf_website/static/css/site/components/search_bar.css create mode 100644 tcf_website/static/css/site/main.css create mode 100644 tcf_website/static/css/site/pages/about.css create mode 100644 tcf_website/static/css/site/pages/browse.css create mode 100644 tcf_website/static/css/site/pages/club.css create mode 100644 tcf_website/static/css/site/pages/club_category.css create mode 100644 tcf_website/static/css/site/pages/course.css create mode 100644 tcf_website/static/css/site/pages/course_instructor.css create mode 100644 tcf_website/static/css/site/pages/course_instructor_grades.css create mode 100644 tcf_website/static/css/site/pages/department.css create mode 100644 tcf_website/static/css/site/pages/instructor.css create mode 100644 tcf_website/static/css/site/pages/landing.css create mode 100644 tcf_website/static/css/site/pages/legal.css create mode 100644 tcf_website/static/css/site/pages/profile.css create mode 100644 tcf_website/static/css/site/pages/review.css create mode 100644 tcf_website/static/css/site/pages/reviews.css create mode 100644 tcf_website/static/css/site/pages/schedule.css create mode 100644 tcf_website/static/css/site/pages/search.css create mode 100644 tcf_website/static/css/site/reset.css create mode 100644 tcf_website/static/css/site/tokens.css create mode 100644 tcf_website/static/css/site/utilities.css delete mode 100644 tcf_website/static/department/department.css delete mode 100644 tcf_website/static/instructor/instructor.css create mode 100644 tcf_website/static/js/site/modal.js create mode 100644 tcf_website/static/js/site/review_votes.js create mode 100644 tcf_website/static/js/site/search_bar.js create mode 100644 tcf_website/static/js/site/theme.js delete mode 100644 tcf_website/static/landing/UVA_rotunda_logo.png delete mode 100644 tcf_website/static/landing/UVA_rotunda_logo_blue.png delete mode 100644 tcf_website/static/landing/Zelle_qr.jpg delete mode 100644 tcf_website/static/landing/dashboard.css delete mode 100644 tcf_website/static/landing/feedbackform.js delete mode 100644 tcf_website/static/landing/landing.css delete mode 100644 tcf_website/static/login/login_button.css delete mode 100644 tcf_website/static/profile/profile.css delete mode 100644 tcf_website/static/qa/qa.css delete mode 100644 tcf_website/static/qa/qa.js delete mode 100644 tcf_website/static/qa/sort_qa.js delete mode 100644 tcf_website/static/reviews/new_review.css delete mode 100644 tcf_website/static/reviews/pagination.css delete mode 100644 tcf_website/static/reviews/review.css delete mode 100644 tcf_website/static/reviews/review.js delete mode 100644 tcf_website/static/reviews/review_stats.css delete mode 100644 tcf_website/static/reviews/sort_reviews.js delete mode 100644 tcf_website/static/schedule/parse_time.js delete mode 100644 tcf_website/static/schedule/schedule.css delete mode 100644 tcf_website/static/schedule/schedule_editor.css delete mode 100644 tcf_website/static/schedule/schedule_select_script.js delete mode 100644 tcf_website/static/schedule/schedule_with_sections.css delete mode 100644 tcf_website/static/search/filters.js delete mode 100644 tcf_website/static/search/search.css delete mode 100644 tcf_website/static/search/searchbar.css delete mode 100644 tcf_website/templates/about/about.html rename tcf_website/templates/about/{privacy.html => partials/privacy_content.html} (96%) rename tcf_website/templates/about/{terms.html => partials/terms_content.html} (96%) delete mode 100644 tcf_website/templates/base/base.html delete mode 100644 tcf_website/templates/base/bugform.html delete mode 100644 tcf_website/templates/base/index.html delete mode 100644 tcf_website/templates/base/messages.html delete mode 100644 tcf_website/templates/base/navbar.html delete mode 100644 tcf_website/templates/base/sidebar.html delete mode 100644 tcf_website/templates/browse/browse.html delete mode 100644 tcf_website/templates/browse/school.html delete mode 100644 tcf_website/templates/club/browse_category.html delete mode 100644 tcf_website/templates/club/category.html delete mode 100644 tcf_website/templates/club/club.html delete mode 100644 tcf_website/templates/club/mode_toggle.html delete mode 100644 tcf_website/templates/common/adblock_modal.html delete mode 100644 tcf_website/templates/common/application_banner.html delete mode 100644 tcf_website/templates/common/coming_soon_modal.html delete mode 100644 tcf_website/templates/common/donate_page_modal.html delete mode 100644 tcf_website/templates/common/history_page_modal.html delete mode 100644 tcf_website/templates/common/leaderboard_ad.html delete mode 100644 tcf_website/templates/common/login-fix-banner.html delete mode 100644 tcf_website/templates/common/marketing_application_banner.html delete mode 100644 tcf_website/templates/common/notification.html delete mode 100644 tcf_website/templates/common/pagination.html delete mode 100644 tcf_website/templates/common/qa_form_banner.html delete mode 100644 tcf_website/templates/common/rating_card.html delete mode 100644 tcf_website/templates/common/review-drive-banner.html delete mode 100644 tcf_website/templates/common/toolbar.html delete mode 100644 tcf_website/templates/course/course.html delete mode 100644 tcf_website/templates/course/course_professor.html delete mode 100644 tcf_website/templates/department/department.html delete mode 100644 tcf_website/templates/department/department_course.html delete mode 100644 tcf_website/templates/instructor/instructor.html delete mode 100644 tcf_website/templates/landing/_faqs.json delete mode 100644 tcf_website/templates/landing/dashboard.html delete mode 100644 tcf_website/templates/landing/landing.html delete mode 100644 tcf_website/templates/login/login_modal.html delete mode 100644 tcf_website/templates/profile/delete_confirm_modal.html delete mode 100644 tcf_website/templates/profile/profile.html delete mode 100644 tcf_website/templates/qa/delete_answer_confirm_modal.html delete mode 100644 tcf_website/templates/qa/delete_question_confirm_modal.html delete mode 100644 tcf_website/templates/qa/qa.html delete mode 100644 tcf_website/templates/reviews/delete_confirm_modal.html delete mode 100644 tcf_website/templates/reviews/duplicate_review_modal.html delete mode 100644 tcf_website/templates/reviews/new_review.html delete mode 100644 tcf_website/templates/reviews/review.html delete mode 100644 tcf_website/templates/reviews/review_form_content.html delete mode 100644 tcf_website/templates/reviews/review_stats.html delete mode 100644 tcf_website/templates/reviews/reviews.html delete mode 100644 tcf_website/templates/reviews/user_reviews.html delete mode 100644 tcf_website/templates/reviews/zero_hours_modal.html delete mode 100644 tcf_website/templates/schedule/add_course_modal.html delete mode 100644 tcf_website/templates/schedule/create_schedule_modal.html delete mode 100644 tcf_website/templates/schedule/delete_schedule_modal.html delete mode 100644 tcf_website/templates/schedule/edit_schedule_modal.html delete mode 100644 tcf_website/templates/schedule/schedule.html delete mode 100644 tcf_website/templates/schedule/schedule_builder.html delete mode 100644 tcf_website/templates/schedule/schedule_editor.html delete mode 100644 tcf_website/templates/schedule/schedule_with_sections.html delete mode 100644 tcf_website/templates/schedule/select_schedule_modal.html delete mode 100644 tcf_website/templates/schedule/user_schedules.html delete mode 100644 tcf_website/templates/search/search.html delete mode 100644 tcf_website/templates/search/searchbar.html create mode 100644 tcf_website/templates/site/base.html create mode 100644 tcf_website/templates/site/components/_about_member_card.html create mode 100644 tcf_website/templates/site/components/_about_member_section.html create mode 100644 tcf_website/templates/site/components/_course_card.html create mode 100644 tcf_website/templates/site/components/_footer.html create mode 100644 tcf_website/templates/site/components/_header.html create mode 100644 tcf_website/templates/site/components/_instructor_card.html create mode 100644 tcf_website/templates/site/components/_leaderboard_ad.html create mode 100644 tcf_website/templates/site/components/_messages.html create mode 100644 tcf_website/templates/site/components/_pagination.html create mode 100644 tcf_website/templates/site/components/_school_section.html create mode 100644 tcf_website/templates/site/components/_search_bar.html create mode 100644 tcf_website/templates/site/components/_section_card.html create mode 100644 tcf_website/templates/site/components/_weekly_calendar.html create mode 100644 tcf_website/templates/site/modals/_adblock.html create mode 100644 tcf_website/templates/site/modals/_login.html create mode 100644 tcf_website/templates/site/pages/about.html create mode 100644 tcf_website/templates/site/pages/browse.html create mode 100644 tcf_website/templates/site/pages/club.html create mode 100644 tcf_website/templates/site/pages/club_category.html create mode 100644 tcf_website/templates/site/pages/course.html create mode 100644 tcf_website/templates/site/pages/course_instructor.html create mode 100644 tcf_website/templates/site/pages/department.html create mode 100644 tcf_website/templates/site/pages/instructor.html create mode 100644 tcf_website/templates/site/pages/landing.html create mode 100644 tcf_website/templates/site/pages/privacy.html create mode 100644 tcf_website/templates/site/pages/profile.html create mode 100644 tcf_website/templates/site/pages/review.html create mode 100644 tcf_website/templates/site/pages/reviews.html create mode 100644 tcf_website/templates/site/pages/schedule.html create mode 100644 tcf_website/templates/site/pages/schedule_add_course.html create mode 100644 tcf_website/templates/site/pages/search.html create mode 100644 tcf_website/templates/site/pages/terms.html create mode 100644 tcf_website/templates/site/partials/privacy_content.html create mode 100644 tcf_website/templates/site/partials/terms_content.html create mode 100644 tcf_website/views/utils.py diff --git a/doc/dev.md b/doc/dev.md index 2413c88fc..aba7f74c2 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -1,6 +1,6 @@ # tCF Developer Info -Ensure your system has [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Docker](https://docs.docker.com/install/) installed. +Ensure your system has [Git](https://git-scm.com/book/en/Getting-Started-Installing-Git) and [Docker](https://docs.docker.com/install/) installed. ## Setup diff --git a/doc/parity_tracker.md b/doc/parity_tracker.md new file mode 100644 index 000000000..a93a2533e --- /dev/null +++ b/doc/parity_tracker.md @@ -0,0 +1,56 @@ +# UI Parity Tracker + +Last updated: 2026-02-07 + +## Scope +- Goal: full page-level and function-level parity between legacy templates and the current production templates. +- Out of scope: schedule-page course autocomplete for now. + +## Route Status +| Route | Status | Notes | +| --- | --- | --- | +| `/` | Done | Landing uses current template set. | +| `/about/` | Done | Team/contributors/sponsors/history tabs supported. | +| `/privacy/` | Done | Current template wired. | +| `/terms/` | Done | Current template wired. | +| `/browse/` | Done | Courses and clubs modes supported. | +| `/department//` | Done | Sorting, recency, pagination maintained. | +| `/course///` | Done | Instructor list, ratings/GPA, add-to-schedule preserved. | +| `/course///` | Done | Ratings, reviews, grade distribution, sections panel preserved. | +| `/instructor//` | Done | Aggregates and grouped course history preserved. | +| `/search/` | Done | Search grouping, pagination, mode handling preserved. | +| `/reviews/new/` | Partial | Full backend flow works; planned UI refinement remains. | +| `/reviews/` | Done | User stats, pagination, vote/delete actions preserved. | +| `/profile/` | Done | Edit and delete profile flows preserved. | +| `/schedule/` | Done | Builder list/detail/actions and weekly calendar available. | +| `/course//add-to-schedule/` | Done | Course-to-schedule flow with conflict/duplicate checks preserved. | +| `/club-category//` | Done | Category listing and pagination preserved. | +| `/club///` | Done | Club reviews, sorting, voting, add review supported. | + +## Feature Parity Checklist +- [x] Shared header/footer/nav behavior +- [x] Shared flash messages across all pages +- [x] Shared top leaderboard ad on all non-landing pages +- [x] Adblock detection via ad script `onerror` +- [x] Review voting parity (up/down/toggle) +- [x] Sort controls on instructor reviews without page-level UX regressions +- [x] Course and instructor metric cards aligned with current product decisions +- [x] Lecture/other section display rules and scroll behavior +- [x] Schedule conflict detection and duplicate prevention +- [x] Terms/privacy flow in current styling +- [ ] New review form UI redesign (deferred) +- [ ] Schedule-page course autocomplete (deferred) + +## QA Smoke Test Matrix +- [ ] Anonymous browse/search/course navigation +- [ ] Authenticated add review (course + club) +- [ ] Authenticated vote review (my reviews + instructor page) +- [ ] Profile edit and delete +- [ ] Schedule create, rename, duplicate, delete +- [ ] Add section to schedule + conflict path +- [ ] Remove scheduled course +- [ ] Club category and club detail review flow + +## Notes +- No backward-compatibility route layer is required. +- Canonical routes now serve the current template set. diff --git a/tcf_website/static/base/reset.css b/tcf_website/static/base/reset.css index 80e45b859..d72705dec 100644 --- a/tcf_website/static/base/reset.css +++ b/tcf_website/static/base/reset.css @@ -1,5 +1,5 @@ /* http://meyerweb.com/eric/tools/css/reset/ - v2.0-modified | 20110126 + 2.0-modified | 20110126 License: none (public domain) */ diff --git a/tcf_website/static/browse/browse.css b/tcf_website/static/browse/browse.css deleted file mode 100644 index aa2ac240e..000000000 --- a/tcf_website/static/browse/browse.css +++ /dev/null @@ -1,99 +0,0 @@ -/* Base styles */ -.browse.container { - max-width: 1200px; - margin: 0 auto; -} - -.schools { - margin-top: 30px; -} - -/* School card styling */ -.school { - border: none; - border-radius: 8px; - overflow: hidden; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); - transition: all 0.3s ease; - margin-bottom: 1.5rem; -} - -.school:hover { - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); - transform: translateY(-2px); -} - -.school > .card-header { - background-color: var(--secondary-color); - color: white; - border: none; - padding: 1rem 1.5rem; - cursor: pointer; - transition: all 0.3s ease; -} - -.school > .card-header:hover { - background-color: var(--main-color); -} - -.card-title { - font-weight: 500; - font-size: 1.25rem; -} - -.school-body { - padding: 1.5rem; - background-color: white; -} - -/* Department list styling */ -.department-list { - list-style-type: none; - padding: 0; - margin: 0; -} - -.department-list li { - padding: 0.5rem 0; - transition: all 0.3s ease; -} - -.department-list li:hover { - transform: translateX(5px); -} - -.department-list a { - color: var(--dark-color); - text-decoration: none; - display: inline-block; - font-weight: 400; - transition: all 0.3s ease; -} - -.department-list a:hover { - color: var(--accent-color); - font-weight: 500; -} - -.font-weight-bold { - margin-top: 20px; -} - -/* Chevron icon styling */ -.fa-chevron-down-white, .fa-chevron-up-white { - transition: all 0.3s ease; -} - -/* Responsive columns */ -@media (min-width: 768px) { - .department-list { - columns: 2; - column-gap: 2rem; - } -} - -@media (min-width: 992px) { - .department-list { - columns: 3; - } -} diff --git a/tcf_website/static/club/mode_toggle.css b/tcf_website/static/club/mode_toggle.css deleted file mode 100644 index 21521c11f..000000000 --- a/tcf_website/static/club/mode_toggle.css +++ /dev/null @@ -1,153 +0,0 @@ -/* Mode Toggle Styles - Link Version */ -.mode-toggle-container { - display: flex; - justify-content: flex-end; - max-width: 250px; -} - -.mode-toggle-wrapper { - position: relative; - display: flex; - background-color: #f0f0f0; - border-radius: 20px; - padding: 4px; - width: 100%; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); -} - -.mode-toggle-option { - flex: 1; - text-align: center; - padding: 8px 16px; - z-index: 1; - transition: color 0.3s ease; - font-weight: 500; - color: #555; - text-decoration: none; - font-size: 15px; - display: flex; - justify-content: center; - align-items: center; - min-width: 80px; -} - -.mode-toggle-option.active { - color: #fff; -} - -.mode-toggle-slider { - position: absolute; - top: 4px; - left: 4px; - width: calc(50% - 2px); - height: calc(100% - 8px); - background-color: #d75626; - border-radius: 16px; - transition: transform 0.3s ease; -} - -.mode-toggle-slider.slide-right { - transform: translateX(calc(100% - 4px)); -} - -.mode-toggle-option:hover { - text-decoration: none; - color: #d75626; -} - -.mode-toggle-option.active:hover { - color: #fff; -} - -/* Mode Toggle Styles - Radio Version */ -/* Main container for the radio toggle switch */ -.mode-toggle { - position: relative; - display: flex; - background-color: #f0f0f0; - border-radius: 4px; - border: 1px solid #cbd5e0; - height: 100%; - overflow: hidden; - min-width: 170px; -} - -/* Disable transition effects when no-transition class is applied */ -.mode-toggle.no-transition .toggle-indicator { - transition: none !important; -} - -/* Hide radio inputs visually while keeping them accessible to screen readers and keyboard navigation */ -.mode-toggle input[type="radio"] { - opacity: 0; - position: absolute; - width: 0; - height: 0; -} - -/* Style for the clickable labels that appear as toggle buttons */ -.mode-toggle .mode-label { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - padding: 0 12px; - cursor: pointer; - position: relative; - z-index: 1; - transition: color 0.3s; - margin: 0; - font-size: 14px; - font-weight: 500; - color: #555; - min-width: 80px; - text-align: center; -} - -/* Remove individual label focus styling */ -.mode-toggle input[type="radio"]:focus-visible + .mode-label { - outline: none; -} - -/* Add focus style to the entire toggle container when any radio input is focused */ -.mode-toggle:has(input[type="radio"]:focus-visible) { - outline: 2px solid #4d90fe; - outline-offset: 1px; -} - -/* Moving background element that highlights the selected option */ -.mode-toggle .toggle-indicator { - position: absolute; - top: 2px; - left: 2px; - width: calc(50% - 4px); - height: calc(100% - 4px); - background-color: #d75626; - transition: transform 0.3s ease; - border-radius: 3px; -} - -/* Move the indicator to the right when Clubs option is selected */ -#search-mode-clubs:checked ~ .toggle-indicator { - transform: translateX(105%); -} - -/* Change text color to white for the selected option */ -#search-mode-courses:checked ~ .mode-label[for="search-mode-courses"], -#search-mode-clubs:checked ~ .mode-label[for="search-mode-clubs"] { - color: white; -} - -/* Searchbar specific mode toggle adjustments */ -.search-mode-toggle .mode-toggle { - height: 100%; - display: flex; - align-items: stretch; -} - -/* Disabled state for filter button */ -#filter-button.disabled { - opacity: 0.6; - cursor: not-allowed; - pointer-events: none; -} diff --git a/tcf_website/static/club/mode_toggle.js b/tcf_website/static/club/mode_toggle.js deleted file mode 100644 index 1f8e3ac7f..000000000 --- a/tcf_website/static/club/mode_toggle.js +++ /dev/null @@ -1,61 +0,0 @@ -// Common mode toggle functionality -document.addEventListener("DOMContentLoaded", function () { - // For radio button toggle (searchbar) - const radioToggle = document.querySelector(".mode-toggle"); - if (radioToggle) { - const coursesRadio = document.getElementById("search-mode-courses"); - const clubsRadio = document.getElementById("search-mode-clubs"); - - // Abort early if the expected inputs are missing - if (!coursesRadio || !clubsRadio) { - console.error( - "mode_toggle.js: Could not find expected #search-mode-(courses|clubs) radios.", - ); - return; - } - - const filterButtonElement = document.getElementById("filter-button"); - - // Find search input element - const searchInput = document.querySelector( - 'input[type="search"][name="q"]', - ); - - // Update both filter button state and search placeholder - function updateSearchbarState() { - // Update filter button if it exists - if (filterButtonElement) { - if (clubsRadio.checked) { - filterButtonElement.disabled = true; - filterButtonElement.setAttribute("aria-disabled", "true"); - filterButtonElement.classList.add("disabled"); - } else { - filterButtonElement.disabled = false; - filterButtonElement.setAttribute("aria-disabled", "false"); - filterButtonElement.classList.remove("disabled"); - } - } - - // Update search input placeholder if it exists - if (searchInput) { - if (clubsRadio.checked) { - searchInput.placeholder = "Search for a club..."; - } else { - searchInput.placeholder = "Search for a class or professor..."; - } - } - } - - // Set initial state - updateSearchbarState(); - - // Listen for changes - coursesRadio.addEventListener("change", updateSearchbarState); - clubsRadio.addEventListener("change", updateSearchbarState); - - // Remove no-transition class after page load to enable animations - setTimeout(function () { - radioToggle.classList.remove("no-transition"); - }, 100); - } -}); diff --git a/tcf_website/static/common/form.js b/tcf_website/static/common/form.js deleted file mode 100644 index 6be5fc80f..000000000 --- a/tcf_website/static/common/form.js +++ /dev/null @@ -1,14 +0,0 @@ -// form validation -// parameter: eg. var form = document.getElementById("bugform"); -function validateForm(form) { - const valid = form.checkValidity(); - if (valid === false) { - event.preventDefault(); - event.stopPropagation(); - } - form.classList.add("was-validated"); - - return valid; -} - -export { validateForm }; diff --git a/tcf_website/static/common/rating_card.css b/tcf_website/static/common/rating_card.css deleted file mode 100644 index f71829946..000000000 --- a/tcf_website/static/common/rating_card.css +++ /dev/null @@ -1,22 +0,0 @@ -.rating-card-link { - background-color: var(--secondary-color); - color: white; -} - -.rating-card-link:hover { - background-color: var(--main-color); - color: var(--background-color); -} - -.rating-card, -.rating-card-link { - border-radius: 5px; -} - -.rating-card .info { - font-size: 1.25rem; -} - -small { - color: var(--accent-color); -} diff --git a/tcf_website/static/common/recently_viewed.js b/tcf_website/static/common/recently_viewed.js deleted file mode 100644 index a4926d152..000000000 --- a/tcf_website/static/common/recently_viewed.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * This file handles the recently viewed courses/instructors using localStorage - * It replaces the server-side session storage with client-side storage - */ - -document.addEventListener("DOMContentLoaded", function () { - // Check if we're on a course page by looking for course code and title in the page - saveCourseInfoIfPresent(); -}); - -/** - * Saves course information to localStorage if we're on a course or course-instructor page - */ -function saveCourseInfoIfPresent() { - // Find course information in the page - // This is adapting the session-based logic from browse.py and course_instructor views - const courseCode = document.querySelector( - 'meta[name="course-code"]', - )?.content; - const courseTitle = document.querySelector( - 'meta[name="course-title"]', - )?.content; - - if (!courseTitle) { - return; // Not on a course or club page, or missing required data - } - - // Get current URL - const currentUrl = window.location.href; - - // Create the title in the same format as the server-side code - - let title = ""; - - if (courseCode) { - title += courseCode + " | "; - } - - title += courseTitle; - - // Get existing history from localStorage - let previousPaths = []; - let previousPathsTitles = []; - - try { - const savedPaths = localStorage.getItem("previous_paths"); - const savedTitles = localStorage.getItem("previous_paths_titles"); - - if (savedPaths && savedTitles) { - previousPaths = JSON.parse(savedPaths); - previousPathsTitles = JSON.parse(savedTitles); - } - } catch (e) { - console.error("Error loading history from localStorage:", e); - } - - // Insert at beginning and remove duplicates - // First check if the title already exists in the array - const existingIndex = previousPathsTitles.indexOf(title); - if (existingIndex !== -1) { - // If the title exists, remove both the title and its corresponding path - previousPathsTitles.splice(existingIndex, 1); - previousPaths.splice(existingIndex, 1); - } - - // Add the new path and title at the beginning - previousPaths.unshift(currentUrl); - previousPathsTitles.unshift(title); - - // Keep only the top 10 items - if (previousPaths.length > 10) { - previousPaths = previousPaths.slice(0, 10); - previousPathsTitles = previousPathsTitles.slice(0, 10); - } - - // Save back to localStorage - localStorage.setItem("previous_paths", JSON.stringify(previousPaths)); - localStorage.setItem( - "previous_paths_titles", - JSON.stringify(previousPathsTitles), - ); -} diff --git a/tcf_website/static/common/show-hide.css b/tcf_website/static/common/show-hide.css deleted file mode 100644 index 5ef6fe530..000000000 --- a/tcf_website/static/common/show-hide.css +++ /dev/null @@ -1,13 +0,0 @@ -.old { - display: none; -} -.old.shown { - display: block; -} - -.new { - display: block; -} -.new.hide { - display: none; -} diff --git a/tcf_website/static/course/course_professor.css b/tcf_website/static/course/course_professor.css deleted file mode 100644 index d77c52fe7..000000000 --- a/tcf_website/static/course/course_professor.css +++ /dev/null @@ -1,524 +0,0 @@ -/* Instructor info row */ -.course-instructor-header .instructor-info .instructor-select { - font-size: 28px; -} -@media (max-width: 900px) { - .course-instructor-header .instructor-info .instructor-select { - font-size: 22px; - } -} -.course-instructor-header .instructor-menu { - box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.2); -} -/* Change tooltip alignment */ -@media (min-width: 768px) { - .tooltip-inner { - margin-left: 8.15rem; - } -} - -.course-info-container { - width: 100%; - margin-bottom: 50px; - margin-top: 30px; - display: flex; - flex-direction: column; - row-gap: 10px; -} - -.times-info-container { - width: 100%; - display: flex; - margin-top: 10px; -} - -.grades-ratings-container { - width: 100%; - height: 300px; - display: flex; - gap: 10px; -} - -.grades-container { - position: relative; - width: 45%; - height: 100%; - flex-shrink: 1; - display: flex; - align-items: center; - justify-content: center; - background-color: white; - z-index: 0; -} - -.chart-container { - position: relative; - margin-top: none; - height: 80vh; - width: 80vw; -} - -.chart-button { - position: absolute; - bottom: 10px; - right: 10px; - display: flex; -} - -.ratings-container { - height: 100%; - display: flex; - flex-grow: 1; - flex-direction: column; - gap: 10px; -} - -.ratings-row { - display: flex; - height: 50%; -} - -.rating-card { - height: 100%; - background-color: white; - width: 200px; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; -} - -.rating-card-num { - font-size: 42px; - color: #d75626; -} - -.rating-card-desc { - font-size: 18px; -} - -.info-container { - display: flex; - flex-direction: column; - flex-grow: 1; - background-color: white; - height: 100%; - margin-left: 10px; - padding-top: 6px; - padding-bottom: 6px; - padding-left: 8px; - justify-content: space-around; - min-width: 250px; -} - -.absolute-center { - position: absolute; - top: auto; - left: auto; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; -} - -.bottom-center { - position: absolute; - bottom: 10px; - left: auto; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; -} - -.top-center { - position: absolute; - top: 10px; - left: auto; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; -} - -.gpa-text { - color: #384676; - font-size: 28px; - margin: 0; -} - -.students-text { - color: #56679d; - font-size: 16px; - margin: 0; -} - -.info-row { - display: flex; - align-items: center; - justify-content: space-between; -} - -.info-title { - font-size: 14px; - width: 130px; - padding-left: 5px; - display: flex; - justify-content: flex-start; - align-items: center; -} - -.info-bar .progress { - margin: 0; - padding: 0; -} -.info-bar .progress-bar { - margin: 0; - padding: 0; -} - -.info-num { - display: flex; - justify-content: flex-end; - width: 70px; - padding-right: 10px; -} - -.info-bar { - flex-grow: 1; - flex-shrink: 1; -} - -@media (max-width: 1100px) { - .grades-ratings-container { - flex-direction: column; - height: 650px; - } - - .grades-container { - height: 50%; - width: 100%; - } - - .ratings-container { - height: 50%; - } - - .info-container { - margin-right: 0; - } - - .gpa-text { - font-size: 24px; - } - .students-text { - font-size: 14; - } -} - -@media (max-width: 800px) { - .info-num { - width: 60px; - } -} - -@media (max-width: 500px) { - .ratings-container { - height: 200px; - } - .ratings-container { - height: 38%; - } - .grades-container { - height: 62%; - } - .rating-card-num { - font-size: 24px; - } - .rating-card-desc { - font-size: 14px; - } - .info-title { - font-size: 12px; - width: 120px; - } - .info-num { - font-size: 14px; - width: 30; - } - .progress { - height: 14px; - } -} - -@media (max-width: 410px) { - .grades-ratings-container { - height: 400px; - } - - .grades-container { - height: 50%; - } - - .ratings-container { - height: 50%; - } - .rating-card-num { - font-size: 16px; - } - .rating-card-desc { - font-size: 10px; - } - .info-title { - font-size: 10px; - width: 90; - } - .info-container { - font-size: 10px; - } - .info-num { - font-size: 10px; - } - .progress { - height: 10px; - } - - .gpa-text { - font-size: 18px; - } - .students-text { - font-size: 12px; - } - .info-container { - width: 250px; - flex-shrink: 1; - min-width: 0; - } - .rating-card { - width: 70px; - min-width: 80px; - flex-grow: 1; - flex-shrink: 1; - } - .grades-container { - margin-bottom: 0; - } -} - -@media (max-width: 380px) { - .info-bar { - width: 50%; - flex-shrink: 2; - margin-right: 0; - padding-right: 0; - } - .info-num { - margin-left: 2; - } -} - -.rotated { - transform: rotateZ(90deg); -} - -#sectionInfo { - max-width: 100%; - overflow-x: hidden; - padding-top: 4px; -} - -/* Enrollment progress bar */ -@media (max-width: 1200px) { - .enrollment-progress, - .waitlist-progress { - width: 30px !important; - } -} -@media (max-width: 1024px) { - .enrollment-info { - display: none !important; - } - .times-info-container .grid { - grid-template-columns: [start] 6ch [sec-num] 150px [type] 1fr [end] !important; - } - .section-row { - grid-template-columns: [start] 6ch [sec-num] 150px [type] 1fr [end]; - } -} - -.enrollment-info { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; -} - -.enrollment-wrapper { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - flex: 1; - margin: 0 60px; -} - -.enrollment-metrics { - display: flex; - gap: 30px; - flex: 1; - max-width: 700px; - margin: 0 auto; -} - -.enrollment-metric { - display: flex; - align-items: center; - gap: 8px; - flex: 1; - justify-content: flex-end; -} - -.metric-label { - font-size: 14px; - white-space: nowrap; - width: 85px; - text-align: right; -} - -.enrollment-progress, -.waitlist-progress { - width: 100px; - height: 8px; - margin: 0; -} - -.metric-text { - font-size: 14px; - white-space: nowrap; - width: 70px; - text-align: left; -} - -.waitlist-bar { - background-color: #ffc107; -} - -/* Section styles */ -.sections-container { - width: 100%; - background-color: white; - padding: 10px 15px; - border-radius: 6px; -} - -.sections-header { - margin-bottom: 15px; -} - -.section-count { - background-color: var(--tcf-blue, #384676); - color: white; - padding: 2px 8px; - border-radius: 12px; - font-size: 12px; - margin-left: 7px; - font-weight: 500; -} - -.section-row { - display: grid; - grid-template-columns: - [start] 6ch [sec-num] 150px [type] minmax(200px, 1fr) [times] minmax( - 120px, - auto - ) - [end]; - gap: 16px; - margin-bottom: 8px; - align-items: center; - padding: 12px 16px; - background-color: white; - border-radius: 6px; - transition: all 0.2s ease; - border: 1px solid transparent; - cursor: pointer; - text-decoration: none; - color: inherit; -} - -.section-row:last-child { - margin-bottom: 0; -} - -.section-row:hover { - transform: translateY(-1px); - border-color: var(--tcf-blue, #384676); - box-shadow: 0 2px 4px rgba(56, 70, 118, 0.08); - text-decoration: none; - color: inherit; -} - -.section-row > div { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-size: 14px; - line-height: 1.4; -} - -.section-row > div:first-child { - font-weight: 500; - color: var(--tcf-blue, #384676); -} - -.section-row > div:last-child { - text-align: right; - color: #666; -} - -/* Section number accent color */ -.section-number { - color: var(--tcf-orange, #d75626); - font-weight: 500; -} - -/* Section times right-aligned */ -.section-times { - text-align: right; - color: #666; -} - -/* Navigation tabs */ -.nav-tabs .nav-link { - font-size: 18px; -} - -/* Show more sections link */ -.show-more-sections { - display: block; - padding: 12px 16px; - text-align: center; - color: var(--tcf-blue, #384676); - font-weight: 500; - cursor: pointer; - border: 1px dashed var(--tcf-blue, #384676); - border-radius: 6px; - margin-top: 8px; - transition: all 0.2s ease; - text-decoration: none; -} - -.show-more-sections:hover { - background-color: rgba(56, 70, 118, 0.05); - text-decoration: none; - color: var(--tcf-blue, #384676); -} - -/* Hidden sections - first 3 shown by default */ -.sections-list .section-row:nth-child(n+4) { - display: none; -} - -.sections-list.expanded .section-row { - display: grid; -} diff --git a/tcf_website/static/course/javascript/course_data.js b/tcf_website/static/course/javascript/course_data.js deleted file mode 100644 index e898f0a60..000000000 --- a/tcf_website/static/course/javascript/course_data.js +++ /dev/null @@ -1,329 +0,0 @@ -let barConfig; -let pieConfig; -let myChart; -const ctx = document.getElementById("myChart"); - -function togglePieChart() { - if (myChart) { - myChart.destroy(); - } - pieConfig.options.plugins.legend.display = false; - // eslint-disable-next-line no-new,no-undef - myChart = new Chart(ctx, pieConfig); -} - -function toggleBarChart() { - if (myChart) { - myChart.destroy(); - } - barConfig.options.plugins.legend.display = false; - // eslint-disable-next-line no-new,no-undef - myChart = new Chart(ctx, barConfig); -} - -$(".pieToBar").click(function () { - if (document.getElementById("toggle-btn").value === "bar") { - toggleBarChart(); - document.getElementById("chart-label").className = "bottom-center"; - document.getElementById("grades-container").style.paddingBottom = "3em"; - document.getElementById("toggle-btn").innerHTML = "Pie"; - document.getElementById("toggle-btn").value = "pie"; - } else { - togglePieChart(); - document.getElementById("chart-label").className = "absolute-center"; - document.getElementById("grades-container").style.paddingBottom = "0em"; - document.getElementById("toggle-btn").innerHTML = "Bar"; - document.getElementById("toggle-btn").value = "bar"; - } -}); - -const loadData = (data) => { - // order in the input data - /* eslint-disable camelcase */ - const { a_plus, a, a_minus, b_plus, b, b_minus, c_plus, c, c_minus, dfw } = - data; - // order we want for the pie chart - const grades_data = [ - a_plus, - a, - a_minus, - b_plus, - b, - b_minus, - c_plus, - c, - c_minus, - dfw, - ]; - - // Create default pie chart - /* eslint-enable camelcase */ - createChart(grades_data); - - const formatWorkload = (x) => `${(100 * x) / data.average_hours_per_week}%`; - // Subtracting 0.8 from both the numerators and the denominator - // so that the rating of 1 does look like a low one - const formatRating = (x) => `${(100 * (x - 0.8)) / (5 - 0.8)}%`; - - // Change display if there is no data - if (exist(data.average_gpa)) { - $(".gpa-text").html( - data.average_gpa === 0.0 ? "Pass/Fail" : `${data.average_gpa} GPA`, - ); - } - if (exist(data.total_enrolled)) { - $(".students-text").html(`${data.total_enrolled} Students`); - } else { - $(".students-text").remove(); - $(".chart-button").remove(); - } - - // Summary numbers - if (exist(data.average_rating)) { - $(".rating-num").html(data.average_rating); - } - if (exist(data.average_hours_per_week)) { - $(".hours-num").html(data.average_hours_per_week); - } - // Rating numbers - if (exist(data.average_instructor)) { - $(".instructor-num").html(data.average_instructor); - } - if (exist(data.average_fun)) { - $(".fun-num").html(data.average_fun); - } - if (exist(data.average_difficulty)) { - $(".difficulty-num").html(data.average_difficulty); - } - if (exist(data.average_recommendability)) { - $(".recommend-num").html(data.average_recommendability); - } - // Workload numbers - if (exist(data.average_amount_reading)) { - $(".reading-num").html(data.average_amount_reading); - } - if (exist(data.average_amount_writing)) { - $(".writing-num").html(data.average_amount_writing); - } - if (exist(data.average_amount_group)) { - $(".group-num").html(data.average_amount_group); - } - if (exist(data.average_amount_homework)) { - $(".homework-num").html(data.average_amount_homework); - } - - // Rating bars - if (exist(data.average_instructor)) { - $(".instructor-bar").width(formatRating(data.average_instructor)); - } - if (exist(data.average_fun)) { - $(".fun-bar").width(formatRating(data.average_fun)); - } - if (exist(data.average_difficulty)) { - $(".difficulty-bar").width(formatRating(data.average_difficulty)); - } - if (exist(data.average_recommendability)) { - $(".recommend-bar").width(formatRating(data.average_recommendability)); - } - // Workload bars - if (exist(data.average_amount_reading)) { - $(".reading-bar").width(formatWorkload(data.average_amount_reading)); - } - if (exist(data.average_amount_writing)) { - $(".writing-bar").width(formatWorkload(data.average_amount_writing)); - } - if (exist(data.average_amount_group)) { - $(".group-bar").width(formatWorkload(data.average_amount_group)); - } - if (exist(data.average_amount_homework)) { - $(".homework-bar").width(formatWorkload(data.average_amount_homework)); - } -}; - -const createChart = (gradesData) => { - const chartData = { - datasets: [ - { - data: gradesData, - backgroundColor: [ - "#182D66", // A+ - "#274F97", // A - "#4467B6", // A- - "#5D83D1", // B+ - "#6E98E4", // B - "#8FA9DF", // B- - "#DAA38E", // C+ - "#DD734C", // C - "#D75626", // C- - "#BE4B20", // DFW - ], - }, - ], - labels: ["A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "DFW"], - }; - - // Generate configuration for Bar Chart with chartData - barConfig = { - type: "bar", - data: chartData, - options: { - layout: { - padding: { - bottom: 20, - top: 25, - }, - }, - responsive: true, - scales: { - xAxes: [ - { - ticks: { - autoSkip: false, - }, - gridLines: { - drawOnChartArea: false, - }, - }, - ], - yAxes: [ - { - scaleLabel: { - display: true, - labelString: "Number of Students", - }, - ticks: { - beginAtZero: true, - }, - gridLines: { - drawOnChartArea: true, - }, - }, - ], - }, - plugins: { - legend: { - display: false, - }, - labels: { - // render 'label', 'value', 'percentage', 'image' or custom function, default is 'percentage' - render: "value", - - // font size, default is defaultFontSize - fontSize: 10, - - // font color, can be color array for each data or function for dynamic color, default is defaultFontColor - // fontColor: "#fff", - - // font style, default is defaultFontStyle - fontStyle: "normal", - - // draw label in arc, default is false - // bar chart ignores this - arc: false, - - // position to draw label, available value is 'default', 'border' and 'outside' - // bar chart ignores this - // default is 'default' - position: "default", - - // draw label even it's overlap, default is true - // bar chart ignores this - overlap: false, - - // add margin of text when position is `outside` or `border` - // default is 2 - textMargin: 2, - }, - }, - }, - }; - // eslint-disable-next-line no-new,no-undef - Chart.register(ChartDataLabels); - - pieConfig = { - type: "doughnut", - data: chartData, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - display: false, - }, - datalabels: { - color: "#fff", - formatter: (value, context) => { - const dataset = context.chart.data.datasets[0]; - const total = dataset.data.reduce((acc, num) => acc + num, 0); - const percentage = (value / total) * 100; - return percentage > 5 - ? context.chart.data.labels[context.dataIndex] - : ""; - }, - font: { - size: 14, - }, - anchor: "center", - align: "center", - }, - tooltip: { - callbacks: { - label: function (tooltipItem) { - const dataset = tooltipItem.dataset; - const total = dataset.data.reduce((acc, num) => acc + num, 0); - const value = dataset.data[tooltipItem.dataIndex]; - const percent = ((value / total) * 100).toFixed(1); - return `${value} (${percent}%)`; - }, - }, - displayColors: false, - }, - labels: { - // render 'label', 'value', 'percentage', 'image' or custom function, default is 'percentage' - render: "label", - - // font size, default is defaultFontSize - fontSize: 12, - - // font color, can be color array for each data or function for dynamic color, default is defaultFontColor - fontColor: "#fff", - - // font style, default is defaultFontStyle - fontStyle: "normal", - - // draw label in arc, default is false - // bar chart ignores this - arc: false, - - // position to draw label, available value is 'default', 'border' and 'outside' - // bar chart ignores this - // default is 'default' - position: "default", - - // draw label even it's overlap, default is true - // bar chart ignores this - overlap: false, - - // add margin of text when position is `outside` or `border` - // default is 2 - textMargin: 4, - }, - }, - }, - }; - // 1. Justification for no-new: (Do not use 'new' for side effects) - // Without disabling the warning, eslint complains about using `new` to produce side-effects. - // (Which is how chart.js works. We can't change that.) - // You can silence it by assigning the expression to a variable. But then, eslint complains that we have an unused variable. - // We're not going to be able to avoid this, so I've disabled the error. - // 2. Justification for no-undef: ('Chart' is not defined) - // We could avoid this in the future by using WebPack or plain old ES6 modules. - // But right now, the chart.js source is referenced in the templates themselves through a CDN, - // so eslint will always complain. We'll just silence it. - // eslint-disable-next-line no-new,no-undef - myChart = new Chart(ctx, pieConfig); -}; - -const exist = (data) => { - return data !== null && data !== undefined; -}; -export { loadData }; diff --git a/tcf_website/static/css/site/base.css b/tcf_website/static/css/site/base.css new file mode 100644 index 000000000..a5ca1b6ea --- /dev/null +++ b/tcf_website/static/css/site/base.css @@ -0,0 +1,226 @@ +/* + * Base Styles + * =========== + * Default styling for HTML elements using design tokens. + */ + +/* ===== ROOT ===== */ +html { + font-size: 16px; +} + +body { + font-family: var(--font-sans); + font-size: var(--text-base); + font-weight: var(--font-normal); + color: var(--fg); + background-color: var(--bg); + transition: background-color var(--duration-normal) var(--ease-default), + color var(--duration-normal) var(--ease-default); +} + + +/* ===== TYPOGRAPHY ===== */ + +h1, h2, h3, h4, h5, h6 { + font-weight: var(--font-semibold); + color: var(--fg); + letter-spacing: var(--tracking-tight); +} + +h1 { + font-size: var(--text-4xl); + line-height: var(--leading-tight); +} + +h2 { + font-size: var(--text-3xl); + line-height: var(--leading-tight); +} + +h3 { + font-size: var(--text-2xl); + line-height: var(--leading-snug); +} + +h4 { + font-size: var(--text-xl); + line-height: var(--leading-snug); +} + +h5 { + font-size: var(--text-lg); + line-height: var(--leading-snug); +} + +h6 { + font-size: var(--text-base); + line-height: var(--leading-normal); +} + +p { + color: var(--fg); + line-height: var(--leading-relaxed); +} + +/* Display heading (serif) */ +.display { + font-family: var(--font-display); + font-weight: var(--font-normal); + letter-spacing: var(--tracking-tight); +} + +.display-xl { + font-family: var(--font-display); + font-size: var(--text-6xl); + font-weight: var(--font-normal); + line-height: var(--leading-none); + letter-spacing: var(--tracking-tight); +} + +/* Muted text */ +.text-muted { + color: var(--fg-muted); +} + +.text-subtle { + color: var(--fg-subtle); +} + +/* Text sizes */ +.text-xs { font-size: var(--text-xs); } +.text-sm { font-size: var(--text-sm); } +.text-base { font-size: var(--text-base); } +.text-lg { font-size: var(--text-lg); } +.text-xl { font-size: var(--text-xl); } +.text-2xl { font-size: var(--text-2xl); } +.text-3xl { font-size: var(--text-3xl); } +.text-4xl { font-size: var(--text-4xl); } + +/* Font weights */ +.font-normal { font-weight: var(--font-normal); } +.font-medium { font-weight: var(--font-medium); } +.font-semibold { font-weight: var(--font-semibold); } +.font-bold { font-weight: var(--font-bold); } + + +/* ===== LINKS ===== */ + +a { + color: var(--primary); + text-decoration: none; + transition: color var(--duration-fast) var(--ease-default); +} + +a:hover { + color: var(--primary-hover); + text-decoration: underline; +} + +/* Unstyled link */ +.link-unstyled { + color: inherit; + text-decoration: none; +} + +.link-unstyled:hover { + color: inherit; + text-decoration: none; +} + +/* Subtle link (muted until hover) */ +.link-subtle { + color: var(--fg-muted); +} + +.link-subtle:hover { + color: var(--fg); +} + + +/* ===== LISTS ===== */ + +ul, ol { + padding-left: var(--space-6); +} + +li { + margin-bottom: var(--space-2); +} + +/* Remove list styles */ +.list-none { + list-style: none; + padding-left: 0; +} + + +/* ===== CODE ===== */ + +code { + font-family: var(--font-mono); + font-size: 0.875em; + background-color: var(--bg-muted); + padding: var(--space-0-5) var(--space-1-5); + border-radius: var(--radius-sm); +} + +pre { + font-family: var(--font-mono); + font-size: var(--text-sm); + background-color: var(--bg-muted); + padding: var(--space-4); + border-radius: var(--radius-lg); + overflow-x: auto; +} + +pre code { + background: none; + padding: 0; +} + + +/* ===== HORIZONTAL RULE ===== */ + +hr { + border: none; + border-top: 1px solid var(--border); + margin: var(--space-8) 0; +} + + +/* ===== SELECTION ===== */ + +::selection { + background-color: var(--primary); + color: var(--primary-fg); +} + + +/* ===== PLACEHOLDER ===== */ + +::placeholder { + color: var(--fg-subtle); + opacity: 1; +} + + +/* ===== SCROLLBAR (WebKit) ===== */ + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-muted); +} + +::-webkit-scrollbar-thumb { + background: var(--fg-subtle); + border-radius: var(--radius-full); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--fg-muted); +} diff --git a/tcf_website/static/css/site/components/button.css b/tcf_website/static/css/site/components/button.css new file mode 100644 index 000000000..ea8f6acd1 --- /dev/null +++ b/tcf_website/static/css/site/components/button.css @@ -0,0 +1,222 @@ +/* + * Button Component + * ================ + */ + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + + font-family: var(--font-sans); + font-weight: var(--font-medium); + text-decoration: none; + white-space: nowrap; + + border-radius: var(--radius-lg); + transition: all var(--duration-fast) var(--ease-default); + cursor: pointer; +} + +.btn:focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + + +/* ===== SIZES ===== */ + +.btn--sm { + height: 2rem; + padding: 0 var(--space-3); + font-size: var(--text-sm); +} + +.btn--md { + height: 2.5rem; + padding: 0 var(--space-4); + font-size: var(--text-sm); +} + +.btn--lg { + height: 3rem; + padding: 0 var(--space-6); + font-size: var(--text-base); +} + +.btn--xl { + height: 3.5rem; + padding: 0 var(--space-8); + font-size: var(--text-lg); +} + + +/* ===== VARIANTS ===== */ + +/* Primary - main actions */ +.btn--primary { + background-color: var(--primary); + color: var(--primary-fg); + border: 1px solid transparent; +} + +.btn--primary:hover { + background-color: var(--primary-hover); +} + +/* Accent - highlighted actions */ +.btn--accent { + background-color: var(--accent); + color: var(--accent-fg); + border: 1px solid transparent; +} + +.btn--accent:hover { + background-color: var(--accent-hover); +} + +/* Secondary - outlined style */ +.btn--secondary { + background-color: transparent; + color: var(--fg); + border: 1px solid var(--border); +} + +.btn--secondary:hover { + background-color: var(--bg-hover); + border-color: var(--fg-subtle); +} + +/* Ghost - minimal style */ +.btn--ghost { + background-color: transparent; + color: var(--fg); + border: 1px solid transparent; +} + +.btn--ghost:hover { + background-color: var(--bg-hover); +} + +/* Danger - destructive actions */ +.btn--danger { + background-color: var(--color-danger-600); + color: var(--color-white); + border: 1px solid transparent; +} + +.btn--danger:hover { + background-color: var(--color-danger-500); +} + +/* Link style - looks like a link */ +.btn--link { + background-color: transparent; + color: var(--primary); + border: none; + padding: 0; + height: auto; +} + +.btn--link:hover { + text-decoration: underline; +} + + +/* ===== ICON BUTTONS ===== */ + +.btn--icon { + padding: 0; + aspect-ratio: 1; +} + +.btn--icon.btn--sm { + width: 2rem; +} + +.btn--icon.btn--md { + width: 2.5rem; +} + +.btn--icon.btn--lg { + width: 3rem; +} + + +/* ===== FULL WIDTH ===== */ + +.btn--full { + width: 100%; +} + + +/* ===== LOADING STATE ===== */ + +.btn--loading { + position: relative; + color: transparent; + pointer-events: none; +} + +.btn--loading::after { + content: ''; + position: absolute; + width: 1em; + height: 1em; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: btn-spin 0.6s linear infinite; +} + +.btn--primary.btn--loading::after, +.btn--accent.btn--loading::after, +.btn--danger.btn--loading::after { + border-color: var(--color-white); + border-right-color: transparent; +} + +@keyframes btn-spin { + to { + transform: rotate(360deg); + } +} + + +/* ===== BUTTON GROUP ===== */ + +.btn-group { + display: inline-flex; +} + +.btn-group .btn { + border-radius: 0; +} + +.btn-group .btn:first-child { + border-top-left-radius: var(--radius-lg); + border-bottom-left-radius: var(--radius-lg); +} + +.btn-group .btn:last-child { + border-top-right-radius: var(--radius-lg); + border-bottom-right-radius: var(--radius-lg); +} + +.btn-group .btn:not(:first-child) { + margin-left: -1px; +} + +.btn-group .btn--secondary.active, +.btn-group .btn--secondary:hover { + background-color: var(--bg-muted); + border-color: var(--fg-subtle); + z-index: 1; +} diff --git a/tcf_website/static/css/site/components/calendar.css b/tcf_website/static/css/site/components/calendar.css new file mode 100644 index 000000000..1282a68fb --- /dev/null +++ b/tcf_website/static/css/site/components/calendar.css @@ -0,0 +1,184 @@ +.weekly-calendar { + border: 1px solid var(--border); + border-radius: var(--radius-xl); + background-color: var(--bg-elevated); + overflow: hidden; +} + +.weekly-calendar--empty { + padding: var(--space-6); +} + +.weekly-calendar__empty-text { + margin: 0; + color: var(--fg-muted); + font-size: var(--text-sm); +} + +.weekly-calendar__header { + display: grid; + grid-template-columns: 4.5rem repeat(5, minmax(0, 1fr)); + border-bottom: 1px solid var(--border); + background-color: var(--bg-muted); +} + +.weekly-calendar__time-header, +.weekly-calendar__day-header { + padding: var(--space-3) var(--space-2); + font-size: var(--text-xs); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + color: var(--fg-muted); + font-weight: var(--font-semibold); +} + +.weekly-calendar__day-header { + text-align: center; + border-left: 1px solid var(--border); +} + +.weekly-calendar__schedule { + display: grid; + grid-template-columns: 4.5rem repeat(5, minmax(0, 1fr)); +} + +.weekly-calendar__time-scale { + min-height: 32rem; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3); +} + +.weekly-calendar__time-label { + font-size: var(--text-xs); + color: var(--fg-muted); + line-height: 1; +} + +.weekly-calendar__day-column { + position: relative; + min-height: 32rem; + border-left: 1px solid var(--border); +} + +.weekly-calendar__day-track { + position: absolute; + inset: 0; + background-image: repeating-linear-gradient( + to bottom, + transparent 0, + transparent calc((100% / var(--calendar-slots)) - 1px), + var(--border) calc((100% / var(--calendar-slots)) - 1px), + var(--border) calc(100% / var(--calendar-slots)) + ); +} + +.weekly-calendar__events { + position: absolute; + inset: 0; + padding: var(--space-2); +} + +.weekly-calendar__event { + position: absolute; + left: var(--space-2); + right: var(--space-2); + border-radius: var(--radius-md); + border: 1px solid transparent; + padding: 0.3rem 0.4rem; + text-decoration: none; + display: flex; + flex-direction: column; + gap: 0.1rem; + overflow: hidden; +} + +.weekly-calendar__event:hover { + border-color: currentColor; + text-decoration: none; +} + +.weekly-calendar__event-title { + font-size: 0.72rem; + font-weight: var(--font-semibold); + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.weekly-calendar__event-subtitle, +.weekly-calendar__event-time { + font-size: 0.65rem; + line-height: 1.2; + opacity: 0.92; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.weekly-calendar__event--tone-1 { + background-color: rgba(37, 99, 235, 0.15); + color: #1d4ed8; +} + +.weekly-calendar__event--tone-2 { + background-color: rgba(5, 150, 105, 0.15); + color: #047857; +} + +.weekly-calendar__event--tone-3 { + background-color: rgba(217, 119, 6, 0.16); + color: #b45309; +} + +.weekly-calendar__event--tone-4 { + background-color: rgba(124, 58, 237, 0.14); + color: #6d28d9; +} + +.weekly-calendar__event--tone-5 { + background-color: rgba(220, 38, 38, 0.13); + color: #b91c1c; +} + +.weekly-calendar__event--tone-6 { + background-color: rgba(14, 116, 144, 0.14); + color: #0e7490; +} + +[data-theme="dark"] .weekly-calendar__event--tone-1 { + color: #93c5fd; +} + +[data-theme="dark"] .weekly-calendar__event--tone-2 { + color: #6ee7b7; +} + +[data-theme="dark"] .weekly-calendar__event--tone-3 { + color: #fcd34d; +} + +[data-theme="dark"] .weekly-calendar__event--tone-4 { + color: #c4b5fd; +} + +[data-theme="dark"] .weekly-calendar__event--tone-5 { + color: #fca5a5; +} + +[data-theme="dark"] .weekly-calendar__event--tone-6 { + color: #67e8f9; +} + +@media (max-width: 1024px) { + .weekly-calendar { + overflow-x: auto; + } + + .weekly-calendar__header, + .weekly-calendar__schedule { + min-width: 52rem; + } +} diff --git a/tcf_website/static/css/site/components/dropdown.css b/tcf_website/static/css/site/components/dropdown.css new file mode 100644 index 000000000..2ca651979 --- /dev/null +++ b/tcf_website/static/css/site/components/dropdown.css @@ -0,0 +1,132 @@ +/* + * Dropdown Component + * ================== + */ + +.dropdown { + position: relative; + display: inline-block; +} + + +/* ===== TRIGGER ===== */ + +.dropdown__trigger { + display: inline-flex; + align-items: center; + gap: var(--space-2); + cursor: pointer; +} + +.dropdown__trigger svg { + width: 1rem; + height: 1rem; + transition: transform var(--duration-fast) var(--ease-default); +} + +.dropdown.is-open .dropdown__trigger svg { + transform: rotate(180deg); +} + + +/* ===== MENU ===== */ + +.dropdown__menu { + position: absolute; + top: calc(100% + var(--space-1)); + left: 0; + + min-width: 12rem; + padding: var(--space-1); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + + z-index: var(--z-dropdown); + + opacity: 0; + visibility: hidden; + transform: translateY(-4px); + transition: opacity var(--duration-fast) var(--ease-default), + visibility var(--duration-fast) var(--ease-default), + transform var(--duration-fast) var(--ease-default); +} + +.dropdown.is-open .dropdown__menu { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +/* Right-aligned menu */ +.dropdown__menu--right { + left: auto; + right: 0; +} + + +/* ===== MENU ITEMS ===== */ + +.dropdown__item { + display: flex; + align-items: center; + gap: var(--space-2); + + width: 100%; + padding: var(--space-2) var(--space-3); + + font-size: var(--text-sm); + color: var(--fg); + text-align: left; + text-decoration: none; + + background: none; + border: none; + border-radius: var(--radius-md); + + cursor: pointer; + transition: background-color var(--duration-fast) var(--ease-default); +} + +.dropdown__item:hover { + background-color: var(--bg-muted); + text-decoration: none; +} + +.dropdown__item svg { + width: 1rem; + height: 1rem; + color: var(--fg-muted); +} + +.dropdown__item--danger { + color: var(--color-danger-600); +} + +.dropdown__item--danger svg { + color: var(--color-danger-600); +} + + +/* ===== DIVIDER ===== */ + +.dropdown__divider { + height: 1px; + margin: var(--space-1) 0; + background-color: var(--border); +} + + +/* ===== HEADER ===== */ + +.dropdown__header { + padding: var(--space-2) var(--space-3); + + font-size: var(--text-xs); + font-weight: var(--font-semibold); + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); +} diff --git a/tcf_website/static/css/site/components/footer.css b/tcf_website/static/css/site/components/footer.css new file mode 100644 index 000000000..a223a29ec --- /dev/null +++ b/tcf_website/static/css/site/components/footer.css @@ -0,0 +1,230 @@ +/* + * Footer Component + * ================ + */ + +.footer { + background-color: var(--color-neutral-900); + color: var(--color-neutral-200); + padding: var(--space-12) var(--space-4); +} + +[data-theme="dark"] .footer { + background-color: var(--color-neutral-950); + border-top: 1px solid var(--color-neutral-800); +} + +@media (min-width: 640px) { + .footer { + padding: var(--space-16) var(--space-6); + } +} + +@media (min-width: 1024px) { + .footer { + padding: var(--space-16) var(--space-8); + } +} + + +/* ===== FOOTER CONTAINER ===== */ + +.footer__inner { + max-width: var(--size-max); + margin: 0 auto; +} + + +/* ===== FOOTER TOP ===== */ + +.footer__top { + display: grid; + gap: var(--space-8); + margin-bottom: var(--space-12); +} + +@media (min-width: 768px) { + .footer__top { + grid-template-columns: 2fr 1fr 1fr 1fr; + gap: var(--space-12); + } +} + + +/* ===== FOOTER BRAND ===== */ + +.footer__brand { + max-width: 20rem; +} + +.footer__logo { + display: flex; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-4); + + text-decoration: none; + color: var(--color-white); +} + +.footer__logo:hover { + color: var(--color-white); + text-decoration: none; +} + +.footer__logo-icon { + width: 2.5rem; + height: 2.5rem; +} + +.footer__logo-text { + font-size: var(--text-xl); + font-weight: var(--font-semibold); +} + +.footer__tagline { + font-size: var(--text-sm); + color: var(--color-neutral-400); + line-height: var(--leading-relaxed); +} + + +/* ===== FOOTER LINKS ===== */ + +.footer__section { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.footer__section-title { + font-size: var(--text-sm); + font-weight: var(--font-semibold); + color: var(--color-white); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); +} + +.footer__links { + display: flex; + flex-direction: column; + gap: var(--space-3); + list-style: none; + padding: 0; + margin: 0; +} + +.footer__link { + font-size: var(--text-sm); + color: var(--color-neutral-400); + text-decoration: none; + transition: color var(--duration-fast) var(--ease-default); +} + +.footer__link:hover { + color: var(--color-white); + text-decoration: none; +} + + +/* ===== FOOTER SOCIAL ===== */ + +.footer__social { + display: flex; + gap: var(--space-3); + margin-top: var(--space-4); +} + +.footer__social-link { + display: flex; + align-items: center; + justify-content: center; + + width: 2.25rem; + height: 2.25rem; + + color: var(--color-neutral-400); + background-color: var(--color-neutral-800); + border-radius: var(--radius-lg); + + transition: color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.footer__social-link:hover { + color: var(--color-white); + background-color: var(--color-neutral-700); +} + +.footer__social-link svg { + width: 1.25rem; + height: 1.25rem; +} + + +/* ===== FOOTER BOTTOM ===== */ + +.footer__bottom { + display: flex; + flex-direction: column; + gap: var(--space-4); + + padding-top: var(--space-8); + border-top: 1px solid var(--color-neutral-800); +} + +@media (min-width: 768px) { + .footer__bottom { + flex-direction: row; + align-items: center; + justify-content: space-between; + } +} + +.footer__copyright { + font-size: var(--text-sm); + color: var(--color-neutral-500); +} + +.footer__legal { + display: flex; + gap: var(--space-6); +} + +.footer__legal-link { + font-size: var(--text-sm); + color: var(--color-neutral-500); + text-decoration: none; + transition: color var(--duration-fast) var(--ease-default); +} + +.footer__legal-link:hover { + color: var(--color-neutral-300); + text-decoration: none; +} + + +/* ===== DONATION CTA ===== */ + +.footer__donate { + margin-top: var(--space-6); + padding: var(--space-4); + + background-color: var(--color-neutral-800); + border-radius: var(--radius-lg); +} + +.footer__donate-text { + font-size: var(--text-sm); + color: var(--color-neutral-300); + margin-bottom: var(--space-3); +} + +.footer__donate .btn { + background-color: var(--accent); + color: var(--color-white); +} + +.footer__donate .btn:hover { + background-color: var(--accent-hover); +} diff --git a/tcf_website/static/css/site/components/header.css b/tcf_website/static/css/site/components/header.css new file mode 100644 index 000000000..d8c700a91 --- /dev/null +++ b/tcf_website/static/css/site/components/header.css @@ -0,0 +1,316 @@ +/* + * Header Component + * ================ + */ + +.header { + position: sticky; + top: 0; + left: 0; + right: 0; + + height: 4rem; + padding: 0 var(--space-4); + + background-color: var(--bg); + border-bottom: 1px solid var(--border); + + z-index: var(--z-sticky); + transition: background-color var(--duration-normal) var(--ease-default), + border-color var(--duration-normal) var(--ease-default); +} + +@media (min-width: 640px) { + .header { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .header { + padding: 0 var(--space-8); + } +} + + +/* ===== HEADER CONTAINER ===== */ + +.header__inner { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + + height: 100%; + max-width: var(--size-max); + margin: 0 auto; +} + + +/* ===== LOGO ===== */ + +.header__logo { + display: flex; + align-items: center; + gap: var(--space-3); + + text-decoration: none; + color: var(--fg); + flex-shrink: 0; +} + +.header__logo:hover { + text-decoration: none; + color: var(--fg); +} + +.header__logo-icon { + width: 2rem; + height: 2rem; +} + +.header__logo-text { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + letter-spacing: var(--tracking-tight); +} + +@media (max-width: 639px) { + .header__logo-text { + display: none; + } +} + + +/* ===== NAVIGATION ===== */ + +.header__nav { + display: none; + align-items: center; + gap: var(--space-1); +} + +@media (min-width: 768px) { + .header__nav { + display: flex; + } +} + +.header__nav-link { + display: flex; + align-items: center; + + padding: var(--space-2) var(--space-3); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg-muted); + text-decoration: none; + + border-radius: var(--radius-lg); + transition: color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.header__nav-link:hover { + color: var(--fg); + background-color: var(--bg-muted); + text-decoration: none; +} + +.header__nav-link.is-active { + color: var(--fg); + background-color: var(--bg-muted); +} + + +/* ===== ACTIONS ===== */ + +.header__actions { + display: flex; + align-items: center; + gap: var(--space-2); + flex-shrink: 0; +} + + +/* ===== MODE TOGGLE ===== */ + +.mode-toggle { + display: flex; + background-color: var(--bg-muted); + border-radius: var(--radius-lg); + padding: 2px; + gap: 2px; +} + +.mode-toggle__item { + display: flex; + align-items: center; + justify-content: center; + + padding: var(--space-1) var(--space-3); + + font-size: var(--text-xs); + font-weight: var(--font-medium); + color: var(--fg-muted); + text-decoration: none; + border-radius: var(--radius-md); + + transition: all var(--duration-fast) var(--ease-default); +} + +.mode-toggle__item:hover { + text-decoration: none; + color: var(--fg); +} + +.mode-toggle__item.is-active { + background-color: var(--bg); + color: var(--fg); + box-shadow: var(--shadow-sm); + font-weight: var(--font-semibold); +} + + +/* ===== THEME TOGGLE ===== */ + +.theme-toggle { + display: flex; + align-items: center; + justify-content: center; + + width: 2.25rem; + height: 2.25rem; + + color: var(--fg-muted); + background: transparent; + border: none; + border-radius: var(--radius-lg); + + cursor: pointer; + transition: color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.theme-toggle:hover { + color: var(--fg); + background-color: var(--bg-muted); +} + +.theme-toggle svg { + width: 1.25rem; + height: 1.25rem; +} + +/* Hide icons based on theme */ +.theme-toggle__sun { + display: none; +} + +.theme-toggle__moon { + display: block; +} + +[data-theme="dark"] .theme-toggle__sun { + display: block; +} + +[data-theme="dark"] .theme-toggle__moon { + display: none; +} + + +/* ===== USER MENU ===== */ + +.header__user { + position: relative; +} + +.header__user-trigger { + display: flex; + align-items: center; + gap: var(--space-2); + + padding: var(--space-1-5) var(--space-3); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg); + + background: transparent; + border: 1px solid var(--border); + border-radius: var(--radius-lg); + + cursor: pointer; + transition: border-color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.header__user-trigger:hover { + background-color: var(--bg-muted); + border-color: var(--fg-subtle); +} + +.header__user-avatar { + width: 1.5rem; + height: 1.5rem; + + background-color: var(--primary); + color: var(--primary-fg); + + border-radius: var(--radius-full); + + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-xs); + font-weight: var(--font-semibold); +} + + +/* ===== MOBILE MENU TRIGGER ===== */ + +.header__mobile-trigger { + display: flex; + align-items: center; + justify-content: center; + + width: 2.25rem; + height: 2.25rem; + + color: var(--fg); + background: transparent; + border: none; + border-radius: var(--radius-lg); + + cursor: pointer; +} + +.header__mobile-trigger:hover { + background-color: var(--bg-muted); +} + +.header__mobile-trigger svg { + width: 1.5rem; + height: 1.5rem; +} + +@media (min-width: 768px) { + .header__mobile-trigger { + display: none; + } +} + + +/* ===== TRANSPARENT HEADER (for landing) ===== */ + +.header--transparent { + background-color: transparent; + border-bottom: none; +} + +.header--transparent.is-scrolled { + background-color: var(--bg); + border-bottom: 1px solid var(--border); +} diff --git a/tcf_website/static/css/site/components/input.css b/tcf_website/static/css/site/components/input.css new file mode 100644 index 000000000..42863bfb5 --- /dev/null +++ b/tcf_website/static/css/site/components/input.css @@ -0,0 +1,350 @@ +/* + * Form Input Components + * ===================== + */ + +/* ===== BASE INPUT ===== */ + +.input { + display: block; + width: 100%; + height: 2.5rem; + padding: 0 var(--space-3); + + font-family: var(--font-sans); + font-size: var(--text-sm); + color: var(--fg); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + + transition: border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default); +} + +.input:hover { + border-color: var(--fg-subtle); +} + +.input:focus { + outline: none; + border-color: var(--ring); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); +} + +.input:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: var(--bg-muted); +} + +.input::placeholder { + color: var(--fg-subtle); +} + + +/* ===== INPUT SIZES ===== */ + +.input--sm { + height: 2rem; + padding: 0 var(--space-2-5); + font-size: var(--text-xs); +} + +.input--lg { + height: 3rem; + padding: 0 var(--space-4); + font-size: var(--text-base); +} + + +/* ===== INPUT STATES ===== */ + +.input--error { + border-color: var(--color-danger-500); +} + +.input--error:focus { + border-color: var(--color-danger-500); + box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15); +} + +.input--success { + border-color: var(--color-success-500); +} + + +/* ===== TEXTAREA ===== */ + +.textarea { + display: block; + width: 100%; + min-height: 6rem; + padding: var(--space-3); + + font-family: var(--font-sans); + font-size: var(--text-sm); + color: var(--fg); + line-height: var(--leading-relaxed); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + + resize: vertical; + transition: border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default); +} + +.textarea:hover { + border-color: var(--fg-subtle); +} + +.textarea:focus { + outline: none; + border-color: var(--ring); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); +} + + +/* ===== SELECT ===== */ + +.select { + display: block; + width: 100%; + height: 2.5rem; + padding: 0 var(--space-8) 0 var(--space-3); + + font-family: var(--font-sans); + font-size: var(--text-sm); + color: var(--fg); + + background-color: var(--bg-elevated); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right var(--space-2) center; + background-repeat: no-repeat; + background-size: 1.5rem 1.5rem; + + border: 1px solid var(--border); + border-radius: var(--radius-lg); + + appearance: none; + cursor: pointer; + transition: border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default); +} + +.select:hover { + border-color: var(--fg-subtle); +} + +.select:focus { + outline: none; + border-color: var(--ring); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); +} + + +/* ===== CHECKBOX ===== */ + +.checkbox { + width: 1rem; + height: 1rem; + + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background-color: var(--bg-elevated); + + appearance: none; + cursor: pointer; + transition: all var(--duration-fast) var(--ease-default); +} + +.checkbox:hover { + border-color: var(--fg-subtle); +} + +.checkbox:checked { + background-color: var(--primary); + border-color: var(--primary); + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +.checkbox:focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; +} + + +/* ===== RADIO ===== */ + +.radio { + width: 1rem; + height: 1rem; + + border: 1px solid var(--border); + border-radius: var(--radius-full); + background-color: var(--bg-elevated); + + appearance: none; + cursor: pointer; + transition: all var(--duration-fast) var(--ease-default); +} + +.radio:hover { + border-color: var(--fg-subtle); +} + +.radio:checked { + border-color: var(--primary); + border-width: 4px; +} + +.radio:focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; +} + + +/* ===== LABEL ===== */ + +.label { + display: block; + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg); + margin-bottom: var(--space-1-5); +} + +.label--required::after { + content: ' *'; + color: var(--color-danger-500); +} + + +/* ===== FORM GROUP ===== */ + +.form-group { + margin-bottom: var(--space-4); +} + +.form-group--inline { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.form-group--inline .label { + margin-bottom: 0; +} + + +/* ===== HELP TEXT ===== */ + +.help-text { + font-size: var(--text-xs); + color: var(--fg-muted); + margin-top: var(--space-1); +} + +.help-text--error { + color: var(--color-danger-500); +} + + +/* ===== INPUT GROUP ===== */ + +.input-group { + display: flex; + align-items: stretch; +} + +.input-group .input { + border-radius: 0; +} + +.input-group .input:first-child { + border-top-left-radius: var(--radius-lg); + border-bottom-left-radius: var(--radius-lg); +} + +.input-group .input:last-child { + border-top-right-radius: var(--radius-lg); + border-bottom-right-radius: var(--radius-lg); +} + +.input-group__addon { + display: flex; + align-items: center; + padding: 0 var(--space-3); + + font-size: var(--text-sm); + color: var(--fg-muted); + + background-color: var(--bg-muted); + border: 1px solid var(--border); +} + +.input-group__addon:first-child { + border-right: none; + border-top-left-radius: var(--radius-lg); + border-bottom-left-radius: var(--radius-lg); +} + +.input-group__addon:last-child { + border-left: none; + border-top-right-radius: var(--radius-lg); + border-bottom-right-radius: var(--radius-lg); +} + +.input-group .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + + +/* ===== RANGE SLIDER ===== */ + +.range { + width: 100%; + height: 0.5rem; + + background: var(--bg-muted); + border-radius: var(--radius-full); + + appearance: none; + cursor: pointer; +} + +.range::-webkit-slider-thumb { + appearance: none; + width: 1.25rem; + height: 1.25rem; + + background: var(--primary); + border: 2px solid var(--bg-elevated); + border-radius: var(--radius-full); + box-shadow: var(--shadow-sm); + + cursor: pointer; + transition: transform var(--duration-fast) var(--ease-default); +} + +.range::-webkit-slider-thumb:hover { + transform: scale(1.1); +} + +.range::-moz-range-thumb { + width: 1.25rem; + height: 1.25rem; + + background: var(--primary); + border: 2px solid var(--bg-elevated); + border-radius: var(--radius-full); + + cursor: pointer; +} diff --git a/tcf_website/static/css/site/components/misc.css b/tcf_website/static/css/site/components/misc.css new file mode 100644 index 000000000..5b719cd12 --- /dev/null +++ b/tcf_website/static/css/site/components/misc.css @@ -0,0 +1,428 @@ +/* + * Breadcrumb Component + * ==================== + */ + +.breadcrumb { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-1); + + list-style: none; + padding: 0; + margin: 0; +} + +.breadcrumb__item { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.breadcrumb__item:not(:last-child)::after { + content: ''; + display: block; + width: 1rem; + height: 1rem; + margin-left: var(--space-1); + + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%23a8a29e'%3e%3cpath fill-rule='evenodd' d='M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z' clip-rule='evenodd' /%3e%3c/svg%3e"); + background-size: contain; + background-repeat: no-repeat; +} + +.breadcrumb__link { + font-size: var(--text-sm); + color: var(--fg-muted); + text-decoration: none; + transition: color var(--duration-fast) var(--ease-default); +} + +.breadcrumb__link:hover { + color: var(--fg); + text-decoration: none; +} + +.breadcrumb__current { + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg); +} + + +/* + * Pagination Component + * ==================== + */ + +.pagination { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.pagination__item { + display: flex; + align-items: center; + justify-content: center; + + min-width: 2rem; + height: 2rem; + padding: 0 var(--space-2); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg-muted); + text-decoration: none; + + background: transparent; + border: none; + border-radius: var(--radius-md); + + cursor: pointer; + transition: color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.pagination__item:hover { + color: var(--fg); + background-color: var(--bg-muted); + text-decoration: none; +} + +.pagination__item.is-active { + color: var(--primary-fg); + background-color: var(--primary); +} + +.pagination__item:disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} + +.pagination__item svg { + width: 1rem; + height: 1rem; +} + +.pagination__ellipsis { + padding: 0 var(--space-2); + color: var(--fg-muted); +} + + +/* + * Badge Component + * =============== + */ + +.badge { + display: inline-flex; + align-items: center; + gap: var(--space-1); + + padding: var(--space-0-5) var(--space-2); + + font-size: var(--text-xs); + font-weight: var(--font-medium); + line-height: var(--leading-normal); + + border-radius: var(--radius-full); +} + +.badge--default { + background-color: var(--bg-muted); + color: var(--fg-muted); +} + +.badge--primary { + background-color: var(--color-primary-100); + color: var(--color-primary-700); +} + +[data-theme="dark"] .badge--primary { + background-color: rgba(59, 130, 246, 0.2); + color: var(--color-primary-400); +} + +.badge--accent { + background-color: var(--color-accent-100); + color: var(--color-accent-700); +} + +[data-theme="dark"] .badge--accent { + background-color: rgba(249, 115, 22, 0.2); + color: var(--color-accent-400); +} + +.badge--success { + background-color: rgba(34, 197, 94, 0.15); + color: var(--color-success-600); +} + +.badge--warning { + background-color: rgba(234, 179, 8, 0.15); + color: var(--color-warning-600); +} + +.badge--danger { + background-color: rgba(239, 68, 68, 0.15); + color: var(--color-danger-600); +} + + +/* + * Accordion Component + * =================== + */ + +.accordion { + border: 1px solid var(--border); + border-radius: var(--radius-xl); + overflow: hidden; +} + +.accordion__item { + border-bottom: 1px solid var(--border); +} + +.accordion__item:last-child { + border-bottom: none; +} + +.accordion__trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + + width: 100%; + padding: var(--space-4) var(--space-5); + + font-size: var(--text-base); + font-weight: var(--font-medium); + color: var(--fg); + text-align: left; + + background: transparent; + border: none; + + cursor: pointer; + transition: background-color var(--duration-fast) var(--ease-default); +} + +.accordion__trigger:hover { + background-color: var(--bg-muted); +} + +.accordion__trigger svg { + width: 1.25rem; + height: 1.25rem; + color: var(--fg-muted); + flex-shrink: 0; + transition: transform var(--duration-normal) var(--ease-default); +} + +.accordion__item.is-open .accordion__trigger svg { + transform: rotate(180deg); +} + +.accordion__content { + display: none; + padding: 0 var(--space-5) var(--space-4); +} + +.accordion__item.is-open .accordion__content { + display: block; +} + +.accordion__content p { + font-size: var(--text-sm); + color: var(--fg-muted); + line-height: var(--leading-relaxed); +} + + +/* + * Alert Component + * =============== + */ + +.alert { + display: flex; + gap: var(--space-3); + + padding: var(--space-4); + + border-radius: var(--radius-lg); +} + +.alert__icon { + flex-shrink: 0; + width: 1.25rem; + height: 1.25rem; +} + +.alert__content { + flex: 1; +} + +.alert__title { + font-size: var(--text-sm); + font-weight: var(--font-semibold); + margin-bottom: var(--space-1); +} + +.alert__description { + font-size: var(--text-sm); +} + +.alert--info { + background-color: var(--color-primary-50); + color: var(--color-primary-700); +} + +[data-theme="dark"] .alert--info { + background-color: rgba(59, 130, 246, 0.1); + color: var(--color-primary-300); +} + +.alert--success { + background-color: rgba(34, 197, 94, 0.1); + color: var(--color-success-600); +} + +.alert--warning { + background-color: rgba(234, 179, 8, 0.1); + color: var(--color-warning-600); +} + +.alert--danger { + background-color: rgba(239, 68, 68, 0.1); + color: var(--color-danger-600); +} + + +/* + * Leaderboard Ad + * ============== + */ + +.leaderboard-ad { + padding: var(--space-4) 0 0; +} + +.leaderboard-ad__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .leaderboard-ad__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .leaderboard-ad__inner { + padding: 0 var(--space-8); + } +} + +.leaderboard-ad__label { + display: inline-block; + margin-bottom: var(--space-2); + font-size: var(--text-xs); + color: var(--fg-subtle); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; +} + +.leaderboard-ad__slot { + border: 1px solid var(--border); + border-radius: var(--radius-xl); + background-color: var(--bg-elevated); + overflow: hidden; +} + + +/* + * Flash Messages + * ============== + */ + +.flash-messages { + padding: var(--space-4) 0 0; +} + +.flash-messages__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +@media (min-width: 640px) { + .flash-messages__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .flash-messages__inner { + padding: 0 var(--space-8); + } +} + + +/* + * Skeleton Loading + * ================ + */ + +.skeleton { + background: linear-gradient( + 90deg, + var(--bg-muted) 0%, + var(--bg-hover) 50%, + var(--bg-muted) 100% + ); + background-size: 200% 100%; + animation: skeleton-shimmer 1.5s ease-in-out infinite; + border-radius: var(--radius-md); +} + +@keyframes skeleton-shimmer { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +.skeleton--text { + height: 1rem; + margin-bottom: var(--space-2); +} + +.skeleton--title { + height: 1.5rem; + width: 60%; + margin-bottom: var(--space-3); +} + +.skeleton--avatar { + width: 3rem; + height: 3rem; + border-radius: var(--radius-full); +} + +.skeleton--card { + height: 12rem; +} diff --git a/tcf_website/static/css/site/components/modal.css b/tcf_website/static/css/site/components/modal.css new file mode 100644 index 000000000..cb138b9d8 --- /dev/null +++ b/tcf_website/static/css/site/components/modal.css @@ -0,0 +1,295 @@ +/* + * Modal Component + * =============== + */ + +/* ===== BACKDROP ===== */ + +.modal-backdrop { + position: fixed; + inset: 0; + + background-color: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(4px); + + z-index: var(--z-modal-backdrop); + + opacity: 0; + visibility: hidden; + transition: opacity var(--duration-normal) var(--ease-default), + visibility var(--duration-normal) var(--ease-default); +} + +.modal-backdrop.is-open { + opacity: 1; + visibility: visible; +} + +[data-theme="dark"] .modal-backdrop { + background-color: rgba(0, 0, 0, 0.7); +} + + +/* ===== MODAL CONTAINER ===== */ + +.modal { + position: fixed; + inset: 0; + + display: flex; + align-items: center; + justify-content: center; + padding: var(--space-4); + + z-index: var(--z-modal); + + opacity: 0; + visibility: hidden; + transition: opacity var(--duration-normal) var(--ease-default), + visibility var(--duration-normal) var(--ease-default); +} + +.modal.is-open { + opacity: 1; + visibility: visible; +} + + +/* ===== MODAL DIALOG ===== */ + +.modal__dialog { + position: relative; + width: 100%; + max-width: 32rem; + max-height: calc(100vh - var(--space-8)); + + background-color: var(--bg-elevated); + border-radius: var(--radius-2xl); + box-shadow: var(--shadow-xl); + + display: flex; + flex-direction: column; + + transform: scale(0.95) translateY(10px); + transition: transform var(--duration-normal) var(--ease-out); +} + +.modal.is-open .modal__dialog { + transform: scale(1) translateY(0); +} + + +/* ===== SIZE VARIANTS ===== */ + +.modal__dialog--sm { + max-width: 24rem; +} + +.modal__dialog--lg { + max-width: 48rem; +} + +.modal__dialog--xl { + max-width: 64rem; +} + +.modal__dialog--full { + max-width: calc(100vw - var(--space-8)); + max-height: calc(100vh - var(--space-8)); +} + + +/* ===== MODAL PARTS ===== */ + +.modal__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + + padding: var(--space-6); + border-bottom: 1px solid var(--border); + + flex-shrink: 0; +} + +.modal__title { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + margin: 0; +} + +.modal__close { + display: flex; + align-items: center; + justify-content: center; + + width: 2rem; + height: 2rem; + + color: var(--fg-muted); + background: transparent; + border: none; + border-radius: var(--radius-lg); + + cursor: pointer; + transition: color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.modal__close:hover { + color: var(--fg); + background-color: var(--bg-muted); +} + +.modal__close svg { + width: 1.25rem; + height: 1.25rem; +} + +.modal__body { + padding: var(--space-6); + overflow-y: auto; + flex: 1; +} + +.modal__footer { + display: flex; + align-items: center; + justify-content: flex-end; + gap: var(--space-3); + + padding: var(--space-4) var(--space-6); + border-top: 1px solid var(--border); + background-color: var(--bg-muted); + border-radius: 0 0 var(--radius-2xl) var(--radius-2xl); + + flex-shrink: 0; +} + + +/* ===== CENTERED CONTENT ===== */ + +.modal__body--centered { + text-align: center; + padding: var(--space-8); +} + +.modal__icon { + display: inline-flex; + align-items: center; + justify-content: center; + + width: 4rem; + height: 4rem; + margin-bottom: var(--space-4); + + background-color: var(--bg-muted); + border-radius: var(--radius-full); +} + +.modal__icon svg { + width: 2rem; + height: 2rem; +} + +.modal__icon--success { + background-color: rgba(34, 197, 94, 0.1); + color: var(--color-success-600); +} + +.modal__icon--warning { + background-color: rgba(234, 179, 8, 0.1); + color: var(--color-warning-600); +} + +.modal__icon--danger { + background-color: rgba(239, 68, 68, 0.1); + color: var(--color-danger-600); +} + + +/* ===== SCROLLABLE BODY ===== */ + +.modal__body--scroll { + max-height: 60vh; + overflow-y: auto; +} + + +/* ===== PREVENT BODY SCROLL ===== */ + +body.modal-open { + overflow: hidden; +} + + +/* ===== ANIMATIONS ===== */ + +@media (prefers-reduced-motion: no-preference) { + .modal-backdrop { + transition: opacity var(--duration-normal) var(--ease-default), + visibility var(--duration-normal) var(--ease-default); + } + + .modal__dialog { + transition: transform var(--duration-normal) var(--ease-out), + opacity var(--duration-normal) var(--ease-out); + } +} + + +/* ===== ADBLOCK GATE ===== */ + +.adblock-gate { + position: fixed; + inset: 0; + z-index: var(--z-modal); + display: none; + align-items: center; + justify-content: center; + padding: var(--space-4); +} + +.adblock-gate.is-open { + display: flex; +} + +.adblock-gate__backdrop { + position: absolute; + inset: 0; + background-color: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); +} + +.adblock-gate__dialog { + position: relative; + z-index: 1; + width: 100%; + max-width: 32rem; + padding: var(--space-8); + text-align: center; + border: 1px solid var(--border); + border-radius: var(--radius-2xl); + background-color: var(--bg-elevated); + box-shadow: var(--shadow-xl); +} + +.adblock-gate__logo { + width: auto; + max-width: 11rem; + max-height: 3.25rem; + margin: 0 auto var(--space-4); +} + +.adblock-gate__title { + margin: 0 0 var(--space-3); + font-size: var(--text-2xl); + font-weight: var(--font-semibold); +} + +.adblock-gate__description { + margin: 0 0 var(--space-6); + color: var(--fg-muted); + line-height: var(--leading-relaxed); +} diff --git a/tcf_website/static/css/site/components/rating.css b/tcf_website/static/css/site/components/rating.css new file mode 100644 index 000000000..bc8023027 --- /dev/null +++ b/tcf_website/static/css/site/components/rating.css @@ -0,0 +1,270 @@ +/* + * Rating Component + * ================ + * Used for course/instructor rating cards + */ + +/* ===== RATING CARD ===== */ + +.rating-card { + display: flex; + flex-direction: column; + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + overflow: hidden; + transition: box-shadow var(--duration-normal) var(--ease-default), + border-color var(--duration-normal) var(--ease-default), + transform var(--duration-normal) var(--ease-default); +} + +.rating-card:hover { + border-color: var(--fg-subtle); + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +@media (min-width: 768px) { + .rating-card { + flex-direction: row; + } +} + + +/* ===== CARD HEADER (Title) ===== */ + +.rating-card__header { + display: flex; + align-items: center; + + padding: var(--space-4) var(--space-5); + + text-decoration: none; + color: var(--fg); + + border-bottom: 1px solid var(--border); +} + +@media (min-width: 768px) { + .rating-card__header { + flex: 0 0 auto; + width: 280px; + + border-bottom: none; + border-right: 1px solid var(--border); + } +} + +.rating-card__header:hover { + text-decoration: none; +} + +.rating-card__title { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + line-height: var(--leading-tight); +} + +.rating-card__subtitle { + font-size: var(--text-sm); + color: var(--fg-muted); + margin-top: var(--space-1); +} + + +/* ===== CARD BODY (Stats) ===== */ + +.rating-card__body { + flex: 1; + padding: var(--space-4) var(--space-5); +} + + +/* ===== STATS GRID ===== */ + +.rating-card__stats { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-4); +} + +@media (min-width: 640px) { + .rating-card__stats { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (min-width: 1024px) { + .rating-card__stats { + grid-template-columns: repeat(5, 1fr); + } +} + + +/* ===== INDIVIDUAL STAT ===== */ + +.rating-stat { + text-align: center; +} + +.rating-stat__label { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-1); + + font-size: var(--text-xs); + font-weight: var(--font-medium); + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); + + margin-bottom: var(--space-1); +} + +.rating-stat__label svg { + width: 0.875rem; + height: 0.875rem; +} + +.rating-stat__value { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + color: var(--fg); +} + +.rating-stat__value--empty { + color: var(--fg-subtle); +} + + +/* ===== RATING BAR ===== */ + +.rating-bar { + height: 0.375rem; + margin-top: var(--space-2); + + background-color: var(--bg-muted); + border-radius: var(--radius-full); + + overflow: hidden; +} + +.rating-bar__fill { + height: 100%; + border-radius: var(--radius-full); + transition: width var(--duration-slow) var(--ease-out); +} + +/* Color coding for ratings */ +.rating-bar__fill--excellent { + background-color: var(--color-success-500); +} + +.rating-bar__fill--good { + background-color: var(--color-primary-500); +} + +.rating-bar__fill--average { + background-color: var(--color-warning-500); +} + +.rating-bar__fill--poor { + background-color: var(--color-danger-500); +} + + +/* ===== RATING BADGE ===== */ + +.rating-badge { + display: inline-flex; + align-items: center; + justify-content: center; + + min-width: 2.5rem; + padding: var(--space-1) var(--space-2); + + font-size: var(--text-sm); + font-weight: var(--font-semibold); + + border-radius: var(--radius-md); +} + +.rating-badge--excellent { + background-color: rgba(34, 197, 94, 0.1); + color: var(--color-success-600); +} + +.rating-badge--good { + background-color: rgba(59, 130, 246, 0.1); + color: var(--color-primary-600); +} + +.rating-badge--average { + background-color: rgba(234, 179, 8, 0.1); + color: var(--color-warning-600); +} + +.rating-badge--poor { + background-color: rgba(239, 68, 68, 0.1); + color: var(--color-danger-600); +} + + +/* ===== COMPACT RATING CARD ===== */ + +.rating-card--compact { + padding: var(--space-3); +} + +.rating-card--compact .rating-card__header { + width: auto; + padding: 0 0 var(--space-3) 0; + border-right: none; + border-bottom: 1px solid var(--border); +} + +.rating-card--compact .rating-card__body { + padding: var(--space-3) 0 0 0; +} + +.rating-card--compact .rating-card__title { + font-size: var(--text-base); +} + +.rating-card--compact .rating-stat__value { + font-size: var(--text-lg); +} + + +/* ===== STARS DISPLAY ===== */ + +.rating-stars { + display: inline-flex; + gap: var(--space-0-5); +} + +.rating-stars__star { + width: 1rem; + height: 1rem; + color: var(--color-neutral-300); +} + +.rating-stars__star--filled { + color: var(--color-warning-500); +} + +.rating-stars__star--half { + color: var(--color-warning-500); +} + + +/* ===== RATING LIST ===== */ + +.rating-list { + display: flex; + flex-direction: column; + gap: var(--space-3); +} diff --git a/tcf_website/static/css/site/components/search.css b/tcf_website/static/css/site/components/search.css new file mode 100644 index 000000000..867e72656 --- /dev/null +++ b/tcf_website/static/css/site/components/search.css @@ -0,0 +1,378 @@ +/* + * Search Component + * ================ + */ + +/* ===== SEARCH BAR ===== */ + +.search { + position: relative; + width: 100%; + max-width: 40rem; +} + + +/* ===== SEARCH INPUT ===== */ + +.search__input-wrapper { + position: relative; + display: flex; + align-items: center; +} + +.search__icon { + position: absolute; + left: var(--space-4); + + width: 1.25rem; + height: 1.25rem; + + color: var(--fg-muted); + pointer-events: none; +} + +.search__input { + width: 100%; + height: 3rem; + padding: 0 var(--space-12) 0 var(--space-12); + + font-family: var(--font-sans); + font-size: var(--text-base); + color: var(--fg); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + transition: border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default); +} + +.search__input::placeholder { + color: var(--fg-subtle); +} + +.search__input:hover { + border-color: var(--fg-subtle); +} + +.search__input:focus { + outline: none; + border-color: var(--ring); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); +} + + +/* ===== SEARCH ACTIONS ===== */ + +.search__actions { + position: absolute; + right: var(--space-1-5); + + display: flex; + align-items: center; + gap: var(--space-1); +} + +.search__submit { + display: flex; + align-items: center; + justify-content: center; + + width: 2.25rem; + height: 2.25rem; + + color: var(--primary-fg); + background-color: var(--primary); + border: none; + border-radius: var(--radius-lg); + + cursor: pointer; + transition: background-color var(--duration-fast) var(--ease-default); +} + +.search__submit:hover { + background-color: var(--primary-hover); +} + +.search__submit svg { + width: 1rem; + height: 1rem; +} + + +/* ===== LARGE SEARCH (for landing) ===== */ + +.search--lg .search__input { + height: 3.5rem; + font-size: var(--text-lg); + border-radius: var(--radius-2xl); + box-shadow: var(--shadow-lg); +} + +.search--lg .search__icon { + width: 1.5rem; + height: 1.5rem; +} + +.search--lg .search__submit { + width: 2.5rem; + height: 2.5rem; +} + + +/* ===== FILTER BUTTON ===== */ + +.search__filter { + display: flex; + align-items: center; + gap: var(--space-1); + + padding: var(--space-1-5) var(--space-3); + + font-size: var(--text-sm); + color: var(--fg-muted); + + background: transparent; + border: none; + border-radius: var(--radius-lg); + + cursor: pointer; + transition: color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.search__filter:hover { + color: var(--fg); + background-color: var(--bg-muted); +} + +.search__filter svg { + width: 1rem; + height: 1rem; +} + +.search__filter.is-active { + color: var(--primary); + background-color: var(--color-primary-50); +} + +[data-theme="dark"] .search__filter.is-active { + background-color: rgba(59, 130, 246, 0.1); +} + + +/* ===== MODE TOGGLE ===== */ + +.search__mode { + display: flex; + padding: var(--space-0-5); + + background-color: var(--bg-muted); + border-radius: var(--radius-lg); +} + +.search__mode-option { + padding: var(--space-1-5) var(--space-3); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg-muted); + + background: transparent; + border: none; + border-radius: var(--radius-md); + + cursor: pointer; + transition: color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.search__mode-option:hover { + color: var(--fg); +} + +.search__mode-option.is-active { + color: var(--fg); + background-color: var(--bg-elevated); + box-shadow: var(--shadow-sm); +} + + +/* ===== FILTER DROPDOWN ===== */ + +.search__dropdown { + position: absolute; + top: calc(100% + var(--space-2)); + left: 0; + right: 0; + + padding: var(--space-4); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-xl); + + z-index: var(--z-dropdown); + + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: opacity var(--duration-fast) var(--ease-default), + visibility var(--duration-fast) var(--ease-default), + transform var(--duration-fast) var(--ease-default); +} + +.search__dropdown.is-open { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + + +/* ===== FILTER SECTIONS ===== */ + +.search__filter-grid { + display: grid; + gap: var(--space-4); +} + +@media (min-width: 640px) { + .search__filter-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.search__filter-section { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.search__filter-title { + font-size: var(--text-sm); + font-weight: var(--font-semibold); + color: var(--fg); +} + + +/* ===== DAY TOGGLES ===== */ + +.search__days { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.search__day { + display: flex; + align-items: center; + justify-content: center; + + width: 2.5rem; + height: 2.5rem; + + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg-muted); + + background-color: var(--bg-muted); + border: 1px solid transparent; + border-radius: var(--radius-lg); + + cursor: pointer; + transition: all var(--duration-fast) var(--ease-default); +} + +.search__day:hover { + color: var(--fg); + border-color: var(--border); +} + +.search__day.is-active { + color: var(--primary-fg); + background-color: var(--primary); +} + + +/* ===== FILTER ACTIONS ===== */ + +.search__filter-actions { + display: flex; + justify-content: flex-end; + gap: var(--space-3); + + margin-top: var(--space-4); + padding-top: var(--space-4); + border-top: 1px solid var(--border); +} + + +/* ===== SEARCH RESULTS DROPDOWN ===== */ + +.search__results { + position: absolute; + top: calc(100% + var(--space-2)); + left: 0; + right: 0; + + max-height: 24rem; + overflow-y: auto; + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-xl); + + z-index: var(--z-dropdown); +} + +.search__result { + display: flex; + align-items: center; + gap: var(--space-3); + + padding: var(--space-3) var(--space-4); + + text-decoration: none; + color: var(--fg); + + transition: background-color var(--duration-fast) var(--ease-default); +} + +.search__result:hover { + background-color: var(--bg-muted); + text-decoration: none; +} + +.search__result-icon { + display: flex; + align-items: center; + justify-content: center; + + width: 2rem; + height: 2rem; + + color: var(--fg-muted); + background-color: var(--bg-muted); + border-radius: var(--radius-md); +} + +.search__result-content { + flex: 1; + min-width: 0; +} + +.search__result-title { + font-size: var(--text-sm); + font-weight: var(--font-medium); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.search__result-subtitle { + font-size: var(--text-xs); + color: var(--fg-muted); +} diff --git a/tcf_website/static/css/site/components/search_bar.css b/tcf_website/static/css/site/components/search_bar.css new file mode 100644 index 000000000..7cfa08c08 --- /dev/null +++ b/tcf_website/static/css/site/components/search_bar.css @@ -0,0 +1,318 @@ +/* + * Global Search Bar Component + * =========================== + */ + +.search-bar { + position: relative; + display: flex; + align-items: center; + flex: 1; + max-width: 24rem; + margin: 0 var(--space-4); +} + +.search-bar__input-wrapper { + position: relative; + display: flex; + align-items: center; + width: 100%; +} + +.search-bar__icon { + position: absolute; + left: var(--space-3); + width: 1rem; + height: 1rem; + color: var(--fg-muted); + pointer-events: none; +} + +.search-bar__input { + width: 100%; + height: 2.25rem; + padding-left: var(--space-9); + padding-right: 5rem; /* Space for actions group */ + + font-size: var(--text-sm); + color: var(--fg); + background-color: var(--bg-muted); + border: 1px solid transparent; + border-radius: var(--radius-lg); + + transition: all var(--duration-fast) var(--ease-default); +} + +.search-bar__input:focus { + background-color: var(--bg); + border-color: var(--primary); + box-shadow: 0 0 0 2px var(--color-primary-100); + outline: none; +} + +/* Actions Group */ +.search-bar__actions-group { + position: absolute; + right: var(--space-2); + display: flex; + align-items: center; + gap: 2px; +} + +.search-bar__filter-trigger, +.search-bar__submit { + display: flex; + align-items: center; + justify-content: center; + + width: 1.75rem; + height: 1.75rem; + + color: var(--fg-muted); + background: transparent; + border: none; + border-radius: var(--radius-md); + + cursor: pointer; + transition: all var(--duration-fast) var(--ease-default); +} + +.search-bar__filter-trigger:hover, +.search-bar__submit:hover, +.search-bar__filter-trigger.is-active { + color: var(--fg); + background-color: var(--bg-surface-hover); +} + +.search-bar__filter-trigger.is-active-filter { + color: var(--primary); + background-color: var(--color-primary-100); +} + +.search-bar__submit { + color: var(--primary); +} + +.search-bar__submit:hover { + background-color: var(--primary); + color: var(--color-white); +} + + +/* ===== FILTER DROPDOWN ===== */ + +.search-filters { + position: absolute; + top: calc(100% + var(--space-2)); + right: 0; /* Align right */ + left: auto; /* Allow expanding left */ + width: 600px; + max-width: 90vw; + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-xl); + + padding: var(--space-6); + z-index: 100; + + display: none; /* Hidden by default */ + opacity: 0; + transform: translateY(-8px); + transition: opacity var(--duration-normal) var(--ease-default), + transform var(--duration-normal) var(--ease-default); +} + +.search-filters.is-open { + display: block; + opacity: 1; + transform: translateY(0); +} + +/* Filter Sections Layout */ +.filter-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-4); /* Reduced from 6 */ + margin-bottom: var(--space-4); /* Reduced from 6 */ + text-align: center; /* Global centering */ +} + +.filter-section { + display: flex; + flex-direction: column; + gap: var(--space-2); /* Reduced from 3 */ + align-items: center; /* Global alignment */ + justify-content: flex-start; /* Align to top */ + height: 100%; /* Match heights */ +} + +.filter-title { + font-size: var(--text-xs); + font-weight: var(--font-bold); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); + color: var(--primary); /* Consistent color */ + margin-bottom: var(--space-1); + width: 100%; + text-align: center; +} + + + +/* Scrollable Lists (Subjects/Disciplines) */ +.filter-list-container { + border: 1px solid var(--border); + border-radius: var(--radius-lg); + overflow: hidden; + height: 140px; /* Reduced from 200px */ + display: flex; + flex-direction: column; + width: 100%; + max-width: 240px; /* Constraint for better centering */ +} + +.filter-list-search { + padding: var(--space-1); /* Reduced padding */ + border-bottom: 1px solid var(--border); +} + +.filter-list-search input { + width: 100%; + padding: var(--space-1) var(--space-2); + font-size: var(--text-xs); + border: none; + background: transparent; + outline: none; +} + +.filter-list { + flex: 1; + overflow-y: auto; + padding: var(--space-1); +} + +.filter-item { + display: flex; + align-items: center; + padding: var(--space-1) var(--space-2); + gap: var(--space-2); + font-size: var(--text-xs); /* Smaller text */ + color: var(--fg); + cursor: pointer; + border-radius: var(--radius-sm); + text-align: left; /* Keep list items left-aligned for readability */ +} + +.filter-item:hover { + background-color: var(--bg-muted); +} + +.filter-actions { + display: flex; + justify-content: center; /* Global centering */ + gap: var(--space-3); + padding-top: var(--space-6); /* Reduced from 4 */ + border-top: 1px solid var(--border-subtle); +} + +/* Responsive adjustment */ +@media (max-width: 768px) { + .search-filters { + position: fixed; + top: 4rem; + left: 0; + right: 0; + width: 100%; + max-width: none; + height: calc(100vh - 4rem); + border-radius: 0; + overflow-y: auto; + } + + .filter-grid { + grid-template-columns: 1fr; + } + + .filter-list-container { + max-width: 100%; /* Full width on mobile */ + } +} + +/* Hero Search Variant */ +/* Hero Search Variant */ +.search-bar--hero { + max-width: 100%; /* Override default 24rem */ + margin: 0; /* Let container handle margin */ +} + +.search-bar--hero .search-bar__input { + height: 3.5rem; + font-size: var(--text-base); + padding-left: 3.5rem; + padding-right: 6rem; + background-color: var(--bg-elevated); /* Slight contrast against hero bg */ + border: 1px solid var(--border); + box-shadow: var(--shadow-xl); /* Stronger shadow */ + border-radius: var(--radius-2xl); /* More pill-shaped for hero */ +} + +.search-bar--hero .search-bar__input:focus { + background-color: var(--bg); + border-color: var(--primary); + transform: translateY(-1px); +} + +.search-bar--hero .search-bar__icon { + width: 1.5rem; + height: 1.5rem; + left: 1.25rem; + color: var(--primary); /* Highlight icon in hero */ +} + +.search-bar--hero .search-bar__actions-group { + right: 0.75rem; + gap: var(--space-2); +} + +.search-bar--hero .search-bar__filter-trigger, +.search-bar--hero .search-bar__submit { + width: 2.5rem; + height: 2.5rem; + border-radius: var(--radius-xl); +} + +.search-bar--hero .search-bar__submit { + background-color: var(--primary); + color: var(--color-white); + box-shadow: var(--shadow-md); +} + +.search-bar--hero .search-bar__submit:hover { + background-color: var(--primary-dark); + transform: translateY(-1px); + box-shadow: var(--shadow-lg); +} + +/* Hero Filter Dropdown Redesign */ +.search-bar--hero .search-filters { + width: 100%; /* Match container width */ + max-width: none; + left: 0; + right: 0; + top: calc(100% + var(--space-4)); /* Little more air */ + + border-radius: var(--radius-2xl); + border: 1px solid var(--border-subtle); + background-color: rgba(255, 255, 255, 0.95); /* Slight transparency */ + backdrop-filter: blur(12px); + + padding: var(--space-6); + box-shadow: var(--shadow-2xl); +} + +[data-theme="dark"] .search-bar--hero .search-filters { + background-color: rgba(30, 30, 30, 0.95); + border-color: var(--border); +} diff --git a/tcf_website/static/css/site/main.css b/tcf_website/static/css/site/main.css new file mode 100644 index 000000000..74a5c215f --- /dev/null +++ b/tcf_website/static/css/site/main.css @@ -0,0 +1,24 @@ +/* + * Main Entry Point + * ================ + * Import all CSS files in the correct order. + */ + +/* Foundation */ +@import url('./tokens.css'); +@import url('./reset.css'); +@import url('./base.css'); +@import url('./utilities.css'); + +/* Components */ +@import url('./components/button.css'); +@import url('./components/input.css'); +@import url('./components/modal.css'); +@import url('./components/header.css'); +@import url('./components/footer.css'); +@import url('./components/search.css'); +@import url('./components/rating.css'); +@import url('./components/dropdown.css'); +@import url('./components/search_bar.css'); +@import url('./components/misc.css'); +@import url('./components/calendar.css'); diff --git a/tcf_website/static/css/site/pages/about.css b/tcf_website/static/css/site/pages/about.css new file mode 100644 index 000000000..84fe1526e --- /dev/null +++ b/tcf_website/static/css/site/pages/about.css @@ -0,0 +1,349 @@ +/* + * About Page Styles + * ================= + */ + +/* ===== PAGE LAYOUT ===== */ + +.about-page { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-8) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .about-page { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .about-page { + padding: var(--space-12) var(--space-8) var(--space-16); + } +} + + +/* ===== HERO SECTION ===== */ + +.about-hero { + text-align: center; + padding: var(--space-12) 0; + + border-bottom: 1px solid var(--border); + margin-bottom: var(--space-10); +} + +.about-hero__logo { + width: 120px; + height: 120px; + margin: 0 auto var(--space-6); +} + +.about-hero__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); + margin-bottom: var(--space-2); +} + +.about-hero__tagline { + font-size: var(--text-xl); + color: var(--fg-muted); +} + + +/* ===== DESCRIPTION ===== */ + +.about-description { + max-width: 700px; + margin: 0 auto var(--space-10); + + font-size: var(--text-lg); + text-align: center; + line-height: var(--leading-relaxed); + color: var(--fg-muted); +} + +.about-description strong { + color: var(--fg); +} + + +/* ===== TABS ===== */ + +.about-tabs { + display: flex; + justify-content: center; + gap: var(--space-2); + + margin-bottom: var(--space-8); + + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.about-tab { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-1); + + padding: var(--space-4) var(--space-6); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg-muted); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + cursor: pointer; + + transition: all var(--duration-fast) var(--ease-default); +} + +.about-tab:hover { + border-color: var(--primary); + color: var(--fg); +} + +.about-tab.is-active { + background-color: var(--primary); + border-color: var(--primary); + color: white; +} + +.about-tab__icon { + width: 1.5rem; + height: 1.5rem; +} + + +/* ===== TAB CONTENT ===== */ + +.about-content { + display: none; +} + +.about-content.is-active { + display: block; +} + + +/* ===== CONTENT SECTIONS ===== */ + +.about-section { + margin-bottom: var(--space-8); +} + +.about-section:last-child { + margin-bottom: 0; +} + +.about-section__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-4); +} + +.about-empty { + color: var(--fg-muted); +} + + +/* ===== TEAM GRID ===== */ + +.team-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: var(--space-6); +} + + +/* ===== MEMBER CARD ===== */ + +.member-card { + display: flex; + flex-direction: column; + align-items: center; + + padding: var(--space-6); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + text-align: center; +} + +.member-card__avatar { + width: 88px; + height: 88px; + + margin-bottom: var(--space-4); + + background: linear-gradient(135deg, var(--primary) 0%, var(--color-blue-700) 100%); + border-radius: var(--radius-full); + border: 1px solid var(--border); + overflow: hidden; + + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.member-card__image { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.member-card__initials { + position: absolute; + + font-size: var(--text-2xl); + font-weight: var(--font-bold); + color: white; +} + +.member-card__avatar.has-image .member-card__initials { + display: none; +} + +.member-card__name { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + margin-bottom: var(--space-1); +} + +.member-card__role { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.member-card__class { + margin-top: var(--space-1); + font-size: var(--text-xs); + color: var(--fg-subtle); +} + +.member-card__link { + margin-top: var(--space-2); + font-size: var(--text-sm); + color: var(--primary); + text-decoration: none; +} + +.member-card__link:hover { + text-decoration: underline; +} + + +/* ===== HISTORY SECTION ===== */ + +.history-content { + max-width: 800px; + margin: 0 auto; +} + +.history-content h3 { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-4); +} + +.history-content p { + font-size: var(--text-base); + line-height: var(--leading-relaxed); + color: var(--fg-muted); + margin-bottom: var(--space-4); +} + + +/* ===== HISTORY TIMELINE ===== */ + +.about-history-grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-6); +} + +.history-card { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); + padding: var(--space-4); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + background-color: var(--bg-elevated); +} + +@media (min-width: 900px) { + .history-card { + grid-template-columns: 220px 1fr; + align-items: start; + } +} + +.history-card__image { + width: 100%; + height: 160px; + object-fit: cover; + border-radius: var(--radius-lg); +} + +.history-card__title { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + margin-bottom: var(--space-2); +} + +.history-card__content p { + color: var(--fg-muted); + line-height: var(--leading-relaxed); +} + + +/* ===== SPONSORS SECTION ===== */ + +.sponsors-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: var(--space-6); +} + +.sponsor-copy { + margin-bottom: var(--space-6); +} + +.sponsor-copy h3 { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-2); +} + +.sponsor-copy p { + color: var(--fg-muted); +} + +.sponsor-card { + display: flex; + align-items: center; + justify-content: center; + + padding: var(--space-6); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); +} + +.sponsor-card__logo { + max-width: 150px; + max-height: 60px; + object-fit: contain; +} diff --git a/tcf_website/static/css/site/pages/browse.css b/tcf_website/static/css/site/pages/browse.css new file mode 100644 index 000000000..d339b11e7 --- /dev/null +++ b/tcf_website/static/css/site/pages/browse.css @@ -0,0 +1,331 @@ +/* + * Browse Page Styles + * ================== + */ + +/* ===== PAGE HEADER ===== */ + +.browse-header { + padding: var(--space-12) 0 var(--space-8); +} + +.browse-header__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .browse-header__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .browse-header__inner { + padding: 0 var(--space-8); + } +} + +.browse-header__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); + margin-bottom: var(--space-2); +} + +.browse-header__description { + font-size: var(--text-lg); + color: var(--fg-muted); + max-width: 48rem; +} + + +/* ===== MODE TOGGLE ===== */ + +.mode-toggle { + display: inline-flex; + padding: var(--space-1); + + background-color: var(--bg-muted); + border-radius: var(--radius-lg); +} + +.mode-toggle__option { + padding: var(--space-2) var(--space-4); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg-muted); + + background: transparent; + border: none; + border-radius: var(--radius-md); + + cursor: pointer; + transition: color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.mode-toggle__option:hover { + color: var(--fg); +} + +.mode-toggle__option.is-active { + color: var(--fg); + background-color: var(--bg-elevated); + box-shadow: var(--shadow-sm); +} + + +/* ===== SCHOOLS CONTAINER ===== */ + +.browse-content { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .browse-content { + padding: 0 var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .browse-content { + padding: 0 var(--space-8) var(--space-16); + } +} + + +/* ===== SCHOOL SECTION ===== */ + +.school-section { + margin-bottom: var(--space-8); +} + +.school-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + + padding: var(--space-4) var(--space-5); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + cursor: pointer; + transition: border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default); +} + +.school-header:hover { + border-color: var(--fg-subtle); + box-shadow: var(--shadow-sm); +} + +.school-header.is-open { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom-color: transparent; +} + +.school-header__info { + display: flex; + align-items: center; + gap: var(--space-4); +} + +.school-header__icon { + display: flex; + align-items: center; + justify-content: center; + + width: 3rem; + height: 3rem; + + background: linear-gradient(135deg, var(--color-primary-100) 0%, var(--color-primary-50) 100%); + color: var(--primary); + + border-radius: var(--radius-lg); +} + +[data-theme="dark"] .school-header__icon { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.2) 0%, rgba(59, 130, 246, 0.1) 100%); +} + +.school-header__icon svg { + width: 1.5rem; + height: 1.5rem; +} + +.school-header__title { + font-size: var(--text-lg); + font-weight: var(--font-semibold); +} + +.school-header__count { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.school-header__chevron { + width: 1.25rem; + height: 1.25rem; + color: var(--fg-muted); + transition: transform var(--duration-normal) var(--ease-default); +} + +.school-header.is-open .school-header__chevron { + transform: rotate(180deg); +} + + +/* ===== DEPARTMENTS GRID ===== */ + +.departments-grid { + display: none; + + padding: var(--space-4); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-top: none; + border-bottom-left-radius: var(--radius-xl); + border-bottom-right-radius: var(--radius-xl); +} + +.departments-grid.is-open { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-2); +} + +@media (min-width: 640px) { + .departments-grid.is-open { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .departments-grid.is-open { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (min-width: 1280px) { + .departments-grid.is-open { + grid-template-columns: repeat(4, 1fr); + } +} + + +/* ===== DEPARTMENT CARD ===== */ + +.department-card { + display: flex; + align-items: center; + gap: var(--space-3); + + padding: var(--space-3) var(--space-4); + + text-decoration: none; + color: var(--fg); + + border-radius: var(--radius-lg); + + transition: background-color var(--duration-fast) var(--ease-default); +} + +.department-card:hover { + background-color: var(--bg-muted); + text-decoration: none; +} + +.department-card__name { + font-size: var(--text-sm); + font-weight: var(--font-medium); + flex: 1; +} + +.department-card__arrow { + width: 1rem; + height: 1rem; + color: var(--fg-subtle); + opacity: 0; + transition: opacity var(--duration-fast) var(--ease-default), + transform var(--duration-fast) var(--ease-default); +} + +.department-card:hover .department-card__arrow { + opacity: 1; + transform: translateX(4px); +} + + +/* ===== CLUB CATEGORIES ===== */ + +.club-grid { + display: grid; + gap: var(--space-4); + grid-template-columns: 1fr; +} + +@media (min-width: 640px) { + .club-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .club-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +.club-card { + display: flex; + flex-direction: column; + + padding: var(--space-6); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + text-decoration: none; + color: var(--fg); + + transition: border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default), + transform var(--duration-fast) var(--ease-default); +} + +.club-card:hover { + border-color: var(--fg-subtle); + box-shadow: var(--shadow-md); + transform: translateY(-2px); + text-decoration: none; +} + +.club-card__title { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + margin-bottom: var(--space-2); +} + +.club-card__count { + font-size: var(--text-sm); + color: var(--fg-muted); +} + + +/* ===== SEARCH SECTION ===== */ + +.browse-search { + max-width: 32rem; + margin-bottom: var(--space-6); +} diff --git a/tcf_website/static/css/site/pages/club.css b/tcf_website/static/css/site/pages/club.css new file mode 100644 index 000000000..f6e471549 --- /dev/null +++ b/tcf_website/static/css/site/pages/club.css @@ -0,0 +1,150 @@ +/* + * Club Detail Page Styles + * ======================= + */ + +.club-header { + padding: var(--space-8) 0 var(--space-6); + border-bottom: 1px solid var(--border); + background: linear-gradient(135deg, var(--bg) 0%, var(--bg-muted) 100%); +} + +.club-header__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .club-header__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .club-header__inner { + padding: 0 var(--space-8); + } +} + +.club-header__title-row { + display: flex; + flex-direction: column; + gap: var(--space-2); + margin-top: var(--space-5); +} + +@media (min-width: 768px) { + .club-header__title-row { + flex-direction: row; + align-items: baseline; + gap: var(--space-3); + } +} + +.club-header__title { + font-family: var(--font-display); + font-size: var(--text-5xl); + font-weight: var(--font-normal); +} + +.club-header__category { + font-size: var(--text-lg); + color: var(--fg-muted); +} + +.club-content { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-8) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .club-content { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .club-content { + padding: var(--space-8) var(--space-8) var(--space-16); + } +} + +.club-description-card { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-6); + padding: var(--space-6); + margin-bottom: var(--space-8); + border-radius: var(--radius-2xl); + border: 1px solid var(--border); + background: var(--bg-elevated); +} + +@media (min-width: 960px) { + .club-description-card { + grid-template-columns: 1fr 220px; + } +} + +.club-description-card__title { + font-size: var(--text-2xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-3); +} + +.club-description-card__text { + color: var(--fg-muted); + line-height: var(--leading-relaxed); +} + +.club-meta { + margin-top: var(--space-4); + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-3); +} + +.club-meta__item { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.club-meta__badge { + display: inline-flex; + align-items: center; + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-full); + font-size: var(--text-xs); + font-weight: var(--font-semibold); +} + +.club-meta__badge--success { + color: var(--color-success-700); + background: rgba(34, 197, 94, 0.15); +} + +.club-meta__badge--warning { + color: var(--color-warning-700); + background: rgba(234, 179, 8, 0.2); +} + +.club-description-card__media { + display: flex; + align-items: center; + justify-content: center; +} + +.club-photo { + width: 100%; + max-width: 220px; + max-height: 220px; + object-fit: contain; + border-radius: var(--radius-xl); +} + +.club-reviews { + margin-top: var(--space-2); +} diff --git a/tcf_website/static/css/site/pages/club_category.css b/tcf_website/static/css/site/pages/club_category.css new file mode 100644 index 000000000..9278b0ecf --- /dev/null +++ b/tcf_website/static/css/site/pages/club_category.css @@ -0,0 +1,108 @@ +/* + * Club Category Page Styles + * ========================= + */ + +.club-category-header { + padding: var(--space-8) 0 var(--space-6); + border-bottom: 1px solid var(--border); + background: linear-gradient(135deg, var(--bg) 0%, var(--bg-muted) 100%); +} + +.club-category-header__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .club-category-header__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .club-category-header__inner { + padding: 0 var(--space-8); + } +} + +.club-category-header__title { + margin-top: var(--space-4); + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); +} + +.club-category-header__description { + margin-top: var(--space-3); + color: var(--fg-muted); + max-width: 70ch; +} + +.club-category-content { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-8) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .club-category-content { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .club-category-content { + padding: var(--space-8) var(--space-8) var(--space-16); + } +} + +.club-list { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); + margin-bottom: var(--space-8); +} + +.club-list-card { + display: block; + padding: var(--space-5); + border-radius: var(--radius-2xl); + border: 1px solid var(--border); + background: var(--bg-elevated); + text-decoration: none; + color: var(--fg); + transition: border-color var(--duration-fast) var(--ease-default), + transform var(--duration-fast) var(--ease-default); +} + +.club-list-card:hover { + text-decoration: none; + border-color: var(--primary); + transform: translateY(-1px); +} + +.club-list-card__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + color: var(--primary); + margin-bottom: var(--space-2); +} + +.club-list-card__description { + color: var(--fg-muted); + line-height: var(--leading-relaxed); +} + +.club-category-empty { + padding: var(--space-10); + border-radius: var(--radius-2xl); + border: 1px solid var(--border); + background: var(--bg-elevated); +} + +.club-category-empty__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); +} diff --git a/tcf_website/static/css/site/pages/course.css b/tcf_website/static/css/site/pages/course.css new file mode 100644 index 000000000..f58fce777 --- /dev/null +++ b/tcf_website/static/css/site/pages/course.css @@ -0,0 +1,311 @@ +/* + * Course Page Styles + * ================== + */ + +/* ===== PAGE HEADER ===== */ + +.course-header { + padding: var(--space-8) 0; + background-color: var(--bg); + border-bottom: 1px solid var(--border); +} + +.course-header__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .course-header__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .course-header__inner { + padding: 0 var(--space-8); + } +} + + +/* ===== COURSE TITLE SECTION ===== */ + +.course-title { + display: flex; + flex-direction: column; + gap: var(--space-4); + + margin-top: var(--space-4); +} + +@media (min-width: 768px) { + .course-title { + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + } +} + +.course-title__main { + flex: 1; +} + +.course-title__code { + font-family: var(--font-display); + font-size: var(--text-3xl); + font-weight: var(--font-normal); + color: var(--primary); + margin-bottom: var(--space-1); +} + +@media (min-width: 768px) { + .course-title__code { + font-size: var(--text-4xl); + } +} + +.course-title__name { + font-size: var(--text-xl); + color: var(--fg); +} + +.course-title__actions { + flex-shrink: 0; +} + + +/* ===== COURSE DESCRIPTION ===== */ + +.course-description { + margin-top: var(--space-6); + padding: var(--space-5); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); +} + +.course-description__title { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + margin-bottom: var(--space-4); +} + +.course-description__meta { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); + margin-bottom: var(--space-4); +} + +.course-meta-item { + display: flex; + align-items: flex-start; + gap: var(--space-2); +} + +.course-meta-item__label { + font-size: var(--text-xs); + font-weight: var(--font-semibold); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); + color: var(--fg-muted); +} + +.course-meta-item__value { + font-size: var(--text-sm); + color: var(--fg); +} + +.course-description__text { + font-size: var(--text-base); + color: var(--fg-muted); + line-height: var(--leading-relaxed); +} + + +/* ===== CONTENT AREA ===== */ + +.course-content { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-6) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .course-content { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .course-content { + padding: var(--space-8) var(--space-8) var(--space-16); + } +} + + +/* ===== INSTRUCTORS HEADER ===== */ + +.instructors-header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: var(--space-3); + + margin-bottom: var(--space-6); +} + +.instructors-header__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); +} + +.instructors-header__count { + font-size: var(--text-sm); + color: var(--fg-muted); +} + + +/* ===== RECENCY TOGGLE ===== */ + +.recency-toggle { + display: inline-flex; + padding: var(--space-1); + background-color: var(--bg-muted); + border-radius: var(--radius-lg); +} + +.recency-toggle__option { + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg-muted); + border-radius: var(--radius-md); + text-decoration: none; + transition: color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.recency-toggle__option:hover { + color: var(--fg); + text-decoration: none; +} + +.recency-toggle__option.is-active { + color: var(--fg); + background-color: var(--bg-elevated); + box-shadow: var(--shadow-sm); +} + + +/* ===== INSTRUCTOR LIST ===== */ + +.instructor-list { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + + +/* ===== INSTRUCTOR CARD ===== */ + +.instructor-card { + display: flex; + flex-direction: column; + + padding: var(--space-5); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + text-decoration: none; + color: var(--fg); + + transition: border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default); +} + +.instructor-card:hover { + border-color: var(--fg-subtle); + box-shadow: var(--shadow-md); + text-decoration: none; +} + +@media (min-width: 768px) { + .instructor-card { + flex-direction: row; + align-items: center; + gap: var(--space-6); + } +} + +.instructor-card__main { + flex: 1; + min-width: 0; +} + +.instructor-card__title-row { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: var(--space-2); + margin-bottom: var(--space-2); +} + +.instructor-card__name { + font-size: var(--text-lg); + font-weight: var(--font-semibold); +} + +.instructor-card__semester { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.instructor-card__ratings { + display: flex; + gap: var(--space-3); + + margin-top: var(--space-4); + padding-top: var(--space-4); + border-top: 1px solid var(--border); +} + +@media (min-width: 768px) { + .instructor-card__ratings { + margin-top: 0; + padding-top: 0; + padding-left: var(--space-6); + border-top: none; + border-left: 1px solid var(--border); + } +} + + +/* ===== SECTION TIMES ===== */ + +.section-times { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + + margin-top: var(--space-2); +} + +.section-time { + display: inline-flex; + align-items: center; + + padding: var(--space-1) var(--space-2); + + font-size: var(--text-xs); + + background-color: var(--bg-muted); + border-radius: var(--radius-md); + white-space: nowrap; +} diff --git a/tcf_website/static/css/site/pages/course_instructor.css b/tcf_website/static/css/site/pages/course_instructor.css new file mode 100644 index 000000000..5dae15e69 --- /dev/null +++ b/tcf_website/static/css/site/pages/course_instructor.css @@ -0,0 +1,683 @@ +/* + * Course-Instructor Page Styles + * ============================= + */ + +/* ===== PAGE HEADER ===== */ + +.course-instructor-header { + padding: var(--space-8) 0; + background: linear-gradient(135deg, var(--bg) 0%, var(--bg-muted) 100%); + border-bottom: 1px solid var(--border); +} + +.course-instructor-header__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .course-instructor-header__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .course-instructor-header__inner { + padding: 0 var(--space-8); + } +} + +.course-instructor-title { + display: flex; + flex-direction: column; + gap: var(--space-2); + + margin-top: var(--space-6); +} + +@media (min-width: 768px) { + .course-instructor-title { + flex-direction: row; + align-items: baseline; + gap: var(--space-4); + } +} + +.course-instructor-title__code { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); +} + +.course-instructor-title__name { + font-size: var(--text-xl); + color: var(--fg-muted); +} + +/* ===== INSTRUCTOR ROW ===== */ + +.instructor-row { + display: flex; + flex-direction: column; + gap: var(--space-4); + + margin-top: var(--space-4); +} + +@media (min-width: 768px) { + .instructor-row { + flex-direction: row; + align-items: center; + justify-content: space-between; + } +} + +.instructor-row__left { + display: flex; + align-items: center; + gap: var(--space-4); +} + +.instructor-link { + display: flex; + align-items: center; + gap: var(--space-2); + + font-size: var(--text-lg); + font-weight: var(--font-medium); + color: var(--primary); + + text-decoration: none; +} + +.instructor-link:hover { + text-decoration: underline; +} + +.instructor-link svg { + width: 1.25rem; + height: 1.25rem; +} + +.semester-badge { + padding: var(--space-2) var(--space-3); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + + background-color: var(--primary); + color: white; + + border-radius: var(--radius-lg); +} + +/* ===== CONTENT AREA ===== */ + +.course-instructor-content { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-8) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .course-instructor-content { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .course-instructor-content { + padding: var(--space-8) var(--space-8) var(--space-16); + } +} + +/* ===== STATS GRID ===== */ + +.stats-grid { + display: grid; + grid-template-columns: 1fr; + align-items: stretch; + gap: var(--space-6); + + margin-bottom: var(--space-8); +} + +@media (min-width: 1024px) { + .stats-grid { + grid-template-columns: 1fr 1fr; + } +} + +.ratings-panel, +.grades-panel, +.sections-panel { + height: 100%; +} + +/* ===== RATINGS PANEL ===== */ + +.ratings-panel { + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-2xl); + + padding: var(--space-6); +} + +.ratings-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + + margin-bottom: var(--space-6); +} + +.ratings-panel__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); +} + +.ratings-panel__count { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +/* ===== RATING ROW ===== */ + +.rating-row { + display: flex; + align-items: center; + gap: var(--space-3); + + margin-bottom: var(--space-4); +} + +.rating-row:last-child { + margin-bottom: 0; +} + +.rating-row__label { + width: 100px; + flex-shrink: 0; + + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.rating-row__bar { + flex: 1; + height: 8px; + + background-color: var(--bg-muted); + border-radius: var(--radius-full); + + overflow: hidden; +} + +.rating-row__fill { + height: 100%; + border-radius: var(--radius-full); + + transition: width var(--duration-normal) var(--ease-default); +} + +.rating-row__fill--success { + background-color: var(--success); +} + +.rating-row__fill--warning { + background-color: var(--warning); +} + +.rating-row__fill--danger { + background-color: var(--danger); +} + +.rating-row__value { + width: 40px; + text-align: right; + + font-size: var(--text-lg); + font-weight: var(--font-bold); +} + +.rating-row__value--muted { + color: var(--fg-muted); +} + +.ratings-panel__divider { + border-top: 1px solid var(--border); + margin: var(--space-5) 0 var(--space-3); +} + +.ratings-panel__subheading { + margin: 0; + font-size: var(--text-xs); + font-weight: var(--font-semibold); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + color: var(--fg-muted); +} + +.ratings-panel__subheading-row { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--space-3); + margin: 0 0 var(--space-3); +} + +.ratings-panel__hours-summary { + display: inline-flex; + align-items: baseline; + gap: var(--space-1); + padding: 0 var(--space-2); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background-color: var(--bg); +} + +.ratings-panel__hours-value { + font-size: var(--text-base); + font-weight: var(--font-semibold); + color: var(--fg); +} + +.ratings-panel__hours-unit { + font-size: var(--text-xs); + font-weight: var(--font-medium); + color: var(--fg-muted); + text-transform: lowercase; +} + +.rating-row--compact { + margin-bottom: var(--space-3); +} + +.rating-row--compact .rating-row__label { + width: 88px; +} + +/* ===== SECTIONS PANEL ===== */ + +.sections-panel { + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-2xl); + + display: flex; + flex-direction: column; + padding: var(--space-6); + min-height: 0; +} + +.sections-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + + margin-bottom: var(--space-4); +} + +.sections-panel__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); +} + +.sections-panel__count { + display: inline-flex; + align-items: center; + justify-content: center; + + min-width: 1.5rem; + height: 1.5rem; + + padding: 0 var(--space-2); + + font-size: var(--text-sm); + font-weight: var(--font-bold); + + background-color: var(--primary); + color: white; + + border-radius: var(--radius-full); +} + +.sections-panel__list { + flex: 1; + min-height: 0; + max-height: clamp(18rem, 52vh, 30rem); + overflow-y: auto; + padding-right: var(--space-2); +} + +.sections-panel__group + .sections-panel__group { + margin-top: var(--space-4); +} + +.sections-panel__group-title { + margin: 0 0 var(--space-2); + font-size: var(--text-xs); + font-weight: var(--font-semibold); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + color: var(--fg-muted); +} + +/* ===== SECTION CARD ===== */ + +.section-card { + display: flex; + flex-direction: column; + gap: var(--space-2); + + padding: var(--space-4); + margin-bottom: var(--space-3); + + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + text-decoration: none; + color: var(--fg); + + transition: border-color var(--duration-fast) var(--ease-default); +} + +.section-card:hover { + border-color: var(--primary); + text-decoration: none; +} + +.section-card:last-child { + margin-bottom: 0; +} + +.section-card__header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.section-card__number { + font-weight: var(--font-semibold); +} + +.section-card__type { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.section-card__times { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.section-card__enrollment { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); + + margin-top: var(--space-2); +} + +.enrollment-stat { + display: flex; + align-items: center; + gap: var(--space-2); + + font-size: var(--text-sm); +} + +.enrollment-stat__bar { + width: 60px; + height: 6px; + + background-color: var(--bg-muted); + border-radius: var(--radius-full); + + overflow: hidden; +} + +.enrollment-stat__fill { + height: 100%; + border-radius: var(--radius-full); +} + +.enrollment-stat__fill--success { + background-color: var(--success); +} + +.enrollment-stat__fill--danger { + background-color: var(--danger); +} + +.enrollment-stat__fill--warning { + background-color: var(--warning); +} + +/* ===== REVIEWS SECTION ===== */ + +.reviews-header { + display: flex; + flex-direction: column; + gap: var(--space-4); + + margin-bottom: var(--space-6); +} + +@media (min-width: 768px) { + .reviews-header { + flex-direction: row; + align-items: center; + justify-content: space-between; + } +} + +.reviews-header__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); +} + +.reviews-header__actions { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: var(--space-3); +} + +/* ===== REVIEW CARD ===== */ + +.review-card { + padding: var(--space-6); + margin-bottom: var(--space-4); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-2xl); +} + +.review-card__header { + display: flex; + align-items: flex-start; + justify-content: space-between; + + margin-bottom: var(--space-4); +} + +.review-card__meta { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.review-card__semester { + font-weight: var(--font-medium); + color: var(--fg); +} + +.review-card__ratings { + display: flex; + gap: var(--space-4); +} + +.review-rating { + text-align: center; +} + +.review-rating__value { + font-size: var(--text-lg); + font-weight: var(--font-bold); +} + +.review-rating__label { + font-size: var(--text-xs); + color: var(--fg-muted); + text-transform: uppercase; +} + +.review-card__text { + font-size: var(--text-base); + line-height: var(--leading-relaxed); + + margin-bottom: var(--space-4); +} + +.review-card__category-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--space-2) var(--space-4); + margin-bottom: var(--space-4); +} + +@media (min-width: 1024px) { + .review-card__category-grid { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } +} + +.review-category { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--space-2); +} + +.review-category__label { + font-size: var(--text-xs); + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); +} + +.review-category__value { + font-size: var(--text-sm); + font-weight: var(--font-semibold); +} + +.review-card__footer { + display: flex; + align-items: center; + justify-content: space-between; + + padding-top: var(--space-4); + border-top: 1px solid var(--border); +} + +.review-card__votes { + display: flex; + gap: var(--space-2); +} + +.vote-btn { + display: inline-flex; + align-items: center; + gap: var(--space-1); + + padding: var(--space-2) var(--space-3); + + font-size: var(--text-sm); + + background: none; + border: 1px solid var(--border); + border-radius: var(--radius-lg); + + color: var(--fg-muted); + + cursor: pointer; + + transition: all var(--duration-fast) var(--ease-default); +} + +.vote-btn:hover { + border-color: var(--primary); + color: var(--primary); +} + +.vote-btn.is-active { + background-color: var(--primary); + border-color: var(--primary); + color: white; +} + +.vote-btn svg { + width: 1rem; + height: 1rem; +} + +.review-card__date { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +/* ===== ADD REVIEW BUTTON ===== */ + +.add-review-btn svg { + width: 1rem; + height: 1rem; +} + +.reviews-sort-select { + width: 11rem; + flex-shrink: 0; +} + +.add-review-btn { + flex-shrink: 0; +} + +.pagination__status { + background-color: var(--bg-muted); + color: var(--fg); + cursor: default; +} + +/* ===== EMPTY STATE ===== */ + +.reviews-empty { + text-align: center; + padding: var(--space-12); +} + +.reviews-empty__icon { + width: 4rem; + height: 4rem; + margin: 0 auto var(--space-4); + color: var(--fg-muted); +} + +.reviews-empty__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-2); +} + +.reviews-empty__text { + color: var(--fg-muted); + margin-bottom: var(--space-6); +} diff --git a/tcf_website/static/css/site/pages/course_instructor_grades.css b/tcf_website/static/css/site/pages/course_instructor_grades.css new file mode 100644 index 000000000..68de11d11 --- /dev/null +++ b/tcf_website/static/css/site/pages/course_instructor_grades.css @@ -0,0 +1,83 @@ + +/* ===== GRADES CHART ===== */ + +.grades-panel { + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-2xl); + + padding: var(--space-6); + + display: flex; + flex-direction: column; +} + +.grades-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + + margin-bottom: var(--space-6); +} + +.grades-panel__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); +} + +.grades-panel__chart-container { + position: relative; + height: 250px; + width: 100%; +} + +.grades-panel__empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 200px; + color: var(--fg-muted); + text-align: center; +} + +.grades-panel__empty-icon { + width: 3rem; + height: 3rem; + margin-bottom: var(--space-2); + opacity: 0.5; +} + +.grades-panel__summary { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: var(--space-4); + margin-top: var(--space-4); + padding-top: var(--space-4); + border-top: 1px solid var(--border); +} + +.grades-metric { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.grades-metric__label { + font-size: var(--text-xs); + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); +} + +.grades-metric__value { + font-size: var(--text-xl); + font-weight: var(--font-semibold); +} + +/* Ensure stats grid has 3 columns on large screens if possible, or reflow */ +@media (min-width: 1280px) { + .stats-grid { + grid-template-columns: 1fr 1fr 1fr; + } +} diff --git a/tcf_website/static/css/site/pages/department.css b/tcf_website/static/css/site/pages/department.css new file mode 100644 index 000000000..fbd8f3d11 --- /dev/null +++ b/tcf_website/static/css/site/pages/department.css @@ -0,0 +1,329 @@ +/* + * Department Page Styles + * ====================== + */ + +/* ===== PAGE HEADER ===== */ + +.department-header { + padding: var(--space-8) 0; + background-color: var(--bg); + border-bottom: 1px solid var(--border); +} + +.department-header__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .department-header__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .department-header__inner { + padding: 0 var(--space-8); + } +} + +/* ===== BREADCRUMB ===== */ + +.breadcrumb { + display: flex; + align-items: center; + gap: var(--space-2); + + margin-bottom: var(--space-4); + + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.breadcrumb__link { + color: var(--fg-muted); + text-decoration: none; + transition: color var(--duration-fast) var(--ease-default); +} + +.breadcrumb__link:hover { + color: var(--primary); + text-decoration: none; +} + +.breadcrumb__separator { + color: var(--fg-subtle); +} + +.breadcrumb__current { + color: var(--fg); + font-weight: var(--font-medium); +} + +/* ===== TOOLBAR ===== */ + +.department-toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: var(--space-4); +} + +.department-toolbar__title { + font-family: var(--font-display); + font-size: var(--text-3xl); + font-weight: var(--font-normal); +} + +.department-toolbar__filters { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-3); +} + +/* ===== RECENCY TOGGLE ===== */ + +.recency-toggle { + display: inline-flex; + padding: var(--space-1); + + background-color: var(--bg-muted); + border-radius: var(--radius-lg); +} + +.recency-toggle__option { + padding: var(--space-2) var(--space-3); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg-muted); + + background: transparent; + border: none; + border-radius: var(--radius-md); + + cursor: pointer; + text-decoration: none; + transition: + color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.recency-toggle__option:hover { + color: var(--fg); + text-decoration: none; +} + +.recency-toggle__option.is-active { + color: var(--fg); + background-color: var(--bg-elevated); + box-shadow: var(--shadow-sm); +} + +/* ===== SORT DROPDOWN ===== */ + +.sort-select { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.sort-select__label { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.sort-select select { + min-width: 10rem; +} + +/* ===== CONTENT AREA ===== */ + +.department-content { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-6) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .department-content { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .department-content { + padding: var(--space-8) var(--space-8) var(--space-16); + } +} + +/* ===== COURSE LIST ===== */ + +.course-list { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +/* ===== COURSE CARD ===== */ + +.course-card { + display: flex; + flex-direction: column; + + padding: var(--space-5); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + text-decoration: none; + color: var(--fg); + + transition: + border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default); +} + +.course-card:hover { + border-color: var(--fg-subtle); + box-shadow: var(--shadow-md); + text-decoration: none; +} + +@media (min-width: 768px) { + .course-card { + flex-direction: row; + align-items: center; + gap: var(--space-6); + } +} + +.course-card__main { + flex: 1; + min-width: 0; +} + +.course-card__header { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: var(--space-2); + + margin-bottom: var(--space-1); +} + +.course-card__code { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + color: var(--primary); +} + +.course-card__title { + font-size: var(--text-base); + color: var(--fg); +} + +.course-card__meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-4); + + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.course-card__stat { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.course-card__stat svg { + width: 1rem; + height: 1rem; +} + +/* ===== COURSE CARD RATINGS ===== */ + +.course-card__ratings { + display: flex; + gap: var(--space-3); + + margin-top: var(--space-4); + padding-top: var(--space-4); + border-top: 1px solid var(--border); +} + +@media (min-width: 768px) { + .course-card__ratings { + margin-top: 0; + padding-top: 0; + padding-left: var(--space-6); + border-top: none; + border-left: 1px solid var(--border); + } +} + +.rating-stat { + text-align: center; + padding: var(--space-2) var(--space-3); +} + +.rating-stat__value { + font-size: var(--text-xl); + font-weight: var(--font-bold); +} + +.rating-stat__value--success { + color: var(--success); +} + +.rating-stat__value--warning { + color: var(--warning); +} + +.rating-stat__value--danger { + color: var(--danger); +} + +.rating-stat__value--muted { + color: var(--fg-muted); +} + +.rating-stat__label { + font-size: var(--text-xs); + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); +} + +/* ===== EMPTY STATE ===== */ + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + padding: var(--space-16) var(--space-4); + + text-align: center; +} + +.empty-state__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-2); +} + +.empty-state__description { + font-size: var(--text-base); + color: var(--fg-muted); + max-width: 24rem; +} diff --git a/tcf_website/static/css/site/pages/instructor.css b/tcf_website/static/css/site/pages/instructor.css new file mode 100644 index 000000000..72d0784aa --- /dev/null +++ b/tcf_website/static/css/site/pages/instructor.css @@ -0,0 +1,277 @@ +/* + * Instructor Page Styles + * ====================== + */ + +/* ===== PAGE HEADER ===== */ + +.instructor-header { + padding: var(--space-8) 0 var(--space-10); + background: linear-gradient(135deg, var(--bg) 0%, var(--bg-muted) 100%); + border-bottom: 1px solid var(--border); +} + +.instructor-header__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .instructor-header__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .instructor-header__inner { + padding: 0 var(--space-8); + } +} + + +/* ===== INSTRUCTOR PROFILE ===== */ + +.instructor-profile { + display: flex; + flex-direction: column; + gap: var(--space-6); + + margin-top: var(--space-6); +} + +@media (min-width: 768px) { + .instructor-profile { + flex-direction: row; + align-items: flex-start; + } +} + +.instructor-profile__info { + flex: 1; +} + +.instructor-profile__name { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); + margin-bottom: var(--space-2); +} + +.instructor-profile__email { + font-size: var(--text-base); + color: var(--fg-muted); + margin-bottom: var(--space-4); +} + +.instructor-profile__email a { + color: var(--primary); + text-decoration: none; +} + +.instructor-profile__email a:hover { + text-decoration: underline; +} + +.instructor-profile__badge { + display: inline-flex; + align-items: center; + gap: var(--space-2); + + padding: var(--space-2) var(--space-3); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + + background-color: var(--color-warning-100); + color: var(--color-warning-600); + + border-radius: var(--radius-lg); +} + +[data-theme="dark"] .instructor-profile__badge { + background-color: rgba(234, 179, 8, 0.15); + color: var(--warning); +} + + +/* ===== STATS CARD ===== */ + +.stats-card { + display: flex; + gap: var(--space-6); + + padding: var(--space-5); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); +} + +.stats-card__item { + text-align: center; +} + +.stats-card__value { + font-size: var(--text-2xl); + font-weight: var(--font-bold); +} + +.stats-card__value--success { + color: var(--success); +} + +.stats-card__value--warning { + color: var(--warning); +} + +.stats-card__value--danger { + color: var(--danger); +} + +.stats-card__value--muted { + color: var(--fg-muted); +} + +.stats-card__label { + font-size: var(--text-xs); + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); +} + + +/* ===== CONTENT AREA ===== */ + +.instructor-content { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-8) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .instructor-content { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .instructor-content { + padding: var(--space-8) var(--space-8) var(--space-16); + } +} + + +/* ===== COURSES SECTION ===== */ + +.courses-section { + margin-bottom: var(--space-10); +} + +.courses-section__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-4); + + padding-bottom: var(--space-2); + border-bottom: 2px solid var(--primary); +} + + +/* ===== COURSE TAUGHT CARD ===== */ + +.course-taught { + display: flex; + flex-direction: column; + + padding: var(--space-4); + margin-bottom: var(--space-3); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + + text-decoration: none; + color: var(--fg); + + transition: border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default); +} + +.course-taught:hover { + border-color: var(--primary); + box-shadow: var(--shadow-md); + text-decoration: none; +} + +@media (min-width: 768px) { + .course-taught { + flex-direction: row; + align-items: center; + justify-content: space-between; + } +} + +.course-taught__name { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + color: var(--primary); +} + +.course-taught__stats { + display: flex; + gap: var(--space-4); + + margin-top: var(--space-3); +} + +@media (min-width: 768px) { + .course-taught__stats { + margin-top: 0; + } +} + +.course-taught__stat { + text-align: center; +} + +.course-taught__stat-value { + font-size: var(--text-lg); + font-weight: var(--font-bold); +} + +.course-taught__stat-label { + font-size: var(--text-xs); + color: var(--fg-muted); + text-transform: uppercase; +} + + +/* ===== NOT TEACHING ALERT ===== */ + +.not-teaching-alert { + display: flex; + align-items: center; + gap: var(--space-3); + + padding: var(--space-4); + margin-top: var(--space-4); + + background-color: var(--color-warning-50); + border: 1px solid var(--color-warning-200); + border-radius: var(--radius-lg); + + color: var(--color-warning-700); +} + +[data-theme="dark"] .not-teaching-alert { + background-color: rgba(234, 179, 8, 0.1); + border-color: rgba(234, 179, 8, 0.3); + color: var(--warning); +} + +.not-teaching-alert svg { + width: 1.25rem; + height: 1.25rem; + flex-shrink: 0; +} diff --git a/tcf_website/static/css/site/pages/landing.css b/tcf_website/static/css/site/pages/landing.css new file mode 100644 index 000000000..30c351ca1 --- /dev/null +++ b/tcf_website/static/css/site/pages/landing.css @@ -0,0 +1,350 @@ +/* + * Landing Page Styles + * =================== + */ + +/* ===== HERO SECTION ===== */ + +.hero { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + min-height: 70vh; + padding: var(--space-16) var(--space-4); + + text-align: center; + + background: linear-gradient( + 135deg, + var(--bg) 0%, + var(--bg-muted) 100% + ); + + /* overflow: hidden; Removed to allow search dropdown to overlap next section */ + z-index: 10; /* Ensure it stays above features */ +} + +[data-theme="dark"] .hero { + background: linear-gradient( + 135deg, + var(--color-neutral-950) 0%, + var(--color-neutral-900) 100% + ); +} + +.hero::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + + background-image: + radial-gradient(circle at 20% 80%, rgba(37, 99, 235, 0.1) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba(249, 115, 22, 0.1) 0%, transparent 50%); + + pointer-events: none; +} + +[data-theme="dark"] .hero::before { + background-image: + radial-gradient(circle at 20% 80%, rgba(59, 130, 246, 0.15) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba(249, 115, 22, 0.15) 0%, transparent 50%); +} + +.hero__content { + position: relative; + z-index: 1; + + max-width: 48rem; + margin: 0 auto; +} + +.hero__logo { + width: clamp(128px, 18vw, 232px); + height: auto; + margin: 0 auto var(--space-5); +} + + +.hero__title { + font-family: var(--font-display); + font-size: var(--text-5xl); + font-weight: var(--font-normal); + line-height: var(--leading-tight); + letter-spacing: var(--tracking-tight); + + margin-bottom: var(--space-6); +} + +@media (min-width: 768px) { + .hero__title { + font-size: var(--text-6xl); + } +} + +@media (min-width: 1024px) { + .hero__title { + font-size: var(--text-7xl); + } +} + +.hero__title-gradient { + background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + + + +.hero__search { + max-width: 36rem; + margin: 0 auto var(--space-8); +} + + + + +/* ===== FEATURES SECTION ===== */ + +.features { + padding: var(--space-24) var(--space-4); +} + +.features__header { + text-align: center; + max-width: 48rem; + margin: 0 auto var(--space-16); +} + +.features__subtitle { + font-size: var(--text-sm); + font-weight: var(--font-semibold); + color: var(--primary); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); + + margin-bottom: var(--space-3); +} + +.features__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); + + margin-bottom: var(--space-4); +} + +@media (min-width: 768px) { + .features__title { + font-size: var(--text-5xl); + } +} + +.features__description { + font-size: var(--text-lg); + color: var(--fg-muted); +} + +.features__grid { + display: grid; + gap: var(--space-6); + + max-width: var(--size-max); + margin: 0 auto; +} + +@media (min-width: 768px) { + .features__grid { + grid-template-columns: repeat(3, 1fr); + gap: var(--space-8); + } +} + +.feature-card { + position: relative; + + padding: var(--space-8); + + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-2xl); + + text-align: center; + + transition: border-color var(--duration-normal) var(--ease-default), + box-shadow var(--duration-normal) var(--ease-default), + transform var(--duration-normal) var(--ease-default); +} + +.feature-card:hover { + border-color: var(--fg-subtle); + box-shadow: var(--shadow-lg); + transform: translateY(-4px); +} + +.feature-card__icon { + display: inline-flex; + align-items: center; + justify-content: center; + + width: 4rem; + height: 4rem; + margin-bottom: var(--space-6); + + border-radius: var(--radius-xl); + + background: linear-gradient(135deg, var(--color-primary-100) 0%, var(--color-primary-50) 100%); + color: var(--primary); +} + +[data-theme="dark"] .feature-card__icon { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.2) 0%, rgba(59, 130, 246, 0.1) 100%); +} + +.feature-card__icon svg { + width: 2rem; + height: 2rem; +} + +.feature-card__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-3); +} + +.feature-card__description { + font-size: var(--text-sm); + color: var(--fg-muted); + line-height: var(--leading-relaxed); +} + +.feature-card__link { + display: inline-flex; + align-items: center; + gap: var(--space-1); + + margin-top: var(--space-4); + + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--primary); +} + +.feature-card__link svg { + width: 1rem; + height: 1rem; + transition: transform var(--duration-fast) var(--ease-default); +} + +.feature-card:hover .feature-card__link svg { + transform: translateX(4px); +} + + +/* ===== FAQ SECTION ===== */ + +.faq { + padding: var(--space-24) var(--space-4); + background-color: var(--bg-muted); +} + +[data-theme="dark"] .faq { + background-color: var(--color-neutral-900); +} + +.faq__inner { + max-width: 48rem; + margin: 0 auto; +} + +.faq__header { + text-align: center; + margin-bottom: var(--space-12); +} + +.faq__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); + + margin-bottom: var(--space-4); +} + + +/* ===== TEAM SECTION ===== */ + +.team { + padding: var(--space-24) var(--space-4); +} + +.team__header { + text-align: center; + max-width: 48rem; + margin: 0 auto var(--space-12); +} + +.team__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); + + margin-bottom: var(--space-4); +} + +.team__description { + font-size: var(--text-lg); + color: var(--fg-muted); +} + +.team__grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-4); + + max-width: 64rem; + margin: 0 auto; +} + +@media (min-width: 640px) { + .team__grid { + grid-template-columns: repeat(3, 1fr); + gap: var(--space-6); + } +} + +@media (min-width: 1024px) { + .team__grid { + grid-template-columns: repeat(4, 1fr); + } +} + +.team-member { + text-align: center; +} + +.team-member__avatar { + width: 6rem; + height: 6rem; + margin: 0 auto var(--space-3); + + border-radius: var(--radius-full); + object-fit: cover; + + border: 2px solid var(--border); +} + +.team-member__name { + font-size: var(--text-base); + font-weight: var(--font-semibold); +} + +.team-member__role { + font-size: var(--text-sm); + color: var(--fg-muted); +} diff --git a/tcf_website/static/css/site/pages/legal.css b/tcf_website/static/css/site/pages/legal.css new file mode 100644 index 000000000..c2dc2ddf7 --- /dev/null +++ b/tcf_website/static/css/site/pages/legal.css @@ -0,0 +1,61 @@ +/* + * Legal Pages (Terms / Privacy) + * ============================= + */ + +.legal-page { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-8) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .legal-page { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .legal-page { + padding: var(--space-8) var(--space-8) var(--space-16); + } +} + +.legal-card { + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-2xl); + padding: var(--space-6); +} + +.legal-card h1 { + font-size: var(--text-3xl); + font-weight: var(--font-semibold); + margin-bottom: var(--space-2); +} + +.legal-card h6 { + font-size: var(--text-sm); + color: var(--fg-muted); + margin-bottom: var(--space-5); +} + +.legal-card h4 { + font-size: var(--text-lg); + margin: var(--space-4) 0 var(--space-2); +} + +.legal-card p, +.legal-card li { + color: var(--fg-muted); + line-height: var(--leading-relaxed); +} + +.legal-card a { + color: var(--primary); +} + +.legal-card ul { + margin: var(--space-2) 0; + padding-left: var(--space-6); +} diff --git a/tcf_website/static/css/site/pages/profile.css b/tcf_website/static/css/site/pages/profile.css new file mode 100644 index 000000000..353cf3d88 --- /dev/null +++ b/tcf_website/static/css/site/pages/profile.css @@ -0,0 +1,260 @@ +/* + * Profile Page Styles + * =================== + */ + +/* ===== PAGE LAYOUT ===== */ + +.profile-page { + max-width: 800px; + margin: 0 auto; + padding: var(--space-8) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .profile-page { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .profile-page { + padding: var(--space-12) var(--space-8) var(--space-16); + } +} + +.profile-page__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); + text-align: center; + margin-bottom: var(--space-8); +} + + +/* ===== PROFILE CARD ===== */ + +.profile-card { + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-2xl); + + overflow: hidden; +} + +.profile-card__header { + position: relative; + height: 160px; + + background: linear-gradient(135deg, var(--primary) 0%, var(--color-blue-700) 100%); + + overflow: hidden; +} + +.profile-card__header-image { + width: 100%; + height: 100%; + object-fit: cover; + + filter: brightness(0.7); +} + +.profile-card__body { + padding: var(--space-8); +} + + +/* ===== FORM SECTION ===== */ + +.form-section { + margin-bottom: var(--space-8); +} + +.form-section__title { + display: flex; + align-items: center; + gap: var(--space-2); + + font-size: var(--text-xl); + font-weight: var(--font-semibold); + + margin-bottom: var(--space-6); + padding-bottom: var(--space-3); + border-bottom: 1px solid var(--border); +} + +.form-section__title svg { + width: 1.25rem; + height: 1.25rem; + color: var(--fg-muted); +} + + +/* ===== FORM GRID ===== */ + +.form-grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); +} + +@media (min-width: 640px) { + .form-grid { + grid-template-columns: 1fr 1fr; + } +} + +@media (min-width: 768px) { + .form-grid { + grid-template-columns: 1fr 1fr 1fr; + } +} + + +/* ===== FORM GROUP ===== */ + +.form-group { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.form-group__label { + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg); +} + +.form-group__input { + padding: var(--space-3) var(--space-4); + + font-size: var(--text-base); + + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + + color: var(--fg); + + transition: border-color var(--duration-fast) var(--ease-default), + box-shadow var(--duration-fast) var(--ease-default); +} + +.form-group__input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15); +} + +.form-group__input::placeholder { + color: var(--fg-muted); +} + +.form-group__hint { + font-size: var(--text-xs); + color: var(--fg-muted); +} + + +/* ===== BUTTON GROUP ===== */ + +.button-group { + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + + margin-top: var(--space-6); + padding-top: var(--space-6); + border-top: 1px solid var(--border); +} + + +/* ===== BUTTONS ===== */ + +.btn-primary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + + padding: var(--space-3) var(--space-5); + + font-size: var(--text-base); + font-weight: var(--font-semibold); + + background-color: var(--primary); + color: white; + + border: none; + border-radius: var(--radius-xl); + + cursor: pointer; + + transition: background-color var(--duration-fast) var(--ease-default); +} + +.btn-primary:hover { + background-color: var(--color-blue-700); +} + +.btn-danger { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + + padding: var(--space-3) var(--space-5); + + font-size: var(--text-base); + font-weight: var(--font-semibold); + + background-color: transparent; + color: var(--danger); + + border: 1px solid var(--danger); + border-radius: var(--radius-xl); + + cursor: pointer; + + transition: background-color var(--duration-fast) var(--ease-default), + color var(--duration-fast) var(--ease-default); +} + +.btn-danger:hover { + background-color: var(--danger); + color: white; +} + + +/* ===== DELETE MODAL ===== */ + +.delete-warning { + display: flex; + align-items: flex-start; + gap: var(--space-4); + + padding: var(--space-4); + margin-bottom: var(--space-6); + + background-color: var(--color-red-50); + border: 1px solid var(--color-red-200); + border-radius: var(--radius-lg); + + color: var(--color-red-700); +} + +[data-theme="dark"] .delete-warning { + background-color: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.3); + color: var(--danger); +} + +.delete-warning svg { + width: 1.5rem; + height: 1.5rem; + flex-shrink: 0; +} + +.delete-warning__text { + font-size: var(--text-sm); +} diff --git a/tcf_website/static/css/site/pages/review.css b/tcf_website/static/css/site/pages/review.css new file mode 100644 index 000000000..382afaa8b --- /dev/null +++ b/tcf_website/static/css/site/pages/review.css @@ -0,0 +1,277 @@ +/* + * Review Page Styles + * ================== + */ + +.review-container { + max-width: 48rem; /* ~768px */ + margin: 0 auto; + padding: var(--space-8) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .review-container { + padding: var(--space-12) var(--space-6) var(--space-20); + } +} + +.review-header { + margin-bottom: var(--space-8); + text-align: center; +} + +.review-header__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); + margin-bottom: var(--space-2); +} + +.review-header__subtitle { + color: var(--fg-muted); + font-size: var(--text-lg); +} + +/* ===== FORM SECTIONS ===== */ + +.review-section { + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + padding: var(--space-6); + margin-bottom: var(--space-6); + transition: box-shadow var(--duration-default) var(--ease-default); +} + +.review-section:focus-within { + box-shadow: var(--shadow-md); + border-color: var(--border-muted); +} + +.review-section__title { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + margin-bottom: var(--space-4); + color: var(--fg); + display: flex; + align-items: center; + gap: var(--space-2); +} + +.review-section__title-icon { + color: var(--primary); +} + +/* ===== RATINGS ===== */ + +.rating-grid { + display: grid; + gap: var(--space-6); +} + +@media (min-width: 640px) { + .rating-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.rating-field { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.rating-field__label { + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg); +} + +/* Segmented Control for Ratings */ +.rating-options { + display: flex; + background-color: var(--bg-muted); + border-radius: var(--radius-lg); + padding: var(--space-1); +} + +.rating-option { + flex: 1; + position: relative; +} + +.rating-option input { + position: absolute; + opacity: 0; + width: 100%; + height: 100%; + cursor: pointer; +} + +.rating-option__label { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + padding: var(--space-2) 0; + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--fg-muted); + border-radius: var(--radius-md); + transition: all var(--duration-fast) var(--ease-default); + cursor: pointer; +} + +.rating-option input:checked + .rating-option__label { + background-color: var(--bg-elevated); + color: var(--primary); + box-shadow: var(--shadow-sm); +} + +.rating-option input:focus-visible + .rating-option__label { + outline: 2px solid var(--primary); + outline-offset: -2px; +} + + +/* ===== WORKLOAD INPUTS ===== */ + +.workload-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-4); +} + +.workload-field { + position: relative; +} + +.workload-field label { + display: block; + font-size: var(--text-xs); + font-weight: var(--font-medium); + color: var(--fg-muted); + margin-bottom: var(--space-1); +} + +.workload-input-wrapper { + position: relative; +} + +.workload-input { + width: 100%; + padding: var(--space-2) var(--space-3); + padding-right: var(--space-8); + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-md); + font-size: var(--text-base); + color: var(--fg); + transition: border-color 0.2s; +} + +.workload-input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px var(--primary-100); +} + +.workload-suffix { + position: absolute; + right: var(--space-3); + top: 50%; + transform: translateY(-50%); + font-size: var(--text-xs); + color: var(--fg-muted); + pointer-events: none; +} + +/* ===== TEXTAREA ===== */ + +.review-textarea { + width: 100%; + min-height: 12rem; + padding: var(--space-4); + background-color: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + font-family: var(--font-sans); + font-size: var(--text-base); + color: var(--fg); + resize: vertical; + transition: border-color 0.2s; +} + +.review-textarea:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px var(--color-primary-100); +} + +.word-count { + display: block; + text-align: right; + font-size: var(--text-xs); + color: var(--fg-muted); + margin-top: var(--space-2); +} + +.word-count--error { + color: var(--danger); +} + +/* ===== CONTEXT INFO ===== */ + +.context-info { + display: flex; + align-items: center; + gap: var(--space-4); + padding: var(--space-4); + background-color: var(--bg-muted); + border-radius: var(--radius-lg); + margin-bottom: var(--space-6); +} + +.context-icon { + width: 3rem; + height: 3rem; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--bg-elevated); + border-radius: var(--radius-full); + color: var(--primary); + font-size: 1.5rem; +} + +.context-details { + flex: 1; +} + +.context-title { + font-weight: var(--font-semibold); + font-size: var(--text-lg); + color: var(--fg); +} + +.context-subtitle { + color: var(--fg-muted); + font-size: var(--text-sm); +} + +/* ===== INSTRUCTOR SELECT ===== */ +.instructor-select { + width: 100%; + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); + border: 1px solid var(--border); + background-color: var(--bg); + color: var(--fg); + font-size: var(--text-base); +} + +.instructor-select:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px var(--color-primary-100); +} diff --git a/tcf_website/static/css/site/pages/reviews.css b/tcf_website/static/css/site/pages/reviews.css new file mode 100644 index 000000000..a5744bda7 --- /dev/null +++ b/tcf_website/static/css/site/pages/reviews.css @@ -0,0 +1,126 @@ +/* + * My Reviews Page Styles + * ====================== + */ + +.reviews-page { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-8) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .reviews-page { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .reviews-page { + padding: var(--space-8) var(--space-8) var(--space-16); + } +} + +.reviews-page__header { + display: flex; + flex-wrap: wrap; + gap: var(--space-4); + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-6); +} + +.reviews-page__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); +} + +.reviews-page__header .btn svg { + width: 1rem; + height: 1rem; +} + +.reviews-stats { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); + margin-bottom: var(--space-8); +} + +@media (min-width: 768px) { + .reviews-stats { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +.reviews-stat-card { + padding: var(--space-5); + border: 1px solid var(--border); + border-radius: var(--radius-2xl); + background: var(--bg-elevated); +} + +.reviews-stat-card__label { + color: var(--fg-muted); + font-size: var(--text-sm); + margin-bottom: var(--space-2); +} + +.reviews-stat-card__value { + font-size: var(--text-3xl); + font-weight: var(--font-semibold); +} + +.review-card__context { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.review-card__subject { + color: var(--primary); + font-weight: var(--font-medium); + text-decoration: none; +} + +.review-card__subject:hover { + text-decoration: underline; +} + +.review-card__footer-right { + display: inline-flex; + align-items: center; + gap: var(--space-2); +} + +.review-delete-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: transparent; + color: var(--fg-muted); + cursor: pointer; + transition: color var(--duration-fast) var(--ease-default), + border-color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.review-delete-btn svg { + width: 1rem; + height: 1rem; +} + +.review-delete-btn:hover { + color: var(--danger); + border-color: var(--danger); + background-color: rgba(239, 68, 68, 0.08); +} + +.reviews-page__pagination { + margin-top: var(--space-6); +} diff --git a/tcf_website/static/css/site/pages/schedule.css b/tcf_website/static/css/site/pages/schedule.css new file mode 100644 index 000000000..43d609974 --- /dev/null +++ b/tcf_website/static/css/site/pages/schedule.css @@ -0,0 +1,358 @@ +.schedule-builder, +.schedule-add { + padding: var(--space-8) 0 var(--space-16); +} + +.schedule-builder__inner, +.schedule-add__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .schedule-builder__inner, + .schedule-add__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .schedule-builder__inner, + .schedule-add__inner { + padding: 0 var(--space-8); + } +} + +.schedule-builder__header, +.schedule-add__header { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + justify-content: space-between; + gap: var(--space-4); + margin-bottom: var(--space-6); +} + +.schedule-builder__title, +.schedule-add__title { + font-size: var(--text-3xl); + margin: 0; +} + +.schedule-builder__subtitle, +.schedule-add__subtitle { + margin: var(--space-1) 0 0; + color: var(--fg-muted); +} + +.schedule-builder__create-form, +.schedule-add__create-form { + display: flex; + gap: var(--space-2); + align-items: center; +} + +.schedule-builder__create-form .input, +.schedule-add__create-form .input { + min-width: 14rem; +} + +.schedule-builder__grid { + display: grid; + gap: var(--space-5); + grid-template-columns: minmax(17rem, 22rem) minmax(0, 1fr); + align-items: start; +} + +@media (max-width: 1024px) { + .schedule-builder__grid { + grid-template-columns: 1fr; + } +} + +.schedule-list, +.schedule-detail, +.schedule-add__form, +.schedule-add__empty { + border: 1px solid var(--border); + border-radius: var(--radius-xl); + background-color: var(--bg-elevated); + padding: var(--space-4); +} + +.schedule-list__title, +.schedule-detail__section-title { + font-size: var(--text-lg); + margin: 0 0 var(--space-3); +} + +.schedule-list__items { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.schedule-list__empty { + margin: 0; + color: var(--fg-muted); +} + +.schedule-list-card { + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: var(--space-3); + background-color: var(--bg); +} + +.schedule-list-card.is-active { + border-color: var(--primary); + box-shadow: 0 0 0 1px var(--primary); +} + +.schedule-list-card__name { + font-weight: var(--font-semibold); + color: var(--fg); + text-decoration: none; +} + +.schedule-list-card__name:hover { + color: var(--primary); + text-decoration: none; +} + +.schedule-list-card__metrics { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--space-2); + margin-top: var(--space-3); +} + +.schedule-list-card__metric-label { + display: block; + font-size: var(--text-xs); + text-transform: uppercase; + color: var(--fg-muted); + letter-spacing: var(--tracking-wide); +} + +.schedule-list-card__metric-value { + display: block; + margin-top: 0.1rem; + font-weight: var(--font-semibold); +} + +.schedule-list-card__actions { + margin-top: var(--space-3); + display: flex; + gap: var(--space-2); +} + +.schedule-list-card__danger { + color: var(--danger); +} + +.schedule-detail { + display: flex; + flex-direction: column; + gap: var(--space-5); +} + +.schedule-detail__header { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: var(--space-4); +} + +.schedule-detail__rename-form { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.schedule-detail__name-input { + min-width: 14rem; +} + +.schedule-detail__stats { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: var(--space-2); +} + +.schedule-detail__stats span { + display: block; + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); + color: var(--fg-muted); +} + +.schedule-detail__stats strong { + display: block; + margin-top: 0.2rem; + font-size: var(--text-base); +} + +@media (max-width: 640px) { + .schedule-detail__stats { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +.schedule-detail__course-list { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.scheduled-course-card { + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: var(--space-3); + display: flex; + flex-wrap: wrap; + gap: var(--space-3); + align-items: center; + justify-content: space-between; +} + +.scheduled-course-card__title { + margin: 0; + font-size: var(--text-base); +} + +.scheduled-course-card__meta, +.scheduled-course-card__time { + margin: 0.1rem 0 0; + color: var(--fg-muted); + font-size: var(--text-sm); +} + +.scheduled-course-card__stats { + display: flex; + gap: var(--space-3); + color: var(--fg-muted); + font-size: var(--text-sm); +} + +.scheduled-course-card__remove { + color: var(--danger); +} + +.schedule-detail__empty, +.schedule-detail__empty-state { + margin: 0; + color: var(--fg-muted); +} + +.schedule-detail__note { + margin: 0; + padding: var(--space-3); + border: 1px dashed var(--border); + border-radius: var(--radius-lg); + color: var(--fg-muted); + font-size: var(--text-sm); +} + +.schedule-add__controls { + margin-bottom: var(--space-4); +} + +.schedule-add__label { + display: block; + margin-bottom: var(--space-2); + font-size: var(--text-xs); + text-transform: uppercase; + letter-spacing: var(--tracking-wide); + color: var(--fg-muted); +} + +.schedule-add__options { + display: flex; + flex-direction: column; + gap: var(--space-2); + max-height: clamp(18rem, 52vh, 30rem); + overflow-y: auto; + padding-right: var(--space-2); +} + +.schedule-add__group + .schedule-add__group { + margin-top: var(--space-4); +} + +.schedule-add__group-title { + margin: 0 0 var(--space-2); + font-size: var(--text-xs); + font-weight: var(--font-semibold); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + color: var(--fg-muted); +} + +.schedule-add-option { + display: flex; + gap: var(--space-3); + align-items: flex-start; + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background-color: var(--bg); + padding: var(--space-3); + cursor: pointer; +} + +.schedule-add-option:has(input:checked) { + border-color: var(--primary); + box-shadow: 0 0 0 1px var(--primary); +} + +.schedule-add-option input { + margin-top: 0.2rem; +} + +.schedule-add-option__content { + flex: 1; + min-width: 0; +} + +.schedule-add-option__row { + display: flex; + justify-content: space-between; + gap: var(--space-2); + font-size: var(--text-sm); +} + +.schedule-add-option__row--meta { + margin-top: var(--space-1); + color: var(--fg-muted); +} + +@media (max-width: 640px) { + .schedule-add-option__row { + flex-direction: column; + } +} + +.schedule-add__actions { + margin-top: var(--space-4); + display: flex; + gap: var(--space-2); +} + +.schedule-add__empty-options { + margin: 0; + color: var(--fg-muted); +} + +.schedule-add__empty { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.schedule-add__empty h2, +.schedule-add__empty p { + margin: 0; +} diff --git a/tcf_website/static/css/site/pages/search.css b/tcf_website/static/css/site/pages/search.css new file mode 100644 index 000000000..662a2faa3 --- /dev/null +++ b/tcf_website/static/css/site/pages/search.css @@ -0,0 +1,271 @@ +/* + * Search Results Page Styles + * ========================== + */ + +.search-results-header { + padding: var(--space-8) 0; + background: linear-gradient(135deg, var(--bg) 0%, var(--bg-muted) 100%); + border-bottom: 1px solid var(--border); +} + +.search-results-header__inner { + max-width: var(--size-max); + margin: 0 auto; + padding: 0 var(--space-4); +} + +@media (min-width: 640px) { + .search-results-header__inner { + padding: 0 var(--space-6); + } +} + +@media (min-width: 1024px) { + .search-results-header__inner { + padding: 0 var(--space-8); + } +} + +.search-results-header__title { + font-family: var(--font-display); + font-size: var(--text-4xl); + font-weight: var(--font-normal); +} + +.search-results-header__subtitle { + margin-top: var(--space-2); + color: var(--fg-muted); + font-size: var(--text-base); +} + +.search-results-header__query { + color: var(--fg); + font-weight: var(--font-semibold); +} + +.search-results-content { + max-width: var(--size-max); + margin: 0 auto; + padding: var(--space-6) var(--space-4) var(--space-16); +} + +@media (min-width: 640px) { + .search-results-content { + padding: var(--space-8) var(--space-6) var(--space-16); + } +} + +@media (min-width: 1024px) { + .search-results-content { + padding: var(--space-8) var(--space-8) var(--space-16); + } +} + +.search-tabs { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-bottom: var(--space-6); +} + +.search-tab { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background-color: var(--bg-elevated); + color: var(--fg-muted); + font-size: var(--text-sm); + font-weight: var(--font-medium); + cursor: pointer; + transition: color var(--duration-fast) var(--ease-default), + border-color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.search-tab:hover { + color: var(--fg); + border-color: var(--fg-subtle); +} + +.search-tab.is-active { + color: var(--primary); + border-color: var(--primary); + background-color: var(--color-primary-50); +} + +[data-theme="dark"] .search-tab.is-active { + background-color: rgba(59, 130, 246, 0.1); +} + +.search-tab__count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 1.5rem; + height: 1.5rem; + padding: 0 var(--space-1-5); + border-radius: var(--radius-full); + font-size: var(--text-xs); + font-weight: var(--font-semibold); + background-color: var(--bg-muted); + color: var(--fg); +} + +.search-panel { + display: none; +} + +.search-panel.is-active { + display: block; +} + +.search-panel__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + margin-bottom: var(--space-4); +} + +.search-panel__title { + font-size: var(--text-xl); + font-weight: var(--font-semibold); +} + +.search-panel__count { + font-size: var(--text-sm); + color: var(--fg-muted); +} + +.search-group { + margin-bottom: var(--space-8); +} + +.search-group:last-child { + margin-bottom: 0; +} + +.search-group__title { + margin-bottom: var(--space-3); +} + +.search-group__title a { + color: var(--primary); + text-decoration: none; + font-size: var(--text-base); + font-weight: var(--font-semibold); +} + +.search-group__title a:hover { + text-decoration: underline; +} + +.search-result-list { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.search-result-card { + padding: var(--space-4); + background-color: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-xl); +} + +.search-result-card__link { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: var(--space-2); + text-decoration: none; +} + +.search-result-card__link:hover .search-result-card__title { + text-decoration: underline; +} + +.search-result-card__code { + color: var(--primary); + font-size: var(--text-sm); + font-weight: var(--font-semibold); +} + +.search-result-card__title { + color: var(--fg); + font-size: var(--text-base); + font-weight: var(--font-medium); +} + +.search-result-card__description { + margin-top: var(--space-2); + color: var(--fg-muted); + font-size: var(--text-sm); + line-height: var(--leading-relaxed); +} + +.search-simple-list { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.search-simple-item { + display: block; + padding: var(--space-3) var(--space-4); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background-color: var(--bg-elevated); + color: var(--fg); + text-decoration: none; + transition: border-color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); +} + +.search-simple-item:hover { + border-color: var(--fg-subtle); + background-color: var(--bg-muted); + text-decoration: none; +} + +.search-simple-item--split { + display: grid; + grid-template-columns: minmax(4rem, auto) 1fr; + gap: var(--space-3); +} + +.search-simple-item__code { + color: var(--primary); + font-weight: var(--font-semibold); +} + +.search-simple-item__name { + color: var(--fg); +} + +.search-empty { + padding: var(--space-10) var(--space-4); + border: 1px dashed var(--border); + border-radius: var(--radius-xl); + text-align: center; +} + +.search-empty h3 { + font-size: var(--text-lg); + font-weight: var(--font-semibold); +} + +.search-empty p { + margin-top: var(--space-2); + color: var(--fg-muted); +} + +.search-pagination { + margin-top: var(--space-5); + justify-content: center; + flex-wrap: wrap; +} diff --git a/tcf_website/static/css/site/reset.css b/tcf_website/static/css/site/reset.css new file mode 100644 index 000000000..7f21b2988 --- /dev/null +++ b/tcf_website/static/css/site/reset.css @@ -0,0 +1,140 @@ +/* + * Modern CSS Reset + * ================ + * A minimal, modern CSS reset for consistent cross-browser styling. + * Based on Josh Comeau's CSS Reset with adjustments. + */ + +/* Box sizing rules */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* Remove default margin and padding */ +* { + margin: 0; + padding: 0; +} + +/* Prevent font size inflation on mobile */ +html { + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + text-size-adjust: none; +} + +/* Set core root defaults */ +html { + scroll-behavior: smooth; +} + +/* Set core body defaults */ +body { + min-height: 100vh; + line-height: var(--leading-normal, 1.5); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Remove list styles on ul, ol with role="list" */ +ul[role='list'], +ol[role='list'] { + list-style: none; +} + +/* Set shorter line heights on headings and interactive elements */ +h1, h2, h3, h4, h5, h6, +button, input, label { + line-height: var(--leading-tight, 1.25); +} + +/* Balance text wrapping on headings */ +h1, h2, h3, h4, h5, h6 { + text-wrap: balance; +} + +/* Improve paragraph readability */ +p { + text-wrap: pretty; +} + +/* A elements that don't have a class get default styles */ +a:not([class]) { + text-decoration-skip-ink: auto; + color: currentColor; +} + +/* Make images easier to work with */ +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; + height: auto; +} + +/* Inherit fonts for inputs and buttons */ +input, +button, +textarea, +select { + font: inherit; + color: inherit; +} + +/* Remove default button styles */ +button { + background: none; + border: none; + cursor: pointer; +} + +/* Textarea without resize handle */ +textarea:not([rows]) { + min-height: 10em; +} + +/* Anything with an anchor should have extra scroll margin */ +:target { + scroll-margin-block: 5ex; +} + +/* Remove animations for people who turned them off */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* Focus visible styles for accessibility */ +:focus-visible { + outline: 2px solid var(--ring, #3b82f6); + outline-offset: 2px; +} + +/* Remove focus outline for mouse users */ +:focus:not(:focus-visible) { + outline: none; +} + +/* Hidden but accessible to screen readers */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} diff --git a/tcf_website/static/css/site/tokens.css b/tcf_website/static/css/site/tokens.css new file mode 100644 index 000000000..fee42898d --- /dev/null +++ b/tcf_website/static/css/site/tokens.css @@ -0,0 +1,265 @@ +/* + * theCourseForum Design Tokens + * ============================ + * Central source of truth for all design values. + * Import this file first in any stylesheet. + */ + +:root { + /* ===== COLOR TOKENS ===== */ + + /* Base palette */ + --color-white: #ffffff; + --color-black: #000000; + + /* Neutral scale (warm grays) */ + --color-neutral-50: #fafaf9; + --color-neutral-100: #f5f5f4; + --color-neutral-200: #e7e5e4; + --color-neutral-300: #d6d3d1; + --color-neutral-400: #a8a29e; + --color-neutral-500: #78716c; + --color-neutral-600: #57534e; + --color-neutral-700: #44403c; + --color-neutral-800: #292524; + --color-neutral-900: #1c1917; + --color-neutral-950: #0c0a09; + + /* Primary (blue) */ + --color-primary-50: #eff6ff; + --color-primary-100: #dbeafe; + --color-primary-200: #bfdbfe; + --color-primary-300: #93c5fd; + --color-primary-400: #60a5fa; + --color-primary-500: #3b82f6; + --color-primary-600: #2563eb; + --color-primary-700: #1d4ed8; + --color-primary-800: #1e40af; + --color-primary-900: #1e3a8a; + + /* Accent (UVA orange) */ + --color-accent-50: #fff7ed; + --color-accent-100: #ffedd5; + --color-accent-200: #fed7aa; + --color-accent-300: #fdba74; + --color-accent-400: #fb923c; + --color-accent-500: #f97316; + --color-accent-600: #ea580c; + --color-accent-700: #c2410c; + --color-accent-800: #9a3412; + --color-accent-900: #7c2d12; + + /* Success (green) */ + --color-success-500: #22c55e; + --color-success-600: #16a34a; + + /* Warning (yellow) */ + --color-warning-500: #eab308; + --color-warning-600: #ca8a04; + + /* Danger (red) */ + --color-danger-500: #ef4444; + --color-danger-600: #dc2626; + + + /* ===== SEMANTIC TOKENS (Light Theme) ===== */ + --bg: var(--color-neutral-50); + --bg-elevated: var(--color-white); + --bg-muted: var(--color-neutral-100); + --bg-hover: var(--color-neutral-200); + + --fg: var(--color-neutral-900); + --fg-muted: var(--color-neutral-500); + --fg-subtle: var(--color-neutral-400); + + --primary: var(--color-primary-600); + --primary-hover: var(--color-primary-700); + --primary-fg: var(--color-white); + + --accent: var(--color-accent-600); + --accent-hover: var(--color-accent-700); + --accent-fg: var(--color-white); + + --border: var(--color-neutral-200); + --border-muted: var(--color-neutral-100); + + --ring: var(--color-primary-500); + + --success: var(--color-success-600); + --warning: var(--color-warning-500); + --danger: var(--color-danger-500); + + + /* ===== TYPOGRAPHY ===== */ + --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-display: 'Instrument Serif', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif; + --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; + + /* Font sizes */ + --text-xs: 0.75rem; /* 12px */ + --text-sm: 0.875rem; /* 14px */ + --text-base: 1rem; /* 16px */ + --text-lg: 1.125rem; /* 18px */ + --text-xl: 1.25rem; /* 20px */ + --text-2xl: 1.5rem; /* 24px */ + --text-3xl: 1.875rem; /* 30px */ + --text-4xl: 2.25rem; /* 36px */ + --text-5xl: 3rem; /* 48px */ + --text-6xl: 3.75rem; /* 60px */ + --text-7xl: 4.5rem; /* 72px */ + + /* Line heights */ + --leading-none: 1; + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + + /* Font weights */ + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + + /* Letter spacing */ + --tracking-tight: -0.025em; + --tracking-normal: 0; + --tracking-wide: 0.025em; + + + /* ===== SPACING ===== */ + --space-0: 0; + --space-px: 1px; + --space-0-5: 0.125rem; /* 2px */ + --space-1: 0.25rem; /* 4px */ + --space-1-5: 0.375rem; /* 6px */ + --space-2: 0.5rem; /* 8px */ + --space-2-5: 0.625rem; /* 10px */ + --space-3: 0.75rem; /* 12px */ + --space-3-5: 0.875rem; /* 14px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-7: 1.75rem; /* 28px */ + --space-8: 2rem; /* 32px */ + --space-9: 2.25rem; /* 36px */ + --space-10: 2.5rem; /* 40px */ + --space-11: 2.75rem; /* 44px */ + --space-12: 3rem; /* 48px */ + --space-14: 3.5rem; /* 56px */ + --space-16: 4rem; /* 64px */ + --space-20: 5rem; /* 80px */ + --space-24: 6rem; /* 96px */ + --space-28: 7rem; /* 112px */ + --space-32: 8rem; /* 128px */ + + + /* ===== SIZING ===== */ + --size-max: 80rem; /* 1280px - max content width */ + --size-prose: 65ch; /* Optimal reading width */ + + + /* ===== BORDERS ===== */ + --radius-none: 0; + --radius-sm: 0.25rem; /* 4px */ + --radius-md: 0.375rem; /* 6px */ + --radius-lg: 0.5rem; /* 8px */ + --radius-xl: 0.75rem; /* 12px */ + --radius-2xl: 1rem; /* 16px */ + --radius-3xl: 1.5rem; /* 24px */ + --radius-full: 9999px; + + + /* ===== SHADOWS ===== */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + + + /* ===== TRANSITIONS ===== */ + --duration-fast: 150ms; + --duration-normal: 200ms; + --duration-slow: 300ms; + --duration-slower: 500ms; + + --ease-default: cubic-bezier(0.4, 0, 0.2, 1); + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + + + /* ===== Z-INDEX ===== */ + --z-dropdown: 100; + --z-sticky: 200; + --z-fixed: 300; + --z-modal-backdrop: 400; + --z-modal: 500; + --z-popover: 600; + --z-tooltip: 700; +} + + +/* ===== DARK THEME ===== */ +[data-theme="dark"] { + --bg: var(--color-neutral-950); + --bg-elevated: var(--color-neutral-900); + --bg-muted: var(--color-neutral-800); + --bg-hover: var(--color-neutral-700); + + --fg: var(--color-neutral-50); + --fg-muted: var(--color-neutral-400); + --fg-subtle: var(--color-neutral-500); + + --primary: var(--color-primary-500); + --primary-hover: var(--color-primary-400); + + --accent: var(--color-accent-500); + --accent-hover: var(--color-accent-400); + + --border: var(--color-neutral-800); + --border-muted: var(--color-neutral-900); + + --success: var(--color-success-500); + --warning: var(--color-warning-500); + --danger: var(--color-danger-500); + + /* Adjusted shadows for dark mode */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.3); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.4); +} + + +/* ===== SYSTEM PREFERENCE (fallback) ===== */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --bg: var(--color-neutral-950); + --bg-elevated: var(--color-neutral-900); + --bg-muted: var(--color-neutral-800); + --bg-hover: var(--color-neutral-700); + + --fg: var(--color-neutral-50); + --fg-muted: var(--color-neutral-400); + --fg-subtle: var(--color-neutral-500); + + --primary: var(--color-primary-500); + --primary-hover: var(--color-primary-400); + + --accent: var(--color-accent-500); + --accent-hover: var(--color-accent-400); + + --border: var(--color-neutral-800); + --border-muted: var(--color-neutral-900); + + --success: var(--color-success-500); + --warning: var(--color-warning-500); + --danger: var(--color-danger-500); + + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.3); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.4); + } +} diff --git a/tcf_website/static/css/site/utilities.css b/tcf_website/static/css/site/utilities.css new file mode 100644 index 000000000..8fbca5e41 --- /dev/null +++ b/tcf_website/static/css/site/utilities.css @@ -0,0 +1,359 @@ +/* + * Utility Classes + * =============== + * Single-purpose classes for common styling needs. + */ + +/* ===== LAYOUT ===== */ + +.container { + width: 100%; + max-width: var(--size-max); + margin-left: auto; + margin-right: auto; + padding-left: var(--space-4); + padding-right: var(--space-4); +} + +@media (min-width: 640px) { + .container { + padding-left: var(--space-6); + padding-right: var(--space-6); + } +} + +@media (min-width: 1024px) { + .container { + padding-left: var(--space-8); + padding-right: var(--space-8); + } +} + +/* Narrow container for readable content */ +.container-prose { + max-width: var(--size-prose); +} + + +/* ===== DISPLAY ===== */ + +.block { display: block; } +.inline-block { display: inline-block; } +.inline { display: inline; } +.hidden { display: none; } + + +/* ===== FLEXBOX ===== */ + +.flex { display: flex; } +.inline-flex { display: inline-flex; } + +.flex-row { flex-direction: row; } +.flex-col { flex-direction: column; } +.flex-wrap { flex-wrap: wrap; } + +.items-start { align-items: flex-start; } +.items-center { align-items: center; } +.items-end { align-items: flex-end; } +.items-stretch { align-items: stretch; } +.items-baseline { align-items: baseline; } + +.justify-start { justify-content: flex-start; } +.justify-center { justify-content: center; } +.justify-end { justify-content: flex-end; } +.justify-between { justify-content: space-between; } +.justify-around { justify-content: space-around; } + +.flex-1 { flex: 1 1 0%; } +.flex-auto { flex: 1 1 auto; } +.flex-none { flex: none; } + +.gap-1 { gap: var(--space-1); } +.gap-2 { gap: var(--space-2); } +.gap-3 { gap: var(--space-3); } +.gap-4 { gap: var(--space-4); } +.gap-5 { gap: var(--space-5); } +.gap-6 { gap: var(--space-6); } +.gap-8 { gap: var(--space-8); } +.gap-10 { gap: var(--space-10); } +.gap-12 { gap: var(--space-12); } + + +/* ===== GRID ===== */ + +.grid { display: grid; } + +.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } +.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } +.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } +.grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } +.grid-cols-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); } + +.col-span-1 { grid-column: span 1 / span 1; } +.col-span-2 { grid-column: span 2 / span 2; } +.col-span-3 { grid-column: span 3 / span 3; } +.col-span-4 { grid-column: span 4 / span 4; } +.col-span-6 { grid-column: span 6 / span 6; } +.col-span-12 { grid-column: span 12 / span 12; } +.col-span-full { grid-column: 1 / -1; } + + +/* ===== SPACING (MARGIN) ===== */ + +.m-0 { margin: var(--space-0); } +.m-1 { margin: var(--space-1); } +.m-2 { margin: var(--space-2); } +.m-3 { margin: var(--space-3); } +.m-4 { margin: var(--space-4); } +.m-6 { margin: var(--space-6); } +.m-8 { margin: var(--space-8); } +.m-auto { margin: auto; } + +.mx-auto { margin-left: auto; margin-right: auto; } +.my-auto { margin-top: auto; margin-bottom: auto; } + +.mt-0 { margin-top: var(--space-0); } +.mt-1 { margin-top: var(--space-1); } +.mt-2 { margin-top: var(--space-2); } +.mt-3 { margin-top: var(--space-3); } +.mt-4 { margin-top: var(--space-4); } +.mt-6 { margin-top: var(--space-6); } +.mt-8 { margin-top: var(--space-8); } +.mt-12 { margin-top: var(--space-12); } +.mt-16 { margin-top: var(--space-16); } +.mt-20 { margin-top: var(--space-20); } +.mt-24 { margin-top: var(--space-24); } + +.mb-0 { margin-bottom: var(--space-0); } +.mb-1 { margin-bottom: var(--space-1); } +.mb-2 { margin-bottom: var(--space-2); } +.mb-3 { margin-bottom: var(--space-3); } +.mb-4 { margin-bottom: var(--space-4); } +.mb-6 { margin-bottom: var(--space-6); } +.mb-8 { margin-bottom: var(--space-8); } +.mb-12 { margin-bottom: var(--space-12); } +.mb-16 { margin-bottom: var(--space-16); } + +.ml-auto { margin-left: auto; } +.mr-auto { margin-right: auto; } + + +/* ===== SPACING (PADDING) ===== */ + +.p-0 { padding: var(--space-0); } +.p-1 { padding: var(--space-1); } +.p-2 { padding: var(--space-2); } +.p-3 { padding: var(--space-3); } +.p-4 { padding: var(--space-4); } +.p-5 { padding: var(--space-5); } +.p-6 { padding: var(--space-6); } +.p-8 { padding: var(--space-8); } +.p-10 { padding: var(--space-10); } +.p-12 { padding: var(--space-12); } + +.px-0 { padding-left: var(--space-0); padding-right: var(--space-0); } +.px-2 { padding-left: var(--space-2); padding-right: var(--space-2); } +.px-3 { padding-left: var(--space-3); padding-right: var(--space-3); } +.px-4 { padding-left: var(--space-4); padding-right: var(--space-4); } +.px-6 { padding-left: var(--space-6); padding-right: var(--space-6); } +.px-8 { padding-left: var(--space-8); padding-right: var(--space-8); } + +.py-0 { padding-top: var(--space-0); padding-bottom: var(--space-0); } +.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); } +.py-3 { padding-top: var(--space-3); padding-bottom: var(--space-3); } +.py-4 { padding-top: var(--space-4); padding-bottom: var(--space-4); } +.py-6 { padding-top: var(--space-6); padding-bottom: var(--space-6); } +.py-8 { padding-top: var(--space-8); padding-bottom: var(--space-8); } +.py-10 { padding-top: var(--space-10); padding-bottom: var(--space-10); } +.py-12 { padding-top: var(--space-12); padding-bottom: var(--space-12); } +.py-16 { padding-top: var(--space-16); padding-bottom: var(--space-16); } +.py-20 { padding-top: var(--space-20); padding-bottom: var(--space-20); } +.py-24 { padding-top: var(--space-24); padding-bottom: var(--space-24); } + + +/* ===== WIDTH & HEIGHT ===== */ + +.w-full { width: 100%; } +.w-screen { width: 100vw; } +.w-auto { width: auto; } + +.h-full { height: 100%; } +.h-screen { height: 100vh; } +.min-h-screen { min-height: 100vh; } + + +/* ===== TEXT ALIGNMENT ===== */ + +.text-left { text-align: left; } +.text-center { text-align: center; } +.text-right { text-align: right; } + + +/* ===== COLORS ===== */ + +.text-primary { color: var(--primary); } +.text-accent { color: var(--accent); } +.text-success { color: var(--color-success-600); } +.text-warning { color: var(--color-warning-600); } +.text-danger { color: var(--color-danger-600); } + +.bg-primary { background-color: var(--primary); } +.bg-accent { background-color: var(--accent); } +.bg-muted { background-color: var(--bg-muted); } +.bg-elevated { background-color: var(--bg-elevated); } + + +/* ===== BORDERS ===== */ + +.border { border: 1px solid var(--border); } +.border-t { border-top: 1px solid var(--border); } +.border-b { border-bottom: 1px solid var(--border); } +.border-l { border-left: 1px solid var(--border); } +.border-r { border-right: 1px solid var(--border); } +.border-none { border: none; } + +.rounded-none { border-radius: var(--radius-none); } +.rounded-sm { border-radius: var(--radius-sm); } +.rounded { border-radius: var(--radius-md); } +.rounded-lg { border-radius: var(--radius-lg); } +.rounded-xl { border-radius: var(--radius-xl); } +.rounded-2xl { border-radius: var(--radius-2xl); } +.rounded-full { border-radius: var(--radius-full); } + + +/* ===== SHADOWS ===== */ + +.shadow-none { box-shadow: none; } +.shadow-sm { box-shadow: var(--shadow-sm); } +.shadow { box-shadow: var(--shadow-md); } +.shadow-lg { box-shadow: var(--shadow-lg); } +.shadow-xl { box-shadow: var(--shadow-xl); } + + +/* ===== POSITIONING ===== */ + +.relative { position: relative; } +.absolute { position: absolute; } +.fixed { position: fixed; } +.sticky { position: sticky; } + +.inset-0 { top: 0; right: 0; bottom: 0; left: 0; } +.top-0 { top: 0; } +.right-0 { right: 0; } +.bottom-0 { bottom: 0; } +.left-0 { left: 0; } + + +/* ===== Z-INDEX ===== */ + +.z-0 { z-index: 0; } +.z-10 { z-index: 10; } +.z-20 { z-index: 20; } +.z-30 { z-index: 30; } +.z-40 { z-index: 40; } +.z-50 { z-index: 50; } +.z-dropdown { z-index: var(--z-dropdown); } +.z-modal { z-index: var(--z-modal); } + + +/* ===== OVERFLOW ===== */ + +.overflow-hidden { overflow: hidden; } +.overflow-auto { overflow: auto; } +.overflow-x-auto { overflow-x: auto; } +.overflow-y-auto { overflow-y: auto; } + + +/* ===== CURSOR ===== */ + +.cursor-pointer { cursor: pointer; } +.cursor-default { cursor: default; } +.cursor-not-allowed { cursor: not-allowed; } + + +/* ===== TRANSITIONS ===== */ + +.transition { + transition-property: color, background-color, border-color, box-shadow, opacity, transform; + transition-duration: var(--duration-normal); + transition-timing-function: var(--ease-default); +} + +.transition-colors { + transition-property: color, background-color, border-color; + transition-duration: var(--duration-normal); + transition-timing-function: var(--ease-default); +} + +.transition-transform { + transition-property: transform; + transition-duration: var(--duration-normal); + transition-timing-function: var(--ease-default); +} + + +/* ===== TRANSFORMS ===== */ + +.scale-100 { transform: scale(1); } +.scale-105 { transform: scale(1.05); } +.scale-110 { transform: scale(1.1); } + +.translate-y-0 { transform: translateY(0); } +.translate-y-1 { transform: translateY(var(--space-1)); } +.-translate-y-1 { transform: translateY(calc(-1 * var(--space-1))); } + + +/* ===== OPACITY ===== */ + +.opacity-0 { opacity: 0; } +.opacity-25 { opacity: 0.25; } +.opacity-50 { opacity: 0.5; } +.opacity-75 { opacity: 0.75; } +.opacity-100 { opacity: 1; } + + +/* ===== POINTER EVENTS ===== */ + +.pointer-events-none { pointer-events: none; } +.pointer-events-auto { pointer-events: auto; } + + +/* ===== RESPONSIVE UTILITIES ===== */ + +@media (min-width: 640px) { + .sm\:hidden { display: none; } + .sm\:block { display: block; } + .sm\:flex { display: flex; } + .sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .sm\:text-left { text-align: left; } + .sm\:text-center { text-align: center; } +} + +@media (min-width: 768px) { + .md\:hidden { display: none; } + .md\:block { display: block; } + .md\:flex { display: flex; } + .md\:flex-row { flex-direction: row; } + .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .md\:gap-6 { gap: var(--space-6); } + .md\:gap-8 { gap: var(--space-8); } + .md\:text-left { text-align: left; } +} + +@media (min-width: 1024px) { + .lg\:hidden { display: none; } + .lg\:block { display: block; } + .lg\:flex { display: flex; } + .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } + .lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } + .lg\:gap-8 { gap: var(--space-8); } + .lg\:gap-10 { gap: var(--space-10); } +} + +@media (min-width: 1280px) { + .xl\:hidden { display: none; } + .xl\:block { display: block; } + .xl\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } +} diff --git a/tcf_website/static/department/department.css b/tcf_website/static/department/department.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/tcf_website/static/instructor/instructor.css b/tcf_website/static/instructor/instructor.css deleted file mode 100644 index 260ef22a0..000000000 --- a/tcf_website/static/instructor/instructor.css +++ /dev/null @@ -1,13 +0,0 @@ -.card-header { - background-color: var(--secondary-color); - color: white; - height: 100%; -} - -.course-header:hover { - background-color: var(--main-color); -} - -small { - color: var(--accent-color); -} diff --git a/tcf_website/static/js/site/modal.js b/tcf_website/static/js/site/modal.js new file mode 100644 index 000000000..86d6e7f98 --- /dev/null +++ b/tcf_website/static/js/site/modal.js @@ -0,0 +1,182 @@ +/** + * Modal Manager + * ============= + * Handles modal open/close, backdrop clicks, and keyboard navigation. + */ + +(function () { + "use strict"; + + const BACKDROP_CLASS = "modal-backdrop"; + const OPEN_CLASS = "is-open"; + const BODY_OPEN_CLASS = "modal-open"; + const FOCUSABLE_SELECTOR = + 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])'; + + let activeModal = null; + let previouslyFocused = null; + + /** + * Open a modal by ID. + */ + function openModal(modalId) { + const modal = document.getElementById(modalId); + if (!modal) return; + + if (activeModal) { + closeModal(); + } + + const backdrop = modal.previousElementSibling; + + // Store currently focused element + previouslyFocused = document.activeElement; + + // Prevent body scroll + document.body.classList.add(BODY_OPEN_CLASS); + + // Show modal and backdrop + if (backdrop && backdrop.classList.contains(BACKDROP_CLASS)) { + backdrop.classList.add(OPEN_CLASS); + } + modal.classList.add(OPEN_CLASS); + + activeModal = modal; + + // Focus first focusable element + const focusable = modal.querySelector(FOCUSABLE_SELECTOR); + if (focusable) { + setTimeout(() => focusable.focus(), 100); + } + + // Dispatch event + modal.dispatchEvent(new CustomEvent("modal:open")); + } + + /** + * Close the currently active modal. + */ + function closeModal() { + if (!activeModal) return; + + const backdrop = activeModal.previousElementSibling; + + // Hide modal and backdrop + activeModal.classList.remove(OPEN_CLASS); + if (backdrop && backdrop.classList.contains(BACKDROP_CLASS)) { + backdrop.classList.remove(OPEN_CLASS); + } + + // Restore body scroll + document.body.classList.remove(BODY_OPEN_CLASS); + + // Restore focus + if (previouslyFocused) { + previouslyFocused.focus(); + } + + // Dispatch event + activeModal.dispatchEvent(new CustomEvent("modal:close")); + + activeModal = null; + previouslyFocused = null; + } + + /** + * Close modal by ID. + */ + function closeModalById(modalId) { + const modal = document.getElementById(modalId); + if (modal && modal === activeModal) { + closeModal(); + } + } + + /** + * Initialize modal triggers. + */ + function initModalTriggers() { + // Open triggers + document.querySelectorAll("[data-modal-open]").forEach((trigger) => { + trigger.addEventListener("click", (e) => { + e.preventDefault(); + const modalId = trigger.getAttribute("data-modal-open"); + openModal(modalId); + }); + }); + + // Close triggers + document.querySelectorAll("[data-modal-close]").forEach((trigger) => { + trigger.addEventListener("click", (e) => { + e.preventDefault(); + closeModal(); + }); + }); + + // Backdrop click to close + document.querySelectorAll(`.${BACKDROP_CLASS}`).forEach((backdrop) => { + backdrop.addEventListener("click", closeModal); + }); + + // Close button inside modals + document.querySelectorAll(".modal__close").forEach((closeBtn) => { + closeBtn.addEventListener("click", (e) => { + e.preventDefault(); + closeModal(); + }); + }); + } + + /** + * Handle keyboard events. + */ + function initKeyboardHandler() { + document.addEventListener("keydown", (e) => { + if (!activeModal) return; + + // Close on Escape + if (e.key === "Escape") { + e.preventDefault(); + closeModal(); + return; + } + + // Trap focus within modal + if (e.key === "Tab") { + const focusableElements = + activeModal.querySelectorAll(FOCUSABLE_SELECTOR); + + const firstFocusable = focusableElements[0]; + const lastFocusable = focusableElements[focusableElements.length - 1]; + + if (e.shiftKey) { + if (document.activeElement === firstFocusable) { + e.preventDefault(); + lastFocusable.focus(); + } + } else if (document.activeElement === lastFocusable) { + e.preventDefault(); + firstFocusable.focus(); + } + } + }); + } + + // Initialize on DOM ready + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => { + initModalTriggers(); + initKeyboardHandler(); + }); + } else { + initModalTriggers(); + initKeyboardHandler(); + } + + // Expose API globally + window.modal = { + open: openModal, + close: closeModal, + closeById: closeModalById, + }; +})(); diff --git a/tcf_website/static/js/site/review_votes.js b/tcf_website/static/js/site/review_votes.js new file mode 100644 index 000000000..0ec600d44 --- /dev/null +++ b/tcf_website/static/js/site/review_votes.js @@ -0,0 +1,135 @@ +/** + * Review Vote Manager + * =================== + * Handles secure in-place voting updates for review cards. + */ + +(function () { + "use strict"; + + function getCookie(name) { + const cookie = document.cookie + .split(";") + .map((part) => part.trim()) + .find((part) => part.startsWith(`${name}=`)); + return cookie + ? decodeURIComponent(cookie.split("=").slice(1).join("=")) + : ""; + } + + function openLoginModal() { + if (window.modal && typeof window.modal.open === "function") { + window.modal.open("loginModal"); + } + } + + function isAuthenticated() { + return document.body?.dataset.userAuthenticated === "true"; + } + + async function submitVote(button) { + const reviewId = button.dataset.review; + const action = button.dataset.action === "upvote" ? "up" : "down"; + const voteContainer = button.closest(".review-card__votes"); + if (!reviewId || !voteContainer) { + return; + } + + if (!isAuthenticated()) { + openLoginModal(); + return; + } + + const csrfToken = getCookie("csrftoken"); + const buttons = voteContainer.querySelectorAll(".vote-btn"); + buttons.forEach((item) => { + item.disabled = true; + }); + + try { + const response = await fetch(`/reviews/${reviewId}/vote/`, { + method: "POST", + credentials: "same-origin", + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", + "X-CSRFToken": csrfToken, + "X-Requested-With": "XMLHttpRequest", + }, + body: new URLSearchParams({ action }).toString(), + }); + + if (!response.ok) { + throw new Error("Vote request failed."); + } + + const payload = await response.json(); + const upvoteButton = voteContainer.querySelector( + '[data-action="upvote"]', + ); + const downvoteButton = voteContainer.querySelector( + '[data-action="downvote"]', + ); + const scoreElement = upvoteButton?.querySelector("span"); + + if (scoreElement && typeof payload.sum_votes === "number") { + scoreElement.textContent = String(payload.sum_votes); + } + + upvoteButton?.classList.toggle("is-active", payload.user_vote > 0); + downvoteButton?.classList.toggle("is-active", payload.user_vote < 0); + } catch (error) { + // Keep this intentionally quiet to avoid noisy UI errors. + // eslint-disable-next-line no-console + console.error(error); + } finally { + buttons.forEach((item) => { + item.disabled = false; + }); + } + } + + function initVotes() { + document + .querySelectorAll(".review-card__votes") + .forEach((voteContainer) => { + if (voteContainer.dataset.voteInit === "true") { + return; + } + voteContainer.dataset.voteInit = "true"; + voteContainer.addEventListener("click", (event) => { + const button = event.target.closest(".vote-btn"); + if (!button) { + return; + } + event.preventDefault(); + submitVote(button); + }); + }); + } + + function initLoginButtons() { + document.querySelectorAll('[data-action="login"]').forEach((trigger) => { + if (trigger.dataset.loginInit === "true") { + return; + } + trigger.dataset.loginInit = "true"; + trigger.addEventListener("click", (event) => { + event.preventDefault(); + openLoginModal(); + }); + }); + } + + function init() { + initVotes(); + initLoginButtons(); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } + + window.reviewVotes = { init }; +})(); diff --git a/tcf_website/static/js/site/search_bar.js b/tcf_website/static/js/site/search_bar.js new file mode 100644 index 000000000..2297e3433 --- /dev/null +++ b/tcf_website/static/js/site/search_bar.js @@ -0,0 +1,135 @@ +document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll(".search-bar-container").forEach((container) => { + const toggle = container.querySelector(".search-bar__filter-trigger"); + const dropdown = container.querySelector(".search-filters"); + const form = container.querySelector("form"); + const gpaSlider = container.querySelector('input[name="min_gpa"]'); + + const updateActiveState = () => { + if (!toggle) { + return; + } + + let hasActiveFilters = false; + + container + .querySelectorAll('input[type="checkbox"]') + .forEach((checkbox) => { + if (checkbox.checked) { + hasActiveFilters = true; + } + }); + + container.querySelectorAll('input[type="time"]').forEach((input) => { + if (input.value) { + hasActiveFilters = true; + } + }); + + if (gpaSlider && parseFloat(gpaSlider.value) > 0) { + hasActiveFilters = true; + } + + toggle.classList.toggle("is-active-filter", hasActiveFilters); + }; + + if (toggle && dropdown) { + toggle.addEventListener("click", (e) => { + e.stopPropagation(); + + document + .querySelectorAll(".search-filters.is-open") + .forEach((openDropdown) => { + if (openDropdown !== dropdown) { + openDropdown.classList.remove("is-open"); + const openTrigger = openDropdown + .closest(".search-bar-container") + ?.querySelector(".search-bar__filter-trigger"); + if (openTrigger) { + openTrigger.classList.remove("is-active"); + } + } + }); + + dropdown.classList.toggle("is-open"); + toggle.classList.toggle("is-active"); + }); + + document.addEventListener("click", (e) => { + if ( + dropdown.classList.contains("is-open") && + !dropdown.contains(e.target) && + !toggle.contains(e.target) + ) { + dropdown.classList.remove("is-open"); + toggle.classList.remove("is-active"); + } + }); + } + + if (gpaSlider) { + const valueDisplay = + gpaSlider.parentElement.querySelector(".gpa-value-display"); + if (valueDisplay) { + gpaSlider.addEventListener("input", () => { + valueDisplay.textContent = parseFloat(gpaSlider.value).toFixed(1); + }); + } + } + + const searchInputs = container.querySelectorAll("[data-filter-list]"); + searchInputs.forEach((input) => { + input.addEventListener("keyup", () => { + const listContainer = input + .closest(".filter-list-container") + ?.querySelector(".filter-list"); + if (!listContainer) { + return; + } + + const filterText = input.value.toLowerCase(); + listContainer.querySelectorAll(".filter-item").forEach((item) => { + item.style.display = item.textContent + .toLowerCase() + .includes(filterText) + ? "flex" + : "none"; + }); + }); + }); + + const clearButton = container.querySelector( + '[data-action="clear-filters"]', + ); + if (clearButton && form) { + clearButton.addEventListener("click", () => { + form.reset(); + + if (gpaSlider) { + gpaSlider.value = "0.0"; + const valueDisplay = + gpaSlider.parentElement.querySelector(".gpa-value-display"); + if (valueDisplay) { + valueDisplay.textContent = "0.0"; + } + gpaSlider.dispatchEvent(new Event("input", { bubbles: true })); + gpaSlider.dispatchEvent(new Event("change", { bubbles: true })); + } + + searchInputs.forEach((input) => { + input.value = ""; + input.dispatchEvent(new Event("keyup")); + }); + + updateActiveState(); + }); + } + + if (form) { + form.addEventListener("change", updateActiveState); + form.addEventListener("input", updateActiveState); + } + + updateActiveState(); + }); +}); diff --git a/tcf_website/static/js/site/theme.js b/tcf_website/static/js/site/theme.js new file mode 100644 index 000000000..0b93cca74 --- /dev/null +++ b/tcf_website/static/js/site/theme.js @@ -0,0 +1,117 @@ +/** + * Theme Toggle + * ============ + * Handles light/dark mode switching with localStorage persistence. + */ + +(function () { + "use strict"; + + const STORAGE_KEY = "theme"; + const USER_SET_KEY = "theme-user-set"; + const THEMES = ["light", "dark"]; + + /** + * Get the current theme from localStorage or system preference. + */ + function getTheme() { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored && THEMES.includes(stored)) { + return stored; + } + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; + } + + /** + * Apply theme to the document and notify listeners. + */ + function applyTheme(theme) { + if (!THEMES.includes(theme)) return; + document.documentElement.setAttribute("data-theme", theme); + window.dispatchEvent(new CustomEvent("themechange", { detail: { theme } })); + } + + /** + * Set the theme on the document and persist to localStorage. + */ + function setTheme(theme, persist = true) { + if (!THEMES.includes(theme)) return; + + applyTheme(theme); + if (persist) { + localStorage.setItem(STORAGE_KEY, theme); + return; + } + localStorage.removeItem(STORAGE_KEY); + } + + /** + * Toggle between light and dark themes. + */ + function toggleTheme() { + const current = getTheme(); + const newTheme = current === "dark" ? "light" : "dark"; + localStorage.setItem(USER_SET_KEY, "1"); + setTheme(newTheme); + } + + /** + * Initialize theme toggle buttons. + */ + function initThemeToggle() { + const toggleButtons = document.querySelectorAll("[data-theme-toggle]"); + + toggleButtons.forEach((button) => { + button.addEventListener("click", (e) => { + e.preventDefault(); + toggleTheme(); + }); + }); + } + + /** + * Listen for system preference changes. + */ + function initSystemPreferenceListener() { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + + mediaQuery.addEventListener("change", (e) => { + if (!localStorage.getItem(USER_SET_KEY)) { + setTheme(e.matches ? "dark" : "light", false); + } + }); + } + + function initializeThemeState() { + if ( + localStorage.getItem(STORAGE_KEY) && + !localStorage.getItem(USER_SET_KEY) + ) { + // Preserve existing explicit user choices from before USER_SET_KEY existed. + localStorage.setItem(USER_SET_KEY, "1"); + } + applyTheme(getTheme()); + } + + // Initialize on DOM ready + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => { + initializeThemeState(); + initThemeToggle(); + initSystemPreferenceListener(); + }); + } else { + initializeThemeState(); + initThemeToggle(); + initSystemPreferenceListener(); + } + + // Expose API globally + window.theme = { + get: getTheme, + set: setTheme, + toggle: toggleTheme, + }; +})(); diff --git a/tcf_website/static/landing/UVA_rotunda_logo.png b/tcf_website/static/landing/UVA_rotunda_logo.png deleted file mode 100644 index d933ca602136f2b2a82ba215569afd06441ce930..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20529 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 diff --git a/tcf_website/static/landing/UVA_rotunda_logo_blue.png b/tcf_website/static/landing/UVA_rotunda_logo_blue.png deleted file mode 100644 index dd875c5210a43339aad2563061906bdbccee8519..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8139 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 diff --git a/tcf_website/static/landing/Zelle_qr.jpg b/tcf_website/static/landing/Zelle_qr.jpg deleted file mode 100644 index 8960f9116a8d06c34ef44b42cfdfc2df4d82d1e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 290553 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
+{% block content %} +
theCourseForum logo - +

Oops!

+

+ Looks like this page doesn't exist. + Go back home. +

{% endblock %} diff --git a/tcf_website/templates/about/about.html b/tcf_website/templates/about/about.html deleted file mode 100644 index 0730c48b1..000000000 --- a/tcf_website/templates/about/about.html +++ /dev/null @@ -1,109 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}About | theCourseForum{% endblock %} - -{% block styles %} - -{% endblock %} - - -{% block content %} -
-
-
- -

theCourseForum is a place for - University of Virginia students to share their academic experiences. - These are captured in the form of course reviews, which provide insight, - advice, and caution to students before course enrollment. We believe - that reading student reflections is the best way to understand a class and - its professor without actually experiencing it. -

- Of the students, by the students, for the students... -

-
-
- -
- - -
-
-
- {% include "about/history.html" %} -
-
- {% include "about/current_team.html" %} -
-
- {% include "about/contributors.html"%} -
-
- {% include "about/sponsors.html" %} -
-
-
- -
- -
- -{% endblock %} - -{% block js %} - -{% endblock %} diff --git a/tcf_website/templates/about/privacy.html b/tcf_website/templates/about/partials/privacy_content.html similarity index 96% rename from tcf_website/templates/about/privacy.html rename to tcf_website/templates/about/partials/privacy_content.html index 90cbb780d..cadf7439d 100644 --- a/tcf_website/templates/about/privacy.html +++ b/tcf_website/templates/about/partials/privacy_content.html @@ -1,16 +1,3 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}Privacy | theCourseForum{% endblock %} - -{% block styles %} -{% endblock %} - -{% block content %} -
-
-

Privacy Policy

-
This policy is effective as of January 14, 2008.

theCourseForum Privacy Policy

We built theCourseForum to make it easy to share course information with your @@ -39,8 +26,6 @@

The Information We Collect

cookie is a piece of data stored on the user's computer tied to information about the user. We use session ID cookies to confirm that users are logged in. These cookies terminate once the user closes the browser.

- -

When you use theCourseForum, you set up your personal account. We collect this information so that we can provide you the service and offer personalized features. In most cases, we retain it so that, for instance, you can return to view prior reviews you have written. When you update information, we usually keep a backup copy of the prior version for a reasonable period of time to enable reversion to the prior version of that information.

You post User Content (as defined in the theCourseForum Terms of Use on the Site at your own risk. Although we do not share your name, we cannot control the actions of other Users with whom you may choose to share your reviews and information. Therefore, we cannot and do not guarantee that User Content you post on the Site will not be viewed by unauthorized persons. We are not responsible for circumvention of any privacy settings or security measures contained on the Site. You understand and acknowledge that, even after removal, copies of User Content may remain viewable in cached and archived pages or if other Users have copied or stored your User Content.

@@ -102,6 +87,3 @@

Contacting the Website

support@theCourseForum.com.

-
-
-{% endblock %} diff --git a/tcf_website/templates/about/terms.html b/tcf_website/templates/about/partials/terms_content.html similarity index 96% rename from tcf_website/templates/about/terms.html rename to tcf_website/templates/about/partials/terms_content.html index 230d5021b..b1bd4e3a1 100644 --- a/tcf_website/templates/about/terms.html +++ b/tcf_website/templates/about/partials/terms_content.html @@ -1,16 +1,3 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}Terms | theCourseForum{% endblock %} - -{% block styles %} -{% endblock %} - -{% block content %} -
-
-

Terms of Use

-
Date of Last Revision: September 1, 2021.

Welcome to theCourseForum, a service that connects you with the courses you love. The theCourseForum service and network (collectively, "theCourseForum" or @@ -71,7 +58,7 @@

User Conduct

  • solicit personal information from anyone under 18 or solicit passwords or personally identifying information for commercial or unlawful purposes;
  • upload, post, transmit, share or otherwise make available any material that contains software viruses or any other computer code, files or programs designed to interrupt, destroy or limit the functionality of any computer software or hardware or telecommunications equipment;
  • intimidate or harass another;
  • -
  • upload, post, transmit, share, store or otherwise make
  • available content that would constitute, encourage or provide instructions for a criminal offense, violate the rights of any party, or that would otherwise create liability or violate any local, state, national or international law; +
  • upload, post, transmit, share, store or otherwise make available content that would constitute, encourage or provide instructions for a criminal offense, violate the rights of any party, or that would otherwise create liability or violate any local, state, national or international law;
  • use or attempt to use another's account, service or system without authorization from us, or create a false identity on the Service or the Site;
  • upload, post, transmit, share, store or otherwise make available content that, in our sole judgment, is objectionable or which restricts or inhibits any other person from using or enjoying the Site, or which may expose us or our users to any harm or liability of any type;
  • Without limiting any of the foregoing, you also agree to abide by our theCourseForum Code of Conduct that provides further information regarding the authorized conduct of users on theCourseForum.
  • @@ -89,7 +76,7 @@

    User Disputes

    You are solely responsible for your interactions with other theCourseForum users. We reserve the right, but have no obligation, to monitor disputes between you and other users.

    Privacy

    -

    We care about the privacy of our users. Click here to view the theCourseForum's Privacy Policy. By using the Site or the Service, you are consenting to have your personal data transferred to and processed in the United States.

    +

    We care about the privacy of our users. Click here to view the theCourseForum's Privacy Policy. By using the Site or the Service, you are consenting to have your personal data transferred to and processed in the United States.

    Disclaimers

    We are not responsible or liable in any manner for any User Content posted on the Site or in connection with the Service, whether posted or caused by users of the Site, by theCourseForum, by third parties or by any of the equipment or programming associated with or utilized in the Site or the Service. Although we provide rules for user conduct and postings, we do not control and are not responsible for what users post, transmit or share on the Site and are not responsible for any offensive, inappropriate, obscene, unlawful or otherwise objectionable content you may encounter on the Site or in connection with any User Content or other Content. We are not responsible for the conduct, whether online or offline, of any user of the Site or Service.

    @@ -122,6 +109,3 @@

    CIO Disclaimer

    Other

    These Terms of Use constitute the entire agreement between you and us regarding the use of the Site and/or the Service, superseding any prior agreements between you and us relating to your use of the Site or the Service. Our failure to exercise or enforce any right or provision of these Terms of Use shall not constitute a waiver of such right or provision in that or any other instance. If any provision of this Agreement is held invalid, the remainder of this Agreement shall continue in full force and effect. If any provision of these Terms of Use shall be deemed unlawful, void or for any reason unenforceable, then that provision shall be deemed severable from these Terms of Use and shall not affect the validity and enforceability of any remaining provisions.

    -
    -
    -{% endblock %} diff --git a/tcf_website/templates/base/base.html b/tcf_website/templates/base/base.html deleted file mode 100644 index f62c4c8b2..000000000 --- a/tcf_website/templates/base/base.html +++ /dev/null @@ -1,93 +0,0 @@ -{% extends "base/index.html" %} -{% load static %} - -{% block basestyles %} - - - - -{% endblock %} - -{% block body %} -
    - - {% include "base/sidebar.html" %} - - -
    - - {% include "base/navbar.html" %} - {% include "base/messages.html" %} -
    -
    - {% block content %} -

    Base content goes here.

    - {% endblock %} -
    -
    - -
    - - -
    -{% endblock %} - - -{% block basejs %} - - - - - - - - - - - - -{% endblock %} diff --git a/tcf_website/templates/base/bugform.html b/tcf_website/templates/base/bugform.html deleted file mode 100644 index ecfffc07b..000000000 --- a/tcf_website/templates/base/bugform.html +++ /dev/null @@ -1,82 +0,0 @@ -{% load static %} - - - - - - - -{% block js %} - -{% endblock %} diff --git a/tcf_website/templates/base/index.html b/tcf_website/templates/base/index.html deleted file mode 100644 index 73d53549c..000000000 --- a/tcf_website/templates/base/index.html +++ /dev/null @@ -1,101 +0,0 @@ -{% load static %} - - - - - - - {% block title %}theCourseForum{% endblock %} - - - - - - - - - - - - - {% block page_metadata %}{% endblock %} - - - - - - - - - - - - - - - - - - - - - - - - - - {% block basestyles %}{% endblock %} - - {% block styles %}{% endblock %} - - - - {% block body %}{% endblock %} - - - {% include "login/login_modal.html" with path_id="loginModal" path=request.get_full_path %} - - - {% include "common/history_page_modal.html" with path_id="historyPage" path=request.get_full_path %} - - - {% include "common/donate_page_modal.html" with path_id="donatePage" path=request.get_full_path %} - - - {% include "common/adblock_modal.html" %} - - - - - - - - - - - - - - - - {% block basejs %}{% endblock %} - - {% block js %}{% endblock %} - - - diff --git a/tcf_website/templates/base/messages.html b/tcf_website/templates/base/messages.html deleted file mode 100644 index 80fdfdeab..000000000 --- a/tcf_website/templates/base/messages.html +++ /dev/null @@ -1,18 +0,0 @@ -{% load static %} - -{% block styles %} - -{% endblock %} - -{% if messages %} -
    - {% for message in messages %} -
    - {{ message }} - -
    - {% endfor %} -
    -{% endif %} diff --git a/tcf_website/templates/base/navbar.html b/tcf_website/templates/base/navbar.html deleted file mode 100644 index 106d7ffda..000000000 --- a/tcf_website/templates/base/navbar.html +++ /dev/null @@ -1,59 +0,0 @@ -{% load static %} - -{% block styles %} - -{% endblock %} - -
    - -
    - - \ No newline at end of file diff --git a/tcf_website/templates/base/sidebar.html b/tcf_website/templates/base/sidebar.html deleted file mode 100644 index 5a50bd131..000000000 --- a/tcf_website/templates/base/sidebar.html +++ /dev/null @@ -1,144 +0,0 @@ -{% load static %} - - - - -{% include "base/bugform.html" %} diff --git a/tcf_website/templates/browse/browse.html b/tcf_website/templates/browse/browse.html deleted file mode 100644 index bd2f966da..000000000 --- a/tcf_website/templates/browse/browse.html +++ /dev/null @@ -1,75 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}Browse | theCourseForum{% endblock %} - -{% block styles %} - - -{% endblock %} - -{% block content %} - {% comment %}
    - {% include "../common/notification.html" %} -
    {% endcomment %} -
    - -{# {% include "../common/application_banner.html" %}#} - - {% include "../common/leaderboard_ad.html" with ad_slot="9691204298" %} - - -
    -
    -

    Browse by {% if is_club %}Category{% else %}Department{% endif %}

    - {% include "../club/mode_toggle.html" with is_club=is_club %} -
    -
    - - - {% if is_club %} -
    - {% if club_categories %} - {% for category in club_categories %} - {% include "../club/browse_category.html" with category=category %} - {% endfor %} - {% else %} -

    No club categories available.

    - {% endif %} -
    - {% else %} -
    - {% include "browse/school.html" with school=CLAS %} - {% include "browse/school.html" with school=SEAS %} - {% for school in other_schools %} - {% include "browse/school.html" with school=school %} - {% endfor %} -
    - {% endif %} -
    -{% endblock %} - -{% block js %} - -{% endblock %} \ No newline at end of file diff --git a/tcf_website/templates/browse/school.html b/tcf_website/templates/browse/school.html deleted file mode 100644 index 50bb001ce..000000000 --- a/tcf_website/templates/browse/school.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    -

    {{ school.name }}

    -
    -
    - -
    -
    -
    -
    -
      - {% for d in school.department_set.all|dictsort:"name" %} -
    • - {{ d }} -
    • - {% endfor %} -
    -
    -
    \ No newline at end of file diff --git a/tcf_website/templates/club/browse_category.html b/tcf_website/templates/club/browse_category.html deleted file mode 100644 index c61b39c0d..000000000 --- a/tcf_website/templates/club/browse_category.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    -

    {{ category.name }}

    -
    -
    - -
    -
    -
    -
    -
      - {% for club in category.clubs %} -
    • - {{ club.name }} -
    • - {% endfor %} -
    -
    -
    \ No newline at end of file diff --git a/tcf_website/templates/club/category.html b/tcf_website/templates/club/category.html deleted file mode 100644 index b2c92353a..000000000 --- a/tcf_website/templates/club/category.html +++ /dev/null @@ -1,65 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}{{ category.name }} Clubs | theCourseForum{% endblock %} - -{% block styles %} - - -{% endblock %} - -{% block content %} -
    -
    - {% include "../common/leaderboard_ad.html" with ad_slot="3719332473" %} -
    - -
    - {% include "common/toolbar.html" with breadcrumbs=breadcrumbs %} -
    - -
    -
    -

    {{ category.name }} Clubs

    - {% if category.description %} -

    {{ category.description }}

    - {% endif %} -
    - -
    - {% if paginated_clubs|length == 0 %} -
    -
    -

    - No clubs found in this category. -

    -
    -
    - {% else %} -
      - {% for club in paginated_clubs %} -
    • -
      -
      - -
      -

      {{ club.name }}

      -
      -
      -
      -
      -

      {{ club.description }}

      -
      -
      -
      -
      -
    • - {% endfor %} -
    - {% include "common/pagination.html" with paginated_items=paginated_clubs %} - {% endif %} -
    -
    -
    -{% endblock %} \ No newline at end of file diff --git a/tcf_website/templates/club/club.html b/tcf_website/templates/club/club.html deleted file mode 100644 index dac13b9a7..000000000 --- a/tcf_website/templates/club/club.html +++ /dev/null @@ -1,143 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}{{ club.name }} | theCourseForum{% endblock %} - -{% block page_metadata %} - -{% if course_title %}{% endif %} -{% endblock %} - -{% block styles %} - -{% endblock %} - -{% block content %} -
    -
    - {% include "../common/leaderboard_ad.html" with ad_slot="3719332473" %} -
    -
    - {% include "common/toolbar.html" with breadcrumbs=breadcrumbs %} -
    - -
    -
    -
    -

    {{ club.name }}

    -

    {{ club.category.name }}

    -
    - - -
    -
    -
    -
    -

    - Club Description -

    -

    - {{ club.description }} -

    - -
    - {% if club.meeting_time %} -
    - Meeting Time: {{ club.meeting_time }} -
    - {% endif %} - - - {% if club.application_required %} - Application Required - {% else %} - No Application Required - {% endif %} - -
    -
    - - {% if club.photo_url %} -
    - {{ club.name }} -
    - {% endif %} -
    -
    -
    - -
    -
    -
    - - - - -
    -
    -
    -
    -

    - {% if paginated_reviews %} - {{ paginated_reviews.paginator.count }} {% if paginated_reviews.paginator.count == 1 %} Review {% else %} Reviews {% endif %} - {% else %} - 0 Reviews - {% endif %} -

    -
    - {% if user.is_authenticated %} - - Add your review! - - {% else %} - - Add your review! - - {% endif %} - {% if paginated_reviews and paginated_reviews.number > 0 %} - -
    - -
    - {% endif %} -
    -
    - {% if paginated_reviews %} - {% include "reviews/reviews.html" with paginated_reviews=paginated_reviews %} - {% else %} -
    -

    No reviews yet. Be the first to write a review!

    -
    - {% endif %} -
    -
    -
    -
    - -{% if paginated_reviews %} - -{% endif %} -{% endblock %} \ No newline at end of file diff --git a/tcf_website/templates/club/mode_toggle.html b/tcf_website/templates/club/mode_toggle.html deleted file mode 100644 index c4d5d0d6c..000000000 --- a/tcf_website/templates/club/mode_toggle.html +++ /dev/null @@ -1,25 +0,0 @@ -{% comment %} - Mode toggle component that can be used across the site - Parameters: - - is_club: Boolean that determines which option is active - - toggle_type: String - either "link" (default) or "radio" for searchbar - - container_class: Optional class for the container -{% endcomment %} - -{% if toggle_type == "radio" %} -
    - - - - -
    -
    -{% else %} -
    -
    - Courses - Clubs -
    -
    -
    -{% endif %} \ No newline at end of file diff --git a/tcf_website/templates/common/adblock_modal.html b/tcf_website/templates/common/adblock_modal.html deleted file mode 100644 index 4a2d895ba..000000000 --- a/tcf_website/templates/common/adblock_modal.html +++ /dev/null @@ -1,15 +0,0 @@ - -{% load static %} - diff --git a/tcf_website/templates/common/application_banner.html b/tcf_website/templates/common/application_banner.html deleted file mode 100644 index 98ce73097..000000000 --- a/tcf_website/templates/common/application_banner.html +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/tcf_website/templates/common/coming_soon_modal.html b/tcf_website/templates/common/coming_soon_modal.html deleted file mode 100644 index 9ebe61b9f..000000000 --- a/tcf_website/templates/common/coming_soon_modal.html +++ /dev/null @@ -1,23 +0,0 @@ - - diff --git a/tcf_website/templates/common/donate_page_modal.html b/tcf_website/templates/common/donate_page_modal.html deleted file mode 100644 index 6e16c1593..000000000 --- a/tcf_website/templates/common/donate_page_modal.html +++ /dev/null @@ -1,25 +0,0 @@ - -{% load static %} - diff --git a/tcf_website/templates/common/history_page_modal.html b/tcf_website/templates/common/history_page_modal.html deleted file mode 100644 index 2d8087831..000000000 --- a/tcf_website/templates/common/history_page_modal.html +++ /dev/null @@ -1,73 +0,0 @@ - -{% load static %} - - - diff --git a/tcf_website/templates/common/leaderboard_ad.html b/tcf_website/templates/common/leaderboard_ad.html deleted file mode 100644 index b8e32b7a4..000000000 --- a/tcf_website/templates/common/leaderboard_ad.html +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/tcf_website/templates/common/login-fix-banner.html b/tcf_website/templates/common/login-fix-banner.html deleted file mode 100644 index 98a6142a2..000000000 --- a/tcf_website/templates/common/login-fix-banner.html +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/tcf_website/templates/common/marketing_application_banner.html b/tcf_website/templates/common/marketing_application_banner.html deleted file mode 100644 index 060bfc14e..000000000 --- a/tcf_website/templates/common/marketing_application_banner.html +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/tcf_website/templates/common/notification.html b/tcf_website/templates/common/notification.html deleted file mode 100644 index 1c885b7de..000000000 --- a/tcf_website/templates/common/notification.html +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/tcf_website/templates/common/pagination.html b/tcf_website/templates/common/pagination.html deleted file mode 100644 index e20369c43..000000000 --- a/tcf_website/templates/common/pagination.html +++ /dev/null @@ -1,53 +0,0 @@ -{% load static %} -{% block styles %} - -{% endblock %} - - - \ No newline at end of file diff --git a/tcf_website/templates/common/qa_form_banner.html b/tcf_website/templates/common/qa_form_banner.html deleted file mode 100644 index 162461fd8..000000000 --- a/tcf_website/templates/common/qa_form_banner.html +++ /dev/null @@ -1,15 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/templates/common/rating_card.html b/tcf_website/templates/common/rating_card.html deleted file mode 100644 index 6b2369c64..000000000 --- a/tcf_website/templates/common/rating_card.html +++ /dev/null @@ -1,75 +0,0 @@ -
    -
    - -
    -

    {{ title }}

    - {% if subtitle %} -
    {{ subtitle }}
    - {% endif %} -
    -
    -
    -
    -
    -
    - -  Rating - -

    - {% if rating and rating <= 5 and rating >= 0 %} - {{ rating|floatformat:2 }} - {% else %} - — - {% endif %} -

    -
    -
    - -  Difficulty - -

    - {% if difficulty and difficulty <= 5 and difficulty >= 0 %} - {{ difficulty|floatformat:2 }} - {% else %} - — - {% endif %} -

    -
    -
    - -  GPA - -

    - {% if gpa and gpa <= 4 and gpa >= 0 %} - {{ gpa|floatformat:2 }} - {% else %} - — - {% endif %} -

    -
    -
    - -  Sections - -

    - {% if semester_last_taught == latest_semester %} - {{ sec_nums|length }} - {% else %} - — - {% endif %} -

    -
    -
    - - Last Taught - -

    {{ semester_last_taught }}

    -
    -
    - {% if content %} -

    {{ content }}

    - {% endif %} -
    -
    -
    -
    diff --git a/tcf_website/templates/common/review-drive-banner.html b/tcf_website/templates/common/review-drive-banner.html deleted file mode 100644 index 4aacc7e22..000000000 --- a/tcf_website/templates/common/review-drive-banner.html +++ /dev/null @@ -1,25 +0,0 @@ - diff --git a/tcf_website/templates/common/toolbar.html b/tcf_website/templates/common/toolbar.html deleted file mode 100644 index 42d7bdde4..000000000 --- a/tcf_website/templates/common/toolbar.html +++ /dev/null @@ -1,66 +0,0 @@ -
    - - - diff --git a/tcf_website/templates/course/course.html b/tcf_website/templates/course/course.html deleted file mode 100644 index 053a56eeb..000000000 --- a/tcf_website/templates/course/course.html +++ /dev/null @@ -1,142 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}{{course.code}} | theCourseForum{% endblock %} - -{% block page_metadata %} - -{% if course_code %}{% endif %} -{% if course_title %}{% endif %} -{% endblock %} - -{% block styles %} - - - - -{% endblock %} - -{% block content %} - -
    -
    - {% include "../common/leaderboard_ad.html" with ad_slot="9533576562" %} -
    -
    - {% include "common/toolbar.html" with breadcrumbs=breadcrumbs sorting=instructors|length %} -
    - -
    -
    -

    {{ course.code }}

    -

    {{ course.title }}

    -
    - New - - Add to Schedule - - {% include "schedule/add_course_modal.html" with user_id=user.id course=course%} -
    -
    - - - {% if course.description|length > 0 %} -
    -
    -

    - Course Description -

    - - {% if course.compute_pre_req|length > 0 %} -
    - - Pre-Requisite(s): - - {{ course.compute_pre_req }} -
    - {% endif %} - {% if course.disciplines.exists %} -
    - - Discipline(s): - - {% for discipline in course.disciplines.all %} - {{ discipline.name }} - {% if not forloop.last %} / {% endif %} - {% endfor %} -
    - {% endif %} -

    - {{ course.course_description_without_pre_req }} -

    -
    -
    - {% endif %} - -
    - - {% if instructors|length == 0 %} -
    -
    -

    - Looks like this course isn't being taught this semester. -

    -

    - Sort by "All" in the top right to see previous semesters. -

    -
    -
    - {% endif %} - -
      - {% for i in instructors %} -
    • - {% url 'course_instructor' course_id=course.pk instructor_id=i.pk as link %} - {% include "common/rating_card.html" with title=i.full_name link=link rating=i.rating difficulty=i.difficulty gpa=i.gpa sec_nums=i.section_nums semester_last_taught=i.semester_last_taught latest_semester=latest_semester %} -
    • - {% endfor %} -
    -
    -
    - -{% endblock %} - -{% block js %} - - - - - {#initializes Bootstrap tooltip#} - - -{% endblock %} diff --git a/tcf_website/templates/course/course_professor.html b/tcf_website/templates/course/course_professor.html deleted file mode 100644 index c17ae8aeb..000000000 --- a/tcf_website/templates/course/course_professor.html +++ /dev/null @@ -1,364 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}{{course.code}} | {{instructor.full_name}} | theCourseForum{% endblock %} - -{% block page_metadata %} - -{% if course_code %}{% endif %} -{% if course_title %}{% endif %} -{% if instructor_fullname %}{% endif %} -{% if not is_current_semester %} - -{% endif %} -{% endblock %} - -{% block styles %} - -{% endblock %} - -{% block content %} - -
    - {% include "../common/leaderboard_ad.html" with ad_slot="5402759869" %} - -
    - - {% include "common/toolbar.html" with breadcrumbs=breadcrumbs %} - -
    -
    -
    -

    {{ course.code }}

    -

    {{ course.title }}

    -
    - -
    - -
    - - -

    - -

    {{ instructor.last_name }}, {{ instructor.first_name }}

    -

    -
    - - - {% if other_instructors %} - - {% endif %} -
    - - -
    - Last taught - {{ semester_last_taught }} -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -

     {{ num_ratings }} {% if num_ratings == 1 %} Rating {% else %} Ratings {% endif %}

    -
    -
    -
    -
    -  Instructor -
    -
    -
    -
    -
    -
    -
    - — -
    -
    -
    -
    -  Enjoyability -
    -
    -
    -
    -
    -
    -
    - — -
    -
    -
    -
    -  Difficulty -
    -
    -
    -
    -
    -
    -
    - — -
    -
    -
    -
    -  Recommend -
    -
    -
    -
    -
    -
    -
    - — -
    -
    -
    -
    -
    -
    - -

     Hours/Week

    -
    -
    -
    -
    -  Reading -
    -
    -
    -
    -
    -
    -
    - — -
    -
    -
    -
    -  Writing -
    -
    -
    -
    -
    -
    -
    - — -
    -
    -
    -
    -  Groupwork -
    -
    -
    -
    -
    -
    -
    - — -
    -
    -
    -
    -  Other -
    -
    -
    -
    -
    -
    -
    - — -
    -
    -
    -
    -
    -
    - -
    -

    No grades found

    -

    — Students

    -
    -
    - -
    -
    -
    - {% if display_times and sections %} - - {% endif %} -
    - - - - - -
    -
    -
    -
    -

    {{ num_reviews }} {% if num_reviews == 1 %} Review {% else %} Reviews {% endif %}

    -
    - {% if user.is_authenticated %} - - Add your review! - - {% else %} - - Add your review! - - {% endif %} - {% if paginated_reviews and paginated_reviews.number > 0 %} - -
    - -
    - {% endif %} -
    -
    - {% include "reviews/reviews.html" with paginated_reviews=paginated_reviews %} -
    -
    -
    -
    - -{% endblock %} - -{% block js %} - - - - - - - - - - -{% endblock %} diff --git a/tcf_website/templates/department/department.html b/tcf_website/templates/department/department.html deleted file mode 100644 index 649068a77..000000000 --- a/tcf_website/templates/department/department.html +++ /dev/null @@ -1,46 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}{{breadcrumbs.1.0}} | theCourseForum{% endblock %} - -{% block styles %} - - - -{% endblock %} - -{% block content %} - -
    -
    - {% include "../common/leaderboard_ad.html" with ad_slot="3719332473" %} -
    -
    - {% include "common/toolbar.html" with active_course_recency=active_course_recency latest_semester=latest_semester last_five_years=last_five_years dept_id=dept_id breadcrumbs=breadcrumbs sorting=True %} -
    -
    -
    - {% if paginated_courses|length == 0 %} -
    -
    -

    - {% if active_course_recency == latest_semester %}It appears that no courses are being taught this semester.{% else %}It appears that no courses have been taught recently.{% endif %} -

    -

    - {% if active_course_recency == latest_semester %}Select the "Last 5 Years" button in the top right to see courses from previous semesters.{% else %}Consider selecting another department.{% endif %} -

    -
    -
    - {% else %} -
      - {% for course in paginated_courses %} - {% include "department/department_course.html" with course=course %} - {% endfor %} -
    - {% endif %} -
    -
    - {% include "common/pagination.html" with paginated_items=paginated_courses %} -
    - -{% endblock %} \ No newline at end of file diff --git a/tcf_website/templates/department/department_course.html b/tcf_website/templates/department/department_course.html deleted file mode 100644 index 895f422c6..000000000 --- a/tcf_website/templates/department/department_course.html +++ /dev/null @@ -1,64 +0,0 @@ -
  • -
    -
    - -
    -

    {{ course.subdepartment.mnemonic }} {{ course.number }}

    -
    {{ course.title }}
    -
    -
    -
    -
    -
    -
    - -  Rating - -

    - {% if course.average_rating %} - {{ course.average_rating|floatformat:2 }} - {% else %} - — - {% endif %} -

    -
    -
    - -  Difficulty - -

    - {% if course.average_difficulty %} - {{ course.average_difficulty|floatformat:2 }} - {% else %} - — - {% endif %} -

    -
    -
    - -  GPA - -

    - {% if course.average_gpa %} - {{ course.average_gpa|floatformat:2 }} - {% else %} - — - {% endif %} -

    -
    -
    - Last Taught -

    {{ course.semester_last_taught}}

    -
    -
    -

    {{ course.description }}

    -
    -
    -
    -
    -
  • diff --git a/tcf_website/templates/instructor/instructor.html b/tcf_website/templates/instructor/instructor.html deleted file mode 100644 index d88eadeb6..000000000 --- a/tcf_website/templates/instructor/instructor.html +++ /dev/null @@ -1,135 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}{{instructor.full_name}} | theCourseForum{% endblock %} - -{% block page_metadata %} -{% if not is_teaching_current_semester %} - -{% endif %} -{% endblock %} - -{% block styles %} - - - -{% endblock %} - -{% block content %} -
    -
    -
    -
    -
    -

    - {{ instructor.full_name }} -

    - {{ instructor.email }} - {% if not is_teaching_current_semester %} - - {% endif %} -
    -
    -
    -
    -

    Average Ratings

    -
      -
    • - - Rating - -

      - {% if avg_rating %} - {{ avg_rating }} - {% else %} - — - {% endif %} -

      -
    • -
    • - - Difficulty - -

      - {% if avg_difficulty %} - {{ avg_difficulty }} - {% else %} - — - {% endif %} -

      -
    • -
    • - - GPA - -

      - {{ avg_gpa }} -

      -
    • -
    -
    -
    -
    -
    - {% if courses %} - {% for subdepartment_name, subdepartment_courses in courses.items %} -

    {{ subdepartment_name }}

    - {% for course in subdepartment_courses %} -
    - -
    - {{ course.name }} -
    -
    -
    -
      -
    • - - Rating - -

      - {{ course.avg_rating }} -

      -
    • -
    • - - Difficulty - -

      - {{ course.avg_difficulty }} -

      -
    • -
    • - - GPA - -

      - {% if course.avg_gpa == 0.0 %} - — - {% else %} - {{ course.avg_gpa }} - {% endif %} -

      -
    • -
      - - Last Taught - -

      {{ course.last_taught }}

      -
      -
    -
    -
    - {% endfor %} - {% endfor %} - {% endif %} -
    - -{% endblock %} - - -{% block js %} - -{% endblock %} diff --git a/tcf_website/templates/landing/_faqs.json b/tcf_website/templates/landing/_faqs.json deleted file mode 100644 index 3fadc1e7b..000000000 --- a/tcf_website/templates/landing/_faqs.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "question": "Why the switch to tCF 2.0?", - "answer": "The previous version of theCourseForum was developed using the Ruby on Rails framework in 2012, over 8 years ago. That's a long time in tech years! Not many of our student developers know Ruby anymore, so we rebuilt the site from scratch with Django, a popular Python web framework that's taught in UVA's CS 3240 (Advanced Software Development). What this means for you is that it's easier for us in the long term to maintain the site and develop new features for you." - }, - { - "question": "Where did [previous feature] go?", - "answer": "During our tCF2.0 rewrite, some features had to be left out due to low usage. If you'd like to propose we bring one of them back, or suggest an entirely new feature, submit a feedback form or join the discussion on our Discord server!" - }, - { - "question": "Do you profit from people using the site?", - "answer": "No, our site is entirely a student volunteer effort. To be clear: We have never and will never profit from the site. While we do run ads to cover part of our server costs, they are only a small fraction of our budget and we don't pocket any of it. We get most of our funding from university grants, sponsors, and donations from users like you!" - }, - { - "question": "Where does your grade data come from?", - "answer": "We use completely anonymized official grade data from UVA! Don't worry, nobody will find out about the C you got in MATH 1200." - }, - { - "question": "How can I join the team?", - "answer": "We're currently recruiting for both our dev team and design team — apply here!" - }, - { - "question": "What does the dev team do?", - "answer": "Our dev team develops and maintains the site! We do front-end, back-end, infrastructure, and database work." - }, - { - "question": "What does the design team do?", - "answer": "Our design team works on UI design and publicity, getting the word out about theCourseForum." - }, - { - "question": "How do you protect reviewers' privacy?", - "answer": "We will never display your name on reviews, nor release this information to anyone. Read our Privacy Policy to learn more!" - }, - { - "question": "Where should I report bugs or leave a comment?", - "answer": "Fill out the feedback form or report a bug form on the site or join our Discord server!" - } -] diff --git a/tcf_website/templates/landing/dashboard.html b/tcf_website/templates/landing/dashboard.html deleted file mode 100644 index 7e8651cb3..000000000 --- a/tcf_website/templates/landing/dashboard.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block styles %} - -{% endblock %} - -{% block content %} - -
    -

    Welcome to theCourseForum -

    -

    connecting you to the courses you love

    -
    -

    See what other students say about classes and professors

    -

    Plan and customize your class schedule

    -

    Write reviews for courses you've taken. -

    -
    - Browse reviews - Sign in -
    -{% endblock %} diff --git a/tcf_website/templates/landing/landing.html b/tcf_website/templates/landing/landing.html deleted file mode 100644 index 469b87a2e..000000000 --- a/tcf_website/templates/landing/landing.html +++ /dev/null @@ -1,390 +0,0 @@ -{% extends "base/index.html" %} -{% load static %} - - -{% block styles %} - - - - - - -{% endblock %} - - -{% block body %} -
    - - - - - -
    -
    -
    - -{# {% include "../common/application_banner.html" %}#} - - -
    -
    - {% include "search/searchbar.html" %} -
    -
    -
    - - -
    - -
    - - - -
    -
    -
    -

    We'll connect you to courses you'll love, and make enrollment stress - free.

    -
    -
    -
    -
    - - - -
    - -
    -
    - -
    -

    FAQs

    -
    - -
    -
    - {% for i in FAQs %} -
    -
    - {{ i.question }} -
    -

    - {{ i.answer | safe }} -

    -
    - {% endfor %} -
    -
    - -
    -
    - - - - -
    - - - - {% endblock %} - - {% block js %} - - - {% endblock %} diff --git a/tcf_website/templates/login/login_modal.html b/tcf_website/templates/login/login_modal.html deleted file mode 100644 index 0b9b9ef26..000000000 --- a/tcf_website/templates/login/login_modal.html +++ /dev/null @@ -1,28 +0,0 @@ - -{% load static %} - diff --git a/tcf_website/templates/profile/delete_confirm_modal.html b/tcf_website/templates/profile/delete_confirm_modal.html deleted file mode 100644 index 1489d4a52..000000000 --- a/tcf_website/templates/profile/delete_confirm_modal.html +++ /dev/null @@ -1,25 +0,0 @@ - - diff --git a/tcf_website/templates/profile/profile.html b/tcf_website/templates/profile/profile.html deleted file mode 100644 index b911e49a9..000000000 --- a/tcf_website/templates/profile/profile.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} -{% block title %}My Profile | theCourseForum{% endblock %} - -{% block styles %} - -{% endblock %} - -{% block content %} -
    -
    -

    Profile

    -
    - -
    -

    My Details

    -
    - {% csrf_token %} -
    -
    - {{ form.first_name.label_tag }} - {{ form.first_name }} - Your name is never public. -
    -
    - {{ form.last_name.label_tag }} - {{ form.last_name }} -
    -
    - {{ form.graduation_year.label_tag }} - {{ form.graduation_year }} -
    -
    -
    -
    - - - {% include "profile/delete_confirm_modal.html" with profile=profile %} -
    -
    -
    -
    -
    -{% endblock %} - diff --git a/tcf_website/templates/qa/delete_answer_confirm_modal.html b/tcf_website/templates/qa/delete_answer_confirm_modal.html deleted file mode 100644 index 05a5a7af6..000000000 --- a/tcf_website/templates/qa/delete_answer_confirm_modal.html +++ /dev/null @@ -1,24 +0,0 @@ - - - \ No newline at end of file diff --git a/tcf_website/templates/qa/delete_question_confirm_modal.html b/tcf_website/templates/qa/delete_question_confirm_modal.html deleted file mode 100644 index a006235e3..000000000 --- a/tcf_website/templates/qa/delete_question_confirm_modal.html +++ /dev/null @@ -1,24 +0,0 @@ - - - \ No newline at end of file diff --git a/tcf_website/templates/qa/qa.html b/tcf_website/templates/qa/qa.html deleted file mode 100644 index 6e0b6539d..000000000 --- a/tcf_website/templates/qa/qa.html +++ /dev/null @@ -1,404 +0,0 @@ -{% load static %} - - - - - -
    -
    -

    Questions

    -
    - - Ask your Question! - - {% if questions %} -
    - -
    - {% endif %} -
    -
    - - - -
    -
      -
      - {% for question in questions %} -
      -
      -
      -
      -
      -
      -
      - - -
        -
      • - - - -
      • -
      • - {{ question.sum_q_votes }} -
      • -
      • - - - -
      • -
      -
      -
      -
      -
      -
      Updated {{ question.created|date:"n/d/y" }}
      -
      -
      -
      -
      -
      {{ question.text|linebreaks }}
      -
      -
      - -
      -
      - -
      -
      -
      -
      -
      -
      - {% for question_id, answer_list in answers.items %} - {% if question.id == question_id %} -
      - {% for answer in answer_list %} -
      -
      -
      -
      -
      -
      {{ answer.semester }}
      -
      Updated {{ answer.created|date:"n/d/y" }}
      -
      -
      -
      -

      {{ answer.text }}

      -
      - -
      -
      - - {% endfor %} -
      -
      -
      -
      - {% if answer_list|length > 1 %} - - {% else %} -
      - {% endif %} - - Answer this Question! - -
      - {% endif %} - {% endfor %} -
      -
      -
      -
      -
      -
      -
      -
      - {% empty %} -
      -
      -

      - No Questions and Answers -

      -

      - Get us started by writing a question! -

      -
      -
      - {% endfor %} -
      -
      -
    -
    -
    - {% if questions|length > 3 %} - - {% endif %} -
    - -
    - - - \ No newline at end of file diff --git a/tcf_website/templates/reviews/delete_confirm_modal.html b/tcf_website/templates/reviews/delete_confirm_modal.html deleted file mode 100644 index 2ffedb15a..000000000 --- a/tcf_website/templates/reviews/delete_confirm_modal.html +++ /dev/null @@ -1,23 +0,0 @@ - - diff --git a/tcf_website/templates/reviews/duplicate_review_modal.html b/tcf_website/templates/reviews/duplicate_review_modal.html deleted file mode 100644 index 62a477448..000000000 --- a/tcf_website/templates/reviews/duplicate_review_modal.html +++ /dev/null @@ -1,22 +0,0 @@ - - diff --git a/tcf_website/templates/reviews/new_review.html b/tcf_website/templates/reviews/new_review.html deleted file mode 100644 index 97174d732..000000000 --- a/tcf_website/templates/reviews/new_review.html +++ /dev/null @@ -1,240 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}Write a Review | theCourseForum{% endblock %} - -{% block styles %} - - -{% endblock %} - -{% block content %} - -
    -
    -
    -

    Write a review

    - {% include "../club/mode_toggle.html" with is_club=is_club %} -
    -
    - -
    -
    - {% csrf_token %} - - {% if is_club %} - -
    -

    Which club are you reviewing?

    -
    -
    - {{ club.name }} - {{ club.category.name }} -
    - -
    -
    - -
    -
    - - -
    -
    - {% else %} - -
    -

    Which class are you reviewing?

    -
    -
    - {{ course.code }} — {{ course.title }} -
    - -
    -
    - -
    -
    - - {% if instructor %} -
    {{ instructor.full_name }}
    - - {% else %} - - {% endif %} -
    -
    - - -
    -
    - {% endif %} - -
    - - -
    -

    - Tell us about the {% if is_club %}club{% else %}course{% endif %} -

    -
    - - 0 words -
    -
    - -
    - - -
    -
    -

    Rate the {% if is_club %}club{% else %}course{% endif %}

    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    -
    - -
    -

    Hours per week

    - -
    - - -
    - -
    - - -
    - -
    - - -
    - -
    - - -
    -
    -
    - -
    - - {% include 'reviews/duplicate_review_modal.html' %} - {% include 'reviews/zero_hours_modal.html' %} - -
    - -
    -
    -
    -
    - -{% endblock %} - -{% block js %} - -{% endblock %} diff --git a/tcf_website/templates/reviews/review.html b/tcf_website/templates/reviews/review.html deleted file mode 100644 index 30b30cac0..000000000 --- a/tcf_website/templates/reviews/review.html +++ /dev/null @@ -1,117 +0,0 @@ -{% load static %} - - -
    -
    -
    - -
    - - {% if profile %} - -
    -
    - -
    - - - - {% include "reviews/delete_confirm_modal.html" with review_id=review.id %} -
    -
    -
    - {% else %} - -
    -
    -
    {{ review.semester }}
    -
    Updated {{ review.created|date:"n/d/y" }}
    -
    -
    - {% endif %} - -
    - - - - -
    - - -
    -
    -
    -
    diff --git a/tcf_website/templates/reviews/review_form_content.html b/tcf_website/templates/reviews/review_form_content.html deleted file mode 100644 index 08841d819..000000000 --- a/tcf_website/templates/reviews/review_form_content.html +++ /dev/null @@ -1,225 +0,0 @@ - -{% if not is_club %} - -
    -

    - Which class are you reviewing?

    -
    -

    You can type or use Arrowkeys+Enter to select.

    -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - -
    -
    - - -
    -
    -{% else %} - -
    -

    - Which club are you reviewing?

    -
    -

    You can type or use Arrowkeys+Enter to select.

    -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - -
    -
    -{% endif %} -
    - - -
    -

    - Tell us more about the {% if is_club %}club{% else %}course and instructor{% endif %}

    -
    - -
    - -
    -

    -
    - -
    -
    -
    -
    -
    - - -
    -
    -

    - Rate the {% if is_club %}club{% else %}course{% endif %}

    -
    - -
    -
    - -
    -
    - - /5 -
    -
    -
    -
    - -
    -
    - -
    -
    - - /5 -
    -
    -
    -
    - -
    -
    - -
    -
    - - /5 -
    -
    -
    -
    - -
    -
    - -
    -
    - - /5 -
    -
    -
    -
    -
    -

    - Hours of {% if is_club %}participation{% else %}work{% endif %}/week

    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -
    diff --git a/tcf_website/templates/reviews/review_stats.html b/tcf_website/templates/reviews/review_stats.html deleted file mode 100644 index 0ab9a536e..000000000 --- a/tcf_website/templates/reviews/review_stats.html +++ /dev/null @@ -1,16 +0,0 @@ -
    -
    -
    -

    Total Written

    -

    {{ total_reviews_written }}

    -
    -
    -

    Upvotes Received

    -

    {{ total_review_upvotes }}

    -
    -
    -

    Average Rating

    -

    {{ average_review_rating }}

    -
    -
    -
    diff --git a/tcf_website/templates/reviews/reviews.html b/tcf_website/templates/reviews/reviews.html deleted file mode 100644 index 369fb289f..000000000 --- a/tcf_website/templates/reviews/reviews.html +++ /dev/null @@ -1,95 +0,0 @@ -{% load static %} - - -
    -
      - {% for r in paginated_reviews %} -
    • - {% include "reviews/review.html" with profile=profile review=r %} -
    • - {% empty %} -
      -
      -

      - No Reviews -

      -

      - Help us out by writing a review! -

      -
      -
      - {% endfor %} -
    - - {% if paginated_reviews and paginated_reviews.number > 0 %} - - {% endif %} -
    - -{% if reviews %} - -{% endif %} - - diff --git a/tcf_website/templates/reviews/user_reviews.html b/tcf_website/templates/reviews/user_reviews.html deleted file mode 100644 index 935a85409..000000000 --- a/tcf_website/templates/reviews/user_reviews.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}My Reviews | theCourseForum{% endblock %} - -{% block styles %} - -{% endblock %} - -{% block content %} -
    -
    -

    My reviews

    - - New review - -
    - {% include "reviews/review_stats.html" with total_reviews_written=total_reviews_written total_review_upvotes=total_review_upvotes average_review_rating=average_review_rating %} - {% include "reviews/reviews.html" with paginated_reviews=user.reviews profile=True %} -
    -{% endblock %} diff --git a/tcf_website/templates/reviews/zero_hours_modal.html b/tcf_website/templates/reviews/zero_hours_modal.html deleted file mode 100644 index fec1a92d0..000000000 --- a/tcf_website/templates/reviews/zero_hours_modal.html +++ /dev/null @@ -1,29 +0,0 @@ - - diff --git a/tcf_website/templates/schedule/add_course_modal.html b/tcf_website/templates/schedule/add_course_modal.html deleted file mode 100644 index b231a276e..000000000 --- a/tcf_website/templates/schedule/add_course_modal.html +++ /dev/null @@ -1,109 +0,0 @@ -{% load static %} - -{% block styles %} - -{% endblock %} - - - - - diff --git a/tcf_website/templates/schedule/create_schedule_modal.html b/tcf_website/templates/schedule/create_schedule_modal.html deleted file mode 100644 index 3ccd4193a..000000000 --- a/tcf_website/templates/schedule/create_schedule_modal.html +++ /dev/null @@ -1,51 +0,0 @@ -{% load custom_tags %} -{% load static %} - - - -{% block js%} - -{% endblock %} \ No newline at end of file diff --git a/tcf_website/templates/schedule/delete_schedule_modal.html b/tcf_website/templates/schedule/delete_schedule_modal.html deleted file mode 100644 index 9caf02ed6..000000000 --- a/tcf_website/templates/schedule/delete_schedule_modal.html +++ /dev/null @@ -1,116 +0,0 @@ -{% load custom_tags %} -{% load static %} - - - - - - - - -{% block js %} - -{% endblock %} diff --git a/tcf_website/templates/schedule/edit_schedule_modal.html b/tcf_website/templates/schedule/edit_schedule_modal.html deleted file mode 100644 index 11a03ca3d..000000000 --- a/tcf_website/templates/schedule/edit_schedule_modal.html +++ /dev/null @@ -1,119 +0,0 @@ -{% load custom_tags %} -{% load static %} - - - - diff --git a/tcf_website/templates/schedule/schedule.html b/tcf_website/templates/schedule/schedule.html deleted file mode 100644 index 57fa6efdc..000000000 --- a/tcf_website/templates/schedule/schedule.html +++ /dev/null @@ -1,335 +0,0 @@ -{% load static %} -{% load custom_tags %} - - -
    -
    -
    - -
    -

    {{schedule.name}}

    -
    -
    - -
    - -  Units - -

    - {% if credits %} - {{credits}} - {% else %} - — - {% endif %} -

    -
    - -
    - -  Rating - -

    - {% if rating %} - {{rating|floatformat:2}} - {% else %} - — - {% endif %} -

    -
    - -
    - -  Difficulty - -

    - {% if difficulty %} - {{difficulty|floatformat:2}} - {% else %} - — - {% endif %} -

    -
    - -
    - -  GPA - -

    - {% if gpa %} - {{ gpa|floatformat:2 }} - {% else %} - — - {% endif %} -

    - -
    - - -
    -
    -
    -
    -
      - {% for course in courses %} -
    • - -
      -
      - -
      -
      -
      -
      - -  Section Type - -

      {{course.section.section_type|slice:":3"}}.

      -
      -
      - -  Section Time - -

      {{course.time}}

      -
      -
      - -  GPA - -

      - {% if course.gpa %} - {{ course.gpa|floatformat:2 }} - {% else %} - — - {% endif %} -

      -
      -
      -
      -
      - -  Units - -

      - {% if course.credits and course.credits != 0.0 %} - {{ course.credits }} - {% else %} - — - {% endif %} -

      -
      -
      - -  Rating - -

      - {% if course.total_rating %} - {{ course.total_rating|floatformat:2 }} - {% else %} - — - {% endif %} -

      -
      -
      - -  Difficulty - -

      - {% if course.difficulty %} - {{ course.difficulty|floatformat:2 }} - {% else %} - — - {% endif %} -

      -
      -
      - -
      -
      -
      -
      - -
    • - {% empty %} - Schedule has no courses! - {% endfor %} - -
    -
    -
    -
    -
    - -{% block js %} -{% if mode == "edit" %} - -{% endif %} - -{% endblock %} \ No newline at end of file diff --git a/tcf_website/templates/schedule/schedule_builder.html b/tcf_website/templates/schedule/schedule_builder.html deleted file mode 100644 index 50a8994a6..000000000 --- a/tcf_website/templates/schedule/schedule_builder.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}Make a Schedule | theCourseForum{% endblock %} - -{% block content %} - -
    -
    - {% csrf_token %} - {{form}} - {% if user.is_authenticated %} - - {% else %} - - {% endif %} - -
    - -
    - -{% endblock %} \ No newline at end of file diff --git a/tcf_website/templates/schedule/schedule_editor.html b/tcf_website/templates/schedule/schedule_editor.html deleted file mode 100644 index 16cbc6956..000000000 --- a/tcf_website/templates/schedule/schedule_editor.html +++ /dev/null @@ -1,71 +0,0 @@ -{% load custom_tags %} -{% load static %} -{% block content %} - -
    -
    - {% include "schedule/schedule.html" with mode="edit" schedule=schedule gpa=schedule_gpa courses=schedule_courses rating=schedule_ratings difficulty=schedule_difficulty credits=schedule_credits %} -
    -
    - {% csrf_token %} - - -
    - Schedule Name: - -
    - - - - - - - - - - {% for course in schedule_courses %} - - - - - - {% endfor %} - -
    CourseTypeAction
    {{course.title}}{{course.section.section_type}} - -
    -
    -
    - -{% endblock %} - - \ No newline at end of file diff --git a/tcf_website/templates/schedule/schedule_with_sections.html b/tcf_website/templates/schedule/schedule_with_sections.html deleted file mode 100644 index 280c43ec9..000000000 --- a/tcf_website/templates/schedule/schedule_with_sections.html +++ /dev/null @@ -1,96 +0,0 @@ -{% load custom_tags %} -{% load static %} -{% block content %} - -
    -
    - {% include "schedule/schedule.html" with schedule=schedule mode="edit" gpa=schedule_gpa courses=schedule_courses rating=schedule_ratings difficulty=schedule_difficulty credits=schedule_credits %} -
    -
    - - {% csrf_token %} - - {% for insId, insInfo in instructors_data.items %} - - {% endfor %} -
    -
    - -{% endblock %} - \ No newline at end of file diff --git a/tcf_website/templates/schedule/select_schedule_modal.html b/tcf_website/templates/schedule/select_schedule_modal.html deleted file mode 100644 index e3b34cb47..000000000 --- a/tcf_website/templates/schedule/select_schedule_modal.html +++ /dev/null @@ -1,61 +0,0 @@ -{% load static %} -{% load custom_tags %} - - - - - - - diff --git a/tcf_website/templates/schedule/user_schedules.html b/tcf_website/templates/schedule/user_schedules.html deleted file mode 100644 index 9ef84cd35..000000000 --- a/tcf_website/templates/schedule/user_schedules.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "base/base.html" %} -{% load custom_tags %} -{% load static %} - -{% block title %}My Schedules | theCourseForum{% endblock %} - - -{% block content %} -
    - {% include "../common/leaderboard_ad.html" with ad_slot="4899960156" %} -
    -

    My Schedules

    - -
    - {% include "schedule/select_schedule_modal.html" with schedules=schedules user_id=user.id mode="edit_schedule"%} - {% include "schedule/edit_schedule_modal.html" with user_id=user.id %} - {% include "schedule/create_schedule_modal.html" with user_id=user.id %} - {% include "schedule/delete_schedule_modal.html" with schedules=schedules courses=courses user_id=user.id ratings=ratings difficulty=difficulty %} - - -
      - {% for s in schedules %} -
    • -
      - {% include "schedule/schedule.html" with courses=courses|get_item:s.id gpa=schedules_gpa|get_item:s.id rating=ratings|get_item:s.id difficulty=difficulty|get_item:s.id schedule=s credits=credits|get_item:s.id %} - - -
      -
    • - {% empty %} -
      You haven't made any schedules yet!
      - {% endfor %} -
    -
    - - - -{% endblock %} diff --git a/tcf_website/templates/search/search.html b/tcf_website/templates/search/search.html deleted file mode 100644 index f6eabc7db..000000000 --- a/tcf_website/templates/search/search.html +++ /dev/null @@ -1,264 +0,0 @@ -{% extends "base/base.html" %} -{% load static %} - -{% block title %}{{query}} | theCourseForum{% endblock %} - -{% block styles %} - -{% endblock %} - -{% block content %} - -{% endblock %} - -{% block js %} - -{% endblock %} diff --git a/tcf_website/templates/search/searchbar.html b/tcf_website/templates/search/searchbar.html deleted file mode 100644 index 1851ad5cd..000000000 --- a/tcf_website/templates/search/searchbar.html +++ /dev/null @@ -1,210 +0,0 @@ -{% load static %} - - - - -
    -
    - -
    - - {% include "../club/mode_toggle.html" with is_club=is_club toggle_type="radio" no_transition=True container_class="search-mode-toggle" %} - - -
    - -
    - - -
    - - - diff --git a/tcf_website/templates/site/base.html b/tcf_website/templates/site/base.html new file mode 100644 index 000000000..3a825ab3f --- /dev/null +++ b/tcf_website/templates/site/base.html @@ -0,0 +1,116 @@ +{% load static %} + + + + + + + + {% block title %}theCourseForum{% endblock %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% block styles %}{% endblock %} + {% block page_metadata %}{% endblock %} + + + + + {% block head %}{% endblock %} + + + + + + + + Skip to main content + + + {% block header %} + {% include "site/components/_header.html" %} + {% endblock %} + + {% include "site/components/_messages.html" %} + {% if request.resolver_match and request.resolver_match.url_name != "index" %} + {% include "site/components/_leaderboard_ad.html" %} + {% endif %} + + +
    + {% block content %}{% endblock %} +
    + + + {% block footer %} + {% include "site/components/_footer.html" %} + {% endblock %} + + + {% block modals %} + {% include "site/modals/_login.html" %} + {% include "site/modals/_adblock.html" %} + {% endblock %} + + + + + + + + + + {% block scripts %}{% endblock %} + + diff --git a/tcf_website/templates/site/components/_about_member_card.html b/tcf_website/templates/site/components/_about_member_card.html new file mode 100644 index 000000000..1ec16f69b --- /dev/null +++ b/tcf_website/templates/site/components/_about_member_card.html @@ -0,0 +1,27 @@ +{% load static %} +
    +
    + {% if member.img_filename %} + {{ member.name }} + {% else %} + {{ member.initials }} + {% endif %} +
    +
    {{ member.name }}
    + {% if member.role %} +
    {{ member.role }}
    + {% endif %} + {% if member.class %} +
    Class of {{ member.class }}
    + {% endif %} + {% if member.github %} + + GitHub + + {% endif %} +
    diff --git a/tcf_website/templates/site/components/_about_member_section.html b/tcf_website/templates/site/components/_about_member_section.html new file mode 100644 index 000000000..0680881c8 --- /dev/null +++ b/tcf_website/templates/site/components/_about_member_section.html @@ -0,0 +1,10 @@ +
    +

    {{ title }}

    +
    + {% for member in members %} + {% include "site/components/_about_member_card.html" with member=member image_dir=image_dir %} + {% empty %} +

    No members available.

    + {% endfor %} +
    +
    diff --git a/tcf_website/templates/site/components/_course_card.html b/tcf_website/templates/site/components/_course_card.html new file mode 100644 index 000000000..85878234e --- /dev/null +++ b/tcf_website/templates/site/components/_course_card.html @@ -0,0 +1,41 @@ +{# Course Card Component #} + +
    +
    + {{ course.subdepartment.mnemonic }} {{ course.number }} + {{ course.title }} +
    + +
    + + + + + + + + {{ course.semester_last_taught|default:"—" }} + +
    + + {% if course.description %} +

    {{ course.description|truncatewords:30 }}

    + {% endif %} +
    + +
    +
    +
    + {% if course.average_rating %}{{ course.average_rating|floatformat:1 }}{% else %}—{% endif %} +
    +
    Rating
    +
    + +
    +
    + {% if course.average_gpa %}{{ course.average_gpa|floatformat:2 }}{% else %}—{% endif %} +
    +
    GPA
    +
    +
    +
    diff --git a/tcf_website/templates/site/components/_footer.html b/tcf_website/templates/site/components/_footer.html new file mode 100644 index 000000000..49163d087 --- /dev/null +++ b/tcf_website/templates/site/components/_footer.html @@ -0,0 +1,87 @@ +{% load static %} +{# Footer Component #} + + + diff --git a/tcf_website/templates/site/components/_header.html b/tcf_website/templates/site/components/_header.html new file mode 100644 index 000000000..852c8b55a --- /dev/null +++ b/tcf_website/templates/site/components/_header.html @@ -0,0 +1,138 @@ +{% load static %} +{% load custom_tags %} +{# Header Component #} +
    +
    + + + + + + + + {% if request.resolver_match.url_name != 'index' %} + {% include "site/components/_search_bar.html" %} + {% endif %} + + +
    + + + + + + + + {% if user.is_authenticated %} + + {% else %} + + {% endif %} + + + +
    +
    +
    + + diff --git a/tcf_website/templates/site/components/_instructor_card.html b/tcf_website/templates/site/components/_instructor_card.html new file mode 100644 index 000000000..77279d78b --- /dev/null +++ b/tcf_website/templates/site/components/_instructor_card.html @@ -0,0 +1,35 @@ +{# Instructor Card Component #} + +
    +
    +

    {{ instructor.full_name }}

    + {{ instructor.semester_last_taught }} +
    + + {% if instructor.times %} +
    + {% for section_num, times in instructor.times.items %} + {% for time in times %} + {{ time }} + {% endfor %} + {% endfor %} +
    + {% endif %} +
    + +
    +
    +
    + {% if instructor.rating and instructor.rating <= 5 and instructor.rating >= 0 %}{{ instructor.rating|floatformat:1 }}{% else %}—{% endif %} +
    +
    Rating
    +
    + +
    +
    + {% if instructor.gpa and instructor.gpa <= 4 and instructor.gpa >= 0 %}{{ instructor.gpa|floatformat:2 }}{% else %}—{% endif %} +
    +
    GPA
    +
    +
    +
    diff --git a/tcf_website/templates/site/components/_leaderboard_ad.html b/tcf_website/templates/site/components/_leaderboard_ad.html new file mode 100644 index 000000000..bca8874b0 --- /dev/null +++ b/tcf_website/templates/site/components/_leaderboard_ad.html @@ -0,0 +1,14 @@ +
    +
    + Sponsored + + +
    +
    diff --git a/tcf_website/templates/site/components/_messages.html b/tcf_website/templates/site/components/_messages.html new file mode 100644 index 000000000..4a7a5d28a --- /dev/null +++ b/tcf_website/templates/site/components/_messages.html @@ -0,0 +1,13 @@ +{% if messages %} +
    +
    + {% for message in messages %} +
    +
    +

    {{ message }}

    +
    +
    + {% endfor %} +
    +
    +{% endif %} diff --git a/tcf_website/templates/site/components/_pagination.html b/tcf_website/templates/site/components/_pagination.html new file mode 100644 index 000000000..82f980ac3 --- /dev/null +++ b/tcf_website/templates/site/components/_pagination.html @@ -0,0 +1,48 @@ +{% load custom_tags %} +{% if paginated_items.has_other_pages %} + +{% endif %} diff --git a/tcf_website/templates/site/components/_school_section.html b/tcf_website/templates/site/components/_school_section.html new file mode 100644 index 000000000..3362887cb --- /dev/null +++ b/tcf_website/templates/site/components/_school_section.html @@ -0,0 +1,31 @@ +{# School Section Component #} +
    +
    +
    +
    + + + + +
    +
    +

    {{ school.name }}

    + {{ school.department_set.count }} department{{ school.department_set.count|pluralize }} +
    +
    + + + +
    + +
    + {% for dept in school.department_set.all|dictsort:"name" %} + + {{ dept.name }} + + + + + {% endfor %} +
    +
    diff --git a/tcf_website/templates/site/components/_search_bar.html b/tcf_website/templates/site/components/_search_bar.html new file mode 100644 index 000000000..3a74ce890 --- /dev/null +++ b/tcf_website/templates/site/components/_search_bar.html @@ -0,0 +1,120 @@ +{% load static %} +{% load custom_tags %} +
    + +
    diff --git a/tcf_website/templates/site/components/_section_card.html b/tcf_website/templates/site/components/_section_card.html new file mode 100644 index 000000000..7941e8233 --- /dev/null +++ b/tcf_website/templates/site/components/_section_card.html @@ -0,0 +1,35 @@ + +
    + Section {{ section.number }} + {{ section.type }}{% if section.units %} ({{ section.units }} Unit{{ section.units|pluralize }}){% endif %} +
    + + {% if section.times %} +
    {{ section.times }}
    + {% endif %} + +
    +
    + Enrolled: +
    +
    +
    + {{ section.enrollment_taken }}/{{ section.enrollment_limit }} +
    + + {% if section.waitlist_limit > 0 %} +
    + Waitlist: +
    +
    +
    + {{ section.waitlist_taken }}/{{ section.waitlist_limit }} +
    + {% endif %} +
    +
    diff --git a/tcf_website/templates/site/components/_weekly_calendar.html b/tcf_website/templates/site/components/_weekly_calendar.html new file mode 100644 index 000000000..124224639 --- /dev/null +++ b/tcf_website/templates/site/components/_weekly_calendar.html @@ -0,0 +1,41 @@ +{% if calendar.time_labels %} +
    +
    +
    Time
    + {% for column in calendar.columns %} +
    {{ column.label }}
    + {% endfor %} +
    + +
    +
    + {% for label in calendar.time_labels %} + {{ label }} + {% endfor %} +
    + + {% for column in calendar.columns %} + + {% endfor %} +
    +
    +{% else %} +
    +

    No meeting times to display yet.

    +
    +{% endif %} diff --git a/tcf_website/templates/site/modals/_adblock.html b/tcf_website/templates/site/modals/_adblock.html new file mode 100644 index 000000000..723e4d85a --- /dev/null +++ b/tcf_website/templates/site/modals/_adblock.html @@ -0,0 +1,26 @@ +{% load static %} + diff --git a/tcf_website/templates/site/modals/_login.html b/tcf_website/templates/site/modals/_login.html new file mode 100644 index 000000000..beef3128d --- /dev/null +++ b/tcf_website/templates/site/modals/_login.html @@ -0,0 +1,44 @@ +{% load static %} +{# Login Modal #} + + diff --git a/tcf_website/templates/site/pages/about.html b/tcf_website/templates/site/pages/about.html new file mode 100644 index 000000000..f425a9e98 --- /dev/null +++ b/tcf_website/templates/site/pages/about.html @@ -0,0 +1,179 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}About | theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} + +
    + +
    + +

    theCourseForum

    +

    Connecting you to the courses you love.

    +
    + + +

    + theCourseForum is a place for University of Virginia students to share their academic experiences. + These are captured in the form of course reviews, which provide insight, advice, and caution to students before course enrollment. + We believe that reading student reflections is the best way to understand a class and its professor without actually experiencing it. +

    + Of the students, by the students, for the students... +

    + + +
    + + + + + + + +
    + + +
    +
    +
    + theCourseForum founding team in 2005 +
    +

    Founding

    +

    In Fall 2005, Jeff Bordogna and Alan Webb started theCourseForum to let UVA students share classroom experiences.

    +
    +
    + +
    + theCourseForum team in 2014 +
    +

    Revival

    +

    In 2012-2013, a student engineering team redesigned the platform and modernized the overall architecture.

    +
    +
    + +
    + theCourseForum team in 2017 +
    +

    CIO Era

    +

    In 2017, organizational growth and student impact led to theCourseForum becoming an official UVA CIO.

    +
    +
    + +
    + theCourseForum team in 2020 +
    +

    Django Rebuild

    +

    In Spring 2020, the site was rebuilt from scratch in Django to create a more stable and extensible platform.

    +
    +
    +
    +
    + + +
    + {% include "site/components/_about_member_section.html" with title="Executive Team" members=executive_team image_dir="about/team-pfps" %} + {% include "site/components/_about_member_section.html" with title="Engineering Team" members=engineering_team image_dir="about/team-pfps" %} + {% include "site/components/_about_member_section.html" with title="Design Team" members=design_team image_dir="about/team-pfps" %} + {% include "site/components/_about_member_section.html" with title="Marketing Team" members=marketing_team image_dir="about/team-pfps" %} +
    + + +
    + {% include "site/components/_about_member_section.html" with title="Founders" members=founders image_dir="about/alum-pfps" %} + + {% for group in contributors %} + {% include "site/components/_about_member_section.html" with title=group.group_name members=group.members image_dir="about/alum-pfps" %} + {% endfor %} + {% if not contributors %} +

    No contributors available.

    + {% endif %} +
    + + +
    + +
    + + +
    +
    +
    + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/tcf_website/templates/site/pages/browse.html b/tcf_website/templates/site/pages/browse.html new file mode 100644 index 000000000..6f52cc91a --- /dev/null +++ b/tcf_website/templates/site/pages/browse.html @@ -0,0 +1,82 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}Browse | theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} + + +
    +
    +
    +
    +

    Browse {% if is_club %}Categories{% else %}Departments{% endif %}

    +

    + {% if is_club %} + Explore student organizations by category. + {% else %} + Find courses and instructors across all UVA departments. + {% endif %} +

    +
    + + +
    +
    +
    + + +
    + {% if is_club %} + +
    + {% for category in club_categories %} + +

    {{ category.name }}

    + {{ category.clubs|length }} club{{ category.clubs|length|pluralize }} +
    + {% empty %} +

    No club categories available.

    + {% endfor %} +
    + + {% else %} + + + {% if CLAS %} + {% include "site/components/_school_section.html" with school=CLAS %} + {% endif %} + + {% if SEAS %} + {% include "site/components/_school_section.html" with school=SEAS %} + {% endif %} + + {% for school in other_schools %} + {% include "site/components/_school_section.html" with school=school %} + {% endfor %} + + {% endif %} +
    + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/tcf_website/templates/site/pages/club.html b/tcf_website/templates/site/pages/club.html new file mode 100644 index 000000000..8e0ccd583 --- /dev/null +++ b/tcf_website/templates/site/pages/club.html @@ -0,0 +1,248 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}{{ club.name }} | theCourseForum{% endblock %} + +{% block styles %} + + +{% endblock %} + +{% block content %} +
    +
    + + +
    +

    {{ club.name }}

    + {{ club.category.name }} +
    +
    +
    + +
    +
    +
    +

    Club Description

    +

    + {% if club.description %} + {{ club.description }} + {% else %} + No club description available. + {% endif %} +

    + +
    + {% if club.meeting_time %} + Meeting Time: {{ club.meeting_time }} + {% endif %} + + {% if club.application_required %}Application Required{% else %}No Application Required{% endif %} + +
    +
    + + {% if club.photo_url %} +
    + {{ club.name }} +
    + {% endif %} +
    + +
    +
    +

    {{ num_reviews }} Review{{ num_reviews|pluralize }}

    + +
    + {% if user.is_authenticated %} + + + + + + Add Review + + {% else %} + + + + + + Add Review + + {% endif %} + + {% if paginated_reviews and num_reviews > 0 %} + + {% endif %} +
    +
    + +
    + {% if paginated_reviews and num_reviews > 0 %} + {% for review in paginated_reviews %} +
    +
    +
    + {{ review.semester }} +
    + +
    +
    +
    {{ review.average|floatformat:1 }}
    +
    Average
    +
    +
    +
    + + {% if review.text %} +

    {{ review.text }}

    + {% endif %} + +
    +
    + Leadership + {{ review.instructor_rating|floatformat:1 }} +
    +
    + Enjoyability + {{ review.enjoyability|floatformat:1 }} +
    +
    + Recommend + {{ review.recommendability|floatformat:1 }} +
    +
    + Commitment + {{ review.difficulty|floatformat:1 }} +
    +
    + Hours/Week + {{ review.hours_per_week|floatformat:1 }} +
    +
    + + +
    + {% endfor %} + + {% if paginated_reviews.has_other_pages %} + + {% endif %} + {% else %} +
    + + + +

    No reviews yet

    +

    Be the first to share your experience with this club!

    +
    + {% endif %} +
    +
    +
    +{% endblock %} + +{% block scripts %} + + +{% endblock %} diff --git a/tcf_website/templates/site/pages/club_category.html b/tcf_website/templates/site/pages/club_category.html new file mode 100644 index 000000000..729106ca5 --- /dev/null +++ b/tcf_website/templates/site/pages/club_category.html @@ -0,0 +1,55 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}{{ category.name }} Clubs | theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} +
    +
    + + +

    {{ category.name }} Clubs

    + {% if category.description %} +

    {{ category.description }}

    + {% endif %} +
    +
    + +
    + {% if paginated_clubs %} + + + {% include "site/components/_pagination.html" with paginated_items=paginated_clubs aria_label="Club pagination" %} + {% else %} +
    +

    No clubs found in this category.

    +
    + {% endif %} +
    +{% endblock %} diff --git a/tcf_website/templates/site/pages/course.html b/tcf_website/templates/site/pages/course.html new file mode 100644 index 000000000..ee4b709a5 --- /dev/null +++ b/tcf_website/templates/site/pages/course.html @@ -0,0 +1,124 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}{{ course.code }} | theCourseForum{% endblock %} + +{% block page_metadata %} +{% if course_code %}{% endif %} +{% if course_title %}{% endif %} +{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} + + +
    +
    + + + + +
    +
    +

    {{ course.code }}

    +

    {{ course.title }}

    +
    + + {% if user.is_authenticated %} + + {% endif %} +
    + + + {% if course.description %} +
    +

    Course Description

    + +
    + {% if course.compute_pre_req %} +
    +
    + Prerequisites +

    {{ course.compute_pre_req }}

    +
    +
    + {% endif %} + + {% if course.disciplines.exists %} +
    +
    + Disciplines +

    + {% for discipline in course.disciplines.all %} + {{ discipline.name }}{% if not forloop.last %}, {% endif %} + {% endfor %} +

    +
    +
    + {% endif %} +
    + +

    {{ course.course_description_without_pre_req }}

    +
    + {% endif %} +
    +
    + + +
    +
    +

    Instructors

    + + + +
    + + {% if instructors|length == 0 %} + +
    +

    No instructors this semester

    +

    + This course isn't being taught this semester. Click "All Time" to see previous instructors. +

    +
    + {% else %} + +
    + {% for instructor in instructors %} + {% include "site/components/_instructor_card.html" with instructor=instructor course=course latest_semester=latest_semester %} + {% endfor %} +
    + + {% endif %} +
    + +{% endblock %} diff --git a/tcf_website/templates/site/pages/course_instructor.html b/tcf_website/templates/site/pages/course_instructor.html new file mode 100644 index 000000000..291f32a1a --- /dev/null +++ b/tcf_website/templates/site/pages/course_instructor.html @@ -0,0 +1,607 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}{{ course.code }} | {{ instructor.full_name }} | theCourseForum{% endblock %} + +{% block page_metadata %} +{% if course_code %}{% endif %} +{% if course_title %}{% endif %} +{% if instructor_fullname %}{% endif %} +{% if not is_current_semester %} + +{% endif %} +{% endblock %} + +{% block styles %} + + +{% endblock %} + +{% block content %} + + +
    +
    + + + + +
    + {{ course.code }} + {{ course.title }} +
    + + +
    + + + + Last taught: {{ semester_last_taught }} + +
    +
    +
    + + +
    + +
    + +
    +
    +

    Ratings

    + {{ num_ratings }} rating{{ num_ratings|pluralize }} +
    + +
    +
    + Instructor +
    +
    +
    + +
    + +
    + Enjoyability +
    +
    +
    + +
    + +
    + Difficulty +
    +
    +
    + +
    + +
    + Recommend +
    +
    +
    + +
    + +
    +
    +

    Weekly Breakdown

    + + + hrs/wk + +
    + +
    + Reading +
    +
    +
    + +
    + +
    + Writing +
    +
    +
    + +
    + +
    + Groupwork +
    +
    +
    + +
    + +
    + Other +
    +
    +
    + +
    +
    +
    + + +
    +
    +

    Grade Distribution

    +
    + +
    +
    + +
    + + +
    +
    +
    + Average GPA + +
    +
    + Students Measured + +
    +
    +
    + + + {% if display_times and sections_count %} +
    +
    +

    Sections

    + {{ sections_count }} +
    + +
    + {% if lecture_sections %} +
    +

    Lecture ({{ lecture_sections|length }})

    + {% for section in lecture_sections %} + {% include "site/components/_section_card.html" with section=section sem_code=sem_code %} + {% endfor %} +
    + {% endif %} + + {% if other_sections %} +
    +

    Other Sections ({{ other_sections|length }})

    + {% for section in other_sections %} + {% include "site/components/_section_card.html" with section=section sem_code=sem_code %} + {% endfor %} +
    + {% endif %} +
    +
    + {% endif %} +
    + +
    +
    +

    {{ num_reviews }} Review{{ num_reviews|pluralize }}

    + +
    + {% if user.is_authenticated %} + + + + + + Add Review + + {% else %} + + + + + + Add Review + + {% endif %} + + {% if paginated_reviews and num_reviews > 0 %} + + {% endif %} +
    +
    + +
    + {% if paginated_reviews and num_reviews > 0 %} + {% for review in paginated_reviews %} +
    +
    +
    + {{ review.semester }} + {% if review.grade %} + Grade: {{ review.grade }} + {% endif %} +
    + +
    +
    +
    {{ review.average|floatformat:1 }}
    +
    Average
    +
    +
    +
    + + {% if review.text %} +

    {{ review.text }}

    + {% endif %} + +
    +
    + Instructor + {{ review.instructor_rating|floatformat:1 }} +
    +
    + Enjoyability + {{ review.enjoyability|floatformat:1 }} +
    +
    + Recommend + {{ review.recommendability|floatformat:1 }} +
    +
    + Difficulty + {{ review.difficulty|floatformat:1 }} +
    +
    + Hours/Week + {{ review.hours_per_week|floatformat:1 }} +
    +
    + + +
    + {% endfor %} + + {% if paginated_reviews.has_other_pages %} + + {% endif %} + {% else %} +
    + + + +

    No reviews yet

    +

    Be the first to share your experience with this course!

    + {% if user.is_authenticated %} + + + + + + Add Review + + {% else %} + + + + + + Sign in to Add Review + + {% endif %} +
    + {% endif %} +
    +
    +
    + +{% endblock %} + +{% block scripts %} + + + +{% endblock %} diff --git a/tcf_website/templates/site/pages/department.html b/tcf_website/templates/site/pages/department.html new file mode 100644 index 000000000..5f671018d --- /dev/null +++ b/tcf_website/templates/site/pages/department.html @@ -0,0 +1,138 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}{{ breadcrumbs.1.0 }} | theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} + + +
    +
    + + + + +
    +

    {{ breadcrumbs.1.0 }}

    + +
    + + + + +
    + + +
    +
    +
    +
    +
    + + +
    + {% if paginated_courses|length == 0 %} + +
    +

    + {% if active_course_recency == latest_semester %} + No courses this semester + {% else %} + No courses found + {% endif %} +

    +

    + {% if active_course_recency == latest_semester %} + It appears no courses are being taught this semester. Try viewing courses from the last 5 years. + {% else %} + Consider selecting a different department. + {% endif %} +

    +
    + {% else %} + +
    + {% for course in paginated_courses %} + {% include "site/components/_course_card.html" with course=course %} + {% endfor %} +
    + + + {% if paginated_courses.has_other_pages %} + + {% endif %} + + {% endif %} +
    + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/tcf_website/templates/site/pages/instructor.html b/tcf_website/templates/site/pages/instructor.html new file mode 100644 index 000000000..8d0a4a9bc --- /dev/null +++ b/tcf_website/templates/site/pages/instructor.html @@ -0,0 +1,131 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}{{ instructor.full_name }} | theCourseForum{% endblock %} + +{% block page_metadata %} +{% if not is_teaching_current_semester %} + +{% endif %} +{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} + + +
    +
    + + + + +
    +
    +

    {{ instructor.full_name }}

    + + {% if instructor.email %} + + {% endif %} + + {% if not is_teaching_current_semester %} +
    + + + + + + Not currently teaching UVA courses +
    + {% endif %} +
    + + +
    +
    +
    + {% if avg_rating %}{{ avg_rating }}{% else %}—{% endif %} +
    +
    Rating
    +
    + +
    +
    + {% if avg_difficulty %}{{ avg_difficulty }}{% else %}—{% endif %} +
    +
    Difficulty
    +
    + +
    +
    + {% if avg_gpa and avg_gpa != "—" %}{{ avg_gpa }}{% else %}—{% endif %} +
    +
    GPA
    +
    +
    +
    +
    +
    + + +
    + {% if courses %} + {% for subdepartment_name, subdepartment_courses in courses.items %} +
    +

    {{ subdepartment_name }}

    + + {% for course in subdepartment_courses %} + + {{ course.name }} + +
    +
    +
    {{ course.avg_rating|floatformat:1 }}
    +
    Rating
    +
    + +
    +
    {{ course.avg_difficulty|floatformat:1 }}
    +
    Difficulty
    +
    + +
    +
    + {% if course.avg_gpa == 0.0 %} + — + {% else %} + {{ course.avg_gpa|floatformat:2 }} + {% endif %} +
    +
    GPA
    +
    + +
    +
    {{ course.last_taught }}
    +
    Last Taught
    +
    +
    +
    + {% endfor %} +
    + {% endfor %} + {% else %} + +
    +

    No courses found

    +

    + This instructor hasn't taught any courses recently. +

    +
    + {% endif %} +
    + +{% endblock %} diff --git a/tcf_website/templates/site/pages/landing.html b/tcf_website/templates/site/pages/landing.html new file mode 100644 index 000000000..6bb2d7480 --- /dev/null +++ b/tcf_website/templates/site/pages/landing.html @@ -0,0 +1,191 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block header %} +{# Use transparent header for landing #} +{% include "site/components/_header.html" %} +{% endblock %} + +{% block content %} + + +
    +
    + + +

    + Find the perfect {{ mode_noun }} for you +

    + + + + +
    +
    + + + +
    +
    +
    Features
    +

    Everything you need to plan your semester

    +

    + We've got you covered every step of the way. +

    +
    + +
    + +
    +
    + + + + +
    +

    Explore Courses

    +

    + Browse departments and find courses that match your interests and requirements. +

    + + Browse courses + + + + +
    + + +
    +
    + + + + + + +
    +

    Build Your Schedule

    +

    + Plan your semester with our visual schedule builder. Compare sections and find the perfect fit. +

    + + Build schedule + + + + +
    + + +
    +
    + + + + +
    +

    Share Your Experience

    +

    + Help fellow Hoos by writing reviews. Your insights help build a stronger community. +

    + + Write a review + + + + +
    +
    +
    + + + +
    +
    +
    +

    Frequently Asked Questions

    +
    + +
    + +
    + +
    +

    + theCourseForum is a student-run nonprofit dedicated to helping UVA students + make informed decisions about their courses. We've been serving the UVA community + since 2005, providing course reviews, instructor ratings, and scheduling tools. +

    +
    +
    + + +
    + +
    +

    + Simply sign in with your UVA Email, navigate to a course you've taken, + and click "Write a Review." Share your experience with ratings for instructor quality, + course difficulty, and a written review to help other students. +

    +
    +
    + + +
    + +
    +

    + Yes! All reviews are completely anonymous. While you need to sign in to write a review + (to verify you're a UVA student), your identity is never displayed publicly. + Only team administrators have access to review author information. +

    +
    +
    + + +
    + +
    +

    + The best way to support us is by writing reviews! You can also follow us on social media, + join our Discord community, or consider making a donation to help cover hosting costs + and support our development efforts. +

    +
    +
    +
    +
    +
    + +{% endblock %} diff --git a/tcf_website/templates/site/pages/privacy.html b/tcf_website/templates/site/pages/privacy.html new file mode 100644 index 000000000..963440d04 --- /dev/null +++ b/tcf_website/templates/site/pages/privacy.html @@ -0,0 +1,18 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}Privacy | theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} + +{% endblock %} diff --git a/tcf_website/templates/site/pages/profile.html b/tcf_website/templates/site/pages/profile.html new file mode 100644 index 000000000..ea04e5bc7 --- /dev/null +++ b/tcf_website/templates/site/pages/profile.html @@ -0,0 +1,146 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}My Profile | theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} + +
    +

    My Profile

    + +
    +
    + UVA Library +
    + +
    +
    + {% csrf_token %} + +
    +

    + + + + + My Details +

    + +
    +
    + + {{ form.first_name }} + Your name is never public. +
    + +
    + + {{ form.last_name }} +
    + +
    + + {{ form.graduation_year }} +
    +
    +
    + +
    + + + +
    +
    +
    +
    +
    + + + + +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/tcf_website/templates/site/pages/review.html b/tcf_website/templates/site/pages/review.html new file mode 100644 index 000000000..11f78f440 --- /dev/null +++ b/tcf_website/templates/site/pages/review.html @@ -0,0 +1,328 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}Write a Review | theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} +
    + +
    +

    Write a Review

    +

    Share your experience to help other students.

    +
    + +
    + {% csrf_token %} + + + {% if is_club %} + + {% else %} + + {% endif %} + + + +
    +
    + {% if is_club %}🛡️{% else %}📚{% endif %} +
    +
    + {% if is_club %} +
    {{ club.name }}
    +
    {{ club.category.name }}
    + {% else %} +
    {{ course.code }}
    +
    {{ course.title }}
    + {% endif %} +
    +
    + + +
    +

    + 🗓️ Logistics +

    + +
    + +
    + + +
    + + + {% if not is_club %} +
    + + {% if instructor %} +
    + {{ instructor.full_name }} +
    + + {% else %} + + {% endif %} +
    + {% endif %} +
    +
    + + +
    +

    + ✍️ Your Review +

    + +
    + + 0 words +
    +
    + + +
    +

    + Ratings +

    + +
    + + +
    + +
    + {% for i in "12345" %} + + {% endfor %} +
    +
    + + +
    + +
    + {% for i in "12345" %} + + {% endfor %} +
    +
    + + +
    + +
    + {% for i in "12345" %} + + {% endfor %} +
    +
    + + +
    + +
    + {% for i in "12345" %} + + {% endfor %} +
    +
    + +
    +
    + + +
    +

    + ⏱️ Weekly Hours +

    + +
    +
    + +
    + + hrs +
    +
    + +
    + +
    + + hrs +
    +
    + +
    + +
    + + hrs +
    +
    + +
    + +
    + + hrs +
    +
    +
    +
    + + + + + +
    + +
    + +
    +
    +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/tcf_website/templates/site/pages/reviews.html b/tcf_website/templates/site/pages/reviews.html new file mode 100644 index 000000000..9f98f3a71 --- /dev/null +++ b/tcf_website/templates/site/pages/reviews.html @@ -0,0 +1,176 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}My Reviews | theCourseForum{% endblock %} + +{% block styles %} + + +{% endblock %} + +{% block content %} +
    +
    +

    My Reviews

    + + + + + + New Review + +
    + +
    +
    +

    Total Reviews

    +

    {{ total_reviews_written|default:0 }}

    +
    +
    +

    Upvotes Received

    +

    {{ total_review_upvotes|default:0 }}

    +
    +
    +

    Average Rating

    +

    + {% if average_review_rating %} + {{ average_review_rating|floatformat:2 }} + {% else %} + — + {% endif %} +

    +
    +
    + +
    + {% if paginated_reviews %} + {% for review in paginated_reviews %} +
    +
    +
    + {% if review.club %} + + {{ review.club.name }} + + {% else %} + + {{ review.course }} — {{ review.instructor.full_name }} + + {% endif %} + +
    + {{ review.semester }} + {% if review.grade %} + Grade: {{ review.grade }} + {% endif %} +
    +
    + +
    +
    +
    {{ review.average|floatformat:1 }}
    +
    Average
    +
    +
    +
    + + {% if review.text %} +

    {{ review.text }}

    + {% endif %} + +
    +
    + Instructor + {{ review.instructor_rating|floatformat:1 }} +
    +
    + Enjoyability + {{ review.enjoyability|floatformat:1 }} +
    +
    + Recommend + {{ review.recommendability|floatformat:1 }} +
    +
    + Difficulty + {{ review.difficulty|floatformat:1 }} +
    +
    + Hours/Week + {{ review.hours_per_week|floatformat:1 }} +
    +
    + + +
    + {% endfor %} + + {% if paginated_reviews.has_other_pages %} + + {% endif %} + {% else %} +
    + + + +

    No reviews yet

    +

    Write your first review to help other students.

    + Write a Review +
    + {% endif %} +
    +
    +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/tcf_website/templates/site/pages/schedule.html b/tcf_website/templates/site/pages/schedule.html new file mode 100644 index 000000000..79cc8da3f --- /dev/null +++ b/tcf_website/templates/site/pages/schedule.html @@ -0,0 +1,173 @@ +{% extends "site/base.html" %} +{% load custom_tags %} + +{% block title %}Schedule Builder | theCourseForum{% endblock %} + +{% block styles %} +{% load static %} + +{% endblock %} + +{% block content %} +
    +
    +
    +
    +

    Schedule Builder

    +

    Build and compare schedules with a weekly calendar view.

    +
    + +
    + {% csrf_token %} + + + +
    +
    + +
    + + +
    + {% if selected_schedule %} +
    +
    + {% csrf_token %} + + + + +
    + +
    +
    + Units + {% if selected_schedule_stats.credits %}{{ selected_schedule_stats.credits }}{% else %}—{% endif %} +
    +
    + Rating + {% if selected_schedule_stats.rating %}{{ selected_schedule_stats.rating|floatformat:2 }}{% else %}—{% endif %} +
    +
    + GPA + {% if selected_schedule_stats.gpa %}{{ selected_schedule_stats.gpa|floatformat:2 }}{% else %}—{% endif %} +
    +
    +
    + +
    + {% for course in selected_courses %} +
    +
    +

    {{ course.title }}

    +

    + {{ course.instructor|remove_email }} • Section {{ course.section.sis_section_number }} +

    +

    {{ course.time|default:course.section.section_times|default:"No time listed" }}

    +
    + +
    + GPA: {% if course.gpa %}{{ course.gpa|floatformat:2 }}{% else %}—{% endif %} + Rating: {% if course.total_rating %}{{ course.total_rating|floatformat:2 }}{% else %}—{% endif %} +
    + +
    + {% csrf_token %} + + +
    +
    + {% empty %} +

    This schedule has no courses yet.

    + {% endfor %} +
    + +
    +

    Weekly Calendar

    + {% include "site/components/_weekly_calendar.html" with calendar=calendar %} +
    + {% else %} +
    +

    Create your first schedule

    +

    Use the form above to create a schedule, then add courses from any course page.

    +
    + {% endif %} + +

    + Add courses from the course page using “Add to Schedule”. Schedule-page search/autocomplete will be added later and reused for the new-review flow. +

    +
    +
    +
    +
    +{% endblock %} diff --git a/tcf_website/templates/site/pages/schedule_add_course.html b/tcf_website/templates/site/pages/schedule_add_course.html new file mode 100644 index 000000000..b59639e50 --- /dev/null +++ b/tcf_website/templates/site/pages/schedule_add_course.html @@ -0,0 +1,128 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}Add {{ course.code }} to Schedule | theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} +
    +
    + + +
    +

    Add {{ course.code }} to a Schedule

    +

    Select a schedule and section.

    +
    + + {% if not schedules %} +
    +

    Create a schedule first

    +

    You need at least one schedule before adding courses.

    +
    + {% csrf_token %} + + + +
    + Go to Schedule Builder +
    + {% else %} +
    + {% csrf_token %} + + +
    + + +
    + +
    + {% if lecture_options %} +
    +

    Lecture Sections ({{ lecture_options|length }})

    + {% for option in lecture_options %} + + {% endfor %} +
    + {% endif %} + + {% if other_options %} +
    +

    Other Sections ({{ other_options|length }})

    + {% for option in other_options %} + + {% endfor %} +
    + {% endif %} + + {% if not lecture_options and not other_options %} +

    No sections are available this semester.

    + {% endif %} +
    + +
    + + Cancel +
    +
    + {% endif %} +
    +
    +{% endblock %} diff --git a/tcf_website/templates/site/pages/search.html b/tcf_website/templates/site/pages/search.html new file mode 100644 index 000000000..52bf5549d --- /dev/null +++ b/tcf_website/templates/site/pages/search.html @@ -0,0 +1,274 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %} +{% if query %} +{{ query }} | Search | theCourseForum +{% else %} +Search | theCourseForum +{% endif %} +{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} +
    +
    +

    Search Results

    +

    + {% if query %} + Showing matches for "{{ query }}" + {% elif is_club %} + Browse clubs by category. + {% else %} + Use filters to narrow down courses. + {% endif %} +

    +
    +
    + +
    +
    + {% if is_club %} + + {% else %} + + + + {% endif %} +
    + + {% if is_club %} +
    +
    +

    Clubs

    +

    {{ total }} result{{ total|pluralize }}

    +
    + + {% if grouped %} + {% for category_slug, category_data in grouped.items %} +
    +

    + + {{ category_data.category_name }} + +

    + +
    + {% for club in category_data.clubs %} +
    + + {{ club.name }} + +

    + {% if club.description %} + {{ club.description }} + {% else %} + No club description available. + {% endif %} +

    +
    + {% endfor %} +
    +
    + {% endfor %} + + {% include "site/components/_pagination.html" with paginated_items=page_obj aria_label="Club pagination" %} + {% else %} +
    +

    No clubs matched your search.

    +

    Try a broader keyword.

    +
    + {% endif %} +
    + {% else %} +
    +
    +

    Courses

    +

    {{ total }} result{{ total|pluralize }}

    +
    + + {% if grouped %} + {% for dept, dept_data in grouped.items %} +
    +

    + + {{ dept }} / {{ dept_data.subdept_name }} + +

    +
    + {% for c in dept_data.courses %} + + {% endfor %} +
    +
    + {% endfor %} + + {% include "site/components/_pagination.html" with paginated_items=page_obj aria_label="Course pagination" %} + {% else %} +
    +

    No courses matched your search.

    +

    Try adjusting your filters or search terms.

    +
    + {% endif %} +
    + +
    +
    +

    Instructors

    +

    {{ instructors|length }} result{{ instructors|length|pluralize }}

    +
    + + {% if instructors %} +
    + {% for i in instructors %} + + {{ i.first_name }} {{ i.last_name }} + + {% endfor %} +
    + {% else %} +
    +

    No professors matched your search.

    +

    Try searching by last name.

    +
    + {% endif %} +
    + + + {% endif %} +
    +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/tcf_website/templates/site/pages/terms.html b/tcf_website/templates/site/pages/terms.html new file mode 100644 index 000000000..43e245dea --- /dev/null +++ b/tcf_website/templates/site/pages/terms.html @@ -0,0 +1,18 @@ +{% extends "site/base.html" %} +{% load static %} + +{% block title %}Terms | theCourseForum{% endblock %} + +{% block styles %} + +{% endblock %} + +{% block content %} + +{% endblock %} diff --git a/tcf_website/templates/site/partials/privacy_content.html b/tcf_website/templates/site/partials/privacy_content.html new file mode 100644 index 000000000..cf32cc4e3 --- /dev/null +++ b/tcf_website/templates/site/partials/privacy_content.html @@ -0,0 +1 @@ +{% include "about/partials/privacy_content.html" %} diff --git a/tcf_website/templates/site/partials/terms_content.html b/tcf_website/templates/site/partials/terms_content.html new file mode 100644 index 000000000..7131c6307 --- /dev/null +++ b/tcf_website/templates/site/partials/terms_content.html @@ -0,0 +1 @@ +{% include "about/partials/terms_content.html" with privacy_url_name=privacy_url_name %} diff --git a/tcf_website/templatetags/custom_tags.py b/tcf_website/templatetags/custom_tags.py index 816baf0c7..50172d056 100644 --- a/tcf_website/templatetags/custom_tags.py +++ b/tcf_website/templatetags/custom_tags.py @@ -1,17 +1,99 @@ -""" custom tags to be used in templates """ +"""Custom tags and filters to be used in templates.""" + +from urllib.parse import urlencode from django import template register = template.Library() +COURSE_FILTER_KEYS = { + "discipline", + "subdepartment", + "weekdays", + "from_time", + "to_time", + "open_sections", + "min_gpa", +} + @register.filter def get_item(dictionary, key): - """This filter is used to access a dictonary context variable""" + """Return a dictionary item for template access.""" return dictionary.get(key) @register.filter def remove_email(value): - """This filter will remove the professors email from the string""" + """Remove instructor email suffix from display strings.""" return str(value).split("(", maxsplit=1)[0] + + +def _split_csv_keys(raw_keys): + """Split comma-separated key strings into a clean key list.""" + if not raw_keys: + return [] + return [key.strip() for key in str(raw_keys).split(",") if key.strip()] + + +def _querydict_to_lists(query_dict): + """Convert QueryDict to a mutable dict[str, list[str]].""" + return { + key: [str(value) for value in values] + for key, values in query_dict.lists() + } + + +@register.simple_tag +def querystring(request, include="", remove="", **overrides): + """Build an encoded querystring with include/remove/override support.""" + params = _querydict_to_lists(request.GET) + + include_keys = set(_split_csv_keys(include)) + if include_keys: + params = { + key: values + for key, values in params.items() + if key in include_keys + } + + remove_keys = set(_split_csv_keys(remove)) + for key in remove_keys: + params.pop(key, None) + + for key, value in overrides.items(): + if value in (None, ""): + params.pop(key, None) + else: + params[key] = [str(value)] + + return urlencode(params, doseq=True) + + +@register.simple_tag +def mode_toggle_url(request, target_mode): + """Build a mode-toggle URL while preserving an allowlist of query parameters.""" + url_name = getattr(request.resolver_match, "url_name", "") + allowed = {"q"} + if url_name == "search" and target_mode == "courses": + allowed = allowed.union(COURSE_FILTER_KEYS) + + params = { + key: values + for key, values in _querydict_to_lists(request.GET).items() + if key in allowed + } + params["mode"] = [str(target_mode)] + params.pop("page", None) + + query = urlencode(params, doseq=True) + if not query: + return request.path + return f"{request.path}?{query}" + + +@register.simple_tag +def getlist_contains(request, key, value): + """Return True when value exists in request.GET list for key.""" + values = request.GET.getlist(key) + return str(value) in values diff --git a/tcf_website/tests/test_browse.py b/tcf_website/tests/test_browse.py index e24e43e0f..813e134f7 100644 --- a/tcf_website/tests/test_browse.py +++ b/tcf_website/tests/test_browse.py @@ -1,32 +1,33 @@ -# pylint: disable=no-member -"""Tests for browse.py.""" -from django.test import TestCase -from django.urls import reverse - -from .test_utils import setup, suppress_request_warnings - - -class CourseViewTestCase(TestCase): - """Tests for Course views.""" - - def setUp(self): - setup(self) - - @suppress_request_warnings - def test_legacy_course_url_404(self): - """Test if the legacy course view can handle wrong URLs""" - url = reverse("course_legacy", args=[999999]) - response = self.client.get(url) - self.assertEqual(response.status_code, 404) - - def test_legacy_course_url_redirect(self): - """Test if the legacy course view can handle redirects""" - url = reverse("course_legacy", args=[self.course.id]) - response = self.client.get(url) - self.assertRedirects(response, "/course/CS/1420/") - - def test_redirect_lowercase_mnemonic(self): - """Test if course page URLs with lowercase mnemonics are redirected""" - url = reverse("course", args=["cs", 1421]) - response = self.client.get(url) - self.assertRedirects(response, "/course/CS/1421/") +# pylint: disable=no-member +"""Tests for browse.py.""" +from django.test import TestCase +from django.urls import reverse + +from .test_utils import setup, suppress_request_warnings + + +class CourseViewTestCase(TestCase): + """Tests for Course views.""" + + def setUp(self): + setup(self) + + def test_redirect_lowercase_mnemonic(self): + """Test if course page URLs with lowercase mnemonics are redirected""" + url = reverse("course", args=["cs", 1421]) + response = self.client.get(url) + self.assertRedirects(response, "/course/CS/1421/") + + @suppress_request_warnings + def test_unknown_course_returns_404(self): + """Unknown course should return 404.""" + url = reverse("course", args=["CS", 9999]) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + @suppress_request_warnings + def test_unknown_department_returns_404(self): + """Unknown department should return 404.""" + url = reverse("department", args=[999999]) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) diff --git a/tcf_website/urls.py b/tcf_website/urls.py index b23ed4578..1e9396a7e 100644 --- a/tcf_website/urls.py +++ b/tcf_website/urls.py @@ -11,6 +11,11 @@ views.club_category, name="club_category", ), + path( + "club///", + views.club_view, + name="club", + ), path("", views.index, name="index"), path("about/", views.AboutView.as_view(), name="about"), path("privacy/", views.privacy, name="privacy"), @@ -22,11 +27,6 @@ views.department, name="department_course_recency", ), - path( - "course//", - views.course_view_legacy, - name="course_legacy", - ), path( "course///", views.course_instructor, @@ -47,6 +47,11 @@ views.course_view, name="course_recency", ), + path( + "course//add-to-schedule/", + views.schedule_add_course, + name="schedule_add_course", + ), path( "instructor//", views.instructor_view, @@ -61,10 +66,16 @@ path("reviews/", views.reviews, name="reviews"), path("reviews//upvote/", views.upvote), path("reviews//downvote/", views.downvote), - path("reviews/check_duplicate/", views.review.check_duplicate), + path("reviews//vote/", views.vote_review, name="vote_review"), + path( + "reviews/check_duplicate/", + views.review.check_duplicate, + name="check_review_duplicate", + ), path( "reviews/check_zero_hours_per_week/", views.review.check_zero_hours_per_week, + name="check_zero_hours_per_week", ), path("profile/", views.profile, name="profile"), path( @@ -78,23 +89,16 @@ path("schedule/new/", views.new_schedule, name="new_schedule"), path("schedule/delete/", views.delete_schedule, name="delete_schedule"), path("schedule/edit/", views.edit_schedule, name="edit_schedule"), + path( + "schedule/course//remove/", + views.remove_scheduled_course, + name="remove_scheduled_course", + ), path( "schedule/duplicate//", views.duplicate_schedule, name="duplicate_schedule", ), - path("schedule/modal/editor", views.modal_load_editor, name="modal_load_editor"), - path( - "schedule/modal/sections/", - views.modal_load_sections, - name="modal_load_sections", - ), - path( - "schedule/modal//", - views.view_select_schedules_modal, - name="modal_load_schedules", - ), - path("schedule/add_course/", views.schedule_add_course, name="schedule_add_course"), # QA URLs path("answers/check_duplicate/", views.qa.check_duplicate), path("qa/new_question/", views.new_question, name="new_question"), diff --git a/tcf_website/views/__init__.py b/tcf_website/views/__init__.py index f83dec6b5..94f276a35 100644 --- a/tcf_website/views/__init__.py +++ b/tcf_website/views/__init__.py @@ -6,14 +6,19 @@ from .auth import login, logout from .browse import ( browse, + club_category, + club_view, course_instructor, course_view, - course_view_legacy, department, instructor_view, - club_category, ) -from .index import AboutView, index, privacy, terms +from .index import ( + AboutView, + index, + privacy, + terms, +) from .profile import DeleteProfile, profile, reviews from .qa import ( DeleteAnswer, @@ -27,16 +32,20 @@ upvote_answer, upvote_question, ) -from .review import DeleteReview, downvote, new_review, upvote +from .review import ( + DeleteReview, + downvote, + new_review, + upvote, + vote_review, +) from .schedule import ( delete_schedule, duplicate_schedule, edit_schedule, - modal_load_editor, - modal_load_sections, new_schedule, + remove_scheduled_course, schedule_add_course, view_schedules, - view_select_schedules_modal, ) from .search import search diff --git a/tcf_website/views/auth.py b/tcf_website/views/auth.py index 89cfa76c9..3188d0603 100644 --- a/tcf_website/views/auth.py +++ b/tcf_website/views/auth.py @@ -10,7 +10,8 @@ from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout from django.contrib.auth.decorators import login_required from django.http import HttpResponseRedirect -from django.shortcuts import redirect, render +from django.shortcuts import redirect +from django.views.decorators.http import require_POST logger = logging.getLogger(__name__) @@ -97,12 +98,8 @@ def cognito_callback(request): return redirect("index") -def unauthenticated_index(request): - """Index shown to non-logged in users.""" - return render(request, "landing/landing.html") - - @login_required +@require_POST def logout(request): """Logs out user and redirects to Cognito logout.""" auth_logout(request) diff --git a/tcf_website/views/browse.py b/tcf_website/views/browse.py index 4aaceacb2..1f5bfddb0 100644 --- a/tcf_website/views/browse.py +++ b/tcf_website/views/browse.py @@ -22,14 +22,12 @@ from django.urls import reverse from ..models import ( - Answer, Club, ClubCategory, Course, CourseInstructorGrade, Department, Instructor, - Question, Review, School, Section, @@ -38,11 +36,10 @@ def browse(request): - """View for browse page.""" + """View for browse page - Modern design.""" mode, is_club = parse_mode(request) if is_club: - # Get all club categories club_categories = ( ClubCategory.objects.all() .prefetch_related( @@ -57,7 +54,7 @@ def browse(request): return render( request, - "browse/browse.html", + "site/pages/browse.html", { "is_club": True, "mode": mode, @@ -69,13 +66,11 @@ def browse(request): seas = School.objects.get(name="School of Engineering & Applied Science") excluded_list = [clas.pk, seas.pk] - - # Other schools besides CLAS, SEAS, and Misc. other_schools = School.objects.exclude(pk__in=excluded_list).order_by("name") return render( request, - "browse/browse.html", + "site/pages/browse.html", { "is_club": False, "mode": mode, @@ -87,31 +82,24 @@ def browse(request): def department(request, dept_id: int, course_recency=None): - """View for department page.""" - - # Prefetch related subdepartments and courses to improve performance. - # department.html loops through related subdepartments and courses. - # See: - # https://docs.djangoproject.com/en/3.0/ref/models/querysets/#django.db.models.query.QuerySet.prefetch_related - dept = Department.objects.prefetch_related("subdepartment_set").get(pk=dept_id) - # Current semester or last five years + """View for department page - Modern design.""" + dept = get_object_or_404( + Department.objects.prefetch_related("subdepartment_set"), pk=dept_id + ) + if not course_recency: course_recency = str(Semester.latest()) - # Navigation breadcrimbs breadcrumbs = [ (dept.school.name, reverse("browse"), False), (dept.name, None, True), ] - # Setting up sorting and course age variables latest_semester = Semester.latest() last_five_years = get_object_or_404(Semester, number=latest_semester.number - 50) - # Fetch season and year (either current semester or 5 years previous) season, year = course_recency.upper().split() active_semester = Semester.objects.filter(year=year, season=season).first() - # Fetch sorting variables sortby = request.GET.get("sortby", "course_id") order = request.GET.get("order", "asc") page = request.GET.get("page", 1) @@ -122,9 +110,8 @@ def department(request, dept_id: int, course_recency=None): return render( request, - "department/department.html", + "site/pages/department.html", { - # "subdepartments": dept.subdepartment_set.all(), "dept_id": dept_id, "latest_semester": str(latest_semester), "breadcrumbs": breadcrumbs, @@ -137,97 +124,113 @@ def department(request, dept_id: int, course_recency=None): ) -def course_view_legacy(request, course_id): - """Legacy view for course page.""" - course = get_object_or_404(Course, pk=course_id) - return redirect( - "course", - mnemonic=course.subdepartment.mnemonic, - course_number=course.number, - ) - - def parse_mode(request): """Parse the mode parameter from the request.""" mode = request.GET.get("mode", "courses") return mode, (mode == "clubs") -def course_view( - request, - mnemonic: str, - course_number: int, - instructor_recency=None, -): - """A new Course view that allows you to input mnemonic and number instead.""" +def _is_lecture_section(section_type: str | None) -> bool: + """Return True when a section type should be treated as lecture.""" + if not section_type: + return True + normalized = section_type.strip().lower() + return normalized.startswith("lec") - mode, is_club = parse_mode(request) - if not instructor_recency: - instructor_recency = str(Semester.latest()) +def _split_section_times(section_times: str) -> list[str]: + """Convert comma-separated section times into a clean list.""" + if not section_times: + return [] + return [ + entry.strip() for entry in section_times.rstrip(",").split(",") if entry.strip() + ] - # No longer storing in session - client-side storage will handle this - # (JavaScript now handles this via localStorage) - if is_club: - # 'mnemonic' is actually category_slug, 'course_number' is club.id - club = get_object_or_404( - Club, id=course_number, category__slug=mnemonic.upper() - ) +def _build_section_times_maps_by_instructor( + course_id: int, semester: Semester +) -> tuple[dict[int, dict[str, list[str]]], dict[int, dict[str, list[str]]]]: + """Build all-sections and lecture-only section-time maps keyed by instructor ID.""" + all_times_by_instructor: dict[int, dict[str, list[str]]] = {} + lecture_times_by_instructor: dict[int, dict[str, list[str]]] = {} - # Pull reviews exactly as you do for courses, but filter on club=club - page_number = request.GET.get("page", 1) - paginated_reviews = Review.objects.filter( - club=club, - toxicity_rating__lt=settings.TOXICITY_THRESHOLD, - hidden=False, - ).exclude(text="") - - if request.user.is_authenticated: - paginated_reviews = paginated_reviews.annotate( - sum_votes=Coalesce(Sum("vote__value"), Value(0)), - user_vote=Coalesce( - Sum("vote__value", filter=Q(vote__user=request.user)), - Value(0), - ), - ) + sections = ( + Section.objects.filter(course_id=course_id, semester=semester) + .prefetch_related("instructors") + .order_by("sis_section_number") + ) - paginated_reviews = Review.sort( - paginated_reviews, request.GET.get("method", "") - ) + for section in sections: + times = _split_section_times(section.section_times) + if not times: + continue - paginated_reviews = Review.paginate(paginated_reviews, page_number) + section_number = str(section.sis_section_number) + for section_instructor in section.instructors.all(): + all_times_by_instructor.setdefault(section_instructor.id, {})[ + section_number + ] = times + if _is_lecture_section(section.section_type): + lecture_times_by_instructor.setdefault(section_instructor.id, {})[ + section_number + ] = times - # Breadcrumbs for club - breadcrumbs = [ - ("Clubs", reverse("browse") + "?mode=clubs", False), - ( - club.category.name, - reverse("club_category", args=[club.category.slug]) + "?mode=clubs", - False, - ), - (club.name, None, True), - ] + return all_times_by_instructor, lecture_times_by_instructor - # Pass club info to template for meta tags - club_code = f"{club.category.slug} {club.id}" - return render( - request, - "club/club.html", - { - "is_club": True, - "mode": mode, - "club": club, - "paginated_reviews": paginated_reviews, - "sort_method": request.GET.get("method", ""), - "breadcrumbs": breadcrumbs, - "course_code": club_code, - "course_title": club.name, - }, +def _get_paginated_club_reviews(club: Club, user, page_number=1, method=""): + """Build sorted/paginated club reviews with vote annotations.""" + reviews = Review.objects.filter( + club=club, + toxicity_rating__lt=settings.TOXICITY_THRESHOLD, + hidden=False, + ).exclude(text="") + + if user.is_authenticated: + reviews = reviews.annotate( + sum_votes=Coalesce(Sum("vote__value"), Value(0)), + user_vote=Coalesce( + Sum("vote__value", filter=Q(vote__user=user)), + Value(0), + ), ) - # Redirect if the mnemonic is not all uppercase + return Review.paginate(Review.sort(reviews, method), page_number) + + +def _build_club_page_context(request, club: Club, mode: str): + """Build shared context for club detail pages.""" + sort_method = request.GET.get("sort", "") + page_number = request.GET.get("page", 1) + paginated_reviews = _get_paginated_club_reviews( + club, request.user, page_number, sort_method + ) + + breadcrumbs = [ + ("Clubs", reverse("browse") + "?mode=clubs", False), + ( + club.category.name, + reverse("club_category", args=[club.category.slug]) + "?mode=clubs", + False, + ), + (club.name, None, True), + ] + + return { + "is_club": True, + "mode": mode, + "club": club, + "paginated_reviews": paginated_reviews, + "num_reviews": paginated_reviews.paginator.count, + "sort_method": sort_method, + "breadcrumbs": breadcrumbs, + "course_code": f"{club.category.slug} {club.id}", + "course_title": club.name, + } + + +def course_view(request, mnemonic: str, course_number: int, instructor_recency=None): + """Course view.""" if mnemonic != mnemonic.upper(): return redirect( "course", mnemonic=mnemonic.upper(), course_number=course_number @@ -240,79 +243,70 @@ def course_view( ) latest_semester = Semester.latest() - recent = str(latest_semester) == instructor_recency + show_all = request.GET.get("show") == "all" + if not instructor_recency: + instructor_recency = str(latest_semester) + recent = not show_all and str(latest_semester) == instructor_recency - # Fetch sorting variables sortby = request.GET.get("sortby", "last_taught") order = request.GET.get("order", "desc") instructors = course.sort_instructors_by_key(latest_semester, recent, order, sortby) - # Remove none values from section_times and section_nums - # For whatever reason, it is not possible to remove None from .annotate()'s ArrayAgg() function + for instructor in instructors: if hasattr(instructor, "section_times") and instructor.section_times: instructor.section_times = [ s for s in instructor.section_times if s is not None ] - if hasattr(instructor, "section_nums") and instructor.section_nums: instructor.section_nums = [ s for s in instructor.section_nums if s is not None ] - # Note: Could be simplified further + all_times_by_instructor, lecture_times_by_instructor = ( + _build_section_times_maps_by_instructor(course.id, latest_semester) + ) for instructor in instructors: - # Convert to string for templating instructor.semester_last_taught = str( get_object_or_404(Semester, pk=instructor.semester_last_taught) ) - if instructor.section_times and instructor.section_nums: - if instructor.section_times[0] and instructor.section_nums[0]: - instructor.times = { - num: times[:-1].split(",") - for num, times in zip( - instructor.section_nums, instructor.section_times - ) - if num and times - } + instructor.times = lecture_times_by_instructor.get(instructor.id, {}) + instructor.all_times = all_times_by_instructor.get(instructor.id, {}) dept = course.subdepartment.department - # Navigation breadcrumbs breadcrumbs = [ (dept.school.name, reverse("browse"), False), (dept.name, reverse("department", args=[dept.pk]), False), (course.code, None, True), ] - # Pass course info to template for meta tags - # (JavaScript will retrieve these from meta tags) - return render( request, - "course/course.html", + "site/pages/course.html", { - "is_club": False, - "mode": mode, "course": course, "instructors": instructors, "latest_semester": str(latest_semester), "breadcrumbs": breadcrumbs, "sortby": sortby, "order": order, - "active_instructor_recency": instructor_recency, + "active_instructor_recency": "all_time" if show_all else instructor_recency, "course_code": course.code(), "course_title": course.title, + "all_section_times_by_instructor": all_times_by_instructor, }, ) def course_instructor(request, course_id, instructor_id, method="Default"): - """View for course instructor page.""" + """Course-instructor view.""" section_last_taught = ( - Section.objects.filter(course=course_id, instructors=instructor_id) + Section.objects.filter(course_id=course_id, instructors__id=instructor_id) .order_by("-semester__number") + .select_related("course", "semester") + .prefetch_related("instructors") .first() ) if section_last_taught is None: @@ -320,7 +314,6 @@ def course_instructor(request, course_id, instructor_id, method="Default"): course = section_last_taught.course instructor = section_last_taught.instructors.get(pk=instructor_id) - # ratings: reviews with and without text; reviews: ratings with text reviews = Review.objects.filter( instructor=instructor_id, course=course_id, @@ -330,13 +323,15 @@ def course_instructor(request, course_id, instructor_id, method="Default"): dept = course.subdepartment.department + sort_method = request.GET.get("sort", method) page_number = request.GET.get("page", 1) paginated_reviews = Review.get_paginated_reviews( - course_id, instructor_id, request.user, page_number, method + course_id, instructor_id, request.user, page_number, sort_method ) - course_url = reverse("course", args=[course.subdepartment.mnemonic, course.number]) - # Navigation breadcrumbs + course_url = reverse( + "course", args=[course.subdepartment.mnemonic, course.number] + ) breadcrumbs = [ (dept.school.name, reverse("browse"), False), (dept.name, reverse("department", args=[dept.pk]), False), @@ -345,37 +340,34 @@ def course_instructor(request, course_id, instructor_id, method="Default"): ] data = Review.objects.filter(course=course_id, instructor=instructor_id).aggregate( - # rating stats average_rating=( Avg("instructor_rating") + Avg("enjoyability") + Avg("recommendability") ) / 3, - average_instructor=Avg("instructor_rating"), - average_fun=Avg("enjoyability"), - average_recommendability=Avg("recommendability"), - average_difficulty=Avg("difficulty"), - # workload stats - average_hours_per_week=Avg("hours_per_week"), - average_amount_reading=Avg("amount_reading"), - average_amount_writing=Avg("amount_writing"), - average_amount_group=Avg("amount_group"), - average_amount_homework=Avg("amount_homework"), + instructor=Avg("instructor_rating"), + enjoyability=Avg("enjoyability"), + difficulty=Avg("difficulty"), + recommendability=Avg("recommendability"), + hours=Avg("hours_per_week"), + amount_reading=Avg("amount_reading"), + amount_writing=Avg("amount_writing"), + amount_group=Avg("amount_group"), + amount_homework=Avg("amount_homework"), ) - data = {key: safe_round(value) for key, value in data.items()} + for key, value in data.items(): + if value is not None: + data[key] = round(value, 2) try: grades_data = CourseInstructorGrade.objects.get( instructor=instructor, course=course ) - except ObjectDoesNotExist: # if no data found + except ObjectDoesNotExist: pass - # NOTE: Don't catch MultipleObjectsReturned because we want to be notified - else: # Fill in the data found - # grades stats + else: data["average_gpa"] = ( round(grades_data.average, 2) if grades_data.average else None ) - # pylint: disable=duplicate-code fields = [ "a_plus", "a", @@ -392,34 +384,11 @@ def course_instructor(request, course_id, instructor_id, method="Default"): for field in fields: data[field] = getattr(grades_data, field) - # No longer storing in session - # Course and instructor info is passed to template context for meta tags - # Sections will be fetched via API when dropdown is expanded - - # QA Data - questions = Question.objects.filter(course=course_id, instructor=instructor_id) - answers = {} - for question in questions: - answers[question.id] = Answer.display_activity(question.id, request.user) - questions = Question.display_activity(course_id, instructor_id, request.user) - latest_semester = Semester.latest() is_current_semester = section_last_taught.semester.number == latest_semester.number - # instructor dropdown - other_instructors = ( - Instructor.objects.filter( - section__course_id=course_id, - section__semester=section_last_taught.semester, - hidden=False, - ) - .exclude(id=instructor_id) - .distinct() - .order_by("last_name")[:30] - ) - - # sections (current semester only) - sections = [] + lecture_sections = [] + other_sections = [] if is_current_semester: sections_qs = Section.objects.filter( course_id=course_id, instructors__id=instructor_id, semester=latest_semester @@ -430,21 +399,24 @@ def course_instructor(request, course_id, instructor_id, method="Default"): if section.section_times: times_display = section.section_times.rstrip(",") - sections.append( - { - "number": section.sis_section_number, - "type": section.section_type or "Lecture", - "units": section.units, - "times": times_display, - "enrollment_taken": section.enrollment_taken or 0, - "enrollment_limit": section.enrollment_limit or 0, - "waitlist_taken": section.waitlist_taken or 0, - "waitlist_limit": section.waitlist_limit or 0, - } - ) + section_data = { + "number": section.sis_section_number, + "type": section.section_type or "Section", + "units": section.units, + "times": times_display, + "enrollment_taken": section.enrollment_taken or 0, + "enrollment_limit": section.enrollment_limit or 0, + "waitlist_taken": section.waitlist_taken or 0, + "waitlist_limit": section.waitlist_limit or 0, + } + if _is_lecture_section(section.section_type): + lecture_sections.append(section_data) + else: + other_sections.append(section_data) + return render( request, - "course/course_professor.html", + "site/pages/course_instructor.html", { "course": course, "course_id": course_id, @@ -457,22 +429,20 @@ def course_instructor(request, course_id, instructor_id, method="Default"): "data": json.dumps(data), "display_times": latest_semester == section_last_taught.semester, "is_current_semester": is_current_semester, - "questions": questions, - "answers": answers, - "sort_method": method, + "sort_method": sort_method, "sem_code": section_last_taught.semester.number, - "latest_semester_id": latest_semester.id, "course_code": course.code(), "course_title": course.title, "instructor_fullname": instructor.full_name, - "other_instructors": other_instructors, - "sections": sections, + "lecture_sections": lecture_sections, + "other_sections": other_sections, + "sections_count": len(lecture_sections) + len(other_sections), }, ) def instructor_view(request, instructor_id): - """View for instructor page, showing all their courses taught.""" + """Instructor view.""" instructor: Instructor = get_object_or_404(Instructor, pk=instructor_id) stats: dict[str, float] = Instructor.objects.filter(pk=instructor.pk).aggregate( @@ -489,7 +459,6 @@ def instructor_view(request, instructor_id): courses = list(instructor.get_course_summaries()) is_teaching_current_semester = any(course.get("is_current") for course in courses) - # Build a mapping from semester number to (season, year) in one query semester_numbers = { num for num in (c.get("latest_semester_number") for c in courses) if num } @@ -520,7 +489,7 @@ def instructor_view(request, instructor_id): "courses": grouped_courses, "is_teaching_current_semester": is_teaching_current_semester, } - return render(request, "instructor/instructor.html", context) + return render(request, "site/pages/instructor.html", context) def safe_round(num): @@ -535,17 +504,12 @@ def safe_round(num): def club_category(request, category_slug: str): """View for club category page.""" - mode = parse_mode(request)[0] # Only use the mode, ignoring is_club - - # Get the category by slug + mode = "clubs" category = get_object_or_404(ClubCategory, slug=category_slug.upper()) - - # Get clubs in this category clubs = Club.objects.filter(category=category).order_by("name") - # Pagination page_number = request.GET.get("page", 1) - paginator = Paginator(clubs, 10) # 10 clubs per page + paginator = Paginator(clubs, 10) try: paginated_clubs = paginator.page(page_number) except PageNotAnInteger: @@ -553,7 +517,6 @@ def club_category(request, category_slug: str): except EmptyPage: paginated_clubs = paginator.page(paginator.num_pages) - # Navigation breadcrumbs breadcrumbs = [ ("Clubs", reverse("browse") + "?mode=clubs", False), (category.name, None, True), @@ -561,7 +524,7 @@ def club_category(request, category_slug: str): return render( request, - "club/category.html", + "site/pages/club_category.html", { "is_club": True, "mode": mode, @@ -570,3 +533,14 @@ def club_category(request, category_slug: str): "breadcrumbs": breadcrumbs, }, ) + + +def club_view(request, category_slug: str, club_id: int): + """View for club detail page.""" + mode = "clubs" + club = get_object_or_404(Club, id=club_id, category__slug=category_slug.upper()) + return render( + request, + "site/pages/club.html", + _build_club_page_context(request, club, mode), + ) diff --git a/tcf_website/views/index.py b/tcf_website/views/index.py index 92ebc0dec..1d9a291e2 100644 --- a/tcf_website/views/index.py +++ b/tcf_website/views/index.py @@ -3,62 +3,114 @@ import json from django.shortcuts import render -from django.template.loader import render_to_string from django.views.generic.base import TemplateView def index(request): """ Index view. - - Redirect to landing page if user not authorized. """ - - # Load "About Team" data from json file + # Load team info for the landing page with open("tcf_website/views/team_info.json", encoding="UTF-8") as data_file: team_info = json.load(data_file) - # Load FAQ data from json file, evaluating tags and filters - rendered = render_to_string("landing/_faqs.json") - faqs = json.loads(rendered) - response = render( + + mode = request.GET.get("mode", "courses") + is_club = mode == "clubs" + + context = { + "executive_team": team_info["executive_team"], + "mode": mode, + "mode_noun": "club" if is_club else "course", + "search_placeholder": ( + "Search for a club..." + if is_club + else "Search for a course or professor..." + ), + } + + return render( request, - "landing/landing.html", - { - "executive_team": team_info["executive_team"], - "FAQs": faqs, - }, + "site/pages/landing.html", + context, ) - return response def privacy(request): """Privacy view.""" - return render(request, "about/privacy.html") + return render(request, "site/pages/privacy.html") def terms(request): """Terms view.""" - return render(request, "about/terms.html") + return render(request, "site/pages/terms.html") class AboutView(TemplateView): """About view.""" - template_name = "about/about.html" + template_name = "site/pages/about.html" - # Load data from json files with open("tcf_website/views/team_info.json", encoding="UTF-8") as data_file: team_info = json.load(data_file) with open("tcf_website/views/team_alums.json", encoding="UTF-8") as data_file: alum_info = json.load(data_file) + @staticmethod + def _normalize_member(member: dict, fallback_role: str = "") -> dict: + """Normalize member fields for template rendering.""" + name = member.get("name", "").strip() + parts = name.split() + first_name = parts[0] if parts else "" + last_name = parts[-1] if len(parts) > 1 else "" + initials = ( + f"{first_name[:1]}{last_name[:1]}".upper() + if first_name and last_name + else (first_name[:2] if first_name else "TC").upper() + ) + return { + "name": name, + "first_name": first_name, + "last_name": last_name, + "initials": initials, + "role": member.get("role", fallback_role), + "class": member.get("class", ""), + "img_filename": member.get("img_filename", ""), + "github": member.get("github", ""), + } + + def _normalize_members(self, members: list[dict], fallback_role: str = "") -> list[dict]: + """Normalize a list of members for shared card rendering.""" + return [ + self._normalize_member(member, fallback_role=fallback_role) + for member in members + ] + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["executive_team"] = self.team_info["executive_team"] - context["engineering_team"] = self.team_info["engineering_team"] - context["marketing_team"] = self.team_info["marketing_team"] - context["design_team"] = self.team_info["design_team"] - context["founders"] = self.alum_info["founders"] - context["contributors"] = self.alum_info["contributors"] + executive_team = self._normalize_members(self.team_info.get("executive_team", [])) + engineering_team = self._normalize_members( + self.team_info.get("engineering_team", []) + ) + design_team = self._normalize_members(self.team_info.get("design_team", [])) + marketing_team = self._normalize_members( + self.team_info.get("marketing_team", []) + ) + + contributors = [] + for group in self.alum_info.get("contributors", []): + contributors.append( + { + "group_name": group.get("group_name", ""), + "members": self._normalize_members(group.get("members", [])), + } + ) + + context["executive_team"] = executive_team + context["engineering_team"] = engineering_team + context["design_team"] = design_team + context["marketing_team"] = marketing_team + context["team"] = executive_team + engineering_team + design_team + marketing_team + context["founders"] = self._normalize_members(self.alum_info.get("founders", [])) + context["contributors"] = contributors return context diff --git a/tcf_website/views/profile.py b/tcf_website/views/profile.py index a73d0bf4c..8fffd3989 100644 --- a/tcf_website/views/profile.py +++ b/tcf_website/views/profile.py @@ -16,20 +16,37 @@ from ..models import Review, User from .browse import safe_round +from .utils import safe_next_url + + +def _review_stats_for_user(user): + """Build review stats for a given user.""" + upvote_stat = Review.objects.filter(user=user).aggregate( + total_review_upvotes=Count("vote", filter=Q(vote__value=1)), + ) + other_stats = User.objects.filter(id=user.id).aggregate( + total_reviews_written=Count("review"), + average_review_rating=( + Avg("review__instructor_rating") + + Avg("review__enjoyability") + + Avg("review__recommendability") + ) + / 3, + ) + merged = upvote_stat | other_stats + return {key: safe_round(value) for key, value in merged.items()} class ProfileForm(ModelForm): - """Form updating user profile.""" + """Form for profile page.""" class Meta: model = User fields = ["first_name", "last_name", "graduation_year"] - - # Add the form-control class to make the form work with Bootstrap widgets = { - "first_name": forms.TextInput(attrs={"class": "form-control"}), - "last_name": forms.TextInput(attrs={"class": "form-control"}), - "graduation_year": forms.NumberInput(attrs={"class": "form-control"}), + "first_name": forms.TextInput(attrs={"class": "form-group__input"}), + "last_name": forms.TextInput(attrs={"class": "form-group__input"}), + "graduation_year": forms.NumberInput(attrs={"class": "form-group__input"}), } @@ -47,31 +64,18 @@ def profile(request): return HttpResponseRedirect("/profile") form = ProfileForm(label_suffix="", instance=request.user) - return render(request, "profile/profile.html", {"form": form}) + return render(request, "site/pages/profile.html", {"form": form}) @login_required def reviews(request): """User reviews view.""" - # Handled separately because it requires joining 1 more table (i.e. Vote) - upvote_stat = Review.objects.filter(user=request.user).aggregate( - total_review_upvotes=Count("vote", filter=Q(vote__value=1)), - ) - # Get other statistics - other_stats = User.objects.filter(id=request.user.id).aggregate( - total_reviews_written=Count("review"), - average_review_rating=( - Avg("review__instructor_rating") - + Avg("review__enjoyability") - + Avg("review__recommendability") - ) - / 3, - ) - # Merge the two dictionaries - merged = upvote_stat | other_stats - # Round floats - stats = {key: safe_round(value) for key, value in merged.items()} - return render(request, "reviews/user_reviews.html", context=stats) + page_number = request.GET.get("page", 1) + paginated_reviews = Review.paginate(request.user.reviews(), page_number) + + context = _review_stats_for_user(request.user) + context["paginated_reviews"] = paginated_reviews + return render(request, "site/pages/reviews.html", context=context) class DeleteProfile(LoginRequiredMixin, SuccessMessageMixin, generic.DeleteView): @@ -80,6 +84,10 @@ class DeleteProfile(LoginRequiredMixin, SuccessMessageMixin, generic.DeleteView) model = User success_url = reverse_lazy("browse") + def get_success_url(self): + """Use caller-provided next URL when safe, otherwise pick default.""" + return safe_next_url(self.request, str(self.success_url)) + def form_valid(self, form): """Override DeleteView's function to just call logout before deleting""" # form_valid() is overrideen instead of delete() since it's more in line diff --git a/tcf_website/views/review.py b/tcf_website/views/review.py index ad8b9f495..ac71ffee1 100644 --- a/tcf_website/views/review.py +++ b/tcf_website/views/review.py @@ -6,12 +6,16 @@ from django.contrib.auth.mixins import LoginRequiredMixin # For class-based views from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import PermissionDenied, ValidationError +from django.db.models import Sum from django.http import JsonResponse from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse from django.urls import reverse_lazy from django.views import generic +from django.views.decorators.http import require_POST from ..models import Review, Club, Course, Instructor, Semester +from .utils import safe_next_url # pylint: disable=fixme,unused-argument @@ -76,35 +80,154 @@ def save(self, commit=True): return instance +def _vote_response_payload(review: Review, user) -> dict[str, int]: + """Return vote state payload for frontend updates.""" + user_vote = ( + review.vote_set.filter(user=user).values_list("value", flat=True).first() or 0 + ) + sum_votes = review.vote_set.aggregate(total=Sum("value"))["total"] or 0 + return {"ok": True, "sum_votes": sum_votes, "user_vote": user_vote} + + @login_required +@require_POST def upvote(request, review_id): """Upvote a view.""" - if request.method == "POST": - review = Review.objects.get(pk=review_id) - review.upvote(request.user) - return JsonResponse({"ok": True}) - return JsonResponse({"ok": False}) + review = get_object_or_404(Review, pk=review_id) + review.upvote(request.user) + return JsonResponse(_vote_response_payload(review, request.user)) @login_required +@require_POST def downvote(request, review_id): """Downvote a view.""" - if request.method == "POST": - review = Review.objects.get(pk=review_id) + review = get_object_or_404(Review, pk=review_id) + review.downvote(request.user) + return JsonResponse(_vote_response_payload(review, request.user)) + + +@login_required +@require_POST +def vote_review(request, review_id): + """Vote on a review using a single endpoint.""" + review = get_object_or_404(Review, pk=review_id) + action = request.POST.get("action") + + if action == "up": + review.upvote(request.user) + elif action == "down": review.downvote(request.user) - return JsonResponse({"ok": True}) - return JsonResponse({"ok": False}) + else: + return JsonResponse({"ok": False, "error": "Invalid action"}, status=400) + + return JsonResponse(_vote_response_payload(review, request.user)) + + +@login_required() +def check_duplicate(request): + """Check for duplicate reviews when a user submits a review.""" + + form = ReviewForm(request.POST) + if form.is_valid(): + instance = form.save(commit=False) + + if instance.club: + # Check if user has reviewed given club before + reviews_on_same_club = request.user.review_set.filter(club=instance.club) + # Review already exists so it's a duplicate; inform user + if reviews_on_same_club.exists(): + return JsonResponse({"duplicate": True}) + return JsonResponse({"duplicate": False}) + + # First check if user has reviewed given course during same + # semester before + reviews_on_same_class = request.user.review_set.filter( + course=instance.course, semester=instance.semester + ) + + # Review already exists so it's a duplicate; inform user + if reviews_on_same_class.exists(): + response = {"duplicate": True} + return JsonResponse(response) + + # Then check if user has reviewed given course with same + # instructor before + reviews_on_same_class = request.user.review_set.filter( + course=instance.course, instructor=instance.instructor + ) + # Review already exists so it's a duplicate; inform user + if reviews_on_same_class.exists(): + response = {"duplicate": True} + return JsonResponse(response) + + # User has not reviewed course/club before; proceed with standard form submission + response = {"duplicate": False} + return JsonResponse(response) + return redirect("new_review") + + +@login_required() +def check_zero_hours_per_week(request): + """Check that user hasn't submitted 0 *total* hours/week + for a given course/review. + Used for an Ajax request in new_review.html""" + + form = ReviewForm(request.POST) + if form.is_valid(): + instance = form.save(commit=False) + + hours_per_week = ( + instance.amount_reading + + instance.amount_writing + + instance.amount_group + + instance.amount_homework + ) + + # Review has 0 total hours/week + # Send user a warning message that they have entered 0 hours + if hours_per_week == 0: + response = {"zero": True} + return JsonResponse(response) + + # Otherwise, proceed with normal form submission + response = {"zero": False} + return JsonResponse(response) + return redirect("new_review") + + +# Note: Class-based views can't use the @login_required decorator + + +class DeleteReview(LoginRequiredMixin, SuccessMessageMixin, generic.DeleteView): + """Review deletion view.""" + + model = Review + success_url = reverse_lazy("reviews") + + def get_success_url(self): + """Use caller-provided next URL when safe, otherwise default.""" + return safe_next_url(self.request, str(self.success_url)) + + def get_object(self): # pylint: disable=arguments-differ + """Override DeleteView's function to validate review belonging to user.""" + obj = super().get_object() + # For security: Make sure target review belongs to the current user + if obj.user != self.request.user: + raise PermissionDenied("You are not allowed to delete this review!") + return obj + + def get_success_message(self, cleaned_data) -> str: + """Overrides SuccessMessageMixin's get_success_message method.""" + # Check if it's a club review + if self.object.club: + return f"Successfully deleted your review for {self.object.club}!" + return f"Successfully deleted your review for {self.object.course}!" @login_required def new_review(request): - """Review creation view with context-required logic. - - Routes: - - /reviews/new/?course=X&instructor=Y → Full form, server-rendered - - /reviews/new/?club=Z → Full club review form - - /reviews/new/ - """ + """Review creation view with context-required logic.""" mode, is_club = parse_mode(request) # Handle POST (form submission) @@ -127,36 +250,48 @@ def new_review(request): else: messages.success(request, f"Successfully reviewed {instance.course}!") - return redirect("reviews") + # Redirect to the new relevant page + if instance.club: + club_url = reverse( + "club", + kwargs={ + "category_slug": instance.club.category.slug, + "club_id": instance.club.id, + }, + ) + return redirect( + f"{club_url}?mode=clubs" + ) + return redirect( + "course", + mnemonic=instance.course.subdepartment.mnemonic, + course_number=instance.course.number, + ) # Form invalid - re-render with errors - # Need to re-fetch context for the template return _render_review_form_with_errors(request, form, is_club, mode) - # Handle GET - require context or redirect to search + # Handle GET if is_club: return _handle_club_review_get(request, mode) return _handle_course_review_get(request, mode) def _handle_course_review_get(request, mode): - """Handle GET for course reviews - require course context.""" + """Handle GET for course reviews.""" course_id = request.GET.get("course") instructor_id = request.GET.get("instructor") - # No context - need course ID, redirect to home if not course_id: messages.info(request, "Please select a course to review from a course page.") - return redirect("/browse?mode=courses") + return redirect("browse") course = get_object_or_404(Course, id=course_id) latest = Semester.latest() if instructor_id: - # Full context: course + instructor instructor = get_object_or_404(Instructor, id=instructor_id) instructors = None - # Semesters when this instructor taught this course (last 5 years) semesters = ( Semester.objects.filter( section__course=course, @@ -167,7 +302,6 @@ def _handle_course_review_get(request, mode): .order_by("-number") ) else: - # Partial context: only course - let user pick instructor instructor = None instructors = ( Instructor.objects.filter( @@ -178,7 +312,6 @@ def _handle_course_review_get(request, mode): .distinct() .order_by("last_name")[:50] ) - # All recent semesters for this course semesters = ( Semester.objects.filter(section__course=course, year__gte=latest.year - 5) .distinct() @@ -187,7 +320,7 @@ def _handle_course_review_get(request, mode): return render( request, - "reviews/new_review.html", + "site/pages/review.html", { "is_club": False, "mode": mode, @@ -200,25 +333,23 @@ def _handle_course_review_get(request, mode): def _handle_club_review_get(request, mode): - """Handle GET for club reviews - require club context.""" + """Handle GET for club reviews.""" club_id = request.GET.get("club") - # No context - need club ID, redirect to home if not club_id: messages.info(request, "Please select a club to review.") - return redirect("/browse?mode=clubs") + return redirect("browse") club = get_object_or_404(Club, id=club_id) latest = Semester.latest() - # Recent semesters for the dropdown semesters = Semester.objects.filter(year__gte=latest.year - 5).order_by("-number")[ :10 ] return render( request, - "reviews/new_review.html", + "site/pages/review.html", { "is_club": True, "mode": mode, @@ -229,7 +360,7 @@ def _handle_club_review_get(request, mode): def _render_review_form_with_errors(request, form, is_club, mode): - """Re-render the form with validation errors, fetching context from POST data.""" + """Re-render the form with validation errors.""" context = {"form": form, "is_club": is_club, "mode": mode} if is_club and form.cleaned_data.get("club"): @@ -238,107 +369,9 @@ def _render_review_form_with_errors(request, form, is_club, mode): context["course"] = form.cleaned_data["course"] context["instructor"] = form.cleaned_data.get("instructor") - # Fetch semesters for re-render latest = Semester.latest() context["semesters"] = Semester.objects.filter(year__gte=latest.year - 5).order_by( "-number" )[:10] - return render(request, "reviews/new_review.html", context) - - -@login_required() -def check_duplicate(request): - """Check for duplicate reviews when a user submits a review.""" - - form = ReviewForm(request.POST) - if form.is_valid(): - instance = form.save(commit=False) - - if instance.club: - # Check if user has reviewed given club before - reviews_on_same_club = request.user.review_set.filter(club=instance.club) - # Review already exists so it's a duplicate; inform user - if reviews_on_same_club.exists(): - return JsonResponse({"duplicate": True}) - return JsonResponse({"duplicate": False}) - - # First check if user has reviewed given course during same - # semester before - reviews_on_same_class = request.user.review_set.filter( - course=instance.course, semester=instance.semester - ) - - # Review already exists so it's a duplicate; inform user - if reviews_on_same_class.exists(): - response = {"duplicate": True} - return JsonResponse(response) - - # Then check if user has reviewed given course with same - # instructor before - reviews_on_same_class = request.user.review_set.filter( - course=instance.course, instructor=instance.instructor - ) - # Review already exists so it's a duplicate; inform user - if reviews_on_same_class.exists(): - response = {"duplicate": True} - return JsonResponse(response) - - # User has not reviewed course/club before; proceed with standard form submission - response = {"duplicate": False} - return JsonResponse(response) - return redirect("new_review") - - -@login_required() -def check_zero_hours_per_week(request): - """Check that user hasn't submitted 0 *total* hours/week - for a given course/review. - Used for an Ajax request in new_review.html""" - - form = ReviewForm(request.POST) - if form.is_valid(): - instance = form.save(commit=False) - - hours_per_week = ( - instance.amount_reading - + instance.amount_writing - + instance.amount_group - + instance.amount_homework - ) - - # Review has 0 total hours/week - # Send user a warning message that they have entered 0 hours - if hours_per_week == 0: - response = {"zero": True} - return JsonResponse(response) - - # Otherwise, proceed with normal form submission - response = {"zero": False} - return JsonResponse(response) - return redirect("new_review") - - -# Note: Class-based views can't use the @login_required decorator - - -class DeleteReview(LoginRequiredMixin, SuccessMessageMixin, generic.DeleteView): - """Review deletion view.""" - - model = Review - success_url = reverse_lazy("reviews") - - def get_object(self): # pylint: disable=arguments-differ - """Override DeleteView's function to validate review belonging to user.""" - obj = super().get_object() - # For security: Make sure target review belongs to the current user - if obj.user != self.request.user: - raise PermissionDenied("You are not allowed to delete this review!") - return obj - - def get_success_message(self, cleaned_data) -> str: - """Overrides SuccessMessageMixin's get_success_message method.""" - # Check if it's a club review - if self.object.club: - return f"Successfully deleted your review for {self.object.club}!" - return f"Successfully deleted your review for {self.object.course}!" + return render(request, "site/pages/review.html", context) diff --git a/tcf_website/views/schedule.py b/tcf_website/views/schedule.py index 549cb8289..e89a710b6 100644 --- a/tcf_website/views/schedule.py +++ b/tcf_website/views/schedule.py @@ -1,19 +1,27 @@ """View pertaining to schedule creation/viewing.""" -import json import logging +import re +from datetime import datetime, time +from urllib.parse import urlencode from django import forms from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.contrib.postgres.aggregates.general import ArrayAgg -from django.db.models import Avg, Case, CharField, Max, Prefetch, Q, Value, When -from django.db.models.functions import Cast, Concat -from django.http import HttpResponse, JsonResponse +from django.db.models import Prefetch from django.shortcuts import get_object_or_404, redirect, render -from django.template.loader import render_to_string - -from ..models import Course, Instructor, Schedule, ScheduledCourse, Section, Semester +from django.urls import reverse + +from ..models import ( + Course, + Instructor, + Schedule, + ScheduledCourse, + Section, + SectionTime, + Semester, +) +from .utils import safe_next_url # pylint: disable=line-too-long # pylint: disable=duplicate-code @@ -23,89 +31,310 @@ logger = logging.getLogger(__name__) -def load_secs_helper(course, latest_semester): - """Helper function for course_view and for a view in schedule.py""" - instructors = ( - Instructor.objects.filter(section__course=course, hidden=False) - .distinct() - .annotate( - gpa=Avg( - "courseinstructorgrade__average", - filter=Q(courseinstructorgrade__course=course), - ), - difficulty=Avg("review__difficulty", filter=Q(review__course=course)), - rating=( - Avg("review__instructor_rating", filter=Q(review__course=course)) - + Avg("review__enjoyability", filter=Q(review__course=course)) - + Avg("review__recommendability", filter=Q(review__course=course)) - ) - / 3, - semester_last_taught=Max( - "section__semester", filter=Q(section__course=course) - ), - # ArrayAgg: - # https://docs.djangoproject.com/en/3.2/ref/contrib/postgres/aggregates/#arrayagg - section_times=ArrayAgg( - Case( - When( - section__semester=latest_semester, - then="section__section_times", - ), - output_field=CharField(), - ), - distinct=True, - ), - section_nums=ArrayAgg( - Case( - When( - section__semester=latest_semester, - then="section__sis_section_number", - ), - output_field=CharField(), - ), - distinct=True, - ), - section_details=ArrayAgg( - # this is to get sections in this format: section.id /% - # section.section_num /% section.time /% section_type - Concat( - Cast("section__id", CharField()), - Value(" /% "), - Case( - When( - section__semester=latest_semester, - then=Cast("section__sis_section_number", CharField()), - ), - default=Value(""), - output_field=CharField(), - ), - Value(" /% "), - "section__section_times", - Value(" /% "), - "section__section_type", - Value(" /% "), - "section__units", - output_field=CharField(), - ), - distinct=True, - ), +DAY_FIELDS = ( + ("MON", "Mon", "monday"), + ("TUE", "Tue", "tuesday"), + ("WED", "Wed", "wednesday"), + ("THU", "Thu", "thursday"), + ("FRI", "Fri", "friday"), +) +DAY_TOKEN_MAP = { + "mo": "MON", + "tu": "TUE", + "we": "WED", + "th": "THU", + "fr": "FRI", +} +DAY_TOKEN_PATTERN = re.compile(r"(mo|tu|we|th|fr)", flags=re.IGNORECASE) +MEETING_BLOCK_PATTERN = re.compile( + r"(?P[A-Za-z]+)\s+(?P\d{1,2}:\d{2}\s*[APMapm]{2})\s*-\s*(?P\d{1,2}:\d{2}\s*[APMapm]{2})" +) + + +def _is_lecture_section(section_type: str | None) -> bool: + """Return True when a section type should be treated as lecture.""" + if not section_type: + return True + normalized = section_type.strip().lower() + return normalized.startswith("lec") or "lecture" in normalized + + +def _clock_to_minutes(raw_clock: str) -> int | None: + """Convert clock string like '9:30am' into minutes from midnight.""" + if not raw_clock: + return None + try: + parsed = datetime.strptime(raw_clock.replace(" ", "").upper(), "%I:%M%p") + except ValueError: + return None + return (parsed.hour * 60) + parsed.minute + + +def _time_to_minutes(value: time) -> int: + """Convert a Python time object into minutes from midnight.""" + return (value.hour * 60) + value.minute + + +def _parse_fallback_meeting_blocks(raw_times: str) -> list[tuple[str, int, int]]: + """Parse serialized section_times string into day/time tuples.""" + if not raw_times: + return [] + + parsed_blocks: list[tuple[str, int, int]] = [] + for block in raw_times.split(","): + stripped = block.strip() + if not stripped: + continue + + match = MEETING_BLOCK_PATTERN.search(stripped) + if not match: + continue + + start_minutes = _clock_to_minutes(match.group("start")) + end_minutes = _clock_to_minutes(match.group("end")) + if start_minutes is None or end_minutes is None or end_minutes <= start_minutes: + continue + + day_tokens = DAY_TOKEN_PATTERN.findall(match.group("days")) + for token in day_tokens: + day_code = DAY_TOKEN_MAP.get(token.lower()) + if day_code: + parsed_blocks.append((day_code, start_minutes, end_minutes)) + + return parsed_blocks + + +def _section_time_rows_to_blocks( + section_time_rows: list[SectionTime], +) -> list[tuple[str, int, int]]: + """Convert SectionTime rows into day/time tuples.""" + blocks: list[tuple[str, int, int]] = [] + for section_time in section_time_rows: + start_minutes = _time_to_minutes(section_time.start_time) + end_minutes = _time_to_minutes(section_time.end_time) + if end_minutes <= start_minutes: + continue + for day_code, _, day_field in DAY_FIELDS: + if getattr(section_time, day_field): + blocks.append((day_code, start_minutes, end_minutes)) + return blocks + + +def _format_minutes(minutes: int) -> str: + """Render minutes from midnight into 12-hour clock text.""" + hour = minutes // 60 + minute = minutes % 60 + suffix = "AM" if hour < 12 else "PM" + hour_12 = hour % 12 or 12 + if minute: + return f"{hour_12}:{minute:02d} {suffix}" + return f"{hour_12} {suffix}" + + +def _empty_weekly_calendar() -> dict: + """Return empty calendar payload structure.""" + return { + "columns": [{"code": code, "label": label, "events": []} for code, label, _ in DAY_FIELDS], + "time_labels": [], + "slot_markers": [], + } + + +def _build_section_time_map(section_ids: set[int]) -> dict[int, list[SectionTime]]: + """Build map of section_id -> section time rows.""" + if not section_ids: + return {} + + section_time_map: dict[int, list[SectionTime]] = {section_id: [] for section_id in section_ids} + for section_time in SectionTime.objects.filter(section_id__in=section_ids): + section_time_map.setdefault(section_time.section_id, []).append(section_time) + return section_time_map + + +def _meeting_blocks_for_schedule_course( + schedule_course: ScheduledCourse, + section_time_map: dict[int, list[SectionTime]], +) -> list[tuple[str, int, int]]: + """Return meeting blocks for one scheduled course.""" + section_rows = section_time_map.get(schedule_course.section_id, []) + meeting_blocks = _section_time_rows_to_blocks(section_rows) + if meeting_blocks: + return meeting_blocks + return _parse_fallback_meeting_blocks( + schedule_course.time or schedule_course.section.section_times + ) + + +def _schedule_course_title(schedule_course: ScheduledCourse) -> str: + """Return display title for a scheduled course.""" + return getattr( + schedule_course, + "title", + f"{schedule_course.section.course.subdepartment.mnemonic} " + f"{schedule_course.section.course.number}", + ) + + +def _schedule_course_instructor_name(schedule_course: ScheduledCourse) -> str: + """Return display name for a scheduled course instructor.""" + if schedule_course.instructor.full_name: + return schedule_course.instructor.full_name + return ( + f"{schedule_course.instructor.first_name} {schedule_course.instructor.last_name}".strip() + ) + + +def _event_payloads_for_course( + schedule_course: ScheduledCourse, + meeting_blocks: list[tuple[str, int, int]], +) -> list[dict]: + """Build event payloads for one scheduled course.""" + if not meeting_blocks: + return [] + + section = schedule_course.section + common_payload = { + "title": _schedule_course_title(schedule_course), + "subtitle": _schedule_course_instructor_name(schedule_course), + "meta": f"Section {section.sis_section_number}", + "tone": ((section.course_id % 6) + 1), + "href": reverse( + "course_instructor", + args=[section.course_id, schedule_course.instructor_id], + ), + } + + events = [] + for day_code, start_minutes, end_minutes in meeting_blocks: + events.append( + { + "day_code": day_code, + "start_minutes": start_minutes, + "end_minutes": end_minutes, + "start_label": _format_minutes(start_minutes), + "end_label": _format_minutes(end_minutes), + **common_payload, + } + ) + return events + + +def _weekly_events( + schedule_courses: list[ScheduledCourse], + section_time_map: dict[int, list[SectionTime]], +) -> list[dict]: + """Build all calendar events for schedule courses.""" + events: list[dict] = [] + for schedule_course in schedule_courses: + meeting_blocks = _meeting_blocks_for_schedule_course( + schedule_course, + section_time_map, + ) + events.extend(_event_payloads_for_course(schedule_course, meeting_blocks)) + return events + + +def _calendar_window(events: list[dict]) -> tuple[int, int]: + """Return calendar window start/end minutes.""" + earliest_start = min(event["start_minutes"] for event in events) + latest_end = max(event["end_minutes"] for event in events) + start_minutes = max(7 * 60, min(8 * 60, (earliest_start // 60) * 60)) + end_minutes = min(22 * 60, max(18 * 60, ((latest_end + 59) // 60) * 60)) + if end_minutes <= start_minutes: + end_minutes = start_minutes + 60 + return start_minutes, end_minutes + + +def _apply_event_geometry(events: list[dict], start_minutes: int, end_minutes: int) -> None: + """Mutate event payloads with layout geometry fields.""" + total_minutes = end_minutes - start_minutes + for event in events: + clamped_start = max(start_minutes, min(end_minutes, event["start_minutes"])) + clamped_end = max(clamped_start, min(end_minutes, event["end_minutes"])) + event["top_pct"] = ((clamped_start - start_minutes) / total_minutes) * 100 + event["height_pct"] = max( + 4.0, + ((clamped_end - clamped_start) / total_minutes) * 100, ) + + +def _calendar_columns(events: list[dict]) -> list[dict]: + """Group and sort events by day.""" + columns = [] + for day_code, day_label, _ in DAY_FIELDS: + day_events = sorted( + [event for event in events if event["day_code"] == day_code], + key=lambda item: (item["start_minutes"], item["end_minutes"]), + ) + columns.append({"code": day_code, "label": day_label, "events": day_events}) + return columns + + +def _build_weekly_calendar(schedule_courses: list[ScheduledCourse]) -> dict: + """Build normalized weekly calendar payload for calendar component.""" + if not schedule_courses: + return _empty_weekly_calendar() + + section_ids = {course.section_id for course in schedule_courses} + section_time_map = _build_section_time_map(section_ids) + events = _weekly_events(schedule_courses, section_time_map) + + if not events: + return _empty_weekly_calendar() + + start_minutes, end_minutes = _calendar_window(events) + _apply_event_geometry(events, start_minutes, end_minutes) + + columns = _calendar_columns(events) + slot_count = max(1, (end_minutes - start_minutes) // 60) + time_labels = [_format_minutes(start_minutes + (hour * 60)) for hour in range(slot_count + 1)] + + return { + "columns": columns, + "time_labels": time_labels, + "slot_markers": list(range(slot_count)), + } + + +def _existing_schedule_blocks(schedule: Schedule) -> list[tuple[str, int, int]]: + """Return meeting blocks for all existing courses in a schedule.""" + existing_courses = list( + ScheduledCourse.objects.filter(schedule=schedule).select_related("section") ) + if not existing_courses: + return [] + + section_ids = {course.section_id for course in existing_courses} + section_time_map = _build_section_time_map(section_ids) + + existing_blocks: list[tuple[str, int, int]] = [] + for existing_course in existing_courses: + existing_blocks.extend( + _meeting_blocks_for_schedule_course(existing_course, section_time_map) + ) + return existing_blocks + + +def _has_schedule_conflict( + schedule: Schedule, + candidate_blocks: list[tuple[str, int, int]], +) -> bool: + """Return True if candidate meeting blocks conflict with schedule contents.""" + if not candidate_blocks: + return False - # Note: Refactor pls + existing_blocks = _existing_schedule_blocks(schedule) + if not existing_blocks: + return False - for i in instructors: - if i.section_times[0] is not None and i.section_nums[0] is not None: - i.times = {} - for idx, _ in enumerate(i.section_times): - if i.section_times[idx] is not None and i.section_nums[idx] is not None: - i.times[str(i.section_nums[idx])] = i.section_times[idx][:-1].split( - "," - ) - if None in i.section_nums: - i.section_nums.remove(None) + for candidate_day, candidate_start, candidate_end in candidate_blocks: + for existing_day, existing_start, existing_end in existing_blocks: + if candidate_day != existing_day: + continue + if candidate_start < existing_end and candidate_end > existing_start: + return True - return instructors + return False class ScheduleForm(forms.ModelForm): @@ -113,30 +342,18 @@ class ScheduleForm(forms.ModelForm): Django form for interacting with a schedule """ - user_id = forms.IntegerField(widget=forms.HiddenInput()) name = forms.CharField(max_length=15) class Meta: model = Schedule - fields = ["name", "user_id"] + fields = ["name"] -class SectionForm(forms.ModelForm): - """ - Django form for adding a course to a schedule - """ - - class Meta: - model = ScheduledCourse - fields = ["schedule", "section", "instructor", "time"] - - -@login_required def schedule_data_helper(request): """ This helper method is for getting schedule data for a request. """ - schedules = Schedule.objects.filter(user=request.user).prefetch_related( + schedules = Schedule.objects.filter(user=request.user).order_by("name").prefetch_related( Prefetch( "scheduledcourse_set", queryset=ScheduledCourse.objects.select_related("section", "instructor"), @@ -178,35 +395,42 @@ def schedule_data_helper(request): return ret +@login_required def view_schedules(request): - """ - Get all schedules, and the related courses, for a given user. - """ + """Render schedule builder page.""" schedule_context = schedule_data_helper(request) - - # add an empty schedule form into the context - # to be used in the create_schedule_modal - form = ScheduleForm() - schedule_context["form"] = form - - return render(request, "schedule/user_schedules.html", schedule_context) - - -def view_select_schedules_modal(request, mode): - """ - Get all schedules and display in the modal. - """ - schedule_context = schedule_data_helper(request) - - if mode == "add_course": - schedule_context["mode"] = mode - else: - schedule_context["mode"] = "edit_schedule" - modal_content = render_to_string( - "schedule/select_schedule_modal.html", schedule_context, request + schedules = list(schedule_context["schedules"]) + + selected_schedule = None + selected_schedule_data = None + selected_schedule_id = request.GET.get("schedule") + if schedules: + if selected_schedule_id: + selected_schedule = next( + (schedule for schedule in schedules if str(schedule.id) == selected_schedule_id), + None, + ) + if selected_schedule is None: + selected_schedule = schedules[0] + selected_schedule_data = selected_schedule.get_schedule() + + selected_courses = selected_schedule_data[0] if selected_schedule_data else [] + calendar = _build_weekly_calendar(selected_courses) + + schedule_context.update( + { + "selected_schedule": selected_schedule, + "selected_courses": selected_courses, + "selected_schedule_stats": { + "credits": (selected_schedule_data[1] if selected_schedule_data else 0), + "rating": (selected_schedule_data[2] if selected_schedule_data else 0), + "gpa": (selected_schedule_data[4] if selected_schedule_data else 0), + }, + "calendar": calendar, + } ) - return HttpResponse(modal_content) + return render(request, "site/pages/schedule.html", schedule_context) @login_required @@ -222,14 +446,12 @@ def new_schedule(request): schedule.user = request.user if schedule.user is None: messages.error(request, "There was an error") - return render(request, "schedule/user_schedules.html", {"form": form}) + return redirect(safe_next_url(request, reverse("schedule"))) schedule.save() messages.success(request, "Successfully created schedule!") - else: - # if schedule isn't getting saved, then don't do anything - # for part two of the this project, load the actual course builder page - form = ScheduleForm() - return redirect("schedule") + else: + messages.error(request, "Invalid schedule data.") + return redirect(safe_next_url(request, reverse("schedule"))) @login_required @@ -254,7 +476,7 @@ def delete_schedule(request): request, f"Successfully deleted {schedule_count} schedules and {deleted_count - schedule_count} courses", ) - return redirect("schedule") + return redirect(safe_next_url(request, reverse("schedule"))) @login_required @@ -262,7 +484,7 @@ def duplicate_schedule(request, schedule_id): """ Duplicate a schedule given a schedule id in the request. """ - schedule = get_object_or_404(Schedule, pk=schedule_id) + schedule = get_object_or_404(Schedule, pk=schedule_id, user=request.user) schedule.pk = None # reset the key so it will be recreated when it's saved old_name = schedule.name schedule.name = "Copy of " + old_name @@ -277,43 +499,7 @@ def duplicate_schedule(request, schedule_id): course.save() messages.success(request, f"Successfully duplicated {old_name}") - return redirect("schedule") - - -@login_required -def modal_load_editor(request): - """ - Load the schedule editor modal with schedule data. - """ - if request.method != "POST": - messages.error(request, f"Invalid request method: {request.method}") - return JsonResponse({"status": "Method Not Allowed"}, status=405) - - body_unicode = request.body.decode("utf-8") - body = json.loads(body_unicode) - schedule_id = body.get("schedule_id") - - if not schedule_id: - messages.error(request, "Schedule ID is missing") - return JsonResponse({"status": "Bad Request"}, status=400) - - try: - schedule = Schedule.objects.get(pk=schedule_id) - except Schedule.DoesNotExist: - messages.error(request, "Schedule not found") - return JsonResponse({"status": "Not Found"}, status=404) - - schedule_data = schedule.get_schedule() - - context = { - "schedule": schedule, - "schedule_courses": schedule_data[0], - "schedule_credits": schedule_data[1], - "schedule_ratings": schedule_data[2], - "schedule_difficulty": schedule_data[3], - "schedule_gpa": schedule_data[4], - } - return render(request, "schedule/schedule_editor.html", context) + return redirect(safe_next_url(request, reverse("schedule"))) @login_required @@ -323,127 +509,276 @@ def edit_schedule(request): """ if request.method != "POST": messages.error(request, f"Invalid request method: {request.method}") - return JsonResponse({"status": "Method Not Allowed"}, status=405) + return redirect(safe_next_url(request, reverse("schedule"))) - schedule = Schedule.objects.get(pk=request.POST["schedule_id"]) - if schedule.name != request.POST["schedule_name"]: - schedule.name = request.POST["schedule_name"] + schedule = get_object_or_404( + Schedule, pk=request.POST["schedule_id"], user=request.user + ) + updated_name = request.POST.get("schedule_name", "").strip() + if updated_name and schedule.name != updated_name: + schedule.name = updated_name schedule.save() deleted_courses = request.POST.getlist("removed_course_ids[]") if deleted_courses: - ScheduledCourse.objects.filter(id__in=deleted_courses).delete() + ScheduledCourse.objects.filter( + id__in=deleted_courses, + schedule__user=request.user, + ).delete() messages.success(request, f"Successfully made changes to {schedule.name}") - return redirect("schedule") + return redirect(safe_next_url(request, reverse("schedule"))) @login_required -def modal_load_sections(request): - """ - Load the instructors and section times for a course, and the schedule, when adding to schedule from the modal. - """ - # pylint: disable=too-many-locals - body_unicode = request.body.decode("utf-8") - body = json.loads(body_unicode) - course_id = body["course_id"] - schedule_id = body["schedule_id"] - - # get the course based off passed in course_id - course = Course.objects.get(pk=course_id) - latest_semester = Semester.latest() +def remove_scheduled_course(request, scheduled_course_id): + """Remove one scheduled course from the active user's schedule.""" + if request.method != "POST": + return redirect(reverse("schedule")) - data = {} - instructors = load_secs_helper(course, latest_semester).filter( - semester_last_taught=latest_semester.id + scheduled_course = get_object_or_404( + ScheduledCourse, + id=scheduled_course_id, + schedule__user=request.user, ) + schedule_id = scheduled_course.schedule_id + course_label = ( + f"{scheduled_course.section.course.subdepartment.mnemonic} " + f"{scheduled_course.section.course.number}" + ) + scheduled_course.delete() + messages.success(request, f"Removed {course_label} from your schedule.") + + default_url = f"{reverse('schedule')}?{urlencode({'schedule': schedule_id})}" + return redirect(safe_next_url(request, default_url)) + + +def _build_schedule_add_options( + course: Course, latest_semester: Semester +) -> tuple[list[dict], list[dict]]: + """Build lecture and non-lecture options for the add-course form.""" + lecture_options = [] + other_options = [] + sections = ( + Section.objects.filter(course=course, semester=latest_semester) + .prefetch_related("instructors") + .order_by("sis_section_number") + ) + for section in sections: + section_time = (section.section_times or "").rstrip(",") + for instructor in section.instructors.all(): + if instructor.hidden: + continue + display_name = ( + instructor.full_name + if instructor.full_name + else f"{instructor.first_name} {instructor.last_name}".strip() + ) + option = { + "value": f"{section.id}:{instructor.id}", + "section_id": section.id, + "instructor_id": instructor.id, + "section_number": section.sis_section_number, + "section_type": section.section_type or "Lecture", + "section_units": section.units, + "section_time": section_time, + "instructor_name": display_name, + } + if _is_lecture_section(section.section_type): + lecture_options.append(option) + else: + other_options.append(option) + return lecture_options, other_options + + +def _parse_schedule_add_selection( + selected_schedule_id: str, selected_option: str +) -> tuple[int | None, int | None, int | None, str | None]: + """Parse schedule and section selection values from POST data.""" + if not selected_schedule_id: + return None, None, None, "Choose a schedule first." + if not selected_option: + return None, None, None, "Choose a section to add." - for i in instructors: - temp = {} - data[i.id] = temp - - # decode the string in section_details and take skip strings without a time or section_id - encoded_sections = [ - x.split(" /% ") - for x in i.section_details - if x.split(" /% ")[2] != "" and x.split(" /% ")[1] != "" - ] - - # strip the traling comma - for section in encoded_sections: - if section[2].endswith(","): - section[2] = section[2].rstrip(",") - - temp["sections"] = encoded_sections - temp["name"] = i.first_name + " " + i.last_name - temp["rating"] = i.rating - temp["difficulty"] = i.difficulty - temp["gpa"] = i.gpa - - schedule = Schedule.objects.get(pk=schedule_id) - schedule_data = schedule.get_schedule() - context = { - "instructors_data": data, - "schedule": schedule, - "schedule_courses": schedule_data[0], - "schedule_credits": schedule_data[1], - "schedule_ratings": schedule_data[2], - "schedule_difficulty": schedule_data[3], - "schedule_gpa": schedule_data[4], + try: + section_id_raw, instructor_id_raw = selected_option.split(":") + schedule_id = int(selected_schedule_id) + section_id = int(section_id_raw) + instructor_id = int(instructor_id_raw) + except (AttributeError, TypeError, ValueError): + return None, None, None, "Invalid section selection." + + return schedule_id, section_id, instructor_id, None + + +def _resolve_schedule_add_selection( + user, + course: Course, + latest_semester: Semester, + selection_ids: tuple[int, int, int], +) -> tuple[Schedule | None, Section | None, Instructor | None, str | None]: + """Validate selected schedule/section/instructor objects.""" + schedule_id, section_id, instructor_id = selection_ids + + schedule = Schedule.objects.filter(id=schedule_id, user=user).first() + if schedule is None: + return None, None, None, "Invalid schedule selection." + + section = Section.objects.filter( + id=section_id, course=course, semester=latest_semester + ).first() + if section is None: + return None, None, None, "Invalid section selection." + + instructor = Instructor.objects.filter(id=instructor_id, hidden=False).first() + if instructor is None: + return None, None, None, "Invalid instructor selection." + + if not section.instructors.filter(id=instructor.id).exists(): + return None, None, None, "The selected instructor does not teach that section." + + return schedule, section, instructor, None + + +def _schedule_add_success_redirect(request, schedule_id: int): + """Return success redirect response for schedule add flow.""" + default_url = f"{reverse('schedule')}?{urlencode({'schedule': schedule_id})}" + return redirect(safe_next_url(request, default_url)) + + +def _add_course_to_schedule( + request, schedule: Schedule, section: Section, instructor: Instructor +): + """Attempt to add the selected section and emit user-facing messages.""" + if ScheduledCourse.objects.filter( + schedule=schedule, section=section, instructor=instructor + ).exists(): + messages.info(request, "That section is already in this schedule.") + return None + + candidate_blocks = _section_time_rows_to_blocks(list(section.sectiontime_set.all())) + if not candidate_blocks: + candidate_blocks = _parse_fallback_meeting_blocks(section.section_times) + + if _has_schedule_conflict(schedule, candidate_blocks): + messages.error( + request, + "This section conflicts with another meeting in the selected schedule.", + ) + return None + + ScheduledCourse.objects.create( + schedule=schedule, + section=section, + instructor=instructor, + time=(section.section_times or "").rstrip(","), + ) + messages.success(request, "Successfully added course to schedule.") + return _schedule_add_success_redirect(request, schedule.id) + + +def _handle_schedule_add_post( + request, + course: Course, + latest_semester: Semester, + selected_schedule_id: str, + selected_option: str, +): + """Handle POST submission for schedule add flow.""" + schedule_id, section_id, instructor_id, parse_error = _parse_schedule_add_selection( + selected_schedule_id, selected_option + ) + if parse_error: + messages.error(request, parse_error) + return None + + schedule, section, instructor, validation_error = _resolve_schedule_add_selection( + request.user, + course, + latest_semester, + (schedule_id, section_id, instructor_id), + ) + if validation_error: + messages.error(request, validation_error) + return None + + return _add_course_to_schedule(request, schedule, section, instructor) + + +def _build_schedule_add_page_context( + course: Course, + page_state: dict, +) -> dict: + """Build template context for schedule add page.""" + dept = course.subdepartment.department + breadcrumbs = [ + (dept.school.name, reverse("browse"), False), + (dept.name, reverse("department", args=[dept.id]), False), + ( + course.code(), + reverse("course", args=[course.subdepartment.mnemonic, course.number]), + False, + ), + ("Add to Schedule", None, True), + ] + return { + "course": course, + "breadcrumbs": breadcrumbs, + "lecture_options": page_state["lecture_options"], + "other_options": page_state["other_options"], + "schedules": page_state["schedules"], + "selected_schedule_id": ( + str(page_state["selected_schedule_id"]) + if page_state["selected_schedule_id"] + else "" + ), + "selected_option": page_state["selected_option"], + "default_next_url": page_state["fallback_course_url"], + "next_url": page_state["next_url"], } - return render(request, "schedule/schedule_with_sections.html", context) @login_required -def schedule_add_course(request): - """Add a course to a schedule, the request should be FormData for the SectionForm class.""" - - if request.method == "POST": - # Parse the JSON-encoded 'selected_course' field - try: - selected_course = json.loads( - request.POST.get("selected_course", "{}") - ) # Default to empty dict if not found - except json.JSONDecodeError: - return JsonResponse( - {"status": "error", "message": "Invalid JSON data"}, status=400 - ) - - form_data = { - "schedule": request.POST.get("schedule_id"), - "instructor": int(selected_course.get("instructor")), - "section": int(selected_course.get("section")), - "time": selected_course.get("section_time"), - } +def schedule_add_course(request, course_id): + """Add a course to a schedule from the course flow.""" + course = get_object_or_404(Course, id=course_id) + latest_semester = Semester.latest() + schedules = Schedule.objects.filter(user=request.user).order_by("name") + lecture_options, other_options = _build_schedule_add_options(course, latest_semester) - # make form object with our passed in data - form = SectionForm(form_data) + selected_schedule_id = request.POST.get("schedule_id") or request.GET.get("schedule", "") + selected_option = request.POST.get("selection", "") - if form.is_valid(): - scheduled_course = form.save(commit=False) - # extract id's for all related fields - schedule_id = form.cleaned_data[ - "schedule" - ].id # get the schedule's primary key - instructor_id = form.cleaned_data[ - "instructor" - ].id # get the instructor's primary key - section_id = form.cleaned_data[ - "section" - ].id # get the section's primary key - course_time = form.cleaned_data["time"] - - # update the form object with the related objects returned from the database - # Note: there might be some optimzation where we could do the request in - # bulk instead of 4 seperate queries - scheduled_course.schedule = Schedule.objects.get(id=schedule_id) - scheduled_course.instructor = Instructor.objects.get(id=instructor_id) - scheduled_course.section = Section.objects.get(id=section_id) - scheduled_course.time = course_time - scheduled_course.save() - else: - messages.error(request, "Invalid form data") - return JsonResponse({"status": "error"}, status=400) + fallback_course_url = reverse( + "course", + args=[course.subdepartment.mnemonic, course.number], + ) + next_url = safe_next_url(request, fallback_course_url) - messages.success(request, "Succesfully added course!") - return JsonResponse({"status": "success"}) + if request.method == "POST": + success_response = _handle_schedule_add_post( + request, + course, + latest_semester, + selected_schedule_id, + selected_option, + ) + if success_response is not None: + return success_response + + return render( + request, + "site/pages/schedule_add_course.html", + _build_schedule_add_page_context( + course, + { + "lecture_options": lecture_options, + "other_options": other_options, + "schedules": schedules, + "selected_schedule_id": selected_schedule_id, + "selected_option": selected_option, + "fallback_course_url": fallback_course_url, + "next_url": next_url, + }, + ), + ) diff --git a/tcf_website/views/search.py b/tcf_website/views/search.py index 62934b38e..c56f44310 100644 --- a/tcf_website/views/search.py +++ b/tcf_website/views/search.py @@ -142,7 +142,8 @@ def search(request): "page_obj": page_obj, } - return render(request, "search/search.html", ctx) + template_name = "site/pages/search.html" + return render(request, template_name, ctx) def fetch_clubs(query): diff --git a/tcf_website/views/utils.py b/tcf_website/views/utils.py new file mode 100644 index 000000000..cc11b2275 --- /dev/null +++ b/tcf_website/views/utils.py @@ -0,0 +1,15 @@ +"""Shared helper utilities for view logic.""" + +from django.utils.http import url_has_allowed_host_and_scheme + + +def safe_next_url(request, default_url: str) -> str: + """Return validated next URL when present, otherwise default.""" + next_url = request.POST.get("next") or request.GET.get("next") + if next_url and url_has_allowed_host_and_scheme( + url=next_url, + allowed_hosts={request.get_host()}, + require_https=request.is_secure(), + ): + return next_url + return default_url From c77c103b961ca1927c3ffc15259965da7b3f918f Mon Sep 17 00:00:00 2001 From: Jay-Lalwani Date: Sat, 7 Feb 2026 17:27:48 -0500 Subject: [PATCH 2/8] fix(schedule): remove future plans --- tcf_website/templates/site/pages/schedule.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcf_website/templates/site/pages/schedule.html b/tcf_website/templates/site/pages/schedule.html index 79cc8da3f..53bd445f9 100644 --- a/tcf_website/templates/site/pages/schedule.html +++ b/tcf_website/templates/site/pages/schedule.html @@ -164,7 +164,7 @@

    Create your first schedule

    {% endif %}

    - Add courses from the course page using “Add to Schedule”. Schedule-page search/autocomplete will be added later and reused for the new-review flow. + Add courses from the course page using “Add to Schedule”.

    From 9b9cc52b42c5478e120fd638948c7185fe7794ad Mon Sep 17 00:00:00 2001 From: Jay-Lalwani Date: Sat, 7 Feb 2026 17:48:56 -0500 Subject: [PATCH 3/8] Implement modal header auth fixes --- tcf_website/static/about/about.css | 57 --- tcf_website/static/about/history/history.css | 105 ----- .../static/about/resize-alum-photos.py | 76 --- tcf_website/static/base/base.css | 160 ------- tcf_website/static/base/bugform.js | 64 --- tcf_website/static/base/messages.css | 8 - tcf_website/static/base/navbar.css | 98 ---- tcf_website/static/base/reset.css | 446 ------------------ tcf_website/static/base/sidebar.css | 86 ---- tcf_website/static/base/tcf-style.css | 131 ----- .../static/css/site/pages/department.css | 1 + tcf_website/static/icons/icons.css | 338 ------------- tcf_website/static/icons/icons.html | 7 - .../static/icons/img/fa-arrow-right.svg | 1 - tcf_website/static/icons/img/fa-book.svg | 1 - tcf_website/static/icons/img/fa-calendar.svg | 1 - tcf_website/static/icons/img/fa-chart-bar.svg | 1 - .../static/icons/img/fa-chevron-down.svg | 1 - .../static/icons/img/fa-chevron-up.svg | 1 - tcf_website/static/icons/img/fa-clock.svg | 1 - tcf_website/static/icons/img/fa-code.svg | 1 - tcf_website/static/icons/img/fa-copy.svg | 1 - tcf_website/static/icons/img/fa-discord.svg | 1 - tcf_website/static/icons/img/fa-dumbbell.svg | 1 - tcf_website/static/icons/img/fa-facebook.svg | 1 - tcf_website/static/icons/img/fa-flask.svg | 1 - .../static/icons/img/fa-frown-open.svg | 1 - tcf_website/static/icons/img/fa-github.svg | 1 - .../static/icons/img/fa-graduation-cap.svg | 1 - tcf_website/static/icons/img/fa-handshake.svg | 1 - tcf_website/static/icons/img/fa-heart.svg | 1 - .../static/icons/img/fa-hourglass-half.svg | 1 - tcf_website/static/icons/img/fa-instagram.svg | 1 - tcf_website/static/icons/img/fa-list.svg | 1 - tcf_website/static/icons/img/fa-pencil.svg | 1 - tcf_website/static/icons/img/fa-plus.svg | 1 - tcf_website/static/icons/img/fa-search.svg | 1 - .../static/icons/img/fa-smile-beam.svg | 1 - tcf_website/static/icons/img/fa-star.svg | 1 - .../static/icons/img/fa-thumbs-down.svg | 1 - tcf_website/static/icons/img/fa-thumbs-up.svg | 1 - tcf_website/static/icons/img/fa-trash-o.svg | 1 - .../static/icons/img/fa-user-friends.svg | 1 - tcf_website/static/icons/img/fa-user.svg | 1 - tcf_website/templates/about/contributors.html | 62 --- tcf_website/templates/about/current_team.html | 105 ----- tcf_website/templates/about/history.html | 54 --- tcf_website/templates/about/logo-tagline.html | 10 - .../about/partials/privacy_content.html | 89 ---- .../about/partials/terms_content.html | 111 ----- tcf_website/templates/about/sponsors.html | 20 - .../templates/site/pages/department.html | 2 +- .../site/partials/privacy_content.html | 90 +++- .../site/partials/terms_content.html | 112 ++++- 54 files changed, 202 insertions(+), 2061 deletions(-) delete mode 100644 tcf_website/static/about/about.css delete mode 100644 tcf_website/static/about/history/history.css delete mode 100644 tcf_website/static/about/resize-alum-photos.py delete mode 100644 tcf_website/static/base/base.css delete mode 100644 tcf_website/static/base/bugform.js delete mode 100644 tcf_website/static/base/messages.css delete mode 100644 tcf_website/static/base/navbar.css delete mode 100644 tcf_website/static/base/reset.css delete mode 100644 tcf_website/static/base/sidebar.css delete mode 100644 tcf_website/static/base/tcf-style.css delete mode 100644 tcf_website/static/icons/icons.css delete mode 100644 tcf_website/static/icons/icons.html delete mode 100644 tcf_website/static/icons/img/fa-arrow-right.svg delete mode 100644 tcf_website/static/icons/img/fa-book.svg delete mode 100644 tcf_website/static/icons/img/fa-calendar.svg delete mode 100644 tcf_website/static/icons/img/fa-chart-bar.svg delete mode 100644 tcf_website/static/icons/img/fa-chevron-down.svg delete mode 100644 tcf_website/static/icons/img/fa-chevron-up.svg delete mode 100644 tcf_website/static/icons/img/fa-clock.svg delete mode 100644 tcf_website/static/icons/img/fa-code.svg delete mode 100644 tcf_website/static/icons/img/fa-copy.svg delete mode 100644 tcf_website/static/icons/img/fa-discord.svg delete mode 100644 tcf_website/static/icons/img/fa-dumbbell.svg delete mode 100644 tcf_website/static/icons/img/fa-facebook.svg delete mode 100644 tcf_website/static/icons/img/fa-flask.svg delete mode 100644 tcf_website/static/icons/img/fa-frown-open.svg delete mode 100644 tcf_website/static/icons/img/fa-github.svg delete mode 100644 tcf_website/static/icons/img/fa-graduation-cap.svg delete mode 100644 tcf_website/static/icons/img/fa-handshake.svg delete mode 100644 tcf_website/static/icons/img/fa-heart.svg delete mode 100644 tcf_website/static/icons/img/fa-hourglass-half.svg delete mode 100644 tcf_website/static/icons/img/fa-instagram.svg delete mode 100644 tcf_website/static/icons/img/fa-list.svg delete mode 100644 tcf_website/static/icons/img/fa-pencil.svg delete mode 100644 tcf_website/static/icons/img/fa-plus.svg delete mode 100644 tcf_website/static/icons/img/fa-search.svg delete mode 100644 tcf_website/static/icons/img/fa-smile-beam.svg delete mode 100644 tcf_website/static/icons/img/fa-star.svg delete mode 100644 tcf_website/static/icons/img/fa-thumbs-down.svg delete mode 100644 tcf_website/static/icons/img/fa-thumbs-up.svg delete mode 100644 tcf_website/static/icons/img/fa-trash-o.svg delete mode 100644 tcf_website/static/icons/img/fa-user-friends.svg delete mode 100644 tcf_website/static/icons/img/fa-user.svg delete mode 100644 tcf_website/templates/about/contributors.html delete mode 100644 tcf_website/templates/about/current_team.html delete mode 100644 tcf_website/templates/about/history.html delete mode 100644 tcf_website/templates/about/logo-tagline.html delete mode 100644 tcf_website/templates/about/partials/privacy_content.html delete mode 100644 tcf_website/templates/about/partials/terms_content.html delete mode 100644 tcf_website/templates/about/sponsors.html diff --git a/tcf_website/static/about/about.css b/tcf_website/static/about/about.css deleted file mode 100644 index 353588fc5..000000000 --- a/tcf_website/static/about/about.css +++ /dev/null @@ -1,57 +0,0 @@ -/* - This file contains all css pertaining to the - about page. Please keep the css isolated here - to improve maintainability within theCourseForum. -*/ - -.about-logo { - display: flex; - flex-wrap: wrap; - justify-content: center; - padding: 30px 0px 20px 0px; -} - -.about-logo > .tagline { - display: flex; - flex-direction: column; - margin: 15px 0px 0px 15px; -} - -.tabs .about-tab { - padding: 0rem; - background-color: var(--main-color); -} -.tabs .about-tab:hover { - background-color: var(--secondary-color); -} -.tabs .about-tab a { - color: white; - width: 100%; -} - -.tabs .about-tab a:not(.collapsed) { - color: #ff7d4e; -} - -.pfp { - overflow: hidden; -} -.pfp img { - transition: all 0.5s ease; -} -.pfp:hover img { - transform: scale(1.1); -} -.member:hover { - box-shadow: 2px 2px 5px #888888 !important; -} - -.fit-image { - width: 100%; - object-fit: cover; -} - -.history-img { - overflow: hidden; - opacity: 0.8; -} diff --git a/tcf_website/static/about/history/history.css b/tcf_website/static/about/history/history.css deleted file mode 100644 index 6af33d84f..000000000 --- a/tcf_website/static/about/history/history.css +++ /dev/null @@ -1,105 +0,0 @@ -/* - File: history.css - Description: This file contains the css for - the about page's history SUBCONTENT tab. -*/ - -.grid { - display: grid; - align-items: stretch; - justify-items: stretch; - grid-template-rows: auto; - grid-template-columns: repeat(2, 1fr); - grid-template-areas: - "header header" - "img1 text1" - "text2 img2" - "img3 text3" - "text4 img4" - "sponsors sponsors" - "sponsors-img1 sponsors-img2"; -} - -.grid > * { - padding: 50px; -} - -.grid > img { - object-fit: contain; -} - -.grid > div > p { - text-align: left; -} - -@media (max-width: 992px) { - .grid { - grid-template-columns: repeat(1, 1fr); - grid-template-areas: - "header" - "img1" - "text1" - "img2" - "text2" - "img3" - "text3" - "img4" - "text4" - "sponsors" - "sponsors-img1" - "sponsors-img2"; - } - - .grid > * { - padding: 30px; - } -} - -.grid-header { - grid-area: header; - text-align: center; -} - -.founders-img { - grid-area: img1; -} - -.founders-text { - grid-area: text1; -} - -.revival-img { - grid-area: img2; -} - -.revival-text { - grid-area: text2; -} - -.cio-img { - grid-area: img3; -} - -.cio-text { - grid-area: text3; -} - -.revival2-img { - grid-area: img4; -} - -.revival2-text { - grid-area: text4; -} - -.sponsors-text { - grid-area: sponsors; -} - -.sponsors-img1 { - grid-area: sponsors-img1; -} - -.sponsors-img2 { - grid-area: sponsors-img2; -} diff --git a/tcf_website/static/about/resize-alum-photos.py b/tcf_website/static/about/resize-alum-photos.py deleted file mode 100644 index 6431ec292..000000000 --- a/tcf_website/static/about/resize-alum-photos.py +++ /dev/null @@ -1,76 +0,0 @@ -import os - -from PIL import Image - -folders = [ - "tcf_website/static/about/team-pfps", - "tcf_website/static/about/alum-pfps", -] - -eight_hundred_images_alum = [] -resizable_images_alum = [] - -eight_hundred_images_team = [] -resizable_images_team = [] - -for folder_path in folders: - folder_name = os.path.basename(folder_path) - for filename in os.listdir(folder_path): - image_path = os.path.join(folder_path, filename) - try: - with Image.open(image_path) as img: - width, height = img.size - dimensions = f"{width}x{height}" - if width == 800 and height == 800: - if folder_name == "alum-pfps": - eight_hundred_images_alum.append((filename, dimensions)) - elif folder_name == "team-pfps": - eight_hundred_images_team.append((filename, dimensions)) - elif width > 800 or height > 800: - if folder_name == "alum-pfps": - resizable_images_alum.append((filename, dimensions)) - elif folder_name == "team-pfps": - resizable_images_team.append((filename, dimensions)) - except Exception as e: - print(f"Error processing {filename}: {str(e)}") - -print("alum-pfps") - -print("Images that are 800x800:") -for filename, dimensions in eight_hundred_images_alum: - print(f" {filename} ({dimensions})") - -print("Images that are not 800x800 but are resizable:") -for filename, dimensions in resizable_images_alum: - print(f" {filename} ({dimensions})") -print("----------------------------------------") - -print("team-pfps") -print("Images that are 800x800:") -for filename, dimensions in eight_hundred_images_team: - print(f" {filename} ({dimensions})") - -print("Images that are not 800x800 but are resizable:") -for filename, dimensions in resizable_images_team: - print(f" {filename} ({dimensions})") - -for folder_name, images in [ - ("alum-pfps", resizable_images_alum), - ("team-pfps", resizable_images_team), -]: - print(f"Resizing images in {folder_name} folder:") - for filename, dimensions in images: - user_input = input( - f"Do you want to resize {filename} ({dimensions}) to 800x800? (Y/N): " - ) - if user_input.lower() == "y": - with Image.open( - f"tcf_website/static/about/{folder_name}/{filename}" - ) as img: - img = img.resize((800, 800), Image.ANTIALIAS) - img.save( - os.path.join(f"tcf_website/static/about/{folder_name}/{filename}") - ) - print(f"{filename} resized to 800x800") - else: - print(f"{filename} not resized") diff --git a/tcf_website/static/base/base.css b/tcf_website/static/base/base.css deleted file mode 100644 index e9c85ff28..000000000 --- a/tcf_website/static/base/base.css +++ /dev/null @@ -1,160 +0,0 @@ -/* - -Responsive breakpoints: - -// xs: Extra small devices (portrait phones, less than 576px) -// No media query for `xs` since this is the default in Bootstrap - -// sm: Small devices (landscape phones, 576px and up) -@media (min-width: 576px) { ... } - -// md: Medium devices (tablets, 768px and up) -@media (min-width: 768px) { ... } - -// l: Large devices (desktops, 992px and up) -@media (min-width: 992px) { ... } - -// xl: Extra large devices (large desktops, 1200px and up) -@media (min-width: 1200px) { ... } - -*/ - -:root { - --sidebar-width: 7rem; /* Width of sidebar is fixed */ -} - -html { - position: relative; - min-height: 100%; - max-width: 100vw; -} - -body { - padding-top: 70px; /* Height of fixed navbar */ - background-color: var(--background-color); - max-width: 100vw; -} - -.content { - background-color: var(--background-color); -} - -@media (min-width: 768px) { - .content { - width: calc(100vw - var(--sidebar-width)); - margin-left: var(--sidebar-width); - } -} - -footer { - position: absolute; - bottom: 0; - width: 100%; - height: 80px; /* Set the fixed height of the footer here */ - line-height: 30px; /* Vertically center the text there */ - text-align: center; - color: var(--background-color); - background-color: #172248; - padding-top: 15px; -} - -footer > .row { - background-color: #172248; -} - -/* For text on small screens */ -@media (max-width: 900px) { - h1 { - font-size: 32px; - } - h2 { - font-size: 26px; - } - h3 { - font-size: 22px; - } - h4 { - font-size: 19px; - } - h5 { - font-size: 16px; - } - h6 { - font-size: 14px; - } - html { - font-size: 14px; - } -} - -/* For buttons */ -.btn.btn-primary, -.btn.btn-outline-primary { - color: white; - background-color: var(--accent-color); - border-color: var(--accent-color); -} - -.btn.btn-outline-primary:not(:disabled):not(.disabled).active, -.btn.btn-outline-primary:not(:disabled):not(.disabled):active { - color: white; - background-color: #ac451e !important; - border-color: #ac451e !important; -} - -.btn.btn-outline-primary { - color: white; - background-color: var(--accent-color); - border-color: var(--accent-color); -} - -.btn.btn-primary:hover, -.btn.btn-outline-primary:hover { - color: white; - background-color: #ac451e !important; - border-color: #ac451e !important; -} - -.btn:not(:disabled):not(.disabled).active.focus, -.btn:not(:disabled):not(.disabled).active:focus { - box-shadow: none; -} - -/* For forms */ -.form-control { - background-color: var(--background-color); - color: black; - border-color: transparent; -} - -.form-row { - margin: 0.5rem; -} - -/* Change tooltop color */ -.tooltip-inner { - background-color: var(--dark-color) !important; -} -.tooltip.bs-tooltip-right .arrow:before { - border-right-color: var(--dark-color) !important; -} -.tooltip.bs-tooltip-left .arrow:before { - border-left-color: var(--dark-color) !important; -} -.tooltip.bs-tooltip-bottom .arrow:before { - border-bottom-color: var(--dark-color) !important; -} -.tooltip.bs-tooltip-top .arrow:before { - border-top-color: var(--dark-color) !important; -} - -/* Change slider color */ -.custom-range::-webkit-slider-thumb { - background: var(--accent-color); -} -.custom-range::-moz-range-thumb { - background: var(--accent-color); -} -.custom-range::-ms-thumb { - background: var(--accent-color); -} diff --git a/tcf_website/static/base/bugform.js b/tcf_website/static/base/bugform.js deleted file mode 100644 index 38c70981e..000000000 --- a/tcf_website/static/base/bugform.js +++ /dev/null @@ -1,64 +0,0 @@ -// import { validateForm } from "../common/form.js"; -// -// function submitForm() { -// const form = document.getElementById("bugform"); -// const valid = validateForm(form); -// if (valid === true) { -// postToDiscord(); -// // close form after submit -// $("#bugModal").modal("toggle"); -// $("#confirmationModal").modal("toggle"); -// } -// } -// -// function resetForm() { -// const form = document.getElementById("bugform"); -// form.classList.remove("was-validated"); -// const emailField = document.getElementById("emailField"); -// emailField.value = ""; -// const descriptionField = document.getElementById("descriptionField"); -// descriptionField.value = ""; -// -// for (let i = 1; i <= 4; i++) { -// const id = "#category" + i; -// $(id).prop("checked", false); -// } -// } -// -// function postToDiscord() { -// const url = window.location.href; -// const email = $("#emailField").val(); -// const description = $("#descriptionField").val(); -// let categories = ""; -// for (let i = 1; i <= 4; i++) { -// const id = "#category" + i; -// if ($(id).is(":checked")) { -// categories += "[" + $(id).val() + "]"; -// } -// } -// const data = { -// type: "bug", -// content: -// "Bug Found! \n**URL:** " + -// url + -// "\n**Description**: \n" + -// description + -// "\n**Categories: **" + -// categories + -// "\n**Email:** " + -// email, -// }; -// -// $.ajax({ -// type: "GET", -// url: "/discord/", -// data, -// }); -// } -// -// document -// .getElementById("bugSubmitBtn") -// .addEventListener("click", submitForm, false); -// document -// .getElementById("bugFormOpen") -// .addEventListener("click", resetForm, false); diff --git a/tcf_website/static/base/messages.css b/tcf_website/static/base/messages.css deleted file mode 100644 index 75682c528..000000000 --- a/tcf_website/static/base/messages.css +++ /dev/null @@ -1,8 +0,0 @@ -/* - We can use the Django messages framework to notify the user - whenever something important happens. For example, successfully logging out! - This css is used to format and animate any new Django messages that are - triggered in the application. - - https://docs.djangoproject.com/en/3.0/ref/contrib/messages/ -*/ diff --git a/tcf_website/static/base/navbar.css b/tcf_website/static/base/navbar.css deleted file mode 100644 index add3b3892..000000000 --- a/tcf_website/static/base/navbar.css +++ /dev/null @@ -1,98 +0,0 @@ -/* - Any css that involves the navbar should go in this file! - This way things are kept insolated and de-coupled from one - another, making components more maliable and maintainable. -*/ - -.navbar { - flex-wrap: nowrap; -} - -.logo-container { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - width: fit-content; - height: auto; - padding: 3px 2px 1px 2px; - - /* Animation */ - transform: scale(1); - transition: - padding-left 0.25s, - transform 0.2s; - transition-timing-function: ease; -} - -.logo-container:hover { - transform: scale(1.05); -} - -.logo-container:active { - transform: scale(1.05); -} - -.logo-container:focus { - transform: scale(1.05); -} - -@media (min-width: 768px) { - .logo-container { - width: calc(var(--sidebar-width) - 16px); - height: auto; - - /* Animation */ - padding-left: 0.55rem; /* centers logo in logo-container */ - padding-right: 0rem; - } -} - -.logo { - width: auto; - height: calc(70px - 8px - 8px - 4px); - alt: "tCF"; -} - -@media (min-width: 769px) { - .navbar-toggler.menu { - display: none !important; - } -} - -.badge-history-page { - position: relative; - top: -22px; - left: 44px; -} - -.navbar-toggler.menu { - margin-left: 16px; - margin-right: 8px; - border-color: rgb(24, 35, 71); - background-color: rgb(248, 249, 250); -} - -.navbar-toggler.menu:hover { - background-color: rgb(230, 230, 230); -} - -.navbar-toggler.menu:active { - background-color: rgb(200, 200, 200); -} - -.navbar-toggler.menu:focus { - background-color: rgb(190, 190, 190); -} - -@media (max-width: 768px) { - .account-settings { - display: none; - } -} - -@media (min-width: 768px) { - .dropdown-menu.settings { - min-width: 140px !important; - } -} diff --git a/tcf_website/static/base/reset.css b/tcf_website/static/base/reset.css deleted file mode 100644 index d72705dec..000000000 --- a/tcf_website/static/base/reset.css +++ /dev/null @@ -1,446 +0,0 @@ -/* http://meyerweb.com/eric/tools/css/reset/ - 2.0-modified | 20110126 - License: none (public domain) -*/ - -html, -body, -div, -span, -applet, -object, -iframe, -h1, -h2, -h3, -h4, -h5, -h6, -p, -blockquote, -pre, -a, -abbr, -acronym, -address, -big, -cite, -code, -del, -dfn, -em, -img, -ins, -kbd, -q, -s, -samp, -small, -strike, -strong, -sub, -sup, -tt, -var, -b, -u, -i, -center, -dl, -dt, -dd, -ol, -ul, -li, -fieldset, -form, -label, -legend, -table, -caption, -tbody, -tfoot, -thead, -tr, -th, -td, -article, -aside, -canvas, -details, -embed, -figure, -figcaption, -footer, -header, -hgroup, -menu, -nav, -output, -ruby, -section, -summary, -time, -mark, -audio, -video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} - -/* make sure to set some focus styles for accessibility */ -:focus { - outline: 0; -} - -/* HTML5 display-role reset for older browsers */ -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -menu, -nav, -section { - display: block; -} - -body { - line-height: 1; -} - -ol, -ul { - list-style: none; -} - -blockquote, -q { - quotes: none; -} - -blockquote:before, -blockquote:after, -q:before, -q:after { - content: ""; - content: none; -} - -table { - border-collapse: collapse; - border-spacing: 0; -} - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration, -input[type="search"]::-webkit-search-results-button, -input[type="search"]::-webkit-search-results-decoration { - -webkit-appearance: none; - -moz-appearance: none; -} - -input[type="search"] { - -webkit-appearance: none; - -moz-appearance: none; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -textarea { - overflow: auto; - vertical-align: top; - resize: vertical; -} - -/** - * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. - */ - -audio, -canvas, -video { - display: inline-block; - *display: inline; - *zoom: 1; - max-width: 100%; -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. - * Known issue: no IE 6 support. - */ - -[hidden] { - display: none; -} - -/** - * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using - * `em` units. - * 2. Prevent iOS text size adjust after orientation change, without disabling - * user zoom. - */ - -html { - font-size: 100%; /* 1 */ - -webkit-text-size-adjust: 100%; /* 2 */ - -ms-text-size-adjust: 100%; /* 2 */ -} - -/** - * Address `outline` inconsistency between Chrome and other browsers. - */ - -a:focus { - outline: thin dotted; -} - -/** - * Improve readability when focused and also mouse hovered in all browsers. - */ - -a:active, -a:hover { - outline: 0; -} - -/** - * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. - * 2. Improve image quality when scaled in IE 7. - */ - -img { - border: 0; /* 1 */ - -ms-interpolation-mode: bicubic; /* 2 */ -} - -/** - * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. - */ - -figure { - margin: 0; -} - -/** - * Correct margin displayed oddly in IE 6/7. - */ - -form { - margin: 0; -} - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct color not being inherited in IE 6/7/8/9. - * 2. Correct text not wrapping in Firefox 3. - * 3. Correct alignment displayed oddly in IE 6/7. - */ - -legend { - border: 0; /* 1 */ - padding: 0; - white-space: normal; /* 2 */ - *margin-left: -7px; /* 3 */ -} - -/** - * 1. Correct font size not being inherited in all browsers. - * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, - * and Chrome. - * 3. Improve appearance and consistency in all browsers. - */ - -button, -input, -select, -textarea { - font-size: 100%; /* 1 */ - margin: 0; /* 2 */ - vertical-align: baseline; /* 3 */ - *vertical-align: middle; /* 3 */ -} - -/** - * Address Firefox 3+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -button, -input { - line-height: normal; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. - * Correct `select` style inheritance in Firefox 4+ and Opera. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - * 4. Remove inner spacing in IE 7 without affecting normal text inputs. - * Known issue: inner spacing remains in IE 6. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ - *overflow: visible; /* 4 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * 1. Address box sizing set to content-box in IE 8/9. - * 2. Remove excess padding in IE 8/9. - * 3. Remove excess padding in IE 7. - * Known issue: excess padding remains in IE 6. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ - *height: 13px; /* 3 */ - *width: 13px; /* 3 */ -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome - * (include `-moz` to future-proof). - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; /* 2 */ - box-sizing: content-box; -} - -/** - * Remove inner padding and search cancel button in Safari 5 and Chrome - * on OS X. - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Remove inner padding and border in Firefox 3+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * 1. Remove default vertical scrollbar in IE 6/7/8/9. - * 2. Improve readability and alignment in all browsers. - */ - -textarea { - overflow: auto; /* 1 */ - vertical-align: top; /* 2 */ -} - -/** - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} - -html, -button, -input, -select, -textarea { - color: #222; -} - -::-moz-selection { - background: #b3d4fc; - text-shadow: none; -} - -::selection { - background: #b3d4fc; - text-shadow: none; -} - -img { - vertical-align: middle; -} - -fieldset { - border: 0; - margin: 0; - padding: 0; -} - -textarea { - resize: vertical; -} - -.chromeframe { - margin: 0.2em 0; - background: #ccc; - color: #000; - padding: 0.2em 0; -} diff --git a/tcf_website/static/base/sidebar.css b/tcf_website/static/base/sidebar.css deleted file mode 100644 index a1d2e44d3..000000000 --- a/tcf_website/static/base/sidebar.css +++ /dev/null @@ -1,86 +0,0 @@ -body { - overflow-x: hidden; -} - -#sidebar-wrapper { - height: calc(100vh - 70px); /* Subtract height of navbar */ - margin-left: -15rem; - -webkit-transition: margin-left 0.25s; - -moz-transition: margin-left 0.25s; - -o-transition: margin-left 0.25s; - transition: margin-left 0.25s; - background-color: var(--main-color); - position: fixed; - z-index: 9999; -} - -#sidebar-wrapper .list-group { - width: var(--sidebar-width); /* From base.css */ - margin-top: 2rem; -} - -#sidebar-wrapper .list-group hr { - border-color: white; - margin-bottom: 0rem; -} - -#sidebar-wrapper .list-group-item, -.footer, -.footer a { - background-color: var(--main-color); - color: white; - border: 0rem; -} - -#sidebar-wrapper .list-group-item.active, -#sidebar-wrapper .list-group-item:hover { - color: white; - border-color: var(--main-color); - filter: brightness(125%); -} - -#sidebar-wrapper .icon-section { - width: 100%; - padding: 0rem 0.5rem 1rem; -} - -#sidebar-wrapper .icon-wrapper { - display: inline-block; - width: 33%; -} - -#sidebar-wrapper .icon-wrapper i { - font-size: 1.25rem; -} - -#sidebar-wrapper .left-icon { - float: left; -} - -#sidebar-wrapper .right-icon { - float: right; -} - -#page-content-wrapper { - min-width: 100vw; -} - -/* Open sidebar if toggled */ -#wrapper.toggled #sidebar-wrapper { - margin-left: 0; -} - -@media (min-width: 768px) { - #sidebar-wrapper { - margin-left: 0; - } - - #page-content-wrapper { - min-width: 0; - width: 100%; - } -} - -.modal { - z-index: 10000; -} diff --git a/tcf_website/static/base/tcf-style.css b/tcf_website/static/base/tcf-style.css deleted file mode 100644 index f7d5f577a..000000000 --- a/tcf_website/static/base/tcf-style.css +++ /dev/null @@ -1,131 +0,0 @@ -/* tCF Style Classes */ - -/*************COLORS VARS**************/ - -/* Use these variables in CSS stylesheets */ -:root { - --main-color: #274f97; /* Indigo/Dark blue */ - --secondary-color: #4077c4; - --accent-color: #d75626; /* Orange */ - --dark-color: #545454; /* Dark grey, for headers/buttons */ - --shadow-color: #a6a6a6; /* Medium grey, used for shadows */ - --shaded-color: #cfcfcf; /* Medium light grey, used for shading */ - --background-color: #ebebeb; /* Light grey, used for backgrounds */ - /* Note: Can use Bootstrap "light" color and white color */ -} - -/*************DEFAULT STYLES**************/ -h1, -h2, -h3, -h4, -h5, -h6, -.h1, -.h2, -.h3, -.h4, -.h5, -.h6 { - /* Default heading font: Lato (others are fallbacks) */ - font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; -} - -body { - /* Body text font: Open Sans */ - font-family: "Open Sans", "Lato", "Helvetica Neue", Helvetica, Arial, - sans-serif; - font-weight: 500; -} - -a { - color: inherit; -} -a:hover, -.hover-tcf-orange:hover { - color: var(--accent-color); - text-decoration: none; - cursor: pointer; -} -ul { - padding: 0; - list-style-type: none; -} - -/*************UTILITY CLASSES**************/ - -/* To remove auto blue underline formatting from an anchor tag */ -.link-unstyled, -.link-unstyled:link, -.link-unstyled:hover { - color: inherit; - all: unset; - text-decoration: inherit; -} - -/*************COLOR CLASSES**************/ -/* Use these utility classes in HTML tag class styling */ - -/* Main color: Indigo/Dark blue */ -.text-tcf-indigo { - color: var(--main-color); -} -.bg-tcf-indigo { - background-color: var(--main-color); -} -.border-tcf-indigo { - border-color: var(--main-color); -} - -/* Secondary color: Light blue */ -.text-tcf-blue { - color: var(--secondary-color); -} -.bg-tcf-blue { - background-color: var(--secondary-color); -} - -/* Accent color: Orange */ -.text-tcf-orange { - color: var(--accent-color); -} -.bg-tcf-orange { - background-color: var(--accent-color); -} - -/* Dark accent color: Chalkboard */ -.text-tcf-chalkboard { - color: var(--dark-color); -} -.bg-tcf-chalkboard { - background-color: var(--dark-color); -} -.border-tcf-chalkboard { - border-color: var(--dark-color); -} - -.text-tcf-shadow { - color: var(--shadow-color); -} -.bg-tcf-shadow { - background-color: var(--shadow-color); -} -.border-tcf-shadow { - border-color: var(--shadow-color); -} - -.text-tcf-background { - color: var(--background-color); -} -.bg-tcf-background { - background-color: var(--background-color); -} -.border-tcf-background { - border-color: var(--background-color); -} - -/* Note: Can use Bootstrap "light" color - .text-light - .bg-light - .border-light -*/ diff --git a/tcf_website/static/css/site/pages/department.css b/tcf_website/static/css/site/pages/department.css index fbd8f3d11..951e4630b 100644 --- a/tcf_website/static/css/site/pages/department.css +++ b/tcf_website/static/css/site/pages/department.css @@ -135,6 +135,7 @@ .sort-select__label { font-size: var(--text-sm); color: var(--fg-muted); + white-space: nowrap; } .sort-select select { diff --git a/tcf_website/static/icons/icons.css b/tcf_website/static/icons/icons.css deleted file mode 100644 index 261a94203..000000000 --- a/tcf_website/static/icons/icons.css +++ /dev/null @@ -1,338 +0,0 @@ -.fa { - display: inline-block; - font-style: normal; /* Reset any inherited styles */ - line-height: 1; /* Match text line height */ - /* vertical-align: middle; */ - width: 1em; - height: 1em; -} - -.fas { - display: inline-block; - font-style: normal; /* Reset any inherited styles */ - line-height: 1; /* Match text line height */ - /* vertical-align: middle; */ - width: 1em; - height: 1em; -} - -.fa-clock { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-clock.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; - vertical-align: middle; -} - -.fa-arrow-right { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-arrow-right.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-search { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-search.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; - vertical-align: middle; -} - -.fa-book { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-book.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; - width: 1em; - height: 1em; - vertical-align: middle; - display: inline-block; - filter: invert(100%) brightness(150%); -} - -.fa-users { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-user.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; - width: 1em; - height: 1em; - vertical-align: middle; - display: inline-block; - filter: invert(100%) brightness(150%); -} - -.fa-graduation-cap { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-graduation-cap.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; - width: 1em; - height: 1em; - vertical-align: middle; - display: inline-block; - filter: invert(100%) brightness(150%); -} - -.fa-handshake { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-handshake.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; - width: 1em; - height: 1em; - vertical-align: middle; - display: inline-block; - filter: invert(100%) brightness(150%); -} - -.fa-github { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-github.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-user { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-user.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-dumbbell { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-dumbbell.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-chart-bar { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-chart-bar.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-star { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-star.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-calendar { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-calendar.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-plus { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-plus.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-chevron-up { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-chevron-up.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-chevron-down { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-chevron-down.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-chevron-up-white { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-chevron-up.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; - filter: invert(1); -} - -.fa-chevron-down-white { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-chevron-down.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; - filter: invert(1); -} - -.fa-thumbs-up { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-thumbs-up.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-thumbs-down { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-thumbs-down.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-trash-o { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-trash-o.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-hourglass-half { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-hourglass-half.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-pencil { - width: 1em; - height: 1em; - background-image: url("../icons/img/fa-pencil.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-instagram { - background-image: url("../icons/img/fa-instagram.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-discord { - background-image: url("../icons/img/fa-discord.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-facebook { - background-image: url("../icons/img/fa-facebook.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-code { - background-image: url("../icons/img/fa-code.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-list { - background-image: url("../icons/img/fa-list.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; - width: 1em; - height: 1em; - vertical-align: middle; - display: inline-block; - filter: invert(100%) brightness(150%); -} - -.fa-user-friends { - background-image: url("../icons/img/fa-user-friends.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-smile-beam { - background-image: url("../icons/img/fa-smile-beam.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-frown-open { - background-image: url("../icons/img/fa-frown-open.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-heart { - background-image: url("../icons/img/fa-heart.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-copy { - background-image: url("../icons/img/fa-copy.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.fa-flask { - background-image: url("../icons/img/fa-flask.svg"); - background-repeat: no-repeat; - background-size: contain; - background-position: center; -} - -.invert-to-light { - filter: invert(1); -} - -.invert-to-dark { - filter: invert(0); -} diff --git a/tcf_website/static/icons/icons.html b/tcf_website/static/icons/icons.html deleted file mode 100644 index e3ef16902..000000000 --- a/tcf_website/static/icons/icons.html +++ /dev/null @@ -1,7 +0,0 @@ - Instructor - Enjoyability - Difficulty - Recommendability - Reading Writing - Groupwork Other - Schedule diff --git a/tcf_website/static/icons/img/fa-arrow-right.svg b/tcf_website/static/icons/img/fa-arrow-right.svg deleted file mode 100644 index 2fe5f424b..000000000 --- a/tcf_website/static/icons/img/fa-arrow-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-book.svg b/tcf_website/static/icons/img/fa-book.svg deleted file mode 100644 index c60542be2..000000000 --- a/tcf_website/static/icons/img/fa-book.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-calendar.svg b/tcf_website/static/icons/img/fa-calendar.svg deleted file mode 100644 index ea4cd3149..000000000 --- a/tcf_website/static/icons/img/fa-calendar.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-chart-bar.svg b/tcf_website/static/icons/img/fa-chart-bar.svg deleted file mode 100644 index 95d97214d..000000000 --- a/tcf_website/static/icons/img/fa-chart-bar.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-chevron-down.svg b/tcf_website/static/icons/img/fa-chevron-down.svg deleted file mode 100644 index 10d052149..000000000 --- a/tcf_website/static/icons/img/fa-chevron-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-chevron-up.svg b/tcf_website/static/icons/img/fa-chevron-up.svg deleted file mode 100644 index 97e185c67..000000000 --- a/tcf_website/static/icons/img/fa-chevron-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-clock.svg b/tcf_website/static/icons/img/fa-clock.svg deleted file mode 100644 index ff32be94d..000000000 --- a/tcf_website/static/icons/img/fa-clock.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-code.svg b/tcf_website/static/icons/img/fa-code.svg deleted file mode 100644 index 76ed0763f..000000000 --- a/tcf_website/static/icons/img/fa-code.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-copy.svg b/tcf_website/static/icons/img/fa-copy.svg deleted file mode 100644 index 6af51b7e6..000000000 --- a/tcf_website/static/icons/img/fa-copy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-discord.svg b/tcf_website/static/icons/img/fa-discord.svg deleted file mode 100644 index 1dc533d68..000000000 --- a/tcf_website/static/icons/img/fa-discord.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-dumbbell.svg b/tcf_website/static/icons/img/fa-dumbbell.svg deleted file mode 100644 index 780759e3a..000000000 --- a/tcf_website/static/icons/img/fa-dumbbell.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-facebook.svg b/tcf_website/static/icons/img/fa-facebook.svg deleted file mode 100644 index d1ad55c45..000000000 --- a/tcf_website/static/icons/img/fa-facebook.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-flask.svg b/tcf_website/static/icons/img/fa-flask.svg deleted file mode 100644 index 77873b40b..000000000 --- a/tcf_website/static/icons/img/fa-flask.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-frown-open.svg b/tcf_website/static/icons/img/fa-frown-open.svg deleted file mode 100644 index 6dc32b5e6..000000000 --- a/tcf_website/static/icons/img/fa-frown-open.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-github.svg b/tcf_website/static/icons/img/fa-github.svg deleted file mode 100644 index 3d8bc7c09..000000000 --- a/tcf_website/static/icons/img/fa-github.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-graduation-cap.svg b/tcf_website/static/icons/img/fa-graduation-cap.svg deleted file mode 100644 index e8d5af53d..000000000 --- a/tcf_website/static/icons/img/fa-graduation-cap.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-handshake.svg b/tcf_website/static/icons/img/fa-handshake.svg deleted file mode 100644 index 9993db8b5..000000000 --- a/tcf_website/static/icons/img/fa-handshake.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-heart.svg b/tcf_website/static/icons/img/fa-heart.svg deleted file mode 100644 index 0bc9ee033..000000000 --- a/tcf_website/static/icons/img/fa-heart.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-hourglass-half.svg b/tcf_website/static/icons/img/fa-hourglass-half.svg deleted file mode 100644 index caa85c8ce..000000000 --- a/tcf_website/static/icons/img/fa-hourglass-half.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-instagram.svg b/tcf_website/static/icons/img/fa-instagram.svg deleted file mode 100644 index faefc347d..000000000 --- a/tcf_website/static/icons/img/fa-instagram.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-list.svg b/tcf_website/static/icons/img/fa-list.svg deleted file mode 100644 index 7e81dd158..000000000 --- a/tcf_website/static/icons/img/fa-list.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-pencil.svg b/tcf_website/static/icons/img/fa-pencil.svg deleted file mode 100644 index 1c917ef13..000000000 --- a/tcf_website/static/icons/img/fa-pencil.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-plus.svg b/tcf_website/static/icons/img/fa-plus.svg deleted file mode 100644 index 10537559d..000000000 --- a/tcf_website/static/icons/img/fa-plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-search.svg b/tcf_website/static/icons/img/fa-search.svg deleted file mode 100644 index 861d4ae40..000000000 --- a/tcf_website/static/icons/img/fa-search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-smile-beam.svg b/tcf_website/static/icons/img/fa-smile-beam.svg deleted file mode 100644 index 47e3d066c..000000000 --- a/tcf_website/static/icons/img/fa-smile-beam.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-star.svg b/tcf_website/static/icons/img/fa-star.svg deleted file mode 100644 index cb6807771..000000000 --- a/tcf_website/static/icons/img/fa-star.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-thumbs-down.svg b/tcf_website/static/icons/img/fa-thumbs-down.svg deleted file mode 100644 index 3bb06eb60..000000000 --- a/tcf_website/static/icons/img/fa-thumbs-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-thumbs-up.svg b/tcf_website/static/icons/img/fa-thumbs-up.svg deleted file mode 100644 index 9079bb38e..000000000 --- a/tcf_website/static/icons/img/fa-thumbs-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-trash-o.svg b/tcf_website/static/icons/img/fa-trash-o.svg deleted file mode 100644 index 235909cf9..000000000 --- a/tcf_website/static/icons/img/fa-trash-o.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-user-friends.svg b/tcf_website/static/icons/img/fa-user-friends.svg deleted file mode 100644 index 22a9fb7fc..000000000 --- a/tcf_website/static/icons/img/fa-user-friends.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/static/icons/img/fa-user.svg b/tcf_website/static/icons/img/fa-user.svg deleted file mode 100644 index ae097027d..000000000 --- a/tcf_website/static/icons/img/fa-user.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/tcf_website/templates/about/contributors.html b/tcf_website/templates/about/contributors.html deleted file mode 100644 index 840acb825..000000000 --- a/tcf_website/templates/about/contributors.html +++ /dev/null @@ -1,62 +0,0 @@ -{% block subcontent %} -{% load static %} - -
    - -

    Founders & Alumni

    - -
    -
    -

    Founders

    -
    -
    -
    - {% for member in founders %} -
    -
    -
    - {{ member.name }} -
    -
    -

    - {{ member.name }}
    - {{ member.role }} -

    -

    Class of {{ member.class }}

    -
    -
    -
    - {% endfor %} -
    -
    -
    - - {% for group in contributors %} -
    -
    -

    {{ group.group_name }}

    -
    -
    - {% for member in group.members %} -
    -
    -
    - {{ member.name }} -
    -
    -
    - {{ member.name }}
    - {{ member.role }} -
    -
    -
    -
    - {% endfor %} -
    -
    -
    - {% endfor %} - -
    - -{% endblock %} diff --git a/tcf_website/templates/about/current_team.html b/tcf_website/templates/about/current_team.html deleted file mode 100644 index 045981b6b..000000000 --- a/tcf_website/templates/about/current_team.html +++ /dev/null @@ -1,105 +0,0 @@ -{% block subcontent %} -{% load static %} - -
    - -

    Meet the Team

    - -
    -
    -

    Executive Team

    -
    -
    - {% for member in executive_team %} -
    -
    -
    - {{ member.name }} -
    -
    -
    {{ member.name }}
    -

    {{ member.role }}

    -

    Class of {{ member.class }}

    -
    -
    -
    - {% endfor %} -
    -
    -
    - -
    -
    -

    Engineering Team

    -
    -
    - {% for member in engineering_team %} -
    -
    -
    - {{ member.name }} -
    -
    - -
    {{ member.name }}
    -

    Class of {{ member.class }}

    -
    - -

    -
    -
    -
    -
    - {% endfor %} -
    -
    -
    - -
    -
    -

    Design Team

    -
    -
    - {% for member in design_team %} -
    -
    -
    - {{ member.name }} -
    -
    -
    {{ member.name }}
    -

    Class of {{ member.class }}

    -
    -
    -
    - {% endfor %} -
    -
    -
    - - -
    -
    -

    Marketing Team

    -
    -
    - {% for member in marketing_team %} -
    -
    -
    - {{ member.name }} -
    -
    -
    {{ member.name }}
    -

    Class of {{ member.class }}

    -
    -
    -
    - {% endfor %} -
    -
    -
    - -
    - -{% endblock %} diff --git a/tcf_website/templates/about/history.html b/tcf_website/templates/about/history.html deleted file mode 100644 index fa5d7f1d9..000000000 --- a/tcf_website/templates/about/history.html +++ /dev/null @@ -1,54 +0,0 @@ -{% block subcontent %} -{% load static %} - -{% block styles %} - -{% endblock %} - -
    -
    -

    History

    - -
    -

    Founding

    -

    - In Fall 2005, two University of Virginia students, Jeff Bordogna and - Alan Webb had an idea for an app that would let UVA students share - their classroom experiences. -

    -
    - -
    -

    Revival

    -

    - In 2012-2013, a team of 6 student developers led by Justin Dao redesigned - the site to bring it into the modern age using the Ruby on Rails framework. -

    - "Same dog, new tricks..." - Justin Dao -

    -
    - -
    -

    CIO Era

    -

    - In 2017, a successful year of recruitment and organizational growth - resulted in UVA granting theCourseForum CIO status. -

    -
    - -
    -

    Revival 2.0

    -

    - In Spring 2020, theCourseForum needed a fresh start. Under the - leadership of Brian Yu, Sai Konuri, Brad Knaysi, and Jennifer Long, theCourseForum - was re-built from scratch using the Python web-framework Django. -

    -

    - The new site was completed in Fall 2020 with a team led by Jennifer Long, Alex Shen, Jack Liu, - Jules Le Menestrel, and Vi Nguyen. -

    -
    -
    -
    - -{% endblock %} diff --git a/tcf_website/templates/about/logo-tagline.html b/tcf_website/templates/about/logo-tagline.html deleted file mode 100644 index b167c3e1d..000000000 --- a/tcf_website/templates/about/logo-tagline.html +++ /dev/null @@ -1,10 +0,0 @@ - -{% load static %} - diff --git a/tcf_website/templates/about/partials/privacy_content.html b/tcf_website/templates/about/partials/privacy_content.html deleted file mode 100644 index cadf7439d..000000000 --- a/tcf_website/templates/about/partials/privacy_content.html +++ /dev/null @@ -1,89 +0,0 @@ -
    -

    theCourseForum Privacy Policy

    -

    We built theCourseForum to make it easy to share course information with your - fellow students. Our default privacy settings limit the use of your information.

    -

    theCourseForum's Privacy Policy is designed to help you understand how we - collect and use the personal information you decide to share, and help you make - informed decisions when using theCourseForum, located at www.theCourseForum.com - and its directly associated domains (collectively, "theCourseForum" or - "Website").

    -

    By using or accessing theCourseForum, you are accepting the - practices described in this Privacy Policy.

    -

    Disclaimer: Although this organization has members who are University of Virginia students and may have University employees associated or engaged in its activities and affairs, the organization is not a part of or an agency of the University. It is a separate and independent organization, which is responsible for and manages its own activities and affairs. The University does not direct, supervise or control the organization and is not responsible for the organization’s contracts, acts or omissions. -

    -
    -

    The Information We Collect

    -

    When you visit theCourseForum you provide us with two types of - information: personal information you knowingly choose to disclose that is - collected by us and Web Site use information collected by us as you interact - with our Web Site.

    -

    When you register with theCourseForum, you provide us with certain - personal information, such as your name, your email address, your major(s), - and any other personal or preference information that you provide to us.

    -

    When you enter theCourseForum, we collect your browser type and IP - address. This information is gathered for all theCourseForum visitors. In - addition, we store certain information from your browser using "cookies." A - cookie is a piece of data stored on the user's computer tied to information - about the user. We use session ID cookies to confirm that users are logged - in. These cookies terminate once the user closes the browser.

    -

    When you use theCourseForum, you set up your personal account. We collect this information so that we can provide you the service and offer personalized features. In most cases, we retain it so that, for instance, you can return to view prior reviews you have written. When you update information, we usually keep a backup copy of the prior version for a reasonable period of time to enable reversion to the prior version of that information.

    - -

    You post User Content (as defined in the theCourseForum Terms of Use on the Site at your own risk. Although we do not share your name, we cannot control the actions of other Users with whom you may choose to share your reviews and information. Therefore, we cannot and do not guarantee that User Content you post on the Site will not be viewed by unauthorized persons. We are not responsible for circumvention of any privacy settings or security measures contained on the Site. You understand and acknowledge that, even after removal, copies of User Content may remain viewable in cached and archived pages or if other Users have copied or stored your User Content.

    - -

    Any improper collection or misuse of information provided on theCourseForum is a violation of the theCourseForum Terms of Service and should be reported to - - support@theCourseForum.com. -

    - -

    By using theCourseForum, you are consenting to have your personal data transferred to and processed in the United States.

    - -

    Children Under Age 13

    -

    theCourseForum does not knowingly collect or solicit personal information from anyone under the age of 13 or knowingly allow such persons to register. If you are under 13, please do not attempt to register for theCourseForum or send any information about yourself to us, including your name or email address. No one under age 13 may provide any personal information to or on theCourseForum. In the event that we learn that we have collected personal information from a child under age 13 without verification of parental consent, we will delete that information as quickly as possible. If you believe that we might have any information from or about a child under 13, please contact us at - - support@theCourseForum.com. -

    - -

    Children Between the Ages of 13 and 18

    -

    We recommend that minors over the age of 13 ask their parents for permission before sending any information about themselves to anyone over the Internet.

    - -

    Use of Information Obtained by theCourseForum

    -

    When you register with theCourseForum, you create your own account. Your account information, as well as your name and email, are not displayed to other Users. We may occasionally use your name and email address to send you notifications regarding new services offered by theCourseForum that we think you may find valuable.

    - -

    Account information is used by theCourseForum primarily to be presented back to and edited by you when you access the service.

    - -

    theCourseForum may send you service-related announcements from time to time through the general operation of the service.

    - -

    theCourseForum may use information in your account without identifying you as an individual to third parties. We do this for purposes such as aggregating how many people in a class recommend a professor and personalizing advertisements and promotions so that we can provide you with theCourseForum.

    - -

    Sharing Your Information with Third Parties

    - -

    theCourseForum is about sharing information with fellow students while providing you with privacy settings that restrict other users from accessing your information. We do not provide contact information to third party marketers without your permission. We share your information with third parties only in limited circumstances where we believe such sharing is 1) reasonably necessary to offer the service, 2) legally required or, 3) permitted by you. For example:

    - -
      -
    • We may provide information to service providers to help us bring you the services we offer. Specifically, we may use third parties to facilitate our business, such as to host the service at a hosting facility for servers, to send out email updates about theCourseForum, to remove repetitive information from our user lists, or to provide search results or links (including sponsored links). In connection with these offerings and business operations, our service providers may have access to your personal information for use for a limited time in connection with these business activities. Where we utilize third parties for the processing of any personal information, we implement reasonable contractual and technical protections limiting the use of that information to theCourseForum-specified purposes.
    • -
    • We occasionally provide demonstration accounts that allow non-users a glimpse into the theCourseForum world. Such accounts have only limited capabilities (e.g., reviewing is disabled) and passwords are changed regularly to limit possible misuse.
    • -
    • We may be required to disclose user information pursuant to lawful requests, such as subpoenas or court orders, or in compliance with applicable laws. We do not reveal information until we have a good faith belief that an information request by law enforcement or private litigants meets applicable legal standards. Additionally, we may share account or other information when we believe it is necessary to comply with law, to protect our interests or property, to prevent fraud or other illegal activity perpetrated through theCourseForum service or using the theCourseForum name, or to prevent imminent bodily harm. This may include sharing information with other companies, lawyers, agents or government agencies. We may offer stores or provide services jointly with other companies on theCourseForum. You can tell when another company is involved in any store or service provided on theCourseForum, and we may share customer information with that company in connection with your use of that store or service.
    • -
    • If the ownership of all or substantially all of the theCourseForum business, or individual business units owned by theCourseForum, were to change, your user information may be transferred to the new owner so the service can continue operations. In any such transfer of information, your user information would remain subject to the promises made in any pre-existing Privacy Policy.
    • -
    - -

    Links

    -

    theCourseForum may contain links to other websites. We are of course not responsible for the privacy practices of other web sites. We encourage our users to be aware when they leave our site to read the privacy statements of each and every web site that collects personally identifiable information. This Privacy Policy applies solely to information collected by theCourseForum.

    - -

    Changing or Removing Information

    -

    Access and control over personal information on theCourseForum is readily available through the account editing tools. theCourseForum users may modify any of their account information at any time by logging into their account. Information will be updated immediately. Modified information may persist in backup copies for a reasonable period of time but will not be generally available to members of theCourseForum.

    - -

    Security

    -

    theCourseForum takes appropriate precautions to protect our users' information. Because email and instant messaging are not recognized as secure communications, we request that you not send private information to us by email or instant messaging services. If you have any questions about the security of theCourseForum Web Site, please contact us at - - support@theCourseForum.com. -

    - -

    Terms of Use, Notices and Revisions

    -

    Your use of theCourseForum, and any disputes arising from it, is subject to this Privacy Policy as well as our Terms of Use and all of its dispute resolution provisions including arbitration, limitation on damages and choice of law. We reserve the right to change our Privacy Policy and our Terms of Use at any time. Non-material changes and clarifications will take effect immediately, and material changes will take effect within 30 days of their posting on this site. If we make changes, we will post them and will indicate at the top of this page the policy's new effective date. If we make material changes to this policy, we will notify you here, by email, or through notice on our home page. We encourage you to refer to this policy on an ongoing basis so that you understand our current privacy policy. Unless stated otherwise, our current privacy policy applies to all information that we have about you and your account.

    - -

    Contacting the Website

    -

    If you have any questions about this privacy policy, please contact us at - - support@theCourseForum.com. -

    -
    diff --git a/tcf_website/templates/about/partials/terms_content.html b/tcf_website/templates/about/partials/terms_content.html deleted file mode 100644 index b1bd4e3a1..000000000 --- a/tcf_website/templates/about/partials/terms_content.html +++ /dev/null @@ -1,111 +0,0 @@ -
    -

    Welcome to theCourseForum, a service that connects you with the courses you - love. The theCourseForum service and network (collectively, "theCourseForum" or - "the Service") are operated by theCourseForum and its affiliates (collectively, - "us", or "we"). By accessing or using our web site at www.theCourseForum.com - (together the "Site"), you (the "User") signify that you have read, understand - and agree to be bound by these Terms of Use ("Terms of Use" or "Agreement"), - whether or not you are a registered member of theCourseForum.

    -

    We reserve the - right, at our sole discretion, to change, modify, add, or delete portions of - these Terms of Use at any time without further notice. If we do this, we will - post the changes to these Terms of Use on this page and will indicate at the top - of this page the date these terms were last revised. Your continued use of the - Service or the Site after any such changes constitutes your acceptance of the - new Terms of Use. If you do not agree to abide by these or any future Terms of - Use, do not use or access (or continue to use or access) the Service or the - Site. It is your responsibility to regularly check the Site to determine if - there have been changes to these Terms of Use and to review such changes.

    -

    Please read these terms of use carefully as they contain important - information regarding your legal rights, remedies and obligations. These - include various limitations and exclusions, and a dispute resolution clause - that governs how disputes will be resolved.

    -
    -

    Eligibility

    -

    Membership in the Service is void where prohibited. This Site is intended - solely for users who are thirteen (13) years of age or older, and users of - the Site under 18 who are currently in college. Any registration by, use of - or access to the Site by anyone under 13, or by anyone who is under 18 and - not in college, is unauthorized, unlicensed and in violation of these Terms - of Use. By using the Service or the Site, you represent and warrant that you - are 13 or older and in college, or else that you are 18 or older, and that - you agree to and to abide by all of the terms and conditions of this - Agreement.

    - -

    Registration Data; Account Security

    -

    In consideration of your use of the Site, you agree to (a) provide accurate, current and complete information about you as may be prompted by any registration forms on the Site ("Registration Data"); (b) maintain the security of your password and identification; (c) maintain and promptly update the Registration Data, and any other information you provide to us, to keep it accurate, current and complete; and (d) be fully responsible for all use of your account and for any actions that take place using your account.

    - -

    Proprietary Rights in Site Content; Limited License

    -

    All content on the Site and available through the Service, including but not limited to designs, text, graphics, pictures, information, and other files, and their selection and arrangement (the "Site Content"), are our proprietary property, or that of our users or our licensors with all rights reserved. No Site Content may be modified, copied, distributed, framed, reproduced, republished, downloaded, displayed, posted, transmitted, or sold in any form or by any means, in whole or in part, without our prior written permission, except that the foregoing does not apply to your own User Content (as defined below) that you legally post on the Site. Provided that you are eligible for use of the Site, you are granted a limited license to access and use the Site and the Site Content and to download or print a copy of any portion of the Site Content to which you have properly gained access solely for your personal, non-commercial use, provided that you keep all copyright or other proprietary notices intact. Except for your own User Content, you may not upload or republish Site Content on any Internet, Intranet or Extranet site or incorporate the information in any other database or compilation, and any other use of the Site Content is strictly prohibited. Such license is subject to these Terms of Use and does not include use of any data mining, robots or similar data gathering or extraction methods. Any use of the Site or the Site Content other than as specifically authorized herein, without prior written permission from us, is strictly prohibited and will terminate the license granted herein. Such unauthorized use may also violate applicable laws including without limitation copyright and trademark laws and applicable communications regulations and statutes. Unless explicitly stated herein, nothing in these Terms of Use shall be construed as conferring any license to intellectual property rights, whether by estoppel, implication or otherwise. This license is revocable at any time without notice and with or without cause.

    - -

    Trademarks

    -

    THECOURSEFORUM, THE THECOURSEFORUM, and other graphics, logos, designs, page headers, button icons, scripts and service names are trademarks, trademarks or trade dress of ours in the U.S. and/or other countries. Our trademarks and trade dress may not be used, including as part of trademarks and/or as part of domain names, in connection with any product or service in any manner that is likely to cause confusion and may not be copied, imitated, or used, in whole or in part, without the prior written permission from us.

    - -

    User Conduct

    -

    You understand that the Service and the Site are available for your personal, non-commercial use only. You represent, warrant and agree that no materials of any kind submitted through your account or otherwise posted, transmitted, or shared by you on or through the Service will violate or infringe upon the rights of any third party, including copyright, trademark, privacy, publicity or other personal or proprietary rights; or contain libelous, defamatory or otherwise unlawful material.

    - -

    In addition, you agree not to use the Service or the Site to:

    - -
      -
    • upload, post, transmit, share or otherwise make publicly available the names of anyone not listed as part of the provided faculty information on the Site without their explicit consent;
    • -
    • use the Service or the Site in any unlawful manner or in any other manner that could damage, disable, overburden or impair the Site;
    • -
    • use automated scripts to collect information from or otherwise interact with the Service or the Site;
    • -
    • upload, post, transmit, share, store or otherwise make available any content that we deem to be harmful, threatening, unlawful, defamatory, infringing, abusive, inflammatory, harassing, vulgar, obscene, fraudulent, invasive of privacy or publicity rights, hateful, or racially, ethnically or otherwise objectionable;
    • -
    • register for more than one User account, register for a User account on behalf of an individual other than yourself, or register for a User account on behalf of any group or entity;
    • -
    • impersonate any person or entity, or falsely state or otherwise misrepresent yourself;
    • -
    • upload, post, transmit, share or otherwise make available any unsolicited or unauthorized advertising, solicitations, promotional materials, "junk mail," "spam," "chain letters," "pyramid schemes," or any other form of solicitation;
    • -
    • upload, post, transmit, share, store or otherwise make publicly available on the Site any private information of any third party, including, without limitation, addresses, phone numbers, email addresses, Social Security numbers and credit card numbers;
    • -
    • solicit personal information from anyone under 18 or solicit passwords or personally identifying information for commercial or unlawful purposes;
    • -
    • upload, post, transmit, share or otherwise make available any material that contains software viruses or any other computer code, files or programs designed to interrupt, destroy or limit the functionality of any computer software or hardware or telecommunications equipment;
    • -
    • intimidate or harass another;
    • -
    • upload, post, transmit, share, store or otherwise make available content that would constitute, encourage or provide instructions for a criminal offense, violate the rights of any party, or that would otherwise create liability or violate any local, state, national or international law;
    • -
    • use or attempt to use another's account, service or system without authorization from us, or create a false identity on the Service or the Site;
    • -
    • upload, post, transmit, share, store or otherwise make available content that, in our sole judgment, is objectionable or which restricts or inhibits any other person from using or enjoying the Site, or which may expose us or our users to any harm or liability of any type;
    • -
    • Without limiting any of the foregoing, you also agree to abide by our theCourseForum Code of Conduct that provides further information regarding the authorized conduct of users on theCourseForum.
    • -
    - -

    User Content Posted on the Site

    -

    You are solely responsible for the reviews and other content that you upload, publish or display (hereinafter, "post") on or through the Service or the Site, (collectively the "User Content"). You may not post, transmit, or share User Content on the Site or Service that you did not create or that you do not have permission to post. You understand and agree that we may, but are not obligated to, review the Site and may delete or remove (without notice) any Site Content or User Content in our sole discretion, for any reason or no reason, including without limitation User Content that in our sole judgment violates this Agreement or the theCourseForum Code of Conduct, or which might be offensive, illegal, or that might violate the rights, harm, or threaten the safety of users or others. You are solely responsible at your sole cost and expense for creating backup copies and replacing any User Content you post or store on the Site or provide to us.

    - -

    When you post User Content to the Site, you authorize and direct us to make such copies thereof as we deem necessary in order to facilitate the posting and storage of the User Content on the Site. By posting User Content to any part of the Site, you automatically grant, and you represent and warrant that you have the right to grant, to us an irrevocable, perpetual, non-exclusive, transferable, fully paid, worldwide license (with the right to sublicense) to use, copy, publicly perform, publicly display, reformat, translate, excerpt (in whole or in part) and distribute such User Content for any purpose on or in connection with the Site or the promotion thereof, to prepare derivative works of, or incorporate into other works, such User Content, and to grant and authorize sublicenses of the foregoing. You may remove your User Content from the Site at any time. If you choose to remove your User Content, the license granted above will automatically expire, however you acknowledge that we may retain archived copies of your User Content.

    - -

    Third Party Websites and Content

    -

    The Site may contain (or you may be sent through the Site or the Service) links to other web sites as well as content or items belonging to or originating from third parties (the Content). Such Third Party Sites or Content are not investigated, monitored or checked for accuracy, appropriateness, or completeness by us, and we are not responsible for any Third Party Sites accessed through the Site or Content posted on, available through or installed from the Site, including without limitation the content, accuracy, offensiveness, opinions, reliability, privacy practices or other policies of or contained in the Third Party Sites or Content. Inclusion of, linking to or permitting the use of any Third Party Site or Content does not imply approval or endorsement thereof by us. If you decide to leave the Site and access the Third Party Sites or Content, you do so at your own risk and you should be aware that our terms and policies no longer govern. You should review the applicable terms and policies, including privacy and data gathering practices, of any site to which you navigate from the Site.

    - -

    User Disputes

    -

    You are solely responsible for your interactions with other theCourseForum users. We reserve the right, but have no obligation, to monitor disputes between you and other users.

    - -

    Privacy

    -

    We care about the privacy of our users. Click here to view the theCourseForum's Privacy Policy. By using the Site or the Service, you are consenting to have your personal data transferred to and processed in the United States.

    - -

    Disclaimers

    -

    We are not responsible or liable in any manner for any User Content posted on the Site or in connection with the Service, whether posted or caused by users of the Site, by theCourseForum, by third parties or by any of the equipment or programming associated with or utilized in the Site or the Service. Although we provide rules for user conduct and postings, we do not control and are not responsible for what users post, transmit or share on the Site and are not responsible for any offensive, inappropriate, obscene, unlawful or otherwise objectionable content you may encounter on the Site or in connection with any User Content or other Content. We are not responsible for the conduct, whether online or offline, of any user of the Site or Service.

    - -

    The Site and the Service may be temporarily unavailable from time to time for maintenance or other reasons. We assume no responsibility for any error, omission, interruption, deletion, defect, delay in operation or transmission, communications line failure, theft or destruction or unauthorized access to, or alteration of, User communications. We are not responsible for any technical malfunction or other problems of any telephone network or service, computer systems, servers or providers, computer or mobile phone equipment, software, failure of email or players on account of technical problems or traffic congestion on the Internet or at any Site or combination thereof, including injury or damage to User's or to any other person's computer, mobile phone, or other hardware or software, related to or resulting from using or downloading materials in connection with the Web and/or in connection with the Service . Under no circumstances will we be responsible for any loss or damage, including any loss or damage to any User Content or personal injury or death, resulting from anyone's use of the Site or the Service, any User Content or Content posted on or through the Site or the Service or transmitted to Users, or any interactions between users of the Site, whether online or offline.

    - -

    The site, the service and the site content are provided "as-is" and we disclaim any and all representations and warranties, whether express or implied, including without limitation implied warranties of title, merchantability, fitness for a particular purpose or non-infringement. We cannot guarantee and do not promise any specific results from use of the site and/or the service. We do not represent or warrant that software, content or materials on the site or the service are accurate, complete, reliable, current or error-free or that the site or service its servers are free of viruses or other harmful components. Therefore, you should exercise caution in the use and downloading of any such software, content or materials and use industry-recognized software to detect and disinfect viruses. Without limiting the foregoing, you understand and agree that you download or otherwise obtain content, material, data or software from or through the service at your own discretion and risk and that you will be solely responsible for your use thereof and any damages to your mobile device or computer system, loss of data or other harm of any kind that may result.

    - -

    We reserve the right to change any and all content, software and other items used or contained in the Site and any Services offered through the Site at any time without notice. Reference to any products, services, processes or other information, by trade name, trademark, manufacturer, supplier or otherwise does not constitute or imply endorsement, sponsorship or recommendation thereof, or any affiliation therewith, by us.

    - -

    Limitation on Liability

    -

    In no event will we or our directors, employees or agents be liable to you or any third person for any indirect, consequential, exemplary, incidental, special or punitive damages, including for any lost profits or lost data arising from your use of the site or the service or any of the site content or other materials on, accessed through or downloaded from the site, even if the we are aware or has been advised of the possibility of such damages. Notwithstanding anything to the contrary contained herein, our liability to you for any cause whatsoever, and regardless of the form of the action, will at all times be limited to the amount paid, if any, by you to us for the service during the term of membership, but in no case will our liability to you exceed $1000. You acknowledge that if no fees are paid to us for the service, you shall be limited to injunctive relief only, unless otherwise permitted by law, and shall not be entitled to damages of any kind from us, regardless of the cause of action.

    - -

    Certain state laws do not allow limitations on implied warranties or the exclusion or limitation of certain damages. If these laws apply to you, some or all of the above disclaimers, exclusions or limitations may not apply to you, and you may have additional rights.

    - -

    Termination

    -

    We may terminate your membership, delete your account and any content or information that you have posted on the Site and/or prohibit you from using or accessing the Service or the Site (or any portion, aspect or feature of the Service or the Site) for any reason, or no reason, at any time in its sole discretion, with or without notice, including without limitation if it believes that you are under 13, or under 18 and not in college.

    - -

    Governing Law; Venue and Jurisdiction

    -

    By visiting or using the Site and/or the Service, you agree that the laws of the State of Virginia, without regard to principles of conflict of laws, will govern these Terms of Use and any dispute of any sort that might arise between you and us or any of our affiliates. With respect to any disputes or claims not subject to arbitration (as set forth below), you agree not to commence or prosecute any action in connection therewith other than in the state and federal courts of, and you hereby consent to, and waive all defenses of lack of personal jurisdiction and forum non conveniens with respect to, venue and jurisdiction in the state and federal courts of.

    - -

    Indemnity

    -

    You agree to indemnify and hold us, our subsidiaries and affiliates, and each of our directors, officers, agents, contractors, partners and employees, harmless from and against any loss, liability, claim, demand, damages, costs and expenses, including reasonable attorney's fees, arising out of or in connection with any User Content you post or share on or through the Site, your use of the Service or the Site, your conduct in connection with the Service or the Site or with other users of the Service or the Site, or any violation of this Agreement or of any law or the rights of any third party.

    - -

    Submissions

    -

    You acknowledge and agree that any questions, comments, suggestions, ideas, feedback or other information about the Site or the Service ("Submissions"), provided by you to us are non-confidential and shall become our sole property. We shall own exclusive rights, including all intellectual property rights, and shall be entitled to the unrestricted use and dissemination of these Submissions for any purpose, commercial or otherwise, without acknowledgment or compensation to you.

    - -

    CIO Disclaimer

    -

    Although this organization has members who are University of Virginia students and may have University employees associated or engaged in its activities and affairs, the organization is not a part of or an agency of the University. It is a separate and independent organization, which is responsible for and manages its own activities and affairs. The University does not direct, supervise or control the organization and is not responsible for the organization’s contracts, acts or omissions.

    -

    Other

    -

    These Terms of Use constitute the entire agreement between you and us regarding the use of the Site and/or the Service, superseding any prior agreements between you and us relating to your use of the Site or the Service. Our failure to exercise or enforce any right or provision of these Terms of Use shall not constitute a waiver of such right or provision in that or any other instance. If any provision of this Agreement is held invalid, the remainder of this Agreement shall continue in full force and effect. If any provision of these Terms of Use shall be deemed unlawful, void or for any reason unenforceable, then that provision shall be deemed severable from these Terms of Use and shall not affect the validity and enforceability of any remaining provisions.

    -
    diff --git a/tcf_website/templates/about/sponsors.html b/tcf_website/templates/about/sponsors.html deleted file mode 100644 index 5aa37357d..000000000 --- a/tcf_website/templates/about/sponsors.html +++ /dev/null @@ -1,20 +0,0 @@ -{% block subcontent %} -{% load static %} - -
    -
    -

    Thanks to our Sponsors

    -
    -

    - Our sponsors help us cover server costs from the large amount of traffic that we get. - Their help has allowed the site to stay up and running continuously as we keep developing - new features. -

    -
    -
    - - -
    -
    - -{% endblock %} diff --git a/tcf_website/templates/site/pages/department.html b/tcf_website/templates/site/pages/department.html index 5f671018d..3fdb03152 100644 --- a/tcf_website/templates/site/pages/department.html +++ b/tcf_website/templates/site/pages/department.html @@ -31,7 +31,7 @@

    {{ breadcrumbs.1.0 }}

    - Current Semester diff --git a/tcf_website/templates/site/partials/privacy_content.html b/tcf_website/templates/site/partials/privacy_content.html index cf32cc4e3..cadf7439d 100644 --- a/tcf_website/templates/site/partials/privacy_content.html +++ b/tcf_website/templates/site/partials/privacy_content.html @@ -1 +1,89 @@ -{% include "about/partials/privacy_content.html" %} +
    +

    theCourseForum Privacy Policy

    +

    We built theCourseForum to make it easy to share course information with your + fellow students. Our default privacy settings limit the use of your information.

    +

    theCourseForum's Privacy Policy is designed to help you understand how we + collect and use the personal information you decide to share, and help you make + informed decisions when using theCourseForum, located at www.theCourseForum.com + and its directly associated domains (collectively, "theCourseForum" or + "Website").

    +

    By using or accessing theCourseForum, you are accepting the + practices described in this Privacy Policy.

    +

    Disclaimer: Although this organization has members who are University of Virginia students and may have University employees associated or engaged in its activities and affairs, the organization is not a part of or an agency of the University. It is a separate and independent organization, which is responsible for and manages its own activities and affairs. The University does not direct, supervise or control the organization and is not responsible for the organization’s contracts, acts or omissions. +

    +
    +

    The Information We Collect

    +

    When you visit theCourseForum you provide us with two types of + information: personal information you knowingly choose to disclose that is + collected by us and Web Site use information collected by us as you interact + with our Web Site.

    +

    When you register with theCourseForum, you provide us with certain + personal information, such as your name, your email address, your major(s), + and any other personal or preference information that you provide to us.

    +

    When you enter theCourseForum, we collect your browser type and IP + address. This information is gathered for all theCourseForum visitors. In + addition, we store certain information from your browser using "cookies." A + cookie is a piece of data stored on the user's computer tied to information + about the user. We use session ID cookies to confirm that users are logged + in. These cookies terminate once the user closes the browser.

    +

    When you use theCourseForum, you set up your personal account. We collect this information so that we can provide you the service and offer personalized features. In most cases, we retain it so that, for instance, you can return to view prior reviews you have written. When you update information, we usually keep a backup copy of the prior version for a reasonable period of time to enable reversion to the prior version of that information.

    + +

    You post User Content (as defined in the theCourseForum Terms of Use on the Site at your own risk. Although we do not share your name, we cannot control the actions of other Users with whom you may choose to share your reviews and information. Therefore, we cannot and do not guarantee that User Content you post on the Site will not be viewed by unauthorized persons. We are not responsible for circumvention of any privacy settings or security measures contained on the Site. You understand and acknowledge that, even after removal, copies of User Content may remain viewable in cached and archived pages or if other Users have copied or stored your User Content.

    + +

    Any improper collection or misuse of information provided on theCourseForum is a violation of the theCourseForum Terms of Service and should be reported to + + support@theCourseForum.com. +

    + +

    By using theCourseForum, you are consenting to have your personal data transferred to and processed in the United States.

    + +

    Children Under Age 13

    +

    theCourseForum does not knowingly collect or solicit personal information from anyone under the age of 13 or knowingly allow such persons to register. If you are under 13, please do not attempt to register for theCourseForum or send any information about yourself to us, including your name or email address. No one under age 13 may provide any personal information to or on theCourseForum. In the event that we learn that we have collected personal information from a child under age 13 without verification of parental consent, we will delete that information as quickly as possible. If you believe that we might have any information from or about a child under 13, please contact us at + + support@theCourseForum.com. +

    + +

    Children Between the Ages of 13 and 18

    +

    We recommend that minors over the age of 13 ask their parents for permission before sending any information about themselves to anyone over the Internet.

    + +

    Use of Information Obtained by theCourseForum

    +

    When you register with theCourseForum, you create your own account. Your account information, as well as your name and email, are not displayed to other Users. We may occasionally use your name and email address to send you notifications regarding new services offered by theCourseForum that we think you may find valuable.

    + +

    Account information is used by theCourseForum primarily to be presented back to and edited by you when you access the service.

    + +

    theCourseForum may send you service-related announcements from time to time through the general operation of the service.

    + +

    theCourseForum may use information in your account without identifying you as an individual to third parties. We do this for purposes such as aggregating how many people in a class recommend a professor and personalizing advertisements and promotions so that we can provide you with theCourseForum.

    + +

    Sharing Your Information with Third Parties

    + +

    theCourseForum is about sharing information with fellow students while providing you with privacy settings that restrict other users from accessing your information. We do not provide contact information to third party marketers without your permission. We share your information with third parties only in limited circumstances where we believe such sharing is 1) reasonably necessary to offer the service, 2) legally required or, 3) permitted by you. For example:

    + +
      +
    • We may provide information to service providers to help us bring you the services we offer. Specifically, we may use third parties to facilitate our business, such as to host the service at a hosting facility for servers, to send out email updates about theCourseForum, to remove repetitive information from our user lists, or to provide search results or links (including sponsored links). In connection with these offerings and business operations, our service providers may have access to your personal information for use for a limited time in connection with these business activities. Where we utilize third parties for the processing of any personal information, we implement reasonable contractual and technical protections limiting the use of that information to theCourseForum-specified purposes.
    • +
    • We occasionally provide demonstration accounts that allow non-users a glimpse into the theCourseForum world. Such accounts have only limited capabilities (e.g., reviewing is disabled) and passwords are changed regularly to limit possible misuse.
    • +
    • We may be required to disclose user information pursuant to lawful requests, such as subpoenas or court orders, or in compliance with applicable laws. We do not reveal information until we have a good faith belief that an information request by law enforcement or private litigants meets applicable legal standards. Additionally, we may share account or other information when we believe it is necessary to comply with law, to protect our interests or property, to prevent fraud or other illegal activity perpetrated through theCourseForum service or using the theCourseForum name, or to prevent imminent bodily harm. This may include sharing information with other companies, lawyers, agents or government agencies. We may offer stores or provide services jointly with other companies on theCourseForum. You can tell when another company is involved in any store or service provided on theCourseForum, and we may share customer information with that company in connection with your use of that store or service.
    • +
    • If the ownership of all or substantially all of the theCourseForum business, or individual business units owned by theCourseForum, were to change, your user information may be transferred to the new owner so the service can continue operations. In any such transfer of information, your user information would remain subject to the promises made in any pre-existing Privacy Policy.
    • +
    + +

    Links

    +

    theCourseForum may contain links to other websites. We are of course not responsible for the privacy practices of other web sites. We encourage our users to be aware when they leave our site to read the privacy statements of each and every web site that collects personally identifiable information. This Privacy Policy applies solely to information collected by theCourseForum.

    + +

    Changing or Removing Information

    +

    Access and control over personal information on theCourseForum is readily available through the account editing tools. theCourseForum users may modify any of their account information at any time by logging into their account. Information will be updated immediately. Modified information may persist in backup copies for a reasonable period of time but will not be generally available to members of theCourseForum.

    + +

    Security

    +

    theCourseForum takes appropriate precautions to protect our users' information. Because email and instant messaging are not recognized as secure communications, we request that you not send private information to us by email or instant messaging services. If you have any questions about the security of theCourseForum Web Site, please contact us at + + support@theCourseForum.com. +

    + +

    Terms of Use, Notices and Revisions

    +

    Your use of theCourseForum, and any disputes arising from it, is subject to this Privacy Policy as well as our Terms of Use and all of its dispute resolution provisions including arbitration, limitation on damages and choice of law. We reserve the right to change our Privacy Policy and our Terms of Use at any time. Non-material changes and clarifications will take effect immediately, and material changes will take effect within 30 days of their posting on this site. If we make changes, we will post them and will indicate at the top of this page the policy's new effective date. If we make material changes to this policy, we will notify you here, by email, or through notice on our home page. We encourage you to refer to this policy on an ongoing basis so that you understand our current privacy policy. Unless stated otherwise, our current privacy policy applies to all information that we have about you and your account.

    + +

    Contacting the Website

    +

    If you have any questions about this privacy policy, please contact us at + + support@theCourseForum.com. +

    +
    diff --git a/tcf_website/templates/site/partials/terms_content.html b/tcf_website/templates/site/partials/terms_content.html index 7131c6307..b1bd4e3a1 100644 --- a/tcf_website/templates/site/partials/terms_content.html +++ b/tcf_website/templates/site/partials/terms_content.html @@ -1 +1,111 @@ -{% include "about/partials/terms_content.html" with privacy_url_name=privacy_url_name %} +
    +

    Welcome to theCourseForum, a service that connects you with the courses you + love. The theCourseForum service and network (collectively, "theCourseForum" or + "the Service") are operated by theCourseForum and its affiliates (collectively, + "us", or "we"). By accessing or using our web site at www.theCourseForum.com + (together the "Site"), you (the "User") signify that you have read, understand + and agree to be bound by these Terms of Use ("Terms of Use" or "Agreement"), + whether or not you are a registered member of theCourseForum.

    +

    We reserve the + right, at our sole discretion, to change, modify, add, or delete portions of + these Terms of Use at any time without further notice. If we do this, we will + post the changes to these Terms of Use on this page and will indicate at the top + of this page the date these terms were last revised. Your continued use of the + Service or the Site after any such changes constitutes your acceptance of the + new Terms of Use. If you do not agree to abide by these or any future Terms of + Use, do not use or access (or continue to use or access) the Service or the + Site. It is your responsibility to regularly check the Site to determine if + there have been changes to these Terms of Use and to review such changes.

    +

    Please read these terms of use carefully as they contain important + information regarding your legal rights, remedies and obligations. These + include various limitations and exclusions, and a dispute resolution clause + that governs how disputes will be resolved.

    +
    +

    Eligibility

    +

    Membership in the Service is void where prohibited. This Site is intended + solely for users who are thirteen (13) years of age or older, and users of + the Site under 18 who are currently in college. Any registration by, use of + or access to the Site by anyone under 13, or by anyone who is under 18 and + not in college, is unauthorized, unlicensed and in violation of these Terms + of Use. By using the Service or the Site, you represent and warrant that you + are 13 or older and in college, or else that you are 18 or older, and that + you agree to and to abide by all of the terms and conditions of this + Agreement.

    + +

    Registration Data; Account Security

    +

    In consideration of your use of the Site, you agree to (a) provide accurate, current and complete information about you as may be prompted by any registration forms on the Site ("Registration Data"); (b) maintain the security of your password and identification; (c) maintain and promptly update the Registration Data, and any other information you provide to us, to keep it accurate, current and complete; and (d) be fully responsible for all use of your account and for any actions that take place using your account.

    + +

    Proprietary Rights in Site Content; Limited License

    +

    All content on the Site and available through the Service, including but not limited to designs, text, graphics, pictures, information, and other files, and their selection and arrangement (the "Site Content"), are our proprietary property, or that of our users or our licensors with all rights reserved. No Site Content may be modified, copied, distributed, framed, reproduced, republished, downloaded, displayed, posted, transmitted, or sold in any form or by any means, in whole or in part, without our prior written permission, except that the foregoing does not apply to your own User Content (as defined below) that you legally post on the Site. Provided that you are eligible for use of the Site, you are granted a limited license to access and use the Site and the Site Content and to download or print a copy of any portion of the Site Content to which you have properly gained access solely for your personal, non-commercial use, provided that you keep all copyright or other proprietary notices intact. Except for your own User Content, you may not upload or republish Site Content on any Internet, Intranet or Extranet site or incorporate the information in any other database or compilation, and any other use of the Site Content is strictly prohibited. Such license is subject to these Terms of Use and does not include use of any data mining, robots or similar data gathering or extraction methods. Any use of the Site or the Site Content other than as specifically authorized herein, without prior written permission from us, is strictly prohibited and will terminate the license granted herein. Such unauthorized use may also violate applicable laws including without limitation copyright and trademark laws and applicable communications regulations and statutes. Unless explicitly stated herein, nothing in these Terms of Use shall be construed as conferring any license to intellectual property rights, whether by estoppel, implication or otherwise. This license is revocable at any time without notice and with or without cause.

    + +

    Trademarks

    +

    THECOURSEFORUM, THE THECOURSEFORUM, and other graphics, logos, designs, page headers, button icons, scripts and service names are trademarks, trademarks or trade dress of ours in the U.S. and/or other countries. Our trademarks and trade dress may not be used, including as part of trademarks and/or as part of domain names, in connection with any product or service in any manner that is likely to cause confusion and may not be copied, imitated, or used, in whole or in part, without the prior written permission from us.

    + +

    User Conduct

    +

    You understand that the Service and the Site are available for your personal, non-commercial use only. You represent, warrant and agree that no materials of any kind submitted through your account or otherwise posted, transmitted, or shared by you on or through the Service will violate or infringe upon the rights of any third party, including copyright, trademark, privacy, publicity or other personal or proprietary rights; or contain libelous, defamatory or otherwise unlawful material.

    + +

    In addition, you agree not to use the Service or the Site to:

    + +
      +
    • upload, post, transmit, share or otherwise make publicly available the names of anyone not listed as part of the provided faculty information on the Site without their explicit consent;
    • +
    • use the Service or the Site in any unlawful manner or in any other manner that could damage, disable, overburden or impair the Site;
    • +
    • use automated scripts to collect information from or otherwise interact with the Service or the Site;
    • +
    • upload, post, transmit, share, store or otherwise make available any content that we deem to be harmful, threatening, unlawful, defamatory, infringing, abusive, inflammatory, harassing, vulgar, obscene, fraudulent, invasive of privacy or publicity rights, hateful, or racially, ethnically or otherwise objectionable;
    • +
    • register for more than one User account, register for a User account on behalf of an individual other than yourself, or register for a User account on behalf of any group or entity;
    • +
    • impersonate any person or entity, or falsely state or otherwise misrepresent yourself;
    • +
    • upload, post, transmit, share or otherwise make available any unsolicited or unauthorized advertising, solicitations, promotional materials, "junk mail," "spam," "chain letters," "pyramid schemes," or any other form of solicitation;
    • +
    • upload, post, transmit, share, store or otherwise make publicly available on the Site any private information of any third party, including, without limitation, addresses, phone numbers, email addresses, Social Security numbers and credit card numbers;
    • +
    • solicit personal information from anyone under 18 or solicit passwords or personally identifying information for commercial or unlawful purposes;
    • +
    • upload, post, transmit, share or otherwise make available any material that contains software viruses or any other computer code, files or programs designed to interrupt, destroy or limit the functionality of any computer software or hardware or telecommunications equipment;
    • +
    • intimidate or harass another;
    • +
    • upload, post, transmit, share, store or otherwise make available content that would constitute, encourage or provide instructions for a criminal offense, violate the rights of any party, or that would otherwise create liability or violate any local, state, national or international law;
    • +
    • use or attempt to use another's account, service or system without authorization from us, or create a false identity on the Service or the Site;
    • +
    • upload, post, transmit, share, store or otherwise make available content that, in our sole judgment, is objectionable or which restricts or inhibits any other person from using or enjoying the Site, or which may expose us or our users to any harm or liability of any type;
    • +
    • Without limiting any of the foregoing, you also agree to abide by our theCourseForum Code of Conduct that provides further information regarding the authorized conduct of users on theCourseForum.
    • +
    + +

    User Content Posted on the Site

    +

    You are solely responsible for the reviews and other content that you upload, publish or display (hereinafter, "post") on or through the Service or the Site, (collectively the "User Content"). You may not post, transmit, or share User Content on the Site or Service that you did not create or that you do not have permission to post. You understand and agree that we may, but are not obligated to, review the Site and may delete or remove (without notice) any Site Content or User Content in our sole discretion, for any reason or no reason, including without limitation User Content that in our sole judgment violates this Agreement or the theCourseForum Code of Conduct, or which might be offensive, illegal, or that might violate the rights, harm, or threaten the safety of users or others. You are solely responsible at your sole cost and expense for creating backup copies and replacing any User Content you post or store on the Site or provide to us.

    + +

    When you post User Content to the Site, you authorize and direct us to make such copies thereof as we deem necessary in order to facilitate the posting and storage of the User Content on the Site. By posting User Content to any part of the Site, you automatically grant, and you represent and warrant that you have the right to grant, to us an irrevocable, perpetual, non-exclusive, transferable, fully paid, worldwide license (with the right to sublicense) to use, copy, publicly perform, publicly display, reformat, translate, excerpt (in whole or in part) and distribute such User Content for any purpose on or in connection with the Site or the promotion thereof, to prepare derivative works of, or incorporate into other works, such User Content, and to grant and authorize sublicenses of the foregoing. You may remove your User Content from the Site at any time. If you choose to remove your User Content, the license granted above will automatically expire, however you acknowledge that we may retain archived copies of your User Content.

    + +

    Third Party Websites and Content

    +

    The Site may contain (or you may be sent through the Site or the Service) links to other web sites as well as content or items belonging to or originating from third parties (the Content). Such Third Party Sites or Content are not investigated, monitored or checked for accuracy, appropriateness, or completeness by us, and we are not responsible for any Third Party Sites accessed through the Site or Content posted on, available through or installed from the Site, including without limitation the content, accuracy, offensiveness, opinions, reliability, privacy practices or other policies of or contained in the Third Party Sites or Content. Inclusion of, linking to or permitting the use of any Third Party Site or Content does not imply approval or endorsement thereof by us. If you decide to leave the Site and access the Third Party Sites or Content, you do so at your own risk and you should be aware that our terms and policies no longer govern. You should review the applicable terms and policies, including privacy and data gathering practices, of any site to which you navigate from the Site.

    + +

    User Disputes

    +

    You are solely responsible for your interactions with other theCourseForum users. We reserve the right, but have no obligation, to monitor disputes between you and other users.

    + +

    Privacy

    +

    We care about the privacy of our users. Click here to view the theCourseForum's Privacy Policy. By using the Site or the Service, you are consenting to have your personal data transferred to and processed in the United States.

    + +

    Disclaimers

    +

    We are not responsible or liable in any manner for any User Content posted on the Site or in connection with the Service, whether posted or caused by users of the Site, by theCourseForum, by third parties or by any of the equipment or programming associated with or utilized in the Site or the Service. Although we provide rules for user conduct and postings, we do not control and are not responsible for what users post, transmit or share on the Site and are not responsible for any offensive, inappropriate, obscene, unlawful or otherwise objectionable content you may encounter on the Site or in connection with any User Content or other Content. We are not responsible for the conduct, whether online or offline, of any user of the Site or Service.

    + +

    The Site and the Service may be temporarily unavailable from time to time for maintenance or other reasons. We assume no responsibility for any error, omission, interruption, deletion, defect, delay in operation or transmission, communications line failure, theft or destruction or unauthorized access to, or alteration of, User communications. We are not responsible for any technical malfunction or other problems of any telephone network or service, computer systems, servers or providers, computer or mobile phone equipment, software, failure of email or players on account of technical problems or traffic congestion on the Internet or at any Site or combination thereof, including injury or damage to User's or to any other person's computer, mobile phone, or other hardware or software, related to or resulting from using or downloading materials in connection with the Web and/or in connection with the Service . Under no circumstances will we be responsible for any loss or damage, including any loss or damage to any User Content or personal injury or death, resulting from anyone's use of the Site or the Service, any User Content or Content posted on or through the Site or the Service or transmitted to Users, or any interactions between users of the Site, whether online or offline.

    + +

    The site, the service and the site content are provided "as-is" and we disclaim any and all representations and warranties, whether express or implied, including without limitation implied warranties of title, merchantability, fitness for a particular purpose or non-infringement. We cannot guarantee and do not promise any specific results from use of the site and/or the service. We do not represent or warrant that software, content or materials on the site or the service are accurate, complete, reliable, current or error-free or that the site or service its servers are free of viruses or other harmful components. Therefore, you should exercise caution in the use and downloading of any such software, content or materials and use industry-recognized software to detect and disinfect viruses. Without limiting the foregoing, you understand and agree that you download or otherwise obtain content, material, data or software from or through the service at your own discretion and risk and that you will be solely responsible for your use thereof and any damages to your mobile device or computer system, loss of data or other harm of any kind that may result.

    + +

    We reserve the right to change any and all content, software and other items used or contained in the Site and any Services offered through the Site at any time without notice. Reference to any products, services, processes or other information, by trade name, trademark, manufacturer, supplier or otherwise does not constitute or imply endorsement, sponsorship or recommendation thereof, or any affiliation therewith, by us.

    + +

    Limitation on Liability

    +

    In no event will we or our directors, employees or agents be liable to you or any third person for any indirect, consequential, exemplary, incidental, special or punitive damages, including for any lost profits or lost data arising from your use of the site or the service or any of the site content or other materials on, accessed through or downloaded from the site, even if the we are aware or has been advised of the possibility of such damages. Notwithstanding anything to the contrary contained herein, our liability to you for any cause whatsoever, and regardless of the form of the action, will at all times be limited to the amount paid, if any, by you to us for the service during the term of membership, but in no case will our liability to you exceed $1000. You acknowledge that if no fees are paid to us for the service, you shall be limited to injunctive relief only, unless otherwise permitted by law, and shall not be entitled to damages of any kind from us, regardless of the cause of action.

    + +

    Certain state laws do not allow limitations on implied warranties or the exclusion or limitation of certain damages. If these laws apply to you, some or all of the above disclaimers, exclusions or limitations may not apply to you, and you may have additional rights.

    + +

    Termination

    +

    We may terminate your membership, delete your account and any content or information that you have posted on the Site and/or prohibit you from using or accessing the Service or the Site (or any portion, aspect or feature of the Service or the Site) for any reason, or no reason, at any time in its sole discretion, with or without notice, including without limitation if it believes that you are under 13, or under 18 and not in college.

    + +

    Governing Law; Venue and Jurisdiction

    +

    By visiting or using the Site and/or the Service, you agree that the laws of the State of Virginia, without regard to principles of conflict of laws, will govern these Terms of Use and any dispute of any sort that might arise between you and us or any of our affiliates. With respect to any disputes or claims not subject to arbitration (as set forth below), you agree not to commence or prosecute any action in connection therewith other than in the state and federal courts of, and you hereby consent to, and waive all defenses of lack of personal jurisdiction and forum non conveniens with respect to, venue and jurisdiction in the state and federal courts of.

    + +

    Indemnity

    +

    You agree to indemnify and hold us, our subsidiaries and affiliates, and each of our directors, officers, agents, contractors, partners and employees, harmless from and against any loss, liability, claim, demand, damages, costs and expenses, including reasonable attorney's fees, arising out of or in connection with any User Content you post or share on or through the Site, your use of the Service or the Site, your conduct in connection with the Service or the Site or with other users of the Service or the Site, or any violation of this Agreement or of any law or the rights of any third party.

    + +

    Submissions

    +

    You acknowledge and agree that any questions, comments, suggestions, ideas, feedback or other information about the Site or the Service ("Submissions"), provided by you to us are non-confidential and shall become our sole property. We shall own exclusive rights, including all intellectual property rights, and shall be entitled to the unrestricted use and dissemination of these Submissions for any purpose, commercial or otherwise, without acknowledgment or compensation to you.

    + +

    CIO Disclaimer

    +

    Although this organization has members who are University of Virginia students and may have University employees associated or engaged in its activities and affairs, the organization is not a part of or an agency of the University. It is a separate and independent organization, which is responsible for and manages its own activities and affairs. The University does not direct, supervise or control the organization and is not responsible for the organization’s contracts, acts or omissions.

    +

    Other

    +

    These Terms of Use constitute the entire agreement between you and us regarding the use of the Site and/or the Service, superseding any prior agreements between you and us relating to your use of the Site or the Service. Our failure to exercise or enforce any right or provision of these Terms of Use shall not constitute a waiver of such right or provision in that or any other instance. If any provision of this Agreement is held invalid, the remainder of this Agreement shall continue in full force and effect. If any provision of these Terms of Use shall be deemed unlawful, void or for any reason unenforceable, then that provision shall be deemed severable from these Terms of Use and shall not affect the validity and enforceability of any remaining provisions.

    +
    From ceeb4b3fab2c2d8fa38abeba8ccbd5299cdf927f Mon Sep 17 00:00:00 2001 From: Jay-Lalwani Date: Sat, 7 Feb 2026 19:09:47 -0500 Subject: [PATCH 4/8] fix(search): hide filters on mobile --- tcf_website/static/css/site/components/search_bar.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tcf_website/static/css/site/components/search_bar.css b/tcf_website/static/css/site/components/search_bar.css index 7cfa08c08..04d9ba7d8 100644 --- a/tcf_website/static/css/site/components/search_bar.css +++ b/tcf_website/static/css/site/components/search_bar.css @@ -219,6 +219,8 @@ /* Responsive adjustment */ @media (max-width: 768px) { + .search-bar__filter-trigger { display: none; } + .search-filters { position: fixed; top: 4rem; From 122b3435b3778a33a30814d205a8f3c1afb504ae Mon Sep 17 00:00:00 2001 From: Jay-Lalwani Date: Sat, 7 Feb 2026 19:41:26 -0500 Subject: [PATCH 5/8] fix(ui) --- .../static/css/site/components/header.css | 33 ++++++++++++++++++ .../static/css/site/components/search_bar.css | 34 +++++++++++++++++-- tcf_website/static/css/site/pages/landing.css | 33 +++--------------- tcf_website/static/css/site/pages/search.css | 2 +- tcf_website/templates/site/pages/landing.html | 9 +---- 5 files changed, 70 insertions(+), 41 deletions(-) diff --git a/tcf_website/static/css/site/components/header.css b/tcf_website/static/css/site/components/header.css index d8c700a91..7c5295405 100644 --- a/tcf_website/static/css/site/components/header.css +++ b/tcf_website/static/css/site/components/header.css @@ -46,6 +46,39 @@ margin: 0 auto; } +@media (max-width: 767px) { + :root { + --mobile-header-offset: 6.5rem; + } + + .header { + height: auto; + min-height: 4rem; + padding-top: var(--space-2); + padding-bottom: var(--space-2); + } + + .header__inner { + flex-wrap: wrap; + row-gap: var(--space-2); + } + + .header__logo { + order: 1; + } + + .header__actions { + order: 2; + margin-left: auto; + } + + .header .search-bar-container { + order: 3; + flex: 1 0 100%; + width: 100%; + } +} + /* ===== LOGO ===== */ diff --git a/tcf_website/static/css/site/components/search_bar.css b/tcf_website/static/css/site/components/search_bar.css index 04d9ba7d8..a1e85c73c 100644 --- a/tcf_website/static/css/site/components/search_bar.css +++ b/tcf_website/static/css/site/components/search_bar.css @@ -8,6 +8,7 @@ display: flex; align-items: center; flex: 1; + min-width: 0; max-width: 24rem; margin: 0 var(--space-4); } @@ -33,9 +34,13 @@ height: 2.25rem; padding-left: var(--space-9); padding-right: 5rem; /* Space for actions group */ + appearance: none; + -webkit-appearance: none; font-size: var(--text-sm); color: var(--fg); + -webkit-text-fill-color: var(--fg); + caret-color: var(--fg); background-color: var(--bg-muted); border: 1px solid transparent; border-radius: var(--radius-lg); @@ -43,10 +48,15 @@ transition: all var(--duration-fast) var(--ease-default); } +.search-bar__input::placeholder { + color: var(--fg-subtle); +} + .search-bar__input:focus { background-color: var(--bg); border-color: var(--primary); box-shadow: 0 0 0 2px var(--color-primary-100); + -webkit-text-fill-color: var(--fg); outline: none; } @@ -218,17 +228,35 @@ } /* Responsive adjustment */ -@media (max-width: 768px) { +@media (max-width: 767px) { .search-bar__filter-trigger { display: none; } + .header .search-bar { + max-width: none; + margin: 0; + } + + .header .search-bar__input { + font-size: var(--text-base); + padding-right: 3.5rem; + } + + .header .search-bar__actions-group { + right: var(--space-1-5); + } + + .header .search-bar__filter-trigger { + display: none; + } + .search-filters { position: fixed; - top: 4rem; + top: var(--mobile-header-offset, 4rem); left: 0; right: 0; width: 100%; max-width: none; - height: calc(100vh - 4rem); + height: calc(100vh - var(--mobile-header-offset, 4rem)); border-radius: 0; overflow-y: auto; } diff --git a/tcf_website/static/css/site/pages/landing.css b/tcf_website/static/css/site/pages/landing.css index 30c351ca1..346a67a86 100644 --- a/tcf_website/static/css/site/pages/landing.css +++ b/tcf_website/static/css/site/pages/landing.css @@ -113,42 +113,17 @@ /* ===== FEATURES SECTION ===== */ .features { - padding: var(--space-24) var(--space-4); -} - -.features__header { - text-align: center; - max-width: 48rem; - margin: 0 auto var(--space-16); + padding: var(--space-16) var(--space-4) var(--space-24); } .features__subtitle { - font-size: var(--text-sm); + text-align: center; + font-size: var(--text-lg); font-weight: var(--font-semibold); color: var(--primary); text-transform: uppercase; letter-spacing: var(--tracking-wide); - - margin-bottom: var(--space-3); -} - -.features__title { - font-family: var(--font-display); - font-size: var(--text-4xl); - font-weight: var(--font-normal); - - margin-bottom: var(--space-4); -} - -@media (min-width: 768px) { - .features__title { - font-size: var(--text-5xl); - } -} - -.features__description { - font-size: var(--text-lg); - color: var(--fg-muted); + margin: 0 auto var(--space-8); } .features__grid { diff --git a/tcf_website/static/css/site/pages/search.css b/tcf_website/static/css/site/pages/search.css index 662a2faa3..bf5ac0656 100644 --- a/tcf_website/static/css/site/pages/search.css +++ b/tcf_website/static/css/site/pages/search.css @@ -5,7 +5,7 @@ .search-results-header { padding: var(--space-8) 0; - background: linear-gradient(135deg, var(--bg) 0%, var(--bg-muted) 100%); + background: var(--bg); border-bottom: 1px solid var(--border); } diff --git a/tcf_website/templates/site/pages/landing.html b/tcf_website/templates/site/pages/landing.html index 6bb2d7480..15cdadf4d 100644 --- a/tcf_website/templates/site/pages/landing.html +++ b/tcf_website/templates/site/pages/landing.html @@ -35,14 +35,7 @@

    -
    -
    Features
    -

    Everything you need to plan your semester

    -

    - We've got you covered every step of the way. -

    -
    - +
    Features
    From 3ef2b2a5e6e76922cf1cc5d71b9deac6a4416c90 Mon Sep 17 00:00:00 2001 From: Jay-Lalwani Date: Sat, 7 Feb 2026 20:15:12 -0500 Subject: [PATCH 6/8] fix(ui): mobile dropdown --- .../static/css/site/components/header.css | 89 ++++++++++--------- .../templates/site/components/_header.html | 9 +- 2 files changed, 57 insertions(+), 41 deletions(-) diff --git a/tcf_website/static/css/site/components/header.css b/tcf_website/static/css/site/components/header.css index 7c5295405..aade6154c 100644 --- a/tcf_website/static/css/site/components/header.css +++ b/tcf_website/static/css/site/components/header.css @@ -8,16 +8,17 @@ top: 0; left: 0; right: 0; - + height: 4rem; padding: 0 var(--space-4); - + background-color: var(--bg); border-bottom: 1px solid var(--border); - + z-index: var(--z-sticky); - transition: background-color var(--duration-normal) var(--ease-default), - border-color var(--duration-normal) var(--ease-default); + transition: + background-color var(--duration-normal) var(--ease-default), + border-color var(--duration-normal) var(--ease-default); } @media (min-width: 640px) { @@ -32,7 +33,6 @@ } } - /* ===== HEADER CONTAINER ===== */ .header__inner { @@ -40,7 +40,7 @@ align-items: center; justify-content: space-between; gap: var(--space-4); - + height: 100%; max-width: var(--size-max); margin: 0 auto; @@ -79,14 +79,13 @@ } } - /* ===== LOGO ===== */ .header__logo { display: flex; align-items: center; gap: var(--space-3); - + text-decoration: none; color: var(--fg); flex-shrink: 0; @@ -114,7 +113,6 @@ } } - /* ===== NAVIGATION ===== */ .header__nav { @@ -132,17 +130,18 @@ .header__nav-link { display: flex; align-items: center; - + padding: var(--space-2) var(--space-3); - + font-size: var(--text-sm); font-weight: var(--font-medium); color: var(--fg-muted); text-decoration: none; - + border-radius: var(--radius-lg); - transition: color var(--duration-fast) var(--ease-default), - background-color var(--duration-fast) var(--ease-default); + transition: + color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); } .header__nav-link:hover { @@ -156,7 +155,6 @@ background-color: var(--bg-muted); } - /* ===== ACTIONS ===== */ .header__actions { @@ -166,7 +164,6 @@ flex-shrink: 0; } - /* ===== MODE TOGGLE ===== */ .mode-toggle { @@ -181,15 +178,15 @@ display: flex; align-items: center; justify-content: center; - + padding: var(--space-1) var(--space-3); - + font-size: var(--text-xs); font-weight: var(--font-medium); color: var(--fg-muted); text-decoration: none; border-radius: var(--radius-md); - + transition: all var(--duration-fast) var(--ease-default); } @@ -205,25 +202,25 @@ font-weight: var(--font-semibold); } - /* ===== THEME TOGGLE ===== */ .theme-toggle { display: flex; align-items: center; justify-content: center; - + width: 2.25rem; height: 2.25rem; - + color: var(--fg-muted); background: transparent; border: none; border-radius: var(--radius-lg); - + cursor: pointer; - transition: color var(--duration-fast) var(--ease-default), - background-color var(--duration-fast) var(--ease-default); + transition: + color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); } .theme-toggle:hover { @@ -253,7 +250,6 @@ display: none; } - /* ===== USER MENU ===== */ .header__user { @@ -264,20 +260,21 @@ display: flex; align-items: center; gap: var(--space-2); - + padding: var(--space-1-5) var(--space-3); - + font-size: var(--text-sm); font-weight: var(--font-medium); color: var(--fg); - + background: transparent; border: 1px solid var(--border); border-radius: var(--radius-lg); - + cursor: pointer; - transition: border-color var(--duration-fast) var(--ease-default), - background-color var(--duration-fast) var(--ease-default); + transition: + border-color var(--duration-fast) var(--ease-default), + background-color var(--duration-fast) var(--ease-default); } .header__user-trigger:hover { @@ -288,12 +285,12 @@ .header__user-avatar { width: 1.5rem; height: 1.5rem; - + background-color: var(--primary); color: var(--primary-fg); - + border-radius: var(--radius-full); - + display: flex; align-items: center; justify-content: center; @@ -301,22 +298,21 @@ font-weight: var(--font-semibold); } - /* ===== MOBILE MENU TRIGGER ===== */ .header__mobile-trigger { display: flex; align-items: center; justify-content: center; - + width: 2.25rem; height: 2.25rem; - + color: var(--fg); background: transparent; border: none; border-radius: var(--radius-lg); - + cursor: pointer; } @@ -335,6 +331,19 @@ } } +/* ===== MOBILE NAV (toggled by hamburger) ===== */ + +@media (max-width: 767px) { + .header.is-mobile-open .header__nav { + display: flex; + flex-direction: column; + order: 4; + flex: 1 0 100%; + padding-bottom: var(--space-2); + border-top: 1px solid var(--border); + padding-top: var(--space-2); + } +} /* ===== TRANSPARENT HEADER (for landing) ===== */ diff --git a/tcf_website/templates/site/components/_header.html b/tcf_website/templates/site/components/_header.html index 852c8b55a..c35055e68 100644 --- a/tcf_website/templates/site/components/_header.html +++ b/tcf_website/templates/site/components/_header.html @@ -116,7 +116,7 @@ {% endif %} -
    + {% if forloop.counter == 3 and not forloop.last %} + {% include "site/components/_in_feed_ad.html" %} + {% endif %} {% endfor %} {% if paginated_reviews.has_other_pages %} diff --git a/tcf_website/templates/site/pages/search.html b/tcf_website/templates/site/pages/search.html index 52bf5549d..68bac1941 100644 --- a/tcf_website/templates/site/pages/search.html +++ b/tcf_website/templates/site/pages/search.html @@ -173,6 +173,9 @@

    {% endfor %}

    + {% if forloop.counter == 2 and not forloop.last %} + {% include "site/components/_in_feed_ad.html" %} + {% endif %} {% endfor %} {% include "site/components/_pagination.html" with paginated_items=page_obj aria_label="Course pagination" %} From 43ac04ef621cfb2904069a176cfd5c3dcefa4338 Mon Sep 17 00:00:00 2001 From: Lucas Kohler Date: Wed, 11 Feb 2026 23:12:54 -0500 Subject: [PATCH 8/8] [fix]?: temp get rid of serif font until better is found --- tcf_website/static/css/site/tokens.css | 2 +- tcf_website/templates/site/base.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tcf_website/static/css/site/tokens.css b/tcf_website/static/css/site/tokens.css index fee42898d..20fdfe3ea 100644 --- a/tcf_website/static/css/site/tokens.css +++ b/tcf_website/static/css/site/tokens.css @@ -92,7 +92,7 @@ /* ===== TYPOGRAPHY ===== */ --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - --font-display: 'Instrument Serif', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif; + --font-display: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; /* Font sizes */ diff --git a/tcf_website/templates/site/base.html b/tcf_website/templates/site/base.html index 97cfad5dc..d352de4f3 100644 --- a/tcf_website/templates/site/base.html +++ b/tcf_website/templates/site/base.html @@ -31,8 +31,8 @@ - - + +

    xe6NT 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%0DTlw?ItKYS=9J(}+moI(&x2<7g7 z8?3;2IP&*rvjy9YM?y@$GR0v-yFnmJ?GBC;9hTjXntqY~PIP-0l@O^Rc{2#1;*#9yPU!MRf7#(nUoacNj6lnmmI4jhQ4S@_F?8cH_5 z^RwCG>Zj0xBB6jTpPIG{K$lx@GrD6PKVe_+J)X%kYGg1ES2YM{O8PMWP2ng{GU*01 zMH28@0(iDZ8ug>8P^%xDnp4&NYO}0#%)NF42Upk6&y@EGW9Bk=X(K={sTN2&hIy`w z$`L|OfwlBs5<$ccU?fRLm4)fESyQ2nn*cM{wSa*iDm-8$F(vamRHplrzdD0BgY_WZ zY_|?~vgZ|tAZPhZ78@MZyn40!N#F1Mvd>mTW3f|(#pP*SmXw6J@h*aU?KIu#+clup zFHeIbOeJ|N)anUk3-?y)){JR3mpsy2W0}Qn?9nEeBhW ziPPSNN-W~(vo&DH)T_Pq`bl(R{4XP3H!Q!7Fizt2D@<;%MZF0}{tpLu;BP$XO_5E& z5PTT-{}(zRPWZ3*Ti%z9=^x*fj2dZ!|EHgpvB>VfMbiHx#`u-9D-eLYyyigzPrDA- zpr%6)6oX>a2r_;RiE#&vyA1izus64iXD=h->gxc7__NuDM6DG7dUg&ffRLvOfa{ZR75kKpi_smEtVOZ4$ZSYd zuddy8yg5d8R_-g(xNk}+iyzBDoO59=Wb0)BzIS3W$Xh^C8yy0E9Vm!fz{ zxjUz74m*1!CLmx*@c`#SM8Ask@9*EFKfACbp*UwC9Xm&U%ntG(au4N6l8=-zb(xWk z?U0qfgm^$G#WkzZl@0=IrNkIr0Iq|Yr!R3N4`j3)b&l98aVMe0@CN z+VAL`0j8rKU9sB;fNiI*N1z`0NVdDtsBL5S)Z-riQ!XJHbOKj~{9=oz@nujp2(a~) z0O49$2iB)@89p5R{s`_0N!f=U&;Uk{arH9OzVBtVPVqW;7>EJ1L1uPSYuPiAx|*`gtC_VqG|o_<2aoKL9Yx{D z!e5oYD&gB(KbVj^+eQOA)s0JXVu@beetzs(S_~};OzEC4g#%XTQlrie45*U%mc0x( z1exp?<{;k-xT{Y89R*m${L1V&zq<);903ntd*y*Z5OFiAMLw)1VRQ zy<+43k2yIH?Qh;nIT?4=)$gT*v{fVIhZ}?1-5r{b{+8`NYI0*wnYJ5e0irv2S%TA? zD>d5QTEDm~_IxzN>JC^45rp?yHvH~g-Nhiwcf+APZxU5tP?PTkOEHqhN(3XD0PFe~ zKp09)Pw-$?iaD=wS0EeIkBj;ud73*U4LdOsL>fuL4dT<~E$XcfOwj_v)tbEGVg(K{ z_9nzuE^Ysj-6>NmA#r_Bt&9ekjgqNSj81x?uf|)TYfMj}P{$j=JIm6m-ryHv&gd2` z{3nk>87$8d|ES^LjO2#+XIaoL>N4)k&dm6}=*Pexcg=QQnsVk0IFC8yomfA0JKeQB z*bp;CRU6eX(aUtG9JoB@ZeW{z=XRQh1iXp&s^c=ISk`10Leh>WW5QL))45Pllxs$i zOO>dU?SngO^l5obntXZX)Zp@GA+clNgC~sGM3zkiV^`s{hui~>Vl}ft;m8}_GUg}z z^EA~7O;Y#Wn}MQR%#D~xz6kwRk!)U`@Gi=6Q7r3?q@>IpmW^$vT-X{u=CG&C`A}T% z`IqXS{COq()=v5GAj0`ta2`r$y#^3=1U4URI2ZsdbLfN~QoDp828DW}8y+(y#hLzA ze+JJLtYF^DMv{MN=BZy$^W(ZH6xE-$0pDJp`TL9~1*zIFo=_IV$)^!xr@HFS%ztmw zDsza6kr?wiXVnOMgx9lV9eiaf8o%}v5_7oDc|14iKxu<}JbTRKvZDKU3I=Po!g%*z zHWmPdm%%tkZz9$Orw$^{biWXwH0RW_8EOWIFsP6*5m2l`S#o8`OW+2~n1a)&g5RIr zRVfD>c=gU0+AlwyJ^?GA*=7+a?T1gg-^slcs3!eR(SKQZXn zYNBKZVPQKxMtiQg{HLcUhIH#mSFs-L;Z3y@ zEtktXs#Cu$A*#h?p7oaRXv8Rh^^gToBLKDfZ1$6m|8+32D&p(TZ!}d=%ZGm1Zsjub zbuTMKf)tJqN4W=cQTlV{C4C>!b=!x`a-zhhU@1W zLC{$F3a9ahKuA#{Qhf{L69xg&oIBDr3;EmVNk63JUny>9u0ekIRNu{|)w$%pcs$~? zOX8AK+>%#XTt(J2Q9l%Y&{pT#wD(C7qE64a1Zu;myvY2Tk;1QfrxvR0~ir zAIHS_IoxhSB0L}6Rv@dR6ab<`UBV8_v(hz#7Cxk3G{Q8F{PlWeG7ZQv8L!T|__BQD z8_FD^8l5LM@H@`RVFpMC5hPRiY7REvCe>69T_{QUfvpjLHd~2ZhTgoBAnx&}qETi1 zsy>}8$H-|F!PHl>#p*dSjE5sBO9thz8QAb$j>x7M)n*N?TV2Px^4aXGB5q7Kr^d>% zvPU#WxfmGkN&{seC%a>@!ah>j^3obEtNyl zjvs-eu&4~Y>ukmt_8}fZi-D+u*0p=m-F((ykb3wQ<3(U)yA04*F!I2qx~}0+Q&eN5 zX=kyh61Iu`DOiKMff5w##eq<0Y(^#aZA1BK zxvg{jvges2=M6yx6)u!Q_#Mx+qiZ*lS)l@c)}82J$RWpgSmT6Q0;81h3~R*A6WXCv z#82bsDe+i|9@S*Adr5PYHqau}_OZ{Cb1sha7ZAX1z$vJ~AK7X^iF#`Hkt|7o*QiZ_6&q_1>g)IS0 zABe(U70c?D0EV~2u#}(Ru?5-gt%fHirGH2>a4lVygK2b%baY5XW*{jZ$rqsOXWN3BwaOng4rVris5h1)GRIC~CFN*)pJPBzMqkzE7FT8rR! zRoKFFTOdUdRBR~yT?uJAHtKK|^p&Syi}rxkX%AW2;+y0cv`Ro_xDMyQMoKpKU|#LU z&BlC+gC<8#+ld8~8o|+?GX_;g0LX{^+!0;UWpx=>#IKewM({m8X@Gc+l=y5XXb9Jf zWgCmAi+pmS&<^7H6;a$8zVt@moro~W%A1$pWO}{h^nNy**`-G1%*Mkv*!6Ixm=NCC zS~1!Tms+*cbeYIc#$QoOjhAYv#MX}-MXJ2S=x_^8o37b0}x}R0&X#I>CdKX+;Z%VZL%-kC&EW5dDwL?(y&g-{_x2iiK_SUQt{y$Lz zNr@T7T~dc5@smxU!Y7FE8k}8DQj{EVwyB0^qrj<}mE9O+bt@1zTJZpZ-^i`U%J-Z? z;v*fr>tYPSFS?T%9`YKW+{|A_Qp@JN0MO)PknX#6VE0b>-i@6C&oE&p<>~LLwpCx{ zu2LyirS^e&QEX2;u@z>>9aVLxE$~xk8BKZMubF_xDz9TF;fh16+JGm;uLUHOy5vZQ zAM9ve5=`=t80)&HG1aJAv9qP~$&#+X&e%VfxXNUSJMGYki#>!8FJ>plc>HjUwD??^nV5ne;~+aD@4apXoT;~^ zrJvL`)|rOB@u%|~H^=GWBM-Sej%ZAR3ca)nx#ofmYJI@sDfS`tL;WqGhlXhd_FPz2 z_9v^lrH^iuq)q|*p5UllbnAZEbSHL*VRZcW1qciAAS}f6qaVpu2{0A%t$LrObvv-jyP|&}umBxFg-msoH9`a?xS@Nnja|M{VTA{wrzA^&veZ8S))gL`H;ZFwJWqFIqPrg2-<1v2 zJg5W6Og4Z#BD8DTh01yYHnbV41YxsgKd^im&B$-&!3!Tjy0=V*>#5!yH2fIz+tL0` zhTQatg?+ae?6V&~n>7y6cl@W<3ik6e(|;SO%sWs4y8@8$5(&BPip{g+$Dq4ns_?#L ztZ}1LcV=gtF1vji!_8oRB_Qj_(sl%-33kyphVc9MrC?z+s7ZGJ$s=lGI2^GC(w3<~@{NDvmz*PpJH z<O!jY*zhma} zL&r;7y!I1}jWN?3iQ)+35Ln5Z|AIznyDTsgd^b*P> z#+|^(gAo%E-8OFs*V%T$b+N7h^m_wYocGv{{HdLJBB=^oR<)_ZDBX+vP6e@@XxX+_ z!7g_m*%wwybETu83WQ+c6ctsf!i$;o9~3w1;y@sjV1%G&KWZrm;hS^4pR)_Q&# zC1%r-leb^0T03c%>OrV48sv?tj4(?+Yi+x@M^XjS*Hb&f=ohCIt2*cM2ETRx>XnB$AExAL&=x?CEGtz{n>rmz@?-ijTs=G&s*J}g z{$cKx02qL0$&|oW#dZ-jfge*7AGE~f+dd>McpBpQ0|`k8xo%&I+-sFunSCY*Qe2 zhmk85j2KOleZLpn&5{T+RA8GpHi^7&nt!yMYsI`}Xtb7krQ9iGS6&ZpF-Ub5*)93y zN;vy)%M^QQP?&-7&&<_~M*}0ni)P*wESz(Z*pQwg7B}cs;sMG=npfoHaE>6RP?(}F z`AF(CecueAO>mtBxUZ_E9GuW?LLS=X6yF2{^3~$U$1glAc?GA(X(PW<6&Euu?W`xhJ>kRNYFP}2SQJe!$>{M6FtJRI*g|u#JNIwdWz997M&`KQ%PuV0wtwe>nwUP~9)}mY;PO6pMycI_ zM+|pT&uQPxfl&dOu_K7r9;h>N)y&#hsM;mq#twI@>N2}`%{%a453gWWJSNqAaEcGl zqfnzb9dEwB#NU;+`I56+$Znj&nSJxQ4uLV^AeVRYsMd{9@?$S zp`MS1gI70g{k5WL3MfkjHWfUwe)!f0R`AjlpfZ+SeCc*%hnwXU#p_ya@jWN z{#WL198&izy7=0Y{Ko}TVKF0;@%(kKkc|OP0QxXiZ$By_)74N|UkMyD zj&BRW=Dt1-j>0DB6s3yB8?Dp8Y-5_r1Y2opNBZCF8lEP3$^sY{cnp`Y0Wzr4ufS_S zXmo%Tf|fyw?h`?<;su+32vQ*2Fq{5vD=Qi;YfyZ#lAGrj9<8KdcItjvz0U{oK_K(Y z88-&sGP&?FMIOjuFwvf1r5>T-8uKe28_@4T8TLAa_d3h{(-xG_?ZB+H^VsYYy0@8& zi9_;;w#kpzw~ol~cI@-Ays0XfKAhy0J^YkU>C%ttm4(jhY-|eHl3{4PlMUh$8z98t zg;;4FYvgm84zYg6G+x!z8RVE%I4$*R)W2?--d-)L*K+dj6;5U&mll5Ck?JgK7^-oq z-^^)}F?6Qrp`f@3&-Eo0E&70<3xX&TLsW_nwL;dKqu+$;_((uD{emeAI-|ahTg+Uh zG+g8640Sm+T2~pJj+8eR;0vd0X%{16Q!C@6DlQha-aRKCnrxJjCVb9kOca;* z?igv8QB*Qfjww77_`;v`*gOD@%symFgip!aVssZxxA5oX(j{-?=FmA_yR!Qu8xTzB zJG5&9FSi02%6#`9M`$T)4jJ9=5H0dg=e1APHUGA>^7OB|LI#n-oClIdd8S@XhN6~siH@TJs_#kWvN!r8w-3spRLu9YS^RWc!$B=eq#=t9>KD`A zM&N#Lg~Qb{!s{{sr?j(lv08Qwb>>j|)Zo=ApO~r_#NwRXicm5czHn{Y)$pfDcv z*S7JJh4K8I%{+|6Qi8lV#pe{&aWJ`I<&Fw4fcGJZ$sVVWynjG0eoc^dthHBgay)T^h{yb@Pz z^9KJl`kmJ6#*VmRmBoJT%J27H%nMu8kzw6TK@(of7*8m({t(FQ!$(p^RY3Y$1+;7{ zcn|UsoH?o{_$Wo2ehjawpiW|9Lu1FU5q%L zks*lgq*7wSvUat=*c+1aY(r;H5#Uv0Z;72hwf_5D6(@$vs_h=wbs!lE?8uw4BK>&7eI z26<+5!zz8QYoBK?>kt0neZQ%8CHJtdUo-q=~(C*O*I?sv4IWT92#Q%I71Cee%L^RF@bjWsqO|`^8AUMLiS? zG9Ksu(^2f}b|wsZ&8)}SxZ$;-UYcDG&*UJv%Doex!dBeM zvHYOhrT?xw39W?4M4ADfJv=U#A0Q$1tsadOnDax{i6hQMc_e#Oiwx zprh@0l|=$jtMb;(#dlI+2i1#lb9BX3a2V`NH}R+CiJ(rOp9G!9-^#KJ3iv7%8H9kANhYhvuZCGJLVfJ{lL7i^dT`PeOjiuh+?X^qPq)@^3K2S zEZrz@Jpp8STH=Oj!;jLYfLEmX9Y_*j8wUJ2keIasBA+drlFBF7s~qj23i=q7{Vw=>bKm>T@10tx)7U0?#PrEN zTgMLk;ER0FAM{Hl{BbW86eJLs-gm{!C_Z%n2UQs)yTA0foNWgEZ)3l&Y>fYHW+;U$ zrkSUf{&_KFipGR)d|`;<&0$3k8_?ksO+tVyr@=GuXW0veF$d&t#~L^Zq(1B5R)@(A z>pm5MLXs!Gj$;?kg*z~t<`%8vhl|Y2YIvMWL*Y~2Eje3V zUH2@e)>~_}-vl*^djFpGH>~RiDuqTH^8g=SdX1y}a{|fG5^No^W`7g9CP&^y3@b@1yOq&v zWu;s{Pd&Dce8J;mZf8P7aue_jaxtSNnb{vO6z7)Vy`ISoOB_)n;mVz|7me z3+I7LqAA9kAvK-EJAP_kXFI}G78gw`hpCzUrt9Dp?-zr@dr!5@vuy!xLF@w0@|i|8 z66mp@?qY-cjOD$TJue>Rctpi;9tGh6?Ckg|(*9!~S1M^W61|B6Skwt>c&w1`E~w4X zr7^k%GqFId5w>~=46u9D40BHQf=DGx*WUBcV_ykvGCsnqVe?a^p8PR#V_t)H`003K zfk57Gs5e~!V2V{z)*1vhZelj!EZl4P6xchTo<|I+xD3}!Tv9qOR>mpL{b&p!RDhjyr@U;Cv1)S6-0^AQ{ z98~FmKTk@O^Yy9-Fokg#dA;u5HQ?DmPU)zIaCK4u&lquu^dkekqicEDLz5ImX#xE(*X{Wb(-uiom|{-c zcI^6Soso?s{nEU1rg@%qN%hQB_pY9aHk$&d2IA>!Q`BF2e}) zI7S5)&wLY9>FD9ktRfYgSAJ_CSA$$vIh#UI2-$XPBN#P-^lY4Z*2 zK3c-Bc`7l&$^DB?~Fj{bSIL9h+t7J+Lypt7<79+Z^V=WHher=9)vQ zuqB6_>%$>)aa+Uo(w31*lngtGo|g|-^>bynj;Eh2kQc~jVrOUtss8dHXE+s>f(+vX z7;*|G!52B!uw>>6LKQS0DOBK9YqSBic$~*_rIeLYWLk5i8M8$XYCZCMNfsk3u4P7? z-|9f!I%y$u7380^wxkPwC;!YMJ zjWwdL#A_meca_{7dYQf)pb+xK<#8cB?4(soH1a>(yiSu94nJ3(;l&1cHKFd$_Rbbb zlcWyZa1d>SN~JB~%V#q->}!o_avjz)I3QT98FzlsMm>AqUrna+*0Zgps#dx6O`!K@ zCDr%XToZ0#egTxyYOLl46QFMVb$BJV36WJf1$-a3r1M$hXNAiJ_{BRYyT;UewKg}SD53kCx9%_|`$b&X$_=kHuA#ry zxO^o((DjG_^t0McL)QSk`qzU zO`T;L!U=?&FTv4d!9nam8(?jR8#Y)3`d-Ol_i!cZ=?8&f`5XF?}{2Gt_Y9 zguCoBXhnIk-P~dj%nZj&o9h14T*-D130W69I;Y|oe1>=BmECSYS^ZU_WCfQ;I*)X; zxkM*QG!tohNx%N?zsHX72K2+UA3cbuA+ily54@)5Bn{OD{YI_>Yo5TuTJS|qmDDSk zwbr}}e}lc~l%#y!)6UolUvX%u_>rdJ4rid3@T$b<^TN0 zC&?%H-C;*thH}$=f(`j-7^SI;$7GX@Ah1uM(ZAEOUpux@b-swb*807rYaPkLV4_0O^S7&&9wOI*eYf-evaFLo(jJ@=1Oq6JEEtv0|V&<$(V3; zY%m&|@Mff|X;9^|5Iz$ZYrcmTx?^C3zM1*7{(Zdx7+059)U&*gO(Rkcjm)Gx16jm2 z4CH?md2*bG9gyPD9O6k~-4(yUe?8&rSY`Dp`;u2BFu(MxKIrU7YvSsy%;CP%OQEo( z#y_rVKY`K|E+IvcFcWvJ5AZLe`J*04gD zS3uugfdpyfZ+1zuUStbX;yifW0-}K7nQ~?H_<~NyXp#P!#o?c%2Z*2n+QfwS7{p9+ zdT~6a-vM31Fc(U49eeLXdu{wgNr|#Bto%zj>EMd~$Cnq!erpkOk z!LM|(ORh1d|EgBGo`_&H1~VD*><-KduNVbj$}gN6I!IPuC1`-AvuU1AZh}C`dZatv zszxR@^F@IaC8XzR=#oO6J#tS&)MaT0|3tU-Qb@g7aBs!@s)HIX zRvb!uFv7Ialw{f4_=Bdu?BcM1d$?V{Th+5NvK-{05ZvEi7ASkKD{TzixQ>F)F~v=~ zWnl9`!gp4`i32Oj@x^s1u;LM<@s-a7-9AnCglo=uSZUI(p~N25S$U;2XP`nOt7`4C z*1E$wVCkjdT+f2MT*j^D84b?0_{Y0)g=qU46 z*r2vDqcd6lybknIsCMDJkxOyGNG6XSS)@^4Ey`fohTVx>Q!B+^EwZ#d?9y<*q$FWr zO!-DCqPYzPsXkMEWsu+FL*fAC|vW)^fKN!$-%Sl`@vRG#cbbArNqDBEW6J z(wzcJe}~zGpW(5ZS)a99Lig?_0Z(tkbe~cEF&(?%Hp*Bl&0Axs@W0fw0 zuL)9O9dm;wt=bxq^-@=Qk~v7gq1I=-9d?kHrpQ_>>yW-86(wi>FtLH~VBP-pr65~G<*u`5Ja+$i?Koto5u10>7zkDFq;E6^p1@i>EF0--^5{e@uF95Y;h95j zm4Z%k%td1QVJgFILaMTUeNtI9^QOj&d-m>M${qdLghu%z43vBhCSg_wfMabn-^3m_ z-ocjN@fq(0am9_n8r(0xvHjQwp%x=V%FW&U8#4{6{Hxom5|+K$b1xdXtD!B9{6NyM zV(ZsPp14;I-V7N?a&;wY!RE&e>Q@|%=1&QNLCK-$6CQB}S{hf4{@^-1=r-GEKLow3 zyRw61eIBwXKcltq(?$lCR8Sr=l6|#XlOF^QPgJ^#Z1BS?g1Q3Sed9iErA<;BQNupG zgpwn^b~t7r%+YCljLS_Uc9Lz`b%o%uPuZH|dp^8Cmuo^)YCuX}vk$ED0X$E#oIA7q zG=69e1^)kd`trD>(y;BC7AwbWx74H=E2qqyrqo=iX3EM@TD2r)$|)B_#@rV;m6??p zQ&!Hnk(pbDWD4YZGPlZ<6qN)Omy{F{aKXcN=6ihK_xJtdAEk%Gv)=c0U-xx|omLb0 zmny)n^n?aVL2$m$wT?0AvNZrywv%;O>mi#l(7iL;OZQ8_U&2mhomIO{Y0wrvYH$wB zV?9UPj$2~QAM(5p5ndOJ&>B3mc`e|}PX4KJpzPfCjXy3D&lv#rArOZv#g~G~K3z9Y zElqs4y<1-{7vw3h(nP3}YG^!soScfmIo z1CU)d5E6Zj<;{sB9a^sf$#lQOn0yy15BcNGje5*w4u|z&v3Rs;*}&V&9{YdMfJq|0CA?O-A;o zf#yYckM4D~9&%pd3XFPSG1JerY#L-dPgjFEI*gUOmQG`Yn78%V8SzU)uFe2HsRB#w zjG(7D7Rm&X+MQ$0l`;4k;6Qf)h~@48^Nsb_*IYtO=Z3iFu$n(%E*z}=98?5&F=<*e zGSG12UO>RJmjGVK4S#BfXoHiv{MP}l^x%MKlNP7w;(v*^7>qkQyDVfjS@gPBluYz< zekT04v8K$9`8o9DMxhrxf>h~R#D8Q0{3mv|ROG3)jOo>RnQt}dX@d+4_DgkxGv*6} zTaOXVlv>005VE`ZE$yzTyji;jRx%I^`RRAwN%yJSqs8^LtJreR&GJ{Uz__vhUxnT~ zL932c@voHswPQwDm3{SG@#Z&mmp#Z@H36Eeg1(9@X-`DibCPL^SQMt61Bn2PY{LTS z{pHjzpuho1uWP)}w%Ywb=*R)(0;6i}4+_i>rvYBAOVF6V%QpPXS{PbLbhv3u>FWYU zb(pvj-?){weaGW<*3e`2bd>P5s`pK2s3#!{2*#@5$F!O6Q$MdRY zX=g=~oa(J9Z}wa|rxxGy=OxhV3l6@|>7;C^C2NZkEsq2VEaGOrX&|@J?W4ZY+P!Kq z^Q%kEqJTcumXJ9K4C7Gt9vOdgY5%Jbb$XL+m+XJt>Q!GJn?DR(ZGNHZy}3thYIDxF zJ>1p_J?{d`VsDX)NI4FyMz&6BR06^I^YJvk6{suYz-sgSOvQ{7)`5orN)rfWWF4*B&?IPi);n^VegQM5GgRi(D=Z16JbOJ$3nM%AS0&h zzTF?GpGoV+&sI&(azYgIc);YBpnW5jA3Z5liyDyh*1&1q>NhaBD-x*iKuP7w)M8?6 z+C-q@NB-~aFL%#6YKm6gTWsrjf|4HtGJwE=AyHOf|h@;Xk6ZHJMRSlG!_3gRQldr)1MW4X1c6|Jmo=OL@)v;n#?*4?SO}9JaU&&wicP6R<^LreqrX*+L0|;~Sr$ zIy>kx25heZGDrby`c4HMG(!{{RfMJoO^sLFHinwfXVPM_+C;NGs?)4i_+ z0VW6rlUapG!s*Ia6({!4$^{N=ispvrh{4!EccaqsB-;QHC`MC zIGWvcJ0F5@!xADu9vp0E8M+(;W*HEAZu}7scG(Rg3ib!!08qR7m&v0Va7l!vGZ=lO zxjvnd&s4UWoaedo1%Mx7_46`xZT8ix>lRk65JEnmt=(cU&&<)47T_69RV17c`_`R~ zcb-4+RXV-lw=W0fO!{b0QOoPJe#YE!0-q9dsR}HKJJ^B0F@I*IDVyZ>W@mEucn7J* z_!vJ7^FnGuM@i-*`fR=~xN3KL%(%s2A%5Fe(MTR>qHzSHycH{mYfu}ez=4K>MNPh& zVA@$Z_ipXJcn`()>xGBDKN;j;TROZs>O}kOvY3GjJ$?9}1&pj7C(|x&KHX)Mg#T6d zH$0GXG7-G-8R}boAVtHG=iY~>cL~g@DGqVkFRF?heY?C$at#mP=gpsxjrd&%u3;Qu z7K5PEg^)4Ro@w1oLv|Z!*RE9tAdF$r4XiOX$Z!Q>Jrq^%2@KP z71nk1O)i@e%OSX)5hQHLUW_CTQZP-vYxoSPljvf~f4TNlhs{QZOZpl3Yb}2t6ED^Z zFJ;d1DbzSsAlM?*TQ+M$d3d6Zl-q+Eb#0dW)YSxiRn2gQL9Wxx(b@^l#WjBvjq1oE zEh9|Z4Gj$75Oc0}U6K++k2i|Z&(J2=AM{d0HN$INHbv(iL(ZCAAKq-F*%Y2!pRcu* zqcn)L95fkS3bBd+9tNKa^{1EAZF2l8zaoTLHX}nV5VgpI;Q4&Zph3ilx1;E=NzI>Z z-URc>J@kOf?sy9%Np3OZbq`$?e^x%4Ga$PS8AcStE2BB}4$q`D?X$_dOp^7lRWIH2 z&Cc(B%#W}oT3AQxs8yld4TCcMS; zBsXK%xNJfmj-$LoiX(sTL&~K`NI#C0OAp{PGK79;N<;|h++)4yVGOl+tsR$LGdE_I zS&Xh!2c_g3-`!eO@RdJO0-~vxDlgz3@WjQ5+9X=XL|334*p)1LqG)Y+tF?a;lkr~vkqUH`^G~6pNd@CN&u-tU!0OOqj}5P*Dm7|Z6gWh7 zmk_=^2I(qz@f!gAtFysg79rcHVo)_sXCtFL5am-zFnZJ^sO zI~9vJh`x2F9W2WWVn_Wp>AQT(jnE6_Kk}|lOtRTB251?tReF{3@rEYfy1O|wYSDCu zF1P{)ari(vw$xP$NG3}Xbd~Y`njomozSd+ME~ih3hMp{abJL?1Dn8+IylM!V0gX0}Z0m6tO9InQ542mL$O6Z=4h8ktJL#AP>$}E@qMNh-l14gT&QRhS z#HZTDnRw^MS9YG9^Y5k>G&`m5Y>O3r)C98F#{m+1W^VtP9gTevQz1IXv0@qDi&&|$#e8RzSj7$W)dm&rrY@_PwF{kY;BC7C72G<&XX_>Ov_uGkSGWN6W0 zb-_kI-XbzUWLYRyoPH|*NwN%}vk?@lE^;?FeO+%>`dHlDm^uo4vn3*VIILaMtd!{3 zpz>~=Kv)M~7F{#jjj#q<7b&S7PTQD7Gt&&*RC!-4#x%M_`vte8v5)okA~7m3eC4u* zua_RZ+h&va z=kYleeiq_WMiS3A1$q+_c(1)U1uB7fwj1Zf^vHFN*6lel7$pWFva}toqP(=H8cE14 zRd6q;UJZzV5zh)g&06X^-z{<|5elN;2emr45@hapCrh;A3vmyj)wU>qjI+a!5=K_K zxn(Y{db7;sMD!nLb{m{LZctn>Rv?zSPHC93_-oL>5E(K5yb0JOe~@F)g+zOx6!63h zF8C|@DV56wx#nb2H~iXSUb*E5ur<#P(Uzbs(B^7;1-_yG`}Q0l?p@9>wtQ2-D-R?~ z%V#@Hq>-Pmh@?@L#5W6$4R3mS$8n-kRbwsQ0cOW!H{tVApD#)!7ySf_d6=JnIPkVv zh5D<|GYhBqQUb8`Zi7oP^>{3>%i=erSPn+jYq+b&K&GMCP`dt8_cT%6U>CK)IJTa& zxv^aCu^^>^X(_%-abw1?F9z)AuBlB%3i7~R!c z0i6NzXBSIJ_R{u2e)Z{#c?Sy*+CIql^)Ybi9vvVEgw&y-;Y3N>O2dWZhkcOY)FXH% zZK}7mIDmSsn`8ltLA&YXk64vI3f26+OaeRzw&NjkZfUx&pFG<~h5+@Q87|62!A z*2sY`whwjACMN*3giXpPMn>+Lf0!k0YHoedWqj)K1RG>c6m%<(biT^j>jm~c4zc$e znybelJN+jZa?BuTHRLv_t)&qP*SDhbRSJ4?wVvGj;t+H7H(wBoBvOX5Orvv8@&++M zoQ||JUAuy7zeE#TK393=E_u02*stC#l=RhB)}+=JqPz0J^>GMXAMQC9P2o}t>Ef;b zEy(`$ZR|CdgunY?870Q@e3-Y=J!Voz@{gLv&z9!sX&v0q6@)6Xb`Ms%5CCs088qD@ z7tkQaI@KV=SE*ODRB68c2%c#U59C|FohKOtfWZ0ch7v4SHM`!o2c*|upT}Xb^}tec z4M)4i5?%2}1~j|^8?XmPpIiCSnQA-q6J>ALBPADPnA;5f#RxQaum#+9N#J$=d8LAY z(1`x%e+!EA&!L3N`?b2ZSWo@+n}i@*FC%{>bI6s$Cr}2tIScd)jAA!o2eRpG@q}U^ zj(&%30$(vq2K2!Ag?)jX}K<*Qau8_^%zR2s)Yl*0)e>v5jVmHppC#{OU?4D7M!WAr>bR+}q3J9HH`Y zwyPg>~i zVgvS*Qo6E0gXw^!Vx28DitgO_f&Lf!yY9Y{(r}}lPygOK)$nJZOh?s64WG29cUGKb zYvW=-P$}=OhC20!WZHE+<#Gjm;*P?6(C#J_M7wD$YBXGnwqSX z0PgfkIj}@IjjlMIkJQ%#c1tO_8IUq?ij_1d;sxfr@gp5PkAxE%N>h%_`^EZrA`lFy zk8C_}X5h(Bti0HjR^^p*!o#E06J1hDxx>$2?nVT1*4MW5`w*zzPz=gQ$7t8f(Blg6C5wyknUht3Ax5%%AswZ={fPB$YQaxICx@p1>6Z7V=V~>wahxby| zR1*3=?%P@6ZRf?RH92n;73)(hWj+!2Qn8(Do8ZOq{zb@6ndTBul(AN92InGkCP!IU z-1suudve&7?y&__wKeV`B=>w**Nx7_$Iotgn0A=wMPxHydJ1anM#qRLJ8XKS?V_kY zD@r&APak%G1|C52(?#eNs=9NR%O2y;ao(0MJEJ4mGBtEBr?Rg0N(r z4gdx!5paYY+KX`s7s+4S$SHLey!}p@;)w93yeDdkNX(ZB-9O#0cLTfB6{>TyX~i7p zhts|=hm|?GMDK_vrFkL&@*{fAI=t_=NNQ^YMn3AtQPJZMU;Xx*lMH*~K{5qjGjPJ~aBKld<*AhN&o&}Ee8X?X2L}@YBRKVP= z8Nter0+rp*Kdl9d-#LUR^A`Ea7YCH~i=(8O8b2HTv;0}nM^VynMKaxz2T4ANsN?-n zE(QwDjrfalUSt0jIfhn^eG!doqOR%My^pXOA$woz2@=>cuk`ygWfkH-Oyhm%w0OZ+<=Q&vlPUB7jr9sI%uoNJY8)!j z)2@Ng6#&m%JdH$vpMAQ!E*MZWEU8_FV4J9hx@;GdZ)Q8>qgBKDcz3r${O~@^g!)!S zA%1l-F@soq>E~E~b0?daJw;TGiZoSLqdQwY8SeGGQ^OGx12OI>kTvo{<|_>q)CK~J zD=h|t_@k~E3MBWqDIw%vg>1m05x#Bii-als2bs4fBbbYkGS}CDBJlizC_vo^tF2mD ztp3MQ0eGu-0}+f)dsa=_Y>9+KV&cdB@oS0>T7|I3=kR@7XUI|f67d`sd<{^Wz40%# z4hzon9UvytL;5tQwOf%u1z~{xmYjrH>0*woZFJen;Q{M+!un|JJ7u~{>7dRJC!P@G z{~Hmgbo?ASI(Pr-@ma5Tl~F`;%ivgRWFKI9wou>SVG#UL!gWouyk&@Xjrt2vrzNbd z1zna$6!5ibzl*w}L1;sO5ft1Jk+uI*_t5ye0jRmL?1K3M(N3T|8QpiGlvd--H&VPK$u0O2+&}0p5Q7|rE?Qs5tYX25&?amUJpnSH{tz8J22S|e zH45@wX*tE;9p$mvo%@DEmwXIW7Q40JA1^|1Cm(LuHd!zt7PWDKl*-t>oo=m=uo!+A zE39(Mdw~81ies`nwR%V^m#YHN zU%Lnp{+b(ci?DUfMDJgs%j(jzYD=HC7LT!w>B_{LMz+0_{Lg(0V;){@@Uw7?dt_AR zB8=$)V*bopZ*Dh?Q?$G|Adjv4e!$`!? zUZ+^YdrmtBbgrk`S>)1Q7ka3U?(k=yKCzfs{Oi5UgnNZ0M~f}zzNyLl5?Z;(-CkwC z#3y`8H3{Vqv?dCTNBnnSfTEx}2d$;%7qpjd6mO!jrhYctgrq6f`tWPl#{bbN)$Rad zn+Fx!a{~*32-BMm3xf!y&*|Ua7)A0g+*lbEpNBVQfs>s=`WCjESj%r8Lj*!KML_2O z0lACVfq(FBhC*my*>XN=85hb!zuSw(j(y4d3hbQA_IB|}9ca{g84s@vsR&t27mEWqf#hk3( zkWG?#%TJWmQSoO@U%vou&1;X;e7eY`M2&I4;g$dMFLoc{IE7XL0E4}{pk-%O6RfH+ z-c+DgI=-dNUILs5TC@RnZrPK%N=P~1{b!)8y`Xyv5_wvWtMYW0lXWh&9SB{Zo8wJ8 z;{yYWg3LCs6y2}W_f<->Y0eCGjthPC#zy>wK0&eBf%i=lNx$WNf<5{wEob8*B!Atc zy_(eZUP_878C6RWyblN`CUhd$tPa zrW~C?DkG{+-u-8nTbE-bLMSN-MD;&vK1jfX03_(f#qVBVNwjNISTe#W|8D`c2g}6} zDto)euR>l@9eM7YVE?f`0?ye<6HM6*WGw}l*JA1UDCQTm4K{UJqKF)R_kF1ufyHOZj@<%@bfQ3viWb*4cf|fC;G=JnXfL4e`mJE z=t`TERF?U;`7fgljHkMSGyvE3awWifC09Ht5^MP&;S^+M8PiUCZ@pPoo*2s;c5B|z)ji{=e;xW&M`w~)y zbyFypz{XD0X}qj&LxY*9UG$>iW%7sT+=gh#!C49EV)^P~oA*4glT%Actz+d?8r$>G zaM#;`70a_M3`D-^u(jumY=jsGc!Pb|r0JLc_qv}l*acG{w9rfY4G%~T^8PdNRvf9@ zfc@{3y;XIn^UbKSX|am*;rlSopH*{U8HQMMWwv=|T{Eg!sE4mNkx~I~rGoIK?jeB8 zZsq98+AARBWcKpE;}#9moAZygQ7T#k=NlOe^8E8faEVtkgAMPSiHk6CW^}1ss)3dZOfe;|s50$j?)<+k)Zk?3B1p#6rkws?V#n6B z7;AVIg%^o20=lvo*`(PRZ_2NALEE!!6W(H0Xzie!!1U%j1+P18HcGAI4;OiVQy+%E zlmj5*;coKoze86EKR6~YsBDULJpO?^IVWikmrZ-cSN*#3Ma|iC4}M6u7w$|4kDOFh z`9)g_L>o_m^J4Fjwj^a;yJ>;mM>*7B$i>QO)3fGNt!Dxc6T`E#N{OT(Y7HgB%{59$ zXl((icCwwAH%1;?wA-@{D%^C7o>l z>Q!SGSMAeRP&hl$)$o;%BOr0uI!aTsIwk%G@U%Io&XVe+Jt4QKlO@8e-3deF+w?W@ z$MO+eUB4-ijrgfN!;3$gOnd$1oK<7nV`5-HNC_mKl4XYR^fL0v;@1KQu1I`}rG9DrGnvJr@*(}CwA z=y6i$573gG>;|~{g4QLOK+QBPIKLiuBkrDnR{m(V>F51TJ9sG~=3pfsFX3J0xtmrN z@|7MMGLuoAPO-fz%A-vQ(@+C&3*BfwB!mXhT}f@llr=Zh z=nok+%I+F>iIZki?CM-rmD_n5-!Ba<_-4%LE{-I*i?p%c-fIqQa{BHK-zyPj@Y8{l zxbZQEG#@MgN~*zmE}3P~Bzz?ptb`l$0Ul-p)(Jvy5us6wFX@?eP(q>a-Iux+2iArQ=L%W>xC7!n|F967UulKW)k5V_sA$wHA!6P zap7YNUJ5R8juG9*i91!;K0|S9h;2*F3Fs~(j0;8!F6WL#KKR-iS2%98${UZTsga5| zXBRy5m^`;IeZ0|2%ug3t1627z-cG8Q)*7k8cWIfEC`$`2jT;7Fk$cvzjNhx^Hi~YG zUSsc7+sLN}#fdVf%b=0chgfTvZeUTL9+9NHl~eeSRs6xwsBkN*0~$h))$+xE>3t${ zbM36sT55H{unFvZDDN4f(F%-&k*xrSslR`c{p<}8Ul@gR7^At z5ASie{xXwd(rE0NQ+Ppj;^8@=0JQP%X>(C7XN+dBs?|cyEJzL<$j}tTA44z_n-@^s zdqDAtqZ<bvGh?lO+9H>=@rUH}By2Ekf%a5a1ZG0E=o`J#2NG>C#69@0qV>LL^%seX~lVM;g& zJX?J=r~$zLZd6Jt`-_L14VAkUjiOsDE05_59g{=+>dk?R)y~q^?%V<{S(y^;LwQ^- zee)yF{NAF(K3exMm06P9!qR!=v%j6yrF)J8UstU@01AugBCog1|8b`Sqc%W(hr5Yg z4Fxew>glV{b@BV;M)h=KB!s*yx)xu9Y4lwQN`6nH6OOjBQ=@Lhxj`g*E1NjWL_lk)e33#ic#reR}>>4!g!^%%a;zZC0bFHGKd^g_QN|tnQW8 z#dqlFrTcpa38taDn(qM$h|AGU`z_3~yC%-m9|;WGFZ)xre~kShU;hW#4(*6W1XEbeKF7-AVP5U=nxp@`)E(= z{)d8ojG$$BUzy=-?e7GDv4my!IJ!sQXzY=Z~Bf)6;?56iy@ zR#LQVW1P*l&z*`SD;Y8B*u#1OH^qRZM#aa!B$ zx7(&%SKS42dk5A`+M8b=oUZ%OcjK7SzOaZV)1NF*`|*07%vev4Ym z#SP@8h+8wdy7N})Al4*0>{kW(mQ6tQ*Q8UQBXkZzulCu+w@5d)bv^E4bv#+oVCQsi zpOr>dK*3ykai}A3Y@0YP!n3TW*g0|v!6|NnQSOBQ1rhrnI(TC-xR!1`M3U-tGL^nr zoLU_|`?84XPs08?E{&UFOi1S_eA@!E0Gs|k>+%etn+~8v+?K?RJ1Jpms)fq1sWQup zEt%9|M&>r1_Dc7KQ>O{^erjF(cJMK)zzKG?c7OaYKqmy72)`yISL5Z9AzYG`)A<3} zYHwoy${U;g4|I}HUIC$wOkv_L2=$ADO%BFzEHu@|P5fS8-WT6~A1k2qfrqdU&|JFw zF!ma}0ei&|*={sJmzxhmH%H~JjhH0g<=y*0g~69tjBiQVIVo#If9NJj#cz21L)}rf zu~|9WJ73>VaOz%@P*%y~qnK$;U8hn>CV6?#5yKGW45tQ-190yN8nOk}u1_7}OItMO z=5OkW#39)_#A3)=x@5kkW{qO|;}RfQZg);TSu<>fo9C`9X=4z>f;qNcw*P2+p0P&- zSD!lL8+Xi=P1P_!+)pNtOCLgKVY!DPVwp6t1Z=}j-5r-jXg!^6)bvlSA=)7-BiziF zV;W_*(&gt*xhf_|kjW(3P$|rjxq$R< ze8GM92`Pfw%}!Jj6|O3PTHjz$fuI3RyJ4+z9N}mp!8|xnzb67DaH2|a#d>j|uK|+t z%xw_VHF|;^;Oo@}PouF7>Uee3omrB_8I4B-bFy%fZQ-&_V7- z#1{~QH6bYsZ_G@xNkTAxL*t|2rF}}ZIG``j0S1GF15Dpgu4SR9#ht{~F+>m^Lq*%L z@*@~zhnJKnPphY!V*N!u360p*E=EX|=IA%GpLnx0{hpq2!WaIWcVy1jgT>?BUak}T z$TZJ_uV3#e9odgbR$Y)!E(n52cBz>iZa7JI+Xq4!&)tQ~2By>nU=z^p|JJ8jA3d}g zbXoH?QFXjSRM?+iQd{xfrg`Tb4pCGKq6ZqfUa2$ zG&Dd!e*^Q%Robq2!W$q<-87B@B()y1&Sm3xrQuzh&A0=d)u)FIprNHSWy-IhSW(WL zVdyn_dO-@+XGP}4il0-f$bSku1o(bl37_vy$3P=~AJAM+8o@v~ggw z-%!*L-jT{HI}ZEC@d60>?qLNX{Rk-(rNf z^bz01zU;R1dJ>u5c&)R1tg$U4D#|}>$w*#<(Q%S&Q{+RBJr#%iy-)Klv(nvs_SIx% zl(D1&HGK>aWR?<-z;X%qb}$@^6EKGiGyc&5y|gX%AGqY^5wZQBY{Ce+9#8pi0VPGT zC;o$a1|6?W7sioF)b#G@l8NDm4m3U!17rg4@qmE7g^JQ_b+p3O9W;Gc^Fz2_sd!Lj zlb_=v7SM#VA_`qclkU+R90R5R+o5;%`O#&X?_~AVpSnJIZzzQ|E?>F1V^N-OsyA?c z&rN8y-}$a2&Kej;I!gP|yYI7c{k|yPdc-$bRa@^ot^7q-11u+EyGF4^o_0IJ&BxlSV`WxJmJ9#nhP48TZDiU`?~jw+lItJ;zS_6RHDX%Wo@G;H zqfhuinS*^V@qllJkt@OB?u>{@YP&C$WbUBqh?V`{DFlQhg%jQd`pCb~yE|t1O0wjpS zWw++ios;gBgp+XfYO>O~ziLUZ`f!zj|Cx@~79ZjHK2z^VNw(#)4tYJ%mS&PyfA;~hW(NFO1-160Nc+PdpQ1myP!>xD!k@1#5B5`Ab< zR0r@lVR+BuF2@=;;|dwWcV{DcAd zJ5?L4`^U*kvm@R!VJJHV1lNPmJ)n3+@D5Plf8d`#4m@1LYYx3zU=xRTEY1z{C%#dX zQ*4u9;^h{S(P1(3C`_;_uXsKvW2_M!W|aE|&CR5+J9Vbu7}zq>qtI8XuYp1(PA`6C zhCJcC;@e!amI6ifiu1xMr2!%jVb=OBHRrN@0h{D1OMGTuZ}Jfg@j{4& z=|$TAp(c-83ZkOKoQLcepz;4P?Y#)u^1K_89|(j3Bp}%{$b?@Xo|9UUu4sxRSWbSo7tQulJe-A0t$@$X&iqGy>MsWG_>}rc6OTo=p-3WwV3%+ zB)^)bU0t)9fEr5mJMB<_VntpgU8QPjt`Xtrc_?p;vfOW#vSOi6iF&rCBj#KpB zExj98it-)Q*%0Qv@kn|UIX??J7*|~jrGT|CHS3o;No!VhKmVQzC+=fD)t?t6>GT$GtaJHt`IMCv)!i|6Oh%1W5&8rr-{mclVJrIyGgCet8i(L42Ih^Wwci@c z&Tn~BoSb30De4SL73owH31o!YBa*`iNj_Yk8u&!OPmXuRewm>-kO(K(yctDkDA&j? z%9H)i@Tf(>Hri#A8ivpZ%ajzIIa5Hg5&EX14qMqBANsxKs4==Mu-)>YSFrS=;=AEN z!h}L|T>A@BtXRQ6T%}l7yFt!=O^zT>#2umsz^g6-y06o$(bhuq!bLZW!aK(1K*5TG zmDF~OGZTsq`}6=sn#L-b)k|5o`ZsM6I08zNf%9NG)vjrA##SUz?$-*k|FJaMOwp~d z&uen+n?kdDf=+n0k9}ovpk#_hE4>=<@fe#GIX22Fi<+p3=U_kL2BY4IZWD~-;|Aed zK!9W0OGMI){#ggRAAETcdqkxU?bz^!VeV{I~h#}g*@|hJHvX&Q^b%vZ-gKo ziOhVLH6v8aU*lv6Ky|4Xz_#0n9VoKpSdpzKu!_0ZtMSamgqBrY*rfuEZnHdloJjtF zZ99opoe$e~tV$RnM6E#M%V-Rzk%nxykm}@n2gSEhORcvqwe}apa1c(&DX&o2-K%yT z)Yn+)9l4>L>~0cJj*Jd}_vJ!gpTPUR%e2|G6&7ypwi2BWsINT$eD6B5uY17HQ_vro zIWGQEto(&_ALmIZ_Kx(AW__M~<5=6C3unzEh&5eIhLoSji1U`z^r<`&beS%DwIl?u zanjm>))a1v@4j}b8R(IwKd`>7#WVXUG}m^9%w{of;!17BTic;g(7epw>KB$c@LkL2 zF2*<5y16!u@F-@eLWdpD(q|dcu<1%NvfE3$=9bOWppH|k|NG?jv^~)e`^jNn%yLCM zcHp6Wx;#?EG@qvtUfp?$Cyqb}Y9iAeT>e0%)E^r7*LT0df4(7$CNvsAIYYFKMB7-s zq;MiB){r$GJ6aH`j5BapSs=BGa-Dc`E&k<~4;x!A-fsA*tv#hOM0A+c!H=A)>eg)j zIb}fP<4`Mei|`?p1@*<`=7G5L0YV-Y-6P1|z%05Aa^A_lCz{zS#L4IiiLqCrcH2_{ zg>#DHmQuSn#s9`-h#TeSV&MR_TMqmc#Wu%JJD>lA1s_9=P*=0@vU@ zy8O%=uWho!`n&A_PT7HRUmh2Ww93f_> z%8SwEtHEnof@I3@7S&e4nFv$x-u17N!}qnDhoIF+>RI`%oWOzrhovb@K~QBa6ABA$ zWn^VN&g$yw>OS`qW_O=xx4uZic``RM!WI%G9bP-G0?vUPLzRp|&-l^`@OTg1H8_y@ z1&AsmOXNn%=g3Kg^)R7{-Xk7zy@ncTf0mEJOY?@{jnRNfZ&=l?#R4w9hw5-!@r$4| zx6A&4ouUZy8~vF-hgj#OMs`ieBqha6%0x^_F2&nB5GM8C7gpijW=hduJMe9f04m@1 zj6HX8U+BZnL6YH7YuLGcs+4DuFvG17=N&#;SY<6CPdkjcWssezx=2rctLVxhk^Be^ z*?FjbYpdpa^t14XyMcc;Ut^iy&PkBk>rEhI6EVZ4+UhIq>1kuS^Z1ZS)D!EtZ@yBV zbXH+SO91i~C#n_hD9iBH#LTjsY1XD$O|hb&F29aEMQ8N};gko7<(XoQuyFfxa*1SW zOf=&u(h>@@H-IY)Jg4qdI0n!rE_4Fw3(aBk{m0BeBkGVey;_EyVAM#y4{7C!ZAS{x_kvC#}6iV4#l0-yw9HK$$kf?J<{ zxMmJiOiwk*zjcRR_|l8KFk}mU-}T#CVegGSpeCP5d6~A~e2XdHd6z|br8mPtAQVYs zOVXxlS{TRrYK%1*HJqq#Gy1i8hvD?;UsXo^_+-jWM1cfU!zd&sk~bi$?G30kr91h#Lww4;icW~Vt>wW z;BN_ivWU1@*L#6&MkmUy(x+ziPSaJv*p*42H9!&Lcxj=|NBZ3NLrmVBP+ZEp@$k8d z@q&Z)R;y>=RvmMWpnKGZS6N-%uG3fC-el&ly)9T}ziF5mul@?HyExJs=g1-v zeAI!Ys#^5O{t9o8x!0VbXXP>&*9pR0F0$i>J(!HUs6Z}!`LY#Inn`yze#O*IyEbgb zC-9yGFMQv0_3BcSXRlw!68Qu-h%kCFF*%FgSCFagsud%9dEqp~++VxikkB#X6LtvD z#GQ!_ggg6+o&Ao$9on^pK8Fj(1dm?9uQ$A(y0qLX``Z-C2Id0S}_5=Cfd?f2%M=wM}; zjrurx^2m%20Nu*!ssW&2nVmreHN?20a9Z;ltG05Cl?Q|^`&x>YSv{@-S@(dfM088s zFZv!kkV==IWG447k|#A{Z(&O3nwTb97X>>_S2IXh7vQq$O*PM^&orWQpGtu~4n8>7 zoTIls&#m8f8Xo)#x~-tUh@A(bS*o@WyJVZNl*oBbenoru!xigs)iQ2J>@ zv=vmYNUz$FIp$U0Qp>ZUn7mEjkVPrzzfPYi##i|TMl`t<``ME|5HCxR5&`jYehs^K zPwMgKte!^a$e=w_?T$T?yzIF-sJRN&x4n-YD8_Iy56lA}z!n6nQAb=|HW{-~Dl*?+ z{Fg@`7Ne`QMcHsoi$}2%gtzJ7P~ur4M06RrL6_`P(2}ZchPW%xCd_6Vt^rbZ=(WP^ zI`dq#14^@`GhVq){HxKUwh+pg(#;yr_?^f_McIH?9p*cbsWS!)H-L1iCwrS5x)*Pk z>n$XjVYn-}wGCDJN_p{MACiK45%``RC5!n&*vSt z;IXpy1sb1yRdFMXR$f9IaW6{>p%583`c&a1S8oo?+O#MkxFgq>l7JcQ;#DbXY zOvvx^hduV%vgT2ag4+~rFQeU7Gx(g7Vc+)1I;wl>iCS2HEJCFkD=pa`&vSd>1%*S_ z&0nDn1o?R^vJIFY@13W3f|AiE|{vOUl0WX>LuwBX&sf~6z0n=59cpP?ixPX>H19xE#ec}PY`vBx} zBUHOW_Yh0M-Otw3WkCU=n_u!x!7A*Rnw`s6#kaS%P)LBkt&3b|-U@Uk2#AqSm1-O+fV7+LYynP(90kKG zb{QPRT<2D#&Jlni#7$gKr9YhsIg=f}O3RDdo|P^_b=9hhb)B)^I1 zP0u)4m)O6I84<}H_Rt#${GL0y5VM7r_j{VLPlvS{V|(yF`}i_#zXip#b{LnG4upHf%#Dq|ft4y0rL z#c?Wn-C2z8s^{KQ_7=5NuU*b=1Tr>is+viag3QwYhSB3{nV5qvQpdLd7y+9c4;Jl` zL?lr2b2Nl5&trI_U(hXx{V?{9F|&bUG*F;D>s4#Wi$7fu?JGP2^PgTnSkCGS39l;i ze+(qiY%ll+SlzAfOs?$i3#pX#l<>zYYi~~!;M|$iG=YH3KTN6B(BC%zbsQP8^Uu@O zzfkL;a^qjuW%Nd4SB$cku58`(`p4IqBn?tOZcTNrwVE-ZK_nepGd|TqY$XK z%1LX1keEe-h~)xCX36dw8s#sqk&!{}EhT1o3Dy2k{ARRgSk2w8%VO$ z#2qn;Zd&qfyaTYFMVfa-cN91GAj_NS|6lK^eS znu29r%G>SS+s)TqTK0Y2y#zEr91}L}^?MtC>E0?VmGT$%n0&_u<1fZRv4TPtfx(c1UDWC}-i7fuM*t37YE$ zupR#%oTu*u5)PQ7Sj1?SL2cGmsh)0sdEoz4H~#DT9|UD7+h|nljP(uWeO8CwT|kSA zMIXONg1EE)(+l>Op)%udh|m>!4^mhDJbNJ-YVjYT@XwNeHaX8CEnwpB6c3{0g~+Uc;b#{qkYQRkmuK-*)LJ(Vxf%_D2U>nR0ya_-_48!kX{E>iBkIk= zl1ktI@tPLf%ywI4W?HOFZE-0znQF=@Q>RT!R4S)T38~B-IBjNS#@M2hrc|b6ij=Y> z2q$x!G&5Hc1Qc^c1YD5y%eb_dGWrcm#wT#>~Dct#!zmOM3Ab z3T|+fK+{yOe+FLWL--=91Knd~S~OStw;EkZ5CMHts_E1l9D2^kRjj2hw8k*=UE$Et z0of9weNlwX{-m<^To6>kWbr)aS1nt;UE4$>XUQ~##(s8sDSu=md}cRD+ZKKi5d+%l z#MoHJ@3DyGVI2p20Wd&wKrIKJK8?wqHgR}lD)CtJ3n39xh`Auz*-G%euEouS%YG*1 z_YdotQ~G;PZ};u1%d^A!wTolZIlvaL-M*CoDbyg&Ob(p_TQdq$;*5?&@Z*iWrf9Wa zC4&N-0*Hhj0BNouqPQ&{?%mW?7q~U0iRLVg?VcP{1&A%{?1XXy=YCdilNsL}qxAy4 zi^?5?NZ~Z9U4)i9VYceFKxPH>5G+hP!7qM`R3pwB)tzyyh0ePYUP_kH<0w@D>4x)^ zSNqM^3-@TV_aa>>INXs-m5*8?cYg8TTq6R5D@_c`2tP3kBNJu)Eowa!fNq!i={91A z-RFTg!zsG;vC8Y}ur72BHW_VA-lP+LNLWg{>4`F-HU3ZKe%882M@uzu zQgj;rW%x>bTeU>;fz5Ia9j@mMpT3#2F`{5keI%S``jI5-{%BQo6amgSfjF9Ny~7iw z=zdU&P0ux_Ksd@Ya2a_+XBrpAF|?O-P2K#td(3&rk|FLFR#BW))ynhPv~YIFMi%vm zpsdwlOA?($XR+vF5+kOG=mtmYmG;=7)YHYMos$O90{lU#p+7GjfH}CRs0uAka1|j+vf%oHDLK+CrwO zM@oCFRshbVb-8^v)~Z5FPpnE1UNh~ZT`=s=Co(+$oAPH)RbZg__RdbR*Xc`BNtuzE zq+!SPB}kiWTEOSj!>4pNU-p=NqZ(=n$oo}gG~cY|I&khxj77GG5PG4Q z2u8eKW`MET$}wgP5ex2OkH;BCxfrWVe?#g~NxU)hH`X7N>+^Fy2O2fbeIm?~{dwZJz zwV>-6^&LeS-3@j?>~O4{6rha;tI52oQpWPDP?(Q}2y}^ZJ9}3jW~uIGUT084)a%Y~ zDVW5|INa44eKD!@+h>2n|8nS{;$@)yVmSSwub7|zOYKpPr*>HN={mgRTHBERa zV1`TiMsu(CqMnz2zC6S`Skx}13Tl~Hw z+z)G_J}6Yz@1K3SkUb>fPEK$Xy|^_e@7+tFJGPAQ2H}ZBHfc>B@)6BG&&nIjl%^TC zny$27Z8-bUGT>0Fz2_?3ZSURte0VpN%Y^NvbN4#fkJw+o%+;8*=Vq z&#WnN^}P)<>51XE@yJ*tIDuXy{M`3>@c2<~Q!F#_Sb#1Im+NC^DuU9`rX{E)y?$Z501F3t`msQGT88Ow7^y2q3c2&5a zN{Be7eZ*}CBbRjX1m_%KxA(`k?45K6?O=Y1@O)0@I5;UMy1=ibm#S*e)wAyBo z=Jxe2^umZht2-v=BGVqt%one4BHtG?vcG~vT%YZ|gd)aN5VBnI^K~BdB#PuYLEvF^ zXmUPt^Qb%ihdMu6%)!X=YYo}pE?Gw0A!kenKiENM9p~u8zqgv4<*9SK0&ZV5hwW`I zDt40i`D8BRR`boN|5`7b{8bjdac|EqeQ8LHwdhRuc{W~!a_uwxHzk`9b+}^8Utm~I zHrN?1D1{&deL#6kJ8n)H4-P%{)fpsHbfrI~UogB9g%spr0g2FL%_ZlZLw6KkbU2+e zX0{n>@98c3GG}4unGBBQ(Y#njdM7_GzX=J+jHe%7GkYD=lv4mU9||LGJ0+H9=*-~1 z9pVA38&`lIj=*?u%dQP+@$l(h6RS(3dE!wyidyyG5wzWc)T zDawhST+FbR7Ua07Ggf*s4s4YY5WadMmmTR@%<_ssipZ10B7upaT zJq4LR7|?D_T|nF&78_o0QnzYv%g9yZKO4#=Gph4iXMPzB4wyfC`E)n+*UAq3m{dGo z4$+l3@53j&8cN3n+9OaM6OPUwGSpzd|3FtT8gz0w*a|Z?n%@&ob+5r4*4WUbf_$Ri)46-kG~|zj&fGA%6_-P~7kfap}&VT_~%k8-Jkw z6~P5eVMYcSq0)|-TA{9d2#ghRMc|3*EM>CRIjp!EQP1fY6+uZu)~ zbJaFIGGm8JH4-yg{!N!_7=(Hzc2*zsFtGzT0Q@)vbmYf+R3Bs=UdZe-YQHXqa=pe6 zwBSD>+jLRYsHG0&N7S&K%%jDjYeduC8mrdC9&IXiPMvnrxS`;@^X@tF`je&Bd*H8( z)@v@xi$nXk6T;@2mK2egMpv=x^H>rC;XI`iJfRzR$se1yo-h|7Q%kpv>U3${2&`NB z;CoaWwaVdfH(%A_r}L;E9c_uSUIsIoTnX?9x-Jo!Z?92Z_uL3ofNvHdvoSeo0+NaN zZmR{sCfCf`D#5Z=It!0Fl3CwPxXH{)BfQ4+Mq08Oz?iNgwCvK<<#x=$0cLwWn+GM- zNqzJuBYg_-<^(&EsyK^+33M!tZt_-UDVuO$)&UP-qL;0mXllS4xDbR^mHec_*6J~*Bc2^kw- z?-=Vf=?ULdNr;W(v7k!a>$#M8NC&23)3v(BZq+@TpEQI@Wu!`*rP)x3%Z=lKSKvH* zI8W_$gDz;AQJjk-L~Ad{;$kn-aHk7;z0bdSQ|iqzS-I-XA8)DNw2RU0?fzokC_8y< z2T%^|M(SWDtr`G3jWyV%rX9fZ6jK|rycNhu*QEGqERu+e=?d=to(`S$VRqR9Ydl9xgAP7r7nF_eLQh~*_+9>}`@Q_vfv zxUp`O@=OnPF3=z^jszZ9t8^vkpG<*mbkkBfLw8`paaP>SO20Pv-NeoCXsvfW-9a5F zxqid?#{8wv)V|hFvaSfD0rSz0(Bzo&tSTbEoEtwW$ihZXMiEd$lijFV%1yM*c6~qAau%7Bq4Ow_-v>0?>aG*EqqNr3K{t3#?l(WqyJtf#p=)>I$*;qvg?T4~aBk#m z`7~TqbcDlLxc2Sw`PcsT#P{zT&QmaG!MmZx)7c@BTm?gNE;2wQ#TeVsZ#X}89`dnZ z62_sgqqOyMuVi4*>1CRzndTqTFllTyZ-%gOw$YG61I$tDDv>$6jB^$h0&QKWdcF6* zp;bA%n_i%Q3sg*}zPa8y@mdRYn!NwSV+m!*l~kBsR0iKg>D*T~_XA2L-T136S?kV0 zGm9ze{%%!Owqs2KwIcDLDHfINY!6kfwA51Zg`EQ_F>E-3)iO6Q?B{F0?b>YjGH3oW zyab!iM_~@?(RGHB^#MRa!|CaWi7k1eq!s_q5H<@gt*wP_pqIe-`o{Yx3*=Ygj(5WH z8R|^g9)SkF$+DWiuOQ@`F1dOGBK9k5v!^>a1PYZ?Iz=K=%9AY>*DXKY|4_j*G5dmZ zCjAWT3}3gFD&x7xbLV;mldSF~z)DKs9S$X4&)PXe+GgIrY5)4@JOT6uq~iGRARj`! zG++{J@fg_RHEHTU!f&gfS>z?Udin2y6+*<-o9^8un;#K4(UcRSJyv1`%JkV^llgcW9QMaT+4}{v84Rj1Ae(kRP(4N0G8%mm%!_ z=~27>16|F?61j0VIK{)O-$HR?iNB zx$%rZ&EflG)i6r~s5?iYsT%Q4W$jL7=Epw+*jhb-LG7RHY>w&!Y9%~nVxt{(%)EVv z9=Ws*Cno@hF^l>F478`+ceCuLBh= z$B3Ls`k+b3z8jvO8YAr=1910D1#v4FsF@Zm2J*37O%F0>&yRx1OBbwJ+jTxq4S)2s z*Rjr-DftTN)^bhl&GLPO+`%!i`E?d!xM9wyYL~@@epJaGA2m2$ z(q~j4^P|A_rUGP_1$u`o{oRg0FytL)CHG48tDz=mngrJB?~^+sK$_@-=zW)?kThH* zxgMu2Ik-AU*LCcwL?RVX&CMV4{D#z#2GcMr(XL((ce$hbkMTPm_6C{&gfdz1FwLI`wO7Go_wqBhOk{jO1PIZ zc><7!G4aWEECYmXmyv3-?q;d#?HFdKCwELy?~T!>_oBTCg(|(uEF_pRk-h!Bd`7bt zP$T*T2{}pXtZsCb-viVF;9bP%_DhP!t|IQyunWwTZfNenTfG4!P%kHH!b7Kg?|keJ zuqs9S()CwCl#uITREdsP&VlE*n8A@ zo~y%G=0ANoMBb)Znp*DWoRe4R>!fs(%H`6Lo|z*G7dGt~2et9v6Ta3$jS`-Gq5}1m zY4}7$d5%(Pwu=RSwJe+HH?88tDZrjcHQ)X9J><5EdWXv|N(+u}`JJe;&|O=7UwyzL zO1!J#>PH9H2yFoDt!3#SMyMEBf@k#FOsia@p;(L6yJCizXd#GWqj=0#XMGTC27+>H zOtZJGZG;eMxl=-LwidLEcn`8+Lcj+i>|I;Xls)qm&Og%XTHS9G)=LGWQqEdBZkz# zOOb{HsytS3y;;7oWiU)#@ki=`e+GHkGSmYKWaqlR>ix^+K9uH z36apbVQpoC4MV=>%Bak@>FVz7lY#AyBfj=qm`b+}25WBC&D(Y}=x4eD+l3WdblcwZ zAoS1^8Y^=as(X`^EfH?%{CLPrrG3NP9e+Rkw$wD(JEB zAV)JZTnBqcxq=iphvvM=xGQl)%fn;ohV^+2ykT6JW!hXU{hj+h^^}(WS^BN2cK3np zml7u?{uQ(Ax^pu5yuiBp!U3Z})W1LQi*Mba?WBaJIEQB%nHfy-=bT@fh;~LV({zp3 zUFVHVhLsNLK~<6xgIY(fZyn?w$mp>Im&6hHS??8ld;3h4jIg36VyN9v-TDOX!f+FRKRLVGQ= zKgK3t?o{Cj5U(((gI8ef-CUL4%9Mq&sqD z^l$i|ZvW}G?tWTGv`4MTQ;?u{5Bv&5Ncb%euC_2ko4-sZxbtZKjfn(y1125I<~R~O zGosdQ*!4W8Ce~|@vFz-_`DOjLL#U3Dn(bFM4|(SR1;z-thUNzxy4vCscqC>3p4*{{ zrgIC;40X0SpCN@rc0Ihkf8ATFNs%9XuHlGQ?Qh%uPPo7NQg~NSz0V*{ zFGa>-yGccRE$yGPzw842+nYh3iTgZsEom270Nl72_9Ju7XwVhNa$Ry40T@cl?s=ek zNHbfiZLBp*AEXhS(|OM$Vt}z*%S0mXZgGo?F#V>|<@2R#D~ZL@SGFoyrj%H~)Q33UqWPWTDmSsMI1R{c9eXSZa>{(f>5bxjxhAQXSa33Fun5vZ}kuAq44 zuGP`C({_gWW)RY;WKPQnXuz7!M)mpg5r2d|YI+PeCu)BOuZ06uxCmyE>(QnC6_opE z7Ud&)y2)R+U+vwUT*S?rybKU#Z!|!>7i@d#OWqwAXNOCHq_)=u+ z#HXcEd2NjSkT70;oF;l3M#mkI9j?FTqmQ-<{k8EXqjGg*xDa|LGg$>aXK_&=GCxy&fenv=6-P# z%<7$tzy;Eu7b)T3wc%daWQ>xXg^Ou?MFu(Jzk&nt6}+N+cDLg_swMg_zZCLT9jS+I zvnKIl$d=~ul<=#{M6WFL`Br;r&(X$?ksW^@nwqn9s{6abd&d+|I)$)aBqsI6U62%B z<+H%4OY5CnBZex#!s#kH!frAyBu44c{pqRr4<_ed^r52zZ?a?PVK2?3V0mH)K%C|WEkh0BK5Z(9$d$De%%$l1!5W=J^D9v;)8^_Hc^JH zG{MD_WtDgH2XO=pT3yWf-zARF(CU4%Td0M=1E69HI#hNGvNo+R(4pk6H^`_6H|#wR zd%EM3f(w&TI=ISO+b+5>0=(c~gva7qc1a#2w6+Fua6WXQp|h8EYp4IZG5!* z{#p~|1?oxkz8~%kIT$UOPNwP8!B?zCsl5qeqxLRXC&lEiC~IUoQcqokY#_R6{8QAg z2L!h2^sE`utayhswRlg{#Lwq*Mq1})k-QJ{{ijj$?KZUrM6?S-1li7<3H)CPLF{`t zoEPmOGKq-urD5I8;4>D(U4_WmTjo2S?pB z$-f3)ds=Joe;OYai%Z^_+WPk&`h_6TIsIcW&ClK`3@8B` zO^eamA#Dto% zmrs4f;RXjebzgq`*7vWPaa@@}NM;ApozZOyWR{&eK%P7d2KLimn;(Jya|FoWRjcG- zH?LZ)6zP&b8a{8WEDWa?nEud&lwNeKT6LZKp4m#D{ppL#_w1KtW$_p3=jbqzn4nN=A3(n-AK2|bL5I=8G|ULJHZxcz&jLgL4{7@% z=_O7nmt?>E!KISkPdCS_u0Ja*IQB)BFOvkoW*%W%bN-W}M+B%eQSYK8*tX;~HZmxX z`nXDGD;wr(Yy)-sHAd}AU6`6D4@o+cH?)#=tNdUhH2>X<^mq7~xvv`7(wc#V(YUJ_ z-pOB9{PX=y;pQkl3s&&QOW2u_h`$>trD;4P=M$l&xITmvYeZM5OxFF{cpbRSXJE~& zahZsU%x0;3svwrvhb+Pk!7R0Bp=NHkJuG%2==_{4L#tig+SAY?jL*3?C%#Mn0b|JA zOYOTP*X(GD@}^$C{%AlO5@B|)lZ=<=$B~0C!d4DSvfme>*PyFu)m@GE2T1J&Uk9T@ zYvAQZJc2W>HtpB(H2V%{WRVg`GTH*mBxgX7`Sc@MX@ zr9>o*Lp0OYTnk(@$wrc3l)_ftxN<0W0O~4}sSo0wjDc=7((y)fVfDf>h&i#bwfc!2 z0v|ej&0*^$_soAz`0a1OM6mQ29GN?myv6-CH&DtRB$V<^NJ1I324se~ex#xR1PNaN zP~3l0*1%nvf~DY?#yQ?bTag#IUMoK*r`-BG`%t5g<`%NC&H3`HA=CvIs>T_q=a%kV z`&0e!v_50oSH;e1TnvX~jFlB<#^2i7DH=CTKrym5%J)j`V>9UIBdFi{j32pZH=%4wRg`)r;uzCaijx@RvU)CZ)275O0BWm)3nu7{_2i@;Xhdl2vrC~ zs(-THCWkvrIZW6>IQj&@a_s?o}Wg3$oCrr{)icCW>EN4&5FY zbe({Ujt+aW&c%0l+P(T5(0WX}&`mB;{$--(CUP?sOGWkkBETsuwN+}2Hsp@LjC)MD z<7SWWy65(xkXa=yEUTsBxC7cgIq9dCW7+f~OW^HpZ+|E&D=TKs6mn%>*PnWtkl;9m zMG;zw_UxOvvcW&zw^H{#bYu*dwTEa7NlOr5e{EA zMs(`(TeKm%u`a*V0I~(Ah2U|-`WB7%p1LRblUS+Hhl>jE~e4Vd2;}sZDK23zls$TxnbtN2o-$nrWU}b--QZw&@fD zNF|@+Y#iRdzac)=UN!UkV0&MCvoiGqr}|UfYtj6v>qagYgt4>1)g-bt<4AW6OwHWi z`N8Gdw&;);Wf?+}sbeOU9mWmhj-yC57aFeC6n;c6M|X#KX)+anC3mpcwzS#eF1q`t zVCUjUheh9}#hNo>+F!q}yx~O>A1DU?Z{{~9tQ1|)T4|fOLCb>mn=o?KSd7=`JL3+d zmdd0kQ|0)?kEqgskB-Ys!C+kCbcTWBy573j#Vc=diVt&n8(}^7ax@-!AdF+LTOHGb zcJQT-Xc2HPKA;Lo4cYTgu#8Zk=+mUJamWv`G?;Uc?cjP@58)*QZ5Ft|QsS&az{xvv z2%b@nRX114t*UY8Vac4y8tEpX$qvfZB}nBm97rM`LBEZp-&?3<5NR<-)$jopyr=h=k|fVK1gnJ)&kp+dy-^boDw2)y-)6Q8vA>by^Wl)vh2W57@z27B>N)lSU%=>PqrLc zMx3Xlyd6VR%MOl9Thgl2T+uF6V3?tUV^avIM?X^s~+!j1J&hpy^b zgJv>qq}H?$r#)?c35c$`O^w4(G^IUo1xUiQ@LWiA*HLHEelT|ymRm96+$(HWl~R3L z#|IpMZdcJBlNs_fg5W^+5;X|xwr=m(Qr7xXJ1E2H14k3EjGjaz?x}(vCeX-5C|qpV=Io!blxYJZB7Uc>YQC z;MS8M#o6^6WsUVJwy}^fxoWRx6PXGHyT=IYL1X#~P$liOmF6b`G@$xJc&i)m4Cn&o z7HW~FJ#oJ|8(FI(09ysHC1%F;R3hs&0V$?~1s57;rgfXA?Rm?6&ufd`eN?q9CMNyR z=g;DMT`p2I-Pg6GR7o?r9?d(^&F|085d>bC>=P82=-_mjMOFU>eWPl5x?`YBxo%E1 zozU27!*r6a#(6p%2z-Wx+>PC^Btrj}Xy~PntHr*kms>tufl-5=cT@2{l`Q z0pDcq6VZ+jySd|@xhCX<{}rB0jj(6-%qT1|{x@YUMZ$C=#ZYN6UR|KYA`u z_g-&hsTSgdh56wt_di^)vJXLelUOWn`ib-QNoTEgO--^Oizi?`nUt{9XUo2W!L-pB zMfnR7S0%$x?FUdlEc3pFRcB5nmpgVtqLwbJJ09NhETfNOW#;@;jr&K#gS?`lfJgde z?JG%1g8a!HlHhIC^!)bSHNVXbdi>4-V<#(am(g<&?1Sb3?V=-mCj3O%EFGs8XKuRM zgpp1FT7`u+%=}_zgIF+^=vV8RGha9b9s3Mqg-?EOZ+?|8guoropemMEcKG{hn`HeB z2GCObX~0nZMtodGB{Itl19>`nSzKuVaFpq$Sdv>%vskH7n6~^I^*&AG4LfRA9-ji0T<#OtCL>VV7(nx7;Aa&vc0s|vYPmf9WJ2eEQ-T^r z$Fw^0J&$0co9U98aSO2IuV+r)B?hQ($~{4}%v>)pQ6_nvYL8A0{V2+((gsDI+4whe z3&n+nyh0qt+dG)%;HIQsnpm=u#0ge?&d9(_409s31>U^ehAkBr5rp-OJXRUfKZKl8u7C-`!MeXi*$V_5AXVvr9^%E{q%%lP`GqaRg|(sPZ$MM|5{vS zUMv@ZzsT&}sH+?C&acKWL0;>u|EyoPqk7{u@6~B*N-MXlyS?h<(w~QSON9``8U z2+>`Fx6kM$}UjM z(_i@V-p5Xi{V3CKfxF^m5-`4Y$Lcm&4bKuReQT@zXwLaLmAP@{#u35BIL}SxX=i`I zdUi%UWe@)AG5;Ds7qkIYumhG*xeWD?ub1=%}+Gcf@(ZirQ49Klo^-n0HNM&T%rrbn%{U%2RBm_a2SAZAZyy3>;vSn zNlubbAJ8jK8xWCd(nz$R2r!In#XB7DU}c?|bgY{t=A`fN5otp*;9x!=5YOL}n#>$6Vyq~k{=#DRv_7{hQN9zYpJ*ClFCnxC`G zs46s*Q&VrAd$I*tq^p_q1&U73b&4^u>>Ku5c&tmTe+Kq~Ka0!c{&IcVCHEW6Rf2XC z^dPMZ*&VtOWe(nIW_A9XG z@S||yWYhjLGYelTR>pp0EXRgt=0626nGHuoG7BEIbNn-Yo)?VS6%=OiH`Bt^5d(!# z#;`lgCqbj}7|W6k!|EN#>{{ar(=WO<<9^~w9bOaAt*Wq^u5+zHrfD*&?59djIUdZ$rNv1T&~|Wv;6Kd7G=fy-wTO zD+UNBKpj9lo-0bcN*n(dm?u!XFPn<*C&1r8QQF(aL(8D4G&(>IzNybqQavu zo}fpTd`P9Oo>O+f`BxEiQ&nsxNa72F`hCy}gHo4}J#}*;su(g<>dA2h5v<+6GAAfQ zR0!?+?#WTBaI)3Jk~NY8f0%YO_SPi!qyNF&xOAhWBsSOp^9rKw$Ftk;aE2ch>zn*< zj*Gb*Itx(799xa;jq-To5tF4FN-4LGHv>-gytov=J#cD>J<$68zbU@~B2)I!xrK?( zTkZ$;T$0qFZ*TH#PB4nDiUAvyEroWP>HSGpUNQLw=_jM4I6I>#*65}G8}trD!Bk?T z`3>Cl2Gp7TwbTwqcR&@20$Z3a+g5HOuGj47cD&`Wr$;tj@pS<3;mcP%$6BJQ0%ttw z%ZRzk<8K(L8xNH1WR{mkpDwcxxtCpV$XY7GFd2MmpCD|RjZ3pIoL$a>hHPkzx)VF?*Y_W69_L}Q_^cYLbzWYiWE*fPi>1HwM-;p?jpzjuRUMTL?<@Hx`( zZx5d}+GYZEQL(XpkZ~JYhAanA7M+tSSQH1OObbn@_~^@_I61`v=dx0p>fHMjWa-{m zW!_zEpP6_u^%HN}&Rt({ha&v~gn7*qyda>eog~&eQ_0!T?omj#r0qDwFij5GX)WCHyMwjs9NaAM+1FAxFp7Q!62HSC zWH1>XkUXIm`lX^$ZVZ!QCw)X%_g07ey8bR6Vnnp zq9L{C5^^I9lq8th#pJqFD))*M;T4}g%%g7(;*KlK#7__CU_RwY?VY67)q{oaiH^L} zStBb!DOY}`Dq?_0qge! zNN$ABP&xv%x6~HOr#YVEShX$(+ zmV9~4&6>L}v4JM4m@{|jmO&{u0hdpOba)ohX0(6hmfkKVf>|jik>3K-I!xdWzF7lb zXuJ+Ft*foRJvJ=>9>GRAJtN6uq2{(d*>7sxo64%c2N&ZK8n5-gTY1Y6-PT5b$6Fcd z{JL}dw8hUdoA2*BlZX-UB9PG~%e1OO$WgblFyR8U9yRyJ+7s>QNzW=!fc|S1@rW)r zchI`hyUiKr}|I51uEwkNGRY|hYP>^=Q%=9nU^ z{fjEYFAVR<0brPR!W`Q<4z7b6iAZO`ee%CD=Dr?Ah8n8fZnj#98nu6>ZLG_?+ut${ zPjDte^LHMn@*U2JParXRw4ipP0kkvOB5&hN(=l@~SPJ5!a~U0eUOI|;vRgpyvfrn@ zUW%T>R9Q<0Y<>|IJEuTaow|DC&qJ|p=h(Ch!o3Z?GjzwO|G10~mThP*&#B)p9a4$~ z$`}mIA&_|nJ|Q*Z`@?cc{55Idqbh`2A&L`F3w3x^%id)_ma)8Un-;4JYeZHl(W+~K z(T&yaj+POD)O&bmOSE4*%gVyiUn-T4w7ACHP=qEdys_#3g&wWQ!-1AF1glPvMGdHQ zJC14NbQ-m{9ILD-FrCpkYrMLnX?KVV)ekEV@O0O^s0-rut_e{c)>zy>2eaRTq2A5I zrDeP_ru8X8$w*Mxu1K7#UpX1v|F=ONn|JYXM%W-(@DBJ62qy&qZoFAVo^Cp)>oIOY zK4lUrWVb!tbuV5IP*EOxqKm@G)71IzmkL%nJwN9jLcr7>I^MlLMjS9U#=*d4m@l>ZO5$Ggb(GR^Q^*ep`lSW-Vta-9C5H3%DHD#N#FO=6?4Fb+n@igCYY1**A(M zHJ#YBCqxp{1e>{lAj#pI{h^8+*hVnNG{gKFj>yLLxK=|`P4*yhLcL>uixcPsZQ^J? z>d$UZlo_eR6SK$7o;?9PHj`V1xhP#w-gWitCC)J^A5<0Zs2j3Q8DxyCfR@G)7NwJ_ z+oh1xDj8xxIZ|~~Zpz-N9+Yx z{j7o`-13T_<_8U>yKpkjw{9|$J%e^#q4IF!F!hr5sB+Lue59vNfhw|50C}}g`)M=G z^z!|hlrBm&HYw2mo!YB1!6o$#agW;e4iObrD6Q-%#ARjSJxP!^6KN@9Pg{3t*`M2e zRZmaUQS5}+kJ21%BS})u+1@XBCe%Y%+Mwh3vMm-X%$ue(YJu3Rh4J5%GfJdw$}}X9 zjBaN_qpDuR)b;0oFu4Ag6#f-4KV_TA;Nt&3^a^Mif1x+CuetxwDte?Tb=O4C0e<$A zv;H3rtw$;B{cp-wXv?`r4+F944VLx3a4fVQ&x6y}dof2%kn!o#O}m}g2Gr+d)0(Vq z#u6P!gLS80kS_fija`TZW?tjEgVD|pQfWZ6b9=et#E$+uTkQWy<;=M92lDfQ;p;)s zf3+axx#P_(be7|C9jaPt-9i|l zvEK?bsM`~%E89m$W_(s-8lmuu9P2q2YaMI+>UI_v?dbDKh@XTcCPbO{>0WYaHr3>k zHU^P0qNeRpH{Kxz6x8PGLiekO@oF?9vx$jx^_SKas zLwR)sOstVNm=A9doL@+BRtnP1u{fHnx12Pc+z793{0!V&|NPj4{N=dheggD)8)Z=c zU3sshH_|{<;jJIRQbDX+cM=}Q9XcJC0qUd1nS*Z?HuiuX;W+QYSZB&f*6zg0gc?fN zmQkpl+d1Ttu0_aS4}66@EWbQ{oQX4lmZ?Ie6M2j!JMiKOI!Wv>Fm#E7x`e1Dy08jJ zeG+@!W}W6X|G6%#QY)RQYYdN~>r#%gTb#t+^^Lbh6N0Q6)n>$=o;j*8FKSgCJ-910 zwQYGaKEZ5;8M?CtbxpX~$=Fe$ic-v>{BY5qQ4dJMwem32qGW9w5HtGS37HOXlxS>H zd)ft0Yc+iLDk(9zm9@P5NVkz%I=Ip$J3jWcM3REr$8P?z`&-Wg?|nsi!VoRsSnj=0 zj~PFR@SUWpmUE<2i1#rvI6;~F;whKae)49@M9J)O3;7*rhv!pqPHpG7-L`_I?Ggz! zhus2G$^VLuw+xc&5lh*3!FlUDo^yy;b$UiDT}SOERH3ZGm+jr;Fo6%NFd$j^qPuyc zXI-s@{@X<2^ovQB8{~1jdI(3aerNgX1r+YMKpK$I@#V6CH*7}Fl^*0BGCL+ls!(85 z-IW#sC>ui45~0>cI6?Ps`J)NU+Pu3{r3D2;u`$}rbJ@>AHk7(9mPvy8r0w6X;!3k= zE$cIK%AYetLq4-ncrjE`1T&#w)$FlzI*B@au@G6@ zI2RfXb%{5acb@z6^BMbWDz1S6KSVz=?H*osVyE$oJ%%-(os7h%;=Tx z-Bk2I^Eti9KbLjXdgoW~{`40Jo+(Cwd!ZrqtVCJ?oT%Ia(6yE7{UhEW}p%^LYl*fQbdJ-ZtV% zT>x;AoPEN0iHO!=HSQnC+ch;D^|7NXt&+-j$%AcAX>1rCZVxhEn{OUy8g?iyW_j}p zG3Slz>CZc>ZeVV51^gu=JWR2G(AlOJ8C3N9Ym*9-_-%BdB*ql1YUse^fBtXERtlig zBhc2_fw1G!u;wr#tB{bIX$bgSQu}w5&No`dT z%1@Wex@d^$;rMp?KbE|r66zX-g~Ld|ugTSGoJomBQ0KOBuY!hpHvTs#U-Cc}PMye7 zn7z8aO2dYuounZ!iRCiGh&->63kD;ambm0VzQg4s)*pmmyF1;yzW38eo-JP>l-L> zvG-iPV>LVE>+6VjT~kfRHe2m?t9f}~RxOrB{^opz;=mu~MRVd~%}$*i$NO0bR=Drl zdC9n;9{>l~u|dky83(cb{UR{m1}uK^xkBti`C=fTIy;SH;(**%C|0bHUZdF&%Z)N) zu}tp@WGVzklIub+kDlJCyZpN3cOvy0=OYJOo-FL*(=0&K7{*0(C9w2!$CGDqI)V0u$w#)XhR*edAGr@XXm8 ziWxwEmT98?JHdlG8ha)rdFe00L0QCXU-_imI>t2(D1Jvfq?pDAegpqQb1L|=V#U!_ z)JvZ6**FKw$9ghY-TVf6br%kocJ-ZlCDR?*7ZZlZwA~b@hATM-Z3}7aM1+^z{$oO>iM{ z0Sv2n=ypf#%y+yNP)Pus+vwBgq;{r6GcvT_c8g`bG{smXVNiOQWm*_Vwj{Gq$5`3} zFMJgNGa1!5{*L1a{9 zkcAT;b;CEH6((#x1{bnKcT9t-GVCAxJ8&q$>L$zVeK^CSZ-MOFsbGxj?gIfW>lU>w zeDQ_d6szcOZ*21(vp=iMg?op%klNV@;1^3Ov8uRW6%D zZjj9xM=cz1Q{BJ1#_?p+MC#fKL$KflZODye?kZ!q*>6KK&hJ}rWlvCdNj26n;31rz zNpu-RgAWRJI5W@k;01Uix}jLI7=A+G!`%hw^S$$w8gcij=ry8f7I`|@hDAPBBN7X4_|ruDfoaN%wMW=ON}RT?X6C!87Ur) zJ;65ODzcSu99n$A^`Z9~_PdwZih}SKJyua=y(~}pjiJF4EkFD{brrARM1QgZa%~m` zN=y??tBX_a8x0-IOx8mmdPwFbLOY_;fnXAxV5aF3NS7~!I3am?rY*YE!of*U^%Da~ zXI_WrSP4}Z85g(VE>G9CRGi)40Cxx!Klz1NPhlQSiAq!{w27`uWef?;_4S}%M_yq_ zT8nh-VSHxo#&t)Eq4^2Ehb@!@^LHfD`-ZDZU?P12pm$GlBHbsv|-NKR=W*e4U;De1E0mn z*zA~aMI643?wDuTWz#UIhtj!&vJ4QC`59|kqwJy1BmOv{{?S<+RTEN(?md<(Wm=3b z5eJUidAMbnU$SNl{(nrpdpwi^swMXVCLrCGw3VT*;kl*O=Ym_vw}Z4Nu{>ih2V{e2&g-(Ni*9kyK8`~5mT z5AuM_D63bP0{78TzGSUK0(7D6cjTV%=w~n0r%-lWoE5HKEj*HSpg;+w(!hMEgJ@Hu zKLRzp1mP3RqO9P>i)FP3vRi&Df4SqGf#nb8nYhUP9nRfpi1rRP^}keys$9yI9rTpY#O| zg$BgjC5hx=v~Kl`rySUBnM{yP2^Nl%eXOu-ea=v5<+lXY`Ghizv18+nV#DJ zaPKkV{c`Fdp>q#0`TEfYi^1n zq%oJH=54-WoHgl0@j;&nzzRF0GnszEDeWmP(H79+fKB@>@>3;j;`0z8c$PFbG ziHHXG9rFC~vQ;51gPA51npyh&sP^W>VWBYPD1JG0`lfBZL2FE&ta zS=!xE{5~94pxFJmWES9kb5OC&V5Ym~3~UVz%t{RshnvYuk>}B>gRbk!RZm~KUX$lp zNL3!O#s|;TRhgOFC6n61VJ_nzAu33p-$~%Sa>-9Rlj_TD#YmM=5l3%MU>Q&XUss#( zx@&`>Ze8>M&LYuez)?cM4nvC)vl=CM5VGo+9=a|48#?VHqTX4q?t0Q`{HBw7ZimgI z%ZC?ifB&DsPo_}`-;^wH;U#9`Z^xcJ-~mnYK`DupG*P6d-#ntRLsRB}-LdLrpU<@V zEUi_e?a5c`?pZfO%XN!nQy<#C8?>;1D^s$KU3qs$v$!cxQSfB{wQHFw1tb3?lYz?r zE0dXiX8z)~nB(G8SX?*@pvmgr@q6L@K938ssx?(P^>LB@Q35&*=ORFkfd_6_qzh18 zldl<5m{iKl?$ucgpKhm;_I-NcIp_v?@L5gzSEThu(WF>$KKr$vqTNwV=~MgN33EB1 zJU`}LhLV%;_%u0`=<_(sdzN01uNLFH;{)osgGY{S#(r}^N|Q(hxAt+@7xw&De#Tm$ z5ry>G2K|O%w+SNz|3jbF=zTx{EFw9{GXDZ+*ftjLKo3@n1lfM*KT{g zg*W*GxyjP#4dZ5{dG-gC{&^afv-Ms-5+Kl+{q$-Of67Et!Qhj6*2NrFh`3>Rx5v1+ zQT87Feu9M@H3+@)l-dv$$PF7p_SEn~XK>GCxp(Y|B-FlQ@_#esKjp{7Df}EaW){3c z8I1k~?b3)ZzSxR+16NhsL zA~*vJ0Q4>7vZnxTYK~s5JJ4CB-^00&F@HB?Y}r&EXEKYc z?(5ajuV;L{mzH%jYR)WsR(-8!7EmnGnf5_N&uaa7SX-d*NT+VauyQI#U<7+Wf)H*7 zkg*~l&S}ykiP0TPNwg$0kqaxWY=E=tHEqPqe01{ zc&My7lM>w-tNm?Gp-GQD_pmI%geu*YfWMeQNTkBotJHec;nm4{h=-fk8`FRkiy@=*(r3sjAVbW`pDY zOKZnuV_E4-+V{KkpuyMOQZE!c^8I*xE?VsEh&%-i{Ec`mm7Le^ff_(%9)`O|t`2Cx zWv~rN3kmM3WPLSgEzwQVB|v+TZLG6?()v7-d4(-cz3sZ+JR&~tJU|85KpoR1^7}qZ z%*XOZecSr_`>F9#s^;urXXo4*Mlf5^0|moeO|)*Sz6$v7W|Hg~V3wMoxTP-yo4bkW z4(0I*S?-NG)F4oAPl(R}<;SI(8U}ANxV(IgfS>vL zPZ?7p48Xl>%5zVpfM3^vM`ZCZHe)xq5Q7V3H$%ec`G0OpMXn@REF*lO&NO?h8-6BP zScXfDuN)88qiEwZ3#m`x{UOFO+Fc!PD8sPP@~8H_=#_~r@0<%tvIajbI8a@vJli#g z;e8bV#r^6EtzT0Y2WokZT6s+!T`*DB<`xTxb~3fKUPQbfoeZRwgHcg!u)q6PARh@@ zr$u)6ydRL>L)Ax}!+{50p@S8;X9keSLoBFJkf2m>;~X`!b+a?y4W^O+Vxk$LvdYx0 z{+B52IjDHiSICypT(O!0ie-UIoNN0ec=aamH$+I5R0-Cy;8dY^k!4bZYP)-%NcI6) z?F(DbEFhcf-whtebTBPV51}6={R&c_OS%4(dR<+El0mj-tIui>;M6?P=Rz2C4+CCe zrTvwF(?5q7>#%?;b8t}FL|qUFOr$M_XvsR?L3V1tsqB@RztRle*&UY>Y|Hn0@1#B3 zP$%T!K5;_q`RHIvcD^=#uXaxb0Jya`J?h=Pf3s6?{gN_am>A+@ z6T#A=`u)voIr*eOkg5R}5yFi73apQ@`0Q|LhQQ%|#9jsE1;I*{LirM*xPMKV07N}Z zyQq?v8&ivX6RtFN9o~0wY>VF(3LmnY5H5*qhxoV|4;58+5Zso))>xi*t+bq-`my=6Qx-at zxN?g6Y@GN*9!S~*jA9ZbO_(9XoqiYO>x7p7bVlJXRkWs+swX-J5YU%eu73c}oM3c; zaI&V0%6{FJpVp9|^d{Tf&-FX`d}Y5Rsf=WdtS3xikET2TIw6oFKR8Dd#pSMi=@ZTG zuco-1jc0QKa3CIxlvn}3lwI;#te-{Bb*xa6Tliqg)_pgto{uN6r~M#Z?$~*RWL%a_ z0B|8YPA7U;pIYZw*a9CR(hza=Y+JQUbTPOqrE5<=>B=Vi0W%`^j96_o^4%?Lav;u0 zSLN+D8X3))|)zybevtRBbSjq}J{ranZ7qzN&5UkF-d_ zir1Y$O|Iuws_PTTO^ zpg!-!yR}=EF*c{q6neI<*+_G;^KBhb>zh9p#aB&8G1lmO+HQ6)KPUB@KlxfoB-8I! zQSm3twGz>nhJ=z})SoENS#fQ2G}d(=@bmsub8^@9{RX(V_CD)@<}`bd?^92UmBv*p z31WUX2((qi)3wAFRTA6}P#?eO8OX~U1>F^Ez^)cIeEY$d$b|kAyL6uNpFyxZI`(w! z=8YzGR~^RI27bDvMa_+ZcQbE##^FqvHMnLjQJRPn_R!SqXYj#f)h|=IC6R!>LH>d~ z)fFqY&>w}ahgduq)xd2=!qz*_-_+sU!Pe?T^p$t_XV#Ay#4psYlIv$s?VGt*z38D&kBCxO-sf)8Il_RX)Ko;{^^X@su}tlHu(AiSZ~6lZMHh4Zev8nli9ESui5vMisnd&(c`BlHVLMxpVU%ICw!%dC??xa|gv+9KH&%dPU&zuwi#Bv?hkW`4x5@x%QsVIP1m{#5pAt;a{NnSHjQ)c$o)DX_a~D_IdOa%N_5gL~ zVolyDZO_Grt@Z`eJnuJ}t3D()r^UapjFL?g(wudynIrxR&oXo^6Z!}Ei_t4!fLo9q zl!BNKFCLdM;Kklr^TY*10TLT6GI39f`&tz~;hCxk7_-XV{|YM~XEuq_UH5|{^RLjy z_;U?YduCKR>bC56j&t0~DDJHE>knXx*OG?;_fWx5@R$){L$TqpTXfZy^Z!7Fd$4 zK-D7*5r6*r#l<_xR6bj2$xwU7!(WkE6~)Kgjv3ZtP~O87*!$!U&dz(p7Le_5y49rt zF{JnC>Wl4d9vojwP|+Mp&_zr0W=3vi*u+-CW9p-BAfdCmPJS#t*&ZlRD9QOf9jQ@c z8lVF{ABpkp8CKYUmmI3wpeF@#mZ^;uWtH3HMsamx`@^$ zl{3O(wtBa|CI)Be>=|J7o{wy$!OP~WdIk__WSJjV0JV_k!yCr|4Fz~9Caw&&2$mhL zxI(bG=0@g7jm|_OdPW6flP&t+KI>lz4U7l=G;np`DRCoMJ57&LqO@qE?4^=fN9U43!E`B5&}UL5 zjB4pI{L~w|C18gjKR$_-PhmWeFh^$b3wMRI5V0DyAV9?Mil`$Xe)Po~fRI2A8`P0N zlw+@Sfl9;)*aQMA`)+J&fmLw()K>;7CHXjSYELxQiC%-Erte$ZHkndV#e0qB&nZ8A z%Jt8#hsqiLKnhv2U|?*LsJ%$IB)gHw8%TZYdk z6b}ZZZ`<{UOH>|rW9XBG_r@M$@33&*wP+oil7Kn%@)!+|dEG5Q+6Lg~uo^2IeiU&# zWHCU^gb-xdSdNx9Z<4)O$V~Dzba!Pr>m|i}ixDO{IWs!UdTWd)$2S+G2k<&n~@&KMi zF>j&e0g7Al^0GAplNttul|2SwZ%5-2C1L{)|yPUt^hQKGhF zMk;vhKZ-Gw=}F0@PlsZ2X>g0xJWFjl3xD-`aAQ( z9|fg#jNrpNKgZ%sCqlL}JKY`BN3M4aS`l3|1|;Ba$$E0)tsv zwk?a^r_+hX#xZ0NS!Gd%5)&C-@iX{2NFtqy{taxaLMb9uJ&=5O$SnO&U?3m@QT^uT zp{4E?-S(M3p~eNDxT(6|4|UPAUIYt*3$}fe^cO8$iODsw^ym8&7S7e8z4c#2^rC-i zz#iI&kuZ%c0Jt+3oFZMdN_&JPKvzmL+~*U5WR6XuW#h=o$s~uTFI9-M41Sf+#tUAx z!C!sZHp+%E&xy`NH9TIw(-0#^bq081*#*@;g1u_J!uGL*GAM$;MiTB<2WfGz@dAA* z5+FQ#h6k0BvQX_Gx{JV^sy0X-p2jliOAO=>*;W)?;^*Z&0tj3C_|+Q#Q!8{>Q+m`VKWGH^od ziTkzrL_h~f#d}8izSSjnh~+H*-OoEw5@jrj&Zn~UCZCZidus((pAUuOQV?|8Y^?{} zPFkP}eVJI)!d_}uXH`P*lD}XM}mrzn%3(QEmVI+LMb zi{@3ARz!NduP#($y;~GGp)sqWH+u}ZJ6f(_{z@n*??3b#qh% z=razJuf>J02cv%7`7O=eQ<%_E&H#EgM*H|e`U8{Jgq3vi3iwI_GaG)=K$Aw%&hGP!qU2qUA2{~$2$$EZD8Jp*~AMO87*mL@(bIJ@14G6!~;TjLo(9C7pG&-D7*>a4cPH#PKp*H*0S z=TF}apz{q<@!b2NY)=uDg(pnXAWkLo;)`PP1tlM)dqz`{ER_n7#D6+5kq1hA30fPv zb+j0|dPip1m~tMF({pw}jc=>nuie6AT{b!H)1h(=HSo9Az)aKarE#;tEKq_3Gm!1W zNgDW{wJU&`4kd5sJJN4e{HGzFLY#?9= zfRQ&t#7yW0$#(|5ORm%*sr$G5^Z#_6I$%9AH5~G7aU+oj{WfgS*ubRV-|x|XaOjq_ zQGChY=AAOR1Li~&4INSpcbmVBV~H7MTO7T>3}AX}aMZ zw0~{p2c}9dD;-fTcX)OBUX1R{*t(X3$w9mx!^u8m=W14d_e8lyZ);8}$c)L@2A41qL^fSRtHYdGX2jqT@8v4z7r1u*XwL@+` ze2?}?R@W7J_t4mJw$_cx7DTN_8MHjktj((0WF0oGbNp5X3`si1?s{o2kUsqO29&4w zRa?q+3jix@b0g1$V@|7?WEGU{^|td7$*!>#q8Zy07$(CvfmVdhQeTR5Uk1#F=Iajf z&&e2nv33)7$y4u?Z+?Knns7+FWH7DFYw6BwO0(tT8lt4@vr%Nist+%cAAH$Fdt`+X z>-Y8p(;8)O^fN(i1|Oa3HC{IABZ!ndo6HIyv(lLjbKqa(=pTAkgCH%Z%-VRoOX^cL z<{vy?^i6X?(^h=#Ya)lk*i(S?rQhre3KF7F)n8DT8UID6G$fXk>ogef{P;SjC7_1# z)N7hW5O7>oEddR81i@$+-J^t;)XFeJ0D)o%@HQr#VC%Qv4NNc3*;(k0$P`5b;31sp z$G|^z#S+c?$Nc4sSdSvGG5=2R9pe4ZwbsynZkQJx=+(!{M+MXH*WOdLNWThp1yh)< zS4hMncr6T2ssjUD0Y;G^XX{pI6+9W^CTpMWH@OvPzUQf;_(wm_A}mz+lU;MxZ(N~4 z?}L*H=eTNM=hf;~Uxv7U)?oeLM@*lu5*2|xw)=1nF4u=4p8iH-mlxR49M!L(h7G-- z$zOC>e`727QLD`w*VZHXiOdg6i6q>?dqqydpu`$`K}ZPH+9CQ80Es)++tM)b8r2Uy z@|ha=OE$w54;wyqG17yz)4fEc&cHMwTqs0p0bzuEjL>*eZ0!y@gJ1R?*4Vf~A9X3< zKq?qnpd-AYuX`?BB~c+>L~q#y=8V5&Y&Kjc9_Os!x$;dTS3O{XL%T#DqKO;tm>lO! zZ$GP^gLw5KbR7sZpg2~rOH|>CA^rWm`p}cr9rhUr#owx3r4vcTUmN?oKeTaMoV|jB zdV^Z08!))bpA;{@Ubg%%plHRE6~$N2G!N+z(+11sJ4-!lY40INEMiteme`m`lC{?F zm;6VrLN?TA5y6zo$b_>V{z;vR@X>`MwjU^p7poGcA--qGyFdRNW*19Yf{78_xVS== zsxviI6zlU{!Sh@=rl(h74TmX>^?1K7_C?=x2lI>BC5b|gl6kKf@0c8CjYnCa675J` zZcSZXO)vg2c!1h$u(I#;mJ)4wAA>I&pK%JS4Pf={ogF7-Vj_44b(eNXrpl#Q$z{Mq zt9$e2u0SAITh}w1clJiZ(&YzUcyk=*C0zB{xp@AIdiM|gc*RU`tVmi>?Ic9|05*oN zkAPN~+F@=s`BZycC{9|11a(k`tNxVtbc{^YgsunHYK(zvf{;CrL$B{izrMI=A||P9 zoXz2nvaO~aGVl{~tUg!vfUb_KAn2b^=eDy4$ZWI8$=FW^hR@_Hj>$8qFd}G3v$qxEG5kMu6ihMh!K8}^4b0qcjU`J6{ zy%dWT{Q;%9@h2JDqlY&1^^)hg+i|dl1v@(4%al#-r%#ZaL*_Ls7}+db-`(cpW=Wkx zkJvTY71a0l{QuMsT~}_oQ1eIJZ({p%%|yb;@Lbc$1TU0&@+x--vikwgLEPNp3Et2L zRcx6Ks<65RZY;+-GUju_cZ0dd=L_sAXY?LX0i?D*M#yU&`NbCisP{fLc9NxF*CNzA zVU9+RIa=TjJ=reMLnntgcRbTHnp997&RZCh4n`9fxztAAEH$vX3sthV)NMM#hNTSx^Dr*b9GVFC>9M zNyARCg+SgyuL9zOdu5LA(1rx_&jhET%%4<@W^>!*AvdElRa&&ptGWPjf=REfVy<}; z8lG^e=B@V2FXh?Gj+9x`{QA`6U_Zs596T}OZKWwsqo8Z2D&P<@J#&&T*ZS$H><9_8 zU*#k}2pB{lnp`qT4=hO;aznf&ZS1Tf;*Zp;{u)|5xwl{wIL5D;^tz5Ymz{Pya5n2w zBu{&H8l~hNkMhUc*2d!0S-k^e3=b5s>Ql)*F^YQ<8DdENBfsK@GR4n3 zL#~Xo^H51K%4v1)RSp>DfWMkM_r(OrDtJUKabKVdnK6-NrWS60Kg-p5?VBA}tv8R` zmfxk5u=;jT0psq>rxP)iUctfTRkOr8s?su;`VG9%#t{B*r?c!nm$1e$#Pbj>I+Qmj34D=&Ww-)z_TJf_<+CIP) z<0`7b`|~mZoVIMOVNm6xLR=4e;A4iBt~wX~^^chwu+{-c8Ihyd0E-Q(dbCW*B$ml@H-1^6H$PTnLV}EqXpuS(Vg+ivAw+D_f%(; zw>{gA2&T20+ltq69!=3ezbEb%xRbBeXHJ7Okeo+apo;`~Z4FZKKyG-GwVZH3ZkzdI zX&uI!LmDLl9#mU?DKQJ^DcaAt1_AkLR93G|Z9 z*4K-o=!Q9utzL|#2lk*I9hw}SobxP83T&D21kaG0Qk|~{Zho{`H_TC{LtbI-i@=~W zk%4i&$RwkDd$FNn?HC=)v;})Vzquu0~VhRPq%@E#*HHp&m4Qj+Jgj? zCw}NqD(9WjlJl=Fdgf|J!N?4h#qi0Ul73xVXIGgNQR@rvk}+;I>_JHcw3IB1u5>%&P@Ceg|6RY}VXwq)G!t z2LUkAL#`tvf6Pty0qzAZaHl(9YQi;<;;h54|R3(L{7-}(0$#`gqW`4R$l&CXy8#v zIUFDzWt%BMG(Q5VcK_+QMGs1l%h||iclX47Eo2RL0Lv>Za(lH)mF|B$&_VAm?un8z z!hi_Be?$^oh?~)7w%Dm$Py+$YxZ4PsA6j+9)mWB4WR^DTs|wFd@n;K3X0n9adovu) zK4r^~J2b=3w9>x?VxP786K#j6>vuv zvBP`?=aX~`Rjl}U@RqL)W&Q`fd!vD>GxsN-iWlNEI;tLr$^Gr9n_JwM0oO2@Xb8cf zzoyHTe-X|OJc?WU{0;990T%ZJJTf z>k|EUi-(eS_f=1WAa7F9=vpqB$5EaIvfR zJtT8%gzh3&6?Gy9gFzCZ-79MUh!C)4w^f)k{N@5hzYW|A)&tz0jQ<#0aA9qGvaXwt zcZ_!Y5G<(e@5`>6Lgjsfp;0OyYnd;`BYMYl>xeS)Py&aRPNsJ$Gf5j%$_D==2FEug zhlv&4)mgv%wk9xLLE6HfvSL(|&sJ7D?h)F2SZOm4xMp0v`{HDDyIr?ZT^6V(e`p-@ z#*F9*ZnH^Pn%^qzDhp?V8WZeB)q{?E2i zJDzlwQ}agqyUKqWT5!I0eN=fMAbQ1#7{elX-lGN+p^M^JBUb<1-N^mBfu_{mj0+c- z`2M*^{AsnFUBC!zn}KYdTA(%tmyJpA9W)VSDg5-=lVJakTLo^&`fBMC9f3bpOtTZCCc7McFpY#QBy952tbC$Fj9yj-P zt9Rf*Qj9o}ZL>mWnAwxBxE(J9*~oE-{fHN?vlgvRRzdG7*obkZpM7@$5jyqs{K!@& z+wX;r=z?M-{7P1Y>7T9Ahrbo21tkG}eqYz5@`v?qAWu^N3B|h?ve>j7mjB|Dua$nH zJ~bHQU;C6;HlS>R=11%l_uH#5!=j(R&GZ?qjYF?aM7}d$mhU$Cib$D!6*M7;E3ORE zlM&wr)6QM^D$bxyx|oLZ#Mgc=?+q)M>b^YkPVx8Gun z58E!war5wf%4L}5bym7h*#o=!BKqONd^C&*)o+Toqlk~a&A7FA^$N!@(3=$3 z0m|!n9a?55{(VRpoUcPo1F-qZ-y~c%9jO5GXZITQfzfrs*wW*Iam?9~)!EPE* zvP?paIpk%vxDk3E7%xa?=2gA@)u8;!s?2(>Phnq@27ptcM&|rnFyDFBy*mM$g07y{ zn~YH4WwRAO#O`l+f5c)h)+CbLb=LZ>myWAGX~*aGgOIk1{~(6UxA*K<)E9y&pbM$w zVL}*-3_)UeE#S4c)?1&h%7`kj5^B$E%WA!c&-Wa-b#~W(t9%T0?D@Ptu5D-FQ!Ias zJq(HIP5uE15>>DrX#9~BUzPpAOqI1VbK}6Fq~S@ zzQ@)FxNdDFNy_PT*q5buRpfkG0)ME?rWW}ISLG^kLX40)^%#0@JJs5p_Xl*DRV{bO zzTBaPFty2kudlb-$<#obXVi-Co*Ew${F-8mOHz`OsLp3=MLu^$4j1J%-|7>_}y(0dS_-s|=iKAVR~Vf|WY;^f_Ii=C$2y1epjMr%RE zo#Ip3E+Ri-Z3}cw^pzj}xw`V{Z%|Gk$P5N&gI=v*sj`B_+DF|z^!cG7WMyp)_ZMTMEJ94sQJ4AF>3}u7m))K zpYU%J$g(4dyA}(pgJ{6+~rS$5{4AuunEIe2yH zxK+7O594?qqf*QU`}{7e_P%Rc0jRFYt);MEdytq})p{Q1Q1&l3VdFjPpD(iNa^!ag zj3AM|h_e5U>OC0P185GE-zG9Ip68?d;X}Hu8`*M3Wz<3+ zxd{EFdc7W=-eZ+Im#Es%pxwBBn7&8y3F~U@i3--S2gIYp;+n1lyVEU>c6*giNlWNp z)n4l&wXpJ5by97&qS8KbolR|dbj(?{CV~D!K{xseTdMjaCy(K6cRH&$t{jSB${UY%-Te7s=a^t z7kOrKc^KK@v*6&6_mYSbr)lH5HA6F7*6Z7A-5dt~I@>|6r|8@>TYYtTdZt(0{x>ry zO1U7xcghDojC)tGYJb`w`<+qB=c%qA2?owkOjp3mS(_HXD*AUomL zT^8Gc4}pob687BrVl#dKJ<~@YFr>E)`_izryewj={JjO|-~H?FyZ`xM`?JCoJDuM9 zpL~9GU)%5d)Rp&syTX*vysO2y_G&uaUc<~4iarP-q3C6;@DHe#HaQxtMe3HG5)N8% z{!)<*$31xCqRh*L-o!vgik6Dj*W2byOO>L&rm{;(Xx(P!x*h00+C?$aF`o+8_b_{l z@Ig%19jB;C;~>+qt>fxlh+)uxEGnNz4rK(?v1A;2!VXvq8So?5I58U9q z>`sh!IsBhvfVt28n3!m-y>3Na&P1?7@B1x$GEj+*cI}Vt7Pbvv@!XQId30dAFKw=u zV&RMrV|pD|_v#JfA+@Rg zt@uW5y`Sg2DlEt)v=5?gMNR9Z`tr*Mv*Ti^i@FbxsRP#hHHf)glxJhescf?U^T=r0 zG2Y1COPSI8=)ZZmRfr3pRa~Lh!U%0FO^<_Og?})EF_Z#u!>ecNheP2NnZ~8PEQ`;4 z%RKKJ@G+YPL~4jyd?L`SQrgj95cFeSz4yeu_Y(>pyb!qt$^@Rx0G7zq1KGYcdlX;I z9aE}NX%|&RVH+MNg(Q>C=lx1pF_^e#{ujaYPN%Q;S~lM0o5Y1-X+*q8Ba~JYAH7mZ z&pe!4r;Yp3TN3KHvno(p_$2ZglYDin92ILj_KH>~hI@9~BYR>7`X9X9x zk4)~pnnb=$TK{F@0jEqH@^ck`^9qZHac*AwQ~gun#Kl8z@+Ds60id>*YOsM}?ub82 zBSU*}d62cK*@b?#H5K*^5t1k>KGYENyrUdBKt(fxMXANF@;z!)Bed4@w*O!+@J4S6 z#;>QvoHJ&*x7TjoH~!Y;sN#hyGZ07(rnsRdC)plC9GW>cGD1!vcte-+GE!6LT3ZeO zX)&EEzyELW*;cD7jOD_=J!ePickcIVD|VY@j5GePzR1*W zcYwaBUH@Kt<2`rnn<~NUkOWa{2#wtz7x3VpKRep3)GgWHy8 z=UGKDmEI-PPhA*+!=?UF%~$hO?|=lJPeur+Y$S~7))7or8y`g!-2fKX>JE6*tzW9ZzCM($4Gvv^ zI8G#*3})u+S{s7KKbvNU^(B0RX9CZ(_5>sM(nyah;96AwSlqrjFP9qZTq5on>(re; zJ2$<#HMJw@j_iv*hu(vXZb0BCVPgj|(tPnc65ilkZg>1MU3#$j{$n$mT( zyjUB|$!bs68rxX?Zg^_>k#A8plBv~}KRa!xs0tr>andJWDunxqKO`aj{w!_lp6zZ8 z>nB0fxeE9$S{4Qug${6gk`+y9%>mt(D~TS%?%}a~32&6g-Cv#7-YgP+T;vUC#otBm}rbvz%wo6fZkA)=WzfD*Q zaC@yvrLp`rRNScSQO308l2+*W>tgkb(#N%LhJS zLmL9a8>PY$yVa5It9a5V*92KS9wBM%+t#7L?2GkXtAGC1?!y}Tjjw`L!jI?PE?p4x z!FdKP6BSE8Dn&Rpa6-K4FJK2SavdjxY%e-=Ri+XR2Hyc*!gAd89p*Uo@DRdSxDHxW zlbJ9|+#M4b(r4C79@7*5Ydrl_7{Je_F(qY5DBdZZ1Q0#gE5C@5)rd(veo(sn%Y;9j zz0ehh9sTCqM1Q(IBq1FO9OM;59`RxCnCqOu-##%%o=Zw~ei@;DegY;{h4!rmWrG`# zdvHf$t@C)$t5?XTdu3A*gcvD@O!ht*FyF2+F%WSagRc*7fWd+X_R1-*+-y4Io0@L zzoo_JYg}E!tQ_P%oi=vT(|*J@5W{T~VRXCUT2bG$Q-Grb2(y1pfOXWwNMi!BILu5=N~#9hE?bo4lHOBD#t&vjS17haD2+rOp0&VeL15uAg{Z)f3X9 zaqjj!i_!p~i*oH9jtOo=SEH+x+3w1vd9eQ5*)9N?Lpf1PmRfIKx>=?e=>IyO`^5C&gvGc3`3E3p6H89 zyvlrULPqDZ&ZZs7A7tDmNF_fL5YO|U7VBw~f9Porhn15X7k$XPb5sC!z;+EHDcWU2 z)$m6ewmXlFJjBS>3J=N}|yjl0p10KEMU95eUKsQQiJ$A=fsIdB_dl(QXOa@?$r; z$GXR7L1jS=aU($nZ}&1jGQ$d}N~l1=4(ym}$DG=zcqZg8L2`?4mw^j35b*DJ|wHh!A# z&6e;%$pW6I>@qI1&R8abjf}PKiJT$FG@Z);dg(Y&68waXrX^BNzG|OMwA*W!0{_CB zv>9V}8dnzsF&`%Sb-~+PYuC^J#J}CNpEsQVWB2;ch$_+ptQo{OwWuD(qO|-PJq$Hq z!3dlNwhgP1134nP83%}hu`TLW4ShFA2a$y1?T$1|L=RFjtFXQ;fz^}l+hy?6Xw0C* zg(E0vE=wqd!UX-M^lFP&%dYsgHXJSJe*vQ*RIRcnzH_3~0^Rreo0`@Y(po7hlD@tQ zaz;Hg;4A4V4}|_q%pdfSc$RkjJp#=l z(#bigp7i{OXQUmU1d~TeWT?RqfT}aoHIUAftk_kf%F?>(JklEC*(-3ib^9ID;dP0( zkjAJl?+3A0d&0Xwh>FebfIlz-93E;z{GxQ#3w~?N^uvk4p9Ifdeeus@zd{p)pfSLyj0Z|VddY+6FWJl7O;qRMO+wuQ`J6I23|KS9VtmaShK|_hkbP=o z`sR#cR3uxFqcnnVV@-s}+Q)e#FQ)ihE(QuFhJ5AuGwGR?Y>11t1N+p?b}PS;>B{6P zYwikFt&hIc)o$>UtxGhqTy@VR&D&8viS}?&sRhdAGGNdB+xr1|bN-RQZa;e= zKbQ)*DpqWakaTB*$&#Tg64ad$$mbtsDqXoAs;6>olXHD)s?JSrIMiMLa)KWnI3DPb zIm9)a2rMbS_KUuWMWzTNa{~e#4GZWwrG;PCHvQQ__7Xw19{~BgoSUnT{bS$sI1g}w z-LlTgOn@=A#N|X+HP?vL9q90=h_ko5^hKmOvZ>75(YrELIm7*TV%76_I(=BWrq0D@ zSAzvA%(EGUeYM>uBBP{!6_NL)|GE84w*WhRG8-0`chrt(1K^*Z%3WIeZ9zF$0ymLX zfq{8l=n(A(O*SBP77K9z1l^zg@|$^EK|BLhB|#tQw6nACJJ~V#5LiiGI#C;H#E6PQ zVg5h9Lxas6%(iQtVj}sC&~@besl@yNQR6t6KNhm)%PREHYqhkG^qB9`{GJNd&%vE2 zn2H^F_PEBfzKUKJ!nD!5>wV4jMec^W<&^*e>=e`@!wm-6gv(Ng4t?B)NM$X*d+B0w zqj}xup9$1ouK49vY*GG)w8tM-vf)LLzC};C!)YljE>sfDrs?Hfvy;-Vw6a_kZeky* z_EnAAws^hf4h4QVrxd635FkfQ-f3+J`(M2iZlvyYs@J`dIjfo*Ztm9euHY`z%>cqF z{7HcmS&*wtwT?Zn8f0dpOD!&YG=K$!ru^P}&|bygx+2oRG(5>Yya$qjBQfyw)q(vo zoFwUQVbkc}_E7r#wddWMSFKe9;bRxQqIh3*#2~Z00t6?}sh{$l38i4mX##)`cCB4y z+Sf4H{I7%eWC}QnE0ru2MQ*owc&xD&laHZUsGg+E)WtkM=(?xDfpX)@(btT=wq+=z z6Fv_{{X@!Fc}Vfxfw&q+zKj8^+d;0u(l)${f;Xtzx>n(XRF3cBD?HXA>i2vzJ2#J? z^>~?E&9-t+E6SW6)jC$w2Kd-_*%#1)TfF9^xiSDbHV>lQuD(BZNY>K4n=^8#EU3&^ zasR3QpTE+-KDJB=*oulgIvx+`>ufNxUWRQAA5p4Bv|tBNk84+uKA@`|C#wg4C{rmC zf#4{JZFV;TeafEcepUqxa#vn5SH8>q`Rs&qR8fh_Y(K9$1965ox8ZC#HVPZn^r@h) zKoF&le(t8MW$MJ7XAMZL|ozpittH$tKrJe2PFKXNzyAj>s_OAhs6LiGI z`-Eui`h#PhBL}wxhzw^Q#q@^L-spa|igUf1lfjq)^G@LgmY(qP0O#~wK6t87N*Day zpbk`1G~gF=P9-XX`_a|N+bm0+kt&m_bC;V*_Q)f(gHx3QHI~OBxD+&R2wUzzC?+|w1C$=@ytp-sccefPee~3vo94zfUsK|ER<@~1@u(a8>r#R z9~nOauD>gYUcp&bo&$c$cMcA06x!m)C_AAHr$9bO-Ns^S$Kp?C{WVoW26iz%6tT zPFjCR9&t&nLDV@Kwg&i00=&i zXM}t8CGN&xym~&yqQW1X(q4OR7C6@#i0qO7;T_*QGb} z1TPVO6udwiTT5q=l7d3sGjO#O?od2KBv6hZ57oI185=b@Z_6D*8c%eaPyI#h;gpV> zH)kg-8qF^q=b!V%5hJ3bLi&dUBczc=#e0j_&95;9l7xhAp$5gkF-K1i+W2Y4U-O{+ zq&eIR_>At6+z9yRm^@SQLwF=B6-N=sBmMFPeF!t;f?gLw*qPcS>;GtTB=sziw--Dc zo#>zVjIDKYTDS36>SjX=R53`Zx%iDIqKM&!p}v#fU1yCKN5z481H?bemr|zfN|#e><4!zOr@QNwBr8vYTA>Jm0s% zV&@LH<@&z)C3ourVaCPkqEGhqN$6)&LL+mt4W4Fh09BI|Azqbu$vX@QDkHRtwyt(ZeJ_GK4iti|Ks!VyW`9r`)pC>zP_Ms@*$eofCTq2jw1Ly!CWefM2gYxk8_Qzp4k@++N4H@3pF} zqfgHlX_jKi1kmJE*bfyE*$tmG6^FkY}v6vwF+ragKaBpQbO|!}=a1Aa@*j}lA z-4#a+2w~TBR#T5^1G;eAW~6gA{?o%J>Y4dZ3nw(A__Tcbc+N$|*u1jHH>qbGcmZ7^ zqD_0VoflTQ_0_Y-{Y;^pUC>1paKAz&NS%4tF#5~bA@GX)69c?d6+^V1j+P+<)qA=B z*y&fQze;t;Kb3~z+M=gtvFSd#u znVIg|U}UtNU|p<4&5FV>{i3gsuloX-?_`P`w{xKXN(W}@NB7vKE0}WPw%U_x_HUiM@cM(WJkF~6!031p5u7VzG)Sat-*27kcavrXgS(h>p?#Niu8fk|A(h_?;4zWrsF{_y6vVGhohq*5s=925oHkWO7JHL0|@8kFV z%Re3;AD{R8{d&JH&kIywk=U2E%2_&;v@V)#;Jh~JoqxwhmwybLq!|@cjU969waJBo zydWp_RBNtfemCX2AGGow?s85VAIrnedreBpq?0+fWRE1;%6Q|hb!T}cGgFd5QC z+1_v1sdkd(>sowvzI2pXOqDfZT5ruO?^&j6+znNntq`e^Vzgjxs|l-i*MZC<9x)Ts zlb+t+d3)3p-om1F5EDgA^;)2|qj5eWa5?WL@B#`2(_ z)ay>IIMLMfR8!CTQ&FlYTLnvFH_6EzHLOfj{<)Q8Z>mO{YVpmw-Ia+9R4hzlZ`>*` zmIyS$UfsaE{Tr+pqbg@InoKkAN8g1CJ>q&6m!hQh;OF>*Y0Tzg?nO$D$P~&a&#Z!KDE#=x~dk?h?YXPxXZK|ItRFKlN72-KyWwB z83x`Ao?cqoQ-{A!&gc}!GDs!~QDS8dNDU)azP*r_jaRtqPI1_&v*>QxfcFXcXJzo( zG*p%MQTapZ!NQzfl};~ec+{2lmTNQjA!Jk9bpF3`IKcX6|7flPJ94PkV_zCQ2fg(_ zJ+6;1E4Sj}b|J+zc9;#k14f=YIZ0WyiBI2&&bI6`jDvtrk@0W*ZuBL84pMnfo497k zfr|?X4lMrMx^g73Bx!j5(kE}?BN&5&kQOB97-E+q!C_6*N#*Ti;71RlRDx!?6&j^R z>Q)L3FeXMe4gM@K>ZXqOxxi+#P`t##y>0jWp96NU+-C7g*Z@kK2>3MNQNn~7BB%q^ zF4S!31d8*C6_5ZYPi%KE)f|-)Dn-jY=HY+NRjFDGDAOHl$q*}_kNw+LyFYCqB%EuZf_By(ffr# zbPaPzcLwSmM?DVFHB<8`;|#-bf7wa}D6${f?~&1}&nsD{wN(2H!Tof?8DLTc?vRzB zv-#VoPQSRQ9a{i1`P9!n-7#O*y_%N91XY zdO>sGc>h=@HYhJ@PaoUpNb*F-_RG(W{wc_D0;sgSy*YOdb=l}yCVx!wV*eDd)jm4+ zq~btvrI?|r0tL+7I`U;0>{lQwpDa2F%4Tn1g@aQCzGW#;9 zKw!|HNbtklkq*}<53M)dLt5J38!gd%E;@QcJSEg2XnkWWRPryf*^A?oATqyPm0)mE zKqjN2c4*}ZS!aU!qIIS<1D2P{Y7DOKHX_dpo43nj+c^;lGW-sT-mOpLt(UKPYawH0 z(Wi$e$2APU2Avlr5*J4|Pd(3W)i`5sJ)O@|xToZt7{?& z-nImtNGP3R4=#EAXIqa@E@ot613_aYGvP0E&ne@W6yca9dHhBa z^MOlOdQo!?ax(3WJwSn5jb=V1M4d*8tB_lbu) zu2Fc5&cpLKdh->6*Y11i8GubVz;8p};z?BS{m+3VS|*3D!oAVO=qJ-ur5QBt z_#wRZ3~!gc4OSMk;-gTR;nIFQX8wg+wxw$a&Qi}JI^|>)`Q$i}q(7haZ$L9q=r*wsm0irg@y3)NseO)`~-IL8WgS z$=o34nl3_x>dv76=T6K!A19UHE`PyAx++S6DX|5r9@{spy%j20zYRST;z;wV+|BiI z{JKS2tI^-uyg@$8P?}Mu#UJV?k8Y9`pMLiJDJo($3^2HXPpPq4dx~GSw>rsMS+qe$ zQke!ImZD!wi}!MMi(s{1!@^WrRb?e?SoYk^?oZzYsLwy#QhQ2`>KSOVw!v2!iNOebQ(V$rNIaBZV%P>Q4p`e--5_LmZ8`Jrh8P*Pok1S<^= zZtlR8P&-)zew}Zat$(CP&`^d<&~d7}zi3%Q1NV^L($3MlmYyviKb$&c{St?lVDQ^+ zGX5MH%S`A=;z?;^+HJ_e1_1-$?-Xj$p7wF$@?qCfTI&_`^u60>em3lI4a_ z8E%;tJ9HStPBk>h-{Ka~KkE(PdGm?~Uv^x20{O6+&NO}plhO%SsG}Oc-x+jQ26of) zfWpC;1ZeSc)oYz)NBc61w=~EM%6HeAsW2z&RY2}}BL#3Yid!mcIg2<&rAjB+@{X1s@gFJuJ>qix+9q)Jdl&+N zXT5#?m4*yq<@`OQjK^7D)w@~q+9tMS*Xp@TdVJuFUmE!YwOE2VZ;iv6f9cWAfDbH1 zI>b+-JFnvaReN0Hd+Yk6D1YVkp}LTt5i3AXb#d~G;n|+s=k69AxEdTkd2(550?cTc z5JFvNd~wtnW}0qh3e{zFSvFiQfR>DTIQL5)T6`KIynwzDD^1RPZJh|;P&BHUEwhiw zG#Y6Y(do?68MdN?T)6vUM4*a}#JLrwh%(?bUMJW(l6#70N7Z2(v*PDOFU&1CAW^?w zPIc%p`qR8$nRz1$;LiM+Ydbo6t&h*~89qRNiPMrP9%|i`uUY7nc#x0g5qnVLc3RX~ zrQe66I2=wkuS|j6ceVzI-ELeew#3#x`)QEh7B=ticWJ5I;dpfvY2I+?p`s}_}`2F}t_RBI2;=I&E%DTDZy{Tn~O1kd!K5uYY zkION|Y6>7|U#@nzSw7~w1gn~HcV(BVr^&^hsI0T~tDBB=^js*DR`0q~t)tbUWD{3M zZ0FpIsU36!f}AQvj|!teCRH2+DxjknjSAOQU_EX}f~mM#*$l05g6arfQp*?t*M2A! zq?mahKm$;yG-uo|_5J{rUqJ;$go2okAuNti9}Ca(+VMy8OvIaXLx(nlXTkFG&V?bx zBTLt8SU()t@~}`?U&3n;9AgvdD}-JK^Qrj+0Iz88NfWg2MD$|B*rC^)qVo=i_y$5Dx6q|n(U=a^<1X6w#?Gk6Ji#pO4ioI~6nQ73yP{tnc?ZrXW&GJS~vO!X`xpzl`qkg_;#9%TDuji!Z3>3j&&Y zj%HV?_eEJg88bf>w7GO;^*aH?P@iU&1udL+BSdqhAOczgbWbmPct04%8L!H2h{>@gQhM9f ztHCjWcaB;(uXsN`^94e-^LrSL{5y*8nWcG!@T@tnQH{=pT0?Y;J^CfF){xW%`6I(c zhJ0LVp#Nw(u`{P{0p*dHnc#OQ`Vy!mI}Q2VSojZs2kjQV|K_7oOUQ2(ye8GO!CW`Je7&(sNuxeur5H>WE3lJd?agFUvDabE4$2Z4_kpb|D@QkTjvRo75RCNtgJS6Jt@h zZUVPgQc2!f{7=)$sS$qZmn8AO9qKO!pXy)nOZmr{%_j$_v}!L^(C7*LISWpjxzbAX zQQVfr(UaAaxcbTrsZ^I|38?kCD2i)TUV3xA(=Qhpwd1UI$f3^Meeg%=4w;#&^Cu;- zrZCvg;Yy?fdl3q~y$$IwJVmP`Mc~LR(^Lwbq!?W@UWPwWaUuFvr?o&h@&r?xqn}FMOdMR?Lh8?f-nk&r))3#P1%K3{x#qX(Sjd zEVf+ySLQyd3!eZ_PGfSTDDKb>p)|DT^E(k$dfD))swVoQe`E(W;umG57JBdVW6b&? zuvg}OGsk0mJ#H#aV-W(4RHW?<=y^z$6FsCpVi^Dw4B~#Bd zx?3uAf<*lOMWxE`v1C`g(7xC_oqq5QwM~2104gw?c$PkF96-Ck_x}Pg3kO@gi*0_4 z{Ji~~Ao#8|2js)≪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 z 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 diff --git a/tcf_website/static/landing/dashboard.css b/tcf_website/static/landing/dashboard.css deleted file mode 100644 index 6ae13e1ef..000000000 --- a/tcf_website/static/landing/dashboard.css +++ /dev/null @@ -1,11 +0,0 @@ -@media (max-width: 768px) { - .display-4 { - font-size: 3rem; - } -} - -@media (max-width: 576px) { - .display-4 { - font-size: 2rem; - } -} diff --git a/tcf_website/static/landing/feedbackform.js b/tcf_website/static/landing/feedbackform.js deleted file mode 100644 index 004d1290c..000000000 --- a/tcf_website/static/landing/feedbackform.js +++ /dev/null @@ -1,41 +0,0 @@ -// import { validateForm } from "../common/form.js"; - -// function submit(event) { -// var form = document.getElementById("feedbackform"); -// var valid = validateForm(form); -// if (valid === true) { -// postToDiscord(event); -// } -// } - -// function postToDiscord(event) { -// var fname = $("#inputFname").val(); -// var lname = $("#inputLname").val(); -// var email = $("#inputEmail").val(); -// var title = $("#inputTitle").val(); -// var message = $("#inputMessage").val(); -// -// var data = { -// type: "feedback", -// content: "Feedback submitted" + -// "\n**Name:** " + fname + " " + lname + -// "\n**Email:** " + email + -// "\n**Title:** " + title + -// "\n**Message:** \n" + message -// }; -// -// $.ajax({ -// type: "GET", -// url: "/discord/", -// data: data -// }); -// } - -// const form = document.getElementById("feedbackform"); -// form.onsubmit = submit; - -// Show confirmation modal on form submit -$("#feedbackform").submit(function (e) { - $("#confirmationModal").modal("show"); - return false; -}); diff --git a/tcf_website/static/landing/landing.css b/tcf_website/static/landing/landing.css deleted file mode 100644 index 1be429fc2..000000000 --- a/tcf_website/static/landing/landing.css +++ /dev/null @@ -1,183 +0,0 @@ -/* Html Classes */ - -.container-fluid { - overflow: hidden; -} - -/* Navbar */ -nav.navbar { - background-color: white; -} - -nav.navbar img.logo { - height: 2rem; - weight: auto; -} - -/* Masthead */ -header.masthead { - position: relative; - padding: 4rem 2rem 2rem; - /* Top, right/left, bottom */ -} - -header.masthead h1 { - font-size: 2rem; -} - -.about-logo h1, -.about-logo p { - text-align: center; - padding-left: 0rem; -} - -@media (min-width: 768px) { - header.masthead { - position: relative; - padding: 8rem 2rem 2rem; - } - - header.masthead h1 { - font-size: 3rem; - } - - .about-logo h1, - .about-logo p { - text-align: left; - padding-left: 2rem; - } -} - -/* Features */ -.features-icons .card { - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4); - cursor: pointer; -} - -.features-icons .card:hover { - box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4); -} - -.features-icons .features-icons-item { - max-width: 20rem; -} - -.features-icons .features-icons-item .features-icons-icon { - height: 7rem; - color: var(--main-color); -} - -.features-icons .features-icons-item .features-icons-icon i { - font-size: 3.5rem; -} - -@media (min-width: 768px) { - .features-icons .features-icons-item .features-icons-icon i { - font-size: 4.5rem; - } -} - -.features-icons .features-icons-item:hover .features-icons-icon i { - font-size: 5rem; -} - -/* About team */ -.about-team .about-header p { - font-size: 1.1rem; -} - -@media (min-width: 768px) { - .about-team .about-header p { - font-size: 1.5rem; - } -} - -/* About member photo hover animation */ -.about-team .about-pfp { - overflow: hidden; -} - -.about-team .about-pfp img { - transition: all 0.5s ease; - box-shadow: 5px 5px 0px var(--accent-color); -} - -.about-team .about-pfp:hover img { - transform: scale(1.1); -} - -.about-team .about-member { - border: 0px; -} - -.about-team .about-link:hover { - box-shadow: 2px 2px 5px var(--main-color); -} - -.about-link { - font-size: 1.25rem; - cursor: pointer; -} - -/* About info box */ -.about-team .about-info { - /* border: 3px solid var(--accent-color); */ - /* border-radius: 0%; */ - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4); - cursor: pointer; -} - -.about-team .about-info p { - font-size: 0.9rem; -} - -.about-team .about-info:hover { - box-shadow: 2px 2px 5px var(--main-color); -} - -@media (min-width: 768px) { - .about-team .about-info p { - font-size: 1rem; - } -} - -/* Feedback form */ -.faqs .faqs-content h5 { - font-size: 1.1rem; -} - -.faqs .faqs-content p { - font-size: 0.9rem; -} - -@media (min-width: 768px) { - .faqs .faqs-content h5 { - font-size: 1.2rem; - } - - .faqs .faqs-content p { - font-size: 1rem; - } -} - -/* Feedback form */ -.feedback .feedback-header p { - font-size: 1rem; -} - -@media (min-width: 768px) { - .feedback .feedback-header p { - font-size: 1.25rem; - } -} - -/* Social Media Icons */ -ul.list-inline.mb-0 li a i { - vertical-align: middle; - transform: translateY(0.075rem); -} - -ul.list-inline.mb-0 li h5 { - transform: translateY(0.25rem); - vertical-align: middle; -} diff --git a/tcf_website/static/login/login_button.css b/tcf_website/static/login/login_button.css deleted file mode 100644 index 8ac452596..000000000 --- a/tcf_website/static/login/login_button.css +++ /dev/null @@ -1,50 +0,0 @@ -/* - * A spritesheet is used to eliminate CSS flickering. - * https://www.codeandweb.com/texturepacker/tutorials/how-to-create-a-sprite-sheet - * - * ^ great resource from Floryan's Game Design class -*/ - -.login-button-container { - display: grid; - grid-auto-columns: minmax(10px, auto); - grid-auto-rows: minmax(10px, auto); - justify-items: center; - margin-top: 20px; -} - -.login-button-wrapper { - grid-row: 1; - grid-column: 1; - width: 382px; - height: 92px; - transform: scale(0.7); -} - -/* Normal */ -.login-button { - background: url("./google_login_spritesheet.png") 764px; - border: none; - width: 382px; - height: 92px; - padding: 0px; -} - -/* Hovering */ -.login-button:hover { - background: url("./google_login_spritesheet.png") 1146px; - outline: none; - cursor: pointer; -} - -/* Pressed */ -.login-button:active { - background: url("./google_login_spritesheet.png") 382px; - outline: none; - cursor: pointer; -} - -/* Pressed but dragged cursor away */ -.login-button:focus { - outline: none; -} diff --git a/tcf_website/static/profile/profile.css b/tcf_website/static/profile/profile.css deleted file mode 100644 index e9ea58161..000000000 --- a/tcf_website/static/profile/profile.css +++ /dev/null @@ -1,8 +0,0 @@ -.user_delete { - align-items: end; -} - -.button-wrapper { - display: flex; - justify-content: space-between; -} diff --git a/tcf_website/static/qa/qa.css b/tcf_website/static/qa/qa.css deleted file mode 100644 index 31b579005..000000000 --- a/tcf_website/static/qa/qa.css +++ /dev/null @@ -1,33 +0,0 @@ -.question .upvoteQuestion, -.question .downvoteQuestion { - font-size: 1.3rem; - color: #aaa; - cursor: pointer; -} - -.question .upvoteQuestion.active, -.question .upvoteQuestion:hover { - color: dodgerblue; -} - -.question .downvoteQuestion.active, -.question .downvoteQuestion:hover { - color: rgb(215, 86, 38); -} - -.answer .upvoteAnswer, -.answer .downvoteAnswer { - font-size: 1.3rem; - color: #aaa; - cursor: pointer; -} - -.answer .upvoteAnswer.active, -.answer .upvoteAnswer:hover { - color: dodgerblue; -} - -.answer .downvoteAnswer.active, -.answer .downvoteAnswer:hover { - color: rgb(215, 86, 38); -} diff --git a/tcf_website/static/qa/qa.js b/tcf_website/static/qa/qa.js deleted file mode 100644 index 00ab20b03..000000000 --- a/tcf_website/static/qa/qa.js +++ /dev/null @@ -1,94 +0,0 @@ -function handleQAVote(elementID, isQuestion, isUpvote) { - let elem; - let otherElem; - let endpoint; - let newUpvoteCount; - let newDownvoteCount; - - let upvoteCountElem; - let downvoteCountElem; - let totalCountElem; - - if (isQuestion) { - upvoteCountElem = $(`#question${elementID} .upvoteCount`); - downvoteCountElem = $(`#question${elementID} .downvoteCount`); - totalCountElem = $(`#question${elementID} #question-vote-count`); - - if (isUpvote) { - elem = $(`#question${elementID} .upvoteQuestion`); - otherElem = $(`#question${elementID} .downvoteQuestion`); - endpoint = `/questions/${elementID}/upvote/`; - } else { - elem = $(`#question${elementID} .downvoteQuestion`); - otherElem = $(`#question${elementID} .upvoteQuestion`); - endpoint = `/questions/${elementID}/downvote/`; - } - } else { - upvoteCountElem = $(`#answer${elementID} .upvoteCount`); - downvoteCountElem = $(`#answer${elementID} .downvoteCount`); - totalCountElem = $(`#answer${elementID} #answer-vote-count`); - - if (isUpvote) { - elem = $(`#answer${elementID} .upvoteAnswer`); - otherElem = $(`#answer${elementID} .downvoteAnswer`); - endpoint = `/answers/${elementID}/upvote/`; - } else { - elem = $(`#answer${elementID} .downvoteAnswer`); - otherElem = $(`#answer${elementID} .upvoteAnswer`); - endpoint = `/answers/${elementID}/downvote/`; - } - } - - const upvoteCount = parseInt(upvoteCountElem.text()); - const downvoteCount = parseInt(downvoteCountElem.text()); - - if (isUpvote) { - // If already upvoted, subtract 1. - if (elem.hasClass("active")) { - newUpvoteCount = upvoteCount - 1; - newDownvoteCount = downvoteCount; - // If already downvoted, add 1 to upvote and subtract 1 from downvote. - } else if (otherElem.hasClass("active")) { - newUpvoteCount = upvoteCount + 1; - newDownvoteCount = downvoteCount - 1; - // Otherwise add 1. - } else { - newUpvoteCount = upvoteCount + 1; - newDownvoteCount = downvoteCount; - } - } else { - // If already downvoted, add 1. - if (elem.hasClass("active")) { - newDownvoteCount = downvoteCount - 1; - newUpvoteCount = upvoteCount; - // If already upvoted, add 1 to downvote and subtract 1 from upvote. - } else if (otherElem.hasClass("active")) { - newDownvoteCount = downvoteCount + 1; - newUpvoteCount = upvoteCount - 1; - // Otherwise subtract 1. - } else { - newDownvoteCount = downvoteCount + 1; - newUpvoteCount = upvoteCount; - } - } - - // POST to upvote or downvote endpoint. - fetch(endpoint, { - method: "post", - headers: { "X-CSRFToken": getCookie("csrftoken") }, - }); - - // Update vote text. - upvoteCountElem.text(newUpvoteCount); - downvoteCountElem.text(newDownvoteCount); - totalCountElem.text(newUpvoteCount - newDownvoteCount); - - if (elem.hasClass("active")) { - elem.removeClass("active"); - } else { - elem.addClass("active"); - otherElem.removeClass("active"); - } -} - -export { handleQAVote }; diff --git a/tcf_website/static/qa/sort_qa.js b/tcf_website/static/qa/sort_qa.js deleted file mode 100644 index 1f1150412..000000000 --- a/tcf_website/static/qa/sort_qa.js +++ /dev/null @@ -1,142 +0,0 @@ -import { sortHTML } from "../common/sorting.js"; - -function makeQAActive(htmlTag, label) { - $(".dropdown-item").removeClass("active"); - $(htmlTag).addClass("active"); - $("#qa-sort-select").html(label); -} - -function sortQA(btnID) { - const htmlTag = "#".concat(btnID); - let label = ""; - let prop = ""; - const asc = -1; - switch (btnID) { - case "qa-votes-sort-btn": - label = "Most Helpful"; - prop = "question-vote-count"; - break; - case "qa-recent-sort-btn": - label = "Most Recent"; - prop = "date"; - break; - default: - console.log("error"); - return; - } - if (!$(htmlTag).hasClass("active")) { - makeQAActive(htmlTag, label); - sortAnswers(".answer-container"); - sortHTML(".qa", ".question-container", prop, asc); - } - - // collapse QA - collapseQA(3); -} - -function collapseQA(numberShown) { - const ids = $(".question-container") - .map(function (_, x) { - return "#".concat(x.id); - }) - .get(); - for (const [index, id] of ids.entries()) { - const detachedID = $(id).detach(); - if (index < numberShown) { - $("#questionShow").append(detachedID); - } else { - $("#questionCollapse").append(detachedID); - } - } -} - -function collapseAnswers(numberShown) { - const ids = $(".answer-container") - .map(function (_, x) { - return x.id; - }) - .get(); - for (const id of ids) { - collapseQuestionAnswer(numberShown, parseInt(id.substring(16))); - } -} - -function collapseQuestionAnswer(numberShown, questionID) { - const answerContainerID = "#answer-container" + questionID; - const showAnswerContainer = "#answerShow" + questionID; - const collapseAnswerContainer = "#answerCollapse" + questionID; - - const answerIDs = $(answerContainerID.concat(" .answer")) - .map(function (_, x) { - return "#".concat(x.id); - }) - .get(); - - for (const [index, id] of answerIDs.entries()) { - const detachedID = $(id).detach(); - if (index < numberShown) { - $(showAnswerContainer).append(detachedID); - } else { - $(collapseAnswerContainer).append(detachedID); - } - } -} - -function sortAnswers(containerClass) { - const ids = $(containerClass) - .map(function (_, x) { - return "#".concat(x.id); - }) - .get(); - ids.forEach((item) => - sortHTML(item, item.concat(" .answer"), "answer-vote-count", -1), - ); - collapseAnswers(1); -} - -sortQA("qa-votes-sort-btn"); -document - .getElementById("qa-votes-sort-btn") - .addEventListener("click", () => sortQA("qa-votes-sort-btn")); -document - .getElementById("qa-recent-sort-btn") - .addEventListener("click", () => sortQA("qa-recent-sort-btn")); - -// collapse QA functionality -document - .getElementById("collapse-qa-button") - .addEventListener("click", function () { - if ($("#collapse-qa-button").val() === "hide") { - $("#collapse-qa-button").val("show"); - $("#collapse-chevron").removeClass("fa-chevron-up"); - $("#collapse-chevron").addClass("fa-chevron-down"); - } else { - $("#collapse-qa-button").val("hide"); - $("#collapse-chevron").removeClass("fa-chevron-down"); - $("#collapse-chevron").addClass("fa-chevron-up"); - } - }); - -function clickCollapseAnswer(collapseID) { - const questionID = parseInt(collapseID.substring(22)); - const collapseButton = "#collapse-answer-button" + questionID; - - if ($(collapseButton).val() === "hide") { - $(collapseButton).val("show"); - } else { - $(collapseButton).val("hide"); - } -} - -$(function () { - const ids = $(".collapse-answer-button") - .map(function (_, x) { - return x.id; - }) - .get(); - ids.forEach((item) => - document - .getElementById(item) - .addEventListener("click", () => clickCollapseAnswer(item)), - ); -}); diff --git a/tcf_website/static/reviews/new_review.css b/tcf_website/static/reviews/new_review.css deleted file mode 100644 index 434d14b32..000000000 --- a/tcf_website/static/reviews/new_review.css +++ /dev/null @@ -1,149 +0,0 @@ -/* Review Form Styles */ - -.new-review { - max-width: 800px; -} - -.new-review .card { - border: none; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); - border-radius: 12px; -} - -.new-review .card-body { - padding: 2rem; -} - -.new-review .review-form-header { - color: var(--tcf-orange, #d75626); - font-weight: 600; - margin-bottom: 1rem; -} - -.new-review select:disabled { - color: var(--shadow-color); - background-color: var(--shaded-color); -} - -.required-field::after { - content: " *"; - color: #dc3545; -} - -/* Form controls */ -.new-review .form-control { - border-radius: 8px; - border: 1px solid #dee2e6; - transition: border-color 0.2s, box-shadow 0.2s; -} - -.new-review .form-control:focus { - border-color: var(--tcf-blue, #384676); - box-shadow: 0 0 0 3px rgba(56, 70, 118, 0.1); -} - -/* Textarea */ -#reviewtext { - resize: vertical; - min-height: 140px; - font-size: 15px; - line-height: 1.6; -} - -#word-count { - display: block; - margin-top: 8px; - font-size: 13px; -} - -/* Rating and Hours sections */ -.rating-row, -.hours-row { - display: flex; - align-items: center; - justify-content: space-between; - padding: 14px 16px; - margin-bottom: 8px; - background: #f8f9fa; - border-radius: 8px; - transition: background-color 0.2s; -} - -.rating-row:hover, -.hours-row:hover { - background: #f0f2f5; -} - -.rating-row label, -.hours-row label { - margin: 0; - font-weight: 500; - color: #333; - display: flex; - align-items: center; - gap: 10px; -} - -.rating-row label i, -.hours-row label i { - width: 18px; - text-align: center; - color: var(--tcf-blue, #384676); - font-size: 14px; -} - -.rating-select { - width: 70px; - text-align: center; - font-weight: 600; - border-radius: 8px; -} - -.hours-input { - width: 70px; - text-align: center; - font-weight: 600; - border-radius: 8px; -} - -/* Submit button */ -.new-review .btn-primary { - background-color: var(--tcf-blue, #384676); - border-color: var(--tcf-blue, #384676); - padding: 12px 48px; - font-size: 16px; - font-weight: 600; - border-radius: 8px; - transition: all 0.2s; -} - -.new-review .btn-primary:hover { - background-color: #2c3a5f; - border-color: #2c3a5f; - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(56, 70, 118, 0.3); -} - -/* Horizontal rules */ -.new-review hr { - border-color: #eee; - margin: 1.5rem 0; -} - -/* Form control plaintext */ -.new-review .form-control-plaintext { - font-size: 15px; - padding: 8px 0; -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .new-review .card-body { - padding: 1.25rem; - } - - .rating-row, - .hours-row { - padding: 12px; - } -} diff --git a/tcf_website/static/reviews/pagination.css b/tcf_website/static/reviews/pagination.css deleted file mode 100644 index 1819e7431..000000000 --- a/tcf_website/static/reviews/pagination.css +++ /dev/null @@ -1,67 +0,0 @@ -/* CSS for styling pagination section */ -.pagination { - display: flex; - justify-content: center; - align-items: center; - margin: 20px 0; -} - -.pagination a, -.pagination span { - font-size: 1rem; - padding: 8px 12px; - margin: 0 5px; - text-decoration: none; - color: var(--main-color); - border: 1px solid #ddd; - border-radius: 5px; - transition: - background-color 0.3s, - color 0.3s; - background-color: white; -} - -.pagination a:hover { - background-color: var(--main-color); - color: white; -} - -.pagination span.current { - background-color: var(--main-color); - color: white; - font-weight: bold; - border: 1px solid var(--main-color); -} - -.pagination span { - cursor: default; -} - -.pagination a:disabled, -.pagination span:disabled { - color: #ccc; - border-color: #ccc; - cursor: not-allowed; -} - -.pagination a:first-child, -.pagination a:last-child { - font-weight: bold; -} - -/* Responsive styling for pagination */ -@media (max-width: 768px) { - .pagination a, - .pagination span { - font-size: 0.9rem; - padding: 6px 10px; - } -} - -@media (max-width: 480px) { - .pagination a, - .pagination span { - font-size: 0.8rem; - padding: 4px 8px; - } -} diff --git a/tcf_website/static/reviews/review.css b/tcf_website/static/reviews/review.css deleted file mode 100644 index 43566f1bd..000000000 --- a/tcf_website/static/reviews/review.css +++ /dev/null @@ -1,43 +0,0 @@ -.review-content .review-text-body.collapse:not(.show) { - display: block; - height: 12rem; - overflow: hidden; -} -.review-content .review-text-body.collapsing { - height: 12rem; -} - -.review-content a.collapsed::after { - content: "...See More"; - color: var(--secondary-color); -} - -.review-content a:not(.collapsed)::after { - content: "See Less"; - color: var(--secondary-color); -} - -.review .card-body { - padding: 20px 30px; -} - -.review .upvote, -.review .downvote { - font-size: 1.3rem; - color: #aaa; - cursor: pointer; -} - -.review .upvote.active, -.review .upvote:hover { - color: dodgerblue; -} - -.review .downvote.active, -.review .downvote:hover { - color: rgb(215, 86, 38); -} - -.tooltip-inner { - margin: 0; -} diff --git a/tcf_website/static/reviews/review.js b/tcf_website/static/reviews/review.js deleted file mode 100644 index e5d2a5913..000000000 --- a/tcf_website/static/reviews/review.js +++ /dev/null @@ -1,89 +0,0 @@ -/* For review upvote/downvote functionality */ -function handleVote(reviewID, isUpvote) { - const upvoteCountElem = $(`#review${reviewID} .upvoteCount`); - const downvoteCountElem = $(`#review${reviewID} .downvoteCount`); - const upvoteCount = parseInt(upvoteCountElem.text()); - const downvoteCount = parseInt(downvoteCountElem.text()); - - let elem; - let otherElem; - let endpoint; - let newUpvoteCount; - let newDownvoteCount; - - if (isUpvote) { - elem = $(`#review${reviewID} .upvote`); - otherElem = $(`#review${reviewID} .downvote`); - endpoint = `/reviews/${reviewID}/upvote/`; - - // If already upvoted, subtract 1. - if (elem.hasClass("active")) { - newUpvoteCount = upvoteCount - 1; - // If already downvoted, add 1 to upvote and subtract 1 from downvote. - } else if (otherElem.hasClass("active")) { - newUpvoteCount = upvoteCount + 1; - newDownvoteCount = downvoteCount - 1; - // Otherwise add 1. - } else { - newUpvoteCount = upvoteCount + 1; - } - } else { - elem = $(`#review${reviewID} .downvote`); - otherElem = $(`#review${reviewID} .upvote`); - endpoint = `/reviews/${reviewID}/downvote/`; - - // If already downvoted, add 1. - if (elem.hasClass("active")) { - newDownvoteCount = downvoteCount - 1; - // If already upvoted, add 1 to downvote and subtract 1 from upvote. - } else if (otherElem.hasClass("active")) { - newDownvoteCount = downvoteCount + 1; - newUpvoteCount = upvoteCount - 1; - // Otherwise subtract 1. - } else { - newDownvoteCount = downvoteCount + 1; - } - } - - // POST to upvote or downvote endpoint. - fetch(endpoint, { - method: "post", - headers: { "X-CSRFToken": getCookie("csrftoken") }, - }); - - // Update vote text. - upvoteCountElem.text(newUpvoteCount); - downvoteCountElem.text(newDownvoteCount); - - if (elem.hasClass("active")) { - elem.removeClass("active"); - } else { - elem.addClass("active"); - otherElem.removeClass("active"); - } -} - -export { handleVote }; - -/* For review text collapse/expand functionality */ -$(function () { - // Initialize tooltips - $('[data-toggle="tooltip"]').tooltip(); - - // On browser window resize, refresh collapser threshold for each review card - $(".review").each(function (i, review) { - const visibleReviewBody = $(this).find("div.review-text-body"); - const fullReviewText = $(this).find("p.review-text-full"); - const reviewCollapseLink = $(this).find("a.review-collapse-link"); - - // Long review - if (visibleReviewBody.height() < fullReviewText.height()) { - // Show "See More" expander only for long reviews - reviewCollapseLink.show(); - } else { - // Short review - reviewCollapseLink.hide(); - visibleReviewBody.css("height", "auto"); // Remove static blurb height - } - }); -}); diff --git a/tcf_website/static/reviews/review_stats.css b/tcf_website/static/reviews/review_stats.css deleted file mode 100644 index 000dc351d..000000000 --- a/tcf_website/static/reviews/review_stats.css +++ /dev/null @@ -1,13 +0,0 @@ -.large-stats { - padding-top: 15px; - padding-bottom: 10px; -} - -.large-stats h2 { - font-size: 16px; -} -@media (min-width: 768px) { - .large-stats h2 { - font-size: 20px; - } -} diff --git a/tcf_website/static/reviews/sort_reviews.js b/tcf_website/static/reviews/sort_reviews.js deleted file mode 100644 index 967de043d..000000000 --- a/tcf_website/static/reviews/sort_reviews.js +++ /dev/null @@ -1,54 +0,0 @@ -import { sortHTML } from "../common/sorting.js"; - -function makeActive(htmlTag, label) { - $(".dropdown-item").removeClass("active"); - $(htmlTag).addClass("active"); - $("#review-sort-select").html(label); -} - -function sortReviews(btnID) { - const htmlTag = "#".concat(btnID); - let label = ""; - let prop = ""; - let asc = -1; - switch (btnID) { - case "votes-sort-btn": - label = "Most Helpful"; - prop = "vote-count"; - break; - case "recent-sort-btn": - label = "Most Recent"; - prop = "date"; - break; - case "highrating-sort-btn": - label = "Highest Rating"; - prop = "review-average"; - break; - case "lowrating-sort-btn": - label = "Lowest Rating"; - prop = "review-average"; - asc = 1; - break; - default: - console.log("error"); - return; - } - if (!$(htmlTag).hasClass("active")) { - makeActive(htmlTag, label); - sortHTML(".reviews", ".review", prop, asc); - } -} - -sortReviews("votes-sort-btn"); -document - .getElementById("votes-sort-btn") - .addEventListener("click", () => sortReviews("votes-sort-btn"), false); -document - .getElementById("recent-sort-btn") - .addEventListener("click", () => sortReviews("recent-sort-btn"), false); -document - .getElementById("highrating-sort-btn") - .addEventListener("click", () => sortReviews("highrating-sort-btn"), false); -document - .getElementById("lowrating-sort-btn") - .addEventListener("click", () => sortReviews("lowrating-sort-btn"), false); diff --git a/tcf_website/static/schedule/parse_time.js b/tcf_website/static/schedule/parse_time.js deleted file mode 100644 index 89b20c7dd..000000000 --- a/tcf_website/static/schedule/parse_time.js +++ /dev/null @@ -1,95 +0,0 @@ -/* eslint-disable no-unused-vars */ // Since these functions are called by other files - -const WeekdayIndex = { - Mo: 0, - Tu: 1, - We: 2, - Th: 3, - Fr: 4, -}; - -function stringTimeToInt(stringTime) { - let ret = 0; - const parsedTime = stringTime.split(":"); - ret += 60 * parseInt(parsedTime[0]); - if (parsedTime[1][2] === "p" && parsedTime[0] !== "12") { - ret += 720; - } - const minuteString = parsedTime[1].substr(0, 2); - ret += parseInt(minuteString); - - return ret; -} - -function parseTime(timeString) { - timeString = timeString.split(","); // split lab times / discussion times / lecture times - const weekdayMeetingTimes = Array.from({ length: 5 }, () => []); // Monday to Friday - - for (const session of timeString) { - const [days, startTime, , endTime] = session.split(" "); // [daysOfWeek, startTime, "-", endTime] - const classTimePair = [ - stringTimeToInt(startTime), - stringTimeToInt(endTime), - ]; - - // Extract day abbreviations using regex (splitting by uppercase letters) - const daysOfWeek = days.match(/[A-Z][a-z]?/g) || []; - - daysOfWeek.forEach((dayAbbr) => { - const index = WeekdayIndex[dayAbbr]; - if (index !== undefined) { - weekdayMeetingTimes[index].push(classTimePair); - } - }); - } - - return weekdayMeetingTimes; -} - -function consolidateTimes(times) { - const selectedClassesMeetingTimes = [[], [], [], [], []]; - for (let selectedClass = 0; selectedClass < times.length; selectedClass++) { - const classMeetingTimes = parseTime(times[selectedClass]); - for (let i = 0; i < classMeetingTimes.length; i++) { - if (classMeetingTimes[i].length === 0) { - continue; - } - selectedClassesMeetingTimes[i].push(classMeetingTimes[i]); - } - } - return selectedClassesMeetingTimes; -} - -function checkConflict(newTime, times) { - // this method will return true if there is conflict with the list of times passed in and the newTime - const newTimeMeetingTimes = parseTime(newTime); - const consolidatedTimes = times; - - for (let day = 0; day < newTimeMeetingTimes.length; day++) { - if (consolidatedTimes[day].length === 0) { - continue; - } - const dayInSchedule = consolidatedTimes[day]; - - for (let period = 0; period < newTimeMeetingTimes[day].length; period++) { - // period for proposed class - for ( - let periodInSchedule = 0; - periodInSchedule < dayInSchedule.length; - periodInSchedule++ - ) { - // period_in for exisiting schedule - const beginsBefore = - newTimeMeetingTimes[day][period][0] <= - dayInSchedule[periodInSchedule][0][1]; - const endsAfter = - newTimeMeetingTimes[day][period][1] >= - dayInSchedule[periodInSchedule][0][0]; - if (beginsBefore && endsAfter) { - return true; - } - } - } - } - return false; -} diff --git a/tcf_website/static/schedule/schedule.css b/tcf_website/static/schedule/schedule.css deleted file mode 100644 index 1ff007db3..000000000 --- a/tcf_website/static/schedule/schedule.css +++ /dev/null @@ -1,62 +0,0 @@ -.scheduleCardHeader { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - gap: 10px; - margin-left: 15px; - margin-right: 15px; - margin-bottom: 15px; -} - -.schedule_radio_option { - width: 100%; -} - -.schedule_radio_option label { - display: flex; - align-items: center; - cursor: pointer; - padding: 10px; -} - -.schedule_radio_input { - margin-right: 10px; /* Adjust the space between the radio button and the label text */ -} - -.schedule_course_card { - display: flex; -} - -.schedule-card-link { - background-color: var(--secondary-color); - color: white; -} - -.schedule-card-link:hover { - background-color: var(--main-color); - color: var(--background-color); -} - -.schedule_course_card, -.schedule-card-link { - border-radius: 5px; -} - -.schedule_course_card .info { - font-size: 1.25rem; -} - -small { - color: var(--accent-color); -} - -.badge-new { - background-color: #ff0000; /* Red background to draw attention */ - color: #ffffff; /* White text */ - font-size: 12px; /* Smaller font size for the badge */ - padding: 3px 6px; /* Padding for the badge */ - margin-left: 10px; /* Space between button text and badge */ - border-radius: 5px; /* Rounded corners for the badge */ - vertical-align: super; /* Aligns badge with the top of the button text */ -} diff --git a/tcf_website/static/schedule/schedule_editor.css b/tcf_website/static/schedule/schedule_editor.css deleted file mode 100644 index cee7d0adb..000000000 --- a/tcf_website/static/schedule/schedule_editor.css +++ /dev/null @@ -1,62 +0,0 @@ -.schedule_editor_container { - display: flex; - overflow-x: auto; - align-items: flex-start; - gap: 10px; - max-height: 70vh; -} - -.schedule_editor_container > div { - flex: 1.3; -} - -.schedule_editor_container > form { - flex: 0.7; - overflow-y: auto; - padding-left: 10px; - border-left: 3px solid rgba(10, 10, 10, 0.1); -} - -.modal-dialog.modal-lg { - max-width: 75vw; -} - -.schedule_name_input_group { - display: flex; - margin-bottom: 20px; - padding-right: 10px; -} - -.schedule_name_input_group > span { - padding-top: 8px; - padding-right: 8px; -} - -.schedule_name_input_group > input { - padding: 8px 12px; - border: 1px solid #ccc; - border-radius: 10px; - box-shadow: 0 2px 4px rgba(39, 79, 151, 0.1); - transition: all 0.3s ease; -} - -.schedule_name_input_group > input:focus { - border-color: 8px solid rgb(39, 79, 151); - box-shadow: 0 0 8px rgb(39, 79, 151); -} - -.table { - width: 100%; - border-collapse: collapse; -} - -.table th, -.table td { - padding: 10px 15px; - text-align: left; -} - -.table th:last-child, -.table td:last-child { - padding-right: 20px; -} diff --git a/tcf_website/static/schedule/schedule_select_script.js b/tcf_website/static/schedule/schedule_select_script.js deleted file mode 100644 index 8a14c3bca..000000000 --- a/tcf_website/static/schedule/schedule_select_script.js +++ /dev/null @@ -1,101 +0,0 @@ -/* eslint-disable no-unused-vars */ // Since these functions are called by other files -window.modalFunctions = function ( - courseIdParam, - modeParam, - modalSubmitUrlParam, -) { - let modalSubmitUrl = ""; - let nextModalId = ""; - - if (modeParam === "add_course") { - modalSubmitUrl = modalSubmitUrlParam; - nextModalId = "#addCourseModal"; - } else if (modeParam === "edit_schedule") { - modalSubmitUrl = modalSubmitUrlParam; - nextModalId = "#editScheduleModal"; - } else { - console.error("invalid or missing mode"); - } - - const courseId = courseIdParam; - window.attachEventListenersToModalContent( - modalSubmitUrl, - nextModalId, - courseId, - ); - if (courseId) { - document - .getElementById("select_schedule_form") - .setAttribute("data-course-id", courseId); - } -}; - -window.enableSubmit = function () { - const submitButton = document.getElementById("schedule_select_btn"); - submitButton.disabled = false; -}; - -window.attachEventListenersToModalContent = function ( - fetchUrl, - nextModalId, - courseIdParam, -) { - // the fetchUrl is the endpoint for loading the content into the modal - - const form = document.getElementById("select_schedule_form"); - if (!form) { - console.error("ERROR: missing form"); - return; - } - - form.addEventListener("submit", function (event) { - event.preventDefault(); - document.getElementById("schedule_select_btn").disabled = true; // prevent a double submission - - // get the related data from the form - const selectedSchedule = form.querySelector( - 'input[type="radio"][name="selected_schedules"]:checked', - ).value; - const courseId = courseIdParam; - - // prepare the request body - const requestBody = {}; - requestBody.schedule_id = selectedSchedule; - - if (fetchUrl === "/schedule/modal/editor") { - // if the fetch URL is for loading the editor, proceed - } else { - // else the other option is for loading the course sections - requestBody.course_id = courseId; - } - - fetch(fetchUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRFToken": getCookie("csrftoken"), - }, - body: JSON.stringify(requestBody), - }) - .then((resp) => resp.text()) - .then((html) => { - // insert the returned HTML and update hidden schedule ID - $("#second-modal-body").html(html); - - // close the select schedule modal and then open the next modal - $("#selectScheduleModal").modal("hide"); - setTimeout(function () { - // show the modal and then dispatch an event to the modal to - // let it know when to attach the submit event listener - $(nextModalId).modal("show"); - const modal = document.getElementById(nextModalId.substring(1)); - const modalEvent = new Event("modalLoaded"); - modal.dispatchEvent(modalEvent); - }, 400); - }) - .catch((error) => { - console.error("Error:", error); - alert("Something went wrong"); - }); - }); -}; diff --git a/tcf_website/static/schedule/schedule_with_sections.css b/tcf_website/static/schedule/schedule_with_sections.css deleted file mode 100644 index 7bdf3313e..000000000 --- a/tcf_website/static/schedule/schedule_with_sections.css +++ /dev/null @@ -1,44 +0,0 @@ -#schedule_sections_container { - display: flex; - overflow-x: auto; - align-items: flex-start; - gap: 10px; - max-height: 70vh; -} - -#schedule_sections_container > div { - flex: 1.4; -} - -#schedule_sections_container > form { - flex: 0.6; - overflow-y: auto; - padding-left: 10px; - border-left: 3px solid rgba(10, 10, 10, 0.1); -} - -.modal-dialog.modal-lg { - max-width: 75vw; -} - -.modal_instructor_card { - display: flex; - flex-direction: column; - border-bottom: 3px groove rgba(10, 10, 10, 0.5); -} -.instructor_name_label { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - padding-bottom: 10px; -} -.instructor_name_label > a { - display: flex; - margin-right: 10px; - margin-left: auto; -} - -.section { - margin-left: 12px; -} diff --git a/tcf_website/static/search/filters.js b/tcf_website/static/search/filters.js deleted file mode 100644 index d5a99f853..000000000 --- a/tcf_website/static/search/filters.js +++ /dev/null @@ -1,380 +0,0 @@ -document.addEventListener("DOMContentLoaded", function () { - // Determines if any filters (besides day ones) are active - const filterInputs = document.querySelectorAll( - '.dropdown-menu input[type="checkbox"]:not(.day-checkbox)', - ); - const dayFilters = document.querySelectorAll(".day-checkbox"); - const filterButton = document.getElementById("filter-button"); - const timeFrom = document.getElementById("from_time"); - const timeTo = document.getElementById("to_time"); - const openSections = document.getElementById("open-sections"); - const minGpaSlider = document.getElementById("min-gpa-slider"); - const minGpaText = document.getElementById("min-gpa-text"); - const minGpaInput = document.getElementById("min-gpa-input"); - - // Load filters from localStorage before setting up any event handlers - loadFiltersFromLocalStorage(); - - // Check initial state (in case of page refresh with active filters) - updateButtonState(); - - // Add event listeners to all filter inputs - [...filterInputs, ...dayFilters, openSections].forEach((input) => { - input.addEventListener("change", function () { - updateButtonState(); - saveFiltersToLocalStorage(); - }); - }); - - [timeFrom, timeTo].forEach((input) => { - input.addEventListener("input", function () { - updateButtonState(); - saveFiltersToLocalStorage(); - }); - }); - - // Min GPA slider and text input synchronization - minGpaSlider.addEventListener("input", function () { - const value = parseFloat(this.value).toFixed(1); // Ensures clean decimal - minGpaText.value = value; - minGpaInput.value = value; - updateButtonState(); - saveFiltersToLocalStorage(); - }); - - minGpaText.addEventListener("change", function () { - // Only update if valid number - let value = parseFloat(this.value); - - // Validate and constrain the value - if (isNaN(value)) { - value = 0.0; // Default to 0.0 if invalid - } else { - // Keep value between 0 and 4 - value = Math.max(0, Math.min(4, value)); - // Round to nearest 0.1 - value = Math.round(value * 10) / 10; - } - - // Update all inputs with the cleaned value - this.value = value.toFixed(1); - minGpaSlider.value = value; - minGpaInput.value = value; - updateButtonState(); - saveFiltersToLocalStorage(); - }); - - // Allow enter key on min-gpa-text to apply the filter - minGpaText.addEventListener("keyup", function (event) { - if (event.key === "Enter") { - event.preventDefault(); - this.blur(); // Remove focus to trigger change event - document.querySelector('button[type="submit"]').click(); // Submit the form - } - }); - - // Save current filter state to localStorage - function saveFiltersToLocalStorage() { - // Get selected subdepartments - const selectedSubdepts = Array.from( - document.querySelectorAll(".form-check-subjects:checked"), - ).map((input) => input.value); - - // Get selected disciplines - const selectedDisciplines = Array.from( - document.querySelectorAll(".form-check-disciplines:checked"), - ).map((input) => input.value.trim()); - - // Get selected weekdays - const selectedWeekdays = Array.from( - document.querySelectorAll(".day-checkbox:checked"), - ).map((input) => input.value); - - // Create filters object - const filters = { - subdepartments: selectedSubdepts, - disciplines: selectedDisciplines, - weekdays: selectedWeekdays, - from_time: timeFrom ? timeFrom.value : "", - to_time: timeTo ? timeTo.value : "", - open_sections: openSections ? openSections.checked : false, - min_gpa: minGpaInput ? parseFloat(minGpaInput.value || "0.0") : 0.0, - }; - - // Save to localStorage - localStorage.setItem("search_filters", JSON.stringify(filters)); - } - - // Load filters from localStorage - function loadFiltersFromLocalStorage() { - const savedFilters = localStorage.getItem("search_filters"); - if (!savedFilters) return; - - try { - const filters = JSON.parse(savedFilters); - - // Set subdepartments - if (filters.subdepartments && filters.subdepartments.length) { - filters.subdepartments.forEach((mnemonic) => { - const input = document.getElementById(`subject-${mnemonic}`); - if (input) { - input.checked = true; - } - }); - } - - // Set disciplines - if (filters.disciplines && filters.disciplines.length) { - filters.disciplines.forEach((name) => { - // Find the discipline by checking all discipline checkboxes with matching value - const disciplineCheckboxes = document.querySelectorAll( - ".form-check-disciplines", - ); - let found = false; - - disciplineCheckboxes.forEach((checkbox) => { - // Normalize both strings for comparison - trim whitespace and normalize case - const storedName = name.trim(); - const checkboxValue = checkbox.value.trim(); - - if (storedName === checkboxValue) { - checkbox.checked = true; - found = true; - } - }); - - if (!found) { - console.warn(`Discipline not found: ${name}`); - } - }); - } - - // Set weekdays - if (filters.weekdays && filters.weekdays.length) { - filters.weekdays.forEach((day) => { - const dayMap = { - MON: "monday", - TUE: "tuesday", - WED: "wednesday", - THU: "thursday", - FRI: "friday", - }; - const input = document.getElementById(dayMap[day]); - if (input) input.checked = true; - }); - updateWeekdays(); // Update the hidden input - } - - // Set time values - if (timeFrom && filters.from_time) timeFrom.value = filters.from_time; - if (timeTo && filters.to_time) timeTo.value = filters.to_time; - - // Set open sections - if (openSections && filters.open_sections !== undefined) { - openSections.checked = filters.open_sections; - } - - // Set min GPA - if (filters.min_gpa !== undefined) { - const gpaValue = parseFloat(filters.min_gpa).toFixed(1); - if (minGpaSlider) minGpaSlider.value = gpaValue; - if (minGpaText) minGpaText.value = gpaValue; - if (minGpaInput) minGpaInput.value = gpaValue; - } - } catch (e) { - console.error("Error loading filters from localStorage:", e); - } - } - - // Checks for active filters or inactive day filters to determine button state - function updateButtonState() { - const hasActiveFilters = - Array.from(filterInputs).some((input) => input.checked) || - Array.from(dayFilters).some((input) => input.checked) || - (timeFrom && timeFrom.value !== "") || - (timeTo && timeTo.value !== "") || - (openSections && openSections.checked) || - (minGpaInput && parseFloat(minGpaInput.value) !== 0.0); - - if (hasActiveFilters) { - filterButton.classList.add("filter-active"); - } else { - filterButton.classList.remove("filter-active"); - } - } - - const dropdown = document.getElementById("filter-dropdown"); - const searchInput = document.querySelector( - ".form-control.border.border-right-0", - ); - - searchInput.addEventListener("click", function (event) { - event.stopPropagation(); - }); - - // Only prevent dropdown from closing when clicking inside - dropdown.addEventListener("click", function (event) { - event.stopPropagation(); - }); - - // Generic function for filtering items - function setupSearch(searchInput, itemsSelector) { - const items = document.querySelectorAll(itemsSelector); - searchInput.addEventListener("input", function (e) { - const searchTerm = e.target.value.toLowerCase(); - items.forEach((item) => { - const text = item.textContent.toLowerCase(); - item.style.display = text.includes(searchTerm) ? "" : "none"; - }); - }); - } - - // Setup search functionality - setupSearch( - document.getElementById("subject-search"), - ".subject-list .form-check", - ); - setupSearch( - document.getElementById("discipline-search"), - ".discipline-list .form-check", - ); - - // Create a safer version of the reordering function - function reorderItems(containerSelector, checkboxSelector) { - const container = document.querySelector(containerSelector); - if (!container) { - console.error(`Container not found: ${containerSelector}`); - return; - } - - // Get all items and convert to array for sorting - const items = Array.from(container.querySelectorAll(".form-check")); - - if (items.length === 0) { - console.warn(`No items found in container: ${containerSelector}`); - return; - } - - // Instead of moving DOM elements, use CSS to set their order - items.forEach((item, index) => { - const checkbox = item.querySelector(checkboxSelector); - if (checkbox && checkbox.checked) { - item.style.order = "1"; // Checked items first - } else { - item.style.order = "2"; // Unchecked items second - } - }); - - // Make sure container uses flexbox for ordering - container.style.display = "flex"; - container.style.flexDirection = "column"; - } - - // Setup reordering for subjects and disciplines - function setupReordering(checkboxSelector, containerSelector) { - // Initial ordering - reorderItems(containerSelector, checkboxSelector); - - // Add change event to checkboxes - document.querySelectorAll(checkboxSelector).forEach((checkbox) => { - checkbox.addEventListener("change", () => { - // Reorder items when a checkbox changes - reorderItems(containerSelector, checkboxSelector); - saveFiltersToLocalStorage(); - }); - }); - } - - // Setup reordering after DOM is fully loaded - setupReordering(".form-check-subjects", ".subject-list"); - setupReordering(".form-check-disciplines", ".discipline-list"); - - const resetButton = document.querySelector('button[type="reset"]'); - resetButton.addEventListener("click", function (e) { - e.preventDefault(); // Prevent default reset behavior - clearFilters(); - }); - - // Function to clear all filters - function clearFilters() { - // Reset checkboxes - document.querySelectorAll('input[type="checkbox"]').forEach((checkbox) => { - checkbox.checked = false; - }); - - // Clear subject search - document.getElementById("subject-search").value = ""; - document.querySelectorAll(".subject-list .form-check").forEach((item) => { - item.style.display = ""; // Show all subjects - item.style.order = "2"; // Reset ordering - }); - - // Clear discipline search - document.getElementById("discipline-search").value = ""; - document - .querySelectorAll(".discipline-list .form-check") - .forEach((item) => { - item.style.display = ""; // Show all disciplines - item.style.order = "2"; // Reset ordering - }); - - // Set time inputs to empty - timeFrom.value = ""; - timeTo.value = ""; - - // Reset min GPA to default value of 0.0 - if (minGpaSlider) minGpaSlider.value = 0.0; - if (minGpaText) minGpaText.value = 0.0; - if (minGpaInput) minGpaInput.value = 0.0; - - updateWeekdays(); - updateButtonState(); - - // Clear localStorage filters - localStorage.removeItem("search_filters"); - } - - // Add weekdays handling - function updateWeekdays() { - const checkedDays = Array.from( - document.querySelectorAll(".day-checkbox:checked"), - ) - .map((cb) => cb.value) - .join("-"); - document.getElementById("weekdays-input").value = checkedDays; - } - - // Updates weekdays if div of checkbox is clicked - document.querySelectorAll(".form-check-inline").forEach((container) => { - container.addEventListener("click", (event) => { - const checkbox = container.querySelector(".day-checkbox"); - if (event.target === checkbox || event.target.closest("label")) { - return; - } - checkbox.checked = !checkbox.checked; - checkbox.dispatchEvent(new Event("change")); - updateWeekdays(); - updateButtonState(); - saveFiltersToLocalStorage(); - }); - }); - - // Update weekdays on checkbox change - document.querySelectorAll(".day-checkbox").forEach((checkbox) => { - checkbox.addEventListener("change", () => { - updateWeekdays(); - updateButtonState(); - saveFiltersToLocalStorage(); - }); - }); - - // Initialize weekdays - updateWeekdays(); - - // Clear filters when window is resized to mobile view - window.addEventListener("resize", function () { - if (window.innerWidth <= 992) { - clearFilters(); - } - }); -}); diff --git a/tcf_website/static/search/search.css b/tcf_website/static/search/search.css deleted file mode 100644 index f44462c5c..000000000 --- a/tcf_website/static/search/search.css +++ /dev/null @@ -1,16 +0,0 @@ -.search .search-results { - background-color: white; -} - -.result-nav .btn-group { - flex-wrap: wrap; -} - -.search-result-header h3, -.search-result-header p { - display: inline-block; -} - -.search-course-description { - color: var(--accent-color); -} diff --git a/tcf_website/static/search/searchbar.css b/tcf_website/static/search/searchbar.css deleted file mode 100644 index 759175e12..000000000 --- a/tcf_website/static/search/searchbar.css +++ /dev/null @@ -1,305 +0,0 @@ -.filter-summary { - background-color: #f7fafc; - padding: 0.375rem 0.75rem; - border: 1px solid #cbd5e0; - border-radius: 8px; - font-size: 0.9rem; - color: #4a5568; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 50%; -} - -.dropdown-wrapper { - position: relative; -} - -.filter-dropdown { - position: absolute !important; - top: 100% !important; - right: 0 !important; - left: auto !important; - margin-top: 0.5rem !important; - transform: none !important; - width: min(90vw, 800px); - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); - border-radius: 0.25rem; - border: 1px solid rgba(0, 0, 0, 0.1); -} - -.filter-active { - background-color: #d75626; - color: white; -} - -@media (max-width: 992px) { - #filter-button, - .dropdown-wrapper, - .filter-dropdown { - display: none !important; - } -} - -.filter-container { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.filter-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 1.25rem; - align-items: start; -} - -/* Top filter sections layout */ -.time-filter { - display: flex; - gap: 1.5rem; -} - -/* Filter row layout (Availability and Min GPA side by side) */ -.filter-row { - display: flex; - justify-content: space-between; - gap: 1.25rem; -} - -.filter-row .filter-section { - flex: 1; - margin-bottom: 0; -} - -/* Standardized filter containers */ -.filter-option-container { - background: white; - border: 1px solid #cbd5e0; - border-radius: 6px; - padding: 0.5rem 0.75rem; - height: 3rem; - display: flex; - align-items: center; - transition: all 0.15s ease; -} - -.filter-option-container:hover { - background: #f7fafc; - border-color: #90cdf4; -} - -/* Min GPA controls */ -.min-gpa-controls { - display: flex; - align-items: center; - width: 100%; -} - -.form-range { - flex: 1; - margin-right: 0.75rem; -} - -.min-gpa-text { - width: 60px !important; - padding: 0.25rem 0.5rem; - text-align: center; - font-size: 0.95rem; -} - -.filter-section .filter-header { - margin-bottom: 0.35rem; - font-size: 1rem; -} - -.form-check { - width: 100%; -} - -/* Remove redundant styles */ -.filter-section.compact { - margin-bottom: 0.75rem; -} - -.day-toggles { - display: flex; - gap: 0.5rem; - margin-bottom: 0.35rem; - align-items: center; - flex-wrap: wrap; - width: 100%; - justify-content: space-between; -} - -.day-toggles p { - margin: 0; - display: flex; - align-items: center; -} - -.time-selection { - background: white; - border: 1px solid #cbd5e0; - border-radius: 6px; - padding: 0.4rem 0.4rem; -} - -.day-toggles .form-check-inline { - background: white; - border: 1px solid #cbd5e0; - border-radius: 6px; - padding: 0.55rem 0.67rem; - transition: all 0.15s ease; -} - -.day-toggles .form-check-input:checked + .form-check-label { - color: #d75626; - font-weight: 600; -} - -.day-toggles .form-check-inline:hover { - background: #f7fafc; - border-color: #90cdf4; -} - -/* Availability section styles */ -.availability-filters { - display: flex; - flex-wrap: wrap; - width: 100%; -} - -.availability-option { - background: white; - border: 1px solid #cbd5e0; - border-radius: 6px; - padding: 0.55rem 0.67rem; - transition: all 0.15s ease; - width: 100%; -} - -.availability-option:hover { - background: #f7fafc; - border-color: #90cdf4; -} - -.min-gpa-container { - display: flex; - align-items: center; -} - -.form-check-input:checked { - accent-color: #d75626; -} - -.search-icon { - position: absolute; - left: 0.75rem; - top: 50%; - transform: translateY(-50%); - color: #718096; - pointer-events: none; -} - -.form-check-subjects, -.form-check-disciplines { - margin-top: 1.2% !important; - align-self: start !important; -} - -.subject-list, -.discipline-list { - max-height: 150px; - overflow-y: auto; - border: 1px solid #cbd5e0; - border-radius: 8px; - padding: 0.75rem; - margin-top: 0.5rem; - background: #fff; - min-height: 200px; -} - -.form-check.subdepartment-label, -.form-check.discipline-label { - display: flex; - justify-content: flex-start; -} - -.discipline-list { - /* removed max-height: 250px; */ -} - -.search-container { - position: relative; -} - -.search-container .form-control { - width: 100%; - padding-left: 2rem; -} - -.filter-actions { - display: flex; - justify-content: space-between; - margin-top: 1rem; - padding-top: 0.75rem; - border-top: 1px solid #e2e8f0; -} - -.form-check { - padding: 0.375rem 0; - margin: 0; - align-items: center; -} - -.form-check:hover { - background-color: #f7fafc; - border-radius: 4px; -} - -.form-check-label { - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - font-size: 0.9rem; -} - -.subject-list, -.discipline-list { - scrollbar-width: thin; - scrollbar-color: #cbd5e0 transparent; -} - -.btn-custom { - background-color: #d75626; - border: 2px solid #d75626; - border-radius: 4px; - color: white; - box-shadow: none !important; - outline: none !important; -} - -.btn-custom:hover { - background-color: #b4441d; - border-color: #b4441d; -} - -.btn-custom:focus, -.btn-custom:active { - box-shadow: none !important; - outline: none !important; - border: 2px solid #b4441d; -} - -.subject-list::-webkit-scrollbar, -.discipline-list::-webkit-scrollbar { - width: 4px; -} - -.subject-list::-webkit-scrollbar-thumb, -.discipline-list::-webkit-scrollbar-thumb { - background-color: #cbd5e0; - border-radius: 2px; -} diff --git a/tcf_website/templates/404.html b/tcf_website/templates/404.html index 553e6c3a3..7c95624cb 100644 --- a/tcf_website/templates/404.html +++ b/tcf_website/templates/404.html @@ -1,27 +1,21 @@ -{% extends "base/index.html" %} +{% extends "site/base.html" %} {% load static %} {% block title %}404 | theCourseForum{% endblock %} -{% block body %} - -