diff --git a/frontend/bun.lock b/frontend/bun.lock index 0d0118a9..8df865b9 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -12,6 +12,7 @@ "react": "^18.3.1", "react-bootstrap": "^2.10.0", "react-dom": "^18.3.1", + "react-router-dom": "^7.14.0", "react-treeview": "^0.4.7", "styled-components": "^6.1.0", "uuid": "^8.2.0", @@ -260,6 +261,8 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + "css-box-model": ["css-box-model@1.2.1", "", { "dependencies": { "tiny-invariant": "^1.0.6" } }, "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw=="], "css-color-keywords": ["css-color-keywords@1.0.0", "", {}, "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="], @@ -352,6 +355,10 @@ "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + "react-router": ["react-router@7.14.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ=="], + + "react-router-dom": ["react-router-dom@7.14.0", "", { "dependencies": { "react-router": "7.14.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ=="], + "react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="], "react-treeview": ["react-treeview@0.4.7", "", { "dependencies": { "prop-types": "^15.5.8" } }, "sha1-9kfgT3BJbrEfsJEsNRh+gOtg1Fg="], @@ -364,6 +371,8 @@ "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + "shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="], "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], diff --git a/frontend/package.json b/frontend/package.json index f4014341..110824f3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "react": "^18.3.1", "react-bootstrap": "^2.10.0", "react-dom": "^18.3.1", + "react-router-dom": "^7.14.0", "react-treeview": "^0.4.7", "styled-components": "^6.1.0", "uuid": "^8.2.0" diff --git a/frontend/src/assets/TigerPath Landing Graphic.svg b/frontend/src/assets/TigerPath Landing Graphic.svg new file mode 100644 index 00000000..4753d15a --- /dev/null +++ b/frontend/src/assets/TigerPath Landing Graphic.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/TigerPath Waves.svg b/frontend/src/assets/TigerPath Waves.svg new file mode 100644 index 00000000..cb50aff1 --- /dev/null +++ b/frontend/src/assets/TigerPath Waves.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/components/Landing.jsx b/frontend/src/components/Landing.jsx new file mode 100644 index 00000000..86f0222c --- /dev/null +++ b/frontend/src/components/Landing.jsx @@ -0,0 +1,58 @@ +import React from 'react' +import 'styles/landing.css' +import tigerImg from '../assets/TigerPath Landing Graphic.svg' +import waveImg from '../assets/TigerPath Waves.svg' + + +function Landing() { + return ( +
+ + +
+ + feedback + +
+
+
+
+ Tiger on a ship +
+
+ +
+
+

TigerPath

+

+ Plan your entire Princeton journey with TigerPath.

+
+ SIGN IN +

Made by TigerApps +

+
+
+ + +
+ ) +} + +export default Landing diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index db6490be..af840671 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -7,7 +7,9 @@ import './legacy/tutorial'; import React from 'react'; import { createRoot } from 'react-dom/client'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; // landing page import App from 'components/App'; +import Landing from 'components/Landing'; import 'styles/app-style.css'; import 'styles/messages.css'; import 'styles/Courses.css'; diff --git a/frontend/src/landing.jsx b/frontend/src/landing.jsx new file mode 100644 index 00000000..13b31e73 --- /dev/null +++ b/frontend/src/landing.jsx @@ -0,0 +1,9 @@ +import './bootstrap-init'; + +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import Landing from 'components/Landing'; + +const container = document.getElementById('app'); +const root = createRoot(container); +root.render(); diff --git a/frontend/src/styles/app-style.css b/frontend/src/styles/app-style.css index ad9d69c2..0c670f1f 100644 --- a/frontend/src/styles/app-style.css +++ b/frontend/src/styles/app-style.css @@ -95,6 +95,7 @@ h6 { margin-left: 0; } +/* account for navbar, 56 px*/ #app { height: calc(100vh - 78px); overflow: hidden; diff --git a/frontend/src/styles/landing.css b/frontend/src/styles/landing.css new file mode 100644 index 00000000..308fcf96 --- /dev/null +++ b/frontend/src/styles/landing.css @@ -0,0 +1,438 @@ +html, +body { + margin: 0; + min-height: 100%; + overflow-x: hidden; +} + +body { + background: #d9e5ff; +} + +#app { + min-height: 100vh; +} + +.landing-container { + --ink: #3a2a5e; + --link-hover: #762fb8; + --wave-height: clamp(120px, 14vw, 202px); + --scene-gap: clamp(24px, 4vw, 64px); + --scene-offset-y: clamp(28px, 4vh, 52px); + --boat-shift-x: 0; + + position: relative; + isolation: isolate; + min-height: 100vh; + min-height: 100svh; + overflow: hidden; + display: flex; + flex-direction: column; + padding: clamp(12px, 2vw, 24px); + padding-bottom: var(--wave-height); + font-family: 'Lateef', serif; + color: var(--ink); + background: + radial-gradient(circle at 14% 18%, rgba(255, 236, 249, 0.72), transparent 28rem), + linear-gradient(180deg, #d9e5ff 0%, #ffecf9 100%); +} + +.landing-filter { + position: absolute; + pointer-events: none; +} + +.hyperlink { + position: relative; + color: var(--ink); + text-decoration: none; + transition: color 0.2s ease; +} + +.hyperlink::after { + content: ''; + position: absolute; + bottom: 0.06em; + left: 0; + width: 0; + height: 0.12em; + filter: url(#sketchy); + background-color: currentColor; + border-radius: 999px; + transition: width 0.2s ease; +} + +.hyperlink:hover, +.hyperlink:focus-visible { + color: var(--link-hover); +} + +.hyperlink:hover::after, +.hyperlink:focus-visible::after { + width: 100%; +} + +.upper-bar { + position: relative; + z-index: 5; + display: flex; + justify-content: flex-end; + width: 100%; + max-width: 1240px; + margin: 0 auto; + padding: 0 clamp(4px, 1vw, 12px); +} + +.feedback-link { + font-size: clamp(1.2rem, 1.4vw, 1.6rem); + line-height: 1.2; +} + +.body { + position: relative; + z-index: 2; + width: min(100%, 1240px); + min-height: calc(100vh - var(--wave-height) - 56px); + min-height: calc(100svh - var(--wave-height) - 56px); + margin: 0 auto; + display: grid; + grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr); + align-items: end; + gap: var(--scene-gap); + transform: translateY(var(--scene-offset-y)); +} + +.landing-left { + align-self: stretch; + min-height: 0; + min-width: 0; + display: flex; + align-items: end; + justify-content: center; +} + +.landing-left-graphics { + width: 100%; + min-height: min(68vh, 700px); + display: flex; + align-items: end; + justify-content: center; +} + +@keyframes bob { + 0%, + 100% { + transform: translate(var(--boat-shift-x), 0); + } + 50% { + transform: translate(var(--boat-shift-x), -1.05rem); + } +} + +.tiger-illustration { + display: block; + width: min(112%, 620px); + max-height: min(80vh, 740px); + object-fit: contain; + object-position: bottom center; + margin-bottom: calc(var(--wave-height) * -0.46); + filter: drop-shadow(0 18px 20px rgba(58, 42, 94, 0.11)); + animation: bob 5s ease-in-out infinite; +} + +.landing-right { + align-self: center; + min-width: 0; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding-bottom: clamp(32px, 7vh, 96px); +} + +.landing-header { + width: min(100%, 34rem); + max-width: 34rem; + transition: transform 0.25s ease; +} + +.landing-header:hover { + transform: scale(1.015); +} + +.landing-title { + margin: 0; + font-family: 'Yuji Mai', serif; + font-size: clamp(4rem, 7vw, 6rem); + line-height: 0.95; + letter-spacing: 0; + color: var(--ink); +} + +.landing-subtitle { + margin: clamp(12px, 2vh, 20px) auto clamp(28px, 5vh, 56px); + width: 100%; + max-width: 28rem; + font-family: 'Lateef', serif; + font-size: clamp(1.45rem, 2.1vw, 2rem); + line-height: 1.15; + font-weight: 400; + color: var(--ink); +} + +.sign-in-button { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 9.5rem; + min-height: 4.1rem; + padding: 0.55rem 1.75rem 0.8rem; + font-family: 'Yuji Syuku', serif; + font-size: clamp(1.5rem, 2.8vw, 2rem); + font-weight: 800; + letter-spacing: 0; + line-height: 1; + color: var(--ink); + text-decoration: none; + border-radius: 999px; + background: #dfe1ff; + animation: color-shift 8s ease-in-out infinite; + transition: transform 0.2s ease, background-color 0.2s ease; +} + +.sign-in-button:hover, +.sign-in-button:focus-visible { + background: #bfa7ec; + transform: scale(1.045); +} + +@keyframes color-shift { + 0% { + background: #ffecf9; + } + 33% { + background: #c5b8f0; + } + 66% { + background: #c0ccff; + } + 100% { + background: #ffecf9; + } +} + +.sign-in-button::after { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + border: 0.32rem solid var(--ink); + filter: url(#sketchy); + pointer-events: none; +} + +.landing-credit { + margin: clamp(18px, 4vh, 40px) 0 0; + font-size: clamp(1rem, 1.3vw, 1.2rem); + line-height: 1.2; + transition: transform 0.25s ease; +} + +.landing-credit:hover { + transform: scale(1.02); +} + +.wave-wrapper { + position: fixed; + z-index: 3; + left: 0; + right: 0; + bottom: -1px; + height: var(--wave-height); + overflow: hidden; + pointer-events: none; +} + +.wave-carousel { + display: flex; + width: 100%; + height: 100%; + overflow: hidden; +} + +.wave-illustration { + display: block; + width: 112vw; + height: 100%; + flex: 0 0 112vw; + margin-left: -6vw; + object-fit: cover; + object-position: top center; + animation: wave-sway 4.8s ease-in-out infinite alternate; +} + +.wave-illustration:not(:first-child) { + display: none; +} + +@keyframes wave-sway { + 0% { + transform: translateX(-2vw); + } + 100% { + transform: translateX(2vw); + } +} + +::-webkit-scrollbar { + width: 12px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background-color: #c0ccff; + border: 3px solid transparent; + border-radius: 20px; + background-clip: content-box; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #bfa7ec; +} + +@supports (scrollbar-color: auto) { + html { + scrollbar-color: #c0ccff transparent; + scrollbar-width: thin; + } +} + +@media (max-width: 860px) { + .landing-container { + --wave-height: clamp(96px, 24vw, 150px); + --scene-offset-y: clamp(18px, 3vh, 30px); + --boat-shift-x: -8vw; + + min-height: 100vh; + min-height: 100svh; + overflow-y: auto; + padding: 14px 18px var(--wave-height); + } + + .upper-bar { + padding: 0; + } + + .body { + min-height: auto; + grid-template-columns: 1fr; + align-items: center; + gap: clamp(8px, 2vh, 20px); + padding-top: clamp(18px, 4vh, 40px); + } + + .landing-right { + order: 1; + padding-bottom: 0; + } + + .landing-left { + order: 2; + width: 100%; + } + + .landing-left-graphics { + min-height: clamp(270px, 43vh, 430px); + } + + .tiger-illustration { + width: min(104vw, 430px); + max-height: 52vh; + margin-bottom: calc(var(--wave-height) * -0.4); + } + + .landing-title { + font-size: clamp(2.25rem, 9.5vw, 3.7rem); + } + + .landing-subtitle { + margin-bottom: clamp(22px, 4vh, 38px); + max-width: 18rem; + font-size: clamp(1.2rem, 5vw, 1.45rem); + } + + .sign-in-button { + min-width: 8.5rem; + min-height: 3.7rem; + } +} + +@media (max-width: 520px) { + .landing-container { + --wave-height: clamp(86px, 28vw, 126px); + --scene-offset-y: 18px; + } + + .feedback-link { + font-size: 1.15rem; + } + + .landing-left-graphics { + min-height: clamp(230px, 38vh, 340px); + } + + .tiger-illustration { + width: min(108vw, 390px); + } +} + +@media (max-height: 650px) and (min-width: 861px) { + .landing-container { + --wave-height: clamp(98px, 12vw, 150px); + --scene-offset-y: 20px; + } + + .body { + align-items: center; + } + + .landing-left-graphics { + min-height: 0; + } + + .tiger-illustration { + max-height: 66vh; + margin-bottom: calc(var(--wave-height) * -0.48); + } + + .landing-right { + padding-bottom: 24px; + } + + .landing-title { + font-size: clamp(3.6rem, 6.3vw, 5.2rem); + } + + .landing-subtitle { + margin-bottom: 26px; + } +} + +@media (prefers-reduced-motion: reduce) { + .tiger-illustration, + .wave-illustration, + .sign-in-button { + animation: none; + } + + .landing-header, + .landing-credit, + .sign-in-button { + transition: none; + } +} diff --git a/tigerpath/templates/tigerpath/new_landing.html b/tigerpath/templates/tigerpath/new_landing.html new file mode 100644 index 00000000..f3e1df79 --- /dev/null +++ b/tigerpath/templates/tigerpath/new_landing.html @@ -0,0 +1,58 @@ +{% load django_vite static %} + + + + + + + + + TigerPath - Four-Year Course Planner + + + + + + + + + + + + + + + + {% include 'includes/analytics/analytics_head.html' %} + + + {% vite_hmr_client %} + {% vite_react_refresh %} + + + + {% include 'includes/analytics/analytics_body.html' %} + +
+ {% vite_asset 'src/landing.jsx' %} + + + diff --git a/tigerpath/views.py b/tigerpath/views.py index 3a0f1dd5..1d6a7743 100644 --- a/tigerpath/views.py +++ b/tigerpath/views.py @@ -110,7 +110,7 @@ def index(request): context["preloaded_requirements_json"] = json.dumps(preloaded_requirements) return render(request, "tigerpath/index.html", context) else: - return landing(request) + return render(request, "tigerpath/new_landing.html") # landing page