diff --git a/docs/join-form-flow.md b/docs/join-form-flow.md
index e242bb1c9..7a9bad8fb 100644
--- a/docs/join-form-flow.md
+++ b/docs/join-form-flow.md
@@ -206,7 +206,7 @@ stateDiagram-v2
Step1 --> Browse: "I just want to take action now" (mode=browse)
Browse: Browse mode (act-now, no signup) includes ActionCards
- Step2: Step 2 — Intent (keep informed / newsletter / act-now / volunteer / lead)
+ Step2: Step 2 — Opt-ins + intent (opt-ins: keep informed / newsletter; intent: act-now / volunteer / lead)
Step2 --> Step1: Back
Step2 --> Submit2: Submit (POST /embed/onboarding-form?/submit)
Submit2 --> Step3Confirm: intent = null OR act-now (contact)
@@ -235,6 +235,12 @@ stateDiagram-v2
Browse --> [*]
```
+Step 2 shows the two email opt-ins above the intent cards, with a
+critical-alert disclosure under the opt-ins (`aria-describedby` on both, so
+screen readers reach it). On `/join`, neither the opt-ins nor an intent is
+required: a submission carrying only the GDPR consent is valid and lands on
+the critical-alerts tier only.
+
### Continuation mode
The diagram above describes a fresh `/join` visit. When `OnboardingFlow` is
@@ -245,9 +251,9 @@ question, "what do you want to do", rather than a signup:
- The keep-informed and Substack opt-in cards are hidden, as is the GDPR
consent checkbox, because both were answered on the subscribe form.
-- Picking an intent becomes **required** to submit. On a fresh visit an opt-in
- alone is enough, so this is a different submit gate, not just a different
- layout.
+- Picking an intent becomes **required** to submit. On a fresh visit nothing
+ beyond the GDPR consent is required, so this is a different submit gate, not
+ just a different layout.
- The two hidden inputs described under "Chapter sharing" are added.
So the `Step2 --> Step1: Back` edge and the opt-in-only submit path in the
@@ -346,10 +352,13 @@ an update is in "Create versus update" above.
- Required: `full_name`, `email`, `country`, `city`.
- `email` must match `^\S+@\S+\.\S+$`.
- `country` must be in `COUNTRIES`, checked only when one is supplied.
-- `intent` must be one of `INTENTS` (`Act now` | `Volunteer` | `Lead` | `Keep informed`).
-- GDPR consent (`agree_gdpr`). `/subscribe` posts it as a hidden field, since
- signing up on that form is itself the privacy-policy consent, which its
- microcopy links.
+- `intent` must be one of `INTENTS` (`None` | `Keep informed` | `Act now` |
+ `Volunteer` | `Lead`). Step 2 submits `None` when no intent is picked; the
+ browse signup hardcodes `Act now`; `/subscribe` hardcodes `Keep informed`.
+- GDPR consent (`agree_gdpr`) required **only on the create path** — step-3
+ volunteer updates are exempt because consent was captured at step 2.
+ `/subscribe` posts it as a hidden field, since signing up on that form is
+ itself the privacy-policy consent, which its microcopy links.
- Volunteer path additionally requires: ≥1 language, a valid `hours` value, and
both `agree_volunteer` and `agree_conduct` checkboxes.
diff --git a/src/lib/components/onboarding/OnboardingFlow.svelte b/src/lib/components/onboarding/OnboardingFlow.svelte
index 827a2dd4a..2def68dc3 100644
--- a/src/lib/components/onboarding/OnboardingFlow.svelte
+++ b/src/lib/components/onboarding/OnboardingFlow.svelte
@@ -123,6 +123,8 @@
// back so the server updates the record instead of creating another. When the
// /subscribe flow hands off, it's seeded with the record already created there.
let recordId = $state(initialRecordId)
+ // Never pre-checked on /join: marketing consent must be freely given, for
+ // Volunteers/Leads too (operational volunteer comms ride legitimate interest).
let keepInformed = $state(initialKeepInformed)
let submitting = $state(false)
let browseSignedUp = $state(false)
@@ -203,9 +205,9 @@
const isContinuation = $derived(startStep === 2 && !!initialRecordId)
// GDPR data-processing consent gating every record-creating submission
- // (step 2 + browse). Chapter-sharing consent is not bundled here: it lives
- // in the optional "Keep me informed" opt-in, since we only share details
- // with a local chapter when the person asks to be connected to one.
+ // (step 2 + browse). On /join this checkbox also carries chapter sharing:
+ // the server writes share permission on every /join create. /subscribe asks
+ // for chapter sharing as its own checkbox instead.
// Stays false so it never pre-checks a visible consent box; the continuation
// already consented on the subscribe form and is exempted at the submit gate.
let gdprConsent = $state(false)
@@ -555,7 +557,7 @@
+ {@html msgs.onboarding_email_critical_notice}
+
{msgs.onboarding_intent_more_optional}
{/if}
-
+
{#each intentOptions as option (option.key)}
{submitting
? msgs.onboarding_btn_submitting
@@ -1077,7 +1088,7 @@
h2 {
font-family: var(--font-heading);
- margin-top: 0;
+ margin: 0;
}
.browse-banner {
@@ -1102,7 +1113,7 @@
display: flex;
flex-direction: column;
align-items: stretch;
- gap: 1rem;
+ gap: 0.75rem;
width: 100%;
max-width: none;
}
@@ -1520,7 +1531,7 @@
text-transform: uppercase;
letter-spacing: 0.05em;
opacity: 0.7;
- margin: 1.5rem 0 0.5rem 0;
+ margin: 0.75rem 0 0.5rem 0;
}
.confirmation-footer :global(.discord-button) {
diff --git a/src/lib/components/onboarding/messages.ts b/src/lib/components/onboarding/messages.ts
index 0d3b5bb8e..20c0b1a8b 100644
--- a/src/lib/components/onboarding/messages.ts
+++ b/src/lib/components/onboarding/messages.ts
@@ -19,6 +19,8 @@ export interface OnboardingMessages {
onboarding_btn_browse: string
onboarding_browse_helper: string
onboarding_step2_heading: string
+ onboarding_optins_heading: string
+ onboarding_email_critical_notice: string
onboarding_intent_keep_informed_label: string
onboarding_intent_keep_informed_sub: string
onboarding_intent_newsletter_label: string
@@ -196,27 +198,32 @@ const en: OnboardingMessages = {
onboarding_btn_browse: '👀 I just want to take action now',
onboarding_browse_helper: 'Skip ahead to see the actions you can take today, no email required.',
onboarding_step2_heading: 'What brings you here?',
+ onboarding_optins_heading: 'Want to hear from us?',
+ onboarding_email_critical_notice:
+ 'We may occasionally send you a critical alert, even if you don\'t opt into any of these. You can unsubscribe from any list at any time. See our privacy policy .',
onboarding_intent_keep_informed_label: 'Keep me informed',
onboarding_intent_keep_informed_sub:
- 'Connect me with my local PauseAI chapter, and keep me updated on global campaigns.',
+ 'Get global campaign updates, plus news and ways to help from your local chapter.',
onboarding_intent_newsletter_label: 'Subscribe to our Substack',
- onboarding_intent_newsletter_sub: 'General news on AI, delivered via our Substack newsletter.',
+ onboarding_intent_newsletter_sub: "AI news from the PauseAI team's perspective.",
onboarding_intent_more_optional: 'Want to do more? (optional)',
onboarding_intent_act_now_label: 'I just want to take action now',
onboarding_intent_act_now_sub: 'Show me what I can do today.',
onboarding_intent_volunteer_label: 'I want to volunteer regularly',
- onboarding_intent_volunteer_sub: 'Help me find a role that fits.',
+ onboarding_intent_volunteer_sub:
+ 'Help me find a role that fits. Your local chapter will be in touch.',
onboarding_intent_lead_label: 'I want to lead',
- onboarding_intent_lead_sub: "I'm ready to organize in my country or region.",
+ onboarding_intent_lead_sub:
+ "I'm ready to organize in my country or region. Your local chapter will be in touch.",
onboarding_gdpr_consent:
- 'I agree to the Privacy Policy , including sharing my details with my local PauseAI chapter (which may be a separate entity to PauseAI Global) for local coordination. *',
+ 'I agree to the Privacy Policy . I understand my details will be shared with my local PauseAI chapter (which may be a separate entity to PauseAI Global) for local coordination. *',
onboarding_btn_submit: 'Submit →',
onboarding_btn_submitting: 'Submitting...',
onboarding_btn_back: '← Back',
onboarding_confirm_keep_informed:
- "We'll connect you with your local PauseAI chapter and keep you updated on global campaigns.",
+ "You'll get global campaign updates, plus news and ways to help from your local chapter.",
onboarding_confirm_newsletter:
- "You're subscribed to our Substack newsletter for general news on AI.",
+ "You're subscribed to our Substack: AI news from the PauseAI team's perspective, delivered by email.",
onboarding_confirm_a_title: "You're in.",
onboarding_confirm_b_title: "You're in, thanks for joining us.",
onboarding_confirm_b_sub: "You're all set. Here are a few ways to make a difference today.",
@@ -226,10 +233,10 @@ const en: OnboardingMessages = {
onboarding_browse_header_sub:
'Below are some actions you can take right now. The best way to stay informed about new opportunities is to sign up below.',
onboarding_browse_signed_up:
- "✓ You're in. We'll connect you with your local PauseAI chapter and keep you updated on global campaigns.",
+ "✓ You're in. You'll get global campaign updates, plus news and ways to help from your local chapter.",
onboarding_browse_keep_informed_title: 'Keep me informed',
onboarding_browse_keep_informed_sub:
- 'Connect me with my local PauseAI chapter and keep me updated on global campaigns.',
+ 'Get global campaign updates, plus news and ways to help from your local chapter.',
onboarding_btn_sign_me_up: 'Sign me up →',
onboarding_btn_signing_up: 'Signing up...',
onboarding_section_ways_to_help: 'A few ways to help today',
@@ -406,28 +413,32 @@ const de: OnboardingMessages = {
onboarding_btn_browse: '👀 Ich möchte nur jetzt handeln',
onboarding_browse_helper: 'Überspringe die Anmeldung und sieh, was du heute tun kannst.',
onboarding_step2_heading: 'Was bringt dich hierher?',
+ onboarding_optins_heading: 'Möchtest du von uns hören?',
+ onboarding_email_critical_notice:
+ 'Wir können dir gelegentlich eine kritische Warnung schicken, auch wenn du keine dieser Optionen ankreuzt. Du kannst dich jederzeit von jeder Liste abmelden. Siehe unsere Datenschutzrichtlinie .',
onboarding_intent_keep_informed_label: 'Auf dem Laufenden bleiben',
onboarding_intent_keep_informed_sub:
- 'Verbinde mich mit meinem lokalen PauseAI-Kapitel und halte mich über globale Kampagnen auf dem Laufenden.',
+ 'Erhalte Updates zu globalen Kampagnen sowie Neuigkeiten und Mitmach-Möglichkeiten aus deiner lokalen Gruppe.',
onboarding_intent_newsletter_label: 'Unseren Substack abonnieren',
- onboarding_intent_newsletter_sub:
- 'Allgemeine Neuigkeiten zur KI über unseren Substack-Newsletter.',
+ onboarding_intent_newsletter_sub: 'KI-Nachrichten aus der Perspektive des PauseAI-Teams.',
onboarding_intent_more_optional: 'Möchtest du mehr tun? (optional)',
onboarding_intent_act_now_label: 'Ich möchte jetzt handeln',
onboarding_intent_act_now_sub: 'Zeig mir, was ich heute tun kann.',
onboarding_intent_volunteer_label: 'Ich möchte regelmäßig freiwillig tätig sein',
- onboarding_intent_volunteer_sub: 'Hilf mir, eine passende Rolle zu finden.',
+ onboarding_intent_volunteer_sub:
+ 'Hilf mir, eine passende Rolle zu finden. Deine lokale Gruppe meldet sich bei dir.',
onboarding_intent_lead_label: 'Ich möchte leiten',
- onboarding_intent_lead_sub: 'Ich bin bereit, in meinem Land oder meiner Region zu organisieren.',
+ onboarding_intent_lead_sub:
+ 'Ich bin bereit, in meinem Land oder meiner Region zu organisieren. Deine lokale Gruppe meldet sich bei dir.',
onboarding_gdpr_consent:
- 'Ich stimme der Datenschutzrichtlinie zu, einschließlich der Weitergabe meiner Daten an mein lokales PauseAI-Kapitel (das eine separate Einheit von PauseAI Global sein kann) zur lokalen Koordination. *',
+ 'Ich stimme der Datenschutzrichtlinie zu. Mir ist bewusst, dass meine Daten zur lokalen Koordination an meine lokale PauseAI-Gruppe weitergegeben werden (die eine von PauseAI Global getrennte Einheit sein kann). *',
onboarding_btn_submit: 'Absenden →',
onboarding_btn_submitting: 'Wird abgesendet...',
onboarding_btn_back: '← Zurück',
onboarding_confirm_keep_informed:
- 'Wir werden dich mit deinem lokalen PauseAI-Kapitel verbinden und dich über globale Kampagnen informieren.',
+ 'Du erhältst Updates zu globalen Kampagnen sowie Neuigkeiten und Mitmach-Möglichkeiten aus deiner lokalen Gruppe.',
onboarding_confirm_newsletter:
- 'Du hast unseren Substack-Newsletter für allgemeine KI-Nachrichten abonniert.',
+ 'Du hast unseren Substack abonniert: KI-Nachrichten aus der Perspektive des PauseAI-Teams, per E-Mail.',
onboarding_confirm_a_title: 'Du bist dabei.',
onboarding_confirm_b_title: 'Du bist dabei – danke, dass du dich uns anschließt.',
onboarding_confirm_b_sub:
@@ -439,10 +450,10 @@ const de: OnboardingMessages = {
onboarding_browse_header_sub:
'Im Folgenden findest du Aktionen, die du sofort ergreifen kannst. Am besten bleibst du über neue Möglichkeiten informiert, indem du dich unten anmeldest.',
onboarding_browse_signed_up:
- '✓ Du bist dabei. Wir verbinden dich mit deinem lokalen PauseAI-Kapitel und halten dich über globale Kampagnen auf dem Laufenden.',
+ '✓ Du bist dabei. Du erhältst Updates zu globalen Kampagnen sowie Neuigkeiten und Mitmach-Möglichkeiten aus deiner lokalen Gruppe.',
onboarding_browse_keep_informed_title: 'Auf dem Laufenden bleiben',
onboarding_browse_keep_informed_sub:
- 'Verbinde mich mit meinem lokalen PauseAI-Kapitel und halte mich über globale Kampagnen auf dem Laufenden.',
+ 'Erhalte Updates zu globalen Kampagnen sowie Neuigkeiten und Mitmach-Möglichkeiten aus deiner lokalen Gruppe.',
onboarding_btn_sign_me_up: 'Anmelden →',
onboarding_btn_signing_up: 'Wird angemeldet...',
onboarding_section_ways_to_help: 'Einige Möglichkeiten zu helfen',
@@ -500,9 +511,9 @@ const de: OnboardingMessages = {
onboarding_lead_us_branch:
'Da du in den Vereinigten Staaten ansässig bist, bewirb dich bitte über PauseAI US: PauseAI US Bewerbungsformular .',
onboarding_lead_has_chapter: (country) =>
- `${country} hat bereits ein PauseAI-Kapitel , daher könntest du statt einer nationalen Gruppe eine regionale oder städtische Gruppe leiten. Finde dein Kapitel unter pauseai.info/communities , oder wende dich per E-Mail an unseren Organizing Director (siehe unten), um es zu besprechen.`,
+ `${country} hat bereits eine nationale PauseAI-Gruppe , daher könntest du stattdessen eine regionale oder städtische Gruppe leiten. Finde sie unter pauseai.info/communities , oder wende dich per E-Mail an unseren Organizing Director (siehe unten), um es zu besprechen.`,
onboarding_lead_check_chapter:
- 'Überprüfe zunächst, ob dein Land bereits ein Kapitel hat: pauseai.info/communities .',
+ 'Überprüfe zunächst, ob dein Land bereits eine nationale Gruppe hat: pauseai.info/communities .',
onboarding_lead_what_you_do_title: 'Was du tun wirst',
onboarding_lead_do_1:
'Deine lokale Gruppe aufbauen und wachsen lassen, indem du neue Freiwillige willkommen heißt und gemeinsam Events organisierst.',
@@ -510,7 +521,7 @@ const de: OnboardingMessages = {
onboarding_lead_do_3:
'Monatlich mit dem globalen PauseAI-Team zusammenkommen, um Ideen auszutauschen und mit der globalen Strategie und vierteljährlichen Kampagnen koordiniert zu bleiben.',
onboarding_lead_do_4:
- 'Die Aktivitäten deines Kapitels in sozialen Medien teilen und Events bekannt machen.',
+ 'Die Aktivitäten deiner Gruppe in sozialen Medien teilen und Events bekannt machen.',
onboarding_lead_looking_for_title: 'Was wir suchen',
onboarding_lead_looking_1:
'Fähigkeit, direkte Aktionen zu planen und durchzuführen, einschließlich Workshops und öffentlicher Events.',
@@ -627,28 +638,32 @@ const fr: OnboardingMessages = {
onboarding_browse_helper:
"Passe directement aux actions que tu peux entreprendre aujourd'hui, sans e-mail requis.",
onboarding_step2_heading: "Qu'est-ce qui t'amène ici ?",
+ onboarding_optins_heading: 'Tu veux avoir de nos nouvelles ?',
+ onboarding_email_critical_notice:
+ 'Nous pouvons occasionnellement t\'envoyer une alerte critique, même si tu ne coches aucune de ces options. Tu peux te désinscrire de n\'importe quelle liste à tout moment. Voir notre politique de confidentialité .',
onboarding_intent_keep_informed_label: 'Me tenir informé·e',
onboarding_intent_keep_informed_sub:
- 'Me mettre en relation avec mon chapitre PauseAI local et me tenir informé·e des campagnes mondiales.',
+ "Reçois les actualités des campagnes mondiales, ainsi que les nouvelles et les occasions d'agir de ton groupe local.",
onboarding_intent_newsletter_label: "S'abonner à notre Substack",
- onboarding_intent_newsletter_sub:
- "Actualités générales sur l'IA, livrées via notre newsletter Substack.",
+ onboarding_intent_newsletter_sub: "L'actualité de l'IA vue par l'équipe PauseAI.",
onboarding_intent_more_optional: 'Tu veux en faire plus ? (facultatif)',
onboarding_intent_act_now_label: 'Je veux juste agir maintenant',
onboarding_intent_act_now_sub: "Montre-moi ce que je peux faire aujourd'hui.",
onboarding_intent_volunteer_label: 'Je veux être bénévole régulièrement',
- onboarding_intent_volunteer_sub: 'Aide-moi à trouver un rôle qui me correspond.',
+ onboarding_intent_volunteer_sub:
+ 'Aide-moi à trouver un rôle qui me correspond. Ton groupe local te contactera.',
onboarding_intent_lead_label: 'Je veux prendre la tête',
- onboarding_intent_lead_sub: 'Je suis prêt·e à organiser dans mon pays ou ma région.',
+ onboarding_intent_lead_sub:
+ 'Je suis prêt·e à organiser dans mon pays ou ma région. Ton groupe local te contactera.',
onboarding_gdpr_consent:
- 'J\'accepte la Politique de confidentialité , y compris le partage de mes données avec mon chapitre PauseAI local (qui peut être une entité distincte de PauseAI Global) à des fins de coordination locale. *',
+ 'J\'accepte la Politique de confidentialité . Je comprends que mes données seront partagées avec mon groupe PauseAI local (qui peut être une entité distincte de PauseAI Global) à des fins de coordination locale. *',
onboarding_btn_submit: 'Envoyer →',
onboarding_btn_submitting: 'Envoi en cours...',
onboarding_btn_back: '← Retour',
onboarding_confirm_keep_informed:
- 'Nous te mettrons en relation avec ton chapitre PauseAI local et te tiendrons informé·e des campagnes mondiales.',
+ "Tu recevras les actualités des campagnes mondiales, ainsi que les nouvelles et les occasions d'agir de ton groupe local.",
onboarding_confirm_newsletter:
- "Tu es abonné·e à notre newsletter Substack pour les actualités générales sur l'IA.",
+ "Tu es abonné·e à notre Substack : l'actualité de l'IA vue par l'équipe PauseAI, par e-mail.",
onboarding_confirm_a_title: "C'est fait.",
onboarding_confirm_b_title: "C'est fait, merci de nous rejoindre.",
onboarding_confirm_b_sub:
@@ -660,10 +675,10 @@ const fr: OnboardingMessages = {
onboarding_browse_header_sub:
"Voici quelques actions que tu peux entreprendre dès maintenant. Le meilleur moyen de rester informé·e des nouvelles opportunités est de t'inscrire ci-dessous.",
onboarding_browse_signed_up:
- "✓ C'est fait. Nous te mettrons en relation avec ton chapitre PauseAI local et te tiendrons informé·e des campagnes mondiales.",
+ "✓ C'est fait. Tu recevras les actualités des campagnes mondiales, ainsi que les nouvelles et les occasions d'agir de ton groupe local.",
onboarding_browse_keep_informed_title: 'Me tenir informé·e',
onboarding_browse_keep_informed_sub:
- 'Me mettre en relation avec mon chapitre PauseAI local et me tenir informé·e des campagnes mondiales.',
+ "Reçois les actualités des campagnes mondiales, ainsi que les nouvelles et les occasions d'agir de ton groupe local.",
onboarding_btn_sign_me_up: "M'inscrire →",
onboarding_btn_signing_up: 'Inscription en cours...',
onboarding_section_ways_to_help: "Quelques moyens d'aider dès aujourd'hui",
@@ -720,9 +735,9 @@ const fr: OnboardingMessages = {
onboarding_lead_us_branch:
'Comme tu résides aux États-Unis , merci de postuler via PauseAI US : formulaire de candidature PauseAI US .',
onboarding_lead_has_chapter: (country) =>
- `${country} a déjà un chapitre PauseAI , donc plutôt que de fonder un groupe national, tu pourrais diriger un groupe régional ou local au sein de celui-ci. Trouve ton chapitre sur pauseai.info/communities , ou écris à notre Directrice de l'organisation ci-dessous pour en discuter.`,
+ `${country} a déjà un groupe PauseAI national , donc plutôt que d'en fonder un, tu pourrais diriger un groupe régional ou local au sein de celui-ci. Trouve-le sur pauseai.info/communities , ou écris à notre Directrice de l'organisation ci-dessous pour en discuter.`,
onboarding_lead_check_chapter:
- 'Vérifie d\'abord que ton pays n\'a pas déjà de chapitre : pauseai.info/communities .',
+ 'Vérifie d\'abord que ton pays n\'a pas déjà de groupe national : pauseai.info/communities .',
onboarding_lead_what_you_do_title: 'Ce que tu feras',
onboarding_lead_do_1:
'Recruter et développer ton groupe local en accueillant de nouveaux bénévoles et en organisant des événements ensemble.',
@@ -731,7 +746,7 @@ const fr: OnboardingMessages = {
onboarding_lead_do_3:
"Rencontrer chaque mois l'équipe Global de PauseAI pour échanger des idées et rester coordonné·e avec la stratégie mondiale et les campagnes trimestrielles.",
onboarding_lead_do_4:
- 'Partager les activités de ton chapitre sur les réseaux sociaux et aider à promouvoir les événements.',
+ 'Partager les activités de ton groupe sur les réseaux sociaux et aider à promouvoir les événements.',
onboarding_lead_looking_for_title: 'Ce que nous recherchons',
onboarding_lead_looking_1:
'Capacité à planifier et exécuter des actions directes, y compris des ateliers et des événements publics.',
diff --git a/src/lib/components/onboarding/options.ts b/src/lib/components/onboarding/options.ts
index b83a4e09d..d95404499 100644
--- a/src/lib/components/onboarding/options.ts
+++ b/src/lib/components/onboarding/options.ts
@@ -1,7 +1,10 @@
// Controlled vocabularies for the onboarding pipeline form.
import type { OnboardingMessages } from './messages'
-export const INTENTS = ['Keep informed', 'Act now', 'Volunteer', 'Lead'] as const
+// 'None' is what /join step 2 submits when nothing is picked; /subscribe keeps
+// writing 'Keep informed'. Each value must exist as an Airtable Intent select
+// option and in the CRM's member_intent vocabulary.
+export const INTENTS = ['None', 'Keep informed', 'Act now', 'Volunteer', 'Lead'] as const
export type Intent = (typeof INTENTS)[number]
export type IntentKey = 'act-now' | 'volunteer' | 'lead'