Compare commits
14 Commits
551ae0d217
...
c4a2bc7164
| Author | SHA1 | Date |
|---|---|---|
|
|
c4a2bc7164 | |
|
|
19cf1de886 | |
|
|
2ead9ed666 | |
|
|
7781dad704 | |
|
|
746ae71601 | |
|
|
4e777970cd | |
|
|
67bb61888b | |
|
|
5b87103fdb | |
|
|
acd874924f | |
|
|
93fcbd45ef | |
|
|
a36244187a | |
|
|
0cf11b32fb | |
|
|
810d6bbf33 | |
|
|
fc75b92a29 |
|
|
@ -0,0 +1,7 @@
|
|||
node_modules
|
||||
.next
|
||||
.git
|
||||
.gitignore
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
*.md
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
FROM node:22-alpine AS base
|
||||
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
CMD ["node", "server.js"]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
project_name: CosmoLocal Website
|
||||
project_id: cosmolocal-website
|
||||
description: CosmoLocal World website and related infrastructure
|
||||
created: '2026-02-09'
|
||||
integration_mode: mcp
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
id: task-1
|
||||
title: Set up CosmoLocal email, docs, and newsletter infrastructure
|
||||
status: Done
|
||||
assignee: ['@claude']
|
||||
created_date: '2026-02-09 12:00'
|
||||
updated_date: '2026-02-09 21:30'
|
||||
labels: [infrastructure, email, docs, newsletter]
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Set up CosmoLocal World infrastructure: Mailcow SMTP for cosmolocal.world, Docmost workspace at docs.cosmolocal.world, Listmonk newsletter list with per-list RBAC for Bryan, and email authentication (DKIM, SPF, DMARC).
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Configure Mailcow SMTP for cosmolocal.world domain
|
||||
- [x] #2 Set up DNS records (SPF, DKIM, DMARC) for cosmolocal.world
|
||||
- [x] #3 Create noreply@cosmolocal.world mailbox with newsletter@ alias
|
||||
- [x] #4 Deploy Docmost at docs.cosmolocal.world (separate workspace, shared infra)
|
||||
- [x] #5 Configure SMTP for Docmost CosmoLocal instance
|
||||
- [x] #6 Create CosmoLocal World list in Listmonk
|
||||
- [x] #7 Set up Bryan as editor with per-list RBAC (CosmoLocal list only)
|
||||
- [x] #8 Set up Google Postmaster Tools for cosmolocal.world
|
||||
- [x] #9 Configure Traefik websecure + Let's Encrypt for docs.cosmolocal.world
|
||||
- [x] #10 Invite Bryan to CosmoLocal Docmost workspace
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
### Mailcow (cosmolocal.world)
|
||||
- Mailbox: noreply@cosmolocal.world
|
||||
- Alias: newsletter@cosmolocal.world → noreply@cosmolocal.world (sender_allowed=1)
|
||||
- SMTP: [SMTP_HOST]:465 (TLS)
|
||||
- DNS: SPF, DKIM (2048-bit), DMARC all configured on Cloudflare
|
||||
- Google Postmaster Tools verified
|
||||
|
||||
### Docmost (docs.cosmolocal.world)
|
||||
- Separate Docmost app container (docmost-cl) sharing Postgres + Redis with docs.jeffemmett.com
|
||||
- Database: docmost_cosmolocal (in shared docmost-db Postgres)
|
||||
- Redis: db 1 (shared docmost-redis)
|
||||
- DNS: proxied A record → 159.195.32.209
|
||||
- SSL: Traefik websecure entrypoint + Let's Encrypt cert
|
||||
- SMTP: noreply@cosmolocal.world via Mailcow
|
||||
- Location: /opt/apps/docmost/docker-compose.yml (single compose file)
|
||||
|
||||
### Listmonk (newsletter.cosmolocal.world)
|
||||
- CosmoLocal World list created (id=21, public, single opt-in)
|
||||
- SMTP server "cosmolocal.world" configured in Listmonk settings
|
||||
- Bryan's account: bryan / CosmoLocal-e2dc5eec
|
||||
- User role: Editor (campaigns, subscribers, templates, media - no admin)
|
||||
- List role: CosmoLocal Editor (scoped to CosmoLocal World list only)
|
||||
- Cannot see other lists, settings, users, or roles
|
||||
|
||||
### Bryan's Access Summary
|
||||
| Service | URL | Username | Role |
|
||||
|---------|-----|----------|------|
|
||||
| Listmonk | newsletter.jeffemmett.com/admin | bryan | Editor (CosmoLocal list only) |
|
||||
| Docmost | docs.cosmolocal.world | bryan@cosmolocal.world | Member (invited) |
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
services:
|
||||
cosmolocal-website:
|
||||
build: .
|
||||
container_name: cosmolocal-website
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.cosmolocal.rule=Host(`cosmolocal.world`) || Host(`www.cosmolocal.world`)"
|
||||
- "traefik.http.routers.cosmolocal.entrypoints=web"
|
||||
- "traefik.http.services.cosmolocal.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.cosmolocal-secure.rule=Host(`cosmolocal.world`) || Host(`www.cosmolocal.world`)"
|
||||
- "traefik.http.routers.cosmolocal-secure.entrypoints=websecure"
|
||||
- "traefik.http.routers.cosmolocal-secure.tls=true"
|
||||
- "traefik.http.routers.cosmolocal-secure.service=cosmolocal"
|
||||
networks:
|
||||
- traefik-public
|
||||
|
||||
n8n-cosmolocal:
|
||||
image: n8nio/n8n:latest
|
||||
container_name: n8n-cosmolocal
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- N8N_HOST=automate.cosmolocal.world
|
||||
- N8N_PROTOCOL=https
|
||||
- WEBHOOK_URL=https://automate.cosmolocal.world/
|
||||
- GENERIC_TIMEZONE=Europe/Brussels
|
||||
- DB_TYPE=postgresdb
|
||||
- DB_POSTGRESDB_HOST=n8n-cosmolocal-db
|
||||
- DB_POSTGRESDB_PORT=5432
|
||||
- DB_POSTGRESDB_DATABASE=n8n
|
||||
- DB_POSTGRESDB_USER=n8n
|
||||
- DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD}
|
||||
- TWENTY_API_KEY=${TWENTY_API_KEY}
|
||||
- LISTMONK_CREDENTIALS=${LISTMONK_CREDENTIALS}
|
||||
- N8N_SMTP_HOST=${SMTP_HOST}
|
||||
- N8N_SMTP_PORT=${SMTP_PORT}
|
||||
- N8N_SMTP_USER=${SMTP_USER}
|
||||
- N8N_SMTP_PASS=${SMTP_PASS}
|
||||
- N8N_SMTP_SENDER=${SMTP_SENDER}
|
||||
- N8N_EMAIL_MODE=smtp
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- n8n-cosmolocal-data:/home/node/.n8n
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.n8n-cosmolocal.rule=Host(`automate.cosmolocal.world`)"
|
||||
- "traefik.http.routers.n8n-cosmolocal.entrypoints=web"
|
||||
- "traefik.http.services.n8n-cosmolocal.loadbalancer.server.port=5678"
|
||||
- "traefik.http.routers.n8n-cosmolocal-secure.rule=Host(`automate.cosmolocal.world`)"
|
||||
- "traefik.http.routers.n8n-cosmolocal-secure.entrypoints=websecure"
|
||||
- "traefik.http.routers.n8n-cosmolocal-secure.tls=true"
|
||||
- "traefik.http.routers.n8n-cosmolocal-secure.service=n8n-cosmolocal"
|
||||
depends_on:
|
||||
n8n-cosmolocal-db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- traefik-public
|
||||
- cosmolocal-internal
|
||||
|
||||
n8n-cosmolocal-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: n8n-cosmolocal-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_DB=n8n
|
||||
- POSTGRES_USER=n8n
|
||||
- POSTGRES_PASSWORD=${N8N_DB_PASSWORD}
|
||||
volumes:
|
||||
- n8n-cosmolocal-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U n8n"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- cosmolocal-internal
|
||||
|
||||
volumes:
|
||||
n8n-cosmolocal-data:
|
||||
n8n-cosmolocal-db:
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
cosmolocal-internal:
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
{
|
||||
"name": "Contact Intake — Form to CRM",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "contact-intake",
|
||||
"responseMode": "responseNode",
|
||||
"options": {}
|
||||
},
|
||||
"id": "webhook-trigger",
|
||||
"name": "Webhook — Contact Form",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [240, 300],
|
||||
"webhookId": "contact-intake"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "check-email",
|
||||
"leftValue": "={{ $json.body.email }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "isNotEmpty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "check-name",
|
||||
"leftValue": "={{ $json.body.name }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "isNotEmpty"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "validate-input",
|
||||
"name": "Validate Input",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [460, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://crm.cosmolocal.world/api/v1/objects/people",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer {{ $env.TWENTY_API_KEY }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"name\": {\n \"firstName\": \"{{ $json.body.name.split(' ')[0] }}\",\n \"lastName\": \"{{ $json.body.name.split(' ').slice(1).join(' ') || '' }}\"\n },\n \"emails\": {\n \"primaryEmail\": \"{{ $json.body.email }}\"\n },\n \"phones\": {\n \"primaryPhone\": \"{{ $json.body.phone || '' }}\"\n }\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "create-crm-contact",
|
||||
"name": "Create Contact in Twenty CRM",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [680, 240]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://crm.cosmolocal.world/api/v1/objects/notes",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer {{ $env.TWENTY_API_KEY }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"title\": \"Contact Form Submission\",\n \"body\": \"{{ $('Webhook — Contact Form').item.json.body.message || 'No message provided' }}\",\n \"noteTargets\": [{\n \"personId\": \"{{ $json.data.id }}\"\n }]\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "add-note",
|
||||
"name": "Add Note to Contact",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [900, 240]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={ \"success\": true, \"message\": \"Contact created successfully\" }",
|
||||
"options": {
|
||||
"responseCode": 200
|
||||
}
|
||||
},
|
||||
"id": "respond-success",
|
||||
"name": "Respond — Success",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"position": [1120, 240]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={ \"success\": false, \"message\": \"Missing required fields: name and email\" }",
|
||||
"options": {
|
||||
"responseCode": 400
|
||||
}
|
||||
},
|
||||
"id": "respond-error",
|
||||
"name": "Respond — Validation Error",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"position": [680, 420]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook — Contact Form": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Validate Input",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Validate Input": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Contact in Twenty CRM",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Respond — Validation Error",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create Contact in Twenty CRM": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Add Note to Contact",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Add Note to Contact": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond — Success",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"staticData": null,
|
||||
"tags": [
|
||||
{
|
||||
"name": "cosmolocal"
|
||||
},
|
||||
{
|
||||
"name": "crm"
|
||||
}
|
||||
],
|
||||
"pinData": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
{
|
||||
"name": "Lead Nurturing — Welcome Email Sequence",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "new-contact-created",
|
||||
"options": {}
|
||||
},
|
||||
"id": "webhook-new-contact",
|
||||
"name": "Webhook — New Contact Created",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [240, 300],
|
||||
"webhookId": "new-contact-created"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "set-email",
|
||||
"name": "email",
|
||||
"value": "={{ $json.body.email || $json.body.emails?.primaryEmail }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "set-first-name",
|
||||
"name": "firstName",
|
||||
"value": "={{ $json.body.firstName || $json.body.name?.firstName || 'there' }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "set-contact-id",
|
||||
"name": "contactId",
|
||||
"value": "={{ $json.body.id || $json.body.contactId }}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "extract-fields",
|
||||
"name": "Extract Contact Fields",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [460, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fromEmail": "={{ $env.N8N_SMTP_SENDER }}",
|
||||
"toEmail": "={{ $json.email }}",
|
||||
"subject": "Welcome to the Cosmolocal Foundation",
|
||||
"emailType": "html",
|
||||
"html": "<h2>Welcome, {{ $json.firstName }}!</h2><p>Thank you for connecting with the Cosmolocal Foundation. We're building a world where local communities thrive within regenerative economies, connected through global knowledge-sharing and commons-based collaboration.</p><p><strong>\"What is heavy should be local, and what is light should be global and shared.\"</strong></p><p>Here's what we're working on:</p><ul><li><strong>Decentralized Governance</strong> — Transparent, community-led decision-making</li><li><strong>Open Knowledge Commons</strong> — Sharing sustainable production methods globally</li><li><strong>Commons-Compatible Capital</strong> — Financing transformative local projects</li><li><strong>Cosmolocal Coordination</strong> — Connecting local efforts into a global network</li></ul><p>We'll be in touch with updates on our progress and opportunities to get involved.</p><p>Warm regards,<br/>The Cosmolocal Foundation Team</p><p style='color:#78716c;font-size:12px;'>cosmolocal.world</p>",
|
||||
"options": {}
|
||||
},
|
||||
"id": "send-welcome-email",
|
||||
"name": "Send Welcome Email (Day 0)",
|
||||
"type": "n8n-nodes-base.emailSend",
|
||||
"typeVersion": 2.1,
|
||||
"position": [680, 300],
|
||||
"credentials": {
|
||||
"smtp": {
|
||||
"id": "mailcow-smtp",
|
||||
"name": "Mailcow SMTP"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"amount": 3,
|
||||
"unit": "days"
|
||||
},
|
||||
"id": "wait-3-days",
|
||||
"name": "Wait 3 Days",
|
||||
"type": "n8n-nodes-base.wait",
|
||||
"typeVersion": 1.1,
|
||||
"position": [900, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fromEmail": "={{ $env.N8N_SMTP_SENDER }}",
|
||||
"toEmail": "={{ $('Extract Contact Fields').item.json.email }}",
|
||||
"subject": "Our Strategic Priorities — How We're Building Change",
|
||||
"emailType": "html",
|
||||
"html": "<h2>Hi {{ $('Extract Contact Fields').item.json.firstName }},</h2><p>We wanted to share more about our strategic approach to systemic transformation.</p><p>The Cosmolocal Foundation is executing on eight key initiatives:</p><ol><li><strong>Mapping Regenerative Communities</strong> — Cataloging eco-villages, circular economy hubs, and governance initiatives worldwide</li><li><strong>Web3 Funding</strong> — Quadratic funding, DAOs, and Collaborative Finance for local projects</li><li><strong>Open Resources</strong> — A global knowledge commons of governance models and production blueprints</li><li><strong>Education & Advocacy</strong> — Engaging policymakers to scale cosmolocal principles</li><li><strong>Pilots & Grants</strong> — Funding projects that demonstrate scalable solutions</li><li><strong>Cosmolocal Certification</strong> — Blockchain-verified standards for cosmolocal initiatives</li><li><strong>Global Alliances</strong> — Bioregional collaboration on governance and mutual aid</li><li><strong>Impact Research</strong> — Evidence-based scaling through the Cosmolocal Research Institute</li></ol><p>Want to learn more or get involved? Reply to this email — we'd love to hear from you.</p><p>Best,<br/>The Cosmolocal Foundation Team</p>",
|
||||
"options": {}
|
||||
},
|
||||
"id": "send-followup-email",
|
||||
"name": "Send Follow-up Email (Day 3)",
|
||||
"type": "n8n-nodes-base.emailSend",
|
||||
"typeVersion": 2.1,
|
||||
"position": [1120, 300],
|
||||
"credentials": {
|
||||
"smtp": {
|
||||
"id": "mailcow-smtp",
|
||||
"name": "Mailcow SMTP"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"amount": 7,
|
||||
"unit": "days"
|
||||
},
|
||||
"id": "wait-7-days",
|
||||
"name": "Wait 7 Days",
|
||||
"type": "n8n-nodes-base.wait",
|
||||
"typeVersion": 1.1,
|
||||
"position": [1340, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fromEmail": "={{ $env.N8N_SMTP_SENDER }}",
|
||||
"toEmail": "={{ $('Extract Contact Fields').item.json.email }}",
|
||||
"subject": "Join the Movement — Ways to Participate",
|
||||
"emailType": "html",
|
||||
"html": "<h2>Hi {{ $('Extract Contact Fields').item.json.firstName }},</h2><p>We believe in the power of collaboration. Here are ways you can participate in the cosmolocal movement:</p><ul><li><strong>Subscribe to our newsletter</strong> — Stay updated on projects, pilots, and governance experiments</li><li><strong>Join a working group</strong> — Contribute your expertise to research, technology, or community building</li><li><strong>Support a pilot project</strong> — Help fund or participate in cosmolocal demonstrations</li><li><strong>Spread the word</strong> — Share our vision with your network</li></ul><p>Visit <a href='https://cosmolocal.world'>cosmolocal.world</a> to explore our work, or simply reply to this email to start a conversation.</p><p>Together we can build regenerative economies that serve communities and the planet.</p><p>In solidarity,<br/>The Cosmolocal Foundation Team</p>",
|
||||
"options": {}
|
||||
},
|
||||
"id": "send-engagement-email",
|
||||
"name": "Send Engagement Email (Day 10)",
|
||||
"type": "n8n-nodes-base.emailSend",
|
||||
"typeVersion": 2.1,
|
||||
"position": [1560, 300],
|
||||
"credentials": {
|
||||
"smtp": {
|
||||
"id": "mailcow-smtp",
|
||||
"name": "Mailcow SMTP"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "PATCH",
|
||||
"url": "=https://crm.cosmolocal.world/api/v1/objects/people/{{ $('Extract Contact Fields').item.json.contactId }}",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer {{ $env.TWENTY_API_KEY }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"stage\": \"NURTURE_COMPLETE\"\n}",
|
||||
"options": {
|
||||
"ignore_ssl_issues": false
|
||||
}
|
||||
},
|
||||
"id": "update-crm-stage",
|
||||
"name": "Update CRM — Nurture Complete",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1780, 300]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook — New Contact Created": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Contact Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Contact Fields": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Welcome Email (Day 0)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Send Welcome Email (Day 0)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Wait 3 Days",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Wait 3 Days": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Follow-up Email (Day 3)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Send Follow-up Email (Day 3)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Wait 7 Days",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Wait 7 Days": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Engagement Email (Day 10)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Send Engagement Email (Day 10)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update CRM — Nurture Complete",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"staticData": null,
|
||||
"tags": [
|
||||
{
|
||||
"name": "cosmolocal"
|
||||
},
|
||||
{
|
||||
"name": "email"
|
||||
}
|
||||
],
|
||||
"pinData": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
{
|
||||
"name": "Newsletter Sync — CRM to Listmonk",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 6,
|
||||
"triggerAtMinute": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-trigger",
|
||||
"name": "Daily Sync (6 AM)",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [240, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://crm.cosmolocal.world/api/v1/objects/people",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer {{ $env.TWENTY_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"value": "100"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "fetch-crm-contacts",
|
||||
"name": "Fetch CRM Contacts",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [460, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data.people",
|
||||
"options": {}
|
||||
},
|
||||
"id": "split-contacts",
|
||||
"name": "Split Into Items",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [680, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "has-email",
|
||||
"leftValue": "={{ $json.emails?.primaryEmail }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "isNotEmpty"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "filter-with-email",
|
||||
"name": "Filter — Has Email",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [900, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "=http://listmonk-cosmolocal:9000/api/subscribers",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Basic {{ Buffer.from($env.LISTMONK_CREDENTIALS || 'admin:changeme').toString('base64') }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "query",
|
||||
"value": "=subscribers.email = '{{ $json.emails.primaryEmail }}'"
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"name": "per_page",
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "check-listmonk-exists",
|
||||
"name": "Check If Subscriber Exists",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1120, 240]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "not-exists",
|
||||
"leftValue": "={{ $json.data?.total }}",
|
||||
"rightValue": 0,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-new-subscriber",
|
||||
"name": "If New Subscriber",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [1340, 240]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://listmonk-cosmolocal:9000/api/subscribers",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Basic {{ Buffer.from($env.LISTMONK_CREDENTIALS || 'admin:changeme').toString('base64') }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"email\": \"{{ $('Filter — Has Email').item.json.emails.primaryEmail }}\",\n \"name\": \"{{ $('Filter — Has Email').item.json.name?.firstName || '' }} {{ $('Filter — Has Email').item.json.name?.lastName || '' }}\",\n \"status\": \"enabled\",\n \"lists\": [1],\n \"preconfirm_subscriptions\": true,\n \"attribs\": {\n \"source\": \"twenty-crm-sync\",\n \"crm_id\": \"{{ $('Filter — Has Email').item.json.id }}\"\n }\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "create-subscriber",
|
||||
"name": "Create Listmonk Subscriber",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1560, 180]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "no-op-exists",
|
||||
"name": "Already Subscribed — Skip",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1560, 360]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Daily Sync (6 AM)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch CRM Contacts",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Fetch CRM Contacts": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Into Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Into Items": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter — Has Email",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter — Has Email": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check If Subscriber Exists",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Check If Subscriber Exists": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If New Subscriber",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If New Subscriber": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Listmonk Subscriber",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Already Subscribed — Skip",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"staticData": null,
|
||||
"tags": [
|
||||
{
|
||||
"name": "cosmolocal"
|
||||
},
|
||||
{
|
||||
"name": "newsletter"
|
||||
}
|
||||
],
|
||||
"pinData": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
{
|
||||
"name": "Follow-up Reminders — Stale Contacts",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 9,
|
||||
"triggerAtMinute": 0,
|
||||
"triggerAtDay": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "weekly-schedule",
|
||||
"name": "Weekly Check (Monday 9 AM)",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [240, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://crm.cosmolocal.world/api/v1/objects/people",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer {{ $env.TWENTY_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "limit",
|
||||
"value": "100"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "fetch-all-contacts",
|
||||
"name": "Fetch All Contacts",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [460, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data.people",
|
||||
"options": {}
|
||||
},
|
||||
"id": "split-contacts",
|
||||
"name": "Split Into Items",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [680, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst staleItems = [];\nconst now = new Date();\nconst STALE_DAYS = 14;\n\nfor (const item of items) {\n const updatedAt = new Date(item.json.updatedAt);\n const daysSinceUpdate = Math.floor((now - updatedAt) / (1000 * 60 * 60 * 24));\n \n if (daysSinceUpdate >= STALE_DAYS) {\n staleItems.push({\n json: {\n ...item.json,\n daysSinceUpdate,\n email: item.json.emails?.primaryEmail || 'N/A',\n fullName: `${item.json.name?.firstName || ''} ${item.json.name?.lastName || ''}`.trim() || 'Unknown'\n }\n });\n }\n}\n\nreturn staleItems.length > 0 ? staleItems : [{ json: { noStaleContacts: true } }];"
|
||||
},
|
||||
"id": "filter-stale",
|
||||
"name": "Filter Stale Contacts (14+ days)",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [900, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "has-stale",
|
||||
"leftValue": "={{ $json.noStaleContacts }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "notTrue"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-has-stale",
|
||||
"name": "Any Stale Contacts?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [1120, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst count = items.length;\n\nconst tableRows = items.map(i => `<tr><td style=\"padding:8px;border:1px solid #e7e5e4\">${i.json.fullName}</td><td style=\"padding:8px;border:1px solid #e7e5e4\">${i.json.email}</td><td style=\"padding:8px;border:1px solid #e7e5e4\">${i.json.daysSinceUpdate}</td></tr>`).join('');\n\nreturn [{\n json: {\n subject: `[Cosmolocal CRM] ${count} contact${count !== 1 ? 's' : ''} need${count === 1 ? 's' : ''} follow-up`,\n htmlBody: `<h2>Stale Contact Report</h2><p>The following ${count} contact${count !== 1 ? 's have' : ' has'} not been updated in 14+ days:</p><table style=\"border-collapse:collapse;width:100%\"><tr style=\"background:#f5f5f4\"><th style=\"padding:8px;text-align:left;border:1px solid #e7e5e4\">Name</th><th style=\"padding:8px;text-align:left;border:1px solid #e7e5e4\">Email</th><th style=\"padding:8px;text-align:left;border:1px solid #e7e5e4\">Days Stale</th></tr>${tableRows}</table><p style=\"margin-top:16px\"><a href=\"https://crm.cosmolocal.world\">Open CRM</a> to follow up.</p><p style=\"color:#78716c;font-size:12px\">Automated by n8n — automate.cosmolocal.world</p>`,\n count\n }\n}];"
|
||||
},
|
||||
"id": "build-report",
|
||||
"name": "Build Report Email",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [1340, 240]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fromEmail": "={{ $env.N8N_SMTP_SENDER }}",
|
||||
"toEmail": "={{ $env.N8N_SMTP_SENDER }}",
|
||||
"subject": "={{ $json.subject }}",
|
||||
"emailType": "html",
|
||||
"html": "={{ $json.htmlBody }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "send-reminder-email",
|
||||
"name": "Send Reminder to Team",
|
||||
"type": "n8n-nodes-base.emailSend",
|
||||
"typeVersion": 2.1,
|
||||
"position": [1560, 240],
|
||||
"credentials": {
|
||||
"smtp": {
|
||||
"id": "mailcow-smtp",
|
||||
"name": "Mailcow SMTP"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "no-stale-contacts",
|
||||
"name": "No Stale Contacts — Done",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [1340, 420]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Weekly Check (Monday 9 AM)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch All Contacts",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Fetch All Contacts": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Into Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Into Items": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter Stale Contacts (14+ days)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter Stale Contacts (14+ days)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Any Stale Contacts?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Any Stale Contacts?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Report Email",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "No Stale Contacts — Done",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Report Email": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Reminder to Team",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"staticData": null,
|
||||
"tags": [
|
||||
{
|
||||
"name": "cosmolocal"
|
||||
},
|
||||
{
|
||||
"name": "crm"
|
||||
}
|
||||
],
|
||||
"pinData": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,347 @@
|
|||
{
|
||||
"name": "Webhook Events — Gitea/GitHub to CRM",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "git-events",
|
||||
"options": {}
|
||||
},
|
||||
"id": "webhook-git",
|
||||
"name": "Webhook — Git Events",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [240, 300],
|
||||
"webhookId": "git-events"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const body = $input.first().json.body;\nconst headers = $input.first().json.headers;\n\n// Detect source (Gitea vs GitHub)\nconst isGitea = headers['x-gitea-event'] !== undefined;\nconst isGitHub = headers['x-github-event'] !== undefined;\n\nconst eventType = isGitea \n ? headers['x-gitea-event'] \n : headers['x-github-event'] || 'unknown';\n\nlet result = {\n source: isGitea ? 'gitea' : isGitHub ? 'github' : 'unknown',\n eventType,\n repo: body.repository?.full_name || body.repository?.name || 'unknown',\n action: body.action || 'push',\n sender: body.sender?.login || body.pusher?.name || 'unknown',\n senderEmail: body.sender?.email || body.pusher?.email || '',\n url: '',\n title: '',\n description: ''\n};\n\nswitch (eventType) {\n case 'push':\n const commits = body.commits || [];\n result.title = `Push: ${commits.length} commit(s) to ${result.repo}`;\n result.description = commits.map(c => `- ${c.message}`).join('\\n');\n result.url = body.compare_url || body.compare || '';\n break;\n case 'issues':\n result.title = `Issue ${body.action}: ${body.issue?.title}`;\n result.description = body.issue?.body || '';\n result.url = body.issue?.html_url || '';\n break;\n case 'pull_request':\n result.title = `PR ${body.action}: ${body.pull_request?.title}`;\n result.description = body.pull_request?.body || '';\n result.url = body.pull_request?.html_url || '';\n break;\n case 'create':\n result.title = `Created ${body.ref_type}: ${body.ref}`;\n result.description = `New ${body.ref_type} created in ${result.repo}`;\n break;\n case 'star':\n case 'watch':\n result.title = `${result.sender} starred ${result.repo}`;\n result.description = `Repository now has ${body.repository?.stars_count || body.repository?.stargazers_count || '?'} stars`;\n break;\n default:\n result.title = `${eventType} event on ${result.repo}`;\n result.description = JSON.stringify(body).substring(0, 500);\n}\n\nreturn [{ json: result }];"
|
||||
},
|
||||
"id": "parse-event",
|
||||
"name": "Parse Git Event",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [460, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "is-significant",
|
||||
"leftValue": "={{ $json.eventType }}",
|
||||
"rightValue": "push,issues,pull_request,star",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "contains"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "filter-significant",
|
||||
"name": "Filter Significant Events",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [680, 300]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://crm.cosmolocal.world/api/v1/objects/notes",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer {{ $env.TWENTY_API_KEY }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"title\": \"[{{ $json.source }}] {{ $json.title }}\",\n \"body\": \"**Event**: {{ $json.eventType }}\\n**Repo**: {{ $json.repo }}\\n**By**: {{ $json.sender }}\\n\\n{{ $json.description }}\\n\\n{{ $json.url ? '[View on ' + $json.source + '](' + $json.url + ')' : '' }}\"\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "log-to-crm",
|
||||
"name": "Log Activity to CRM",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [900, 240]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "has-email",
|
||||
"leftValue": "={{ $('Parse Git Event').item.json.senderEmail }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "isNotEmpty"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "has-sender-email",
|
||||
"name": "Sender Has Email?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [1120, 240]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://crm.cosmolocal.world/api/v1/objects/people",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer {{ $env.TWENTY_API_KEY }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "filter",
|
||||
"value": "={\"emails\":{\"primaryEmail\":{\"eq\":\"{{ $('Parse Git Event').item.json.senderEmail }}\"}}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "find-contact",
|
||||
"name": "Find Contact by Email",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1340, 180]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "contact-found",
|
||||
"leftValue": "={{ $json.data?.people?.length }}",
|
||||
"rightValue": 0,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "gt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "contact-exists",
|
||||
"name": "Contact Found?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [1560, 180]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "PATCH",
|
||||
"url": "=https://crm.cosmolocal.world/api/v1/objects/people/{{ $json.data.people[0].id }}",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer {{ $env.TWENTY_API_KEY }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"updatedAt\": \"{{ new Date().toISOString() }}\"\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "touch-contact",
|
||||
"name": "Touch Contact — Update Timestamp",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1780, 120]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://crm.cosmolocal.world/api/v1/objects/people",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer {{ $env.TWENTY_API_KEY }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"name\": {\n \"firstName\": \"{{ $('Parse Git Event').item.json.sender }}\",\n \"lastName\": \"\"\n },\n \"emails\": {\n \"primaryEmail\": \"{{ $('Parse Git Event').item.json.senderEmail }}\"\n }\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "create-contributor-contact",
|
||||
"name": "Create Contributor Contact",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1780, 280]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "no-op-skip",
|
||||
"name": "Skip — Not Significant",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [900, 420]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook — Git Events": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Git Event",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Git Event": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter Significant Events",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter Significant Events": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Log Activity to CRM",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Skip — Not Significant",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Log Activity to CRM": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Sender Has Email?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Sender Has Email?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Find Contact by Email",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Find Contact by Email": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Contact Found?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Contact Found?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Touch Contact — Update Timestamp",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Create Contributor Contact",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"staticData": null,
|
||||
"tags": [
|
||||
{
|
||||
"name": "cosmolocal"
|
||||
},
|
||||
{
|
||||
"name": "git"
|
||||
},
|
||||
{
|
||||
"name": "crm"
|
||||
}
|
||||
],
|
||||
"pinData": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# Cosmolocal n8n Workflows
|
||||
|
||||
Import these JSON files into [automate.cosmolocal.world](https://automate.cosmolocal.world) via **Settings > Import Workflow**.
|
||||
|
||||
## Setup Requirements
|
||||
|
||||
API keys are passed as **Docker environment variables** in `docker-compose.yml` (n8n community edition doesn't support Settings > Variables). The workflows access them via `$env.VARIABLE_NAME`.
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `TWENTY_API_KEY` | CRM API key |
|
||||
| `SMTP_HOST` | SMTP server hostname |
|
||||
| `SMTP_PORT` | SMTP server port |
|
||||
| `SMTP_USER` | SMTP username |
|
||||
| `SMTP_PASS` | SMTP password |
|
||||
| `SMTP_SENDER` | From address for outgoing email |
|
||||
| `LISTMONK_CREDENTIALS` | Listmonk `user:pass` |
|
||||
| `N8N_DB_PASSWORD` | n8n PostgreSQL password |
|
||||
|
||||
All credentials are stored in `.env` on the server (gitignored). See server admin for values.
|
||||
|
||||
## Workflows
|
||||
|
||||
### 01 — Contact Intake (Form to CRM)
|
||||
**Trigger:** Webhook POST to `/webhook/contact-intake`
|
||||
**Flow:** Validate input > Create contact in Twenty CRM > Add note with message > Respond
|
||||
|
||||
Use this webhook URL in your website contact form:
|
||||
```
|
||||
https://automate.cosmolocal.world/webhook/contact-intake
|
||||
```
|
||||
|
||||
POST body:
|
||||
```json
|
||||
{
|
||||
"name": "Jane Doe",
|
||||
"email": "jane@example.com",
|
||||
"phone": "+1234567890",
|
||||
"message": "Interested in collaborating"
|
||||
}
|
||||
```
|
||||
|
||||
### 02 — Lead Nurturing (Welcome Email Sequence)
|
||||
**Trigger:** Webhook POST to `/webhook/new-contact-created`
|
||||
**Flow:** Day 0: Welcome email > Day 3: Strategic priorities > Day 10: Ways to participate > Update CRM stage
|
||||
|
||||
Chain this from workflow 01 or call it when a new contact is created in the CRM.
|
||||
|
||||
### 03 — Newsletter Sync (CRM to Listmonk)
|
||||
**Trigger:** Daily at 6:00 AM
|
||||
**Flow:** Fetch CRM contacts > Check if already in Listmonk > Create new subscribers
|
||||
|
||||
Syncs all CRM contacts with email addresses to Listmonk list #1. Adjust the list ID if needed.
|
||||
|
||||
### 04 — Follow-up Reminders (Stale Contacts)
|
||||
**Trigger:** Weekly on Monday at 9:00 AM
|
||||
**Flow:** Fetch contacts > Filter those not updated in 14+ days > Email report to team
|
||||
|
||||
Sends an HTML table report to the team with stale contacts and a link to the CRM.
|
||||
|
||||
### 05 — Webhook Events (Git to CRM)
|
||||
**Trigger:** Webhook POST to `/webhook/git-events`
|
||||
**Flow:** Parse Gitea/GitHub event > Log as CRM note > Find or create contributor contact
|
||||
|
||||
Add this webhook URL to Gitea/GitHub repos:
|
||||
```
|
||||
https://automate.cosmolocal.world/webhook/git-events
|
||||
```
|
||||
|
||||
Supports: push, issues, pull_request, star/watch events. Auto-detects Gitea vs GitHub from headers.
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 391 B |
|
|
@ -1 +0,0 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -1 +0,0 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 128 B |
|
|
@ -1 +0,0 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
Before Width: | Height: | Size: 385 B |
|
|
@ -1,26 +1,69 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--background: #fafaf9;
|
||||
--foreground: #1c1917;
|
||||
--accent: #4f7942;
|
||||
--accent-light: #6b9f5b;
|
||||
--muted: #78716c;
|
||||
--border: #e7e5e4;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-light: var(--accent-light);
|
||||
--color-muted: var(--muted);
|
||||
--color-border: var(--border);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
--background: #0c0a09;
|
||||
--foreground: #e7e5e4;
|
||||
--accent: #6b9f5b;
|
||||
--accent-light: #4f7942;
|
||||
--muted: #a8a29e;
|
||||
--border: #292524;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* Hero section */
|
||||
.hero-section {
|
||||
background: linear-gradient(
|
||||
145deg,
|
||||
#1a2e1a 0%,
|
||||
#2d4a2d 25%,
|
||||
#1c3a2a 50%,
|
||||
#1a2835 75%,
|
||||
#0f1f2e 100%
|
||||
);
|
||||
}
|
||||
|
||||
.hero-glow {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(ellipse 60% 50% at 50% 0%, rgba(107, 159, 91, 0.2) 0%, transparent 70%),
|
||||
radial-gradient(ellipse 40% 40% at 20% 80%, rgba(79, 121, 66, 0.12) 0%, transparent 60%),
|
||||
radial-gradient(ellipse 40% 40% at 80% 60%, rgba(30, 80, 100, 0.1) 0%, transparent 60%);
|
||||
}
|
||||
|
||||
.hero-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
||||
background-size: 64px 64px;
|
||||
mask-image: radial-gradient(ellipse 70% 70% at 50% 50%, black 20%, transparent 80%);
|
||||
-webkit-mask-image: radial-gradient(ellipse 70% 70% at 50% 50%, black 20%, transparent 80%);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,17 @@ const geistMono = Geist_Mono({
|
|||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "Cosmolocal Foundation",
|
||||
description:
|
||||
"The Cosmolocal Foundation empowers communities to build localized, regenerative economies connected through global knowledge-sharing, commons-based collaboration, and decentralized governance.",
|
||||
openGraph: {
|
||||
title: "Cosmolocal Foundation",
|
||||
description:
|
||||
"What is heavy should be local, and what is light should be global and shared.",
|
||||
url: "https://cosmolocal.world",
|
||||
siteName: "Cosmolocal Foundation",
|
||||
type: "website",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
|
|
|||
521
src/app/page.tsx
521
src/app/page.tsx
|
|
@ -1,65 +1,488 @@
|
|||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
<div className="flex min-h-screen flex-col">
|
||||
{/* Nav */}
|
||||
<header className="border-b border-border sticky top-0 z-50 bg-background/80 backdrop-blur-sm">
|
||||
<nav className="mx-auto flex max-w-5xl items-center justify-between px-6 py-4">
|
||||
<a href="#" className="text-lg font-semibold tracking-tight">
|
||||
Cosmolocal Foundation
|
||||
</a>
|
||||
<div className="flex items-center gap-6 text-sm text-muted">
|
||||
<a href="#vision" className="hidden sm:inline transition-colors hover:text-foreground">
|
||||
Vision
|
||||
</a>
|
||||
<a href="#mission" className="hidden sm:inline transition-colors hover:text-foreground">
|
||||
Mission
|
||||
</a>
|
||||
<a href="#priorities" className="hidden sm:inline transition-colors hover:text-foreground">
|
||||
Priorities
|
||||
</a>
|
||||
<a href="#founder" className="hidden sm:inline transition-colors hover:text-foreground">
|
||||
Founder
|
||||
</a>
|
||||
<a href="#contact" className="transition-colors hover:text-foreground">
|
||||
Contact
|
||||
</a>
|
||||
<a
|
||||
href="https://docs.cosmolocal.world"
|
||||
className="rounded-full bg-accent px-4 py-1.5 text-white transition-colors hover:bg-accent-light"
|
||||
>
|
||||
Docs
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{/* Hero */}
|
||||
<section className="hero-section relative flex flex-1 flex-col items-center justify-center overflow-hidden px-6 py-28 text-center sm:py-36">
|
||||
<div className="hero-glow" aria-hidden="true" />
|
||||
<div className="hero-grid" aria-hidden="true" />
|
||||
<div className="relative z-10">
|
||||
<blockquote className="text-lg italic text-white/70 sm:text-xl">
|
||||
“What is heavy should be local, and what is light should be global and shared.”
|
||||
</blockquote>
|
||||
<h1 className="mt-8 max-w-3xl text-4xl font-bold leading-tight tracking-tight text-white sm:text-5xl">
|
||||
Cosmolocal Foundation
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<p className="mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-white/75">
|
||||
Empowering communities to build localized, regenerative economies
|
||||
connected through global knowledge-sharing and commons-based
|
||||
collaboration.
|
||||
</p>
|
||||
<div className="mt-10 flex justify-center gap-4">
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
href="#vision"
|
||||
className="rounded-full bg-white px-6 py-2.5 text-sm font-medium text-stone-900 transition-colors hover:bg-white/90"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
Learn More
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
href="https://docs.cosmolocal.world"
|
||||
className="rounded-full border border-white/25 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-white/10"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Vision */}
|
||||
<section id="vision" className="border-t border-border bg-foreground/[0.02]">
|
||||
<div className="mx-auto max-w-5xl px-6 py-20">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">Vision</h2>
|
||||
<p className="mt-4 max-w-3xl text-muted leading-relaxed">
|
||||
The unfolding ecological crisis and the broader metacrisis
|
||||
necessitate a fundamental reorientation of our productive forces and
|
||||
financial coordination. We must transition toward production models
|
||||
that prioritize long-term ecological and social resilience rather
|
||||
than short-term efficiency, balancing local autonomy with global
|
||||
cooperation.
|
||||
</p>
|
||||
<p className="mt-4 max-w-3xl text-muted leading-relaxed">
|
||||
The Foundation bridges this gap by connecting localized regenerative
|
||||
efforts with global knowledge commons and decentralized financial
|
||||
coordination — scaling physically rooted projects with the
|
||||
coordination capacity available through Web3 technologies.
|
||||
</p>
|
||||
<p className="mt-4 max-w-3xl text-muted leading-relaxed">
|
||||
To achieve systemic transformation, we must relocalize production,
|
||||
de-risk supply chains, and prioritize economies of scope over
|
||||
economies of scale. Traditional globalization has led to fragile
|
||||
supply networks — the Cosmolocal Foundation addresses this by
|
||||
fostering bioregional resiliency through global collaboration.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
</section>
|
||||
|
||||
{/* Mission */}
|
||||
<section id="mission" className="border-t border-border">
|
||||
<div className="mx-auto max-w-5xl px-6 py-20">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">Mission</h2>
|
||||
<p className="mt-4 max-w-3xl text-muted leading-relaxed">
|
||||
We aim to empower communities to build localized, regenerative
|
||||
economies by providing open-source tools, funding, and global
|
||||
coordination networks. Through strategic investments, pilot
|
||||
projects, and policy advocacy, we foster:
|
||||
</p>
|
||||
<div className="mt-12 grid gap-8 sm:grid-cols-2">
|
||||
<div className="rounded-lg border border-border p-6">
|
||||
<h3 className="font-semibold">Decentralized Governance</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Transparent, community-led decision-making that distributes
|
||||
power equitably and ensures accountability at every level.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border p-6">
|
||||
<h3 className="font-semibold">Open Knowledge Commons</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Sharing sustainable production methods, governance models, and
|
||||
regenerative blueprints globally — free and accessible to all.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border p-6">
|
||||
<h3 className="font-semibold">Commons-Compatible Capital</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Legal and property forms adapted to commons requirements,
|
||||
financing transformative local projects without extractive
|
||||
conditions.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border p-6">
|
||||
<h3 className="font-semibold">Cosmolocal Coordination</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Connecting localized efforts into a global, impact-driven
|
||||
network where knowledge flows freely while production remains
|
||||
rooted in place.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Strategic Priorities */}
|
||||
<section id="priorities" className="border-t border-border bg-foreground/[0.02]">
|
||||
<div className="mx-auto max-w-5xl px-6 py-20">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
Strategic Priorities
|
||||
</h2>
|
||||
<p className="mt-4 max-w-3xl text-muted leading-relaxed">
|
||||
To achieve scalable impact, the Foundation executes these key
|
||||
initiatives:
|
||||
</p>
|
||||
<div className="mt-12 grid gap-6 sm:grid-cols-2">
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent">
|
||||
1
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">Mapping Regenerative Communities</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Identifying and cataloging eco-villages, circular economy hubs,
|
||||
and decentralized governance initiatives worldwide to find
|
||||
strategic points of leverage for intervention.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent">
|
||||
2
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">Web3 Funding</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Connecting local projects with crypto-native financial
|
||||
mechanisms such as quadratic funding, DAOs, and Collaborative
|
||||
Finance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent">
|
||||
3
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">Open Resources</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Developing a global knowledge commons of decentralized
|
||||
governance models, regenerative production blueprints, and
|
||||
circular economy tools.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent">
|
||||
4
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">Education & Advocacy</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Producing educational content and engaging policymakers to
|
||||
scale cosmolocal principles in governance and finance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent">
|
||||
5
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">Pilots & Grants Program</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Funding and launching projects that demonstrate scalable
|
||||
cosmolocal solutions, integrating blockchain transparency and
|
||||
commons-based innovation.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent">
|
||||
6
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">Cosmolocal Certification</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Establishing credibility and standards for cosmolocal
|
||||
initiatives, verified through decentralized, blockchain-based
|
||||
compliance mechanisms.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent">
|
||||
7
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">Global Alliances</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Building bioregional alliances to facilitate transnational
|
||||
collaboration on governance, resource management, and mutual
|
||||
aid.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent">
|
||||
8
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-semibold">Impact Research & Iteration</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Establishing a Cosmolocal Research Institute to track success
|
||||
metrics, iterate on best practices, and drive evidence-based
|
||||
scaling strategies.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Why Support */}
|
||||
<section id="support" className="border-t border-border">
|
||||
<div className="mx-auto max-w-5xl px-6 py-20">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
Why Support the Foundation?
|
||||
</h2>
|
||||
<p className="mt-4 max-w-3xl text-muted leading-relaxed">
|
||||
Supporters play a pivotal role in scaling decentralized,
|
||||
community-driven economies. The Foundation creates systemic
|
||||
transformation by bridging regenerative finance with real-world
|
||||
social and ecological impact.
|
||||
</p>
|
||||
<div className="mt-10 grid gap-6 sm:grid-cols-2">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-1 h-2 w-2 shrink-0 rounded-full bg-accent" />
|
||||
<p className="text-sm leading-relaxed text-muted">
|
||||
<span className="font-medium text-foreground">Economic self-sufficiency</span>{" "}
|
||||
and resilience for local communities
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-1 h-2 w-2 shrink-0 rounded-full bg-accent" />
|
||||
<p className="text-sm leading-relaxed text-muted">
|
||||
<span className="font-medium text-foreground">Decentralized governance</span>{" "}
|
||||
models that distribute power more equitably
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-1 h-2 w-2 shrink-0 rounded-full bg-accent" />
|
||||
<p className="text-sm leading-relaxed text-muted">
|
||||
<span className="font-medium text-foreground">Open-source innovation</span>{" "}
|
||||
to democratize knowledge and technology
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="mt-1 h-2 w-2 shrink-0 rounded-full bg-accent" />
|
||||
<p className="text-sm leading-relaxed text-muted">
|
||||
<span className="font-medium text-foreground">Commons-based economics</span>{" "}
|
||||
ensuring long-term sustainable and fair returns
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-10 rounded-lg border border-accent/20 bg-accent/5 p-6">
|
||||
<h3 className="font-semibold">Cosmo-Local Financing Facility</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
The Foundation will establish a special purpose vehicle to create
|
||||
a seamless bridge between impact-driven investors and local
|
||||
regenerative projects. This facility allows unrooted financial
|
||||
capital to flow into productive, regenerative networks, ensuring
|
||||
sustainable returns through resilient, decentralized economies.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Founder */}
|
||||
<section id="founder" className="border-t border-border bg-foreground/[0.02]">
|
||||
<div className="mx-auto max-w-5xl px-6 py-20">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">Founder</h2>
|
||||
<div className="mt-6 max-w-3xl">
|
||||
<h3 className="text-xl font-semibold">Michel Bauwens</h3>
|
||||
<p className="mt-1 text-sm text-accent">
|
||||
Founder, P2P Foundation
|
||||
</p>
|
||||
<p className="mt-4 text-muted leading-relaxed">
|
||||
Michel Bauwens is the driving force behind the Cosmolocal
|
||||
Foundation and a pioneering thinker in peer-to-peer economics and
|
||||
commons-based governance. As the founder of the P2P Foundation, he
|
||||
has researched and promoted decentralized, collaborative economic
|
||||
models that empower communities through shared knowledge and
|
||||
resources, and hosted the first release of the Bitcoin Whitepaper
|
||||
by Satoshi Nakamoto.
|
||||
</p>
|
||||
<p className="mt-4 text-muted leading-relaxed">
|
||||
Bauwens has led multiple initiatives demonstrating the viability
|
||||
of cosmolocal approaches, such as the Ghent Commons Transition
|
||||
Plan, and has advised governments (Ecuador), international
|
||||
institutions (the Vatican), and grassroots organizations. He
|
||||
regularly speaks at global conferences about the intersection of
|
||||
decentralized technology, regenerative economics, and the future
|
||||
of production and value exchange.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Core Values */}
|
||||
<section id="values" className="border-t border-border">
|
||||
<div className="mx-auto max-w-5xl px-6 py-20">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
Core Values
|
||||
</h2>
|
||||
<p className="mt-4 max-w-3xl text-muted leading-relaxed">
|
||||
Guided by our Social Charter, the Foundation upholds these
|
||||
principles across all initiatives.
|
||||
</p>
|
||||
<div className="mt-12 grid gap-8 sm:grid-cols-3">
|
||||
<div>
|
||||
<h3 className="font-semibold">Regenerative Socio-Economics</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Promoting systems where local communities pool resources for
|
||||
shared prosperity, transitioning from extractive, centralized
|
||||
economies to regenerative, commons-based economies.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">Decentralized Systems</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Leveraging Web3 technologies — blockchain, DAOs, community
|
||||
currencies — to foster transparency, inclusivity, and
|
||||
fairness in governance.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">Transnational Collaboration</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Building global alliances to share resources, knowledge, and
|
||||
strategies across borders, strengthening local resilience
|
||||
through supportive global networks.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Governance */}
|
||||
<section id="governance" className="border-t border-border bg-foreground/[0.02]">
|
||||
<div className="mx-auto max-w-5xl px-6 py-20">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">Governance</h2>
|
||||
<p className="mt-4 max-w-3xl text-muted leading-relaxed">
|
||||
The Foundation operates through transparent, community-led
|
||||
governance with clear accountability frameworks.
|
||||
</p>
|
||||
<div className="mt-10 grid gap-6 sm:grid-cols-2">
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<h3 className="font-semibold">Transparency & Accountability</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
All financial transactions, governance decisions, and project
|
||||
outcomes are documented and accessible. Periodic assessments
|
||||
ensure alignment with cosmolocal principles.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<h3 className="font-semibold">Collaborative Decision-Making</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Decentralized governance mechanisms with voting for key
|
||||
decisions. Members operate with autonomy within the
|
||||
Foundation's principles.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<h3 className="font-semibold">Conflict Resolution</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
Disagreements are resolved through open dialogue and mediation,
|
||||
with escalation paths through the core team in accordance with
|
||||
shared principles.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background p-6">
|
||||
<h3 className="font-semibold">Knowledge Sharing</h3>
|
||||
<p className="mt-2 text-sm text-muted leading-relaxed">
|
||||
All team members document and share experiences, innovations,
|
||||
and lessons learned, contributing to the commons and maintaining
|
||||
thorough records.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Contact */}
|
||||
<section id="contact" className="border-t border-border">
|
||||
<div className="mx-auto max-w-5xl px-6 py-20">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
Get in Touch
|
||||
</h2>
|
||||
<p className="mt-4 max-w-xl text-muted leading-relaxed">
|
||||
Interested in collaborating, supporting, or learning more about
|
||||
cosmolocal approaches? We welcome researchers, practitioners,
|
||||
communities, and impact-driven investors.
|
||||
</p>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="mailto:hello@cosmolocal.world"
|
||||
className="mt-6 inline-block rounded-full bg-accent px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-light"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
hello@cosmolocal.world
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-border">
|
||||
<div className="mx-auto flex max-w-5xl items-center justify-between px-6 py-6 text-sm text-muted">
|
||||
<span>© {new Date().getFullYear()} Cosmolocal Foundation</span>
|
||||
<div className="flex items-center gap-6">
|
||||
<a
|
||||
href="https://p2pfoundation.net"
|
||||
className="transition-colors hover:text-foreground"
|
||||
>
|
||||
P2P Foundation
|
||||
</a>
|
||||
<a
|
||||
href="https://docs.cosmolocal.world"
|
||||
className="transition-colors hover:text-foreground"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue