diff --git a/app.vue b/app.vue index 3f61ca6..5b1d7bd 100644 --- a/app.vue +++ b/app.vue @@ -15,7 +15,8 @@ Urgent Tools -
+
+
@@ -24,7 +25,7 @@ role="navigation" aria-label="Main navigation"> Wizards More Resources & Templates @@ -72,6 +73,7 @@ + @@ -81,18 +83,16 @@ const route = useRoute(); const isCoopBuilderSection = computed( () => - route.path === "/coop-planner" || - route.path === "/coop-builder" || - route.path === "/" || - route.path === "/mix" || - route.path === "/budget" || - route.path === "/project-budget" || - route.path === "/settings" || - route.path === "/glossary" + route.path === "/tools/coop-planner" || + route.path === "/tools/coop-builder" || + route.path === "/tools" || + route.path === "/tools/mix" || + route.path === "/tools/budget" || + route.path === "/tools/project-budget" ); const isWizardSection = computed( - () => route.path === "/wizards" || route.path.startsWith("/templates/") + () => route.path === "/tools/wizards" || route.path.startsWith("/tools/templates/") ); // Run migrations on app startup diff --git a/assets/css/main.css b/assets/css/main.css index 00bea93..998d1d8 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -19,7 +19,9 @@ h1, h2, h3, h4, h5, h6 { font-family: "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } - +a { + @apply underline; +} /* ========================= TEMPLATE DOCUMENT LAYOUT @@ -124,7 +126,7 @@ html.dark .section-card::before { } .inline-field { - @apply inline-block mx-1 min-w-[120px] border-none bg-neutral-50 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 px-2 py-1 rounded; + @apply inline-block mx-1; } .inline-field:focus { diff --git a/components/AnnualBudget.vue b/components/AnnualBudget.vue index 16e9a24..9887842 100644 --- a/components/AnnualBudget.vue +++ b/components/AnnualBudget.vue @@ -85,13 +85,6 @@ v-if="suggestedCategories.length > 0"> Consider developing: {{ suggestedCategories.join(", ") }}

-

- - Learn how to develop these revenue streams → - -

diff --git a/components/AppFooter.vue b/components/AppFooter.vue new file mode 100644 index 0000000..176b58b --- /dev/null +++ b/components/AppFooter.vue @@ -0,0 +1,57 @@ + + + \ No newline at end of file diff --git a/components/CoopBuilderSubnav.vue b/components/CoopBuilderSubnav.vue index 21e0040..d80594f 100644 --- a/components/CoopBuilderSubnav.vue +++ b/components/CoopBuilderSubnav.vue @@ -30,17 +30,17 @@ const coopBuilderItems = [ { id: "coop-builder", name: "Settings", - path: "/coop-builder", + path: "/tools/coop-builder", }, { id: "budget", name: "Studio Budget", - path: "/budget", + path: "/tools/budget", }, { id: "project-budget", name: "Project Budget", - path: "/project-budget", + path: "/tools/project-budget", }, ]; diff --git a/components/CurrencySelector.vue b/components/CurrencySelector.vue new file mode 100644 index 0000000..555f9ce --- /dev/null +++ b/components/CurrencySelector.vue @@ -0,0 +1,40 @@ + + + \ No newline at end of file diff --git a/components/ExportOptions.vue b/components/ExportOptions.vue index 984b6ee..35c2061 100644 --- a/components/ExportOptions.vue +++ b/components/ExportOptions.vue @@ -958,475 +958,13 @@ const formatMembershipAgreementAsMarkdown = (data: any): string => { // Conflict Resolution Framework formatting - Complete document with all static and dynamic content const formatConflictResolutionAsText = (data: any): string => { - const formData = data.formData || {}; - const cooperativeName = formData.orgName || "[Cooperative Name]"; - let content = `${cooperativeName.toUpperCase()} CONFLICT RESOLUTION POLICY\n${"=".repeat( - cooperativeName.length + 30 - )}\n\n`; - - content += `Framework Created: ${ - formData.createdDate || new Date().toISOString().split("T")[0] - }\n`; - if (formData.reviewDate) { - content += `Next Review: ${formData.reviewDate}\n`; - } - content += `\n`; - - // Core Values section (if enabled) - if (data.sectionsEnabled?.values !== false) { - content += `OUR VALUES\n----------\n\n`; - content += `This conflict resolution framework is guided by our core values:\n\n`; - - if (formData.coreValuesList && formData.coreValuesList.length > 0) { - formData.coreValuesList.forEach((value: string) => { - content += `• ${value}\n`; - }); - content += `\n`; - } - - if (formData.customValues) { - content += `${formData.customValues}\n\n`; - } - } - - // Resolution Philosophy - const approachDescriptions: { [key: string]: string } = { - restorative: - "We use a restorative/loving justice approach that focuses on healing, understanding root causes, and repairing relationships rather than punishment.", - mediation: - "We use a mediation-first approach where neutral third-party facilitators help parties dialogue and find solutions.", - progressive: - "We use progressive discipline with clear escalation steps and defined consequences for violations.", - hybrid: - "We use a hybrid approach that combines multiple methods based on the type and severity of conflict.", - }; - - if (formData.approach && approachDescriptions[formData.approach]) { - content += `OUR APPROACH\n------------\n\n`; - content += `${approachDescriptions[formData.approach]}\n\n`; - content += `We do our best to resolve conflicts at the lowest possible escalation step (direct resolution), but agree to escalate conflicts (to assisted resolution) if they are not resolved.\n\n`; - } - - // Reflection Process (if enabled) - if (data.sectionsEnabled?.reflection !== false) { - content += `REFLECTION\n----------\n\n`; - content += `Before engaging in direct resolution, we encourage taking time for reflection:\n\n`; - content += `1. Set aside time to think through what happened. What was the other person's behaviour? How did it affect you? Distinguish other people's actions from your feelings about them.\n`; - content += `2. Consider uncertainties or misunderstandings that may have occurred.\n`; - content += `3. Distinguish disagreement from personal hostility. Disagreement and dissent are part of healthy discussion. Hostility is not.\n`; - content += `4. Use your personal support system (friends, family, therapist, etc.) to work through and clarify your perspective.\n`; - content += `5. Ask yourself what part you played, how you could have behaved differently, and what your needs are.\n\n`; - - if (formData.customReflectionPrompts) { - content += `Additional Reflection Prompts:\n${formData.customReflectionPrompts}\n\n`; - } - - const reflectionTiming = - formData.reflectionPeriod || "Before any escalation"; - content += `Reflection Timing: ${reflectionTiming}\n\n`; - } - - // Direct Resolution (if enabled) - if (data.sectionsEnabled?.directResolution !== false) { - content += `DIRECT RESOLUTION\n-----------------\n\n`; - content += `A direct resolution process occurs when individuals communicate their concerns and work together to resolve disputes without filing an informal or formal complaint.\n\n`; - - content += `Have a Conversation\n-------------------\n\n`; - content += `When there is a disagreement, the involved people should first communicate with each other about their concerns.\n\n`; - - content += `1. Choose a time and place to meet that is private and agreeable to both.\n`; - content += `2. Allow reasonable time for the conversation.\n`; - content += `3. The point is mutual understanding, not determining who is right or wrong. This requires patience and willingness to listen without immediately dismissing the other person's perspective.\n`; - content += `4. Express thoughts and feelings directly without belittling or dismissing. Use "I" statements and active listening techniques.\n`; - content += `5. Communicate your wants and needs and make offers and requests.\n`; - content += `6. Learn for the future. Ask questions like, "If what I/you said or did came across that way, what can we do to prevent this from happening in the future?"\n`; - - if (formData.documentDirectResolution) { - content += `7. Keep a written record of the resolution agreed to by both parties.\n\n`; - } else { - content += `\n`; - } - - // Communication Channels - if (formData.channelsList && formData.channelsList.length > 0) { - content += `Escalating Communication Bandwidth\n-----------------------------------\n\n`; - content += `Whenever a misunderstanding or conflict arises, escalate the bandwidth of the channel:\n\n`; - formData.channelsList.forEach((channel: string, index: number) => { - content += `${index + 1}. ${channel}\n`; - }); - content += `\n`; - } - - if (formData.requireDirectAttempt) { - content += `NOTE: Direct resolution must be attempted before escalating to assisted resolution, unless safety concerns prevent this.\n\n`; - } - } - - // Assisted Resolution - content += `ASSISTED RESOLUTION\n-------------------\n\n`; - content += `If talking things out doesn't work, you can ask a responsible contact person for help in writing.\n\n`; - - // Responsible Contact People - if (formData.receiversList && formData.receiversList.length > 0) { - content += `Initial Contact Options:\n`; - formData.receiversList.forEach((receiver: string) => { - content += `• ${receiver}\n`; - }); - content += `\n`; - } - - // Mediator Structure - if (formData.mediatorType) { - content += `Mediation/Facilitation Structure: ${formData.mediatorType}\n\n`; - - if (formData.supportPeople) { - content += `Support People: Parties may bring a trusted person for emotional support during mediation sessions.\n\n`; - } - } - - // Timeline - content += `Response Times:\n`; - if (formData.initialResponse) { - content += `• Initial Response: ${formData.initialResponse}\n`; - } - if (formData.resolutionTarget) { - content += `• Target Resolution: ${formData.resolutionTarget}\n\n`; - } - - // Formal Complaints - content += `FORMAL COMPLAINTS\n-----------------\n\n`; - content += `If assisted resolution efforts do not result in an acceptable outcome within a reasonable timeframe, a formal complaint may be filed in writing.\n\n`; - - // Required Elements - if ( - formData.complaintElementsList && - formData.complaintElementsList.length > 0 - ) { - content += `Written Complaint Requirements:\n`; - formData.complaintElementsList.forEach((element: string, index: number) => { - content += `${index + 1}. ${element}\n`; - }); - content += `\n`; - } - - // Formal Process Timeline - content += `Formal Process Timeline:\n`; - if (formData.formalAcknowledgmentTime) { - content += `• Acknowledgment: ${formData.formalAcknowledgmentTime}\n`; - } - if (formData.formalReviewTime) { - content += `• Review Completion: ${formData.formalReviewTime}\n\n`; - } - - if (formData.requireExternalAdvice) { - content += `External Expertise: For complex complaints involving multiple parties or organizational leaders, external legal advice will be sought.\n\n`; - } - - // Settlement Documentation - if (formData.requireMinutesOfSettlement) { - content += `Reaching Agreement\n------------------\n\n`; - content += `Any resolution agreed upon must be documented in "Minutes of Settlement" signed by both parties. These agreements will be kept confidential according to our privacy standards.\n\n`; - } - - // Consequences and Actions - if (formData.actionsList && formData.actionsList.length > 0) { - content += `POSSIBLE OUTCOMES\n-----------------\n\n`; - content += `Depending on the situation, resolution may include:\n\n`; - formData.actionsList.forEach((action: string) => { - content += `• ${action}\n`; - }); - content += `\n`; - } - - if (formData.appealProcess) { - content += `Appeals Process: Parties may request review of decisions through our appeals process.\n\n`; - } - - // Documentation and Privacy - if (data.sectionsEnabled?.documentation !== false) { - content += `DOCUMENTATION & PRIVACY\n-----------------------\n\n`; - if (formData.docLevel) { - content += `Documentation Level: ${formData.docLevel}\n\n`; - } - if (formData.confidentiality) { - content += `Confidentiality: ${formData.confidentiality}\n\n`; - } - if (formData.retention) { - content += `Record Retention: ${formData.retention}\n\n`; - } - } - - // External Resources (if enabled) - if (data.sectionsEnabled?.externalResources !== false) { - content += `EXTERNAL RESOURCES\n------------------\n\n`; - if (formData.includeHumanRights) { - content += `Individuals who are not satisfied with the outcome of a harassment or discrimination complaint may file a complaint with the Canadian Human Rights Commission or their provincial human rights tribunal.\n\n`; - } - - if (formData.additionalResources) { - content += `Additional Resources:\n${formData.additionalResources}\n\n`; - } - } - - // Implementation - content += `POLICY MANAGEMENT\n-----------------\n\n`; - if (formData.training) { - content += `Training Requirements:\n${formData.training}\n\n`; - } - - content += `Review and Updates:\n`; - if (formData.reviewSchedule) { - content += `This policy will be reviewed ${formData.reviewSchedule.toLowerCase()}.\n\n`; - } - if (formData.amendments) { - content += `Amendment Process: ${formData.amendments}\n\n`; - } - - // Acknowledgments - if (formData.acknowledgments) { - content += `Acknowledgments:\n${formData.acknowledgments}\n\n`; - } - - return content; + // Use the pre-generated content from the Vue component + return data.content || "No content available"; }; const formatConflictResolutionAsMarkdown = (data: any): string => { - const formData = data.formData || {}; - const cooperativeName = formData.orgName || "[Cooperative Name]"; - let content = `# ${cooperativeName} Conflict Resolution Policy\n\n`; - - content += `*Framework Created: ${ - formData.createdDate || new Date().toISOString().split("T")[0] - }*\n`; - if (formData.reviewDate) { - content += `*Next Review: ${formData.reviewDate}*\n`; - } - content += `\n---\n\n`; - - // Core Values section (if enabled) - if (data.sectionsEnabled?.values !== false) { - content += `## Our Values\n\n`; - content += `This conflict resolution framework is guided by our core values:\n\n`; - - if (formData.coreValuesList && formData.coreValuesList.length > 0) { - formData.coreValuesList.forEach((value: string) => { - content += `- **${value}**\n`; - }); - content += `\n`; - } - - if (formData.customValues) { - content += `${formData.customValues}\n\n`; - } - } - - // Resolution Philosophy - const approachDescriptions: { [key: string]: string } = { - restorative: - "We use a **restorative/loving justice** approach that focuses on healing, understanding root causes, and repairing relationships rather than punishment.", - mediation: - "We use a **mediation-first** approach where neutral third-party facilitators help parties dialogue and find solutions.", - progressive: - "We use **progressive discipline** with clear escalation steps and defined consequences for violations.", - hybrid: - "We use a **hybrid approach** that combines multiple methods based on the type and severity of conflict.", - }; - - if (formData.approach && approachDescriptions[formData.approach]) { - content += `## Our Approach\n\n`; - content += `${approachDescriptions[formData.approach]}\n\n`; - content += `We do our best to resolve conflicts at the lowest possible escalation step (direct resolution), but agree to escalate conflicts (to assisted resolution) if they are not resolved.\n\n`; - } - - // Reflection Process (if enabled) - if (data.sectionsEnabled?.reflection !== false) { - content += `## Reflection\n\n`; - content += `Before engaging in direct resolution, we encourage taking time for reflection:\n\n`; - content += `1. **Set aside time to think** through what happened. What was the other person's behaviour? How did it affect you? *Distinguish other people's **actions** from your **feelings** about them.*\n`; - content += `2. **Consider uncertainties** or misunderstandings that may have occurred.\n`; - content += `3. **Distinguish disagreement from personal hostility.** Disagreement and dissent are part of healthy discussion. Hostility is not.\n`; - content += `4. **Use your personal support system** (friends, family, therapist, etc.) to work through and clarify your perspective.\n`; - content += `5. **Ask yourself** what part you played, how you could have behaved differently, and what your needs are.\n\n`; - - if (formData.customReflectionPrompts) { - content += `### Additional Reflection Prompts\n\n`; - content += `${formData.customReflectionPrompts}\n\n`; - } - - const reflectionTiming = - formData.reflectionPeriod || "Before any escalation"; - content += `**Reflection Timing:** ${reflectionTiming}\n\n`; - } - - // Direct Resolution (if enabled) - if (data.sectionsEnabled?.directResolution !== false) { - content += `## Direct Resolution\n\n`; - content += `A *direct resolution* process occurs when individuals communicate their concerns and work together to resolve disputes without filing an informal or formal complaint.\n\n`; - - content += `### Have a Conversation\n\n`; - content += `When there is a disagreement, the involved people should first **communicate with each other** about their concerns.\n\n`; - - content += `1. **Choose a time and place** to meet that is private and agreeable to both.\n`; - content += `2. **Allow reasonable time** for the conversation.\n`; - content += `3. **The point is mutual understanding**, not determining who is right or wrong. This requires patience and willingness to listen without immediately dismissing the other person's perspective.\n`; - content += `4. **Express thoughts and feelings directly** without belittling or dismissing. Use "I" statements and active listening techniques.\n`; - content += `5. **Communicate your wants and needs** and make offers and requests.\n`; - content += `6. **Learn for the future.** Ask questions like, "If what I/you said or did came across that way, what can we do to prevent this from happening in the future?"\n`; - - if (formData.documentDirectResolution) { - content += `7. **Keep a written record** of the resolution agreed to by both parties.\n\n`; - } else { - content += `\n`; - } - - // Communication Channels - if (formData.channelsList && formData.channelsList.length > 0) { - content += `### Escalating Communication Bandwidth\n\n`; - content += `Whenever a misunderstanding or conflict arises, **escalate the bandwidth of the channel**:\n\n`; - formData.channelsList.forEach((channel: string, index: number) => { - content += `${index + 1}. ${channel}\n`; - }); - content += `\n`; - } - - if (formData.requireDirectAttempt) { - content += `> **Note:** Direct resolution must be attempted before escalating to assisted resolution, unless safety concerns prevent this.\n\n`; - } - } - - // Assisted Resolution - content += `## Assisted Resolution\n\n`; - content += `If talking things out doesn't work, you can ask a responsible contact person for help in writing.\n\n`; - - // Responsible Contact People - if (formData.receiversList && formData.receiversList.length > 0) { - content += `### Initial Contact Options\n\n`; - content += `You can report conflicts to any of the following:\n\n`; - formData.receiversList.forEach((receiver: string) => { - content += `- ${receiver}\n`; - }); - content += `\n`; - } - - // Mediator Structure - if (formData.mediatorType) { - content += `### Mediation/Facilitation\n\n`; - content += `**Structure:** ${formData.mediatorType}\n\n`; - - if (formData.supportPeople) { - content += `**Support People:** Parties may bring a trusted person for emotional support during mediation sessions.\n\n`; - } - } - - // Timeline - content += `### Response Times\n\n`; - if (formData.initialResponse) { - content += `- **Initial Response:** ${formData.initialResponse}\n`; - } - if (formData.resolutionTarget) { - content += `- **Target Resolution:** ${formData.resolutionTarget}\n\n`; - } - - // Formal Complaints - content += `## Formal Complaints\n\n`; - content += `If assisted resolution efforts do not result in an acceptable outcome within a reasonable timeframe, a *formal complaint* may be filed in writing.\n\n`; - - // Required Elements - if ( - formData.complaintElementsList && - formData.complaintElementsList.length > 0 - ) { - content += `### Written Complaint Requirements\n\n`; - content += `The formal complaint must include:\n\n`; - formData.complaintElementsList.forEach((element: string, index: number) => { - content += `${index + 1}. ${element}\n`; - }); - content += `\n`; - } - - // Formal Process Timeline - content += `### Formal Process Timeline\n\n`; - if (formData.formalAcknowledgmentTime) { - content += `- **Acknowledgment:** ${formData.formalAcknowledgmentTime}\n`; - } - if (formData.formalReviewTime) { - content += `- **Review Completion:** ${formData.formalReviewTime}\n\n`; - } - - if (formData.requireExternalAdvice) { - content += `> **External Expertise:** For complex complaints involving multiple parties or organizational leaders, external legal advice will be sought.\n\n`; - } - - // Settlement Documentation - if (formData.requireMinutesOfSettlement) { - content += `### Reaching Agreement\n\n`; - content += `Any resolution agreed upon must be documented in "Minutes of Settlement" signed by both parties. These agreements will be kept confidential according to our privacy standards.\n\n`; - } - - // Consequences and Actions - if (formData.actionsList && formData.actionsList.length > 0) { - content += `## Possible Outcomes\n\n`; - content += `Depending on the situation, resolution may include:\n\n`; - formData.actionsList.forEach((action: string) => { - content += `- ${action}\n`; - }); - content += `\n`; - } - - if (formData.appealProcess) { - content += `### Appeals Process\n\n`; - content += `Parties may request review of decisions through our appeals process.\n\n`; - } - - // Documentation and Privacy - if (data.sectionsEnabled?.documentation !== false) { - content += `## Documentation & Privacy\n\n`; - if (formData.docLevel) { - content += `**Documentation Level:** ${formData.docLevel}\n\n`; - } - if (formData.confidentiality) { - content += `**Confidentiality:** ${formData.confidentiality}\n\n`; - } - if (formData.retention) { - content += `**Record Retention:** ${formData.retention}\n\n`; - } - } - - // External Resources (if enabled) - if (data.sectionsEnabled?.externalResources !== false) { - content += `## External Resources\n\n`; - if (formData.includeHumanRights) { - content += `Individuals who are not satisfied with the outcome of a harassment or discrimination complaint may file a complaint with the [Canadian Human Rights Commission](https://www.chrc-ccdp.gc.ca/eng) or their provincial human rights tribunal.\n\n`; - } - - if (formData.additionalResources) { - content += `### Additional Resources\n\n`; - content += `${formData.additionalResources}\n\n`; - } - } - - // Implementation - content += `## Policy Management\n\n`; - if (formData.training) { - content += `### Training Requirements\n\n`; - content += `${formData.training}\n\n`; - } - - content += `### Review and Updates\n\n`; - if (formData.reviewSchedule) { - content += `This policy will be reviewed ${formData.reviewSchedule.toLowerCase()}.\n\n`; - } - if (formData.amendments) { - content += `**Amendment Process:** ${formData.amendments}\n\n`; - } - - // Acknowledgments - if (formData.acknowledgments) { - content += `### Acknowledgments\n\n`; - content += `${formData.acknowledgments}\n\n`; - } - - return content; + // Use the pre-generated content from the Vue component + return data.content || "No content available"; }; // Decision Framework formatting diff --git a/components/GlossaryTooltip.vue b/components/GlossaryTooltip.vue deleted file mode 100644 index efadf0e..0000000 --- a/components/GlossaryTooltip.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - diff --git a/components/ProjectBudgetEstimate.vue b/components/ProjectBudgetEstimate.vue index 26b2541..7cab08f 100644 --- a/components/ProjectBudgetEstimate.vue +++ b/components/ProjectBudgetEstimate.vue @@ -2,275 +2,296 @@
-
-
-
-
- - - + class="relative bg-white dark:bg-neutral-950 border-1 border-black dark:border-neutral-400"> + +
+
+
+ + + +
-
- -
-
    - -
  • -
    - -
    -
    - Monthly payroll breakdown: -
    -
    - Base hourly rate: {{ currency(theoreticalHourlyRate) }}/hour -
    -
    + +
    +
      + +
    • +
      + +
      +
      + Monthly payroll breakdown: +
      +
      + Base hourly rate: {{ currency(theoreticalHourlyRate) }}/hour +
      +
      +
      + {{ member.name }} @ {{ member.hoursPerMonth }}h: + {{ + currency(member.hoursPerMonth * theoreticalHourlyRate) + }} +
      +
      + class="border-t border-neutral-300 dark:border-neutral-600 pt-1 flex justify-between font-medium"> + Total base pay: + {{ + currency(baseMonthlyPayroll) + }} +
      +
      {{ member.name }} @ {{ member.hoursPerMonth }}h:Payroll taxes & benefits ({{ + percent(props.oncostRate) + }}): {{ - currency(member.hoursPerMonth * theoreticalHourlyRate) + currency(theoreticalOncosts) + }} +
      +
      + Total monthly payroll: + {{ + currency(baseMonthlyPayroll + theoreticalOncosts) }}
      + +
      - Total base pay: - {{ - currency(baseMonthlyPayroll) - }} -
      -
      - Payroll taxes & benefits ({{ - percent(props.oncostRate) - }}): - {{ - currency(theoreticalOncosts) - }} -
      -
      - Total monthly payroll: - {{ - currency(baseMonthlyPayroll + theoreticalOncosts) - }} + class="text-base text-neutral-600 dark:text-neutral-200 space-y-1"> +
      + Complete project budget estimate: +
      +

      + This uses a 1.8x multiplier, based on industry standards. +

      + + +
      + Team Payroll: + {{ + currency(projectBudget) + }} +
      + + +
      +
      + External resources: + {{ + currency(externalResources) + }} +
      +
      + Freelancers, contractors, consultants, voice talent +
      +
      + + +
      +
      + Tools and software: + {{ + currency(toolsSoftware) + }} +
      +
      + Licenses, subscriptions, cloud services, development + tools/kits +
      +
      + + +
      +
      + Testing and QA: + {{ currency(testingQA) }} +
      +
      + User testing sessions, focus groups, QA contractors, + playtesting, bug fixing +
      +
      + + +
      +
      + Marketing and community: + {{ + currency(marketingCommunity) + }} +
      +
      + Community building, promotional materials, launch + preparation (minimum 10% for most funders) +
      +
      + + +
      +
      + Administration: + {{ + currency(administration) + }} +
      +
      + Legal, accounting, insurance, project-specific business + costs +
      +
      + + +
      + Subtotal: + {{ + currency(budgetSubtotal) + }} +
      + + +
      +
      + Contingency (10%): + {{ currency(contingency) }} +
      +
      + + +
      + TOTAL PROJECT: + {{ + currency(totalProjectBudget) + }} +
      - - -
      -
      - Complete project budget estimate: -
      -

      - This uses a 1.8x multiplier, based on industry standards. -

      - - -
      - Team Payroll: - {{ - currency(projectBudget) - }} -
      - - -
      -
      - External resources: - {{ - currency(externalResources) - }} -
      -
      - Freelancers, contractors, consultants, voice talent -
      -
      - - -
      -
      - Tools and software: - {{ currency(toolsSoftware) }} -
      -
      - Licenses, subscriptions, cloud services, development - tools/kits -
      -
      - - -
      -
      - Testing and QA: - {{ currency(testingQA) }} -
      -
      - User testing sessions, focus groups, QA contractors, - playtesting, bug fixing -
      -
      - - -
      -
      - Marketing and community: - {{ - currency(marketingCommunity) - }} -
      -
      - Community building, promotional materials, launch - preparation (minimum 10% for most funders) -
      -
      - - -
      -
      - Administration: - {{ - currency(administration) - }} -
      -
      - Legal, accounting, insurance, project-specific business - costs -
      -
      - - -
      - Subtotal: - {{ currency(budgetSubtotal) }} -
      - - -
      -
      - Contingency (10%): - {{ currency(contingency) }} -
      -
      - - -
      - TOTAL PROJECT: - {{ - currency(totalProjectBudget) - }} -
      -
      -
    -
  • -
-
- - -
-
-

Break-Even Sketch

- -
-
- - - - -
-
- - - - -
-
- - -
-
- - -
    -
  • - At {{ currency(price) }} per copy after store fees, you'd need - about - {{ unitsToBreakEven.toLocaleString() }} sales to - cover this budget. -
  • -
  • - That's roughly - {{ reviewsToBreakEven.toLocaleString() }} Steam reviews - (≈ {{ reviewToSales }} sales per review).
- -

- Assumes {{ percent(storeCutInput / 100) }} store fee. Taxes not - included. -

-
+ + +
+
+

Break-Even Sketch

+

+ There are so many ways to guess at the sales figures of published + games. One is to use a review-to-sales ratio, formalized in the + Boxleiter method and later discussed and refined by many others. + VGInsights + and GameDiscoverCo are good + sources for more information on this method. Your chosen ratio + will vary, typically between 30 and 70 reviews per sale, and is + dependent on factors like genre, pricing, and market conditions at + the time of release. +

+ +
+
+ + + + +
+
+ + + + +
+
+ + +
+
+ + +
    +
  • + At {{ currency(price) }} per copy after store fees, you'd need + about + {{ unitsToBreakEven.toLocaleString() }} sales + to cover this budget. +
  • +
  • + That's roughly + {{ reviewsToBreakEven.toLocaleString() }} Steam + reviews + (≈ {{ reviewToSales }} sales per review). +
  • +
+ +

+ Taxes not included. +

+
+
diff --git a/components/WizardPoliciesStep.vue b/components/WizardPoliciesStep.vue index f4a53c3..bbc448c 100644 --- a/components/WizardPoliciesStep.vue +++ b/components/WizardPoliciesStep.vue @@ -35,24 +35,7 @@ This hourly rate applies to all paid work in your co-op

- - - - - - - - + @@ -73,7 +56,7 @@ diff --git a/pages/help.vue b/pages/help.vue deleted file mode 100644 index 57fc81d..0000000 --- a/pages/help.vue +++ /dev/null @@ -1,191 +0,0 @@ - - - - - \ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue index b72b8bb..eb0a4c1 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,382 +1,432 @@ - -// Use real store data instead of fixtures -const membersStore = useMembersStore(); -const policiesStore = usePoliciesStore(); -const streamsStore = useStreamsStore(); -const budgetStore = useBudgetStore(); -const cashStore = useCashStore(); + \ No newline at end of file diff --git a/pages/settings.vue b/pages/settings.vue deleted file mode 100644 index 88fa907..0000000 --- a/pages/settings.vue +++ /dev/null @@ -1,219 +0,0 @@ - - - diff --git a/pages/budget.vue b/pages/tools/budget.vue similarity index 99% rename from pages/budget.vue rename to pages/tools/budget.vue index bb2976a..c01ebf4 100644 --- a/pages/budget.vue +++ b/pages/tools/budget.vue @@ -40,7 +40,7 @@

Complete Setup Wizard diff --git a/pages/coop-builder.vue b/pages/tools/coop-builder.vue similarity index 99% rename from pages/coop-builder.vue rename to pages/tools/coop-builder.vue index 4b7161c..e6bdb76 100644 --- a/pages/coop-builder.vue +++ b/pages/tools/coop-builder.vue @@ -39,7 +39,7 @@ :disabled="isResetting"> Start Over -
diff --git a/pages/coop-planner.vue b/pages/tools/coop-planner.vue similarity index 100% rename from pages/coop-planner.vue rename to pages/tools/coop-planner.vue diff --git a/pages/dashboard-simple.vue b/pages/tools/dashboard-simple.vue similarity index 100% rename from pages/dashboard-simple.vue rename to pages/tools/dashboard-simple.vue diff --git a/pages/tools/index.vue b/pages/tools/index.vue new file mode 100644 index 0000000..b851e23 --- /dev/null +++ b/pages/tools/index.vue @@ -0,0 +1,381 @@ + + + \ No newline at end of file diff --git a/pages/project-budget.vue b/pages/tools/project-budget.vue similarity index 99% rename from pages/project-budget.vue rename to pages/tools/project-budget.vue index 2974acd..a742458 100644 --- a/pages/project-budget.vue +++ b/pages/tools/project-budget.vue @@ -21,7 +21,7 @@ No team members set up yet.

Set up your team in Setup Wizard diff --git a/pages/resources.vue b/pages/tools/resources.vue similarity index 100% rename from pages/resources.vue rename to pages/tools/resources.vue diff --git a/pages/templates/conflict-resolution-framework.vue b/pages/tools/templates/conflict-resolution-framework.vue similarity index 78% rename from pages/templates/conflict-resolution-framework.vue rename to pages/tools/templates/conflict-resolution-framework.vue index 919c27a..5794362 100644 --- a/pages/templates/conflict-resolution-framework.vue +++ b/pages/tools/templates/conflict-resolution-framework.vue @@ -76,6 +76,7 @@ v-model="value.checked" :id="`core-value-${index}`" :label="value.label" + class="w-full" @change="autoSave" />
@@ -124,6 +125,7 @@ v-model="conflict.checked" :id="`conflict-type-${index}`" :label="conflict.label" + class="w-full" @change="autoSave" />
@@ -157,6 +159,7 @@ id="anonymous-reporting" label="Allow anonymous reporting" help="Members can report issues without revealing their identity" + class="w-full" @change="autoSave" /> @@ -191,6 +194,7 @@ v-model="receiver.checked" :id="`report-receiver-${index}`" :label="receiver.label" + class="w-full" @change="autoSave" /> @@ -221,6 +225,7 @@ id="support-people" label="Allow support people in mediation sessions" help="Parties can bring a trusted person for emotional support" + class="w-full" @change="autoSave" /> @@ -283,6 +288,7 @@ v-model="step.checked" :id="`process-step-${index}`" :label="`${index + 1}. ${step.label}`" + class="w-full" @change="autoSave" /> @@ -378,6 +384,7 @@ v-model="action.checked" :id="`available-action-${index}`" :label="action.label" + class="w-full" @change="autoSave" /> @@ -396,6 +403,7 @@ id="appeal-process" label="Include appeals process" help="Parties can request review of decisions" + class="w-full" @change="autoSave" /> @@ -439,6 +447,7 @@ v-model="circumstance.checked" :id="`special-circumstance-${index}`" :label="circumstance.label" + class="w-full" @change="autoSave" /> @@ -598,6 +607,7 @@ v-model="channel.checked" :id="`comm-channel-${index}`" :label="channel.label" + class="w-full" @change="autoSave" /> @@ -611,6 +621,7 @@ id="require-direct-attempt" label="Require direct resolution attempt before escalation" help="Parties must try to resolve directly before filing complaints" + class="w-full" @change="autoSave" /> @@ -621,6 +632,7 @@ id="document-direct-resolution" label="Require written record of direct resolution attempts" help="Parties should document outcomes of direct conversations" + class="w-full" @change="autoSave" /> @@ -699,6 +711,7 @@ v-model="element.checked" :id="`complaint-element-${index}`" :label="element.label" + class="w-full" @change="autoSave" /> @@ -737,6 +750,7 @@ id="require-external-advice" label="Require external legal advice for complex complaints" help="Seek external expertise for multi-party or member-coordinator complaints" + class="w-full" @change="autoSave" /> @@ -753,6 +767,7 @@ id="minutes-of-settlement" label="Require 'Minutes of Settlement' for resolved complaints" help="Agreements must be documented in writing and signed by both parties" + class="w-full" @change="autoSave" /> @@ -806,6 +821,7 @@ id="include-human-rights" label="Include Human Rights Commission information" help="Reference external discrimination complaint options" + class="w-full" @change="autoSave" /> @@ -1041,7 +1057,7 @@ const coreValues = ref([ { label: "Mutual Care", checked: true }, { label: "Transparency", checked: true }, { label: "Accountability", checked: false }, - { label: "Consent-Based", checked: false }, + { label: "Consent", checked: false }, { label: "Anti-Oppression", checked: false }, { label: "Restorative Justice", checked: false }, { label: "Collective Liberation", checked: false }, @@ -1280,7 +1296,7 @@ watch( { deep: true } ); -// Generate the complete policy document for preview and export +// Comprehensive generatePolicyDocument function with procedural structure const generatePolicyDocument = () => { const cooperativeName = formData.value.orgName || "[Cooperative Name]"; let content = `# ${cooperativeName} Conflict Resolution Policy\n\n`; @@ -1293,25 +1309,93 @@ const generatePolicyDocument = () => { } content += `\n---\n\n`; - // Core Values section (if enabled) - if (sectionsEnabled.value.values) { - content += `## Our Values\n\n`; - content += `This conflict resolution framework is guided by our core values:\n\n`; + // PURPOSE SECTION + content += `## Purpose\n\n`; + content += `Disagreements in groups are par for the course. But ignoring conflicts, or managing them poorly, can deeply harm individuals and our whole community.\n\n`; + content += `Addressing conflict head-on is **a way of caring for each other**.\n\n`; + content += `This policy aims to offer a straightforward, consistently enforced, and transparent approach to resolving conflicts and disputes that may emerge in relation to ${cooperativeName}'s programs, governance, or the actions of its members.\n\n`; + // GUIDING PRINCIPLES + content += `## Guiding Principles\n\n`; + content += `- All parties to a complaint will **actively participate** and strive to achieve a **collaborative** outcome at the earliest possible stage of the process\n`; + content += `- Information about a complaint will only be given to parties directly involved and others on a need-to-know basis\n`; + content += `- Parties will be provided with clear and understandable reasons for complaint decisions\n`; + content += `- Complaints will be dealt with promptly and resolved as quickly as possible\n`; + content += `- Review of complaints will be fair, impartial, and respectful, allowing all parties to have their perspectives heard\n`; + content += `- The review will be thorough and as detailed as possible based on the information provided\n`; + content += `- The process will be accessible and clearly communicated to all members\n\n`; + + // Add selected values if enabled + if (sectionsEnabled.value.values) { const selectedValues = coreValues.value.filter((v) => v.checked); if (selectedValues.length > 0) { + content += `Additionally, this framework is guided by our core values:\n\n`; selectedValues.forEach((value) => { content += `- **${value.label}**\n`; }); content += `\n`; } - if (formData.value.customValues) { content += `${formData.value.customValues}\n\n`; } } - // Resolution Philosophy + // DEFINITIONS SECTION + content += `## Definitions\n\n`; + content += `- **Conflict/Dispute**: Ongoing experiences of tension and misunderstandings, often leading to interpersonal discord. These terms are used interchangeably.\n`; + content += `- **Complainant**: The individual lodging a complaint against another party, policy, or practice.\n`; + content += `- **Respondent**: An individual against whom a complaint has been made.\n`; + + // Add definitions based on selections + const selectedReceivers = reportReceivers.value.filter((r) => r.checked); + if (selectedReceivers.length > 0) { + content += `- **Responsible Contact People**: Those accountable for assisting in conflict resolution (${selectedReceivers + .map((r) => r.label) + .join( + ", " + )}). They act as neutral implementers of this policy, not advocates.\n`; + } + + if (formData.value.internalAdvisorType) { + content += `- **Internal Advisor**: ${formData.value.internalAdvisorType} who facilitates the conflict resolution process as a neutral intermediary.\n`; + } + + if (formData.value.supportPeople) { + content += `- **Support People**: Individuals not connected to the conflict whom parties may choose to have present for emotional support during mediation.\n`; + } + content += `\n`; + + // POLICY ROUTING TABLE + content += `## Which Policy Applies?\n\n`; + content += `| **Who Can File** | **Type of Complaint** | **Policy to Use** | **Initial Contact** |\n`; + content += `|------------------|----------------------|-------------------|--------------------|\n`; + + const selectedConflictTypes = conflictTypes.value.filter((c) => c.checked); + selectedConflictTypes.forEach((conflict) => { + let policy = "This policy"; + let contact = selectedReceivers[0]?.label || "Designated contact"; + + if ( + conflict.label.includes("Harassment") || + conflict.label.includes("discrimination") + ) { + policy = "Code of Conduct / Human Rights"; + if (formData.value.includeHumanRights) { + contact += " or Human Rights Tribunal"; + } + } else if (conflict.label.includes("Code of Conduct")) { + policy = "Code of Conduct"; + } + + content += `| Members | ${conflict.label} | ${policy} | ${contact} |\n`; + }); + + if (formData.value.anonymousReporting) { + content += `| Any party | Anonymous reports | This policy | Anonymous reporting system |\n`; + } + content += `\n`; + + // RESOLUTION APPROACH const approachDescriptions = { restorative: "We use a **restorative/loving justice** approach that focuses on healing, understanding root causes, and repairing relationships rather than punishment.", @@ -1332,9 +1416,9 @@ const generatePolicyDocument = () => { content += `We do our best to resolve conflicts at the lowest possible escalation step (direct resolution), but agree to escalate conflicts (to assisted resolution) if they are not resolved.\n\n`; } - // Reflection Process (if enabled) + // REFLECTION PROCESS (if enabled) if (sectionsEnabled.value.reflection) { - content += `## Reflection\n\n`; + content += `## Reflection Process\n\n`; content += `Before engaging in direct resolution, we encourage taking time for reflection:\n\n`; content += `1. **Set aside time to think** through what happened. What was the other person's behaviour? How did it affect you? *Distinguish other people's **actions** from your **feelings** about them.*\n`; content += `2. **Consider uncertainties** or misunderstandings that may have occurred.\n`; @@ -1352,20 +1436,20 @@ const generatePolicyDocument = () => { content += `**Reflection Timing:** ${reflectionTiming}\n\n`; } - // Direct Resolution (if enabled) + // DIRECT RESOLUTION (if enabled) if (sectionsEnabled.value.directResolution) { content += `## Direct Resolution\n\n`; - content += `A *direct resolution* process occurs when individuals communicate their concerns and work together to resolve disputes without filing an informal or formal complaint.\n\n`; + content += `A *direct resolution* process occurs when individuals communicate their concerns and work together to resolve disputes without filing a formal complaint.\n\n`; content += `### Have a Conversation\n\n`; content += `When there is a disagreement, the involved people should first **communicate with each other** about their concerns.\n\n`; content += `1. **Choose a time and place** to meet that is private and agreeable to both.\n`; content += `2. **Allow reasonable time** for the conversation.\n`; - content += `3. **The point is mutual understanding**, not determining who is right or wrong. This requires patience and willingness to listen without immediately dismissing the other person's perspective.\n`; - content += `4. **Express thoughts and feelings directly** without belittling or dismissing. Use "I" statements and active listening techniques.\n`; + content += `3. **The point is mutual understanding**, not determining who is right or wrong.\n`; + content += `4. **Express thoughts and feelings directly** using "I" statements and active listening.\n`; content += `5. **Communicate your wants and needs** and make offers and requests.\n`; - content += `6. **Learn for the future.** Ask questions like, "If what I/you said or did came across that way, what can we do to prevent this from happening in the future?"\n`; + content += `6. **Learn for the future** – ask what can be done to prevent this from recurring.\n`; if (formData.value.documentDirectResolution) { content += `7. **Keep a written record** of the resolution agreed to by both parties.\n\n`; @@ -1391,43 +1475,175 @@ const generatePolicyDocument = () => { } } - // Assisted Resolution + // RECEIVING REPORTS SECTION + content += `## Receiving Reports\n\n`; + + content += `### Document the Initial Incident Report\n\n`; + content += `Collect the following information and enter it in the Incident Log:\n\n`; + content += `| Field | Information to Collect |\n`; + content += `|-------|------------------------|\n`; + content += `| **Participant Name** | Name of individual(s) involved |\n`; + content += `| **Issue/Violation** | Brief description of the behavior or conflict |\n`; + content += `| **Date & Time** | When the incident occurred |\n`; + content += `| **Circumstances** | Context or situation surrounding the incident |\n`; + content += `| **Others Involved** | Names of any witnesses or additional participants |\n`; + content += `| **Conversation Notes** | Summary of discussion with the complainant |\n\n`; + content += `*Gather this information from the complainant – do not "interview" witnesses unless they approach staff.*\n\n`; + + // SUPPORTING THE COMPLAINANT + content += `### Supporting the Complainant\n\n`; + content += `Follow these steps to help the complainant feel safe:\n\n`; + content += `1. **Provide private space** for discussion (in digital spaces, use DM/private channels)\n`; + content += `2. **Allow the complainant to decide** if further action should be taken\n`; + content += `3. **Explain the process** – walk them through next steps per this policy\n`; + content += `4. **Assure confidentiality** – their identity will not be disclosed without permission\n`; + content += `5. **Confirm follow-up** – they will be informed about any actions taken\n\n`; + + // ASSISTED RESOLUTION content += `## Assisted Resolution\n\n`; - content += `If talking things out doesn't work, you can ask a responsible contact person for help in writing.\n\n`; + content += `If direct resolution doesn't work, parties can request assistance from a responsible contact person.\n\n`; + + // Process Steps + const selectedSteps = processSteps.value.filter((s) => s.checked); + if (selectedSteps.length > 0) { + content += `### Resolution Process Steps\n\n`; + selectedSteps.forEach((step, index) => { + content += `${index + 1}. ${step.label}\n`; + }); + content += `\n`; + } // Responsible Contact People - const selectedReceivers = reportReceivers.value.filter((r) => r.checked); + content += `### Responsible Contact People\n\n`; + if (selectedReceivers.length > 0) { - content += `### Initial Contact Options\n\n`; - content += `You can report conflicts to any of the following:\n\n`; + content += `**Initial Contact Options:**\n`; selectedReceivers.forEach((receiver) => { content += `- ${receiver.label}\n`; }); content += `\n`; } + // Contact People Structure + if (formData.value.internalAdvisorType) { + content += `**Internal Advisor:** ${formData.value.internalAdvisorType}\n`; + } + + if (formData.value.staffLiaison) { + content += `**Member Liaison:** ${formData.value.staffLiaison}\n`; + } + + if (formData.value.boardChairRole) { + content += `**Board Chair Role:** ${formData.value.boardChairRole}\n`; + } + + if ( + formData.value.internalAdvisorType || + formData.value.staffLiaison || + formData.value.boardChairRole + ) { + content += `\n`; + } + // Mediator Structure if (formData.value.mediatorType) { - content += `### Mediation/Facilitation\n\n`; - content += `**Structure:** ${formData.value.mediatorType}\n\n`; + content += `**Mediation Structure:** ${formData.value.mediatorType}\n`; if (formData.value.supportPeople) { - content += `**Support People:** Parties may bring a trusted person for emotional support during mediation sessions.\n\n`; + content += `**Support People:** Parties may bring a trusted person for emotional support during mediation sessions.\n`; } + content += `\n`; } // Timeline - content += `### Response Times\n\n`; + content += `### Response Timeline\n\n`; + content += `| Stage | Timeframe |\n`; + content += `|-------|----------|\n`; if (formData.value.initialResponse) { - content += `- **Initial Response:** ${formData.value.initialResponse}\n`; + content += `| Initial Response | ${formData.value.initialResponse} |\n`; } if (formData.value.resolutionTarget) { - content += `- **Target Resolution:** ${formData.value.resolutionTarget}\n\n`; + content += `| Target Resolution | ${formData.value.resolutionTarget} |\n`; + } + content += `\n`; + + // COMMITTEE MEETING PROCEDURES (if committee-based) + if ( + formData.value.mediatorType && + formData.value.mediatorType.toLowerCase().includes("committee") + ) { + content += `## Committee Meeting Procedures\n\n`; + + content += `### Before the Meeting\n`; + content += `- Notify respondent of complaint\n`; + content += `- Allow respondent to provide their perspective\n`; + content += `- Schedule meeting within ${ + formData.value.initialResponse || "specified timeframe" + }\n\n`; + + content += `### During the Meeting\n`; + content += `Committee members should review the incident report and discuss:\n`; + content += `- What happened?\n`; + content += `- What are we doing about it?\n`; + content += `- Who is implementing the decision?\n`; + content += `- When will it be implemented?\n\n`; + content += `*Neither the complainant nor respondent should attend the deliberation.*\n\n`; + + content += `### After the Meeting\n`; + content += `- Communicate decision to all parties\n`; + content += `- Document all communications\n`; + content += `- Follow up with complainant about outcomes\n`; + content += `- Prepare report for organizational records\n\n`; } - // Formal Complaints + // RESPONSE PROCEDURES MATRIX + content += `## Response Procedures\n\n`; + const selectedActions = availableActions.value.filter((a) => a.checked); + + if (selectedActions.length > 0) { + content += `### Response Matrix\n\n`; + content += `| Issue Severity | Possible Response | Documentation Required |\n`; + content += `|----------------|-------------------|------------------------|\n`; + + // Create severity-based responses + const hasVerbal = selectedActions.some((a) => a.label.includes("Verbal")); + const hasWritten = selectedActions.some((a) => a.label.includes("Written")); + const hasSuspension = selectedActions.some((a) => + a.label.includes("suspension") + ); + const hasRemoval = selectedActions.some( + (a) => a.label.includes("Removal") || a.label.includes("removal") + ); + + if (hasVerbal) { + content += `| First occurrence, minor | Verbal warning | Update incident log |\n`; + } + if (hasWritten) { + content += `| Repeated behavior | Written warning | Formal documentation |\n`; + } + if (hasSuspension) { + content += `| Serious violation | Temporary suspension | Full investigation report |\n`; + } + if (hasRemoval) { + content += `| Severe/safety threat | Immediate removal | Complete documentation + notifications |\n`; + } + content += `\n`; + + content += `### Available Remedial Actions\n\n`; + selectedActions.forEach((action) => { + content += `- ${action.label}\n`; + }); + content += `\n`; + } + + if (formData.value.appealProcess) { + content += `### Appeals Process\n\n`; + content += `Parties may request review of decisions through our appeals process. Appeals must be submitted in writing within 30 days of the original decision.\n\n`; + } + + // FORMAL COMPLAINTS content += `## Formal Complaints\n\n`; - content += `If assisted resolution efforts do not result in an acceptable outcome within a reasonable timeframe, a *formal complaint* may be filed in writing.\n\n`; + content += `If assisted resolution does not result in an acceptable outcome, a formal complaint may be filed in writing.\n\n`; // Required Elements const selectedElements = formalComplaintElements.value.filter( @@ -1444,58 +1660,137 @@ const generatePolicyDocument = () => { // Formal Process Timeline content += `### Formal Process Timeline\n\n`; + content += `| Stage | Timeframe |\n`; + content += `|-------|----------|\n`; if (formData.value.formalAcknowledgmentTime) { - content += `- **Acknowledgment:** ${formData.value.formalAcknowledgmentTime}\n`; + content += `| Acknowledgment of complaint | ${formData.value.formalAcknowledgmentTime} |\n`; } if (formData.value.formalReviewTime) { - content += `- **Review Completion:** ${formData.value.formalReviewTime}\n\n`; + content += `| Review completion | ${formData.value.formalReviewTime} |\n`; } + content += `\n`; if (formData.value.requireExternalAdvice) { content += `> **External Expertise:** For complex complaints involving multiple parties or organizational leaders, external legal advice will be sought.\n\n`; } - // Settlement Documentation - if (formData.value.requireMinutesOfSettlement) { - content += `### Reaching Agreement\n\n`; - content += `Any resolution agreed upon must be documented in "Minutes of Settlement" signed by both parties. These agreements will be kept confidential according to our privacy standards.\n\n`; + // PREVENTING RETALIATION (if anti-retaliation is selected) + const hasAntiRetaliation = specialCircumstances.value.some( + (c) => c.checked && c.label.toLowerCase().includes("retaliation") + ); + + if (hasAntiRetaliation) { + content += `## Preventing Retaliation\n\n`; + content += `**CRITICAL:** The privacy and safety of the complainant is paramount.\n\n`; + content += `- **DO NOT** share details of the incident without express permission from the complainant\n`; + content += `- **DO NOT** reveal the complainant's identity to the respondent or others\n`; + content += `- **MONITOR** for any retaliatory behavior following a complaint\n`; + content += `- **DOCUMENT** any instances of suspected retaliation\n`; + content += `- **TREAT** retaliation as a separate, serious violation requiring immediate action\n\n`; } - // Consequences and Actions - const selectedActions = availableActions.value.filter((a) => a.checked); - if (selectedActions.length > 0) { - content += `## Possible Outcomes\n\n`; - content += `Depending on the situation, resolution may include:\n\n`; - selectedActions.forEach((action) => { - content += `- ${action.label}\n`; - }); + // SETTLEMENT & DOCUMENTATION + content += `## Settlement & Documentation\n\n`; + + if (formData.value.requireMinutesOfSettlement) { + content += `### Minutes of Settlement\n`; + content += `Any resolution must be documented in "Minutes of Settlement" that:\n`; + content += `- Clearly state the agreed-upon resolution\n`; + content += `- Include commitments from all parties\n`; + content += `- Are signed by both complainant and respondent\n`; + content += `- Are kept according to our confidentiality standards\n\n`; + } + + // REGARDING APOLOGIES + content += `### Regarding Apologies\n\n`; + content += `We do not require or facilitate apologies unless explicitly requested by the complainant.\n\n`; + content += `- Forced apologies can constitute continued harassment\n`; + content += `- If offered, apologies should be brief and relayed through the mediator\n`; + content += `- Apologies should not require a response from the recipient\n`; + content += `- Pressing unwanted apologies may result in further disciplinary action\n\n`; + + // DOCUMENTATION & PRIVACY + if (sectionsEnabled.value.documentation) { + content += `## Documentation & Privacy\n\n`; + + content += `### Record Management\n\n`; + content += `| Record Type | Retention Period | Access Level | Storage Location |\n`; + content += `|-------------|------------------|--------------|------------------|\n`; + content += `| Initial incident reports | Permanent | Committee only | Secure database |\n`; + content += `| Investigation notes | ${ + formData.value.retention || "5 years" + } | Designated roles | Confidential files |\n`; + content += `| Resolution agreements | ${ + formData.value.conflictFileRetention || + formData.value.retention || + "5 years" + } | Parties + committee | Secure archive |\n`; + content += `| Committee meeting minutes | ${ + formData.value.retention || "5 years" + } | Committee members | Meeting records |\n\n`; + + if (formData.value.docLevel) { + content += `**Documentation Level:** ${formData.value.docLevel}\n`; + } + if (formData.value.confidentiality) { + content += `**General Confidentiality:** ${formData.value.confidentiality}\n`; + } + if (formData.value.settlementConfidentiality) { + content += `**Settlement Confidentiality:** ${formData.value.settlementConfidentiality}\n`; + } content += `\n`; } - if (formData.value.appealProcess) { - content += `### Appeals Process\n\n`; - content += `Parties may request review of decisions through our appeals process.\n\n`; - } + // SPECIAL CIRCUMSTANCES (if enabled) + if (sectionsEnabled.value.special) { + const selectedCircumstances = specialCircumstances.value.filter( + (c) => c.checked + ); + if (selectedCircumstances.length > 0) { + content += `## Special Circumstances\n\n`; - // Documentation and Privacy - if (sectionsEnabled.value.documentation) { - content += `## Documentation & Privacy\n\n`; - if (formData.value.docLevel) { - content += `**Documentation Level:** ${formData.value.docLevel}\n\n`; - } - if (formData.value.confidentiality) { - content += `**Confidentiality:** ${formData.value.confidentiality}\n\n`; - } - if (formData.value.retention) { - content += `**Record Retention:** ${formData.value.retention}\n\n`; + const hasImmediateRemoval = selectedCircumstances.some( + (c) => + c.label.toLowerCase().includes("immediate removal") || + c.label.toLowerCase().includes("safety") + ); + + if (hasImmediateRemoval) { + content += `### Immediate Safety Threats\n`; + content += `When anyone's physical safety is threatened:\n`; + content += `1. Immediately remove the offender from the space\n`; + content += `2. Implement permanent ban if warranted\n`; + content += `3. Notify relevant authorities if required\n`; + content += `4. Document all actions taken\n`; + content += `5. Inform stakeholders as appropriate while protecting victim privacy\n\n`; + } + + const hasTraumaInformed = selectedCircumstances.some((c) => + c.label.toLowerCase().includes("trauma") + ); + + if (hasTraumaInformed) { + content += `### Trauma-Informed Approach\n`; + content += `All conflict resolution processes will incorporate trauma-informed principles:\n`; + content += `- Recognize the impact of trauma on behavior\n`; + content += `- Prioritize physical and emotional safety\n`; + content += `- Provide choices and restore control\n`; + content += `- Collaborate rather than prescribe solutions\n`; + content += `- Build on strengths and resilience\n\n`; + } } } - // External Resources (if enabled) + // EXTERNAL RESOURCES (if enabled) if (sectionsEnabled.value.externalResources) { - content += `## External Resources\n\n`; + content += `## External Resources & Redress\n\n`; + if (formData.value.includeHumanRights) { - content += `Individuals who are not satisfied with the outcome of a harassment or discrimination complaint may file a complaint with the [Canadian Human Rights Commission](https://www.chrc-ccdp.gc.ca/eng) or their provincial human rights tribunal.\n\n`; + content += `### Human Rights Complaints\n`; + content += `Individuals who are not satisfied with the outcome of a harassment or discrimination complaint may file a complaint with:\n`; + content += `- [Canadian Human Rights Commission](https://www.chrc-ccdp.gc.ca/eng)\n`; + content += `- Provincial human rights tribunal\n`; + content += `- Other relevant regulatory bodies\n\n`; } if (formData.value.additionalResources) { @@ -1504,20 +1799,30 @@ const generatePolicyDocument = () => { } } - // Implementation - content += `## Policy Management\n\n`; + // IMPLEMENTATION & TRAINING + content += `## Implementation\n\n`; + if (formData.value.training) { content += `### Training Requirements\n\n`; content += `${formData.value.training}\n\n`; } - content += `### Review and Updates\n\n`; + content += `### Policy Management\n\n`; + content += `| Aspect | Details |\n`; + content += `|--------|----------|\n`; if (formData.value.reviewSchedule) { - content += `This policy will be reviewed ${formData.value.reviewSchedule.toLowerCase()}.\n\n`; + content += `| Review Schedule | ${formData.value.reviewSchedule} |\n`; } if (formData.value.amendments) { - content += `**Amendment Process:** ${formData.value.amendments}\n\n`; + content += `| Amendment Process | ${formData.value.amendments} |\n`; } + content += `| Last Updated | ${ + formData.value.createdDate || new Date().toISOString().split("T")[0] + } |\n`; + if (formData.value.reviewDate) { + content += `| Next Review | ${formData.value.reviewDate} |\n`; + } + content += `\n`; // Acknowledgments if (formData.value.acknowledgments) { @@ -1558,6 +1863,8 @@ const exportData = computed(() => { return { section: "conflict-resolution-framework", + // Add the generated policy document content for exports + content: generatePolicyDocument(), // Enhanced formData with processed arrays formData: { ...formData.value, diff --git a/pages/templates/decision-framework.vue b/pages/tools/templates/decision-framework.vue similarity index 96% rename from pages/templates/decision-framework.vue rename to pages/tools/templates/decision-framework.vue index fe1c08f..4a055dc 100644 --- a/pages/templates/decision-framework.vue +++ b/pages/tools/templates/decision-framework.vue @@ -440,7 +440,7 @@ :key="step" class="flex items-start"> {{ @@ -453,7 +453,7 @@

- Pro tips: + Hot tips:

  • {{ @@ -508,13 +508,6 @@ Try Another Decision - - Print Recommendation -
@@ -522,6 +515,19 @@ + + +
+
+

With inspiration from:

+
+

Rocket Adrift

+

Baby Ghosts Peer Accelerator curriculum

+

The Decider App

+

Sociocracy 3.0

+
+
+
@@ -571,7 +577,7 @@ const urgencyOptions = [ { value: 5, title: "Crisis mode", - description: "This decision is needed yesterday", + description: "We should have decided yesterday!!", }, ]; @@ -602,7 +608,7 @@ const expertiseOptions = [ { value: "multiple", title: "Multiple experts", - description: "Several people have relevant expertise", + description: "Several people have expertise", }, { value: "distributed", @@ -620,7 +626,7 @@ const impactOptions = [ { value: "narrow", title: "One person or small team", - description: "Affects specific individuals or department", + description: "Affects specific individuals or area", }, { value: "wide", @@ -633,7 +639,7 @@ const optionsOptions = [ { value: "clear", title: "Clear choices", - description: "We know our options and their trade-offs", + description: "We know our options and their tradeoffs", }, { value: "emerging", @@ -768,7 +774,7 @@ function determineFramework() { method: "Strategic Delay", tagline: "Wait for clarity to emerge", reasoning: - "It's not urgent, options aren't clear, and people aren't strongly invested. Sometimes the best decision is to not decide yet.", + "It's not urgent, options aren't clear, and people aren't strongly invested. Sometimes the best decision is to hold off on deciding!", steps: [ "Acknowledge the decision exists", "Set a future check-in date", @@ -803,7 +809,7 @@ function determineFramework() { "This is a high-stakes, permanent decision affecting everyone who cares deeply. Take the time to get real alignment.", steps: [ "Share context and constraints with everyone", - "Gather all perspectives (async or sync)", + "Gather all perspectives (async or live discussion)", "Identify shared values and concerns", "Iterate on proposals until everyone can support it", "Document the decision and everyone's commitment", @@ -902,7 +908,7 @@ function determineFramework() { state.reversible === "high" ) { return { - method: "Controlled Randomness", + method: "Roll the Dice", tagline: "Let chance break the tie", reasoning: "Options are equally good, stakes are low, and people aren't strongly invested. Save time and energy.", diff --git a/pages/templates/index.vue b/pages/tools/templates/index.vue similarity index 100% rename from pages/templates/index.vue rename to pages/tools/templates/index.vue diff --git a/pages/templates/membership-agreement.vue b/pages/tools/templates/membership-agreement.vue similarity index 95% rename from pages/templates/membership-agreement.vue rename to pages/tools/templates/membership-agreement.vue index 39a74d8..1360939 100644 --- a/pages/templates/membership-agreement.vue +++ b/pages/tools/templates/membership-agreement.vue @@ -207,7 +207,8 @@
  • Values alignment conversation
  • - Optional - Equal buy-in contribution of $

    - Decisions under $

    - Decisions between $ - and $Adding or removing members

  • Changing this agreement
  • - Taking on debt over $

    Each member owns an equal share of - {{ getDisplayName().toLowerCase() }}, - regardless of hours worked or tenure. + {{ getDisplayName().toLowerCase() }}, regardless of hours + worked or tenure.

    @@ -450,7 +455,8 @@

    • - Base rate: $/hour for all members
    • - Or: Equal monthly draw of $
      • - Hourly rate: $
      • Regular needs assessment and adjustment process
      • - Minimum guaranteed amount: $ Payment Schedule:

        -
          -
        • - Paid on the - +

          + Paid on the + - of each month -

        • -
        • - Surplus (profit) distributed equally every - -
        • -
        + of each month and the surplus (profit) distributed equally + every + . +

        @@ -805,8 +811,8 @@

        - {{ getDisplayName() || "This cooperative" }} operates as - an informal collective. If we decide to register legally in the + {{ getDisplayName() || "This cooperative" }} operates as an + informal collective. If we decide to register legally in the future, we'll update this section with our legal structure details.

        @@ -826,10 +832,15 @@