Initial commit

This commit is contained in:
Nathan Schneider
2025-03-23 21:44:39 -06:00
commit 521c782c42
56 changed files with 8295 additions and 0 deletions

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<div id="content">
{{- block "main" . }}{{- end }}
</div>
{{- partial "footer.html" . -}}
</body>
</html>

View File

@ -0,0 +1,290 @@
{{ define "main" }}
<div class="builder-container">
<h1>{{ .Title }}</h1>
{{ .Content }}
<div id="protocol-builder" class="builder">
<div class="builder-main">
<div class="protocol-metadata">
<div class="metadata-field">
<label for="community-name">Community Name:</label>
<input type="text" id="community-name" name="community-name" placeholder="Enter your community name...">
</div>
<div class="metadata-field">
<label for="protocol-summary">Protocol Summary:</label>
<textarea id="protocol-summary" name="protocol-summary" placeholder="Briefly describe this dispute resolution protocol and its purpose..."></textarea>
</div>
</div>
<div class="protocol-template-selector">
<label for="protocol-template">Select a Protocol Template:</label>
<select id="protocol-template" class="protocol-template-select">
<option value="">-- Create Your Own Protocol --</option>
<!-- Template options will be populated by JavaScript -->
</select>
</div>
<div class="builder-content">
{{ range $.Site.Data.stages.stages }}
<div class="stage-section" id="stage-{{ .id }}">
<div class="stage-header" data-stage="{{ .id }}">
<div class="stage-header-content">
<h2>{{ .title }}</h2>
<p class="stage-brief">{{ .description }}</p>
</div>
<button type="button" class="toggle-btn" aria-label="Toggle section" onclick="toggleStage(this)">+</button>
</div>
<div class="stage-body">
<div class="components">
{{ $stageId := .id }}
{{ $componentFile := printf "%s" $stageId }}
{{ range index $.Site.Data.components $componentFile }}
{{ $componentId := .id }}
<div class="component-card" id="component-{{ $componentId }}">
<div class="component-header">
<div class="component-title-wrapper">
<div class="component-short-label">{{ $componentId | humanize }}</div>
<h3>{{ .title }}</h3>
</div>
</div>
<div class="component-body">
{{ range .fields }}
<div class="field" id="field-{{ .id }}">
<div class="module-selector">
<label for="module-{{ .id }}">Select a module:</label>
<select id="module-{{ .id }}" class="module-select" data-field-id="{{ .id }}" data-component-id="{{ $componentId }}">
<option value="">-- Choose a module --</option>
</select>
</div>
<textarea id="{{ .id }}" name="{{ .id }}" placeholder="{{ .placeholder }}" {{ if .required }}required{{ end }}></textarea>
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</div>
{{ end }}
</div>
<!-- Export buttons moved to sidebar -->
</div>
<div class="builder-sidebar">
<div class="sidebar-section">
<h3>Tools</h3>
<div id="sidebar-nav" class="sidebar-nav">
<h4>Jump to Section</h4>
<div class="sidebar-nav-content">
<div class="nav-tree">
{{ range $.Site.Data.stages.stages }}
<div class="nav-stage">
<a href="#stage-{{ .id }}" class="nav-stage-link">{{ .title }}</a>
<div class="nav-components">
{{ $stageId := .id }}
{{ $componentFile := printf "%s" $stageId }}
{{ range index $.Site.Data.components $componentFile }}
<a href="#component-{{ .id }}" class="nav-component-link">{{ .title }}</a>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
</div>
<div class="sidebar-toggle" style="margin-top: 1.5rem;">
<label class="toggle-switch" for="preview-toggle">
<input type="checkbox" id="preview-toggle">
<span class="toggle-slider"></span>
<span class="toggle-label">Preview Mode</span>
</label>
</div>
<div class="sidebar-info">
<p>Preview mode shows only completed elements.</p>
</div>
</div>
<div class="sidebar-section">
<h3>Export Options</h3>
<div class="sidebar-export-options">
<a href="#" id="export-md" class="sidebar-btn">Export Markdown</a>
<a href="#" id="export-pdf" class="sidebar-btn">Export PDF</a>
<a href="#" id="export-json" class="sidebar-btn">Export JSON</a>
<button id="import-btn" class="sidebar-btn">Import JSON</button>
<input type="file" id="import-json" accept=".json" style="display: none;">
</div>
</div>
</div>
</div>
</div>
<!-- Scripts are loaded in head.html -->
<script>
function toggleStage(button) {
const section = button.closest('.stage-section');
const body = section.querySelector('.stage-body');
if (body.style.display === 'block') {
body.style.display = 'none';
button.textContent = '+';
} else {
body.style.display = 'block';
button.textContent = '-';
}
// Prevent event from propagating
event.stopPropagation();
}
// Make headers also toggle sections
document.addEventListener('DOMContentLoaded', function() {
const headers = document.querySelectorAll('.stage-header-content');
headers.forEach(header => {
header.addEventListener('click', function() {
const stageHeader = this.closest('.stage-header');
const toggleButton = stageHeader.querySelector('.toggle-btn');
toggleButton.click();
});
});
// Export dropdown removed (moved to sidebar)
// Preview Mode Toggle
const previewToggle = document.getElementById('preview-toggle');
if (previewToggle) {
previewToggle.addEventListener('change', function() {
if (this.checked) {
enablePreviewMode();
} else {
disablePreviewMode();
}
});
}
function enablePreviewMode() {
// Create style element
let style = document.createElement('style');
style.id = 'preview-style';
style.innerHTML = `
.module-selector { display: none !important; }
.field:not(.has-content) { display: none !important; }
.component-card:not(.has-content) { display: none !important; }
.stage-section:not(.has-content) { display: none !important; }
.protocol-template-selector { display: none !important; }
.stage-body { display: block !important; }
textarea {
border: none !important;
background-color: transparent !important;
padding: 0 !important;
min-height: unset !important;
height: auto !important;
resize: none !important;
pointer-events: none !important;
overflow: visible !important;
white-space: pre-wrap !important;
width: 100% !important;
word-wrap: break-word !important;
}
.component-header {
background-color: transparent !important;
border-bottom: none !important;
}
.component-short-label { display: none !important; }
`;
document.head.appendChild(style);
// Mark elements with content
document.querySelectorAll('textarea').forEach(textarea => {
if (textarea.value && textarea.value.trim()) {
const field = textarea.closest('.field');
if (field) field.classList.add('has-content');
const component = textarea.closest('.component-card');
if (component) component.classList.add('has-content');
const stage = textarea.closest('.stage-section');
if (stage) stage.classList.add('has-content');
}
});
// Make fields read-only
document.querySelectorAll('textarea, #community-name, #protocol-summary').forEach(el => {
el.readOnly = true;
});
}
function disablePreviewMode() {
// Remove preview styles
const style = document.getElementById('preview-style');
if (style) {
style.parentNode.removeChild(style);
}
// Remove content markers
document.querySelectorAll('.has-content').forEach(el => {
el.classList.remove('has-content');
});
// Make fields editable again
document.querySelectorAll('textarea, #community-name, #protocol-summary').forEach(el => {
el.readOnly = false;
});
}
// Initialize the sidebar navigation
initSidebarNavigation();
});
// Initialize the sidebar navigation
function initSidebarNavigation() {
// Set up click handlers for navigation links
document.querySelectorAll('.nav-stage-link, .nav-component-link').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
// Get the target element's ID from the href
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// If it's a stage, make sure it's expanded
if (targetId.startsWith('stage-')) {
const stageBody = targetElement.querySelector('.stage-body');
const toggleBtn = targetElement.querySelector('.toggle-btn');
if (stageBody && stageBody.style.display !== 'block') {
stageBody.style.display = 'block';
if (toggleBtn) toggleBtn.textContent = '-';
}
}
// If it's a component, make sure its parent stage is expanded
if (targetId.startsWith('component-')) {
const stageSection = targetElement.closest('.stage-section');
if (stageSection) {
const stageBody = stageSection.querySelector('.stage-body');
const toggleBtn = stageSection.querySelector('.toggle-btn');
if (stageBody && stageBody.style.display !== 'block') {
stageBody.style.display = 'block';
if (toggleBtn) toggleBtn.textContent = '-';
}
}
}
// Scroll to the target with some offset to account for sticky headers
window.scrollTo({
top: targetElement.offsetTop - 20,
behavior: 'smooth'
});
}
});
});
}
</script>
{{ end }}

View File

@ -0,0 +1,36 @@
{{ define "main" }}
<div class="container">
<div class="page-header">
<h1>{{ .Title }}</h1>
<p>{{ .Description }}</p>
</div>
<div class="content">
{{ .Content }}
</div>
<div class="search-filter">
<input type="text" id="module-search" placeholder="Search modules...">
<select id="stage-filter">
<option value="">All Stages</option>
</select>
<select id="component-filter">
<option value="">All Components</option>
</select>
<select id="template-filter">
<option value="">All Templates</option>
</select>
</div>
<div id="modules-container" class="modules-container">
<!-- Modules will be loaded here via JavaScript -->
<div class="loading">Loading modules...</div>
</div>
</div>
<!-- Load the module data -->
<script src="{{ "js/data/modules.js" | relURL }}"></script>
<script src="{{ "js/data/templates.js" | relURL }}"></script>
<script src="{{ "js/data/template-mapper.js" | relURL }}"></script>
<script src="{{ "js/modules-page.js" | relURL }}"></script>
{{ end }}

View File

@ -0,0 +1,7 @@
{{ define "main" }}
<div class="content-container">
<article class="page-content">
{{ .Content }}
</article>
</div>
{{ end }}

View File

@ -0,0 +1,26 @@
{{ define "main" }}
<div class="home-content">
<div class="hero">
<h1>{{ .Title }}</h1>
<p class="lead">Create a customized dispute resolution protocol for your community</p>
<a href="/builder/" class="btn-primary">Start Building</a>
<a href="/about/" class="btn-secondary">Learn More</a>
</div>
<div class="content">
{{ .Content }}
</div>
<div class="stages-overview">
<h2>The Dispute Resolution Process</h2>
<div class="stages-grid">
{{ range $.Site.Data.stages.stages }}
<div class="stage-card">
<h3>{{ .title }}</h3>
<p>{{ .description }}</p>
</div>
{{ end }}
</div>
</div>
</div>
{{ end }}

View File

@ -0,0 +1,6 @@
<footer>
<div class="container">
<p>&copy; {{ now.Format "2006" }} Community Dispute Protocol Builder. All rights reserved.</p>
<p>Built with <a href="https://gohugo.io/" target="_blank">Hugo</a></p>
</div>
</footer>

View File

@ -0,0 +1,36 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ .Title }} | {{ .Site.Title }}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ "css/main.css" | relURL }}">
<link rel="shortcut icon" href="{{ "favicon.ico" | relURL }}" type="image/x-icon">
<!-- JS libraries for PDF export -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<!-- Custom JS for the builder -->
{{ if eq .Layout "builder" }}
<script defer src="{{ "js/data/modules.js" | relURL }}"></script>
<script defer src="{{ "js/data/templates.js" | relURL }}"></script>
<script defer src="{{ "js/data/template-mapper.js" | relURL }}"></script>
<script defer src="{{ "js/debug.js" | relURL }}"></script>
<script defer src="{{ "js/builder.js" | relURL }}"></script>
{{ end }}
<!-- Additional custom JS from site parameters -->
{{ range .Site.Params.customJS }}
{{ if not (in . "modules.js" ) }}
{{ if not (in . "templates.js" ) }}
{{ if not (in . "template-mapper.js" ) }}
{{ if not (in . "builder.js" ) }}
<script defer src="{{ . | relURL }}"></script>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
</head>

View File

@ -0,0 +1,15 @@
<header>
<div class="container">
<div class="logo">
<a href="{{ "/" | relURL }}">{{ .Site.Title }}</a>
</div>
<nav>
<ul>
<li><a href="{{ "/" | relURL }}">Home</a></li>
<li><a href="{{ "builder/" | relURL }}">Protocol Builder</a></li>
<li><a href="{{ "modules/" | relURL }}">Modules Library</a></li>
<li><a href="{{ "about/" | relURL }}">About</a></li>
</ul>
</nav>
</div>
</header>