Spaces:
Running
Running
v2.3: Data accuracy + security + UI cleanup
Browse files- Add 6 new unicorns (Neysa, Ather Energy, Captain Fresh, Kuku FM, Euler Motors, Kissht)
- Update Perfios valuation to $1B
- Replace all hardcoded stats with live DB queries in directory pages
- Add slowapi rate limiting (60/min general, input length validation)
- Sanitize LIKE queries (escape % and _ wildcards)
- Strip glassmorphism/blur, gradient-text, shimmer animations
- Add data disclaimer banner
- Health endpoint returns live counts from DB
- README updated with accurate numbers and data disclaimer
- README.md +40 -92
- backend/directory.py +29 -16
- backend/main.py +55 -11
- backend/routes/entities.py +15 -7
- backend/seed.py +7 -2
- frontend/index.html +1 -1
- frontend/src/App.jsx +10 -1
- frontend/src/components/AnalyticsPanel.jsx +1 -1
- frontend/src/components/EntityDetail.jsx +1 -1
- frontend/src/components/Sidebar.jsx +1 -1
- frontend/src/components/StatsBar.jsx +3 -54
- frontend/src/index.css +4 -11
- frontend/src/styles/index.css +6 -23
- requirements.txt +1 -0
README.md
CHANGED
|
@@ -8,119 +8,67 @@ app_port: 7860
|
|
| 8 |
suggested_hardware: cpu-basic
|
| 9 |
---
|
| 10 |
|
| 11 |
-
# Bharat Tech Atlas v2.
|
| 12 |
|
| 13 |
-
|
| 14 |
|
| 15 |
-
|
| 16 |
|
| 17 |
-
##
|
| 18 |
-
- **Server-side grid clustering** at low zoom → numbered bubbles with count, color-coded by density
|
| 19 |
-
- **Individual points** at high zoom (≥12) with entity labels, unicorn gold strokes
|
| 20 |
-
- **Smooth zoom transitions**: click a cluster → zooms to expansion level
|
| 21 |
-
- **City labels** under cluster bubbles with glow ring effects
|
| 22 |
-
- **Adaptive grid cells**: cell size scales with zoom level for natural clustering
|
| 23 |
|
| 24 |
-
|
| 25 |
-
- **
|
| 26 |
-
- **
|
| 27 |
-
- **
|
| 28 |
-
- **Awards & Recognition** section merges Unicorn Status + Special Awards — less scrolling
|
| 29 |
-
- **Entity Detail panel** (new): full profile with metrics, investors, links, nearby entities
|
| 30 |
-
- **Analytics Panel** (new): Overview / Sectors / Geography tabs with charts
|
| 31 |
|
| 32 |
-
##
|
| 33 |
-
- **Floating widget cards** with breathing room (replaced single-bar strip)
|
| 34 |
-
- Each stat is an independent card with glow hover effects and icon drop shadows
|
| 35 |
-
- Collapsible with ▼ Hide / ▲ Show toggle
|
| 36 |
-
- Unicorn count gets pulsing indicator when > 100
|
| 37 |
-
|
| 38 |
-
### Backend Performance
|
| 39 |
-
- **Bounding-box viewport queries** via R-Tree spatial index → O(log n) lookups
|
| 40 |
-
- **Server-side clustering endpoint** (`/api/entities/clusters`): returns pre-aggregated clusters at low zoom, individual GeoJSON at high zoom — avoids sending full payload
|
| 41 |
-
- **Request abort on pan/zoom**: cancels stale in-flight fetches
|
| 42 |
-
- **Optimized SQLite PRAGMAs**: WAL mode, 8MB cache, 64MB mmap, MEMORY temp store
|
| 43 |
-
- **Composite indexes** for common filter combos (type+state, type+funding)
|
| 44 |
-
- **Viewport summary** endpoint for lightweight count-only queries during pan/zoom
|
| 45 |
-
|
| 46 |
-
### SEO Directory Pages
|
| 47 |
-
- `/api/entities/directory/startups` — Top 100 startups
|
| 48 |
-
- `/api/entities/directory/startups/{state}` — By state
|
| 49 |
-
- `/api/entities/directory/startups/{state}/{sector}` — By state + sector
|
| 50 |
-
- `/api/entities/directory/unicorns` — All unicorns
|
| 51 |
-
- `/api/entities/directory/unicorns/{sector}` — By sector
|
| 52 |
-
- Schema.org JSON-LD, OG tags, canonical URLs, breadcrumbs
|
| 53 |
-
|
| 54 |
-
## Interactive Map Features
|
| 55 |
|
| 56 |
### Map Modes
|
| 57 |
- **Clusters** (default): Server-side numbered bubble clusters at low zoom, individual points at high zoom
|
| 58 |
-
- **Points**:
|
| 59 |
- **Heatmap**: Funding-weighted density visualization
|
| 60 |
|
| 61 |
-
### Filtering
|
| 62 |
-
-
|
| 63 |
-
-
|
| 64 |
-
-
|
| 65 |
-
- **Business Models**: Lifestyle, Scalable, Social Enterprise, Large Company
|
| 66 |
-
- **Special Awards**: Women-led (630), Rural Impact (431), Campus (276), NSA 5.0 (128)
|
| 67 |
-
- **Unicorn Status**: 119 Unicorns
|
| 68 |
-
- **Startup Stage**: Ideation → Validation → Early Traction → Scaling → Mature
|
| 69 |
-
- **Location**: State filter with search
|
| 70 |
|
| 71 |
### Entity Profiles
|
| 72 |
-
-
|
| 73 |
-
-
|
| 74 |
-
-
|
| 75 |
-
- Social links: Website, LinkedIn, Twitter, Instagram
|
| 76 |
-
- Nearby entities with distance
|
| 77 |
-
- Badges: Unicorn, Women-led, Rural Impact, Campus, NSA, DPIIT
|
| 78 |
|
| 79 |
## Tech Stack
|
| 80 |
|
| 81 |
| Layer | Technology |
|
| 82 |
|-------|-----------|
|
| 83 |
-
|
|
| 84 |
-
|
|
| 85 |
-
|
|
| 86 |
-
|
|
| 87 |
-
|
|
| 88 |
|
| 89 |
## API Endpoints
|
| 90 |
|
| 91 |
-
- `GET /api/entities/clusters` —
|
| 92 |
-
- `GET /api/entities/geojson` — Viewport-optimized raw GeoJSON
|
| 93 |
-
- `GET /api/entities/viewport/summary` —
|
| 94 |
-
- `GET /api/entities/heatmap` — Heatmap
|
| 95 |
-
- `GET /api/entities/nearby` — Proximity search
|
| 96 |
-
- `GET /api/entities/search` — Full-text search
|
| 97 |
-
- `GET /api/entities/detail/{slug}` —
|
| 98 |
-
- `GET /api/entities/facets` — Filter
|
| 99 |
-
- `GET /api/entities/analytics/overview` — Ecosystem
|
| 100 |
- `GET /api/entities/analytics/sectors` — Sector breakdown
|
| 101 |
-
- `GET /
|
|
|
|
| 102 |
|
| 103 |
## Data
|
| 104 |
|
| 105 |
-
|
| 106 |
-
- **
|
| 107 |
-
- **
|
| 108 |
-
- **
|
| 109 |
-
- **
|
| 110 |
-
- **
|
| 111 |
-
- **
|
| 112 |
-
|
| 113 |
-
## Geographic Distribution (Top 10 States)
|
| 114 |
-
|
| 115 |
-
| State | Count |
|
| 116 |
-
|-------|-------|
|
| 117 |
-
| Maharashtra | 1,157 |
|
| 118 |
-
| Karnataka | 1,123 |
|
| 119 |
-
| Haryana | 527 |
|
| 120 |
-
| Delhi | 471 |
|
| 121 |
-
| Tamil Nadu | 452 |
|
| 122 |
-
| Telangana | 330 |
|
| 123 |
-
| Gujarat | 226 |
|
| 124 |
-
| Uttar Pradesh | 216 |
|
| 125 |
-
| Kerala | 187 |
|
| 126 |
-
| West Bengal | 146 |
|
|
|
|
| 8 |
suggested_hardware: cpu-basic
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# Bharat Tech Atlas v2.3
|
| 12 |
|
| 13 |
+
Mapping platform for India's startup ecosystem — curated dataset of startups, SMEs, college E-Cells, incubators, and accelerators.
|
| 14 |
|
| 15 |
+
> **Data disclaimer**: This is a curated subset. India has 223,000+ DPIIT-registered startups (as of April 2026). Stats shown here reflect only what's in our database. Sources: DPIIT, Tracxn, Crunchbase, LinkedIn.
|
| 16 |
|
| 17 |
+
## What's New in v2.3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
- **Data accuracy**: 125 unicorns (added Neysa, Ather Energy, Captain Fresh, Kuku FM, Euler Motors, Kissht; updated Perfios)
|
| 20 |
+
- **Security**: Rate limiting (60 req/min general, 10 req/min search), input sanitization, LIKE wildcard escaping
|
| 21 |
+
- **Dynamic stats**: All directory page counts pulled from DB — no hardcoded numbers
|
| 22 |
+
- **UI cleanup**: Removed glassmorphism/blur, gradient text, shimmer animations. Flat dark + single accent (#F97316)
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
## Map Features
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
### Map Modes
|
| 27 |
- **Clusters** (default): Server-side numbered bubble clusters at low zoom, individual points at high zoom
|
| 28 |
+
- **Points**: All entities as colored dots
|
| 29 |
- **Heatmap**: Funding-weighted density visualization
|
| 30 |
|
| 31 |
+
### Filtering
|
| 32 |
+
- Entity types, sectors, DPIIT categories, business models, stage, location
|
| 33 |
+
- Special filters: unicorns, women-led, rural impact, campus startups, NSA winners
|
| 34 |
+
- State filter with fly-to
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
### Entity Profiles
|
| 37 |
+
- Metrics: founded year, team size, funding
|
| 38 |
+
- Investors, social links, nearby entities
|
| 39 |
+
- Badges: unicorn, women-led, rural impact, campus, NSA, DPIIT
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
## Tech Stack
|
| 42 |
|
| 43 |
| Layer | Technology |
|
| 44 |
|-------|-----------|
|
| 45 |
+
| Frontend | React 18 + Vite + MapLibre GL JS + Tailwind CSS |
|
| 46 |
+
| Backend | FastAPI + slowapi rate limiting |
|
| 47 |
+
| Database | SQLite with R-Tree spatial index |
|
| 48 |
+
| Map Tiles | CARTO Dark Matter |
|
| 49 |
+
| Deployment | Docker on Hugging Face Spaces |
|
| 50 |
|
| 51 |
## API Endpoints
|
| 52 |
|
| 53 |
+
- `GET /api/entities/clusters` — Server-side clustered GeoJSON
|
| 54 |
+
- `GET /api/entities/geojson` — Viewport-optimized raw GeoJSON
|
| 55 |
+
- `GET /api/entities/viewport/summary` — Viewport counts
|
| 56 |
+
- `GET /api/entities/heatmap` — Heatmap GeoJSON with weights
|
| 57 |
+
- `GET /api/entities/nearby` — Proximity search
|
| 58 |
+
- `GET /api/entities/search` — Full-text search (sanitized input)
|
| 59 |
+
- `GET /api/entities/detail/{slug}` — Entity profile with nearby
|
| 60 |
+
- `GET /api/entities/facets` — Filter counts
|
| 61 |
+
- `GET /api/entities/analytics/overview` — Ecosystem stats
|
| 62 |
- `GET /api/entities/analytics/sectors` — Sector breakdown
|
| 63 |
+
- `GET /directory/*` — SEO directory pages (HTML)
|
| 64 |
+
- `GET /api/health` — Health check with live entity/unicorn counts
|
| 65 |
|
| 66 |
## Data
|
| 67 |
|
| 68 |
+
Curated dataset across India (counts are dynamic from DB):
|
| 69 |
+
- **125 Unicorns**: Flipkart, PhonePe, Zerodha, Zomato, Swiggy, CRED, Zepto, Neysa, Ather Energy + more
|
| 70 |
+
- **Women-led startups**: Nykaa, Mamaearth, Sugar Cosmetics, OPEN Financial + more
|
| 71 |
+
- **Rural impact**: DeHaat, CropIn, Stellapps, Aye Finance + more
|
| 72 |
+
- **Campus startups**: Pixxel, TartanSense, Zepto + more
|
| 73 |
+
- **NSA winners**: Tagged with award categories
|
| 74 |
+
- **DPIIT recognized**: Government-recognized startups
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/directory.py
CHANGED
|
@@ -59,8 +59,8 @@ DIRECTORY_HTML_TEMPLATE = """<!DOCTYPE html>
|
|
| 59 |
:root{{--bg:#0F172A;--surface:#1E293B;--border:#334155;--text:#F8FAFC;--muted:#94A3B8;--brand:#F97316;}}
|
| 60 |
*{{margin:0;padding:0;box-sizing:border-box}}
|
| 61 |
body{{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh}}
|
| 62 |
-
.header{{background:
|
| 63 |
-
.header h1{{font-size:1.1rem;font-weight:800;
|
| 64 |
.header a{{color:var(--muted);text-decoration:none;font-size:0.85rem}}
|
| 65 |
.header a:hover{{color:var(--brand)}}
|
| 66 |
.hero{{padding:3rem 2rem;text-align:center;border-bottom:1px solid var(--border)}}
|
|
@@ -85,7 +85,7 @@ DIRECTORY_HTML_TEMPLATE = """<!DOCTYPE html>
|
|
| 85 |
.card p.desc{{font-size:0.8rem;color:var(--muted);margin-bottom:0.75rem;line-height:1.4}}
|
| 86 |
.card .meta{{font-size:0.75rem;color:var(--muted);display:flex;gap:1rem;flex-wrap:wrap}}
|
| 87 |
.badge{{font-size:0.7rem;padding:0.2rem 0.5rem;border-radius:4px;font-weight:500}}
|
| 88 |
-
.unicorn-badge{{
|
| 89 |
.women-badge{{background:rgba(236,72,153,0.15);color:#F472B6}}
|
| 90 |
.rural-badge{{background:rgba(34,197,94,0.15);color:#4ADE80}}
|
| 91 |
.dpiit-badge{{background:rgba(59,130,246,0.15);color:#60A5FA}}
|
|
@@ -132,12 +132,12 @@ DIRECTORY_HTML_TEMPLATE = """<!DOCTYPE html>
|
|
| 132 |
{cards_html}
|
| 133 |
</div>
|
| 134 |
<div class="cta">
|
| 135 |
-
<a href="/">🗺️ Explore the full interactive map
|
| 136 |
</div>
|
| 137 |
</div>
|
| 138 |
<footer>
|
| 139 |
-
Bharat Tech Atlas —
|
| 140 |
-
|
| 141 |
</footer>
|
| 142 |
</body>
|
| 143 |
</html>"""
|
|
@@ -225,6 +225,13 @@ router = APIRouter()
|
|
| 225 |
@router.get("/startups", response_class=HTMLResponse)
|
| 226 |
async def directory_startups_root():
|
| 227 |
conn = get_db()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
rows = conn.execute("""
|
| 229 |
SELECT * FROM entities
|
| 230 |
WHERE entity_type = 'startup' AND is_active = 1
|
|
@@ -235,11 +242,11 @@ async def directory_startups_root():
|
|
| 235 |
|
| 236 |
items = [row_to_dict(r) for r in rows]
|
| 237 |
|
| 238 |
-
stats_html = """
|
| 239 |
-
<div class="stat"><div class="num">
|
| 240 |
-
<div class="stat"><div class="num">
|
| 241 |
-
<div class="stat"><div class="num">
|
| 242 |
-
<div class="stat"><div class="num">
|
| 243 |
"""
|
| 244 |
|
| 245 |
filter_nav = "".join([
|
|
@@ -251,7 +258,7 @@ async def directory_startups_root():
|
|
| 251 |
|
| 252 |
html = _build_directory_page(
|
| 253 |
title="Indian Startups Directory",
|
| 254 |
-
description="
|
| 255 |
keywords="Indian startups, India startup directory, unicorn startups India, funded startups, DPIIT startups, tech startups Bangalore, Mumbai startups",
|
| 256 |
canonical="https://huggingface.co/spaces/Ram2005/StartupMap-India/directory/startups",
|
| 257 |
breadcrumb_last="Startups",
|
|
@@ -357,15 +364,21 @@ async def directory_unicorns():
|
|
| 357 |
ORDER BY funding_inr DESC
|
| 358 |
LIMIT 150
|
| 359 |
""").fetchall()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
conn.close()
|
| 361 |
|
| 362 |
items = [row_to_dict(r) for r in rows]
|
| 363 |
|
| 364 |
stats_html = f"""
|
| 365 |
<div class="stat"><div class="num">{len(items)}</div><div class="label">Unicorns</div></div>
|
| 366 |
-
<div class="stat"><div class="num">
|
| 367 |
-
<div class="stat"><div class="num">
|
| 368 |
-
<div class="stat"><div class="num">$
|
| 369 |
"""
|
| 370 |
|
| 371 |
filter_nav = "".join([
|
|
@@ -377,7 +390,7 @@ async def directory_unicorns():
|
|
| 377 |
|
| 378 |
html = _build_directory_page(
|
| 379 |
title="Indian Unicorns",
|
| 380 |
-
description="
|
| 381 |
keywords="Indian unicorns, Indian unicorn startups, India unicorn list, Flipkart, PhonePe, Zepto, Zomato, Swiggy, unicorn valuation",
|
| 382 |
canonical="https://huggingface.co/spaces/Ram2005/StartupMap-India/directory/unicorns",
|
| 383 |
breadcrumb_last="Unicorns",
|
|
|
|
| 59 |
:root{{--bg:#0F172A;--surface:#1E293B;--border:#334155;--text:#F8FAFC;--muted:#94A3B8;--brand:#F97316;}}
|
| 60 |
*{{margin:0;padding:0;box-sizing:border-box}}
|
| 61 |
body{{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh}}
|
| 62 |
+
.header{{background:var(--bg);border-bottom:1px solid var(--border);padding:1rem 2rem;display:flex;align-items:center;justify-content:space-between;}}
|
| 63 |
+
.header h1{{font-size:1.1rem;font-weight:800;color:var(--brand)}}
|
| 64 |
.header a{{color:var(--muted);text-decoration:none;font-size:0.85rem}}
|
| 65 |
.header a:hover{{color:var(--brand)}}
|
| 66 |
.hero{{padding:3rem 2rem;text-align:center;border-bottom:1px solid var(--border)}}
|
|
|
|
| 85 |
.card p.desc{{font-size:0.8rem;color:var(--muted);margin-bottom:0.75rem;line-height:1.4}}
|
| 86 |
.card .meta{{font-size:0.75rem;color:var(--muted);display:flex;gap:1rem;flex-wrap:wrap}}
|
| 87 |
.badge{{font-size:0.7rem;padding:0.2rem 0.5rem;border-radius:4px;font-weight:500}}
|
| 88 |
+
.unicorn-badge{{color:var(--brand);font-weight:600}}
|
| 89 |
.women-badge{{background:rgba(236,72,153,0.15);color:#F472B6}}
|
| 90 |
.rural-badge{{background:rgba(34,197,94,0.15);color:#4ADE80}}
|
| 91 |
.dpiit-badge{{background:rgba(59,130,246,0.15);color:#60A5FA}}
|
|
|
|
| 132 |
{cards_html}
|
| 133 |
</div>
|
| 134 |
<div class="cta">
|
| 135 |
+
<a href="/">🗺️ Explore the full interactive map</a>
|
| 136 |
</div>
|
| 137 |
</div>
|
| 138 |
<footer>
|
| 139 |
+
Bharat Tech Atlas — Curated dataset. India has 223,000+ DPIIT-registered startups.
|
| 140 |
+
Source: DPIIT, Tracxn, Crunchbase. Data may not reflect real-time changes.
|
| 141 |
</footer>
|
| 142 |
</body>
|
| 143 |
</html>"""
|
|
|
|
| 225 |
@router.get("/startups", response_class=HTMLResponse)
|
| 226 |
async def directory_startups_root():
|
| 227 |
conn = get_db()
|
| 228 |
+
|
| 229 |
+
# Live stats from DB
|
| 230 |
+
total_startups = conn.execute("SELECT COUNT(*) FROM entities WHERE entity_type='startup' AND is_active=1").fetchone()[0]
|
| 231 |
+
unicorn_count = conn.execute("SELECT COUNT(*) FROM entities WHERE unicorn_status='unicorn' AND is_active=1").fetchone()[0]
|
| 232 |
+
women_count = conn.execute("SELECT COUNT(*) FROM entities WHERE is_women_led=1 AND is_active=1").fetchone()[0]
|
| 233 |
+
city_count = conn.execute("SELECT COUNT(DISTINCT city) FROM entities WHERE is_active=1").fetchone()[0]
|
| 234 |
+
|
| 235 |
rows = conn.execute("""
|
| 236 |
SELECT * FROM entities
|
| 237 |
WHERE entity_type = 'startup' AND is_active = 1
|
|
|
|
| 242 |
|
| 243 |
items = [row_to_dict(r) for r in rows]
|
| 244 |
|
| 245 |
+
stats_html = f"""
|
| 246 |
+
<div class="stat"><div class="num">{total_startups:,}</div><div class="label">Startups</div></div>
|
| 247 |
+
<div class="stat"><div class="num">{unicorn_count}</div><div class="label">Unicorns</div></div>
|
| 248 |
+
<div class="stat"><div class="num">{city_count}+</div><div class="label">Cities</div></div>
|
| 249 |
+
<div class="stat"><div class="num">{women_count}</div><div class="label">Women-led</div></div>
|
| 250 |
"""
|
| 251 |
|
| 252 |
filter_nav = "".join([
|
|
|
|
| 258 |
|
| 259 |
html = _build_directory_page(
|
| 260 |
title="Indian Startups Directory",
|
| 261 |
+
description=f"Curated directory of {total_startups:,}+ Indian startups — {unicorn_count} unicorns, funded companies, and emerging ventures across fintech, SaaS, AI, edtech, healthtech and more. India has 223,000+ DPIIT-registered startups.",
|
| 262 |
keywords="Indian startups, India startup directory, unicorn startups India, funded startups, DPIIT startups, tech startups Bangalore, Mumbai startups",
|
| 263 |
canonical="https://huggingface.co/spaces/Ram2005/StartupMap-India/directory/startups",
|
| 264 |
breadcrumb_last="Startups",
|
|
|
|
| 364 |
ORDER BY funding_inr DESC
|
| 365 |
LIMIT 150
|
| 366 |
""").fetchall()
|
| 367 |
+
|
| 368 |
+
# Live valuation from DB
|
| 369 |
+
val_row = conn.execute("SELECT SUM(valuation_usd) FROM entities WHERE unicorn_status='unicorn' AND is_active=1").fetchone()
|
| 370 |
+
total_valuation_b = round((val_row[0] or 0) / 1e9)
|
| 371 |
+
city_count = conn.execute("SELECT COUNT(DISTINCT city) FROM entities WHERE unicorn_status='unicorn' AND is_active=1").fetchone()[0]
|
| 372 |
+
sector_count = conn.execute("SELECT COUNT(DISTINCT dpiit_category) FROM entities WHERE unicorn_status='unicorn' AND is_active=1").fetchone()[0]
|
| 373 |
conn.close()
|
| 374 |
|
| 375 |
items = [row_to_dict(r) for r in rows]
|
| 376 |
|
| 377 |
stats_html = f"""
|
| 378 |
<div class="stat"><div class="num">{len(items)}</div><div class="label">Unicorns</div></div>
|
| 379 |
+
<div class="stat"><div class="num">{city_count}+</div><div class="label">Cities</div></div>
|
| 380 |
+
<div class="stat"><div class="num">{sector_count}+</div><div class="label">Sectors</div></div>
|
| 381 |
+
<div class="stat"><div class="num">${total_valuation_b}B+</div><div class="label">Combined Valuation</div></div>
|
| 382 |
"""
|
| 383 |
|
| 384 |
filter_nav = "".join([
|
|
|
|
| 390 |
|
| 391 |
html = _build_directory_page(
|
| 392 |
title="Indian Unicorns",
|
| 393 |
+
description=f"Directory of {len(items)} Indian unicorn startups — from Flipkart and PhonePe to newer entrants like Zepto and Neysa. Valuations, funding, investors, and locations.",
|
| 394 |
keywords="Indian unicorns, Indian unicorn startups, India unicorn list, Flipkart, PhonePe, Zepto, Zomato, Swiggy, unicorn valuation",
|
| 395 |
canonical="https://huggingface.co/spaces/Ram2005/StartupMap-India/directory/unicorns",
|
| 396 |
breadcrumb_last="Unicorns",
|
backend/main.py
CHANGED
|
@@ -1,24 +1,33 @@
|
|
| 1 |
"""
|
| 2 |
-
Bharat Tech Atlas v2.
|
| 3 |
- SPA frontend serving
|
| 4 |
- SEO directory routes at /directory/*
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
import os
|
| 7 |
from contextlib import asynccontextmanager
|
| 8 |
-
from fastapi import FastAPI
|
| 9 |
from fastapi.staticfiles import StaticFiles
|
| 10 |
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
|
| 11 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
-
from .database import init_db, DB_PATH
|
| 14 |
from .seed import seed_database
|
| 15 |
from .routes.entities import router as entities_router
|
| 16 |
from .directory import router as directory_router
|
| 17 |
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
@asynccontextmanager
|
| 20 |
async def lifespan(app: FastAPI):
|
| 21 |
-
print("🚀 Bharat Tech Atlas v2.
|
| 22 |
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
| 23 |
init_db()
|
| 24 |
print("✅ Database schema initialized")
|
|
@@ -30,11 +39,15 @@ async def lifespan(app: FastAPI):
|
|
| 30 |
|
| 31 |
app = FastAPI(
|
| 32 |
title="Bharat Tech Atlas",
|
| 33 |
-
description="
|
| 34 |
-
version="2.
|
| 35 |
lifespan=lifespan,
|
| 36 |
)
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
app.add_middleware(
|
| 39 |
CORSMiddleware,
|
| 40 |
allow_origins=["*"],
|
|
@@ -43,10 +56,41 @@ app.add_middleware(
|
|
| 43 |
allow_headers=["*"],
|
| 44 |
)
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
# ─── Health endpoint ──────────────────────────────────────────────────────────
|
| 47 |
@app.get("/api/health")
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
|
| 52 |
# ─── API routes ───────────────────────────────────────────────────────────────
|
|
@@ -79,15 +123,15 @@ async def serve_spa(full_path: str):
|
|
| 79 |
# Don't intercept API routes or directory routes
|
| 80 |
if full_path.startswith("api/") or full_path.startswith("directory/"):
|
| 81 |
return JSONResponse({"error": "Not found"}, status_code=404)
|
| 82 |
-
|
| 83 |
# Try to serve static file first
|
| 84 |
file_path = os.path.join(STATIC_DIR, full_path)
|
| 85 |
if os.path.isfile(file_path):
|
| 86 |
return FileResponse(file_path)
|
| 87 |
-
|
| 88 |
# Fallback to index.html for SPA routing
|
| 89 |
index_path = os.path.join(STATIC_DIR, "index.html")
|
| 90 |
if os.path.exists(index_path):
|
| 91 |
return FileResponse(index_path)
|
| 92 |
-
|
| 93 |
return JSONResponse({"message": "Bharat Tech Atlas API running."})
|
|
|
|
| 1 |
"""
|
| 2 |
+
Bharat Tech Atlas v2.3 — FastAPI Application Entry Point
|
| 3 |
- SPA frontend serving
|
| 4 |
- SEO directory routes at /directory/*
|
| 5 |
+
- Rate limiting on API endpoints
|
| 6 |
+
- Input sanitization middleware
|
| 7 |
"""
|
| 8 |
import os
|
| 9 |
from contextlib import asynccontextmanager
|
| 10 |
+
from fastapi import FastAPI, Request
|
| 11 |
from fastapi.staticfiles import StaticFiles
|
| 12 |
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
|
| 13 |
from fastapi.middleware.cors import CORSMiddleware
|
| 14 |
+
from slowapi import Limiter, _rate_limit_exceeded_handler
|
| 15 |
+
from slowapi.util import get_remote_address
|
| 16 |
+
from slowapi.errors import RateLimitExceeded
|
| 17 |
|
| 18 |
+
from .database import init_db, DB_PATH, get_db
|
| 19 |
from .seed import seed_database
|
| 20 |
from .routes.entities import router as entities_router
|
| 21 |
from .directory import router as directory_router
|
| 22 |
|
| 23 |
|
| 24 |
+
# ─── Rate limiter ────────────────────────────────────────────────────────────
|
| 25 |
+
limiter = Limiter(key_func=get_remote_address, default_limits=["60/minute"])
|
| 26 |
+
|
| 27 |
+
|
| 28 |
@asynccontextmanager
|
| 29 |
async def lifespan(app: FastAPI):
|
| 30 |
+
print("🚀 Bharat Tech Atlas v2.3 — Initializing...")
|
| 31 |
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
|
| 32 |
init_db()
|
| 33 |
print("✅ Database schema initialized")
|
|
|
|
| 39 |
|
| 40 |
app = FastAPI(
|
| 41 |
title="Bharat Tech Atlas",
|
| 42 |
+
description="Mapping platform for India's startup ecosystem",
|
| 43 |
+
version="2.3.0",
|
| 44 |
lifespan=lifespan,
|
| 45 |
)
|
| 46 |
|
| 47 |
+
# Attach rate limiter
|
| 48 |
+
app.state.limiter = limiter
|
| 49 |
+
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
| 50 |
+
|
| 51 |
app.add_middleware(
|
| 52 |
CORSMiddleware,
|
| 53 |
allow_origins=["*"],
|
|
|
|
| 56 |
allow_headers=["*"],
|
| 57 |
)
|
| 58 |
|
| 59 |
+
|
| 60 |
+
# ─── Input sanitization middleware ───────────────────────────────────────────
|
| 61 |
+
@app.middleware("http")
|
| 62 |
+
async def sanitize_input(request: Request, call_next):
|
| 63 |
+
# Reject query strings longer than 2000 chars
|
| 64 |
+
if len(str(request.url.query)) > 2000:
|
| 65 |
+
return JSONResponse(
|
| 66 |
+
{"error": "Query string too long (max 2000 chars)"},
|
| 67 |
+
status_code=400,
|
| 68 |
+
)
|
| 69 |
+
# Reject any single query param longer than 500 chars
|
| 70 |
+
for key, value in request.query_params.items():
|
| 71 |
+
if len(value) > 500:
|
| 72 |
+
return JSONResponse(
|
| 73 |
+
{"error": f"Parameter '{key}' too long (max 500 chars)"},
|
| 74 |
+
status_code=400,
|
| 75 |
+
)
|
| 76 |
+
return await call_next(request)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
# ─── Health endpoint ──────────────────────────────────────────────────────────
|
| 80 |
@app.get("/api/health")
|
| 81 |
+
@limiter.limit("60/minute")
|
| 82 |
+
async def health(request: Request):
|
| 83 |
+
conn = get_db()
|
| 84 |
+
total = conn.execute("SELECT COUNT(*) FROM entities WHERE is_active=1").fetchone()[0]
|
| 85 |
+
unicorns = conn.execute("SELECT COUNT(*) FROM entities WHERE unicorn_status='unicorn' AND is_active=1").fetchone()[0]
|
| 86 |
+
conn.close()
|
| 87 |
+
return {
|
| 88 |
+
"status": "ok",
|
| 89 |
+
"service": "Bharat Tech Atlas",
|
| 90 |
+
"version": "2.3.0",
|
| 91 |
+
"entities": total,
|
| 92 |
+
"unicorns": unicorns,
|
| 93 |
+
}
|
| 94 |
|
| 95 |
|
| 96 |
# ─── API routes ───────────────────────────────────────────────────────────────
|
|
|
|
| 123 |
# Don't intercept API routes or directory routes
|
| 124 |
if full_path.startswith("api/") or full_path.startswith("directory/"):
|
| 125 |
return JSONResponse({"error": "Not found"}, status_code=404)
|
| 126 |
+
|
| 127 |
# Try to serve static file first
|
| 128 |
file_path = os.path.join(STATIC_DIR, full_path)
|
| 129 |
if os.path.isfile(file_path):
|
| 130 |
return FileResponse(file_path)
|
| 131 |
+
|
| 132 |
# Fallback to index.html for SPA routing
|
| 133 |
index_path = os.path.join(STATIC_DIR, "index.html")
|
| 134 |
if os.path.exists(index_path):
|
| 135 |
return FileResponse(index_path)
|
| 136 |
+
|
| 137 |
return JSONResponse({"message": "Bharat Tech Atlas API running."})
|
backend/routes/entities.py
CHANGED
|
@@ -17,6 +17,11 @@ from ..database import get_db, haversine_distance
|
|
| 17 |
router = APIRouter()
|
| 18 |
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
def row_to_dict(row) -> dict:
|
| 21 |
d = dict(row)
|
| 22 |
for field in ["sectors", "data_sources", "investors", "funding_rounds"]:
|
|
@@ -111,8 +116,9 @@ def _apply_filters(query, params, filters):
|
|
| 111 |
params.append(int(filters["founded_before"]))
|
| 112 |
|
| 113 |
if filters.get("search"):
|
| 114 |
-
|
| 115 |
-
|
|
|
|
| 116 |
|
| 117 |
if filters.get("state"):
|
| 118 |
query += " AND e.state = ?"
|
|
@@ -553,7 +559,7 @@ async def get_entity_detail(slug: str):
|
|
| 553 |
|
| 554 |
@router.get("/search")
|
| 555 |
async def search_entities(
|
| 556 |
-
q: str = Query(..., min_length=1),
|
| 557 |
entity_type: Optional[str] = Query(None),
|
| 558 |
sector: Optional[str] = Query(None),
|
| 559 |
state: Optional[str] = Query(None),
|
|
@@ -562,12 +568,13 @@ async def search_entities(
|
|
| 562 |
offset: int = Query(0, ge=0),
|
| 563 |
):
|
| 564 |
conn = get_db()
|
|
|
|
| 565 |
query = """
|
| 566 |
SELECT * FROM entities e
|
| 567 |
WHERE e.is_active = 1
|
| 568 |
-
AND (e.name LIKE ? OR e.city LIKE ? OR e.description LIKE ? OR e.college_name LIKE ?)
|
| 569 |
"""
|
| 570 |
-
search_term = f"%{
|
| 571 |
params = [search_term, search_term, search_term, search_term]
|
| 572 |
|
| 573 |
filters = {"entity_type": entity_type, "sector": sector, "state": state, "city": city}
|
|
@@ -598,8 +605,9 @@ async def get_facets(
|
|
| 598 |
base_where = "WHERE is_active = 1"
|
| 599 |
params = []
|
| 600 |
if search:
|
| 601 |
-
|
| 602 |
-
|
|
|
|
| 603 |
|
| 604 |
# Entity type counts
|
| 605 |
tq = f"SELECT entity_type, COUNT(*) as count FROM entities {base_where}"
|
|
|
|
| 17 |
router = APIRouter()
|
| 18 |
|
| 19 |
|
| 20 |
+
def _sanitize_like(value: str) -> str:
|
| 21 |
+
"""Escape SQL LIKE wildcards in user input."""
|
| 22 |
+
return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
def row_to_dict(row) -> dict:
|
| 26 |
d = dict(row)
|
| 27 |
for field in ["sectors", "data_sources", "investors", "funding_rounds"]:
|
|
|
|
| 116 |
params.append(int(filters["founded_before"]))
|
| 117 |
|
| 118 |
if filters.get("search"):
|
| 119 |
+
safe = _sanitize_like(filters['search'])
|
| 120 |
+
query += " AND e.name LIKE ? ESCAPE '\\'"
|
| 121 |
+
params.append(f"%{safe}%")
|
| 122 |
|
| 123 |
if filters.get("state"):
|
| 124 |
query += " AND e.state = ?"
|
|
|
|
| 559 |
|
| 560 |
@router.get("/search")
|
| 561 |
async def search_entities(
|
| 562 |
+
q: str = Query(..., min_length=1, max_length=200),
|
| 563 |
entity_type: Optional[str] = Query(None),
|
| 564 |
sector: Optional[str] = Query(None),
|
| 565 |
state: Optional[str] = Query(None),
|
|
|
|
| 568 |
offset: int = Query(0, ge=0),
|
| 569 |
):
|
| 570 |
conn = get_db()
|
| 571 |
+
safe_q = _sanitize_like(q)
|
| 572 |
query = """
|
| 573 |
SELECT * FROM entities e
|
| 574 |
WHERE e.is_active = 1
|
| 575 |
+
AND (e.name LIKE ? ESCAPE '\\' OR e.city LIKE ? ESCAPE '\\' OR e.description LIKE ? ESCAPE '\\' OR e.college_name LIKE ? ESCAPE '\\')
|
| 576 |
"""
|
| 577 |
+
search_term = f"%{safe_q}%"
|
| 578 |
params = [search_term, search_term, search_term, search_term]
|
| 579 |
|
| 580 |
filters = {"entity_type": entity_type, "sector": sector, "state": state, "city": city}
|
|
|
|
| 605 |
base_where = "WHERE is_active = 1"
|
| 606 |
params = []
|
| 607 |
if search:
|
| 608 |
+
safe_search = _sanitize_like(search)
|
| 609 |
+
base_where += " AND name LIKE ? ESCAPE '\\'"
|
| 610 |
+
params.append(f"%{safe_search}%")
|
| 611 |
|
| 612 |
# Entity type counts
|
| 613 |
tq = f"SELECT entity_type, COUNT(*) as count FROM entities {base_where}"
|
backend/seed.py
CHANGED
|
@@ -159,7 +159,7 @@ UNICORNS = [
|
|
| 159 |
{"name": "Dunzo", "city": "Bangalore", "sectors": ["logistics", "d2c"], "founded": 2015, "employees": 600, "funding": 21000000000, "desc": "Hyperlocal delivery platform for essentials.", "investors": ["Reliance Industries", "Google", "Lightbox Ventures"], "funding_stage": "series_e", "linkedin_url": "https://linkedin.com/company/dunzo-digital", "website": "https://dunzo.com", "linkedin_team": 700, "linkedin_industry": "Delivery", "dpiit_cat": "logistics", "valuation_usd": 800000000},
|
| 160 |
{"name": "Porter", "city": "Mumbai", "sectors": ["logistics"], "founded": 2014, "employees": 1000, "funding": 9500000000, "desc": "Intra-city logistics and mini-truck aggregator.", "investors": ["Tiger Global", "Kae Capital"], "funding_stage": "series_e", "linkedin_url": "https://linkedin.com/company/porter-delivery", "website": "https://porter.in", "linkedin_team": 1100, "linkedin_industry": "Logistics", "dpiit_cat": "logistics", "valuation_usd": 1000000000},
|
| 161 |
{"name": "Tracxn", "city": "Bangalore", "sectors": ["fintech", "saas", "saas_ai"], "founded": 2013, "employees": 500, "funding": 5000000000, "desc": "Market intelligence platform for private companies. Public.", "investors": ["Accel", "SAIF Partners"], "funding_stage": "public", "linkedin_url": "https://linkedin.com/company/tracxn", "website": "https://tracxn.com", "linkedin_team": 550, "linkedin_industry": "FinTech", "dpiit_cat": "fintech", "valuation_usd": 600000000},
|
| 162 |
-
{"name": "Perfios", "city": "Bangalore", "sectors": ["fintech", "saas", "saas_ai"], "founded": 2008, "employees":
|
| 163 |
{"name": "Khatabook", "city": "Bangalore", "sectors": ["fintech", "saas"], "founded": 2019, "employees": 300, "funding": 10000000000, "desc": "Digital ledger app for SME bookkeeping.", "investors": ["Sequoia Capital", "B Capital", "Tencent"], "funding_stage": "series_c", "linkedin_url": "https://linkedin.com/company/khatabook", "website": "https://khatabook.com", "linkedin_team": 350, "linkedin_industry": "FinTech", "dpiit_cat": "fintech", "valuation_usd": 600000000},
|
| 164 |
{"name": "Rivigo", "city": "Gurgaon", "sectors": ["logistics", "deeptech"], "founded": 2014, "employees": 2000, "funding": 17000000000, "desc": "Technology-enabled logistics company using relay trucking.", "investors": ["Warburg Pincus", "SAIF Partners"], "funding_stage": "series_e", "linkedin_url": "https://linkedin.com/company/rivigo", "website": "https://rivigo.com", "linkedin_team": 2200, "linkedin_industry": "Logistics", "dpiit_cat": "logistics", "valuation_usd": 1000000000},
|
| 165 |
{"name": "Zepto", "city": "Mumbai", "sectors": ["ecommerce", "foodtech", "logistics"], "founded": 2021, "employees": 3000, "funding": 85000000000, "desc": "10-minute grocery delivery startup founded by teenage entrepreneurs.", "investors": ["StepStone Group", "Glade Brook Capital", "Nexus Ventures", "Y Combinator"], "funding_stage": "series_e", "linkedin_url": "https://linkedin.com/company/zeptonow", "website": "https://zepto.co", "linkedin_team": 3200, "linkedin_industry": "E-Commerce", "dpiit_cat": "foodtech", "valuation_usd": 5000000000, "campus": True},
|
|
@@ -201,6 +201,12 @@ UNICORNS = [
|
|
| 201 |
{"name": "INDwealth", "city": "Mumbai", "sectors": ["fintech", "wealthtech"], "founded": 2019, "employees": 200, "funding": 3000000000, "desc": "Wealth management and investment advisory platform.", "investors": ["Tiger Global", "Steadview Capital"], "funding_stage": "series_c", "linkedin_url": "https://linkedin.com/company/indwealth", "website": "https://indwealth.in", "linkedin_team": 220, "linkedin_industry": "FinTech", "dpiit_cat": "fintech", "valuation_usd": 600000000},
|
| 202 |
{"name": "Simplilearn", "city": "Bangalore", "sectors": ["edtech"], "founded": 2010, "employees": 2000, "funding": 8000000000, "desc": "Online bootcamp and certification training platform.", "investors": ["Blackstone"], "funding_stage": "acquired", "linkedin_url": "https://linkedin.com/company/simplilearn", "website": "https://simplilearn.com", "linkedin_team": 2200, "linkedin_industry": "E-Learning", "dpiit_cat": "edtech", "valuation_usd": 1200000000},
|
| 203 |
{"name": "Jiomeet (Reliance Jio)", "city": "Mumbai", "sectors": ["saas", "saas_ai"], "founded": 2020, "employees": 300, "funding": 0, "desc": "Enterprise video conferencing by Reliance Jio.", "investors": ["Reliance Industries"], "funding_stage": None, "linkedin_url": "https://linkedin.com/company/jiomeet", "website": "https://jiomeet.com", "linkedin_team": 350, "linkedin_industry": "Software", "dpiit_cat": "saas", "dpiit": False, "valuation_usd": None},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
]
|
| 205 |
|
| 206 |
# ─── Well-funded non-unicorn startups ─────────────────────────────────────────
|
|
@@ -210,7 +216,6 @@ FUNDED_STARTUPS = [
|
|
| 210 |
{"name": "CropIn", "city": "Bangalore", "sectors": ["agritech", "ai_ml"], "founded": 2010, "employees": 400, "funding": 3800000000, "dpiit_cat": "agritech", "desc": "AI-powered agri-intelligence platform.", "investors": ["ABC World Asia", "Chiratae Ventures"], "funding_stage": "series_c", "rural_impact": True},
|
| 211 |
{"name": "DeHaat", "city": "Patna", "sectors": ["agritech"], "founded": 2012, "employees": 1500, "funding": 16000000000, "dpiit_cat": "agritech", "desc": "Full-stack agricultural services platform.", "investors": ["Sofina", "Prosus", "RTP Global"], "funding_stage": "series_d", "rural_impact": True, "nsa": True, "nsa_cat": "Agriculture"},
|
| 212 |
{"name": "Ninjacart", "city": "Bangalore", "sectors": ["agritech", "logistics"], "founded": 2015, "employees": 2000, "funding": 30000000000, "dpiit_cat": "agritech", "desc": "B2B fresh produce supply chain platform.", "investors": ["Tiger Global", "Flipkart", "Accel"], "funding_stage": "series_c", "rural_impact": True},
|
| 213 |
-
{"name": "Ather Energy", "city": "Bangalore", "sectors": ["mobility", "cleantech", "ev"], "founded": 2013, "employees": 1500, "funding": 21000000000, "dpiit_cat": "ev", "desc": "Electric scooter manufacturer with charging infra.", "investors": ["Hero MotoCorp", "Tiger Global"], "funding_stage": "series_e", "campus": True},
|
| 214 |
{"name": "Agnikul Cosmos", "city": "Chennai", "sectors": ["spacetech", "deeptech"], "founded": 2017, "employees": 200, "funding": 7500000000, "dpiit_cat": "spacetech", "desc": "Building 3D-printed rocket engines.", "investors": ["Anand Mahindra", "pi Ventures"], "funding_stage": "series_b", "campus": True},
|
| 215 |
{"name": "Skyroot Aerospace", "city": "Hyderabad", "sectors": ["spacetech", "deeptech"], "founded": 2018, "employees": 250, "funding": 5200000000, "dpiit_cat": "spacetech", "desc": "India's first private space launch vehicle (Vikram series).", "investors": ["GIC", "Nexus Venture Partners"], "funding_stage": "series_b"},
|
| 216 |
{"name": "Pixxel", "city": "Bangalore", "sectors": ["spacetech", "ai_ml"], "founded": 2019, "employees": 120, "funding": 4800000000, "dpiit_cat": "spacetech", "desc": "Hyperspectral earth-imaging satellite constellation.", "investors": ["Google", "Radical Ventures"], "funding_stage": "series_b", "campus": True},
|
|
|
|
| 159 |
{"name": "Dunzo", "city": "Bangalore", "sectors": ["logistics", "d2c"], "founded": 2015, "employees": 600, "funding": 21000000000, "desc": "Hyperlocal delivery platform for essentials.", "investors": ["Reliance Industries", "Google", "Lightbox Ventures"], "funding_stage": "series_e", "linkedin_url": "https://linkedin.com/company/dunzo-digital", "website": "https://dunzo.com", "linkedin_team": 700, "linkedin_industry": "Delivery", "dpiit_cat": "logistics", "valuation_usd": 800000000},
|
| 160 |
{"name": "Porter", "city": "Mumbai", "sectors": ["logistics"], "founded": 2014, "employees": 1000, "funding": 9500000000, "desc": "Intra-city logistics and mini-truck aggregator.", "investors": ["Tiger Global", "Kae Capital"], "funding_stage": "series_e", "linkedin_url": "https://linkedin.com/company/porter-delivery", "website": "https://porter.in", "linkedin_team": 1100, "linkedin_industry": "Logistics", "dpiit_cat": "logistics", "valuation_usd": 1000000000},
|
| 161 |
{"name": "Tracxn", "city": "Bangalore", "sectors": ["fintech", "saas", "saas_ai"], "founded": 2013, "employees": 500, "funding": 5000000000, "desc": "Market intelligence platform for private companies. Public.", "investors": ["Accel", "SAIF Partners"], "funding_stage": "public", "linkedin_url": "https://linkedin.com/company/tracxn", "website": "https://tracxn.com", "linkedin_team": 550, "linkedin_industry": "FinTech", "dpiit_cat": "fintech", "valuation_usd": 600000000},
|
| 162 |
+
{"name": "Perfios", "city": "Bangalore", "sectors": ["fintech", "saas", "saas_ai"], "founded": 2008, "employees": 1500, "funding": 9000000000, "desc": "Financial data analytics and decisioning platform for lenders.", "investors": ["Warburg Pincus", "Bessemer Venture Partners"], "funding_stage": "series_d", "linkedin_url": "https://linkedin.com/company/perfios", "website": "https://perfios.com", "linkedin_team": 1600, "linkedin_industry": "FinTech", "dpiit_cat": "fintech", "valuation_usd": 1000000000},
|
| 163 |
{"name": "Khatabook", "city": "Bangalore", "sectors": ["fintech", "saas"], "founded": 2019, "employees": 300, "funding": 10000000000, "desc": "Digital ledger app for SME bookkeeping.", "investors": ["Sequoia Capital", "B Capital", "Tencent"], "funding_stage": "series_c", "linkedin_url": "https://linkedin.com/company/khatabook", "website": "https://khatabook.com", "linkedin_team": 350, "linkedin_industry": "FinTech", "dpiit_cat": "fintech", "valuation_usd": 600000000},
|
| 164 |
{"name": "Rivigo", "city": "Gurgaon", "sectors": ["logistics", "deeptech"], "founded": 2014, "employees": 2000, "funding": 17000000000, "desc": "Technology-enabled logistics company using relay trucking.", "investors": ["Warburg Pincus", "SAIF Partners"], "funding_stage": "series_e", "linkedin_url": "https://linkedin.com/company/rivigo", "website": "https://rivigo.com", "linkedin_team": 2200, "linkedin_industry": "Logistics", "dpiit_cat": "logistics", "valuation_usd": 1000000000},
|
| 165 |
{"name": "Zepto", "city": "Mumbai", "sectors": ["ecommerce", "foodtech", "logistics"], "founded": 2021, "employees": 3000, "funding": 85000000000, "desc": "10-minute grocery delivery startup founded by teenage entrepreneurs.", "investors": ["StepStone Group", "Glade Brook Capital", "Nexus Ventures", "Y Combinator"], "funding_stage": "series_e", "linkedin_url": "https://linkedin.com/company/zeptonow", "website": "https://zepto.co", "linkedin_team": 3200, "linkedin_industry": "E-Commerce", "dpiit_cat": "foodtech", "valuation_usd": 5000000000, "campus": True},
|
|
|
|
| 201 |
{"name": "INDwealth", "city": "Mumbai", "sectors": ["fintech", "wealthtech"], "founded": 2019, "employees": 200, "funding": 3000000000, "desc": "Wealth management and investment advisory platform.", "investors": ["Tiger Global", "Steadview Capital"], "funding_stage": "series_c", "linkedin_url": "https://linkedin.com/company/indwealth", "website": "https://indwealth.in", "linkedin_team": 220, "linkedin_industry": "FinTech", "dpiit_cat": "fintech", "valuation_usd": 600000000},
|
| 202 |
{"name": "Simplilearn", "city": "Bangalore", "sectors": ["edtech"], "founded": 2010, "employees": 2000, "funding": 8000000000, "desc": "Online bootcamp and certification training platform.", "investors": ["Blackstone"], "funding_stage": "acquired", "linkedin_url": "https://linkedin.com/company/simplilearn", "website": "https://simplilearn.com", "linkedin_team": 2200, "linkedin_industry": "E-Learning", "dpiit_cat": "edtech", "valuation_usd": 1200000000},
|
| 203 |
{"name": "Jiomeet (Reliance Jio)", "city": "Mumbai", "sectors": ["saas", "saas_ai"], "founded": 2020, "employees": 300, "funding": 0, "desc": "Enterprise video conferencing by Reliance Jio.", "investors": ["Reliance Industries"], "funding_stage": None, "linkedin_url": "https://linkedin.com/company/jiomeet", "website": "https://jiomeet.com", "linkedin_team": 350, "linkedin_industry": "Software", "dpiit_cat": "saas", "dpiit": False, "valuation_usd": None},
|
| 204 |
+
{"name": "Neysa", "city": "Bangalore", "sectors": ["ai_ml", "deeptech"], "founded": 2023, "employees": 80, "funding": 3000000000, "desc": "AI infrastructure company providing GPU cloud computing.", "investors": ["Matrix Partners", "Lightspeed"], "funding_stage": "series_a", "linkedin_url": "https://linkedin.com/company/neysaai", "website": "https://neysa.ai", "linkedin_team": 100, "linkedin_industry": "AI", "dpiit_cat": "ai_ml", "valuation_usd": 1000000000},
|
| 205 |
+
{"name": "Ather Energy", "city": "Bangalore", "sectors": ["ev", "mobility", "cleantech"], "founded": 2013, "employees": 3000, "funding": 21000000000, "desc": "Electric scooter manufacturer with charging network.", "investors": ["Hero MotoCorp", "Sachin Bansal", "Tiger Global"], "funding_stage": "series_e", "linkedin_url": "https://linkedin.com/company/atherenergy", "website": "https://atherenergy.com", "linkedin_team": 3200, "linkedin_industry": "EV", "dpiit_cat": "ev", "valuation_usd": 1300000000},
|
| 206 |
+
{"name": "Captain Fresh", "city": "Bangalore", "sectors": ["foodtech", "logistics"], "founded": 2019, "employees": 500, "funding": 3500000000, "desc": "B2B seafood supply chain platform.", "investors": ["Tiger Global", "Prosus", "Accel"], "funding_stage": "series_c", "linkedin_url": "https://linkedin.com/company/captainfresh", "website": "https://captainfresh.in", "linkedin_team": 550, "linkedin_industry": "FoodTech", "dpiit_cat": "foodtech", "valuation_usd": 1000000000},
|
| 207 |
+
{"name": "Kuku FM", "city": "Bangalore", "sectors": ["mediatech", "edtech"], "founded": 2018, "employees": 300, "funding": 4000000000, "desc": "Audio content platform for stories, courses, and audiobooks.", "investors": ["Google", "Vertex Ventures", "3one4 Capital"], "funding_stage": "series_c", "linkedin_url": "https://linkedin.com/company/kukufm", "website": "https://kukufm.com", "linkedin_team": 350, "linkedin_industry": "Media", "dpiit_cat": "mediatech", "valuation_usd": 1000000000},
|
| 208 |
+
{"name": "Euler Motors", "city": "Delhi", "sectors": ["ev", "mobility", "cleantech"], "founded": 2018, "employees": 600, "funding": 3000000000, "desc": "Commercial electric vehicle manufacturer for last-mile delivery.", "investors": ["GIC", "Blume Ventures", "Athera Venture Partners"], "funding_stage": "series_c", "linkedin_url": "https://linkedin.com/company/eulermotors", "website": "https://eulermotors.com", "linkedin_team": 650, "linkedin_industry": "EV", "dpiit_cat": "ev", "valuation_usd": 1000000000},
|
| 209 |
+
{"name": "Kissht", "city": "Mumbai", "sectors": ["fintech"], "founded": 2015, "employees": 500, "funding": 4000000000, "desc": "Digital lending platform for instant personal loans.", "investors": ["Vertex Ventures", "Endiya Partners", "BAce Capital"], "funding_stage": "series_d", "linkedin_url": "https://linkedin.com/company/kissht", "website": "https://kissht.com", "linkedin_team": 550, "linkedin_industry": "FinTech", "dpiit_cat": "fintech", "valuation_usd": 1000000000},
|
| 210 |
]
|
| 211 |
|
| 212 |
# ─── Well-funded non-unicorn startups ─────────────────────────────────────────
|
|
|
|
| 216 |
{"name": "CropIn", "city": "Bangalore", "sectors": ["agritech", "ai_ml"], "founded": 2010, "employees": 400, "funding": 3800000000, "dpiit_cat": "agritech", "desc": "AI-powered agri-intelligence platform.", "investors": ["ABC World Asia", "Chiratae Ventures"], "funding_stage": "series_c", "rural_impact": True},
|
| 217 |
{"name": "DeHaat", "city": "Patna", "sectors": ["agritech"], "founded": 2012, "employees": 1500, "funding": 16000000000, "dpiit_cat": "agritech", "desc": "Full-stack agricultural services platform.", "investors": ["Sofina", "Prosus", "RTP Global"], "funding_stage": "series_d", "rural_impact": True, "nsa": True, "nsa_cat": "Agriculture"},
|
| 218 |
{"name": "Ninjacart", "city": "Bangalore", "sectors": ["agritech", "logistics"], "founded": 2015, "employees": 2000, "funding": 30000000000, "dpiit_cat": "agritech", "desc": "B2B fresh produce supply chain platform.", "investors": ["Tiger Global", "Flipkart", "Accel"], "funding_stage": "series_c", "rural_impact": True},
|
|
|
|
| 219 |
{"name": "Agnikul Cosmos", "city": "Chennai", "sectors": ["spacetech", "deeptech"], "founded": 2017, "employees": 200, "funding": 7500000000, "dpiit_cat": "spacetech", "desc": "Building 3D-printed rocket engines.", "investors": ["Anand Mahindra", "pi Ventures"], "funding_stage": "series_b", "campus": True},
|
| 220 |
{"name": "Skyroot Aerospace", "city": "Hyderabad", "sectors": ["spacetech", "deeptech"], "founded": 2018, "employees": 250, "funding": 5200000000, "dpiit_cat": "spacetech", "desc": "India's first private space launch vehicle (Vikram series).", "investors": ["GIC", "Nexus Venture Partners"], "funding_stage": "series_b"},
|
| 221 |
{"name": "Pixxel", "city": "Bangalore", "sectors": ["spacetech", "ai_ml"], "founded": 2019, "employees": 120, "funding": 4800000000, "dpiit_cat": "spacetech", "desc": "Hyperspectral earth-imaging satellite constellation.", "investors": ["Google", "Radical Ventures"], "funding_stage": "series_b", "campus": True},
|
frontend/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
-
<meta name="description" content="
|
| 7 |
<meta name="keywords" content="India startups, startup map, Indian unicorns, DPIIT startups, tech ecosystem India" />
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<meta name="description" content="Curated mapping platform for India's startup ecosystem — startups, unicorns, SMEs, E-Cells, incubators. India has 223,000+ DPIIT-registered startups." />
|
| 7 |
<meta name="keywords" content="India startups, startup map, Indian unicorns, DPIIT startups, tech ecosystem India" />
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
frontend/src/App.jsx
CHANGED
|
@@ -249,7 +249,7 @@ export default function App() {
|
|
| 249 |
) : (
|
| 250 |
<>
|
| 251 |
<span className="text-xl">🗺️</span>
|
| 252 |
-
<span className="font-bold
|
| 253 |
</>
|
| 254 |
)}
|
| 255 |
</button>
|
|
@@ -295,6 +295,15 @@ export default function App() {
|
|
| 295 |
</div>
|
| 296 |
</div>
|
| 297 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
{/* Stats Bar */}
|
| 299 |
<StatsBar overview={overview} viewportSummary={viewportSummary} featureCount={displayCount} loading={loading} isMobile={isMobile} />
|
| 300 |
|
|
|
|
| 249 |
) : (
|
| 250 |
<>
|
| 251 |
<span className="text-xl">🗺️</span>
|
| 252 |
+
<span className="font-bold text-brand-500 hidden sm:inline text-sm">Bharat Tech Atlas</span>
|
| 253 |
</>
|
| 254 |
)}
|
| 255 |
</button>
|
|
|
|
| 295 |
</div>
|
| 296 |
</div>
|
| 297 |
|
| 298 |
+
{/* Data Disclaimer */}
|
| 299 |
+
{overview && (
|
| 300 |
+
<div className="absolute bottom-[90px] sm:bottom-[100px] left-1/2 -translate-x-1/2 z-10 pointer-events-none">
|
| 301 |
+
<p className="text-[10px] text-atlas-muted/70 text-center whitespace-nowrap">
|
| 302 |
+
Curated dataset of {overview.total_entities?.toLocaleString()} entities · India has 223,000+ DPIIT-registered startups · Source: DPIIT, Tracxn, Crunchbase
|
| 303 |
+
</p>
|
| 304 |
+
</div>
|
| 305 |
+
)}
|
| 306 |
+
|
| 307 |
{/* Stats Bar */}
|
| 308 |
<StatsBar overview={overview} viewportSummary={viewportSummary} featureCount={displayCount} loading={loading} isMobile={isMobile} />
|
| 309 |
|
frontend/src/components/AnalyticsPanel.jsx
CHANGED
|
@@ -34,7 +34,7 @@ export default function AnalyticsPanel({ onClose }) {
|
|
| 34 |
}
|
| 35 |
|
| 36 |
return (
|
| 37 |
-
<div className="fixed inset-0 z-40 flex items-center justify-center bg-black/50
|
| 38 |
<div className="glass rounded-2xl shadow-2xl w-full max-w-3xl max-h-[85vh] flex flex-col overflow-hidden">
|
| 39 |
<div className="flex items-center justify-between px-4 sm:px-6 py-3 sm:py-4 border-b border-atlas-border flex-shrink-0">
|
| 40 |
<div>
|
|
|
|
| 34 |
}
|
| 35 |
|
| 36 |
return (
|
| 37 |
+
<div className="fixed inset-0 z-40 flex items-center justify-center bg-black/50 p-2 sm:p-4">
|
| 38 |
<div className="glass rounded-2xl shadow-2xl w-full max-w-3xl max-h-[85vh] flex flex-col overflow-hidden">
|
| 39 |
<div className="flex items-center justify-between px-4 sm:px-6 py-3 sm:py-4 border-b border-atlas-border flex-shrink-0">
|
| 40 |
<div>
|
frontend/src/components/EntityDetail.jsx
CHANGED
|
@@ -37,7 +37,7 @@ export default function EntityDetail({ entity, onClose, onEntityClick, isMobile
|
|
| 37 |
}>
|
| 38 |
<div className={
|
| 39 |
isMobile
|
| 40 |
-
? "bg-atlas-bg/95
|
| 41 |
: "glass rounded-2xl shadow-2xl flex flex-col overflow-hidden pointer-events-auto max-h-full"
|
| 42 |
}>
|
| 43 |
{/* Header */}
|
|
|
|
| 37 |
}>
|
| 38 |
<div className={
|
| 39 |
isMobile
|
| 40 |
+
? "bg-atlas-bg/95 border-t border-atlas-border rounded-t-2xl shadow-2xl flex flex-col overflow-hidden max-h-[80vh]"
|
| 41 |
: "glass rounded-2xl shadow-2xl flex flex-col overflow-hidden pointer-events-auto max-h-full"
|
| 42 |
}>
|
| 43 |
{/* Header */}
|
frontend/src/components/Sidebar.jsx
CHANGED
|
@@ -149,7 +149,7 @@ export default function Sidebar({ filters, facets, onFilterChange, onReset, onCl
|
|
| 149 |
}>
|
| 150 |
<div className={
|
| 151 |
isMobile
|
| 152 |
-
? "bg-atlas-bg/95
|
| 153 |
: "glass rounded-2xl shadow-2xl flex flex-col overflow-hidden pointer-events-auto max-h-full"
|
| 154 |
}>
|
| 155 |
{/* Mobile drag handle */}
|
|
|
|
| 149 |
}>
|
| 150 |
<div className={
|
| 151 |
isMobile
|
| 152 |
+
? "bg-atlas-bg/95 border-t border-atlas-border rounded-t-2xl shadow-2xl flex flex-col overflow-hidden max-h-[75vh]"
|
| 153 |
: "glass rounded-2xl shadow-2xl flex flex-col overflow-hidden pointer-events-auto max-h-full"
|
| 154 |
}>
|
| 155 |
{/* Mobile drag handle */}
|
frontend/src/components/StatsBar.jsx
CHANGED
|
@@ -10,55 +10,36 @@ export default function StatsBar({ overview, viewportSummary, featureCount, load
|
|
| 10 |
label: 'In View',
|
| 11 |
value: viewportSummary?.count || featureCount,
|
| 12 |
icon: '📍',
|
| 13 |
-
gradient: 'from-blue-500/20 to-indigo-500/20',
|
| 14 |
-
border: 'border-blue-500/20',
|
| 15 |
-
glow: '#3B82F6',
|
| 16 |
desc: 'Entities visible on map',
|
| 17 |
},
|
| 18 |
{
|
| 19 |
label: 'Total',
|
| 20 |
value: overview.total_entities,
|
| 21 |
icon: '🏢',
|
| 22 |
-
gradient: 'from-indigo-500/20 to-purple-500/20',
|
| 23 |
-
border: 'border-indigo-500/20',
|
| 24 |
-
glow: '#6366F1',
|
| 25 |
desc: 'All ecosystem entities',
|
| 26 |
},
|
| 27 |
{
|
| 28 |
label: 'Startups',
|
| 29 |
value: overview.by_type?.startup || 0,
|
| 30 |
icon: '🚀',
|
| 31 |
-
gradient: 'from-emerald-500/20 to-teal-500/20',
|
| 32 |
-
border: 'border-emerald-500/20',
|
| 33 |
-
glow: '#10B981',
|
| 34 |
desc: 'Active startups',
|
| 35 |
},
|
| 36 |
{
|
| 37 |
label: 'Unicorns',
|
| 38 |
value: overview.unicorn_count || 0,
|
| 39 |
icon: '🦄',
|
| 40 |
-
gradient: 'from-orange-500/20 to-amber-500/20',
|
| 41 |
-
border: 'border-orange-500/20',
|
| 42 |
-
glow: '#F97316',
|
| 43 |
desc: 'Valued >$1B',
|
| 44 |
-
highlight: true,
|
| 45 |
},
|
| 46 |
{
|
| 47 |
label: 'Women-led',
|
| 48 |
value: overview.women_led_count || 0,
|
| 49 |
icon: '👩',
|
| 50 |
-
gradient: 'from-pink-500/20 to-rose-500/20',
|
| 51 |
-
border: 'border-pink-500/20',
|
| 52 |
-
glow: '#EC4899',
|
| 53 |
desc: 'Women founders',
|
| 54 |
},
|
| 55 |
{
|
| 56 |
label: 'Funding',
|
| 57 |
value: overview.total_funding_display,
|
| 58 |
icon: '💰',
|
| 59 |
-
gradient: 'from-amber-500/20 to-yellow-500/20',
|
| 60 |
-
border: 'border-amber-500/20',
|
| 61 |
-
glow: '#F59E0B',
|
| 62 |
desc: 'Total raised',
|
| 63 |
isText: true,
|
| 64 |
},
|
|
@@ -81,35 +62,11 @@ export default function StatsBar({ overview, viewportSummary, featureCount, load
|
|
| 81 |
{cards.map((card) => (
|
| 82 |
<div
|
| 83 |
key={card.label}
|
| 84 |
-
className=
|
| 85 |
-
relative group rounded-2xl border ${card.border}
|
| 86 |
-
bg-gradient-to-br ${card.gradient}
|
| 87 |
-
backdrop-blur-xl shadow-lg
|
| 88 |
-
px-3 sm:px-4 py-2.5 sm:py-3 min-w-[100px] sm:min-w-[120px]
|
| 89 |
-
transition-all duration-200 ease-out
|
| 90 |
-
hover:scale-105 hover:shadow-xl
|
| 91 |
-
cursor-default
|
| 92 |
-
`}
|
| 93 |
title={card.desc}
|
| 94 |
-
style={{
|
| 95 |
-
background: `linear-gradient(135deg, rgba(30,41,59,0.9), rgba(15,23,42,0.95))`,
|
| 96 |
-
}}
|
| 97 |
>
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
className="absolute inset-0 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
| 101 |
-
style={{
|
| 102 |
-
boxShadow: `0 0 20px ${card.glow}25, inset 0 1px 0 ${card.glow}15`,
|
| 103 |
-
}}
|
| 104 |
-
/>
|
| 105 |
-
|
| 106 |
-
<div className="relative flex items-start gap-2.5">
|
| 107 |
-
<span
|
| 108 |
-
className="text-lg mt-0.5"
|
| 109 |
-
style={{ filter: `drop-shadow(0 0 6px ${card.glow}50)` }}
|
| 110 |
-
>
|
| 111 |
-
{card.icon}
|
| 112 |
-
</span>
|
| 113 |
<div>
|
| 114 |
<p className="text-base font-bold text-atlas-text leading-none tabular-nums">
|
| 115 |
{typeof card.value === 'number' ? card.value.toLocaleString() : card.value}
|
|
@@ -119,14 +76,6 @@ export default function StatsBar({ overview, viewportSummary, featureCount, load
|
|
| 119 |
</p>
|
| 120 |
</div>
|
| 121 |
</div>
|
| 122 |
-
|
| 123 |
-
{/* Unicorn pulse indicator */}
|
| 124 |
-
{card.highlight && card.value > 100 && (
|
| 125 |
-
<div className="absolute -top-1 -right-1 w-2.5 h-2.5">
|
| 126 |
-
<span className="absolute inline-flex h-full w-full rounded-full bg-orange-400 opacity-75 animate-ping" />
|
| 127 |
-
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-orange-500" />
|
| 128 |
-
</div>
|
| 129 |
-
)}
|
| 130 |
</div>
|
| 131 |
))}
|
| 132 |
</div>
|
|
|
|
| 10 |
label: 'In View',
|
| 11 |
value: viewportSummary?.count || featureCount,
|
| 12 |
icon: '📍',
|
|
|
|
|
|
|
|
|
|
| 13 |
desc: 'Entities visible on map',
|
| 14 |
},
|
| 15 |
{
|
| 16 |
label: 'Total',
|
| 17 |
value: overview.total_entities,
|
| 18 |
icon: '🏢',
|
|
|
|
|
|
|
|
|
|
| 19 |
desc: 'All ecosystem entities',
|
| 20 |
},
|
| 21 |
{
|
| 22 |
label: 'Startups',
|
| 23 |
value: overview.by_type?.startup || 0,
|
| 24 |
icon: '🚀',
|
|
|
|
|
|
|
|
|
|
| 25 |
desc: 'Active startups',
|
| 26 |
},
|
| 27 |
{
|
| 28 |
label: 'Unicorns',
|
| 29 |
value: overview.unicorn_count || 0,
|
| 30 |
icon: '🦄',
|
|
|
|
|
|
|
|
|
|
| 31 |
desc: 'Valued >$1B',
|
|
|
|
| 32 |
},
|
| 33 |
{
|
| 34 |
label: 'Women-led',
|
| 35 |
value: overview.women_led_count || 0,
|
| 36 |
icon: '👩',
|
|
|
|
|
|
|
|
|
|
| 37 |
desc: 'Women founders',
|
| 38 |
},
|
| 39 |
{
|
| 40 |
label: 'Funding',
|
| 41 |
value: overview.total_funding_display,
|
| 42 |
icon: '💰',
|
|
|
|
|
|
|
|
|
|
| 43 |
desc: 'Total raised',
|
| 44 |
isText: true,
|
| 45 |
},
|
|
|
|
| 62 |
{cards.map((card) => (
|
| 63 |
<div
|
| 64 |
key={card.label}
|
| 65 |
+
className="relative rounded-2xl border border-atlas-border bg-atlas-bg/90 shadow-lg px-3 sm:px-4 py-2.5 sm:py-3 min-w-[100px] sm:min-w-[120px] cursor-default"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
title={card.desc}
|
|
|
|
|
|
|
|
|
|
| 67 |
>
|
| 68 |
+
<div className="flex items-start gap-2.5">
|
| 69 |
+
<span className="text-lg mt-0.5">{card.icon}</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
<div>
|
| 71 |
<p className="text-base font-bold text-atlas-text leading-none tabular-nums">
|
| 72 |
{typeof card.value === 'number' ? card.value.toLocaleString() : card.value}
|
|
|
|
| 76 |
</p>
|
| 77 |
</div>
|
| 78 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
</div>
|
| 80 |
))}
|
| 81 |
</div>
|
frontend/src/index.css
CHANGED
|
@@ -24,17 +24,13 @@
|
|
| 24 |
|
| 25 |
@layer components {
|
| 26 |
.glass {
|
| 27 |
-
@apply bg-atlas-bg/80 border border-atlas-border
|
| 28 |
}
|
| 29 |
.gradient-text {
|
| 30 |
-
@apply
|
| 31 |
}
|
| 32 |
.unicorn-badge {
|
| 33 |
-
|
| 34 |
-
background-size: 200% auto;
|
| 35 |
-
-webkit-background-clip: text;
|
| 36 |
-
-webkit-text-fill-color: transparent;
|
| 37 |
-
animation: shimmer 3s linear infinite;
|
| 38 |
}
|
| 39 |
.scrollbar-thin::-webkit-scrollbar {
|
| 40 |
width: 4px;
|
|
@@ -44,9 +40,6 @@
|
|
| 44 |
}
|
| 45 |
}
|
| 46 |
|
| 47 |
-
@keyframes shimmer {
|
| 48 |
-
to { background-position: 200% center; }
|
| 49 |
-
}
|
| 50 |
@keyframes slideUp {
|
| 51 |
from { transform: translateY(100%); }
|
| 52 |
to { transform: translateY(0); }
|
|
@@ -54,7 +47,7 @@
|
|
| 54 |
|
| 55 |
/* MapLibre popup overrides */
|
| 56 |
.maplibregl-popup-content {
|
| 57 |
-
@apply !bg-atlas-bg/95 !border !border-atlas-border !rounded-2xl !p-0 !shadow-2xl
|
| 58 |
max-width: 340px !important;
|
| 59 |
}
|
| 60 |
.maplibregl-popup-tip {
|
|
|
|
| 24 |
|
| 25 |
@layer components {
|
| 26 |
.glass {
|
| 27 |
+
@apply bg-atlas-bg/80 border border-atlas-border;
|
| 28 |
}
|
| 29 |
.gradient-text {
|
| 30 |
+
@apply text-brand-500;
|
| 31 |
}
|
| 32 |
.unicorn-badge {
|
| 33 |
+
@apply text-brand-500 font-semibold;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
}
|
| 35 |
.scrollbar-thin::-webkit-scrollbar {
|
| 36 |
width: 4px;
|
|
|
|
| 40 |
}
|
| 41 |
}
|
| 42 |
|
|
|
|
|
|
|
|
|
|
| 43 |
@keyframes slideUp {
|
| 44 |
from { transform: translateY(100%); }
|
| 45 |
to { transform: translateY(0); }
|
|
|
|
| 47 |
|
| 48 |
/* MapLibre popup overrides */
|
| 49 |
.maplibregl-popup-content {
|
| 50 |
+
@apply !bg-atlas-bg/95 !border !border-atlas-border !rounded-2xl !p-0 !shadow-2xl;
|
| 51 |
max-width: 340px !important;
|
| 52 |
}
|
| 53 |
.maplibregl-popup-tip {
|
frontend/src/styles/index.css
CHANGED
|
@@ -79,10 +79,6 @@ body {
|
|
| 79 |
from { opacity: 0; transform: translateY(8px); }
|
| 80 |
to { opacity: 1; transform: translateY(0); }
|
| 81 |
}
|
| 82 |
-
@keyframes pulseGlow {
|
| 83 |
-
0%, 100% { box-shadow: 0 0 0 0 rgba(249, 115, 22, 0.4); }
|
| 84 |
-
50% { box-shadow: 0 0 0 8px rgba(249, 115, 22, 0); }
|
| 85 |
-
}
|
| 86 |
@keyframes countUp {
|
| 87 |
from { opacity: 0; transform: translateY(10px); }
|
| 88 |
to { opacity: 1; transform: translateY(0); }
|
|
@@ -98,34 +94,21 @@ body {
|
|
| 98 |
.animate-count-up { animation: countUp 0.5s ease-out; }
|
| 99 |
.animate-slide-up { animation: slideUp 0.3s ease-out; }
|
| 100 |
|
| 101 |
-
/*
|
| 102 |
.glass {
|
| 103 |
background: rgba(30, 41, 59, 0.92);
|
| 104 |
-
backdrop-filter: blur(16px);
|
| 105 |
-
-webkit-backdrop-filter: blur(16px);
|
| 106 |
border: 1px solid rgba(51, 65, 85, 0.6);
|
| 107 |
}
|
| 108 |
|
| 109 |
-
/*
|
| 110 |
.gradient-text {
|
| 111 |
-
|
| 112 |
-
-webkit-background-clip: text;
|
| 113 |
-
-webkit-text-fill-color: transparent;
|
| 114 |
-
background-clip: text;
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
/* Unicorn badge shimmer */
|
| 118 |
-
@keyframes shimmer {
|
| 119 |
-
0% { background-position: -200% center; }
|
| 120 |
-
100% { background-position: 200% center; }
|
| 121 |
}
|
| 122 |
|
|
|
|
| 123 |
.unicorn-badge {
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
animation: shimmer 3s linear infinite;
|
| 127 |
-
-webkit-background-clip: text;
|
| 128 |
-
-webkit-text-fill-color: transparent;
|
| 129 |
}
|
| 130 |
|
| 131 |
.line-clamp-2 {
|
|
|
|
| 79 |
from { opacity: 0; transform: translateY(8px); }
|
| 80 |
to { opacity: 1; transform: translateY(0); }
|
| 81 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
@keyframes countUp {
|
| 83 |
from { opacity: 0; transform: translateY(10px); }
|
| 84 |
to { opacity: 1; transform: translateY(0); }
|
|
|
|
| 94 |
.animate-count-up { animation: countUp 0.5s ease-out; }
|
| 95 |
.animate-slide-up { animation: slideUp 0.3s ease-out; }
|
| 96 |
|
| 97 |
+
/* Flat dark panel — no blur */
|
| 98 |
.glass {
|
| 99 |
background: rgba(30, 41, 59, 0.92);
|
|
|
|
|
|
|
| 100 |
border: 1px solid rgba(51, 65, 85, 0.6);
|
| 101 |
}
|
| 102 |
|
| 103 |
+
/* Plain brand color text */
|
| 104 |
.gradient-text {
|
| 105 |
+
color: #F97316;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
}
|
| 107 |
|
| 108 |
+
/* Unicorn badge — plain brand color */
|
| 109 |
.unicorn-badge {
|
| 110 |
+
color: #F97316;
|
| 111 |
+
font-weight: 600;
|
|
|
|
|
|
|
|
|
|
| 112 |
}
|
| 113 |
|
| 114 |
.line-clamp-2 {
|
requirements.txt
CHANGED
|
@@ -2,3 +2,4 @@ fastapi==0.115.0
|
|
| 2 |
uvicorn[standard]==0.32.0
|
| 3 |
pydantic==2.9.0
|
| 4 |
aiofiles==24.1.0
|
|
|
|
|
|
| 2 |
uvicorn[standard]==0.32.0
|
| 3 |
pydantic==2.9.0
|
| 4 |
aiofiles==24.1.0
|
| 5 |
+
slowapi==0.1.9
|