# Third Platz — every best-of, ranked by everyone

> Community-driven, ranked lists for places AND things, global in scope —
> any country, city, or district, plus any topic (books, movies, apps,
> games…) under the special "world" place. One canonical list per (place,
> topic) — wiki-style, not competing posts. Entities are deduplicated per
> geo. Human-created, AI-readable.

Any place can be added by any visitor (see `/{parentSlug}`'s "Add a
place" form) — the geo tree grows bottom-up from the community, seeded
with a country-level skeleton for the whole world. The geo index below
lists only places that currently have at least one list; an empty place
not shown here may still exist or may be created on demand.

Not everything worth ranking is a place: `/world` is a special,
non-place geo for any-topic lists (books, films, games, apps, and more —
see `kind` on each entity). It never gets child places — no "Add a
place" form on `/world` itself.

## Homepage (/, /.md)

The homepage is an ADAPTIVE global front page, not a fixed layout — each
section honestly upgrades to a livelier presentation only once real
activity crosses a named threshold, and degrades to a plain, still-useful
fallback otherwise (never fabricated activity). "Moving now" is a top-10
activity feed by trending score once >=5 lists have real windowed
activity, else "Recently updated" (same rows, ordered by last update,
honestly unlabeled). "Hot discussions" lists the top 8 posts site-wide by
hot score once >=3 posts exist, else a single link to the most active
place's discussions feed. "Communities" ranks geo and topic hubs
uniformly by recent activity. There is no separate "The World" panel —
world (non-place) lists are reachable via Topics, search, and the feed
sections above.

Starter content (lists and descriptions suffixed "· community starter list")
is seeded and unverified pending community edits, authored by a single
platform account, `autocurator` (handle badged "AUTO CURATOR" everywhere it
renders — see `/u/autocurator`) rather than fictional personas.

## Markdown twins (agents: read this first)

Every content page on this site is also available as clean markdown: append
`.md` to any content URL. `/uae-dubai-deira/best-shawarma` becomes
`/uae-dubai-deira/best-shawarma.md` — same data, no HTML/CSS/JS, served as
`text/markdown; charset=utf-8`. This works for the homepage (`/.md`),
every place (`/{geoSlug}.md`), every canonical list
(`/{geoSlug}/{topicSlug}.md`), every entity (`/e/{id}.md`), the topic
index (`/topics.md`), every topic page (`/topics/{slug}.md`), every
place's discussions feed (`/{geoSlug}/discussions.md`), every individual
discussion post (`/d/{id}.md`), and the
four trust pages (`/about.md`, `/terms.md`, `/privacy.md`,
`/content-policy.md`). Links
inside the markdown point to further `.md` twins, plus a footer line
linking the matching `/api/v1` JSON endpoint (where one exists) and the
HTML page. An unknown `.md` path 404s with a markdown body. This is
usually a better fit for an agent than scraping HTML or crawling the JSON
API by hand.

## URL structure

- `/{geoSlug}` — a place (country, city, or district). Lists breadcrumb
  ancestors, child places, and lists created directly in that place.
  Example: `/uae-dubai-deira`.
- `/{geoSlug}/{topicSlug}` — the canonical ranked list for a (place, topic)
  pair. Items are ordered by community vote score (descending), then by
  creator-authored position as a tiebreak. Example:
  `/uae-dubai-deira/best-shawarma`.
- `/{geoSlug}/discussions` — reddit-style discussion threads for that
  place (NOT rolled up across child places — a post belongs to exactly one
  community). Sortable `hot` (default) / `new` / `top` (with a
  `week`/`all` window). Example: `/uae-dubai-deira/discussions`.
- `/d/{id}` — a single discussion post with its full threaded comment
  tree ("best"-sorted: score descending, then oldest). Example: `/d/5`.
- `/e/{id}` — an entity page: name, kind, address/url if present, and
  every list it's currently on with its live computed rank there. Example:
  `/e/5`.
- `/topics` — every topic with at least one list, grouped alphabetically.
- `/topics/{slug}` — one topic across every place (and the world) it's
  been ranked in: each list's place, item count, and a top-3 teaser.
  Example: `/topics/best-shawarma`.
- `/topics/{slug}?tab=all` — (I37) "Browse all": every visible entity on
  any visible list of this topic, across every place, ranked by
  cross-list PRESENCE (a Borda-style sum of 1/rank-position per list
  appearance) — never a merged vote total; each list still ranks its own
  items independently (same honesty rule as `aroundModules` below).
  Optional facet chip filters `?cuisine=`/`?price=`/`?genre=`
  (closed vocabularies — see "Facets" below), single value per key, only
  shown for keys applicable to the topic's dominant entity kinds. The
  canonical URL for this tab is always the bare `?tab=all` (no facet
  params). Example: `/topics/best-cafes?tab=all`.
- `/u/{handle}` — (I28) a public profile: member-since, public
  contribution counts (lists/items/entities/posts/comments — never
  votes), top places and topics by contribution count, and a paginated
  (50/page) recent-activity feed newest-first. Every real user AND
  anonymous stub identity has one; unknown handles 404. The single
  `autocurator` account (badged "AUTO CURATOR" everywhere its handle
  renders) is a real profile too: `/u/autocurator`. Only profiles with
  `>=3` contributions are listed in the sitemap, to avoid thin pages.
  Example: `/u/autocurator`.

`geoSlug` is a denormalized full-path routing key (e.g.
`uae-dubai-deira`) — it is not meant to be parsed back into segments.

## Trust pages

- `/about` — what Third Platz is, how it works, wiki principles, starter-content
  disclosure, and this AI-readability section.
- `/content-policy` — what's not allowed and how moderation/enforcement works.
- `/terms` — terms of service.
- `/privacy` — privacy policy: exactly what's collected (account email if
  provided, a stub identity cookie, content contributions) and what isn't
  (no analytics, no trackers).

Each also has a markdown twin (`/about.md`, `/content-policy.md`, etc).

## Geo index

- United Arab Emirates (/uae) — 0 lists
  - Dubai (/uae-dubai) — 8 lists
    - Deira (/uae-dubai-deira) — 5 lists
    - Dubai Marina (/uae-dubai-marina) — 5 lists
    - JBR (/uae-dubai-jbr) — 4 lists
    - Downtown Dubai (/uae-dubai-downtown) — 7 lists
    - Jumeirah (/uae-dubai-jumeirah) — 5 lists
    - Business Bay (/uae-dubai-business-bay) — 5 lists
    - Al Barsha (/uae-dubai-al-barsha) — 5 lists
    - Karama (/uae-dubai-karama) — 5 lists
- The World (/world) — 6 lists
- India (/india) — 0 lists
  - Mumbai (/india-mumbai) — 3 lists
    - Bandra (/india-mumbai-bandra) — 3 lists
- Malaysia (/malaysia) — 0 lists
  - Kuala Lumpur (/malaysia-kuala-lumpur) — 3 lists
    - Bangsar (/malaysia-kuala-lumpur-bangsar) — 3 lists
- Saudi Arabia (/saudi-arabia) — 0 lists
  - Riyadh (/saudi-arabia-riyadh) — 3 lists
    - Al Malqa (/saudi-arabia-riyadh-al-malqa) — 3 lists
- Singapore (/singapore) — 0 lists
  - Singapore (/singapore-singapore) — 4 lists
    - Tiong Bahru (/singapore-singapore-tiong-bahru) — 2 lists
- Turkey (/turkey) — 0 lists
  - Istanbul (/turkey-istanbul) — 3 lists
    - Kadıköy (/turkey-istanbul-kad-koy) — 3 lists
- Germany (/germany) — 0 lists
  - Berlin (/germany-berlin) — 3 lists
    - Prenzlauer Berg (/germany-berlin-prenzlauer-berg) — 4 lists
- United Kingdom (/united-kingdom) — 0 lists
  - London (/united-kingdom-london) — 4 lists
    - Shoreditch (/united-kingdom-london-shoreditch) — 3 lists
- Canada (/canada) — 0 lists
  - Toronto (/canada-toronto) — 3 lists
    - Kensington Market (/canada-toronto-kensington-market) — 4 lists
- United States (/united-states) — 0 lists
  - New York City (/united-states-new-york-city) — 5 lists
    - Brooklyn (/united-states-new-york-city-brooklyn) — 3 lists

## Facets (I37)

Closed-vocabulary structured tags on entities — cuisine and price for
food-serving kinds (restaurant, cafe, bar, cart, cafeteria), genre for
media kinds (book, movie, tv-show, game, podcast, album). Never free
text: every write path validates against a fixed vocabulary and rejects
anything else. Display-only on list pages and entity pages (small
badges, may be absent — facets are optional enrichment, not required
fields). The topic hub's `?tab=all` browse view is the one place facets
double as a filter (see the URL structure section above).

## API

Machine-readable JSON twins of the above pages. No auth, no caching headers,
no pagination.

### GET /api/v1/geos/{slug}

Returns the place, its breadcrumb, its child places, and the lists created
directly in it.

```json
{
  "geo": { "id": 1, "slug": "uae-dubai-deira", "name": "Deira", "level": 2, "parentId": 2 },
  "breadcrumb": [ /* geo objects, root first, this geo last */ ],
  "children": [ /* geo objects */ ],
  "lists": [
    { "topicSlug": "best-shawarma", "title": "Best Shawarma", "itemCount": 12, "updatedAt": "2026-01-01 00:00:00", "trendingScore": 3.42 }
  ],
  "aroundModules": [
    {
      "topicSlug": "best-cafes",
      "topicName": "Best Cafes",
      "localities": [
        { "geoSlug": "uae-dubai-deira", "geoName": "Deira", "entityId": 5, "entityName": "Al Ustad Special Kebab", "blurb": "...", "listPath": "/uae-dubai-deira/best-cafes" }
      ]
    }
  ]
}
```

404s with `{ "error": "Geo not found", "slug": "..." }` for an unknown slug.

`trendingScore` is a read-time, time-decayed activity score (recent votes,
items added, list creation, and blurb edits, decayed over a 14-day
window) — 0 on a list with no recent activity, never omitted.

`aroundModules` (I27) is additive, and only ever present for level-0
COUNTRY geos (never city/district pages, never the special "world" geo) —
`null` for every other geo, including a country with fewer than 2 real
localities (cities/districts) sharing the same topic. Each topic's
`localities` array is capped at 4 (by that locality's item count), topics
capped at 6 (by subtree trending, then list count). This is a
grouped-by-locality view, NEVER a numerically merged cross-community
ranking — votes in one city aren't comparable to votes in another, so
there is no single combined "#1" across the whole country, only each
city/district's own #1 shown side by side.

### GET /api/v1/lists/{geoSlug}/{topicSlug}

Returns a single ranked list, items already in computed rank order (vote
score descending, then position ascending).

```json
{
  "geo": { "id": 1, "slug": "uae-dubai-deira", "name": "Deira", "level": 2, "parentId": 2 },
  "topic": { "id": 3, "slug": "best-shawarma", "name": "Best Shawarma" },
  "title": "Best Shawarma",
  "description": "...",
  "updatedAt": "2026-01-01 00:00:00",
  "items": [
    {
      "position": 1,
      "score": 4,
      "blurb": "...",
      "contributor": "swift-falcon-07",
      "entity": { "id": 5, "name": "Al Ustad Special Kebab", "kind": "restaurant", "address": "...", "url": "..." }
    }
  ]
}
```

404s with `{ "error": "List not found", "geoSlug": "...", "topicSlug": "..." }`
when the place, topic, or list doesn't exist.

### GET /api/v1/entities/{id}

Returns an entity plus every list it's currently on, with its live computed
rank on each (same vote-score-then-position ordering as everywhere else,
not an approximation).

```json
{
  "entity": { "id": 5, "name": "Al Ustad Special Kebab", "kind": "restaurant", "address": "...", "url": null },
  "geo": { "id": 1, "slug": "uae-dubai-deira", "name": "Deira", "level": 2, "parentId": 2 },
  "lists": [
    { "title": "Best Shawarma", "path": "/uae-dubai-deira/best-shawarma", "rank": 1, "score": 4, "blurb": "..." }
  ]
}
```

404s with `{ "error": "Entity not found", "id": "..." }` for an unknown id.
If an entity was merged into another one by an admin (I23 — a canonical-
entity merge, e.g. the same venue was added once at city level and once at
district level), this endpoint returns a `308` redirect to
`/api/v1/entities/{canonicalId}` instead of a body; the HTML `/e/{id}`
page 308-redirects the same way.

### GET /api/v1/topics/{slug}

Returns a topic and every list for it across every place (and the world).

```json
{
  "topic": { "id": 3, "slug": "best-shawarma", "name": "Best Shawarma" },
  "lists": [
    {
      "geo": { "slug": "uae-dubai-deira", "name": "Deira" },
      "title": "Best Shawarma",
      "itemCount": 12,
      "path": "/uae-dubai-deira/best-shawarma",
      "updatedAt": "2026-01-01 00:00:00"
    }
  ]
}
```

404s with `{ "error": "Topic not found", "slug": "..." }` for an unknown slug.

### GET /api/v1/search?q={query}&in={geoSlug}

Full-text search across lists, entities (places and things), and geos.
bm25-ordered, prefix-matched on the last word of `q`, capped at 25 results.

```json
{
  "query": "shawarma",
  "results": [
    { "type": "list", "title": "Best Shawarma in Deira", "snippet": "...", "path": "/uae-dubai-deira/best-shawarma" },
    { "type": "entity", "title": "Al Mallah", "snippet": "restaurant Deira, Dubai", "path": "/uae-dubai-deira/best-shawarma" }
  ]
}
```

`type` is one of `list` / `entity` / `geo` / `post`; `path` is a
site-relative URL. `q` is required and capped at 100 characters (400 on
either violation). Optional `in={geoSlug}` additively scopes results to
that geo's whole subtree (itself + all descendants) and echoes an
additive `scope: { slug, name }` field; an unknown/bogus slug is
silently ignored (no `scope` field, falls back to unscoped search)
rather than erroring. There is also a human `/search` page (also accepts
`&in=`), and every geo page has its own scoped "Search in <Geo>..." box.

### GET /api/v1/discussions/{geoSlug}

Returns a place's discussion feed — same data the `/{geoSlug}/discussions`
page shows, direct-geo only (never rolled up across child places).

```json
{
  "geo": { "id": 1, "slug": "uae-dubai-deira", "name": "Deira", "level": 2, "parentId": 2 },
  "sort": "hot",
  "page": 1,
  "hasNext": false,
  "hasPrev": false,
  "totalCount": 3,
  "posts": [
    { "id": 5, "title": "Best time to visit the souk?", "createdAt": "2026-01-01 00:00:00", "author": "swift-falcon-07", "score": 4, "commentCount": 2, "path": "/d/5" }
  ]
}
```

Optional `?sort=hot|new|top` (default `hot`), `?window=week|all`
(only meaningful with `sort=top`, default `week`), `?page=` — an
unknown/bogus value for either silently falls back to its default rather
than erroring. `window` is only echoed back when `sort=top`.
404s with `{ "error": "Geo not found", "geoSlug": "..." }` for an unknown
place.

### GET /api/v1/posts/{id}

Returns a single discussion post plus its full comment tree, nested and
"best"-sorted (score descending, then oldest — same fixed default the HTML
page uses; there is no "controversial" sort in v1).

```json
{
  "post": { "id": 5, "title": "Best time to visit the souk?", "body": "...", "createdAt": "2026-01-01 00:00:00", "author": "swift-falcon-07", "score": 4, "path": "/d/5", "geo": { "slug": "uae-dubai-deira", "name": "Deira" } },
  "comments": [
    { "id": 12, "parentId": null, "createdAt": "2026-01-01 01:00:00", "score": 2, "depth": 0, "removed": false, "author": "swift-falcon-07", "body": "Evenings are cooler.", "children": [] }
  ]
}
```

A moderated/self-deleted comment keeps its place in the tree with
`"removed": true`, `"body": null`, and no `author` field — its
children still render underneath it, same as the HTML page's
"[removed]" placeholder. 404s with `{ "error": "Post not found", "id": "..." }`
for an unknown or hidden post — a hidden post reads as "does not exist"
everywhere, including here.

### GET /api/v1/users/{handle}

(I28, additive.) Public contribution summary — the same data the `/u/{handle}`
HTML page renders, never an email or votes.

```json
{
  "user": { "handle": "autocurator", "isCurator": true, "memberSince": "2026-01-01 00:00:00" },
  "counts": { "listsCreated": 40, "itemsAdded": 320, "entitiesCreated": 210, "postsCreated": 0, "commentsCreated": 0, "total": 570 },
  "topPlaces": [ { "id": 2, "slug": "uae-dubai", "name": "Dubai", "count": 40 } ],
  "topTopics": [ { "id": 4, "slug": "best-shawarma", "name": "Best Shawarma", "count": 9 } ],
  "activity": { "items": [ /* type, refId, title, createdAt, geoSlug, geoName, topicSlug, postId */ ], "page": 1, "hasNext": true, "hasPrev": false }
}
```

Optional `?page=` (default 1, 50 items/page, newest first). 404s with
`{ "error": "User not found", "handle": "..." }` for an unknown handle.
Anonymous stub identities have profiles too, with real (often zero)
counts.
