Member/Ecology revamp.
Some checks failed
Test / vitest (push) Failing after 7m23s
Test / playwright (push) Has been skipped
Test / visual (push) Has been skipped
Test / Notify on failure (push) Successful in 2s

This commit is contained in:
Jennie Robinson Faber 2026-04-14 09:25:09 +01:00
parent fc7ec52574
commit 59d6e97787
31 changed files with 1763 additions and 1010 deletions

View file

@ -3,7 +3,10 @@
<!-- 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>
<p>
Resources, events, and a community of people figuring it out. Everyone
gets everything. Pay what you can.
</p>
</div>
<!-- Already a member -->
@ -11,31 +14,46 @@
<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.
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>
<div class="info-value capitalize">
{{ memberData?.circle || "Community" }}
</div>
</DashedBox>
<DashedBox :hoverable="false">
<div class="section-label">Contribution</div>
<div class="info-value">${{ memberData?.contributionTier || '0' }} CAD/month</div>
<div class="info-value">
${{ memberData?.contributionTier || "0" }} CAD/month
</div>
</DashedBox>
</div>
<div class="button-row">
<NuxtLink to="/member/dashboard" class="form-submit">Go to Dashboard</NuxtLink>
<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>
<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>
@ -45,183 +63,237 @@
<ParchmentInset>
<h2>How membership works</h2>
<ul>
<li>Full access to the knowledge commons, events, Slack community, and peer support</li>
<li>
Full access to the knowledge commons, events, Slack community, and
peer support
</li>
<li>One member, one vote in all decisions</li>
<li>Your circle is where you are in your journey, not rank</li>
<li>Your contribution is what you can afford ($0--50+/month, separate from your circle)</li>
<li>Higher contributions create solidarity spots for those who need them</li>
<li>
Your contribution is what you can afford ($0--50+/month, separate
from your circle)
</li>
<li>
Higher contributions create solidarity spots for those who need them
</li>
</ul>
</ParchmentInset>
<!-- THREE CIRCLES -->
<div class="content-row">
<div class="content-block">
<div class="section-label" style="color: var(--c-community);">Community</div>
<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>
<p class="circle-not-sure">Not sure where you fit? Start with Community. You can always move later.</p>
<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>
<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>
<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>
<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 business. You are here to teach, advise, mentor, and help shape the program itself. Peer Accelerator alumni land here.</p>
<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>
<!-- CONTRIBUTION TIERS -->
<div class="full-section">
<div class="section-label" style="margin-bottom: 12px;">Monthly Contribution</div>
<h2>Pay what you can</h2>
<div class="tier-row">
<div class="tier-card">
<div class="tier-amount">$0</div>
<div class="tier-freq">/month</div>
<div class="tier-desc">Access is a right</div>
<!-- CONTRIBUTION + SIGN UP (two columns) -->
<div v-if="currentStep === 1" class="join-two-col">
<!-- Left: Monthly Contribution -->
<div class="join-col">
<div class="section-label" style="margin-bottom: 12px">
Monthly Contribution
</div>
<div class="tier-card">
<div class="tier-amount">$5</div>
<div class="tier-freq">/month</div>
<div class="tier-desc">A small gesture</div>
</div>
<div class="tier-card suggested">
<div class="tier-amount">$15</div>
<div class="tier-freq">/month</div>
<div class="tier-desc">Sustaining</div>
<div class="tier-badge">suggested</div>
</div>
<div class="tier-card">
<div class="tier-amount">$30</div>
<div class="tier-freq">/month</div>
<div class="tier-desc">Supporting</div>
</div>
<div class="tier-card">
<div class="tier-amount">$50</div>
<div class="tier-freq">/month</div>
<div class="tier-desc">Solidarity</div>
</div>
</div>
<p class="solidarity-note">Every dollar above $0 goes to the Solidarity Fund, which covers membership for people who need it. Higher tiers directly sponsor other members. Your contribution is never a gate -- it is a gift.</p>
</div>
<!-- SIGN UP FORM -->
<div v-if="currentStep === 1" class="form-section">
<h2>Become a member</h2>
<p class="form-intro">We will send you a magic link to confirm your email. No passwords, no fuss.</p>
<!-- Error Message -->
<div v-if="errorMessage" class="error-box">
{{ errorMessage }}
<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">$5</span> I can contribute</li>
<li>
<span class="tier-amt">$15</span> I can sustain the community
(suggested)
</li>
<li><span class="tier-amt">$30</span> I can support others too</li>
<li>
<span class="tier-amt">$50</span> I want to sponsor multiple
members
</li>
</ul>
<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>
<form @submit.prevent="handleSubmit">
<div class="form-grid">
<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 full-width">
<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>
<!-- 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" for="join-contribution"
>Monthly Contribution</label
>
<select
id="join-contribution"
v-model="form.contributionTier"
class="form-select"
>
<option value="0">$0/mo -- I need support right now</option>
<option value="5">$5/mo -- I can contribute</option>
<option value="15">
$15/mo -- I can sustain the community (suggested)
</option>
<option value="30">$30/mo -- I can support others too</option>
<option value="50">
$50/mo -- I want to sponsor multiple members
</option>
</select>
</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>
<div class="form-group full-width">
<label class="form-label" for="join-contribution">Monthly Contribution</label>
<select
id="join-contribution"
v-model="form.contributionTier"
class="form-select"
>
<option value="0">$0/mo -- Access is a right</option>
<option value="5">$5/mo -- A small gesture</option>
<option value="15">$15/mo -- Sustaining (suggested)</option>
<option value="30">$30/mo -- Supporting</option>
<option value="50">$50/mo -- Solidarity</option>
</select>
</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">By joining you agree to our <NuxtLink to="/guidelines">community guidelines</NuxtLink>. 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>
<p class="form-note">
By joining you agree to our
<NuxtLink to="/guidelines">community guidelines</NuxtLink>. 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>
<!-- Step 2: Payment -->
<div v-if="currentStep === 2" class="form-section">
<h2>Payment Information</h2>
<p class="form-intro">
You're signing up for the {{ selectedTier.label }} plan --
${{ selectedTier.amount }} CAD / month
You're signing up for the {{ selectedTier.label }} plan -- ${{
selectedTier.amount
}}
CAD / month
</p>
<!-- Error Message -->
@ -230,15 +302,14 @@
</div>
<DashedBox :hoverable="false">
<p class="payment-instruction">Click "Complete Payment" below to open the secure payment modal and verify your payment method.</p>
<p class="payment-instruction">
Click "Complete Payment" below to open the secure payment modal and
verify your payment method.
</p>
</DashedBox>
<div class="button-row" style="margin-top: 24px;">
<button
class="btn"
:disabled="isSubmitting"
@click="goBack"
>
<div class="button-row" style="margin-top: 24px">
<button class="btn" :disabled="isSubmitting" @click="goBack">
Back
</button>
<button
@ -261,7 +332,9 @@
</div>
<DashedBox :hoverable="false">
<div class="section-label" style="margin-bottom: 12px;">Membership Details</div>
<div class="section-label" style="margin-bottom: 12px">
Membership Details
</div>
<dl class="details-list">
<div class="details-row">
<dt>Name</dt>
@ -286,17 +359,25 @@
</dl>
</DashedBox>
<p class="form-note" style="margin-top: 20px;">
We've sent a confirmation email to {{ form.email }} with your membership details.
<p class="form-note" style="margin-top: 20px">
We've sent a confirmation email to {{ form.email }} with your
membership details.
</p>
<DashedBox :hoverable="false" style="margin-top: 16px;">
<p class="redirect-note">You will be automatically redirected to your dashboard in a few seconds...</p>
<DashedBox :hoverable="false" style="margin-top: 16px">
<p class="redirect-note">
You will be automatically redirected to your dashboard in a few
seconds...
</p>
</DashedBox>
<div class="button-row" style="margin-top: 24px;">
<NuxtLink to="/member/dashboard" class="form-submit">Go to Dashboard Now</NuxtLink>
<button class="btn" @click="resetForm">Register Another Member</button>
<div class="button-row" style="margin-top: 24px">
<NuxtLink to="/member/dashboard" class="form-submit"
>Go to Dashboard Now</NuxtLink
>
<button class="btn" @click="resetForm">
Register Another Member
</button>
</div>
</div>
</template>
@ -492,7 +573,7 @@ const createSubscription = async (cardToken = null) => {
if (response.success) {
subscriptionData.value = response.subscription;
currentStep.value = 3;
successMessage.value = "Your membership has been activated successfully!";
successMessage.value = "Your membership is active.";
// Check member status to ensure user is properly authenticated
await checkMemberStatus();
@ -560,7 +641,7 @@ onUnmounted(() => {
border-bottom: 1px dashed var(--border);
}
.hero h1 {
font-family: 'Brygada 1918', serif;
font-family: "Brygada 1918", serif;
font-size: 36px;
font-weight: 600;
color: var(--text-bright);
@ -623,7 +704,7 @@ onUnmounted(() => {
border-right: none;
}
.content-block h2 {
font-family: 'Brygada 1918', serif;
font-family: "Brygada 1918", serif;
font-size: 18px;
font-weight: 500;
color: var(--text-bright);
@ -641,13 +722,33 @@ onUnmounted(() => {
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-family: "Brygada 1918", serif;
font-size: 20px;
font-weight: 500;
color: var(--text-bright);
@ -660,65 +761,32 @@ onUnmounted(() => {
margin-bottom: 20px;
}
/* ---- TIER CARDS ---- */
.tier-row {
display: grid;
grid-template-columns: repeat(5, 1fr);
/* ---- 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;
margin: 16px 0;
}
.tier-card {
border: 1px dashed var(--border);
padding: 20px 16px;
text-align: center;
transition: border-color 0.2s;
.tier-list li:last-child {
border-bottom: none;
}
.tier-card:hover {
border-color: var(--candle-faint);
}
.tier-card.suggested {
border-color: var(--candle-dim);
border-style: solid;
}
.tier-card.suggested:hover {
border-color: var(--candle);
}
.tier-amount {
font-family: 'Brygada 1918', serif;
font-size: 22px;
font-weight: 600;
.tier-amt {
color: var(--text-bright);
margin-bottom: 2px;
}
.tier-card.suggested .tier-amount {
color: var(--candle);
}
.tier-freq {
font-size: 11px;
color: var(--text-faint);
margin-bottom: 10px;
}
.tier-desc {
font-size: 11px;
color: var(--text-faint);
line-height: 1.5;
}
.tier-badge {
display: inline-block;
font-size: 9px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--candle-dim);
border: 1px dashed var(--candle-faint);
padding: 1px 6px;
margin-top: 8px;
font-weight: 600;
min-width: 36px;
}
.solidarity-note {
font-size: 12px;
color: var(--text-dim);
line-height: 1.65;
margin-top: 16px;
max-width: 560px;
}
/* ---- FORM SECTION ---- */
@ -727,7 +795,7 @@ onUnmounted(() => {
border-bottom: 1px dashed var(--border);
}
.form-section h2 {
font-family: 'Brygada 1918', serif;
font-family: "Brygada 1918", serif;
font-size: 20px;
font-weight: 500;
color: var(--text-bright);
@ -739,9 +807,9 @@ onUnmounted(() => {
margin-bottom: 24px;
line-height: 1.65;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
.form-stack {
display: flex;
flex-direction: column;
gap: 20px;
max-width: 600px;
}
@ -750,9 +818,6 @@ onUnmounted(() => {
flex-direction: column;
gap: 6px;
}
.form-group.full-width {
grid-column: 1 / -1;
}
.form-label {
font-size: 10px;
letter-spacing: 0.1em;
@ -763,7 +828,7 @@ onUnmounted(() => {
background: var(--surface);
border: 1px dashed var(--border);
color: var(--text-bright);
font-family: 'Commit Mono', monospace;
font-family: "Commit Mono", monospace;
font-size: 13px;
padding: 10px 14px;
transition: border-color 0.2s;
@ -835,7 +900,7 @@ onUnmounted(() => {
background: var(--surface);
border: 1px dashed var(--border);
color: var(--text-bright);
font-family: 'Commit Mono', monospace;
font-family: "Commit Mono", monospace;
font-size: 13px;
padding: 10px 14px;
transition: border-color 0.2s;
@ -861,7 +926,7 @@ onUnmounted(() => {
display: inline-block;
background: var(--parch);
color: var(--parch-accent);
font-family: 'Commit Mono', monospace;
font-family: "Commit Mono", monospace;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
@ -965,7 +1030,7 @@ onUnmounted(() => {
margin-bottom: 8px;
}
.info-value {
font-family: 'Brygada 1918', serif;
font-family: "Brygada 1918", serif;
font-size: 18px;
font-weight: 500;
color: var(--text-bright);
@ -989,14 +1054,12 @@ onUnmounted(() => {
.content-block:last-child {
border-bottom: none;
}
.tier-row {
grid-template-columns: repeat(3, 1fr);
}
.form-grid {
.join-two-col {
grid-template-columns: 1fr;
}
.form-group.full-width {
grid-column: auto;
.join-col:first-child {
border-right: none;
border-bottom: 1px dashed var(--border);
}
.circle-radios {
grid-template-columns: 1fr;
@ -1020,9 +1083,6 @@ onUnmounted(() => {
}
@media (max-width: 480px) {
.tier-row {
grid-template-columns: repeat(2, 1fr);
}
.button-row {
flex-direction: column;
align-items: stretch;