diff --git a/README.md b/README.md index 577d3a2..24d58a5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Szabadpálya -Community funding for Hungarian civil projects, with no platform fee. +Community funding for Hungarian public projects, with no platform fee. **Backend** Go + Gin + pgx, Postgres 17 · **Frontend** React 19 + React Router + TypeScript + Vite + Tailwind 4 · **Deployment** Docker Compose, self-hosted. diff --git a/SPEC.md b/SPEC.md index 959a4bb..960e01a 100644 --- a/SPEC.md +++ b/SPEC.md @@ -8,7 +8,7 @@ ## 1. Positioning -Curated, invite-only crowdfunding platform for civil projects: helyi szervezetek, +Curated, invite-only crowdfunding platform for public projects: helyi szervezetek, Lepke-típusú kulturális egyesületek, szövetséges NGO-k. **Party-adjacent, not party-branded.** The platform has its own name, identity and @@ -38,7 +38,7 @@ registrar and register before any design work ships) **Tagline:** *Szabad a pálya.* ("the track is clear") From the living idiom "szabad a pálya!" — the coast is clear, go ahead, nothing is -in your way. Exactly what the platform says to a civil project waiting for a +in your way. Exactly what the platform says to a public project waiting for a permission it never actually needed. `Pálya` also reads as track / field / career: young, forward-moving, unideological. @@ -348,7 +348,7 @@ These are load-bearing for **money**, not just uptime: ### Server placement Host in the **EU but outside Hungary** — Frankfurt or Helsinki. The database holds a -private list of who funded which civil project; for a party-adjacent platform that is +private list of who funded which public project; for a party-adjacent platform that is politically sensitive data, and Hungarian-jurisdiction infrastructure is more exposed to Hungarian pressure than German-hosted infrastructure is. This is a threat-model decision, not only a GDPR one. Full-disk encryption on the box, encrypted backups. diff --git a/api/internal/httpapi/document.go b/api/internal/httpapi/document.go index e47259d..eca15f1 100644 --- a/api/internal/httpapi/document.go +++ b/api/internal/httpapi/document.go @@ -13,8 +13,8 @@ import ( ) const ( - siteTitle = "Szabadpálya — közösségi finanszírozás civil projekteknek" - siteDesc = "Szabad a pálya. Közösségi finanszírozás magyar civil projekteknek, platformdíj nélkül." + siteTitle = "Szabadpálya — közösségi finanszírozás közéleti projekteknek" + siteDesc = "Szabad a pálya. Közösségi finanszírozás magyar közéleti projekteknek, platformdíj nélkül." ) func pageTitle(s string) string { return s + " · Szabadpálya" } @@ -46,11 +46,6 @@ func (s *Server) document(c *gin.Context) { switch { case path == "/": - campaigns, err := s.store.ListCampaigns(ctx) - if err != nil { - s.failDocument(c, err) - return - } stats, err := s.store.PlatformStats(ctx) if err != nil { s.failDocument(c, err) @@ -61,7 +56,7 @@ func (s *Server) document(c *gin.Context) { Description: siteDesc, Canonical: s.shell.Abs("/"), OG: shell.OpenGraph{URL: s.shell.Abs("/")}, - }, bootstrap{Route: "home", Data: gin.H{"campaigns": campaigns, "stats": stats}}) + }, bootstrap{Route: "home", Data: gin.H{"stats": stats}}) case path == "/kampanyok": campaigns, err := s.store.ListCampaigns(ctx) diff --git a/scripts/ci.sh b/scripts/ci.sh index 8980e38..53b92be 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -238,6 +238,24 @@ assert payload["data"]["slug"] == "'"${SLUG}"'", payload["data"]["slug"] } step "bootstrap payload inlined" check_bootstrap +# The landing page shows the headline numbers only; the campaigns themselves +# live one click away, on /kampanyok. If the list creeps back into the home +# payload the page is loading rows it does not render. +check_home_has_no_list() { + curl -s "http://localhost:${APP_PORT}/" \ + | python3 -c ' +import re, sys, json +html = sys.stdin.read() +m = re.search(r"", html, re.S) +if not m: print("no __DATA__ payload"); raise SystemExit(1) +payload = json.loads(m.group(1)) +assert payload["route"] == "home", payload["route"] +assert "stats" in payload["data"], "the home payload lost its stats" +assert "campaigns" not in payload["data"], "the home payload still carries the campaign list" +' +} +step "landing page carries no campaign list" check_home_has_no_list + check_health_ok() { local body code body=$(curl -s -w '\n%{http_code}' "http://localhost:${APP_PORT}/api/health") diff --git a/web/index.html b/web/index.html index 8e6c5ca..72b5f68 100644 --- a/web/index.html +++ b/web/index.html @@ -8,7 +8,7 @@ - Szabadpálya — közösségi finanszírozás civil projekteknek + Szabadpálya — közösségi finanszírozás közéleti projekteknek diff --git a/web/src/assets/kincso.jpg b/web/src/assets/kincso.jpg new file mode 100644 index 0000000..edb4185 Binary files /dev/null and b/web/src/assets/kincso.jpg differ diff --git a/web/src/components/SiteFooter.tsx b/web/src/components/SiteFooter.tsx index d9243fd..1d26ead 100644 --- a/web/src/components/SiteFooter.tsx +++ b/web/src/components/SiteFooter.tsx @@ -8,7 +8,7 @@ export function SiteFooter() {

- Közösségi finanszírozás civil projekteknek. Platformdíj nélkül. + Közösségi finanszírozás közéleti projekteknek. Platformdíj nélkül.

, - loader: async (): Promise => { - const inlined = takeBootstrap("home"); - if (inlined) return inlined; - const [campaigns, stats] = await Promise.all([api.campaigns(), api.stats()]); - return { campaigns, stats }; - }, + loader: async (): Promise => + takeBootstrap("home") ?? { stats: await api.stats() }, }, { path: "/kampanyok", diff --git a/web/src/routes/Campaigns.tsx b/web/src/routes/Campaigns.tsx index ead00bf..bfc6b81 100644 --- a/web/src/routes/Campaigns.tsx +++ b/web/src/routes/Campaigns.tsx @@ -12,8 +12,8 @@ export default function Campaigns() {

Projektek

Amin most dolgozunk

- Minden kampányt kézzel hagyunk jóvá. Pártot, kampányt és jelöltet nem - finanszírozunk — civil projekteket igen. + Minden projektet kézzel hagyunk jóvá. Kampányt és jelöltet nem + finanszírozunk — közéleti kezdeményezéseket igen.

diff --git a/web/src/routes/Home.tsx b/web/src/routes/Home.tsx index 8864356..f7d8dda 100644 --- a/web/src/routes/Home.tsx +++ b/web/src/routes/Home.tsx @@ -1,72 +1,81 @@ import { Link, useLoaderData } from "react-router"; -import { CampaignCard } from "@/components/CampaignCard"; import { useTitle } from "@/lib/title"; import type { HomeData } from "@/router"; +import kincso from "@/assets/kincso.jpg"; +/** + * Copy on the left, one illustration on the right. Both sit in the site's + * max-w-6xl container, so the headline starts on the same left edge as the + * header logo and the footer; the measure is set on the text itself. + * + * The portrait is imported rather than dropped in web/public: Vite hashes an + * imported asset into dist/assets, which is the one directory the Go server + * serves as static files. A file in public/ would land at the dist root and be + * answered with the HTML document instead. + */ export default function Home() { - const { campaigns, stats } = useLoaderData() as HomeData; - const active = campaigns.filter((c) => c.status !== "lezart"); + const { stats } = useLoaderData() as HomeData; useTitle(""); return ( - <> -
-
-
-

Közösségi finanszírozás

-

- Szabad a pálya{" "} - - → - -

-

- Civil projektek finanszírozása a közösségtől. Nem kérünk platformdíjat: - ami összegyűlik, az a projekté. -

-
- - Nézd meg, mi épül - -
-
+
+
+
+

Közösségi finanszírozás

-
+

+ Szabad a pálya{" "} + + → + +

+ +

+ Közéleti kezdeményezések finanszírozása a közösségtől. Nem kérünk platformdíjat: + ami összegyűlik, az a projekté. +

+ + + {/* The ring is a box-shadow, not an outline: outline is what + :focus-visible uses in styles.css, and a permanent one there + would leave keyboard focus with nothing distinct to show. */} + Nézd meg, mi épül + + → + + + +
-
-
-
-
-

Most fut

-

Aktív kampányok

-
- - Összes projekt → - +
+ {/* Square source in a landscape box, so the crop is biased upwards to + keep the face off the bottom edge. */} + Szilas Kincső
-
- {active.map((c) => ( - - ))} -
-
- +
+
); } function Stat({ value, label }: { value: string; label: string }) { return ( -
-
{value}
-
{label}
+
+
{value}
+
{label}
); }