feat: reskin public pages to zine direction
This commit is contained in:
parent
8b3daadadd
commit
88caca94c7
8 changed files with 2663 additions and 3577 deletions
|
|
@ -1,148 +1,219 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Page Header -->
|
||||
<PageHeader
|
||||
title="About Ghost Guild"
|
||||
subtitle=""
|
||||
size="large"
|
||||
/>
|
||||
<!-- ABOUT HERO (side by side) -->
|
||||
<div class="about-hero">
|
||||
<div class="about-hero-left">
|
||||
<h1>About Ghost Guild</h1>
|
||||
<p>A membership community for game developers exploring cooperative business models.</p>
|
||||
</div>
|
||||
<div class="about-hero-right">
|
||||
<div class="section-label">Our Story</div>
|
||||
<p>Ghost Guild grew out of Baby Ghosts, a Canadian nonprofit that's been supporting indie game developers since 2018. We noticed a gap: game developers interested in cooperative models had nowhere to learn, practice, and connect with others doing the same work.</p>
|
||||
<p>Ghost Guild is the response — a membership program where developers at every stage of cooperative practice can find resources, events, mentorship, and community.</p>
|
||||
<p>We don't prescribe a single model. We're a place to explore the options, learn from people who've tried them, and build something that works for your team.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<section class="py-20 bg-[--ui-bg]">
|
||||
<UContainer>
|
||||
<div class="max-w-3xl prose prose-lg dark:prose-invert">
|
||||
<!-- directives:[] -->
|
||||
<div id="content">
|
||||
<p>
|
||||
Ghost Guild is a community of learning and practice for anyone,
|
||||
anywhere interested in a video game industry-wide shift to
|
||||
worker-owned studio models.
|
||||
</p>
|
||||
<p>
|
||||
We reject hierarchy wherever it shows up. Ghost Guild is
|
||||
tier-less, but peer-full.
|
||||
</p>
|
||||
<h2 id="our-story">Our Story</h2>
|
||||
<p>
|
||||
Ghost Guild is the membership program of Baby Ghosts, a Canadian
|
||||
nonprofit that provides resources and support for worker-owned
|
||||
game studios.
|
||||
</p>
|
||||
<p>
|
||||
For three years, Baby Ghosts has run the Peer Accelerator - an
|
||||
intensive program pairing early-stage studios with mentorship,
|
||||
peer learning, and grants. Twenty-five studios have graduated. The
|
||||
model works! Peer learning builds stronger foundations than
|
||||
top-down advice ever could.
|
||||
</p>
|
||||
<p>
|
||||
But not everyone can commit to a six-month cohort. Some folks are
|
||||
still exploring. Others are already running established
|
||||
cooperatives and want to give back. Many are scattered across the
|
||||
world and just need to know they're not alone in wanting to build
|
||||
something different.
|
||||
</p>
|
||||
<p><em>Ghost Guild is how we open the doors wider.</em></p>
|
||||
<p>
|
||||
As we build our knowledge commons, more folks can benefit from
|
||||
collectively compiled wisdom and find community - whether or not
|
||||
they ever apply to the Peer Accelerator. The intensive program
|
||||
continues. Ghost Guild expands access to everything around it.
|
||||
</p>
|
||||
<h2 id="the-circles">The Circles</h2>
|
||||
<p>
|
||||
We've loosely named some circles you can join. This is not to rank
|
||||
you, but to connect you with folks at a similar stage and with
|
||||
resources that fit where you are right now.
|
||||
</p>
|
||||
<p>
|
||||
No circle is superior. There's no shame in sticking with one for a
|
||||
while, or moving between them to find the best fit.
|
||||
</p>
|
||||
<p>
|
||||
The Community Circle is for individuals exploring cooperative
|
||||
principles. Whether you're working in the industry or in academia,
|
||||
you'll get access to the knowledge commons, workshops, resources,
|
||||
guides, community Slack and peer support, and social events and
|
||||
networking.
|
||||
</p>
|
||||
<p>
|
||||
The Founder Circle is for those actively building a worker-owned
|
||||
studio. You'll have access to everything within the platform, just
|
||||
like any other member, but you might be particularly interested in
|
||||
peer matching with studios at similar stages and Peer Accelerator
|
||||
alumni, and templates for governance, financial modelling, and
|
||||
decision-making.
|
||||
</p>
|
||||
<p>
|
||||
The Practitioner Circle is for Peer Accelerator alumni and
|
||||
experienced cooperative studio leaders. You'll hopefully find
|
||||
yourself providing paid support to other members, as well as
|
||||
engaging in collaborative research opportunities with academic
|
||||
partners, connecting to other coops for business development, and
|
||||
helping build a platform for changing industry practices.
|
||||
</p>
|
||||
<h2 id="how-contribution-works">How Contribution Works</h2>
|
||||
<p>
|
||||
Choosing your financial contribution is also not about paying for
|
||||
access. Everything is available to every member, no matter their
|
||||
circle or contribution level.
|
||||
</p>
|
||||
<p>
|
||||
Rather, it's about finding a dues level that's meaningful to you
|
||||
without being a burden.
|
||||
</p>
|
||||
<p>
|
||||
The knowledge commons is open to all Ghosties. Your contribution
|
||||
sustains a community you believe in.
|
||||
</p>
|
||||
<p>
|
||||
If dues are a barrier, that's okay. Members who are able to
|
||||
contribute more can direct additional funds to the Solidarity
|
||||
Fund, which covers dues for those who need support.
|
||||
</p>
|
||||
<h2 id="community">Community</h2>
|
||||
<p>
|
||||
When you join Ghost Guild, you join a community of Ghosties -
|
||||
folks at every stage of the journey, learning from each other.
|
||||
</p>
|
||||
<p>
|
||||
Our Slack community is built with care. New members are welcomed
|
||||
thoughtfully, channels are structured to help you find your
|
||||
people, and we grow at a pace that protects what makes this space
|
||||
special.
|
||||
</p>
|
||||
<p>
|
||||
This is a cascading mentorship structure where everyone is both
|
||||
learning and teaching. Practitioners mentor Founders. Founders
|
||||
mentor Community members. And Community members bring fresh
|
||||
perspectives that keep everyone honest.
|
||||
</p>
|
||||
<p>Welcome, Ghostie! 👻</p>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
<!-- CONTENT AREA WITH EVENTS SIDEBAR -->
|
||||
<div class="content-area">
|
||||
<div class="content-main">
|
||||
|
||||
<!-- Link to Membership Circles -->
|
||||
<section class="py-20 bg-[--ui-bg-elevated]">
|
||||
<UContainer>
|
||||
<div class="max-w-3xl">
|
||||
<h2 class="text-display-sm font-bold text-[--ui-text] mb-4">
|
||||
Membership Circles
|
||||
</h2>
|
||||
<p class="text-lg text-[--ui-text-muted] mb-6">
|
||||
Learn about our three membership circles and find where you fit.
|
||||
</p>
|
||||
<UButton to="/about/circles" variant="outline" size="lg">
|
||||
Explore Membership Circles
|
||||
</UButton>
|
||||
<!-- THE CIRCLES -->
|
||||
<div class="about-section" id="circles">
|
||||
<div class="section-label">The Circles</div>
|
||||
<div class="circles-grid">
|
||||
<div id="community" class="circle-cell">
|
||||
<h3 style="color: var(--c-community);">Community</h3>
|
||||
<div class="circle-subtitle">"The open hall"</div>
|
||||
<p>For anyone exploring cooperative models. Wiki access, public events, Slack community, monthly meetings.</p>
|
||||
</div>
|
||||
<div id="founder" class="circle-cell">
|
||||
<h3 style="color: var(--c-founder);">Founder</h3>
|
||||
<div class="circle-subtitle">"The workshop"</div>
|
||||
<p>For people actively building cooperatives. Peer accelerator, mentorship, governance templates.</p>
|
||||
</div>
|
||||
<div id="practitioner" class="circle-cell">
|
||||
<h3 style="color: var(--c-practitioner);">Practitioner</h3>
|
||||
<div class="circle-subtitle">"The alcove"</div>
|
||||
<p>For experienced practitioners. Mentoring, teaching, shaping the program direction.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HOW CONTRIBUTION WORKS -->
|
||||
<div class="about-section">
|
||||
<div class="section-label">How Contribution Works</div>
|
||||
<p>Membership is $0–50/month, pay what you can. Nobody is excluded for lack of funds. Your contribution supports infrastructure, events, and community resources.</p>
|
||||
<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</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>
|
||||
</div>
|
||||
|
||||
<!-- COMMUNITY -->
|
||||
<div class="about-section">
|
||||
<div class="section-label">Community</div>
|
||||
<p>We gather in Slack, at monthly meetings, and through peer support sessions. The wiki is our shared knowledge base — growing as members contribute. Events range from workshops to social hangs to deep-dive series.</p>
|
||||
<NuxtLink to="/join" class="cta">Join the Guild →</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- ABOUT BABY GHOSTS -->
|
||||
<div class="about-section">
|
||||
<div class="section-label">About Baby Ghosts</div>
|
||||
<p>Ghost Guild is a program of Baby Ghosts, a Canadian nonprofit advancing cooperative models in game development. No tracking. No ads. No venture capital.</p>
|
||||
<p><a href="https://babyghosts.fund" target="_blank">babyghosts.fund →</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EVENTS MINI SIDEBAR -->
|
||||
<EventsMiniSidebar :events="upcomingEvents" />
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// No specific logic needed for the about page at this time
|
||||
const { data: upcomingEvents } = await useFetch('/api/events', {
|
||||
query: { limit: 3, upcoming: true },
|
||||
default: () => [],
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ---- ABOUT HERO ---- */
|
||||
.about-hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
.about-hero-left {
|
||||
padding: 32px 32px 28px;
|
||||
border-right: 1px dashed var(--border);
|
||||
}
|
||||
.about-hero-left h1 {
|
||||
font-family: 'Brygada 1918', serif;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--text-bright);
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.01em;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.about-hero-left p {
|
||||
color: var(--text-dim);
|
||||
line-height: 1.7;
|
||||
font-size: 13px;
|
||||
}
|
||||
.about-hero-right {
|
||||
padding: 32px;
|
||||
}
|
||||
.about-hero-right p {
|
||||
color: var(--text-dim);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* ---- CONTENT AREA ---- */
|
||||
.content-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 200px;
|
||||
}
|
||||
.content-main {
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ---- SECTIONS ---- */
|
||||
.about-section {
|
||||
padding: 28px 32px;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
.about-section > p {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 8px;
|
||||
max-width: 560px;
|
||||
}
|
||||
.cta {
|
||||
display: inline-block;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--candle);
|
||||
}
|
||||
|
||||
/* ---- CIRCLES GRID ---- */
|
||||
.circles-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0;
|
||||
border: 1px dashed var(--border);
|
||||
}
|
||||
.circle-cell {
|
||||
padding: 20px;
|
||||
border-right: 1px dashed var(--border);
|
||||
}
|
||||
.circle-cell:last-child { border-right: none; }
|
||||
.circle-cell h3 {
|
||||
font-family: 'Brygada 1918', serif;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.circle-subtitle {
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.circle-cell p {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
/* ---- TIER LIST ---- */
|
||||
.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;
|
||||
}
|
||||
|
||||
/* ---- RESPONSIVE ---- */
|
||||
@media (max-width: 1024px) {
|
||||
.content-area { grid-template-columns: 1fr; }
|
||||
.circles-grid { grid-template-columns: 1fr; }
|
||||
.circle-cell {
|
||||
border-right: none;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
.circle-cell:last-child { border-bottom: none; }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.about-hero {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.about-hero-left {
|
||||
border-right: none;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,212 +1,4 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Page Header -->
|
||||
<PageHeader
|
||||
title="About Our Membership Circles"
|
||||
subtitle="All members of Ghost Guild share the Baby Ghosts mission: Advancing cooperative and worker-centric labour models in the Canadian interactive digital arts sector."
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<!-- How Ghost Guild Works -->
|
||||
<section class="py-20 bg-[--ui-bg]">
|
||||
<UContainer>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-display font-bold text-[--ui-text] mb-6">
|
||||
How membership works
|
||||
</h2>
|
||||
|
||||
<div class="prose prose-lg dark:prose-invert max-w-none">
|
||||
<p class="text-xl font-semibold text-[--ui-text] mb-6">
|
||||
Everyone gets everything. Your circle reflects where you are in
|
||||
your cooperative journey. Your financial contribution reflects
|
||||
what you can afford. These are completely separate choices.
|
||||
</p>
|
||||
|
||||
<ul
|
||||
class="list-disc pl-6 text-lg leading-relaxed text-[--ui-text-muted] space-y-3 mb-12"
|
||||
>
|
||||
<li>
|
||||
The entire knowledge commons, all events, and full community
|
||||
participation on our private Slack
|
||||
</li>
|
||||
<li>One member, one vote in all decisions</li>
|
||||
<li>Pay what you can ($0-50+/month)</li>
|
||||
<li>Contribute your skills, time, and knowledge</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
|
||||
<!-- Find Your Circle -->
|
||||
<section class="py-20 bg-[--ui-bg-elevated]">
|
||||
<UContainer>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-display font-bold text-[--ui-text] mb-4">
|
||||
Find your circle
|
||||
</h2>
|
||||
<p class="text-lg text-[--ui-text-muted] mb-12">
|
||||
Circles help us provide relevant guidance and connect you with
|
||||
others at similar stages. Choose based on where you are now!
|
||||
</p>
|
||||
|
||||
<div class="space-y-12">
|
||||
<!-- Community Circle -->
|
||||
<div class="circle-surface-community rounded-xl p-6">
|
||||
<h3 class="text-display-sm font-bold text-[--ui-text] mb-2">
|
||||
Community Circle
|
||||
</h3>
|
||||
|
||||
<div
|
||||
class="prose prose-lg dark:prose-invert max-w-none text-[--ui-text-muted]"
|
||||
>
|
||||
<p>
|
||||
Maybe you've heard rumours about cooperatives in game dev and
|
||||
you're curious. Or you're frustrated with traditional studio
|
||||
hierarchies and wondering if there's another way. This circle
|
||||
is for anyone exploring whether cooperative principles might
|
||||
fit their work.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This space is for you if you're: an individual game worker
|
||||
dreaming of different possibilities • a researcher digging
|
||||
into the rise of alternative studio models • an industry ally
|
||||
who wants to support cooperative work • <em>anyone</em> who's
|
||||
co-op-curious!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Our resources and community space will help you understand
|
||||
cooperative basics, connect with others asking the same
|
||||
questions, and give you a look at real examples from game
|
||||
studios. You don't need to have a studio or project of your
|
||||
own - just join and see what strikes your fancy!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Founder Circle -->
|
||||
<div class="circle-surface-founder rounded-xl p-6">
|
||||
<h3 class="text-display-sm font-bold text-[--ui-text] mb-2">
|
||||
Founder Circle
|
||||
</h3>
|
||||
|
||||
<div
|
||||
class="prose prose-lg dark:prose-invert max-w-none text-[--ui-text-muted]"
|
||||
>
|
||||
<p>
|
||||
You're way past wondering about "what if" and into "how do we
|
||||
actually do this?" Perhaps you're forming a new cooperative
|
||||
studio from scratch, or converting an existing team to a co-op
|
||||
structure, or working through the messy reality of turning
|
||||
values into sustainable practice.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is the space for the practical stuff: governance
|
||||
documents you can read and adapt, financial models for
|
||||
cooperative studios, connections with other founders
|
||||
navigating similar challenges.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We have two paths through this circle that we will be
|
||||
launching soon:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Peer Accelerator Prep Track <em>(coming soon)</em> –
|
||||
Structured preparation if you're planning to apply for the
|
||||
PA program
|
||||
</li>
|
||||
<li>
|
||||
Indie Track <em>(coming soon)</em> – Flexible, self-paced
|
||||
support for teams building at their own pace
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Join us to figure out how you can balance your values with
|
||||
keeping the lights on - whether you're a full founding team, a
|
||||
solo founder exploring structures, or an existing studio in
|
||||
transition.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Practitioner Circle -->
|
||||
<div class="circle-surface-practitioner rounded-xl p-6">
|
||||
<h3 class="text-display-sm font-bold text-[--ui-text] mb-2">
|
||||
Practitioner Circle
|
||||
</h3>
|
||||
|
||||
<div
|
||||
class="prose prose-lg dark:prose-invert max-w-none text-[--ui-text-muted]"
|
||||
>
|
||||
<p>
|
||||
You've done it. You're actually running a
|
||||
cooperative/worker-centric studio or you've been through our
|
||||
Peer Accelerator. Now you're figuring out how to sustain it,
|
||||
improve it, and maybe help others learn from what
|
||||
<em>you've</em> learned.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This circle is for: Peer Accelerator alumni • members of
|
||||
established co-ops • mentors who want to support other
|
||||
cooperatives • researchers studying cooperative models in
|
||||
practice
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Here, we create space for practitioners to share what's
|
||||
actually working (and what isn't), support emerging
|
||||
cooperatives, collaborate across studios, and contribute to
|
||||
building a knowledge commons.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
|
||||
<!-- Important Notes -->
|
||||
<section class="py-20 bg-[--ui-bg]">
|
||||
<UContainer>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-display font-bold text-[--ui-text] mb-8">
|
||||
Important Notes
|
||||
</h2>
|
||||
|
||||
<div class="space-y-6 text-lg text-[--ui-text-muted]">
|
||||
<p>
|
||||
<strong>Movement between circles is fluid.</strong> As you move
|
||||
along in your journey, you can shift circles anytime. Just let us
|
||||
know.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Your contribution is separate from your circle.</strong>
|
||||
Whether you contribute $0 or $50+/month, you get full access to
|
||||
everything. Choose based on your financial capacity, not your
|
||||
circle.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Not sure which circle?</strong> Start with Community - you
|
||||
can always move. Or email us and we'll chat about what makes sense
|
||||
for you.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// No specific logic needed for the circles page at this time
|
||||
// Circles content has moved to the About page
|
||||
navigateTo('/about#circles', { redirectCode: 301 })
|
||||
</script>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,186 +1,270 @@
|
|||
<template>
|
||||
<div class="max-w-6xl mx-auto px-6 md:px-8">
|
||||
<!-- Hero Section -->
|
||||
<section class="py-16 md:py-24 ink-grain">
|
||||
<div class="max-w-2xl">
|
||||
<h1
|
||||
class="text-display-xl font-light text-guild-100 leading-tight mb-2"
|
||||
>
|
||||
Build your co-op studio
|
||||
</h1>
|
||||
<p
|
||||
class="text-display-xl font-light text-guild-500 leading-tight mb-8"
|
||||
>
|
||||
with people who get it.
|
||||
</p>
|
||||
|
||||
<p class="text-lg text-guild-400 leading-relaxed mb-8 max-w-xl">
|
||||
Ghost Guild is a peer community for game developers exploring
|
||||
cooperative models. Find support, share knowledge, grow together.
|
||||
</p>
|
||||
|
||||
<!-- Signup Form -->
|
||||
<form @submit.prevent="handleJoinSubmit" class="mb-4">
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<UInput
|
||||
v-model="joinEmail"
|
||||
type="email"
|
||||
placeholder="your.email@example.com"
|
||||
size="lg"
|
||||
class="flex-1"
|
||||
:disabled="isSubmitting"
|
||||
/>
|
||||
<UButton
|
||||
type="submit"
|
||||
size="lg"
|
||||
:loading="isSubmitting"
|
||||
:disabled="!isEmailValid"
|
||||
>
|
||||
Join Us
|
||||
</UButton>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="text-sm text-guild-600">Free to join. Pay what you can.</p>
|
||||
|
||||
<!-- Success/Error Messages -->
|
||||
<div
|
||||
v-if="submitSuccess"
|
||||
class="mt-4 p-3 bg-primary-500/10 border border-primary-500/30 rounded-lg"
|
||||
>
|
||||
<p class="text-primary-400 text-sm">
|
||||
Check your email to complete signup!
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="submitError"
|
||||
class="mt-4 p-3 bg-ember-900/20 border border-ember-500/30 rounded-lg"
|
||||
>
|
||||
<p class="text-ember-400 text-sm">{{ submitError }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GuildDivider variant="woodcut" />
|
||||
|
||||
<!-- Value Props Section -->
|
||||
<section class="py-16">
|
||||
<div class="grid md:grid-cols-3 gap-8 md:gap-12">
|
||||
<div>
|
||||
<p class="text-ui-label text-candlelight-400 mb-3">Peer Support</p>
|
||||
<p class="text-guild-400 leading-relaxed">
|
||||
Connect with founders at your stage and practitioners who've been
|
||||
there. Real conversations, real help.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-ui-label text-candlelight-400 mb-3">
|
||||
Shared Knowledge
|
||||
</p>
|
||||
<p class="text-guild-400 leading-relaxed">
|
||||
Templates, governance docs, financial models—tools built by co-ops,
|
||||
for co-ops. All members get full access.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-ui-label text-candlelight-400 mb-3">
|
||||
Solidarity Economics
|
||||
</p>
|
||||
<p class="text-guild-400 leading-relaxed">
|
||||
Those who can, support those who can't. No tiers, no gatekeeping.
|
||||
Everyone gets everything.
|
||||
</p>
|
||||
<!-- HERO -->
|
||||
<div class="hero">
|
||||
<h1>Ghost Guild is where game developers practice cooperative business models.</h1>
|
||||
<p>Resources, events, and a community of people figuring it out. Three circles, no hierarchy. $0–50/mo, pay what you can.</p>
|
||||
<div class="hero-links">
|
||||
<NuxtLink to="/join" class="hero-link primary">Become a member</NuxtLink>
|
||||
<NuxtLink to="/wiki" class="hero-link">Read the wiki</NuxtLink>
|
||||
<NuxtLink to="/about" class="hero-link">What is this?</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GuildDivider variant="woodcut" />
|
||||
<!-- THREE CIRCLES -->
|
||||
<div class="content-row">
|
||||
<div v-for="circle in circleData" :key="circle.value" class="content-block">
|
||||
<div class="label" :style="{ color: `var(--c-${circle.value})` }">{{ circle.label }}</div>
|
||||
<h2>{{ circle.metaphor }}</h2>
|
||||
<p>{{ circle.blurb }}</p>
|
||||
<details>
|
||||
<summary>What's included?</summary>
|
||||
<p>{{ circle.included }}</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Circles Section -->
|
||||
<section class="py-16">
|
||||
<p class="text-ui-label text-guild-600 mb-8">Find your people</p>
|
||||
|
||||
<div class="space-y-4 mb-8">
|
||||
<NuxtLink
|
||||
v-for="circle in circles"
|
||||
:key="circle.value"
|
||||
to="/about/circles"
|
||||
class="flex items-baseline gap-8 group py-2"
|
||||
>
|
||||
<span
|
||||
class="text-guild-300 group-hover:text-guild-100 transition-colors w-32 md:w-40"
|
||||
>
|
||||
{{ circle.label }}
|
||||
<!-- UPCOMING EVENTS + WIKI -->
|
||||
<div class="content-row two-col">
|
||||
<div class="content-block">
|
||||
<div class="label">Upcoming Events</div>
|
||||
<div v-if="events?.length" class="event-list">
|
||||
<div v-for="event in events" :key="event._id" class="event-item">
|
||||
<span class="event-date">{{ formatDate(event.date) }}</span>
|
||||
<span class="event-title">
|
||||
<NuxtLink :to="`/events/${event._id}`">{{ event.title }}</NuxtLink>
|
||||
</span>
|
||||
<span class="text-guild-600">
|
||||
{{ circle.shortDescription }}
|
||||
</span>
|
||||
</NuxtLink>
|
||||
<CircleBadge v-if="event.circle" :circle="event.circle" />
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="empty">No upcoming events</p>
|
||||
</div>
|
||||
<div class="content-block">
|
||||
<div class="label">Recently in the Wiki</div>
|
||||
<div class="wiki-list">
|
||||
<div class="wiki-item">
|
||||
<a href="/wiki">Revenue sharing models</a>
|
||||
</div>
|
||||
<div class="wiki-item">
|
||||
<a href="/wiki">What is a cooperative studio?</a>
|
||||
</div>
|
||||
<div class="wiki-item">
|
||||
<a href="/wiki">Governance structures</a>
|
||||
</div>
|
||||
<div class="wiki-item">
|
||||
<a href="/wiki">Legal incorporation guide</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-guild-600 italic">
|
||||
These reflect your journey, not your status. Move between them as you
|
||||
grow.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<GuildDivider variant="woodcut" />
|
||||
|
||||
<!-- Bottom CTA Section -->
|
||||
<section class="py-24 text-center">
|
||||
<p class="text-ui-label text-guild-600 mb-4">Part of the Baby Ghosts family</p>
|
||||
<h2 class="text-display font-light text-guild-200 mb-8">
|
||||
Ready to find your people?
|
||||
</h2>
|
||||
<UButton
|
||||
to="/join"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
class="hover:bg-primary-500/10"
|
||||
>
|
||||
Become a Ghostie
|
||||
</UButton>
|
||||
</section>
|
||||
<!-- PARCHMENT INSET -->
|
||||
<ParchmentInset>
|
||||
<div class="label" style="color: var(--candle-faint); opacity: 0.6; margin-bottom: 12px;">From the Wiki</div>
|
||||
<h2>What is a cooperative studio?</h2>
|
||||
<p>A cooperative studio is a game development company owned and governed by the people who work there. Decisions are made collectively. Profits are shared according to contribution, not ownership stake.</p>
|
||||
<p>The games industry is full of stories about crunch, layoffs, and studios that extract value from workers. Cooperatives are one alternative — not the only one, but one worth <a href="/wiki">practicing together</a>.</p>
|
||||
<p><a href="/wiki">Read more in the wiki →</a></p>
|
||||
</ParchmentInset>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCircleOptions } from "~/config/circles";
|
||||
|
||||
definePageMeta({
|
||||
layout: "default",
|
||||
});
|
||||
})
|
||||
|
||||
const circles = getCircleOptions();
|
||||
const { data: events } = await useFetch('/api/events', {
|
||||
query: { limit: 4, upcoming: true },
|
||||
default: () => [],
|
||||
})
|
||||
|
||||
// Join form state
|
||||
const joinEmail = ref("");
|
||||
const isSubmitting = ref(false);
|
||||
const submitSuccess = ref(false);
|
||||
const submitError = ref("");
|
||||
const circleData = [
|
||||
{
|
||||
value: 'community',
|
||||
label: 'Community',
|
||||
metaphor: 'The open hall',
|
||||
blurb: 'Arrival, curiosity, orientation. For anyone exploring cooperative models in game development. Access the wiki, public events, and Slack.',
|
||||
included: 'Wiki access, public events, Slack community, monthly guild meetings. Free or pay-what-you-can.',
|
||||
},
|
||||
{
|
||||
value: 'founder',
|
||||
label: 'Founder',
|
||||
metaphor: 'The workshop',
|
||||
blurb: 'For people actively building cooperatives. Structured practice, peer support, templates, and hands-on resources.',
|
||||
included: 'Everything in Community plus the peer accelerator, 1:1 mentorship matching, and Founder-only workshops.',
|
||||
},
|
||||
{
|
||||
value: 'practitioner',
|
||||
label: 'Practitioner',
|
||||
metaphor: 'The alcove',
|
||||
blurb: 'Where experience is shared and knowledge given back. Teaching, advising, shaping the program itself.',
|
||||
included: 'Everything in Founder plus the ability to mentor, propose events, contribute to the wiki, and help govern the Guild.',
|
||||
},
|
||||
]
|
||||
|
||||
const isEmailValid = computed(() => {
|
||||
return joinEmail.value && joinEmail.value.includes("@");
|
||||
});
|
||||
|
||||
const handleJoinSubmit = async () => {
|
||||
if (!isEmailValid.value || isSubmitting.value) return;
|
||||
|
||||
isSubmitting.value = true;
|
||||
submitSuccess.value = false;
|
||||
submitError.value = "";
|
||||
|
||||
try {
|
||||
// Redirect to join page with email pre-filled
|
||||
await navigateTo({
|
||||
path: "/join",
|
||||
query: { email: joinEmail.value },
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Join error:", err);
|
||||
submitError.value = "Something went wrong. Please try again.";
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
const d = new Date(dateStr)
|
||||
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
||||
}
|
||||
};
|
||||
</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;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.hero-links {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.hero-link {
|
||||
color: var(--candle);
|
||||
padding: 6px 16px;
|
||||
border: 1px dashed var(--candle-faint);
|
||||
transition: all 0.2s;
|
||||
text-decoration: none;
|
||||
}
|
||||
.hero-link:hover {
|
||||
border-color: var(--candle);
|
||||
border-style: solid;
|
||||
text-decoration: none;
|
||||
}
|
||||
.hero-link.primary {
|
||||
background: var(--candle);
|
||||
color: var(--bg);
|
||||
border-color: var(--candle);
|
||||
border-style: solid;
|
||||
}
|
||||
.hero-link.primary:hover {
|
||||
background: var(--candle-dim);
|
||||
border-color: var(--candle-dim);
|
||||
}
|
||||
|
||||
/* ---- CONTENT GRID ---- */
|
||||
.content-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
.content-row.two-col {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.content-block {
|
||||
padding: 24px 28px;
|
||||
border-right: 1px dashed var(--border);
|
||||
min-width: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.content-block .label {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-faint);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ---- DETAILS ---- */
|
||||
details {
|
||||
margin-top: 12px;
|
||||
}
|
||||
details summary {
|
||||
font-size: 12px;
|
||||
color: var(--candle-dim);
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
details summary::before {
|
||||
content: '+ ';
|
||||
}
|
||||
details[open] summary::before {
|
||||
content: '− ';
|
||||
}
|
||||
details p {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* ---- EVENT LIST ---- */
|
||||
.event-item {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr auto;
|
||||
gap: 16px;
|
||||
align-items: baseline;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
transition: padding-left 0.2s;
|
||||
}
|
||||
.event-item:last-child { border-bottom: none; }
|
||||
.event-item:hover { padding-left: 4px; }
|
||||
.event-date { color: var(--text-faint); font-size: 12px; }
|
||||
.event-title { color: var(--text); font-size: 13px; }
|
||||
.event-title a { color: var(--text); text-decoration: none; }
|
||||
.event-title a:hover { color: var(--candle); }
|
||||
|
||||
/* ---- WIKI LIST ---- */
|
||||
.wiki-item {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
font-size: 13px;
|
||||
}
|
||||
.wiki-item:last-child { border-bottom: none; }
|
||||
.wiki-item a { color: var(--text); text-decoration: none; }
|
||||
.wiki-item a:hover { color: var(--candle); }
|
||||
|
||||
.empty {
|
||||
color: var(--text-faint);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* ---- RESPONSIVE ---- */
|
||||
@media (max-width: 768px) {
|
||||
.content-row,
|
||||
.content-row.two-col {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.content-block {
|
||||
border-right: none;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
.content-block:last-child { border-bottom: none; }
|
||||
.hero-links {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.hero-link {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
1060
app/pages/join.vue
1060
app/pages/join.vue
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,503 +1,164 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="pending" class="min-h-screen flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"
|
||||
></div>
|
||||
<p class="text-[--ui-text-muted]">Loading series details...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="pending" class="loading">Loading series details...</div>
|
||||
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="min-h-screen flex items-center justify-center"
|
||||
>
|
||||
<div class="text-center">
|
||||
<h2 class="text-2xl font-bold text-[--ui-text] mb-2">
|
||||
Series Not Found
|
||||
</h2>
|
||||
<p class="text-[--ui-text-muted] mb-6">
|
||||
The event series you're looking for doesn't exist.
|
||||
</p>
|
||||
<NuxtLink to="/series" class="text-primary hover:underline">
|
||||
← Back to Event Series
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div v-else-if="error" class="loading">
|
||||
<h2>Series Not Found</h2>
|
||||
<p>The event series you're looking for doesn't exist.</p>
|
||||
<NuxtLink to="/events">← Back to Events</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<!-- Page Header -->
|
||||
<PageHeader :title="series.title" size="large" />
|
||||
|
||||
<!-- Series Meta -->
|
||||
<section class="py-20 bg-[--ui-bg]">
|
||||
<UContainer>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<!-- Series Description -->
|
||||
<div v-if="series.description" class="mb-8">
|
||||
<p class="text-lg text-[--ui-text-muted] leading-relaxed">
|
||||
{{ series.description }}
|
||||
</p>
|
||||
<!-- BACK LINK -->
|
||||
<div class="back-link">
|
||||
<NuxtLink to="/events">← Back to Events</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 mb-8 flex-wrap">
|
||||
<span
|
||||
:class="[
|
||||
'inline-flex items-center px-3 py-1 rounded text-sm font-medium',
|
||||
getSeriesTypeBadgeClass(series.type),
|
||||
]"
|
||||
>
|
||||
{{ formatSeriesType(series.type) }}
|
||||
</span>
|
||||
<span
|
||||
:class="[
|
||||
'inline-flex items-center px-3 py-1 rounded text-sm font-medium',
|
||||
getSeriesStatusClass(),
|
||||
]"
|
||||
>
|
||||
{{ getSeriesStatusText() }}
|
||||
<!-- SERIES HEADER -->
|
||||
<div class="series-header">
|
||||
<h1>{{ series.title }}</h1>
|
||||
<div class="series-meta-row">
|
||||
<span v-if="series.type" class="badge all">{{ formatSeriesType(series.type) }}</span>
|
||||
<span class="meta-text">{{ series.events?.length || 0 }} sessions</span>
|
||||
<span v-if="series.startDate" class="meta-text">
|
||||
{{ formatDate(series.startDate) }} – {{ formatDate(series.endDate) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Series Stats -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-6 mb-12">
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-[--ui-text] mb-1">
|
||||
{{ series.statistics.totalEvents }}
|
||||
</div>
|
||||
<div class="text-sm text-[--ui-text-muted]">Total Events</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-[--ui-text] mb-1">
|
||||
{{ series.statistics.completedEvents }}
|
||||
</div>
|
||||
<div class="text-sm text-[--ui-text-muted]">Completed</div>
|
||||
<!-- DESCRIPTION -->
|
||||
<div v-if="series.description" class="section">
|
||||
<p>{{ series.description }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-[--ui-text] mb-1">
|
||||
{{ series.statistics.upcomingEvents }}
|
||||
</div>
|
||||
<div class="text-sm text-[--ui-text-muted]">Upcoming</div>
|
||||
</div>
|
||||
|
||||
<div v-if="series.statistics.totalRegistrations">
|
||||
<div class="text-3xl font-bold text-[--ui-text] mb-1">
|
||||
{{ series.statistics.totalRegistrations }}
|
||||
</div>
|
||||
<div class="text-sm text-[--ui-text-muted]">Registrations</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Series Date Range -->
|
||||
<div
|
||||
v-if="series.startDate && series.endDate"
|
||||
class="flex items-center gap-2 text-[--ui-text-muted] mb-8"
|
||||
>
|
||||
<Icon name="heroicons:calendar-days" class="w-5 h-5" />
|
||||
<span>
|
||||
Series runs from
|
||||
{{ formatDateRange(series.startDate, series.endDate) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Status Message -->
|
||||
<div
|
||||
v-if="series?.statistics?.isOngoing"
|
||||
class="p-4 bg-candlelight-900/20 border border-candlelight-700/30 rounded mb-8"
|
||||
>
|
||||
<p class="text-candlelight-500 dark:text-candlelight-400 font-semibold mb-1">
|
||||
This series is currently ongoing!
|
||||
</p>
|
||||
<p class="text-sm text-[--ui-text-muted]">
|
||||
Register for upcoming events to join the learning journey.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="series?.statistics?.isUpcoming"
|
||||
class="p-4 bg-guild-500/10 border border-guild-500/30 rounded mb-8"
|
||||
>
|
||||
<p class="text-guild-300 dark:text-guild-300 font-semibold mb-1">
|
||||
This series is starting soon!
|
||||
</p>
|
||||
<p class="text-sm text-[--ui-text-muted]">
|
||||
Mark your calendar and register for the events.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="series?.statistics?.isCompleted"
|
||||
class="p-4 bg-guild-500/10 border border-guild-500/30 rounded mb-8"
|
||||
>
|
||||
<p class="text-[--ui-text] font-semibold mb-1">
|
||||
This series has concluded.
|
||||
</p>
|
||||
<p class="text-sm text-[--ui-text-muted]">
|
||||
Check out our other event series for more opportunities to learn
|
||||
and connect.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
|
||||
<!-- Series Pass Purchase (if tickets enabled) -->
|
||||
<section v-if="series?.tickets?.enabled" class="py-20 bg-[--ui-bg]">
|
||||
<UContainer>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-display-sm font-bold text-[--ui-text] mb-8">
|
||||
Get Your Series Pass
|
||||
</h2>
|
||||
<SeriesPassPurchase
|
||||
:series-id="series.id || series._id"
|
||||
:series-info="{
|
||||
id: series.id,
|
||||
title: series.title,
|
||||
totalEvents: series?.statistics?.totalEvents || 0,
|
||||
type: series.type,
|
||||
}"
|
||||
:series-events="series.events || []"
|
||||
:user-email="user?.email"
|
||||
:user-name="user?.name"
|
||||
@purchase-success="handlePurchaseSuccess"
|
||||
/>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
|
||||
<!-- Events Timeline -->
|
||||
<section class="py-20 bg-[--ui-bg-elevated]">
|
||||
<UContainer>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h2 class="text-display-sm font-bold text-[--ui-text] mb-8">
|
||||
Event Schedule
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(event, index) in series?.events || []"
|
||||
:key="event.id"
|
||||
class="group"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<!-- Position indicator -->
|
||||
<div class="flex flex-col items-center flex-shrink-0">
|
||||
<div
|
||||
:class="[
|
||||
'w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold border',
|
||||
getEventTimelineColor(event),
|
||||
]"
|
||||
>
|
||||
{{ event.series?.position || index + 1 }}
|
||||
</div>
|
||||
<div
|
||||
v-if="index < (series?.events?.length || 0) - 1"
|
||||
class="w-0.5 h-12 bg-[--ui-border]"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Event Card -->
|
||||
<NuxtLink
|
||||
:to="`/events/${event.slug || event.id}`"
|
||||
class="flex-1 border border-[--ui-border] rounded p-4 hover:border-primary transition-colors"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col md:flex-row md:items-start md:justify-between gap-3"
|
||||
>
|
||||
<!-- Event Info -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-start gap-2 mb-2 flex-wrap">
|
||||
<h3
|
||||
class="text-lg font-semibold text-[--ui-text] group-hover:text-primary transition-colors"
|
||||
>
|
||||
<!-- EVENT LIST -->
|
||||
<div class="section">
|
||||
<div class="section-label">Sessions</div>
|
||||
<div v-if="series.events?.length">
|
||||
<div v-for="(event, index) in series.events" :key="event._id || index" class="event-row">
|
||||
<span class="event-num">{{ String(index + 1).padStart(2, '0') }}</span>
|
||||
<span class="event-date">{{ formatDate(event.startDate) }}</span>
|
||||
<div class="event-info">
|
||||
<NuxtLink :to="`/events/${event.slug || event._id || event.id}`" class="event-title-link">
|
||||
{{ event.title }}
|
||||
</h3>
|
||||
<span
|
||||
:class="[
|
||||
'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium flex-shrink-0',
|
||||
getEventStatusClass(event),
|
||||
]"
|
||||
>
|
||||
{{ getEventStatus(event) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="event.description"
|
||||
class="text-[--ui-text-muted] mb-3 line-clamp-2"
|
||||
>
|
||||
{{ event.description }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-3 text-sm text-[--ui-text-muted]"
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
<Icon
|
||||
name="heroicons:calendar-days"
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
{{ formatEventDate(event.startDate) }}
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Icon name="heroicons:clock" class="w-4 h-4" />
|
||||
{{ formatEventTime(event.startDate) }}
|
||||
</div>
|
||||
<div
|
||||
v-if="event.location"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<Icon name="heroicons:map-pin" class="w-4 h-4" />
|
||||
{{ event.location }}
|
||||
</div>
|
||||
<div
|
||||
v-if="event.registeredCount"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<Icon name="heroicons:users" class="w-4 h-4" />
|
||||
{{ event.registeredCount }} registered
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arrow -->
|
||||
<div class="flex items-center md:pt-1">
|
||||
<Icon
|
||||
name="heroicons:arrow-right"
|
||||
class="w-5 h-5 text-[--ui-text-muted] group-hover:text-primary group-hover:translate-x-1 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
<span class="event-status">{{ getEventStatus(event) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="empty">No sessions scheduled yet.</p>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
|
||||
<!-- Questions -->
|
||||
<section class="py-20 bg-[--ui-bg]">
|
||||
<UContainer>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h3 class="text-xl font-bold text-[--ui-text] mb-3">
|
||||
Questions About This Series?
|
||||
</h3>
|
||||
<p class="text-[--ui-text-muted] mb-4">
|
||||
If you have any questions about this event series, please reach
|
||||
out to us.
|
||||
</p>
|
||||
<a
|
||||
href="mailto:events@ghostguild.org"
|
||||
class="text-primary hover:underline"
|
||||
>
|
||||
events@ghostguild.org
|
||||
</a>
|
||||
<!-- PASS PURCHASE -->
|
||||
<div v-if="series.passPrice" class="section">
|
||||
<DashedBox>
|
||||
<div class="section-label">Series Pass</div>
|
||||
<p>Get access to all sessions in this series with a single pass.</p>
|
||||
<div class="pass-price">${{ series.passPrice }}</div>
|
||||
</DashedBox>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<NuxtLink to="/series" class="text-primary hover:underline">
|
||||
← Back to all event series
|
||||
</NuxtLink>
|
||||
<!-- QUESTIONS -->
|
||||
<div class="section">
|
||||
<div class="section-label">Questions?</div>
|
||||
<p>If you have questions about this series, reach out to us.</p>
|
||||
<a href="mailto:events@ghostguild.org">events@ghostguild.org</a>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const route = useRoute();
|
||||
const { data: session } = useAuth();
|
||||
const toast = useToast();
|
||||
const route = useRoute()
|
||||
|
||||
// Get user info
|
||||
const user = computed(() => session?.value?.user || null);
|
||||
const { data: series, pending, error } = await useFetch(`/api/series/${route.params.id}`)
|
||||
|
||||
// Fetch series data from API
|
||||
const {
|
||||
data: series,
|
||||
pending,
|
||||
error,
|
||||
refresh: refreshSeries,
|
||||
} = await useFetch(`/api/series/${route.params.id}`);
|
||||
|
||||
// Handle series not found
|
||||
if (error.value?.statusCode === 404) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: "Event series not found",
|
||||
});
|
||||
throw createError({ statusCode: 404, statusMessage: 'Event series not found' })
|
||||
}
|
||||
|
||||
// Handle successful series pass purchase
|
||||
const handlePurchaseSuccess = async (response) => {
|
||||
// Refresh series data to show updated registration status
|
||||
await refreshSeries();
|
||||
|
||||
// Scroll to top to show success message
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
|
||||
// Helper functions
|
||||
const formatSeriesType = (type) => {
|
||||
const types = {
|
||||
workshop_series: "Workshop Series",
|
||||
recurring_meetup: "Recurring Meetup",
|
||||
multi_day: "Multi-Day Event",
|
||||
course: "Course",
|
||||
tournament: "Tournament",
|
||||
};
|
||||
return types[type] || type;
|
||||
};
|
||||
const types = { workshop_series: 'Workshop Series', recurring_meetup: 'Recurring Meetup', multi_day: 'Multi-Day Event', course: 'Course', tournament: 'Tournament' }
|
||||
return types[type] || type
|
||||
}
|
||||
|
||||
const getSeriesTypeBadgeClass = (type) => {
|
||||
const classes = {
|
||||
workshop_series:
|
||||
"bg-candlelight-900/20 text-candlelight-500 dark:text-candlelight-400 border border-candlelight-700/30",
|
||||
recurring_meetup:
|
||||
"bg-guild-500/10 text-guild-300 dark:text-guild-300 border border-guild-500/30",
|
||||
multi_day:
|
||||
"bg-ember-900/20 text-ember-500 dark:text-ember-400 border border-ember-700/30",
|
||||
course:
|
||||
"bg-candlelight-900/20 text-candlelight-500 dark:text-candlelight-400 border border-candlelight-700/30",
|
||||
tournament:
|
||||
"bg-ember-900/20 text-ember-500 dark:text-ember-400 border border-ember-700/30",
|
||||
};
|
||||
return (
|
||||
classes[type] ||
|
||||
"bg-earth-900/20 text-earth-400 dark:text-earth-400 border border-earth-700/30"
|
||||
);
|
||||
};
|
||||
|
||||
const getSeriesStatusText = () => {
|
||||
if (!series.value?.statistics) return "Active";
|
||||
if (series.value.statistics.isOngoing) return "Ongoing";
|
||||
if (series.value.statistics.isUpcoming) return "Starting Soon";
|
||||
if (series.value.statistics.isCompleted) return "Completed";
|
||||
return "Active";
|
||||
};
|
||||
|
||||
const getSeriesStatusClass = () => {
|
||||
if (!series.value?.statistics)
|
||||
return "bg-candlelight-900/20 text-candlelight-500 dark:text-candlelight-400 border border-candlelight-700/30";
|
||||
if (series.value.statistics.isOngoing)
|
||||
return "bg-candlelight-900/20 text-candlelight-500 dark:text-candlelight-400 border border-candlelight-700/30";
|
||||
if (series.value.statistics.isUpcoming)
|
||||
return "bg-guild-500/10 text-guild-300 dark:text-guild-300 border border-guild-500/30";
|
||||
if (series.value.statistics.isCompleted)
|
||||
return "bg-guild-500/10 text-guild-400 dark:text-guild-400 border border-guild-500/30";
|
||||
return "bg-candlelight-900/20 text-candlelight-500 dark:text-candlelight-400 border border-candlelight-700/30";
|
||||
};
|
||||
|
||||
const formatEventDate = (date) => {
|
||||
return new Date(date).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const formatEventTime = (date) => {
|
||||
return new Date(date).toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
};
|
||||
|
||||
const formatDateRange = (startDate, endDate) => {
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
|
||||
const formatter = new Intl.DateTimeFormat("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
return `${formatter.format(start)} to ${formatter.format(end)}`;
|
||||
};
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return ''
|
||||
return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
|
||||
}
|
||||
|
||||
const getEventStatus = (event) => {
|
||||
const now = new Date();
|
||||
const startDate = new Date(event.startDate);
|
||||
const endDate = new Date(event.endDate);
|
||||
|
||||
if (now < startDate) return "Upcoming";
|
||||
if (now >= startDate && now <= endDate) return "Ongoing";
|
||||
return "Completed";
|
||||
};
|
||||
|
||||
const getEventStatusClass = (event) => {
|
||||
const status = getEventStatus(event);
|
||||
const classes = {
|
||||
Upcoming:
|
||||
"bg-guild-500/10 text-guild-300 dark:text-guild-300 border border-guild-500/30",
|
||||
Ongoing:
|
||||
"bg-candlelight-900/20 text-candlelight-500 dark:text-candlelight-400 border border-candlelight-700/30",
|
||||
Completed:
|
||||
"bg-guild-500/10 text-guild-400 dark:text-guild-400 border border-guild-500/30",
|
||||
};
|
||||
return (
|
||||
classes[status] ||
|
||||
"bg-guild-500/10 text-guild-400 dark:text-guild-400 border border-guild-500/30"
|
||||
);
|
||||
};
|
||||
|
||||
const getEventTimelineColor = (event) => {
|
||||
const status = getEventStatus(event);
|
||||
const classes = {
|
||||
Upcoming:
|
||||
"bg-guild-500/10 text-guild-300 dark:text-guild-300 border-guild-500/30",
|
||||
Ongoing:
|
||||
"bg-candlelight-900/20 text-candlelight-500 dark:text-candlelight-400 border-candlelight-700/30",
|
||||
Completed:
|
||||
"bg-earth-900/20 text-earth-400 dark:text-earth-400 border-earth-700/30",
|
||||
};
|
||||
return (
|
||||
classes[status] ||
|
||||
"bg-guild-500/10 text-guild-400 dark:text-guild-400 border-guild-500/30"
|
||||
);
|
||||
};
|
||||
|
||||
// SEO Meta
|
||||
useHead(() => {
|
||||
if (!series || !series.value) {
|
||||
return {
|
||||
title: "Event Series - Ghost Guild",
|
||||
meta: [
|
||||
{
|
||||
name: "description",
|
||||
content:
|
||||
"Explore our multi-event series designed for learning and growth",
|
||||
},
|
||||
],
|
||||
};
|
||||
const now = new Date()
|
||||
const start = new Date(event.startDate)
|
||||
const end = new Date(event.endDate)
|
||||
if (now < start) return 'Upcoming'
|
||||
if (now >= start && now <= end) return 'Ongoing'
|
||||
return 'Completed'
|
||||
}
|
||||
|
||||
return {
|
||||
title: `${series.value.title} - Event Series - Ghost Guild`,
|
||||
meta: [
|
||||
{
|
||||
name: "description",
|
||||
content:
|
||||
series.value.description ||
|
||||
"Explore our multi-event series designed for learning and growth",
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
useHead(() => ({
|
||||
title: series.value ? `${series.value.title} - Event Series - Ghost Guild` : 'Event Series - Ghost Guild',
|
||||
meta: [{ name: 'description', content: series.value?.description || 'Multi-event series' }],
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
.loading { padding: 48px 32px; color: var(--text-dim); }
|
||||
.loading h2 { font-family: 'Brygada 1918', serif; font-size: 22px; color: var(--text-bright); margin-bottom: 8px; }
|
||||
|
||||
.back-link { padding: 12px 32px; border-bottom: 1px dashed var(--border); font-size: 12px; }
|
||||
.back-link a { color: var(--candle); text-decoration: none; }
|
||||
|
||||
.series-header {
|
||||
padding: 28px 32px;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
.series-header h1 {
|
||||
font-family: 'Brygada 1918', serif;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--text-bright);
|
||||
line-height: 1.15;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.series-meta-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.meta-text { color: var(--text-faint); }
|
||||
|
||||
.section {
|
||||
padding: 24px 32px;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
.section p { font-size: 12px; color: var(--text-dim); line-height: 1.7; max-width: 560px; margin-bottom: 8px; }
|
||||
.section a { font-size: 12px; color: var(--candle); }
|
||||
|
||||
.event-row {
|
||||
display: grid;
|
||||
grid-template-columns: 32px 80px 1fr;
|
||||
gap: 12px;
|
||||
align-items: baseline;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
font-size: 12px;
|
||||
}
|
||||
.event-row:last-child { border-bottom: none; }
|
||||
.event-num { color: var(--text-faint); font-size: 11px; }
|
||||
.event-date { color: var(--text-faint); }
|
||||
.event-title-link { color: var(--text); text-decoration: none; font-size: 13px; }
|
||||
.event-title-link:hover { color: var(--candle); }
|
||||
.event-status { font-size: 10px; color: var(--text-faint); margin-left: 8px; }
|
||||
|
||||
.pass-price {
|
||||
font-family: 'Brygada 1918', serif;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--candle);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.empty { font-size: 12px; color: var(--text-faint); }
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue