655 Commits
Author SHA1 Message Date
Jules 8a70822731 refactor(funnel): single source of truth for funnel form options
* refactor(web): dedupe funnel form options across the 3 forms

`skills`, `chatService` and `country` were byte-identical in all three
form definitions. They move to `packages/funnel/src/form-options.ts` and
reach the pages through `AFFORM_OPTIONS`; the form files now describe
rendering only.

Lists verified ordered-identical to the originals, entry for entry.

Closes #140

* refactor(civi-crm): derive notion id/label maps from shared funnel options

`SKILLS_MAP`, `CHAT_SERVICE_MAP` and `COUNTRY_MAP` were a fourth copy of
the funnel option lists. They are now `toLabelMap()` over the shared
arrays, the way `HEAR_ABOUT_MAP` already was, so an id cannot mean one
thing in the dropdown and another in Notion.

All 275 derived entries verified identical to the previous literals, so
the Notion and n8n writes are unchanged. The one exception is country
1054, normalised to `Côte d'Ivoire` -- the spelling Notion already stored
and the one the other apostrophised country names use. The dropdown label
and the Ghost member note now match it.
2026-08-26 00:35:44 +09:00
Jules 4a32420ddf fix(web): build through turbo instead of rebuilding shared deps
* fix(web): build through turbo instead of rebuilding shared deps

The app build re-ran the logos-ui build, whose `tsup --clean` wiped
packages/ui/dist while logos-crm compiled against it in parallel. The deploy
now runs the same turbo build as CI, so only the ui task writes that dist.
`out/**` joins the build outputs since turbo owns the export now.

* docs(web): build the app through turbo

* fix(web): build on Vercel through turbo

Vercel ran the app build directly, which no longer builds
@acid-info/logos-ui and failed to resolve it. vercel.json pins the deploy to
the same turbo command Jenkins runs.
2026-08-25 00:19:01 +09:00
Jules dfe8cf0451 fix(civi-crm): validate required funnel fields on afform-submit
* feat(funnel): add per-form required field list

Single source for which funnel answers are required, plus the check the
intake endpoint runs a submission against.

* refactor(web): build the funnel form schema from the shared list

Form definitions no longer carry a `required` flag, they describe
rendering only. The three pages pass REQUIRED_FIELDS_BY_FORM into
ConnectFormSection, which derives the zod schema and the required markers
from it.

* fix(civi-crm): validate required funnel fields on afform-submit

Only formName, the hear-about answer and the captcha were checked, so a
direct POST could create a record with no name, email or country. A
payload missing a required answer now gets a 400 naming the fields.

* docs(funnel): document the required-field validation
2026-08-24 23:24:13 +09:00
jinhojang6 7af2285712 fix(web): fail the build when the RFP source is unreachable
The sitemap fetched RFPs strictly while generateStaticParams and the RFP
listing used a lenient wrapper that degraded to an empty array. Under
`output: 'export'` there is no runtime fallback, so a GitHub hiccup during
generateStaticParams shipped a site whose RFP detail routes all 404 while
the sitemap still advertised them.

Every caller now uses the throwing fetch, so the page set and the sitemap
always agree. Transient failures (403/429/5xx and network errors) retry
with exponential backoff — including the directory listing, which was
single-shot — while a 404 falls straight through to the next fallback URL
instead of burning two pointless retries.
2026-08-20 23:10:38 +09:00
jinhojang6 e65454efc1 fix(web): harden sitemap RFP sourcing and breadcrumb names
Sitemap RFP entries now come from fetchGithubRfpsStrict, which throws when the Github listing or any individual RFP file cannot be read. Previously a failed fetch silently returned an empty list, so a Github outage shipped a sitemap with no RFP URLs while CI stayed green. The RFP pages keep the lenient loader and still degrade to an empty listing.

The static export smoke test derives RFP routes from the exported HTML instead of making a second Github call, which removes the build-time vs check-time race and makes the check fully offline.

Technology stack pages read the parent /technology-stack page copy for the parent breadcrumb name, instead of deriving it from the child page's own hero eyebrow.

Organization @id and url use the normalised site origin, and the structured data tests derive their base URL from siteConfig rather than hardcoding logos.co.
2026-08-20 23:10:38 +09:00
jinhojang6 f7186c294a fix(web): remove inaccurate sitemap lastmod 2026-08-20 23:10:38 +09:00
jinhojang6 5fcb7c5fa5 feat(web): add structured SEO data 2026-08-20 23:10:38 +09:00
jinhojang6 130a8e029b fix(web): align RFP sitemap with live source 2026-08-20 23:10:38 +09:00
jinhojang6 50be39f982 feat(web): add museum anchor links 2026-08-14 00:34:14 +09:00
jinhojang6 a7571936db fix(web): prevent ambiguous Umami events 2026-08-13 23:02:42 +09:00
Jules bc30c4cede refactor(civi-crm/web): drop CiviCRM integration
* refactor(civi-crm): remove case management pages and API

The Circle Case UI, its API routes and the CiviCRM query layer they used
are gone. Keeps the app shell out entirely: no pages remain, so the
layout, styles and UI dependencies go with them.

* refactor(civi-crm): drop CiviCRM write from the intake endpoint

Submissions now go to Notion and, for the steward form, n8n. The legacy
afformCircleContactForm name was CiviCRM-only and is no longer accepted;
the fields[] payload is ignored.

* chore(web): remove CiviCRM afform generator scripts

They fetched form definitions from the CiviCRM API, which is gone. The
generated files under lib/civicrm are hand-maintained from now on.

* refactor(web): trim funnel form definitions and move to lib/funnel-forms

Fields keep only what renders the form: formKey, label, inputType,
required, repeatable. The CiviCRM entity/join/fieldName plumbing and the
fields[] POST payload are gone, along with the unused circle contact
form.

country was marked required but exempt from validation through its
CiviCRM Address join, so it is now enforced like every other required
select.

* docs: rewrite civi-crm and funnel docs for the intake-only app

Drops the CiviCRM API guide and the case management architecture, and
updates the funnel reference to the Notion + n8n flow.
2026-08-13 22:44:23 +09:00
jinhojang6 dca300f059 fix(web): distinguish repeated analytics events 2026-08-13 21:09:28 +09:00
jinhojang6 67c2ad1c92 feat(web): track past present future interactions 2026-08-13 20:55:51 +09:00
jinhojang6 a37549abad fix: replace blocked museum videos 2026-08-13 02:06:48 +09:00
jinhojang6 1473e0806e fix: use CSP-compatible YouTube embeds 2026-08-12 22:53:41 +09:00
Jules 10f539e406 fix(web): exclude vendored static export from lint
ESLint parsed the minified SvelteKit bundles in public/past-present-future, failing test:release.
2026-08-12 22:23:17 +09:00
jinhojang6 f46bf901eb fix: correct bottom museum CTA link 2026-08-12 22:14:45 +09:00
jinhojang6 bab7ff38a0 fix: update museum CTA links 2026-08-12 22:14:45 +09:00
jinhojang6 f6a5d1d919 perf: optimise past present future background video 2026-08-12 04:27:46 +09:00
jinhojang6 2e97fe2b7e fix: self-host past present future background 2026-08-12 03:14:29 +09:00
jinhojang6 6413c74b81 fix: rename civil liberties museum route 2026-08-12 00:26:16 +09:00
jinhojang6 aad422b89c fix: add missing Wikileaks museum image 2026-08-12 00:26:16 +09:00
jinhojang6 4a8097da49 feat: add past present future static site 2026-08-12 00:26:16 +09:00
Jules aeac2e1a4c fix(tech-stack): replace fixed section heights with min-heights
TechDetailHero and TechTextSplitSection were locked to fixed heights, so
content spilled into the next section once text rewrapped. The hero's
mobile block also positioned its children absolutely inside an h-full
container, which cannot grow with its content at all.

Move that block to normal flow with the same 21/60/122px offsets, and
convert every fixed height to a minimum across both primitives and all
call sites. Section margins are untouched, so the gaps are unchanged.
2026-08-11 20:14:30 +09:00
jinhojang6 23d5f6a691 fix(web): keep roadmap release image height equal across tabs
The feature image follows the copy column, so it resized on every tab
switch (639/592/481/481px at the 1440 canvas). Stack all four releases
into the same desktop grid cell instead: the cell is always as tall as
the tallest release, and only the active one is visible while the rest
hold the height open. Every tab now renders the same image height and
the module table stays flush with the bottom of the image.

A fixed min-height would not work here because the tallest release
changes with the column width - at 1280px the spread is 717/639/496/481.
Mobile still renders the active release alone.
2026-08-11 04:00:54 +09:00
jinhojang6 a1f94f84dc fix(web): match roadmap release columns to Figma
Equalise the release panel columns on desktop: the feature image dropped
its fixed 702/521 aspect and now stretches to the grid row, so its bottom
edge lands on the last module row and the module table sits flush with the
bottom of the image. Mobile keeps the 702/521 crop since it stacks.

Cap the release copy at 92ch instead of 548px so the desktop line breaks
land exactly where Figma has them (storage. / stack / making / through).
The cap sits on the element that owns font-mono-body because ch resolves
against the element's own font, and using ch rather than px keeps the
breaks stable across the Fira Mono fallback stack. Mobile is uncapped and
wraps naturally.
2026-08-11 04:00:54 +09:00
jinhojang6 10b0267f73 fix(web): align roadmap release content 2026-08-11 04:00:54 +09:00
jinhojang6 c1ff22f864 fix(web): keep roadmap release images fixed 2026-08-11 04:00:54 +09:00
jinhojang6 2d498487bd fix(web): prevent roadmap image flicker 2026-08-11 04:00:54 +09:00
jinhojang6 3e2dfbd08f feat(web): switch roadmap images by release tab 2026-08-11 04:00:54 +09:00
jinhojang6 e74f4de755 fix(web): refine roadmap release layout, tabs, and responsive spacing
- Align release image height to the left column and remove the image overlay copy
- Enlarge release tabs (keep 12px min font, wider box and padding) and hide the tab scrollbar
- Normalize below-desktop section spacing (release/overview/faqs) to 120px and hide the faq divider below desktop
2026-08-11 04:00:54 +09:00
jinhojang6 0b5d2dbb43 fix(web): increase roadmap minimum font size 2026-08-11 04:00:54 +09:00
jinhojang6 ecd50638ed fix(web): brighten roadmap card CTA on hover 2026-08-11 04:00:54 +09:00
jinhojang6 5a8ba410b9 fix(web): centre roadmap title above mobile 2026-08-11 04:00:54 +09:00
jinhojang6 ddeb9e0a8e fix(web): align roadmap hero content 2026-08-11 04:00:54 +09:00
jinhojang6 e160be8cf5 fix(web): polish roadmap mobile hero and card hover 2026-08-11 04:00:54 +09:00
jinhojang6 556c8b5cb5 fix(web): refine roadmap spacing and divider 2026-08-11 04:00:54 +09:00
jinhojang6 94d1771c2a fix(web): update roadmap visuals and cards 2026-08-11 04:00:54 +09:00
jinhojang6 a5bf23599b fix(web): align roadmap copy and links 2026-08-11 04:00:54 +09:00
jinhojang6 b937d3b279 fix(web): align roadmap module actions 2026-08-11 04:00:54 +09:00
jinhojang6 a503c0c06f fix(web): refine roadmap release breakpoint 2026-08-11 04:00:54 +09:00
jinhojang6 920e6dd8c0 feat: update roadmap release layout 2026-08-11 04:00:54 +09:00
jinhojang6 69bef48afa fix: refine roadmap release alignment 2026-08-11 04:00:54 +09:00
jinhojang6 bd7711013b fix: correct roadmap tab borders 2026-08-11 04:00:54 +09:00
jinhojang6 bab468f679 fix: align roadmap layout with design 2026-08-11 04:00:54 +09:00
jinhojang6 b6e92a4293 fix: make roadmap hero full width 2026-08-11 04:00:54 +09:00
jinhojang6 80ad69cb39 feat: update roadmap page 2026-08-11 04:00:54 +09:00
jinhojang6 30ffc8c9f2 fix(roadmap): improve FAQ responsive layout 2026-08-11 04:00:54 +09:00
jinhojang6 891804db72 fix(roadmap): restore tablet FAQ layout 2026-08-11 04:00:54 +09:00
jinhojang6 1cb9d55c6e feat: add roadmap section to navigation and footer 2026-08-11 04:00:54 +09:00