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 (