356 lines
8.9 KiB
Vue
356 lines
8.9 KiB
Vue
<template>
|
|
<div>
|
|
<!-- HERO -->
|
|
<div class="hero">
|
|
<h1>Ghost Guild is where game developers explore cooperative models.</h1>
|
|
<p>
|
|
Resources, events, and a community of people figuring it out. Three
|
|
circles, pay what you can.
|
|
</p>
|
|
<div class="hero-links">
|
|
<NuxtLink to="/join" class="hero-link primary"
|
|
>Become a member</NuxtLink
|
|
>
|
|
<a href="https://wiki.ghostguild.org" class="hero-link"
|
|
>Read the wiki</a
|
|
>
|
|
<NuxtLink to="/about" class="hero-link">About the Guild</NuxtLink>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- UPCOMING EVENTS + WIKI (full-bleed row dividers: border on full-width row, padding on inset only) -->
|
|
<div class="content-row two-col">
|
|
<div class="content-block">
|
|
<div class="block-inset">
|
|
<div class="label">Upcoming Events</div>
|
|
</div>
|
|
<div v-if="events?.length" class="event-list">
|
|
<div v-for="event in events" :key="event._id" class="event-item">
|
|
<div class="block-inset event-item-inner">
|
|
<span class="event-date">{{ formatDate(event.startDate) }}</span>
|
|
<span class="event-title">
|
|
<NuxtLink :to="`/events/${event.slug || event._id}`">{{
|
|
event.title
|
|
}}</NuxtLink>
|
|
</span>
|
|
<CircleBadge v-if="event.circle" :circle="event.circle" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="block-inset">
|
|
<p class="empty">No upcoming events</p>
|
|
</div>
|
|
</div>
|
|
<div class="content-block">
|
|
<div class="block-inset">
|
|
<div class="label">Recently in the Wiki</div>
|
|
</div>
|
|
<div v-if="wikiArticles?.length" class="wiki-list">
|
|
<div
|
|
v-for="article in wikiArticles"
|
|
:key="article._id"
|
|
class="wiki-item"
|
|
>
|
|
<div class="block-inset wiki-item-inner">
|
|
<a :href="article.url" target="_blank">{{ article.title }}</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="block-inset">
|
|
<p class="empty">
|
|
<a href="https://wiki.ghostguild.org">Browse the wiki →</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PARCHMENT INSET -->
|
|
<ParchmentInset>
|
|
<div
|
|
class="label"
|
|
style="color: var(--candle-faint); margin-bottom: 12px"
|
|
>
|
|
From the Wiki
|
|
</div>
|
|
<template v-if="hasCustomWikiFeature">
|
|
<h2>{{ wikiFeature.title || DEFAULT_WIKI_FEATURE_TITLE }}</h2>
|
|
<p v-for="(para, i) in customWikiParagraphs" :key="i">{{ para }}</p>
|
|
</template>
|
|
<template v-else>
|
|
<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="https://wiki.ghostguild.org">practicing together</a>.
|
|
</p>
|
|
</template>
|
|
<p>
|
|
<a href="https://wiki.ghostguild.org">Read more in the wiki →</a>
|
|
</p>
|
|
</ParchmentInset>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
definePageMeta({
|
|
layout: "default",
|
|
});
|
|
|
|
const { data: events } = await useFetch("/api/events", {
|
|
query: { limit: 4, upcoming: true },
|
|
default: () => [],
|
|
});
|
|
|
|
const { data: wikiArticles } = await useFetch("/api/wiki/recent", {
|
|
query: { limit: 4 },
|
|
default: () => [],
|
|
});
|
|
|
|
const DEFAULT_WIKI_FEATURE_TITLE = "What is a cooperative studio?";
|
|
|
|
const { data: wikiFeature } = await useFetch(
|
|
"/api/site-content/homepage.wiki_feature",
|
|
{ default: () => ({ title: "", body: "" }) },
|
|
);
|
|
|
|
const hasCustomWikiFeature = computed(() => !!wikiFeature.value?.body?.trim());
|
|
|
|
const customWikiParagraphs = computed(() => {
|
|
const body = wikiFeature.value?.body?.trim() || "";
|
|
return body
|
|
.split(/\n{2,}/)
|
|
.map((p) => p.trim())
|
|
.filter(Boolean);
|
|
});
|
|
|
|
const circleData = [
|
|
{
|
|
value: "community",
|
|
label: "Community",
|
|
metaphor: "The open hall",
|
|
blurb:
|
|
"For anyone exploring cooperative models in game development. Solo devs, researchers, students, people who just heard about this and want to know more.",
|
|
},
|
|
{
|
|
value: "founder",
|
|
label: "Founder",
|
|
metaphor: "The workshop",
|
|
blurb:
|
|
"For people actively building cooperative studios. You're working through governance, legal structure, revenue sharing, and all the hard parts.",
|
|
},
|
|
{
|
|
value: "practitioner",
|
|
label: "Practitioner",
|
|
metaphor: "The alcove",
|
|
blurb:
|
|
"Where experience is shared and knowledge given back. You're here to support newcomers, help shape the Cooperative Foundations program, and find peers.",
|
|
},
|
|
];
|
|
|
|
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));
|
|
align-items: stretch;
|
|
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;
|
|
align-self: stretch;
|
|
}
|
|
.content-row.two-col .content-block {
|
|
padding: 24px 0;
|
|
}
|
|
.content-row.two-col .block-inset {
|
|
padding-left: 28px;
|
|
padding-right: 28px;
|
|
}
|
|
.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;
|
|
}
|
|
|
|
/* ---- EVENT LIST ---- */
|
|
.event-item {
|
|
border-bottom: 1px dashed var(--border);
|
|
}
|
|
.event-list .event-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.event-item-inner {
|
|
display: grid;
|
|
grid-template-columns: 60px 1fr auto;
|
|
gap: 16px;
|
|
align-items: baseline;
|
|
padding-top: 10px;
|
|
padding-bottom: 10px;
|
|
transition: padding-left 0.2s;
|
|
}
|
|
.content-row.two-col .event-item:hover .event-item-inner {
|
|
padding-left: calc(28px + 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 {
|
|
border-bottom: 1px dashed var(--border);
|
|
font-size: 13px;
|
|
}
|
|
.wiki-list .wiki-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.wiki-item-inner {
|
|
padding-top: 8px;
|
|
padding-bottom: 8px;
|
|
}
|
|
.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>
|