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 (
+
+
+
+
+
+
+
+

+
+
+
+
+
+
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