app/pages/templates/membership-agreement.vue

2722 lines
79 KiB
Vue

<template>
<div
class="template-wrapper bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100">
<!-- Export Controls -->
<div class="export-controls no-print no-pdf">
<div class="export-content">
<div class="export-section">
<h3 class="export-title">Export to:</h3>
<div class="export-buttons">
<button
@click="exportPDF"
class="export-btn primary"
title="Download PDF">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<path
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14,2 14,8 20,8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10,9 9,9 8,9" />
</svg>
PDF
</button>
<button
@click="handlePrint"
class="export-btn"
title="Print Document">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<polyline points="6,9 6,2 18,2 18,9" />
<path
d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" />
<rect x="6" y="14" width="12" height="8" />
</svg>
PRINT
</button>
<button
@click="exportText"
class="export-btn"
title="Download Text">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<path
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14,2 14,8 20,8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
<line x1="12" y1="9" x2="8" y2="9" />
</svg>
TXT
</button>
<button
@click="exportMarkdown"
class="export-btn"
title="Download Markdown">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<path
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14,2 14,8 20,8" />
<line x1="12" y1="18" x2="12" y2="12" />
<path d="m9 15 3-3 3 3" />
</svg>
MD
</button>
</div>
</div>
</div>
</div>
<!-- Document Container -->
<div
class="document-page max-w-[8.5in] min-h-[11in] mx-auto bg-white relative p-[0.5in]">
<div class="template-content">
<!-- Document Title -->
<div class="text-center mb-8">
<h1
class="text-3xl md:text-5xl font-bold uppercase text-neutral-900 dark:text-white m-0 py-4 border-t-2 border-b-2 border-neutral-900 dark:border-neutral-100"
:data-coop-name="formData.cooperativeName || 'Worker Cooperative'">
MEMBERSHIP AGREEMENT
</h1>
</div>
<!-- Section 1: Who We Are -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
1. Who We Are
</h2>
<div class="space-y-8">
<UFormField label="Cooperative Name" class="form-group-large">
<UInput
v-model="formData.cooperativeName"
placeholder="Enter cooperative name"
size="xl"
class="w-full"
@input="debouncedAutoSave"
@change="autoSave" />
</UFormField>
<UFormField label="Date Established" class="form-group-large">
<UInput
v-model="formData.dateEstablished"
type="date"
size="xl"
class="large-field"
@change="autoSave" />
</UFormField>
<UFormField label="Our Purpose" class="form-group-large">
<UTextarea
v-model="formData.purpose"
:rows="3"
placeholder="Write 1-2 sentences about what your cooperative does and why it exists"
size="xl"
class="large-field"
@input="debouncedAutoSave"
@change="autoSave" />
</UFormField>
<UFormField label="Our Core Values" class="form-group-large">
<UTextarea
v-model="formData.coreValues"
:rows="4"
placeholder="List 3-5 values that guide everything you do. Examples: mutual care, creative sustainability, economic justice, collective liberation"
size="xl"
class="large-field"
@input="debouncedAutoSave"
@change="autoSave" />
</UFormField>
<div class="form-group-large">
<div class="flex items-center justify-between mb-4">
<label
class="text-lg font-semibold text-neutral-900 dark:text-neutral-100"
>Current Members</label
>
<UButton
@click="addMember"
size="sm"
color="primary"
variant="outline"
icon="i-heroicons-plus">
Add Member
</UButton>
</div>
<div class="space-y-4">
<div
v-for="(member, index) in formData.members"
:key="index"
class="border border-neutral-600 rounded-lg p-4">
<div class="flex items-start justify-between mb-3">
<h4
class="font-medium text-neutral-900 dark:text-neutral-100">
Member {{ index + 1 }}
</h4>
<UButton
v-if="formData.members.length > 1"
@click="removeMember(index)"
size="sm"
color="red"
variant="ghost"
icon="i-heroicons-trash">
</UButton>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<UFormField label="Full Name" class="form-group-large">
<UInput
v-model="member.name"
placeholder="Enter full name"
size="xl"
class="large-field"
@input="debouncedAutoSave"
@change="autoSave" />
</UFormField>
<UFormField label="Email" class="form-group-large">
<UInput
v-model="member.email"
type="email"
placeholder="Enter email address"
size="xl"
class="large-field"
@input="debouncedAutoSave"
@change="autoSave" />
</UFormField>
<UFormField label="Join Date" class="form-group-large">
<UInput
v-model="member.joinDate"
type="date"
size="xl"
class="large-field"
@change="autoSave" />
</UFormField>
<UFormField
label="Current Role (Optional)"
class="form-group-large">
<UInput
v-model="member.role"
placeholder="e.g., Coordinator, Developer, etc."
size="xl"
class="large-field"
@input="debouncedAutoSave"
@change="autoSave" />
</UFormField>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Section 2: Membership -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
2. Membership
</h2>
<div class="space-y-4">
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Who Can Be a Member
</h3>
<p class="content-paragraph mb-3 leading-relaxed text-left">
Any person who:
</p>
<ul class="content-list my-2 pl-6 list-disc">
<li>Shares our values and purpose</li>
<li>
Contributes labour to the cooperative (by doing actual work,
not just investing money)
</li>
<li>Commits to collective decision-making</li>
<li>Participates in governance responsibilities</li>
</ul>
</div>
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Becoming a Member
</h3>
<p class="content-paragraph">
New members join through a consent process, which means existing
members must agree that adding this person won't harm the
cooperative.
</p>
<ol class="content-list numbered my-2 pl-6 list-decimal">
<li class="flex items-baseline gap-2 flex-wrap">
Trial period of
<UInput
v-model="formData.trialPeriodMonths"
type="number"
placeholder="3"
class="inline-field number-field"
@change="autoSave" />
months working together
</li>
<li>Values alignment conversation</li>
<li>Consent decision by current members</li>
<li class="flex items-baseline gap-2 flex-wrap">
Optional - Equal buy-in contribution of $<UInput
v-model="formData.buyInAmount"
type="number"
placeholder="1000"
class="inline-field number-field"
@change="autoSave" />
(can be paid over time or waived based on need)
</li>
</ol>
</div>
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Leaving the Cooperative
</h3>
<p class="content-paragraph flex items-baseline gap-2 flex-wrap">
Members can leave anytime with
<UInput
v-model="formData.noticeDays"
type="number"
placeholder="30"
class="inline-field number-field"
@change="autoSave" />
days notice. The cooperative will:
</p>
<ul class="content-list my-2 pl-6 list-disc">
<li class="flex items-baseline gap-2 flex-wrap">
Pay out their share of any surplus within
<UInput
v-model="formData.surplusPayoutDays"
type="number"
placeholder="30"
class="inline-field number-field"
@change="autoSave" />
days
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Return their buy-in contribution within
<UInput
v-model="formData.buyInReturnDays"
type="number"
placeholder="90"
class="inline-field number-field"
@change="autoSave" />
days
</li>
<li>Maintain respectful ongoing relationships when possible</li>
</ul>
</div>
</div>
</div>
<!-- Section 3: How We Make Decisions -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
3. How We Make Decisions
</h2>
<div class="space-y-4">
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Consent-Based Decisions
</h3>
<p class="content-paragraph mb-3 leading-relaxed text-left">
We use consent, not consensus. This means we move forward when
no one has a principled objection that would harm the
cooperative. An objection must explain how the proposal would
contradict our values or threaten our sustainability.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Day-to-Day Decisions
</h3>
<p
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
Decisions under $<UInput
v-model="formData.dayToDayLimit"
type="number"
placeholder="100"
class="inline-field number-field"
@change="autoSave" />
can be made by any member. Just tell others what you did at
the next meeting.
</p>
</div>
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Regular Decisions
</h3>
<p
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
Decisions between $<UInput
v-model="formData.regularDecisionMin"
type="number"
placeholder="100"
class="inline-field number-field"
@change="autoSave" />
and $<UInput
v-model="formData.regularDecisionMax"
type="number"
placeholder="1000"
class="inline-field number-field"
@change="autoSave" />
need consent from members present at a meeting (minimum 2
members).
</p>
</div>
</div>
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Major Decisions
</h3>
<p class="content-paragraph mb-3 leading-relaxed text-left">
These require consent from all members:
</p>
<ul class="content-list my-2 pl-6 list-disc">
<li>Adding or removing members</li>
<li>Changing this agreement</li>
<li class="flex items-baseline gap-2 flex-wrap">
Taking on debt over $<UInput
v-model="formData.majorDebtThreshold"
type="number"
placeholder="5000"
class="inline-field number-field"
@change="autoSave" />
</li>
<li>Fundamental changes to our purpose or structure</li>
<li>Dissolution of the cooperative</li>
</ul>
</div>
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Meeting Structure
</h3>
<ul class="content-list my-2 pl-6 list-disc">
<li class="flex items-baseline gap-2 flex-wrap">
Regular meetings happen
<UInput
v-model="formData.meetingFrequency"
placeholder="weekly"
class="inline-field"
@change="autoSave" />
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Emergency meetings need
<UInput
v-model="formData.emergencyNoticeHours"
type="number"
placeholder="24"
class="inline-field number-field"
@change="autoSave" />
hours notice
</li>
<li>We rotate who facilitates meetings</li>
<li>Decisions and reasoning get documented in shared notes</li>
</ul>
</div>
</div>
</div>
<!-- Section 4: Money and Labour -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
4. Money and Labour
</h2>
<div class="space-y-4">
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Equal Ownership
</h3>
<p class="content-paragraph mb-3 leading-relaxed text-left">
Each member owns an equal share of the cooperative, regardless
of hours worked or tenure.
</p>
</div>
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Paying Ourselves
</h3>
<ul class="content-list my-2 pl-6 list-disc">
<li class="flex items-baseline gap-2 flex-wrap">
Base rate: $<UInput
v-model="formData.baseRate"
type="number"
placeholder="25"
class="inline-field number-field"
@change="autoSave" />/hour for all members
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Or: Equal monthly draw of $<UInput
v-model="formData.monthlyDraw"
type="number"
placeholder="2000"
class="inline-field number-field"
@change="autoSave" />
per member
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Paid on the
<USelect
v-model="formData.paymentDay"
:items="dayOptions"
placeholder="15"
class="inline-field"
@change="autoSave" />
of each month
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Surplus (profit) distributed equally every
<UInput
v-model="formData.surplusFrequency"
placeholder="quarter"
class="inline-field"
@change="autoSave" />
</li>
</ul>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Work Expectations
</h3>
<ul class="content-list my-2 pl-6 list-disc">
<li class="flex items-baseline gap-2 flex-wrap">
Target hours per week:
<UInput
v-model="formData.targetHours"
type="number"
placeholder="40"
class="inline-field number-field"
@change="autoSave" />
(flexible based on capacity)
</li>
<li>We explicitly reject crunch culture</li>
<li>Members communicate their capacity openly</li>
<li>
We adjust workload collectively when someone needs reduced
hours
</li>
</ul>
</div>
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Financial Transparency
</h3>
<ul class="content-list my-2 pl-6 list-disc">
<li>All members can access all financial records anytime</li>
<li>Monthly financial check-ins at meetings</li>
<li>
Quarterly reviews of our runway (how many months we can
operate)
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Section 5: Roles and Responsibilities -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
5. Roles and Responsibilities
</h2>
<div class="space-y-4">
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Rotating Roles
</h3>
<p
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
We rotate operational roles every
<UInput
v-model="formData.roleRotationMonths"
type="number"
placeholder="6"
class="inline-field number-field"
@change="autoSave" />
months. Current roles include:
</p>
<ul class="content-list">
<li>
Financial coordinator (handles bookkeeping, not financial
decisions)
</li>
<li>Meeting facilitator</li>
<li>External communications</li>
<li>Others</li>
</ul>
</div>
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Shared Responsibilities
</h3>
<p class="content-paragraph mb-3 leading-relaxed text-left">
All members participate in:
</p>
<ul class="content-list my-2 pl-6 list-disc">
<li>Governance and decision-making</li>
<li>Strategic planning</li>
<li>Mutual support and care</li>
</ul>
</div>
</div>
</div>
<!-- Section 6: Conflict and Care -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
6. Conflict and Care
</h2>
<div class="space-y-4">
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
When Conflict Happens
</h3>
<ol class="content-list numbered my-2 pl-6 list-decimal">
<li>Direct conversation between parties (if comfortable)</li>
<li>Mediation with another member</li>
<li>Full group discussion with structured process</li>
<li>External mediation if needed</li>
</ol>
</div>
<div>
<h3
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Care Commitments
</h3>
<ul class="content-list my-2 pl-6 list-disc">
<li>We check in about capacity and wellbeing regularly</li>
<li>We honour diverse access needs</li>
<li>We maintain flexibility for life circumstances</li>
<li>We contribute to mutual aid when members face hardship</li>
</ul>
</div>
</div>
</div>
<!-- Section 7: Changing This Agreement -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
7. Changing This Agreement
</h2>
<p
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
This is a living document. We review it every
<UInput
v-model="formData.reviewFrequency"
placeholder="year"
class="inline-field"
@change="autoSave" />
and update it through our consent process. Small clarifications can
happen anytime; structural changes need full member consent.
</p>
</div>
<!-- Section 8: If We Need to Close -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
8. If We Need to Close
</h2>
<div class="space-y-4">
<p class="content-paragraph mb-3 leading-relaxed text-left">
If the cooperative dissolves:
</p>
<ol class="content-list numbered my-2 pl-6 list-decimal">
<li>Pay all debts and obligations</li>
<li>Return member contributions</li>
<li>Distribute remaining assets equally among members</li>
<li class="flex items-baseline gap-2 flex-wrap">
Or donate remaining assets to
<UInput
v-model="formData.assetDonationTarget"
placeholder="Enter organization name"
class="inline-field wide-field" />
</li>
</ol>
</div>
</div>
<!-- Section 9: Legal Bits -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
9. Legal Bits
</h2>
<div class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<UFormField label="Legal Structure" class="form-group-block">
<UInput
v-model="formData.legalStructure"
size="xl"
class="w-full"
placeholder="Cooperative corporation, LLC, partnership, etc." />
</UFormField>
<UFormField label="Registered in" class="form-group-inline">
<UInput
v-model="formData.registeredLocation"
placeholder="State/Province"
size="xl"
class="inline-field w-full"
@change="autoSave" />
</UFormField>
<div class="fiscal-year-group">
<UFormField label="Fiscal Year-End" class="form-group-inline">
<div class="flex gap-2 items-center">
<USelect
v-model="formData.fiscalYearEndMonth"
:items="monthOptions"
placeholder="Month"
size="xl"
class="w-60"
@change="autoSave" />
<USelect
v-model="formData.fiscalYearEndDay"
:items="dayOptions"
placeholder="Day"
size="xl"
class="w-40"
@change="autoSave" />
</div>
</UFormField>
</div>
</div>
<p class="content-paragraph mb-3 leading-relaxed text-left">
This agreement works alongside but doesn't replace our legal
incorporation documents. Where they conflict, we follow the law
but work to align our legal structure with our values.
</p>
</div>
</div>
<!-- Section 10: Agreement Review -->
<div class="section-card mb-6 p-0">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
10. Agreement Review
</h2>
<div class="space-y-4">
<p class="content-paragraph mb-3 leading-relaxed text-left">
By using this agreement, we commit to these principles and to
showing up for each other.
</p>
<div
class="bg-neutral-50 dark:bg-neutral-900 p-4 rounded-md border-l-4 border-emerald-300">
<p
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
This agreement was last updated on
<UInput
v-model="formData.lastUpdated"
type="date"
class="inline-field"
@change="autoSave" />. We commit to reviewing it on
<UInput
v-model="formData.nextReview"
type="date"
class="inline-field"
@change="autoSave" />
or sooner if circumstances require.
</p>
</div>
<div
class="signature-space mt-8 p-8 border border-dashed border-neutral-300 rounded-md bg-neutral-50 dark:bg-neutral-950">
<p
class="content-paragraph mb-3 leading-relaxed text-center text-neutral-600 italic">
[Space for member signatures when printed]
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
definePageMeta({
layout: false,
});
// Import PDF export composable
const { exportToPDF } = usePdfExportBasic();
const monthOptions = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const dayOptions = Array.from({ length: 31 }, (_, i) => (i + 1).toString());
const formData = ref({
cooperativeName: "",
dateEstablished: "",
purpose: "",
coreValues: "",
members: [{ name: "", email: "", joinDate: "", role: "" }],
trialPeriodMonths: 3,
buyInAmount: "",
noticeDays: 30,
surplusPayoutDays: 30,
buyInReturnDays: 90,
dayToDayLimit: 100,
regularDecisionMin: 100,
regularDecisionMax: 1000,
majorDebtThreshold: 5000,
meetingFrequency: "weekly",
emergencyNoticeHours: 24,
baseRate: 25,
monthlyDraw: "",
paymentDay: 15,
surplusFrequency: "quarter",
targetHours: 40,
roleRotationMonths: 6,
reviewFrequency: "year",
assetDonationTarget: "",
legalStructure: "",
registeredLocation: "",
fiscalYearEndMonth: "December",
fiscalYearEndDay: 31,
lastUpdated: new Date().toISOString().split("T")[0],
nextReview: "",
});
// Load saved data immediately (before watchers)
const loadSavedData = () => {
const saved = localStorage.getItem("membership-agreement-data");
if (saved) {
try {
const parsedData = JSON.parse(saved);
formData.value = { ...formData.value, ...parsedData };
console.log("Loaded saved data:", parsedData);
} catch (error) {
console.error("Error loading saved data:", error);
}
}
};
// Load data immediately
loadSavedData();
// Auto-save to localStorage (removed immediate to prevent overwriting)
watch(
formData,
(newData) => {
localStorage.setItem("membership-agreement-data", JSON.stringify(newData));
console.log("Auto-saved data");
},
{ deep: true }
);
// Also ensure data is loaded on mount as backup
onMounted(() => {
loadSavedData();
});
// Auto-save individual field changes immediately
const autoSave = () => {
localStorage.setItem(
"membership-agreement-data",
JSON.stringify(formData.value)
);
console.log("Manual auto-save triggered:", formData.value);
};
// Debounced auto-save for better performance
const debouncedAutoSave = debounce(autoSave, 300);
// Helper function for debouncing
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Member management functions
const addMember = () => {
formData.value.members.push({ name: "", email: "", joinDate: "", role: "" });
debouncedAutoSave();
};
const removeMember = (index) => {
if (formData.value.members.length > 1) {
formData.value.members.splice(index, 1);
debouncedAutoSave();
}
};
// Print function with proper form value handling
const handlePrint = () => {
// Create print-friendly version by replacing inputs with their values from formData
const inputs = document.querySelectorAll("input, textarea, select");
const printValues = [];
// Also create signature section replacement
const signatureSpace = document.querySelector(".signature-space");
let signatureReplacement = null;
if (signatureSpace) {
const signatureDiv = document.createElement("div");
signatureDiv.className = "print-signatures";
signatureDiv.innerHTML = `
<p style="font-weight: bold; margin-bottom: 12pt; color: #000;">Member Signatures</p>
<p style="margin-bottom: 16pt; color: #000;">By signing below, we agree to these terms and commit to working together as equals in this cooperative.</p>
`;
// Add signature lines for each member
const membersWithNames =
formData.value.members?.filter((m) => m.name) || [];
const numSignatures = Math.max(
2,
Math.min(8, membersWithNames.length || 4)
);
for (let i = 0; i < numSignatures; i++) {
const memberName = membersWithNames[i]?.name || "";
const sigDiv = document.createElement("div");
sigDiv.style.marginBottom = "24pt";
sigDiv.innerHTML = `
<p style="margin-bottom: 8pt; font-weight: bold; color: #000;">${
memberName || `Member ${i + 1}:`
}</p>
<div style="border-bottom: 1pt solid #000; width: 300pt; height: 16pt; margin-bottom: 4pt;"></div>
<p style="font-size: 9pt; color: #666;">Signature &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Date: ___________</p>
`;
signatureDiv.appendChild(sigDiv);
}
signatureReplacement = signatureDiv;
signatureSpace.style.display = "none";
signatureSpace.parentNode.insertBefore(signatureDiv, signatureSpace);
}
inputs.forEach((input, index) => {
// Get value from the actual formData reactive object instead of DOM
let value = "";
// Try to match the input to formData properties
if (input.closest('[label="Cooperative Name"]'))
value = formData.value.cooperativeName;
else if (input.closest('[label="Date Established"]'))
value = formData.value.dateEstablished;
else if (input.closest('[label="Our Purpose"]'))
value = formData.value.purpose;
else if (input.closest('[label="Our Core Values"]'))
value = formData.value.coreValues;
else if (input.closest('[label="Legal Structure"]'))
value = formData.value.legalStructure;
else if (input.closest('[label="Registered in"]'))
value = formData.value.registeredLocation;
else if (input.closest('[label="Fiscal Year-End"]')) {
if (input.tagName === "SELECT" || input.type === "select-one") {
value = formData.value.fiscalYearEndMonth;
} else {
value = formData.value.fiscalYearEndDay;
}
}
// Handle member fields
else if (input.closest(".border-neutral-200")) {
const memberCard = input.closest(".border-neutral-200");
const memberIndex = Array.from(memberCard.parentNode.children).indexOf(
memberCard
);
const member = formData.value.members?.[memberIndex];
if (member) {
if (input.closest('[label="Full Name"]')) value = member.name;
else if (input.closest('[label="Email"]')) value = member.email;
else if (input.closest('[label="Join Date"]')) value = member.joinDate;
else if (input.closest('[label="Current Role (Optional)"]'))
value = member.role;
}
}
// Fallback to input.value
else {
value = input.value?.trim() || "";
}
// Create a span element to replace the input for print
const printSpan = document.createElement("span");
printSpan.className = "print-value";
if (value) {
// Has a value - show it in bold
printSpan.textContent = value;
printSpan.style.fontWeight = "bold";
} else {
// Empty field - show as blank line
printSpan.innerHTML = "&nbsp;";
printSpan.setAttribute("data-empty", "true");
}
printSpan.setAttribute("data-original-index", index);
// Store original element info for restoration
printValues.push({
element: input,
parent: input.parentNode,
nextSibling: input.nextSibling,
replacement: printSpan,
});
// Replace input with span for print
input.style.display = "none";
input.parentNode.insertBefore(printSpan, input);
});
// Trigger print
setTimeout(() => {
window.print();
// Restore original inputs after print dialog
setTimeout(() => {
printValues.forEach((item) => {
item.element.style.display = "";
if (item.replacement.parentNode) {
item.replacement.parentNode.removeChild(item.replacement);
}
});
// Restore signature section
if (signatureReplacement && signatureSpace) {
signatureSpace.style.display = "";
if (signatureReplacement.parentNode) {
signatureReplacement.parentNode.removeChild(signatureReplacement);
}
}
}, 100);
}, 100);
};
// Export functions
const exportPDF = async () => {
// Only run on client side
if (process.server || typeof window === "undefined") {
console.warn("PDF export attempted on server side");
return;
}
try {
console.log("Starting PDF export...");
// Get the document title for filename if available
const cooperativeName = formData.value.cooperativeName || "document";
const filename = `${cooperativeName.replace(
/[^a-zA-Z0-9]/g,
"_"
)}_membership_agreement.pdf`;
console.log("Generated filename:", filename);
await exportToPDF(".document-page", filename);
console.log("PDF export completed successfully");
} catch (error) {
console.error("PDF export failed:", error);
// Show more specific error message
const errorMessage = error?.message || "Unknown error occurred";
alert(
`PDF generation failed: ${errorMessage}\n\nFalling back to print dialog.`
);
// Fallback to print dialog if PDF generation fails
window.print();
}
};
const exportText = () => {
const content = extractTextContent();
downloadFile(content, "membership_agreement.txt", "text/plain");
};
const exportMarkdown = () => {
const content = convertToMarkdown();
downloadFile(content, "membership_agreement.md", "text/markdown");
};
const extractTextContent = () => {
// Format date helper
const formatDate = (dateStr) => {
if (!dateStr) return "[_____]";
return new Date(dateStr).toLocaleDateString();
};
// Build the document content with actual data
const content = `
MEMBERSHIP AGREEMENT
====================
1. Who We Are
-------------
Cooperative Name: ${formData.value.cooperativeName || "[Cooperative Name]"}
Date Established: ${formatDate(formData.value.dateEstablished)}
Our Purpose: ${formData.value.purpose || "[Purpose]"}
Our Core Values: ${formData.value.coreValues || "[Core Values]"}
Current Members:
${
formData.value.members && formData.value.members.some((m) => m.name)
? formData.value.members
.map((member, index) =>
member.name
? `${index + 1}. ${member.name}${
member.email ? ` (${member.email})` : ""
}${
member.joinDate
? ` - Joined: ${formatDate(member.joinDate)}`
: ""
}${member.role ? ` - Role: ${member.role}` : ""}`
: ""
)
.filter(Boolean)
.join("\n")
: "[No members added yet]"
}
2. Membership
-------------
Who Can Be a Member
Any person who:
• Shares our values and purpose
• Contributes labour to the cooperative (by doing actual work, not just investing money)
• Commits to collective decision-making
• Participates in governance responsibilities
Becoming a Member
New members join through a consent process, which means existing members must agree that adding this person won't harm the cooperative.
1. Trial period of ${
formData.value.trialPeriodMonths || "[___]"
} months working together
2. Values alignment conversation
3. Consent decision by current members
4. Optional - Equal buy-in contribution of $${
formData.value.buyInAmount || "[___]"
} (can be paid over time or waived based on need)
Leaving the Cooperative
Members can leave anytime with ${
formData.value.noticeDays || "[___]"
} days notice. The cooperative will:
• Pay out their share of any surplus within ${
formData.value.surplusPayoutDays || "[___]"
} days
• Return their buy-in contribution within ${
formData.value.buyInReturnDays || "[___]"
} days
• Maintain respectful ongoing relationships when possible
3. How We Make Decisions
------------------------
Consent-Based Decisions
We use consent, not consensus. This means we move forward when no one has a principled objection that would harm the cooperative. An objection must explain how the proposal would contradict our values or threaten our sustainability.
Day-to-Day Decisions
Decisions under $${
formData.value.dayToDayLimit || "[___]"
} can be made by any member. Just tell others what you did at the next meeting.
Regular Decisions
Decisions between $${formData.value.regularDecisionMin || "[___]"} and $${
formData.value.regularDecisionMax || "[___]"
} need consent from members present at a meeting (minimum 2 members).
Major Decisions
These require consent from all members:
• Adding or removing members
• Changing this agreement
• Taking on debt over $${formData.value.majorDebtThreshold || "[___]"}
• Fundamental changes to our purpose or structure
• Dissolution of the cooperative
Meeting Structure
• We meet ${
formData.value.meetingFrequency || "[___]"
} to make decisions together
• Emergency meetings need ${
formData.value.emergencyNoticeHours || "[___]"
} hours notice
• All members can add items to the agenda
• We keep simple records of what we decide
4. Money and Labour
-------------------
Equal Ownership
Each member owns an equal share of the cooperative, regardless of when they joined or how much money they put in.
Paying Ourselves
• Base hourly rate: $${formData.value.baseRate || "[___]"}/hour for all members
• Monthly draw: $${
formData.value.monthlyDraw || "[___]"
} per month (if applicable)
• Payment date: ${formData.value.paymentDay || "[___]"}th of each month
• Surplus sharing: ${
formData.value.surplusFrequency || "[___]"
}ly based on hours worked
Work Expectations
• Target hours per week: ${formData.value.targetHours || "[___]"} hours
• All work counts equally - admin, client work, business development
• We track hours honestly and transparently
• Flexible scheduling based on personal needs and business requirements
Financial Transparency
• All members can access all financial records anytime
• Monthly financial updates shared with everyone
• Annual financial review conducted together
• No secret salaries or hidden expenses
5. Roles and Responsibilities
-----------------------------
Rotating Roles
We rotate operational roles every ${
formData.value.roleRotationMonths || "[___]"
} months to share knowledge and prevent burnout. Current roles include:
• Financial coordinator (bookkeeping, invoicing, payments)
• Client relationship manager (main point of contact)
• Operations coordinator (scheduling, project management)
• Business development (marketing, new client outreach)
Shared Responsibilities
All members participate in:
• Weekly planning and check-in meetings
• Monthly financial review
• Annual strategic planning
• Conflict resolution when needed
• Onboarding new members
6. Conflict and Care
--------------------
When Conflict Happens
1. Direct conversation between parties (if comfortable)
2. Bring in a neutral member as mediator
3. Full group conversation if needed
4. External mediation if we can't resolve it ourselves
5. As a last resort, consent process about membership
Care Commitments
• We check in about capacity and wellbeing regularly
• We adjust workload when someone is struggling
• We celebrate successes and support through failures
• We maintain boundaries between work and personal relationships
• We commit to direct, kind communication
7. Changing This Agreement
--------------------------
This agreement gets reviewed every ${
formData.value.reviewFrequency || "[___]"
} and can be changed anytime with consent from all members. We'll update it as we learn what works for us.
8. If We Need to Close
----------------------
If the cooperative dissolves:
• Pay all debts and obligations first
• Return member buy-ins
• Donate remaining assets to ${
formData.value.assetDonationTarget || "[organization to be determined]"
}
• Close all legal and financial accounts
• Celebrate what we built together
9. Legal Bits
-------------
Legal Structure: ${formData.value.legalStructure || "[To be determined]"}
Registered Location: ${
formData.value.registeredLocation || "[To be determined]"
}
Fiscal Year End: ${formData.value.fiscalYearEndMonth || "December"} ${
formData.value.fiscalYearEndDay || "31"
}
10. Agreement Review
--------------------
Last Updated: ${formatDate(formData.value.lastUpdated)}
Next Review: ${formatDate(formData.value.nextReview) || "[To be scheduled]"}
Member Signatures
-----------------
By signing below, we agree to these terms and commit to working together as equals in this cooperative.
${
formData.value.members && formData.value.members.some((m) => m.name)
? formData.value.members
.map((member, index) =>
member.name
? `${index + 1}. ${
member.name
}\n Signature: ___________________________ Date: ___________\n`
: ""
)
.filter(Boolean)
.join("\n")
: "Member signatures:\n\n1. ___________________________ Date: ___________\n\n2. ___________________________ Date: ___________\n"
}
`;
return content.trim();
};
const convertToMarkdown = () => {
// Format date helper
const formatDate = (dateStr) => {
if (!dateStr) return "[_____]";
return new Date(dateStr).toLocaleDateString();
};
// Build the document content with actual data in Markdown format
const content = `# MEMBERSHIP AGREEMENT
## 1. Who We Are
**Cooperative Name:** ${
formData.value.cooperativeName || "[Cooperative Name]"
}
**Date Established:** ${formatDate(formData.value.dateEstablished)}
**Our Purpose:** ${formData.value.purpose || "[Purpose]"}
**Our Core Values:** ${formData.value.coreValues || "[Core Values]"}
### Current Members
${
formData.value.members && formData.value.members.some((m) => m.name)
? formData.value.members
.map((member, index) =>
member.name
? `${index + 1}. **${member.name}**${
member.email ? ` (${member.email})` : ""
}${
member.joinDate
? ` \n Joined: ${formatDate(member.joinDate)}`
: ""
}${member.role ? ` \n Role: ${member.role}` : ""}`
: ""
)
.filter(Boolean)
.join("\n\n")
: "*No members added yet*"
}
## 2. Membership
### Who Can Be a Member
Any person who:
- Shares our values and purpose
- Contributes labour to the cooperative (by doing actual work, not just investing money)
- Commits to collective decision-making
- Participates in governance responsibilities
### Becoming a Member
New members join through a consent process, which means existing members must agree that adding this person won't harm the cooperative.
1. Trial period of **${
formData.value.trialPeriodMonths || "[___]"
}** months working together
2. Values alignment conversation
3. Consent decision by current members
4. Optional - Equal buy-in contribution of **$${
formData.value.buyInAmount || "[___]"
}** (can be paid over time or waived based on need)
### Leaving the Cooperative
Members can leave anytime with **${
formData.value.noticeDays || "[___]"
}** days notice. The cooperative will:
- Pay out their share of any surplus within **${
formData.value.surplusPayoutDays || "[___]"
}** days
- Return their buy-in contribution within **${
formData.value.buyInReturnDays || "[___]"
}** days
- Maintain respectful ongoing relationships when possible
## 3. How We Make Decisions
### Consent-Based Decisions
We use consent, not consensus. This means we move forward when no one has a principled objection that would harm the cooperative. An objection must explain how the proposal would contradict our values or threaten our sustainability.
### Day-to-Day Decisions
Decisions under **$${
formData.value.dayToDayLimit || "[___]"
}** can be made by any member. Just tell others what you did at the next meeting.
### Regular Decisions
Decisions between **$${formData.value.regularDecisionMin || "[___]"}** and **$${
formData.value.regularDecisionMax || "[___]"
}** need consent from members present at a meeting (minimum 2 members).
### Major Decisions
These require consent from all members:
- Adding or removing members
- Changing this agreement
- Taking on debt over **$${formData.value.majorDebtThreshold || "[___]"}**
- Fundamental changes to our purpose or structure
- Dissolution of the cooperative
### Meeting Structure
- We meet **${
formData.value.meetingFrequency || "[___]"
}** to make decisions together
- Emergency meetings need **${
formData.value.emergencyNoticeHours || "[___]"
}** hours notice
- All members can add items to the agenda
- We keep simple records of what we decide
## 4. Money and Labour
### Equal Ownership
Each member owns an equal share of the cooperative, regardless of when they joined or how much money they put in.
### Paying Ourselves
- Base hourly rate: **$${
formData.value.baseRate || "[___]"
}/hour** for all members
- Monthly draw: **$${
formData.value.monthlyDraw || "[___]"
}** per month (if applicable)
- Payment date: **${formData.value.paymentDay || "[___]"}th** of each month
- Surplus sharing: **${
formData.value.surplusFrequency || "[___]"
}ly** based on hours worked
### Work Expectations
- Target hours per week: **${formData.value.targetHours || "[___]"}** hours
- All work counts equally - admin, client work, business development
- We track hours honestly and transparently
- Flexible scheduling based on personal needs and business requirements
### Financial Transparency
- All members can access all financial records anytime
- Monthly financial updates shared with everyone
- Annual financial review conducted together
- No secret salaries or hidden expenses
## 5. Roles and Responsibilities
### Rotating Roles
We rotate operational roles every **${
formData.value.roleRotationMonths || "[___]"
}** months to share knowledge and prevent burnout. Current roles include:
- Financial coordinator (bookkeeping, invoicing, payments)
- Client relationship manager (main point of contact)
- Operations coordinator (scheduling, project management)
- Business development (marketing, new client outreach)
### Shared Responsibilities
All members participate in:
- Weekly planning and check-in meetings
- Monthly financial review
- Annual strategic planning
- Conflict resolution when needed
- Onboarding new members
## 6. Conflict and Care
### When Conflict Happens
1. Direct conversation between parties (if comfortable)
2. Bring in a neutral member as mediator
3. Full group conversation if needed
4. External mediation if we can't resolve it ourselves
5. As a last resort, consent process about membership
### Care Commitments
- We check in about capacity and wellbeing regularly
- We adjust workload when someone is struggling
- We celebrate successes and support through failures
- We maintain boundaries between work and personal relationships
- We commit to direct, kind communication
## 7. Changing This Agreement
This agreement gets reviewed every **${
formData.value.reviewFrequency || "[___]"
}** and can be changed anytime with consent from all members. We'll update it as we learn what works for us.
## 8. If We Need to Close
If the cooperative dissolves:
- Pay all debts and obligations first
- Return member buy-ins
- Donate remaining assets to **${
formData.value.assetDonationTarget || "[organization to be determined]"
}**
- Close all legal and financial accounts
- Celebrate what we built together
## 9. Legal Bits
**Legal Structure:** ${formData.value.legalStructure || "[To be determined]"}
**Registered Location:** ${
formData.value.registeredLocation || "[To be determined]"
}
**Fiscal Year End:** ${formData.value.fiscalYearEndMonth || "December"} ${
formData.value.fiscalYearEndDay || "31"
}
## 10. Agreement Review
**Last Updated:** ${formatDate(formData.value.lastUpdated)}
**Next Review:** ${formatDate(formData.value.nextReview) || "[To be scheduled]"}
## Member Signatures
By signing below, we agree to these terms and commit to working together as equals in this cooperative.
${
formData.value.members && formData.value.members.some((m) => m.name)
? formData.value.members
.map((member, index) =>
member.name
? `**${index + 1}. ${
member.name
}** \nSignature: ___________________________ Date: ___________\n`
: ""
)
.filter(Boolean)
.join("\n")
: "**Member signatures:**\n\n**1.** ___________________________ \nSignature: ___________________________ Date: ___________\n\n**2.** ___________________________ \nSignature: ___________________________ Date: ___________\n"
}
`;
return content.trim();
};
const downloadFile = (content, filename, mimeType) => {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
</script>
<style scoped>
/* Ubuntu font import */
@import url("https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&family=Ubuntu+Mono:wght@400;700&display=swap");
@reference "tailwindcss";
.template-content {
max-width: none;
line-height: 1.6;
color: #1f2937;
}
.section-card:last-child::after {
display: none;
}
.section-title {
font-size: 1.75rem;
font-weight: 800;
color: #111827;
margin: 0 0 1rem 0;
padding: 0;
letter-spacing: -0.025em;
}
.subsection-title {
font-size: 1.25rem;
font-weight: 600;
color: #374151;
margin: 0 0 0.75rem 0;
text-decoration: none;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 0.25rem;
}
/* Content styling */
.content-paragraph {
margin-bottom: 0.75rem;
line-height: 1.6;
text-align: left;
}
.content-list {
margin: 0.5rem 0;
padding-left: 1.5rem;
}
.content-list li {
margin-bottom: 0.5rem;
line-height: 1.5;
display: list-item;
list-style-position: outside;
}
.content-list.numbered {
list-style-type: decimal;
counter-reset: list-counter;
}
.content-list.numbered li {
list-style-type: decimal;
display: list-item;
}
.content-list:not(.numbered) {
list-style-type: disc;
}
.content-list:not(.numbered) li {
list-style-type: disc;
display: list-item;
}
/* Ensure bullets are visible even with flex items */
.content-list li.flex {
display: list-item;
}
.content-list li .flex {
display: flex;
width: 100%;
}
/* Fix flex list items to show bullets */
.content-list li[class*="flex"] {
display: list-item !important;
list-style-position: outside !important;
}
/* Ensure numbered lists show numbers even with flex */
.content-list.numbered li[class*="flex"] {
list-style-type: decimal !important;
}
/* Ensure bullet lists show bullets even with flex */
.content-list:not(.numbered) li[class*="flex"] {
list-style-type: disc !important;
}
.form-group-inline .inline-field:focus {
background: #f3f4f6;
outline: 1px solid #3b82f6;
outline-offset: -1px;
}
.form-group-block .block-field {
display: block;
width: 100%;
margin-top: 0.25rem;
border: none;
background: #f9fafb;
padding: 0.5rem;
border-radius: 0.25rem;
}
.form-group-block .block-field:focus {
background: #f3f4f6;
outline: 1px solid #3b82f6;
outline-offset: -1px;
}
.form-group-large {
margin-bottom: 1rem;
}
.form-group-large .large-field {
@apply block w-full mt-2 text-lg rounded-md border-none transition-colors duration-150 ease-in-out;
}
.form-group-large .large-field:focus {
background: #f3f4f6;
box-shadow: none;
outline: 2px solid #3b82f6;
outline-offset: -2px;
}
.number-field {
min-width: 80px !important;
text-align: center;
}
.wide-field {
min-width: 250px !important;
}
/* Fiscal year group styling */
.fiscal-year-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
.fiscal-year-group .flex {
align-items: center;
}
/* Signature space styling */
.signature-space {
margin-top: 2rem;
padding: 2rem 1rem;
border: 1px dashed #d1d5db;
border-radius: 0.375rem;
background: #f9fafb;
min-height: 4rem;
display: flex;
align-items: center;
justify-content: center;
}
/* Review dates styling */
.review-dates {
background: #f9fafb;
padding: 1rem;
border-radius: 0.375rem;
border-left: 3px solid #3b82f6;
}
/* Hide elements from print */
.no-print {
display: block;
}
/* Print optimizations */
@media print {
.no-print {
display: none !important;
}
.template-content {
font-size: 10pt;
line-height: 1.3;
}
.document-title {
font-size: 16pt;
background: none;
padding: 0.5rem 0;
border-width: 1px;
}
.section-title {
font-size: 14pt;
}
.section-card {
box-shadow: none;
border: none;
break-inside: avoid;
margin-bottom: 1rem;
}
.section-card::after {
height: 1px;
background: #000;
margin: 1rem 0;
}
.signature-space {
border: 1px solid #000;
background: none;
min-height: 3rem;
padding: 1rem;
}
.review-dates {
background: none;
border: 1px solid #000;
padding: 0.5rem;
}
.large-field,
.inline-field,
.block-field {
background: none !important;
border: none !important;
outline: 1px solid #000 !important;
padding: 0.25rem !important;
}
.font-selector {
display: none;
}
}
/* Font selector styling */
.font-selector {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 1000;
background: white;
padding: 0.75rem;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border: 1px solid #d1d5db;
display: flex;
align-items: center;
gap: 0.5rem;
}
.font-selector label {
font-size: 0.875rem;
font-weight: 500;
color: #374151;
margin: 0;
}
.font-select {
min-width: 140px;
}
/* Font families */
.template-content.font-source-serif,
.template-content.font-source-serif * {
font-family: "Source Serif 4", "Source Serif Pro", Georgia, serif !important;
}
.template-content.font-ubuntu,
.template-content.font-ubuntu * {
font-family: "Ubuntu", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
sans-serif !important;
}
.template-content.font-inter,
.template-content.font-inter * {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
sans-serif !important;
}
/* Template wrapper and document styling */
/* rely on Tailwind bg utilities applied on wrapper */
.template-wrapper {
min-height: 100vh;
padding: 2rem;
font-family: "Ubuntu", monospace;
position: relative;
}
.template-wrapper::before {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: radial-gradient(
circle at 25% 25%,
black 1px,
transparent 1px
),
radial-gradient(circle at 75% 75%, black 1px, transparent 1px);
background-size: 8px 8px, 8px 8px;
background-position: 0 0, 4px 4px;
opacity: 0.1;
pointer-events: none;
z-index: -1;
}
.document-page::before {
content: "";
position: absolute;
top: 4px;
left: 4px;
right: -4px;
bottom: -4px;
background: black;
background-image: radial-gradient(white 1px, transparent 1px);
background-size: 2px 2px;
z-index: -1;
}
.document-page::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid black;
z-index: 1;
pointer-events: none;
}
/* Export controls */
.export-controls {
max-width: 8.5in;
margin: 0 auto 1.5rem;
background: white;
border: 1px solid black;
padding: 1rem;
position: relative;
}
.export-controls::before {
content: "";
position: absolute;
top: 2px;
left: 2px;
right: -2px;
bottom: -2px;
background: black;
background-image: radial-gradient(white 1px, transparent 1px);
background-size: 2px 2px;
z-index: -1;
}
.export-content {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
align-items: center;
justify-content: space-between;
}
.export-section {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
flex: 1;
}
.export-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
}
.export-title {
font-weight: 600;
color: #374151;
margin: 0 1rem 0 0;
font-size: 0.875rem;
white-space: nowrap;
}
.separator {
width: 1px;
height: 2rem;
background: #d1d5db;
margin: 0 0.5rem;
}
.font-section {
display: flex;
align-items: center;
gap: 0.75rem;
flex-shrink: 0;
}
.font-label {
font-size: 0.875rem;
font-weight: 500;
color: #374151;
margin: 0;
}
.export-btn {
background: white;
border: 1px solid black;
padding: 0.5rem 1rem;
font-size: 0.875rem;
color: black;
cursor: pointer;
transition: all 0.1s ease;
display: flex;
align-items: center;
gap: 0.5rem;
font-family: "Ubuntu Mono", monospace;
text-transform: uppercase;
font-weight: bold;
letter-spacing: 0.5px;
}
.export-btn:hover {
background: black;
color: white;
transform: translateY(-1px) translateX(-1px);
}
.export-btn.primary {
background: black;
color: white;
}
.export-btn.primary:hover {
background: black;
transform: translateY(-1px) translateX(-1px);
}
/* Bitmap aesthetic overrides - remove all rounded corners */
* {
border-radius: 0 !important;
font-family: "Ubuntu", monospace !important;
}
/* Form fields with bitmap styling */
input,
textarea,
select {
border: 1px solid black !important;
background: white !important;
color: black !important;
font-family: "Ubuntu Mono", monospace !important;
}
input:focus,
textarea:focus,
select:focus {
outline: 2px solid black !important;
outline-offset: -2px !important;
background: white !important;
}
/* Dark mode form fields */
html.dark input,
html.dark textarea,
html.dark select {
border: 1px solid white !important;
background: #0a0a0a !important;
color: white !important;
}
html.dark input:focus,
html.dark textarea:focus,
html.dark select:focus {
outline: 2px solid white !important;
background: #0a0a0a !important;
}
/* Buttons with bitmap styling */
button:not(.export-btn) {
background: white !important;
border: 1px solid black !important;
color: black !important;
font-family: "Ubuntu Mono", monospace !important;
text-transform: uppercase !important;
font-weight: bold !important;
letter-spacing: 0.5px !important;
}
button:not(.export-btn):hover {
background: black !important;
color: white !important;
transform: translateY(-1px) translateX(-1px) !important;
}
/* Remove any card styling roundness */
.section-card,
.form-group-large {
border-radius: 0 !important;
}
/* All text */
p,
span,
div {
font-family: "Ubuntu", monospace !important;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.template-wrapper {
padding: 1rem;
}
.export-content {
flex-direction: column;
align-items: stretch;
gap: 1rem;
}
.export-section {
justify-content: center;
}
.separator {
width: 100%;
height: 1px;
margin: 0;
}
.export-buttons {
justify-content: center;
}
}
/* Professional Print Styles - Match PDF Export Quality */
@media print {
/* Page setup - professional document margins */
@page {
size: letter;
margin: 0.75in;
}
/* Global print reset */
* {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* Hide everything except the document content */
body > *:not(.template-wrapper),
#__nuxt > *:not(.template-wrapper),
.export-controls,
.no-print,
.no-pdf,
header,
nav,
footer,
.nuxt-loading,
[data-testid],
.nuxt-icon,
.app-header,
.page-header,
.brand,
.logo,
h1:contains("Urgent Tools"),
*:contains("Urgent Tools"):not(.document-title):not(.template-content *) {
display: none !important;
}
/* Specifically hide any element that contains "Urgent Tools" text but isn't part of our document */
body *:not(.template-wrapper):not(.template-wrapper *) {
visibility: hidden !important;
}
.template-wrapper,
.template-wrapper * {
visibility: visible !important;
}
/* Document structure - clean and professional */
.template-wrapper {
background: white !important;
padding: 0 !important;
min-height: auto !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif !important;
font-size: 11pt !important;
line-height: 1.6 !important;
color: #000000 !important;
}
.document-page {
max-width: none !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
box-shadow: none !important;
border-radius: 0 !important;
background: white !important;
min-height: auto !important;
}
.template-content {
background: white !important;
padding: 0 !important;
max-width: none !important;
}
/* Professional header with black background - matching PDF but smaller */
.document-page::before {
content: "";
display: block;
width: 100%;
height: 0.8in;
background: #000000 !important;
margin: 0 0 0.15in 0;
position: relative;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* Document title styling - white text on black background */
.document-title {
position: relative !important;
z-index: 1 !important;
margin-top: -0.8in !important;
margin-bottom: 0.15in !important;
padding: 0.2in 0 0.1in 0 !important;
color: white !important;
background: transparent !important;
border: none !important;
font-size: 16pt !important;
font-weight: bold !important;
text-align: center !important;
text-transform: uppercase !important;
letter-spacing: 1pt !important;
page-break-after: avoid !important;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* Add cooperative name below title */
.document-title::after {
content: attr(data-coop-name);
display: block;
font-size: 10pt;
font-weight: normal;
margin-top: 0.1in;
color: white !important;
text-transform: none;
letter-spacing: normal;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* Section headers with professional gray background */
.section-title {
font-size: 14pt !important;
font-weight: bold !important;
margin: 20pt 0 12pt 0 !important;
padding: 6pt 10pt !important;
background: #f0f0f0 !important;
border-radius: 3pt !important;
color: #000000 !important;
page-break-after: avoid !important;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* Subsection headers */
.subsection-title {
font-size: 12pt !important;
font-weight: bold !important;
margin: 14pt 0 8pt 0 !important;
color: #000000 !important;
border-bottom: 0.5pt solid #cccccc !important;
padding-bottom: 2pt !important;
page-break-after: avoid !important;
}
/* Content paragraphs */
.content-paragraph,
p {
margin: 8pt 0 !important;
text-align: justify !important;
line-height: 1.5 !important;
color: #000000 !important;
font-size: 11pt !important;
orphans: 2 !important;
widows: 2 !important;
}
/* Lists with proper spacing */
.content-list,
ul,
ol {
margin: 8pt 0 !important;
padding-left: 20pt !important;
color: #000000 !important;
}
.content-list li,
li {
margin: 4pt 0 !important;
line-height: 1.4 !important;
color: #000000 !important;
font-size: 11pt !important;
page-break-inside: avoid !important;
}
/* Section cards */
.section-card {
margin: 16pt 0 !important;
padding: 0 !important;
border: none !important;
border-radius: 0 !important;
background: white !important;
page-break-inside: avoid !important;
}
.section-card::after {
display: none !important;
}
/* CRITICAL: Form inputs styled to look like filled-in text */
input[type="text"],
input[type="number"],
input[type="date"],
input[type="email"],
textarea,
select,
.inline-field,
.large-field,
.block-field,
.number-field,
.wide-field {
/* Remove all input styling */
background: transparent !important;
border: none !important;
border-bottom: 0.75pt solid #000000 !important;
color: #000000 !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: bold !important;
padding: 0 2pt !important;
margin: 0 1pt !important;
outline: none !important;
box-shadow: none !important;
-webkit-appearance: none !important;
appearance: none !important;
display: inline !important;
vertical-align: baseline !important;
min-width: 80pt !important;
height: auto !important;
line-height: inherit !important;
}
/* Special handling for empty inputs - show as underlined blanks */
input:placeholder-shown:not([value]),
input[value=""]:not(:focus),
textarea:placeholder-shown:not([value]),
textarea[value=""]:not(:focus) {
border-bottom: 0.75pt solid #000000 !important;
min-width: 100pt !important;
height: 11pt !important;
display: inline-block !important;
}
/* Filled inputs should look like handwritten text */
input:not(:placeholder-shown),
input[value]:not([value=""]),
textarea:not(:placeholder-shown),
textarea[value]:not([value=""]),
select:not([value=""]) {
border-bottom: none !important;
font-weight: bold !important;
background: transparent !important;
}
/* Ensure input values are visible in print by using CSS content */
input[data-print-value]:after {
content: attr(data-print-value) !important;
font-weight: bold !important;
color: #000000 !important;
}
/* Textarea specific styling */
textarea {
border: none !important;
padding: 2pt !important;
margin: 0 !important;
resize: none !important;
border-radius: 0 !important;
min-height: auto !important;
line-height: 1.5 !important;
font-weight: normal !important;
vertical-align: top !important;
overflow: hidden !important;
}
/* Select dropdowns */
select {
-webkit-appearance: none !important;
appearance: none !important;
background-image: none !important;
border-bottom: none !important;
}
/* Form labels */
.form-group-large label,
label {
font-weight: bold !important;
color: #000000 !important;
display: block !important;
margin-bottom: 3pt !important;
font-size: 10pt !important;
}
.form-group-large {
margin: 10pt 0 !important;
page-break-inside: avoid !important;
}
/* Layout simplification for print */
.grid {
display: block !important;
}
.grid > div {
margin: 6pt 0 !important;
padding: 2pt 0 !important;
}
.flex {
display: block !important;
}
.flex.items-baseline {
display: inline !important;
}
.flex.items-baseline > * {
display: inline !important;
margin: 0 2pt !important;
}
.flex > *:not(.flex.items-baseline > *) {
display: block !important;
margin: 3pt 0 !important;
}
/* Spacing utilities */
.space-y-4 > * + *,
.space-y-6 > * + *,
.space-y-8 > * + * {
margin-top: 10pt !important;
}
/* Hide all buttons and UI controls */
button,
.export-btn,
.font-select,
.export-controls,
[class*="button"],
[type="button"],
[role="button"],
.no-print,
.no-pdf {
display: none !important;
}
/* Hide the "Add Member" button specifically */
button:contains("Add Member"),
[icon="i-heroicons-plus"] {
display: none !important;
}
/* Member management sections */
.border-neutral-200 {
border: none !important;
background: transparent !important;
padding: 0 !important;
margin: 10pt 0 !important;
page-break-inside: avoid !important;
}
/* Hide member management UI elements */
.border-neutral-200 button,
.border-neutral-200 .flex button,
[icon*="heroicons"],
.flex.items-start.justify-between h4,
.border-neutral-200 h4 {
display: none !important;
}
/* Member cards layout - convert to simple blocks */
.border-neutral-200 .grid {
display: block !important;
}
.border-neutral-200 .grid > * {
display: block !important;
margin: 5pt 0 !important;
}
/* Member card titles and management elements */
.border-neutral-200 .flex.items-start.justify-between {
display: none !important;
}
/* Style member information as clean blocks */
.border-neutral-200 .form-group-large {
margin: 6pt 0 !important;
}
.border-neutral-200 .form-group-large label {
font-size: 10pt !important;
font-weight: bold !important;
margin-bottom: 2pt !important;
}
/* Special areas styling */
.signature-space {
display: none !important;
}
.print-signatures {
margin-top: 20pt !important;
padding: 0 !important;
background: transparent !important;
color: #000000 !important;
}
.print-signatures p {
color: #000000 !important;
font-family: inherit !important;
line-height: 1.5 !important;
}
.print-signatures div {
page-break-inside: avoid !important;
}
.review-dates {
background: #f8f8f8 !important;
padding: 8pt !important;
border-radius: 3pt !important;
border-left: 2pt solid #3b82f6 !important;
margin: 10pt 0 !important;
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
/* Page break controls */
.page-break-before {
page-break-before: always !important;
}
.page-break-after {
page-break-after: always !important;
}
.page-break-inside-avoid {
page-break-inside: avoid !important;
}
}
/* Dark mode enhancements for readability and contrast */
html.dark .template-content {
color: #e5e7eb;
}
html.dark .document-title {
color: #f9fafb;
border-top-color: #f9fafb;
border-bottom-color: #f9fafb;
background: linear-gradient(135deg, #111827 0%, #1f2937 100%);
}
html.dark .section-card::after {
background: linear-gradient(90deg, #444 0%, #333 50%, #444 100%);
}
html.dark .section-title {
color: #f9fafb;
}
html.dark .subsection-title {
color: #e5e7eb;
border-bottom-color: #4b5563;
}
html.dark .content-paragraph {
color: #e5e7eb;
}
html.dark .content-list,
html.dark .content-list li {
color: #e5e7eb;
}
html.dark .signature-space {
border-color: #374151;
background: #0a0a0a;
}
html.dark .review-dates {
background: #0f172a;
border-left-color: #60a5fa;
}
/* Dither overlay inversion */
html.dark .template-wrapper::before {
background-image: radial-gradient(
circle at 25% 25%,
white 1px,
transparent 1px
),
radial-gradient(circle at 75% 75%, white 1px, transparent 1px);
opacity: 0.12;
}
/* Document page inversion for dark */
html.dark .document-page {
background: #0a0a0a;
}
html.dark .document-page::before {
background: white;
background-image: radial-gradient(black 1px, transparent 1px);
}
html.dark .document-page::after {
border-color: white;
}
/* Export controls */
html.dark .export-controls {
background: #0a0a0a;
border-color: white;
}
html.dark .export-controls::before {
background: white;
background-image: radial-gradient(black 1px, transparent 1px);
}
html.dark .export-title,
html.dark .font-label {
color: #e5e7eb;
}
/* Buttons in dark */
html.dark .export-btn {
background: #0a0a0a;
border-color: white;
color: white;
}
html.dark .export-btn:hover {
background: white;
color: black;
}
html.dark .export-btn.primary {
background: white;
color: black;
}
html.dark .export-btn.primary:hover {
background: white;
}
</style>