diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 091e97c..7addd07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: python -m pip install -r requirements.txt -r requirements-dev.txt - name: Run tests env: - DATABASE_URL: sqlite+pysqlite:///:memory: + DATABASE_URL: "sqlite+pysqlite:///:memory:" ENABLE_GEOCODING: "0" run: python -m pytest -q @@ -49,4 +49,3 @@ jobs: run: npm ci - name: Build run: npm run build - diff --git a/frontend/src/App.css b/frontend/src/App.css index 1eb7d96..6b3193c 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -265,6 +265,9 @@ min-height: 100vh; display: flex; flex-direction: column; + background: radial-gradient(900px 520px at 16% 0%, rgba(37, 99, 235, 0.14), transparent 60%), + radial-gradient(900px 520px at 86% 0%, rgba(239, 68, 68, 0.12), transparent 60%), + #f8fafc; } .landingHeader { @@ -273,20 +276,78 @@ justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid #e2e8f0; - background: #ffffff; + background: rgba(255, 255, 255, 0.75); + backdrop-filter: blur(10px); + position: sticky; + top: 0; + z-index: 10; } .landingBrand { font-weight: 800; letter-spacing: 0.2px; + color: #0f172a; + display: flex; + align-items: center; + gap: 10px; +} + +.landingBrandTag { + font-size: 11px; + font-weight: 700; + padding: 4px 8px; + border-radius: 999px; + color: #334155; + background: rgba(226, 232, 240, 0.7); + border: 1px solid rgba(226, 232, 240, 0.9); } .landingMain { + flex: 1; width: min(1040px, 100%); margin: 0 auto; padding: 24px 16px 40px 16px; } +.landingHero { + border: 1px solid rgba(226, 232, 240, 0.9); + border-radius: 18px; + padding: 18px 18px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.72)); + box-shadow: 0 18px 40px rgba(15, 23, 42, 0.07); +} + +.landingHeroGrid { + display: grid; + grid-template-columns: 1.25fr 0.75fr; + gap: 18px; + align-items: center; +} + +.landingHeroLeft { + min-width: 0; +} + +.landingPills { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 10px; +} + +.pill { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 650; + color: #0f172a; + border: 1px solid rgba(226, 232, 240, 0.9); + background: rgba(248, 250, 252, 0.85); +} + .landingHero h1 { margin: 0; font-size: 34px; @@ -316,6 +377,89 @@ color: #475569; } +.landingHeroRight { + min-width: 0; +} + +.landingMock { + border: 1px solid rgba(226, 232, 240, 0.9); + border-radius: 16px; + overflow: hidden; + background: rgba(255, 255, 255, 0.88); + box-shadow: 0 10px 26px rgba(15, 23, 42, 0.1); +} + +.landingMockMap { + height: 170px; + position: relative; + background: radial-gradient(260px 150px at 30% 30%, rgba(37, 99, 235, 0.22), transparent 70%), + radial-gradient(240px 150px at 70% 70%, rgba(239, 68, 68, 0.18), transparent 70%), + linear-gradient(180deg, rgba(241, 245, 249, 0.9), rgba(226, 232, 240, 0.65)); +} + +.landingMockPin { + position: absolute; + width: 12px; + height: 12px; + border-radius: 999px; + border: 2px solid rgba(255, 255, 255, 0.95); + box-shadow: 0 8px 20px rgba(15, 23, 42, 0.22); + transform: translate(-50%, -50%); +} + +.landingMockPin.listing { + background: #2563eb; +} + +.landingMockPin.target { + width: 14px; + height: 14px; + background: #ef4444; + box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.22), 0 8px 20px rgba(15, 23, 42, 0.22); +} + +.landingMockList { + padding: 12px 12px 12px 12px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.landingMockItem { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: 12px; + border: 1px solid rgba(226, 232, 240, 0.9); + background: rgba(248, 250, 252, 0.88); + font-size: 12px; + color: #0f172a; +} + +.landingMockDot { + width: 10px; + height: 10px; + border-radius: 999px; + flex: 0 0 auto; +} + +.landingMockDot.listing { + background: #2563eb; +} + +.landingMockText { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.landingMockMeta { + margin-left: auto; + color: #475569; + font-variant-numeric: tabular-nums; +} + .landingGrid { margin-top: 24px; display: grid; @@ -352,10 +496,27 @@ .landingFooter { border-top: 1px solid #e2e8f0; - background: #ffffff; + background: rgba(255, 255, 255, 0.75); + backdrop-filter: blur(10px); padding: 14px 16px; font-size: 12px; color: #475569; + display: flex; + gap: 8px; + flex-wrap: wrap; + align-items: center; + justify-content: center; + text-align: center; +} + +.landingFooter a { + color: inherit; + text-decoration: underline; + text-underline-offset: 2px; +} + +.landingFooter a:hover { + color: #0f172a; } @media (max-width: 960px) { @@ -373,6 +534,10 @@ grid-template-columns: 1fr; } + .landingHeroGrid { + grid-template-columns: 1fr; + } + .landingHero h1 { font-size: 28px; } diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index 97a1232..396de53 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -6,34 +6,75 @@ export default function LandingPage() { return (
-
EasyRelocate
-
- - Open app - +
+ EasyRelocate US-only MVP
-

Relocation housing, compared in one place.

-

- EasyRelocate is an open-source, non-commercial decision-support tool - for interns/students relocating to a new city. Save listings while - you browse (starting with Airbnb), then compare them on a single map - with price + distance filters. -

-
- - Start comparing - -
-
- MVP is US-only for now. Listing locations may be approximate. +
+
+
+ Chrome extension + Map + list + Open-source +
+

Relocation housing, compared in one place.

+

+ EasyRelocate is a non-commercial decision-support tool for interns/students + relocating to a new city. Save listings while you browse (starting with + Airbnb), then compare them on one map with price + distance filters. +

+
+ + Start comparing + + + How it works + +
+
+ US-only MVP for now. Listing locations may be approximate. +
+
+ +
-
+

What & why

)