Adds a small paragraph directly below the tier list stating the Baby Ghosts Studio Development Fund charity status, noting that Canadian taxpayers can claim contributions, and that setup for receipts happens after joining. Styled in parallel to .solidarity-note (12px, --text-dim, 1.65 line-height) so it reads as a bullet, not a banner. Scope is /join only — /accept-invite and /member/account copy is untouched per spec §3.
1131 lines
30 KiB
Vue
1131 lines
30 KiB
Vue
<template>
|
||
<div>
|
||
<!-- HERO -->
|
||
<div class="hero">
|
||
<h1>Join Ghost Guild</h1>
|
||
<p>
|
||
Resources, events, and a community of people figuring it out. Everyone
|
||
gets everything. Pay what you can.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Already a member -->
|
||
<template v-if="isAuthenticated">
|
||
<div class="full-section">
|
||
<h2>You're already a member</h2>
|
||
<p class="section-intro">
|
||
Welcome back, {{ memberData?.name || "member" }}. You're part of Ghost
|
||
Guild in the
|
||
<span class="capitalize">{{
|
||
memberData?.circle || "community"
|
||
}}</span>
|
||
circle.
|
||
</p>
|
||
|
||
<div class="member-info-grid">
|
||
<DashedBox :hoverable="false">
|
||
<div class="section-label">Circle</div>
|
||
<div class="info-value capitalize">
|
||
{{ memberData?.circle || "Community" }}
|
||
</div>
|
||
</DashedBox>
|
||
<DashedBox :hoverable="false">
|
||
<div class="section-label">Contribution</div>
|
||
<div class="info-value">
|
||
${{ memberData?.contributionAmount ?? 0 }} CAD/month
|
||
</div>
|
||
</DashedBox>
|
||
</div>
|
||
|
||
<div class="button-row">
|
||
<NuxtLink to="/member/dashboard" class="form-submit"
|
||
>Go to Dashboard</NuxtLink
|
||
>
|
||
<NuxtLink to="/member/profile" class="btn">Edit Profile</NuxtLink>
|
||
</div>
|
||
</div>
|
||
|
||
<ParchmentInset>
|
||
<h2>Want to change your circle or contribution?</h2>
|
||
<p>
|
||
You can update your circle and adjust your monthly contribution at any
|
||
time from your profile settings.
|
||
</p>
|
||
<NuxtLink to="/member/profile" class="parchment-link"
|
||
>Update Membership Settings</NuxtLink
|
||
>
|
||
</ParchmentInset>
|
||
</template>
|
||
|
||
<!-- Not authenticated: show full join page -->
|
||
<template v-else>
|
||
<!-- CONTRIBUTION + SIGN UP (two columns) -->
|
||
<div class="join-two-col">
|
||
<!-- Left: Monthly Contribution -->
|
||
<div class="join-col">
|
||
<div class="section-label" style="margin-bottom: 12px">
|
||
{{ cadence === 'annual' ? 'Annual Contribution' : 'Monthly Contribution' }}
|
||
</div>
|
||
<h2>Pay what you can</h2>
|
||
<ul class="tier-list">
|
||
<li><span class="tier-amt">$0</span> I need support right now</li>
|
||
<li><span class="tier-amt">{{ formatContributionAmount(5) }}</span> I can contribute</li>
|
||
<li>
|
||
<span class="tier-amt">{{ formatContributionAmount(15) }}</span> I can sustain the community
|
||
(suggested)
|
||
</li>
|
||
<li><span class="tier-amt">{{ formatContributionAmount(30) }}</span> I can support others too</li>
|
||
<li>
|
||
<span class="tier-amt">{{ formatContributionAmount(50) }}</span> I want to sponsor multiple
|
||
members
|
||
</li>
|
||
</ul>
|
||
<p class="charity-note">
|
||
Baby Ghosts Studio Development Fund is a registered Canadian charity.
|
||
Members who file Canadian taxes can claim their contributions.
|
||
We'll help you set up tax receipts once you've joined.
|
||
</p>
|
||
<p class="solidarity-note">
|
||
Pay what you can. If you can pay more, you're making room for
|
||
someone who can't.
|
||
</p>
|
||
<p class="circle-not-sure">
|
||
Not sure where you fit? Start with Community. You can always move
|
||
later.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Right: Become a member -->
|
||
<div class="join-col">
|
||
<h2>Become a member</h2>
|
||
<p class="form-intro">
|
||
You'll get a magic link to confirm your email. No passwords.
|
||
</p>
|
||
|
||
<!-- Error Message -->
|
||
<div v-if="errorMessage" class="error-box">
|
||
{{ errorMessage }}
|
||
</div>
|
||
|
||
<form @submit.prevent="handleSubmit">
|
||
<div class="form-stack">
|
||
<div class="form-group">
|
||
<label class="form-label" for="join-name">Full Name</label>
|
||
<input
|
||
id="join-name"
|
||
v-model="form.name"
|
||
class="form-input"
|
||
type="text"
|
||
placeholder="Your name"
|
||
required
|
||
>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="join-email">Email Address</label>
|
||
<input
|
||
id="join-email"
|
||
v-model="form.email"
|
||
class="form-input"
|
||
type="email"
|
||
placeholder="you@example.com"
|
||
required
|
||
>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Circle</label>
|
||
<div class="circle-radios">
|
||
<div class="circle-radio community">
|
||
<input
|
||
id="circle-community"
|
||
v-model="form.circle"
|
||
type="radio"
|
||
name="circle"
|
||
value="community"
|
||
>
|
||
<label for="circle-community">
|
||
<span
|
||
class="circle-label-name"
|
||
style="color: var(--c-community)"
|
||
>Community</span
|
||
>
|
||
<span class="circle-label-desc">Exploring</span>
|
||
</label>
|
||
</div>
|
||
<div class="circle-radio founder">
|
||
<input
|
||
id="circle-founder"
|
||
v-model="form.circle"
|
||
type="radio"
|
||
name="circle"
|
||
value="founder"
|
||
>
|
||
<label for="circle-founder">
|
||
<span
|
||
class="circle-label-name"
|
||
style="color: var(--c-founder)"
|
||
>Founder</span
|
||
>
|
||
<span class="circle-label-desc">Building</span>
|
||
</label>
|
||
</div>
|
||
<div class="circle-radio practitioner">
|
||
<input
|
||
id="circle-practitioner"
|
||
v-model="form.circle"
|
||
type="radio"
|
||
name="circle"
|
||
value="practitioner"
|
||
>
|
||
<label for="circle-practitioner">
|
||
<span
|
||
class="circle-label-name"
|
||
style="color: var(--c-practitioner)"
|
||
>Practitioner</span
|
||
>
|
||
<span class="circle-label-desc">Practicing</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Billing Cadence</label>
|
||
<div class="cadence-radios">
|
||
<div class="circle-radio">
|
||
<input
|
||
id="cadence-monthly"
|
||
v-model="cadence"
|
||
type="radio"
|
||
name="cadence"
|
||
value="monthly"
|
||
>
|
||
<label for="cadence-monthly">
|
||
<span class="circle-label-name">Per Month</span>
|
||
</label>
|
||
</div>
|
||
<div class="circle-radio">
|
||
<input
|
||
id="cadence-annual"
|
||
v-model="cadence"
|
||
type="radio"
|
||
name="cadence"
|
||
value="annual"
|
||
>
|
||
<label for="cadence-annual">
|
||
<span class="circle-label-name">Per Year</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label" for="join-contribution">
|
||
Monthly Contribution
|
||
</label>
|
||
<div class="contribution-input-row">
|
||
<span class="contribution-currency">$</span>
|
||
<input
|
||
id="join-contribution"
|
||
v-model.number="form.contributionAmount"
|
||
type="number"
|
||
min="0"
|
||
step="1"
|
||
inputmode="numeric"
|
||
class="contribution-input"
|
||
>
|
||
</div>
|
||
<div class="contribution-presets" role="group" aria-label="Suggested amounts">
|
||
<button
|
||
v-for="preset in CONTRIBUTION_PRESETS"
|
||
:key="preset.amount"
|
||
type="button"
|
||
class="contribution-preset-chip"
|
||
@click="form.contributionAmount = preset.amount"
|
||
>
|
||
${{ preset.amount }}
|
||
</button>
|
||
</div>
|
||
<p v-if="guidanceLabel" class="contribution-guidance">{{ guidanceLabel }}</p>
|
||
</div>
|
||
<div v-if="form.contributionAmount > 0" class="form-group">
|
||
<div class="billing-summary">
|
||
<p class="billing-summary-line">
|
||
You'll be charged <strong>${{ firstCharge }} today</strong><span v-if="cadence === 'annual'"> (${{ form.contributionAmount }}/month × 12)</span>.
|
||
</p>
|
||
<p class="billing-summary-line">
|
||
Then <strong>${{ firstCharge }} every {{ cadence === 'annual' ? 'year' : 'month' }}</strong>, until you cancel.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="form-group full-width">
|
||
<label class="checkbox-label">
|
||
<input
|
||
v-model="form.agreedToGuidelines"
|
||
type="checkbox"
|
||
>
|
||
<span>
|
||
I agree to the Ghost Guild
|
||
<NuxtLink to="/community-guidelines" target="_blank"
|
||
>Community Guidelines</NuxtLink
|
||
>.
|
||
</span>
|
||
</label>
|
||
</div>
|
||
<div class="form-group">
|
||
<button
|
||
class="form-submit"
|
||
type="submit"
|
||
:disabled="!isFormValid || isSubmitting"
|
||
>
|
||
<span v-if="isSubmitting">Processing...</span>
|
||
<span v-else-if="needsPayment">Continue to Payment</span>
|
||
<span v-else>Become a Member</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<p class="form-note">
|
||
You can change your circle or contribution at any time from your
|
||
dashboard. Payment is handled securely through
|
||
<a href="https://www.helcim.com" target="_blank" rel="noopener"
|
||
>Helcim</a
|
||
>.
|
||
</p>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- HOW MEMBERSHIP WORKS -->
|
||
<ParchmentInset>
|
||
<h2>How membership works</h2>
|
||
<ul>
|
||
<li>Full access to the knowledge commons, Slack, and peer support</li>
|
||
<li>Free access to all Ghost Guild events</li>
|
||
<li>Equal access for every member, regardless of contribution</li>
|
||
<li>Your circle reflects where you are, not rank</li>
|
||
<li>Pay what you can ($0–$50+/month, separate from circle)</li>
|
||
<li>Higher contributions create solidarity spots for others</li>
|
||
</ul>
|
||
</ParchmentInset>
|
||
|
||
<!-- THREE CIRCLES -->
|
||
<div class="content-row">
|
||
<div class="content-block">
|
||
<div class="section-label" style="color: var(--c-community)">
|
||
Community
|
||
</div>
|
||
<h2>Exploring</h2>
|
||
<p>
|
||
For game workers curious about cooperatives and people exploring
|
||
alternative work models. You might be a solo developer, a student, a
|
||
researcher, or just someone who heard about this and wants to know
|
||
more. Start here.
|
||
</p>
|
||
</div>
|
||
<div class="content-block">
|
||
<div class="section-label" style="color: var(--c-founder)">
|
||
Founder
|
||
</div>
|
||
<h2>Building</h2>
|
||
<p>
|
||
For people actively building cooperative studios. You have a team,
|
||
or you are forming one. You are working through governance, legal
|
||
structure, revenue sharing, and all the hard parts. You want
|
||
structured support and peers doing the same thing.
|
||
</p>
|
||
</div>
|
||
<div class="content-block">
|
||
<div class="section-label" style="color: var(--c-practitioner)">
|
||
Practitioner
|
||
</div>
|
||
<h2>Practicing</h2>
|
||
<p>
|
||
For those already running cooperative studios or with deep
|
||
experience in cooperative practice. You are here to teach, advise,
|
||
mentor, and help shape the program itself. Alumni.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
</template>
|
||
|
||
<!-- Flow overlay: covers the page from form submit through redirect.
|
||
Lives outside v-if/v-else so it survives the auth state flip that
|
||
fires after checkMemberStatus() at the end of createSubscription. -->
|
||
<SignupFlowOverlay
|
||
:state="flowState"
|
||
:summary="flowSummary"
|
||
:error-message="errorMessage"
|
||
@close="closeFlowOverlay"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { reactive, ref, computed, onMounted, onUnmounted } from "vue";
|
||
import { getCircleOptions } from "~/config/circles";
|
||
import {
|
||
requiresPayment,
|
||
CONTRIBUTION_PRESETS,
|
||
getGuidanceLabel,
|
||
} from "~/config/contributions";
|
||
|
||
// Auth state
|
||
const { isAuthenticated, memberData, checkMemberStatus } = useAuth();
|
||
|
||
// Check authentication status on mount
|
||
onMounted(async () => {
|
||
await checkMemberStatus();
|
||
});
|
||
|
||
// Form state
|
||
const form = reactive({
|
||
email: "",
|
||
name: "",
|
||
circle: "community",
|
||
contributionAmount: 15,
|
||
agreedToGuidelines: false,
|
||
billingAddress: {
|
||
street: "",
|
||
city: "",
|
||
province: "",
|
||
postalCode: "",
|
||
country: "CA",
|
||
},
|
||
});
|
||
|
||
// UI state
|
||
const isSubmitting = ref(false);
|
||
const errorMessage = ref("");
|
||
const successMessage = ref("");
|
||
const cadence = ref("monthly"); // 'monthly' | 'annual'
|
||
|
||
// Flow overlay state — drives the post-submit full-viewport UI.
|
||
// 'idle' = overlay hidden; user is editing the form.
|
||
// 'creating-customer' | 'opening-payment' | 'processing-payment'
|
||
// | 'creating-subscription' = progress states, overlay shows a spinner + label.
|
||
// 'success' = overlay shows confirmation, auto-redirect is queued.
|
||
// 'error' = overlay shows error + Retry/Back buttons.
|
||
const flowState = ref("idle");
|
||
|
||
// Helcim state
|
||
const customerId = ref(null);
|
||
const customerCode = ref(null);
|
||
const subscriptionData = ref(null);
|
||
const paymentToken = ref(null);
|
||
|
||
// Circle options from central config
|
||
const circleOptions = getCircleOptions();
|
||
|
||
const formatContributionAmount = (amount) => {
|
||
if (!amount || amount === 0) return "$0";
|
||
const display = cadence.value === "annual" ? amount * 12 : amount;
|
||
const suffix = cadence.value === "annual" ? "/yr" : "/mo";
|
||
return `$${display}${suffix}`;
|
||
};
|
||
|
||
// Initialize composables
|
||
const {
|
||
initializeHelcimPay,
|
||
verifyPayment,
|
||
cleanup: cleanupHelcimPay,
|
||
} = useHelcimPay();
|
||
|
||
// Form validation
|
||
const isFormValid = computed(() => {
|
||
return (
|
||
form.name &&
|
||
form.email &&
|
||
form.circle &&
|
||
Number.isInteger(form.contributionAmount) && form.contributionAmount >= 0 &&
|
||
form.agreedToGuidelines
|
||
);
|
||
});
|
||
|
||
// Check if payment is required
|
||
const needsPayment = computed(() => {
|
||
return requiresPayment(form.contributionAmount);
|
||
});
|
||
|
||
const guidanceLabel = computed(() => getGuidanceLabel(form.contributionAmount));
|
||
|
||
const firstCharge = computed(() => {
|
||
const amount = form.contributionAmount || 0;
|
||
return cadence.value === "annual" ? amount * 12 : amount;
|
||
});
|
||
|
||
const flowSummary = computed(() => ({
|
||
name: form.name,
|
||
email: form.email,
|
||
circle: form.circle,
|
||
contribution: formatContributionAmount(form.contributionAmount),
|
||
}));
|
||
|
||
const handleSubmit = async () => {
|
||
if (isSubmitting.value || !isFormValid.value) return;
|
||
|
||
isSubmitting.value = true;
|
||
errorMessage.value = "";
|
||
flowState.value = "creating-customer";
|
||
|
||
try {
|
||
// Create customer
|
||
const response = await $fetch("/api/helcim/customer", {
|
||
method: "POST",
|
||
body: {
|
||
name: form.name,
|
||
email: form.email,
|
||
circle: form.circle,
|
||
contributionAmount: form.contributionAmount,
|
||
agreedToGuidelines: form.agreedToGuidelines,
|
||
billingAddress: form.billingAddress,
|
||
},
|
||
});
|
||
|
||
if (!response.success) {
|
||
throw new Error("Failed to create account.");
|
||
}
|
||
|
||
customerId.value = response.customerId;
|
||
customerCode.value = response.customerCode;
|
||
|
||
// Free tier: no Helcim modal, go straight to subscription.
|
||
if (!needsPayment.value) {
|
||
flowState.value = "creating-subscription";
|
||
await createSubscription();
|
||
return;
|
||
}
|
||
|
||
// Paid tier: initialize HelcimPay session, then auto-open modal.
|
||
flowState.value = "opening-payment";
|
||
await initializeHelcimPay(customerId.value, customerCode.value, 0);
|
||
|
||
const paymentResult = await verifyPayment();
|
||
if (!paymentResult?.success) {
|
||
throw new Error("Payment was not completed.");
|
||
}
|
||
paymentToken.value = paymentResult.cardToken;
|
||
|
||
flowState.value = "processing-payment";
|
||
await $fetch("/api/helcim/verify-payment", {
|
||
method: "POST",
|
||
body: {
|
||
cardToken: paymentResult.cardToken,
|
||
customerId: customerId.value,
|
||
},
|
||
});
|
||
|
||
flowState.value = "creating-subscription";
|
||
const subscriptionResult = await createSubscription(
|
||
paymentResult.cardToken,
|
||
);
|
||
|
||
if (!subscriptionResult || subscriptionResult.success === false) {
|
||
// Payment succeeded but subscription couldn't be created.
|
||
// Keep overlay in success state; admin follow-up will reconcile.
|
||
successMessage.value =
|
||
"Payment successful. Subscription setup may need manual completion.";
|
||
flowState.value = "success";
|
||
}
|
||
} catch (error) {
|
||
console.error("Join flow error:", error);
|
||
errorMessage.value =
|
||
error.data?.message ||
|
||
error.message ||
|
||
"Something went wrong. Please try again.";
|
||
flowState.value = "error";
|
||
} finally {
|
||
isSubmitting.value = false;
|
||
}
|
||
};
|
||
|
||
// Create subscription
|
||
const createSubscription = async (cardToken = null) => {
|
||
try {
|
||
const response = await $fetch("/api/helcim/subscription", {
|
||
method: "POST",
|
||
body: {
|
||
customerId: customerId.value,
|
||
customerCode: customerCode.value,
|
||
contributionAmount: form.contributionAmount,
|
||
cadence: cadence.value,
|
||
cardToken: cardToken,
|
||
},
|
||
});
|
||
|
||
if (response.success) {
|
||
subscriptionData.value = response.subscription;
|
||
flowState.value = "success";
|
||
successMessage.value = "Your membership is active.";
|
||
|
||
// Check member status to ensure user is properly authenticated
|
||
await checkMemberStatus();
|
||
|
||
navigateTo("/welcome");
|
||
} else {
|
||
throw new Error("Subscription creation failed - response not successful");
|
||
}
|
||
} catch (error) {
|
||
console.error("Subscription creation error:", error);
|
||
console.error("Error details:", {
|
||
message: error.message,
|
||
statusCode: error.statusCode,
|
||
statusMessage: error.statusMessage,
|
||
data: error.data,
|
||
});
|
||
console.error(
|
||
"Subscription creation completely failed, but payment was successful",
|
||
);
|
||
// Don't throw error - let the calling function handle progression
|
||
return {
|
||
success: false,
|
||
error:
|
||
error.data?.message || error.message || "Failed to create subscription",
|
||
};
|
||
}
|
||
};
|
||
|
||
const closeFlowOverlay = () => {
|
||
flowState.value = "idle";
|
||
errorMessage.value = "";
|
||
};
|
||
|
||
// Cleanup on unmount
|
||
onUnmounted(() => {
|
||
cleanupHelcimPay();
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* ---- HERO ---- */
|
||
.hero {
|
||
padding: 48px 32px;
|
||
border-bottom: 1px dashed var(--border);
|
||
}
|
||
.hero h1 {
|
||
font-family: "Brygada 1918", serif;
|
||
font-size: 36px;
|
||
font-weight: 600;
|
||
color: var(--text-bright);
|
||
line-height: 1.15;
|
||
letter-spacing: -0.01em;
|
||
margin-bottom: 16px;
|
||
max-width: 540px;
|
||
}
|
||
.hero p {
|
||
color: var(--text-dim);
|
||
max-width: 460px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
/* ---- PARCHMENT LIST STYLES ---- */
|
||
:deep(.parchment-inset ul) {
|
||
list-style: none;
|
||
max-width: 560px;
|
||
padding: 0;
|
||
}
|
||
:deep(.parchment-inset ul li) {
|
||
font-size: 13px;
|
||
color: var(--parch-text-dim);
|
||
line-height: 1.75;
|
||
padding: 4px 0;
|
||
padding-left: 16px;
|
||
position: relative;
|
||
}
|
||
:deep(.parchment-inset ul li::before) {
|
||
content: "›";
|
||
position: absolute;
|
||
left: 0;
|
||
color: var(--candle-faint);
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.parchment-link {
|
||
color: var(--candle-faint);
|
||
font-size: 12px;
|
||
}
|
||
.parchment-link:hover {
|
||
color: var(--candle-dim);
|
||
}
|
||
|
||
/* ---- CONTENT ROW (three circles) ---- */
|
||
.content-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
align-items: stretch;
|
||
border-bottom: 1px dashed var(--border);
|
||
}
|
||
.content-block {
|
||
padding: 24px 28px;
|
||
border-right: 1px dashed var(--border);
|
||
min-width: 0;
|
||
overflow-wrap: break-word;
|
||
align-self: stretch;
|
||
}
|
||
.content-block:last-child {
|
||
border-right: none;
|
||
}
|
||
.content-block h2 {
|
||
font-family: "Brygada 1918", serif;
|
||
font-size: 18px;
|
||
font-weight: 500;
|
||
color: var(--text-bright);
|
||
margin-bottom: 8px;
|
||
}
|
||
.content-block p {
|
||
color: var(--text-dim);
|
||
font-size: 12px;
|
||
line-height: 1.65;
|
||
}
|
||
.circle-not-sure {
|
||
font-size: 11px;
|
||
color: var(--text-faint);
|
||
margin-top: 10px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* ---- TWO-COLUMN JOIN LAYOUT ---- */
|
||
.join-two-col {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
border-bottom: 1px dashed var(--border);
|
||
}
|
||
.join-col {
|
||
padding: 32px;
|
||
}
|
||
.join-col:first-child {
|
||
border-right: 1px dashed var(--border);
|
||
}
|
||
.join-col h2 {
|
||
font-family: "Brygada 1918", serif;
|
||
font-size: 20px;
|
||
font-weight: 500;
|
||
color: var(--text-bright);
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
/* ---- FULL-WIDTH SECTION ---- */
|
||
.full-section {
|
||
padding: 32px;
|
||
border-bottom: 1px dashed var(--border);
|
||
}
|
||
.full-section h2 {
|
||
font-family: "Brygada 1918", serif;
|
||
font-size: 20px;
|
||
font-weight: 500;
|
||
color: var(--text-bright);
|
||
margin-bottom: 16px;
|
||
}
|
||
.section-intro {
|
||
font-size: 13px;
|
||
color: var(--text-dim);
|
||
line-height: 1.65;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
/* ---- TIER LIST (matches about page) ---- */
|
||
.tier-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
.tier-list li {
|
||
padding: 5px 0;
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
border-bottom: 1px dashed var(--border);
|
||
display: flex;
|
||
gap: 12px;
|
||
}
|
||
.tier-list li:last-child {
|
||
border-bottom: none;
|
||
}
|
||
.tier-amt {
|
||
color: var(--text-bright);
|
||
font-weight: 600;
|
||
min-width: 36px;
|
||
}
|
||
.solidarity-note {
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
line-height: 1.65;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.charity-note {
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
line-height: 1.65;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
/* ---- FORM SECTION ---- */
|
||
.form-section {
|
||
padding: 32px;
|
||
border-bottom: 1px dashed var(--border);
|
||
}
|
||
.form-section h2 {
|
||
font-family: "Brygada 1918", serif;
|
||
font-size: 20px;
|
||
font-weight: 500;
|
||
color: var(--text-bright);
|
||
margin-bottom: 4px;
|
||
}
|
||
.form-intro {
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
margin-bottom: 24px;
|
||
line-height: 1.65;
|
||
}
|
||
.form-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
max-width: 600px;
|
||
}
|
||
.form-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.form-label {
|
||
font-size: 10px;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--text-faint);
|
||
}
|
||
.form-input {
|
||
background: var(--surface);
|
||
border: 1px dashed var(--border);
|
||
color: var(--text-bright);
|
||
font-family: "Commit Mono", monospace;
|
||
font-size: 13px;
|
||
padding: 10px 14px;
|
||
transition: border-color 0.2s;
|
||
outline: none;
|
||
width: 100%;
|
||
}
|
||
.form-input:focus {
|
||
border-color: var(--candle-dim);
|
||
border-style: solid;
|
||
}
|
||
.form-input::placeholder {
|
||
color: var(--text-faint);
|
||
}
|
||
|
||
/* ---- CADENCE RADIOS ---- */
|
||
.cadence-radios {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 10px;
|
||
}
|
||
|
||
/* ---- CONTRIBUTION AMOUNT INPUT + CHIPS ---- */
|
||
.contribution-input-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
}
|
||
.contribution-currency {
|
||
font-weight: 600;
|
||
}
|
||
.contribution-input {
|
||
flex: 1;
|
||
padding: 0.5rem 0.75rem;
|
||
background: var(--input-bg);
|
||
border: 1px solid var(--parch);
|
||
font-family: 'Commit Mono', monospace;
|
||
font-size: 1rem;
|
||
}
|
||
.contribution-input:focus {
|
||
outline: none;
|
||
border-color: var(--candle);
|
||
}
|
||
.contribution-presets {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
margin-top: 0.5rem;
|
||
}
|
||
.contribution-preset-chip {
|
||
padding: 0.25rem 0.75rem;
|
||
background: transparent;
|
||
border: 1px dashed var(--parch);
|
||
font-family: 'Commit Mono', monospace;
|
||
font-size: 0.875rem;
|
||
cursor: pointer;
|
||
}
|
||
.contribution-preset-chip:hover {
|
||
border-style: solid;
|
||
border-color: var(--candle);
|
||
}
|
||
.contribution-guidance {
|
||
margin-top: 0.5rem;
|
||
font-size: 0.875rem;
|
||
font-style: italic;
|
||
color: var(--ink-soft, currentColor);
|
||
}
|
||
|
||
/* ---- BILLING SUMMARY ---- */
|
||
.billing-summary {
|
||
padding: 12px 16px;
|
||
border: 1px dashed var(--border);
|
||
background: var(--surface);
|
||
}
|
||
.billing-summary-line {
|
||
font-size: 13px;
|
||
color: var(--text);
|
||
line-height: 1.5;
|
||
margin: 0;
|
||
}
|
||
.billing-summary-line + .billing-summary-line {
|
||
margin-top: 4px;
|
||
}
|
||
.billing-summary-line strong {
|
||
color: var(--text-bright);
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* ---- CIRCLE RADIOS ---- */
|
||
.circle-radios {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 10px;
|
||
}
|
||
.circle-radio {
|
||
position: relative;
|
||
}
|
||
.circle-radio input {
|
||
position: absolute;
|
||
opacity: 0;
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
.circle-radio label {
|
||
display: block;
|
||
border: 1px dashed var(--border);
|
||
padding: 14px 12px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
text-align: center;
|
||
}
|
||
.circle-radio label:hover {
|
||
border-color: var(--candle-faint);
|
||
}
|
||
.circle-radio input:checked + label {
|
||
border-style: solid;
|
||
}
|
||
.circle-radio input:checked + label .circle-label-name {
|
||
color: var(--text-bright);
|
||
}
|
||
.circle-radio.community input:checked + label {
|
||
border-color: var(--c-community);
|
||
}
|
||
.circle-radio.founder input:checked + label {
|
||
border-color: var(--c-founder);
|
||
}
|
||
.circle-radio.practitioner input:checked + label {
|
||
border-color: var(--c-practitioner);
|
||
}
|
||
.circle-label-name {
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
display: block;
|
||
margin-bottom: 2px;
|
||
}
|
||
.circle-label-desc {
|
||
font-size: 10px;
|
||
color: var(--text-faint);
|
||
}
|
||
|
||
/* ---- CONTRIBUTION SELECT ---- */
|
||
.form-select {
|
||
background: var(--surface);
|
||
border: 1px dashed var(--border);
|
||
color: var(--text-bright);
|
||
font-family: "Commit Mono", monospace;
|
||
font-size: 13px;
|
||
padding: 10px 14px;
|
||
transition: border-color 0.2s;
|
||
outline: none;
|
||
width: 100%;
|
||
appearance: none;
|
||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238a7e6a' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
|
||
background-repeat: no-repeat;
|
||
background-position: right 12px center;
|
||
cursor: pointer;
|
||
}
|
||
.form-select:focus {
|
||
border-color: var(--candle-dim);
|
||
border-style: solid;
|
||
}
|
||
.form-select option {
|
||
background: var(--surface);
|
||
color: var(--text-bright);
|
||
}
|
||
|
||
/* ---- SUBMIT BUTTON ---- */
|
||
.form-submit {
|
||
display: inline-block;
|
||
background: var(--parch);
|
||
color: var(--parch-accent);
|
||
font-family: "Commit Mono", monospace;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.02em;
|
||
border: 1px solid var(--parch);
|
||
padding: 12px 28px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
text-decoration: none;
|
||
text-align: center;
|
||
}
|
||
.form-submit:hover {
|
||
background: var(--parch-hover);
|
||
border-color: var(--parch-hover);
|
||
color: var(--parch-text);
|
||
text-decoration: none;
|
||
}
|
||
.form-submit:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* ---- FORM NOTE ---- */
|
||
.form-note {
|
||
font-size: 11px;
|
||
color: var(--text-faint);
|
||
line-height: 1.6;
|
||
margin-top: 16px;
|
||
max-width: 460px;
|
||
}
|
||
.form-note a,
|
||
.form-note :deep(a) {
|
||
color: var(--candle-dim);
|
||
}
|
||
|
||
/* ---- CHECKBOX ---- */
|
||
.checkbox-label {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
line-height: 1.5;
|
||
}
|
||
.checkbox-label input {
|
||
margin-top: 3px;
|
||
flex-shrink: 0;
|
||
}
|
||
.checkbox-label a,
|
||
.checkbox-label :deep(a) {
|
||
color: var(--candle);
|
||
}
|
||
|
||
/* ---- ERROR & SUCCESS BOXES ---- */
|
||
.error-box {
|
||
border: 1px dashed var(--ember);
|
||
color: var(--ember);
|
||
padding: 12px 16px;
|
||
font-size: 12px;
|
||
margin-bottom: 20px;
|
||
max-width: 600px;
|
||
}
|
||
.success-box {
|
||
border: 1px dashed var(--green, var(--candle));
|
||
color: var(--green, var(--candle));
|
||
padding: 12px 16px;
|
||
font-size: 12px;
|
||
margin-bottom: 20px;
|
||
max-width: 600px;
|
||
}
|
||
|
||
/* ---- PAYMENT INSTRUCTION ---- */
|
||
.payment-instruction {
|
||
font-size: 13px;
|
||
color: var(--text-dim);
|
||
line-height: 1.65;
|
||
}
|
||
|
||
/* ---- REDIRECT NOTE ---- */
|
||
.redirect-note {
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
text-align: center;
|
||
}
|
||
|
||
/* ---- BUTTON ROW ---- */
|
||
.button-row {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
/* ---- MEMBER INFO GRID ---- */
|
||
.member-info-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 16px;
|
||
max-width: 500px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.info-value {
|
||
font-family: "Brygada 1918", serif;
|
||
font-size: 18px;
|
||
font-weight: 500;
|
||
color: var(--text-bright);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
/* ---- UTILITY ---- */
|
||
.capitalize {
|
||
text-transform: capitalize;
|
||
}
|
||
|
||
/* ---- RESPONSIVE ---- */
|
||
@media (max-width: 768px) {
|
||
.content-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.content-block {
|
||
border-right: none;
|
||
border-bottom: 1px dashed var(--border);
|
||
}
|
||
.content-block:last-child {
|
||
border-bottom: none;
|
||
}
|
||
.join-two-col {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.join-col:first-child {
|
||
border-right: none;
|
||
border-bottom: 1px dashed var(--border);
|
||
}
|
||
.circle-radios {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.member-info-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.hero {
|
||
padding: 32px 20px;
|
||
}
|
||
.hero h1 {
|
||
font-size: 28px;
|
||
}
|
||
.full-section,
|
||
.form-section {
|
||
padding: 24px 20px;
|
||
}
|
||
.content-block {
|
||
padding: 20px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.button-row {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
}
|
||
|
||
</style>
|