grant-workbench-demo / index.html
cjc0013's picture
Clarify Grant Workbench demo profile
60926b8 verified
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Grant Workbench Demo</title>
<style>
:root {
--ink: #1b2426;
--muted: #5a666a;
--line: #d8dfdd;
--paper: #fbfbf7;
--panel: #ffffff;
--green: #256f5d;
--green-soft: #e8f4ef;
--yellow: #a96f16;
--yellow-soft: #fff4db;
--red: #9a3535;
--red-soft: #fdeceb;
--black: #17191a;
--blue: #2f5f89;
--blue-soft: #e9f1f8;
--steel: #425667;
--steel-soft: #edf2f5;
--violet: #66527a;
--violet-soft: #f2eef7;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--paper);
color: var(--ink);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.45;
}
a { color: #174f47; font-weight: 800; }
header {
background: #edf3f0;
border-bottom: 1px solid var(--line);
}
.wrap {
width: min(1180px, calc(100vw - 32px));
margin: 0 auto;
}
.hero {
min-height: 520px;
display: grid;
grid-template-columns: minmax(0, 1.08fr) minmax(360px, 0.92fr);
gap: 30px;
align-items: center;
padding: 44px 0 38px;
}
.eyebrow {
color: #174f47;
font-size: 0.82rem;
font-weight: 850;
letter-spacing: 0;
text-transform: uppercase;
}
h1 {
margin: 10px 0 16px;
font-size: clamp(2.4rem, 5vw, 4.9rem);
line-height: 0.96;
letter-spacing: 0;
max-width: 860px;
}
h2 { margin: 0 0 14px; font-size: 1.48rem; letter-spacing: 0; }
h3 { margin: 0 0 8px; font-size: 1rem; letter-spacing: 0; }
p { margin: 0 0 14px; color: var(--muted); }
.lead { font-size: 1.08rem; max-width: 760px; color: #344044; }
.decision-strip {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
margin: 24px 0 4px;
}
.decision-item {
min-height: 96px;
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(255, 255, 255, 0.76);
padding: 12px;
}
.decision-item span {
display: block;
color: var(--muted);
font-size: 0.76rem;
font-weight: 850;
text-transform: uppercase;
}
.decision-item strong {
display: block;
margin-top: 6px;
font-size: 1.08rem;
line-height: 1.12;
}
.notice {
display: grid;
gap: 8px;
background: #fff8e8;
border: 1px solid #ead5ad;
border-left: 6px solid var(--yellow);
padding: 16px 18px;
border-radius: 8px;
margin-top: 22px;
color: #3b3428;
}
.demo-explainer {
background: #ffffff;
border: 1px solid var(--line);
border-left: 6px solid var(--green);
border-radius: 8px;
padding: 20px;
}
.demo-explainer .demo-copy {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(280px, 0.68fr);
gap: 18px;
align-items: start;
}
.demo-explainer strong { color: var(--ink); }
.demo-explainer ul {
margin: 0;
padding-left: 20px;
color: var(--muted);
}
.demo-explainer li { margin: 0 0 8px; }
.panel {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 8px;
padding: 18px;
}
.context-panel {
align-self: stretch;
display: grid;
align-content: center;
gap: 16px;
}
.org-form {
display: grid;
gap: 12px;
}
label {
display: grid;
gap: 5px;
color: #293437;
font-size: 0.84rem;
font-weight: 800;
}
input, select, textarea, button, .button {
min-height: 42px;
border: 1px solid var(--line);
border-radius: 8px;
padding: 9px 12px;
font: inherit;
background: #fff;
color: var(--ink);
}
textarea { min-height: 82px; resize: vertical; }
button, .button {
cursor: pointer;
background: #277466;
color: #fff;
border-color: #277466;
font-weight: 850;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.secondary, .button.secondary {
background: #fff;
color: #174f47;
border-color: #b8cbc6;
}
.dark {
background: var(--black);
border-color: var(--black);
color: #fff;
}
nav {
position: sticky;
top: 0;
z-index: 5;
background: rgba(251, 251, 247, 0.96);
border-bottom: 1px solid var(--line);
backdrop-filter: blur(8px);
}
nav .wrap {
display: flex;
gap: 8px;
align-items: center;
padding: 10px 0;
overflow-x: auto;
}
nav a {
text-decoration: none;
color: var(--ink);
border: 1px solid var(--line);
background: #fff;
border-radius: 999px;
padding: 8px 12px;
white-space: nowrap;
font-size: 0.88rem;
}
nav a:hover, nav a:focus-visible { border-color: #8eb3aa; outline: none; }
main { padding: 28px 0 54px; }
.section {
padding: 26px 0;
border-bottom: 1px solid var(--line);
}
.section-head {
display: grid;
grid-template-columns: minmax(0, 0.72fr) minmax(280px, 0.28fr);
gap: 20px;
align-items: end;
margin-bottom: 14px;
}
.section-head p { margin-bottom: 0; }
.section-kicker {
color: #174f47;
font-size: 0.76rem;
font-weight: 850;
text-transform: uppercase;
}
.grid-4 {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.grid-2 {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.card {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 8px;
padding: 16px;
}
.step {
border-left: 5px solid #b8cbc6;
min-height: 128px;
}
.step.active { border-left-color: #277466; }
.step.done { border-left-color: var(--green); background: #fbfffd; }
.status-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.pill {
border: 1px solid var(--line);
border-radius: 999px;
background: #f8faf9;
color: #344044;
padding: 5px 9px;
font-size: 0.78rem;
font-weight: 800;
white-space: nowrap;
}
.legend {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
margin-top: 14px;
}
.legend .pill { border-radius: 8px; padding: 10px; white-space: normal; }
.green { background: var(--green-soft); border-color: #a7d1c4; color: #174f47; }
.yellow { background: var(--yellow-soft); border-color: #e6ca8c; color: #6b4611; }
.red { background: var(--red-soft); border-color: #e5b9b7; color: #793131; }
.lock { background: #eceeef; border-color: #b7bcbe; color: var(--black); }
.blue { background: var(--blue-soft); border-color: #bfd3e4; color: #214966; }
.steel { background: var(--steel-soft); border-color: #c8d2d9; color: #2f3f4c; }
.violet { background: var(--violet-soft); border-color: #d8cce5; color: #51415f; }
.source-grid, .question-grid, .packet-grid, .lock-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin-top: 14px;
}
.source-card {
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
padding: 13px;
}
.source-card strong { display: block; margin-bottom: 4px; }
.field-value { color: var(--ink); font-weight: 750; }
.field-source { color: var(--muted); font-size: 0.84rem; }
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
margin-top: 14px;
}
.quality-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-top: 14px;
}
.quality-item {
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
padding: 12px;
min-height: 92px;
}
.quality-item span {
display: block;
color: var(--muted);
font-size: 0.76rem;
font-weight: 850;
text-transform: uppercase;
}
.quality-item strong {
display: block;
margin-top: 6px;
line-height: 1.2;
}
.metrics {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.metric {
border: 1px solid var(--line);
border-radius: 8px;
background: #f7faf8;
padding: 12px;
min-height: 86px;
}
.metric span {
display: block;
color: var(--muted);
font-size: 0.78rem;
font-weight: 800;
}
.metric strong {
display: block;
font-size: 1.55rem;
margin-top: 4px;
}
.grant-board {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-top: 14px;
}
.grant-card {
display: grid;
gap: 10px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
padding: 14px;
min-height: 264px;
}
.grant-meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.grant-meta span {
border: 1px solid var(--line);
border-radius: 8px;
background: #f8faf9;
padding: 8px;
color: var(--muted);
font-size: 0.78rem;
font-weight: 800;
}
.grant-meta strong {
display: block;
color: var(--ink);
font-size: 0.92rem;
margin-top: 2px;
}
.grant-next {
border-top: 1px solid var(--line);
padding-top: 10px;
margin-top: auto;
}
.status-line {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-top: 6px;
}
table {
width: 100%;
border-collapse: collapse;
background: #fff;
border: 1px solid var(--line);
border-radius: 8px;
overflow: hidden;
}
th, td {
text-align: left;
vertical-align: top;
border-bottom: 1px solid var(--line);
padding: 11px 12px;
font-size: 0.91rem;
}
th {
background: #eef5f1;
color: #1f3935;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0;
}
tbody tr:last-child td { border-bottom: 0; }
.caveat {
background: #f4f0fb;
border: 1px solid #d9cfee;
border-left: 6px solid var(--blue);
border-radius: 8px;
padding: 16px;
color: #313a46;
}
.data-note {
border-left: 5px solid var(--steel);
background: #fff;
border-top: 1px solid var(--line);
border-right: 1px solid var(--line);
border-bottom: 1px solid var(--line);
border-radius: 8px;
padding: 14px 16px;
}
.hidden { display: none; }
footer {
padding: 26px 0 40px;
color: var(--muted);
font-size: 0.88rem;
}
@media (max-width: 920px) {
.hero { grid-template-columns: 1fr; min-height: auto; }
.grid-4, .grid-3, .grid-2, .source-grid, .question-grid, .packet-grid, .lock-grid, .decision-strip, .section-head, .quality-grid, .grant-board, .demo-explainer .demo-copy { grid-template-columns: 1fr; }
.legend { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 540px) {
.wrap { width: min(calc(100vw - 22px), 1180px); }
h1 { font-size: 2.35rem; }
.metrics, .legend { grid-template-columns: 1fr; }
.card, .source-card, .grant-card, .quality-item, .decision-item { min-width: 0; overflow-wrap: anywhere; }
.card .pill, .source-card .pill, .grant-card .pill, .legend .pill { white-space: normal; }
.toolbar button, .toolbar .button { width: 100%; }
}
</style>
</head>
<body>
<header>
<div class="wrap hero">
<div>
<div class="eyebrow">Grant workbench demo</div>
<h1>Grant Workbench Demo</h1>
<p class="lead">A compact path from organization facts to grant-fit review, missing human answers, draft packet fields, and locked final approvals.</p>
<p class="lead"><strong>Output:</strong> a grant shortlist, fit notes, missing-question checklist, draft-safe answer bank, and source links.</p>
<div class="decision-strip" aria-label="Demo state summary">
<div class="decision-item"><span>Demo profile</span><strong>Cedar Ridge Heritage Museum is made up for this demo</strong></div>
<div class="decision-item"><span>Grant universe</span><strong>1,912 rows, official-source links preserved</strong></div>
<div class="decision-item"><span>Human input</span><strong>4 missing/private facts stay explicit</strong></div>
<div class="decision-item"><span>Final authority</span><strong>Certification and submission remain locked</strong></div>
</div>
<div class="notice" role="note">
<strong>Demo profile. Not submitted. Not legal/financial advice.</strong>
<span>Verify all funder details before acting. Built as a showcase for how I help small museums/nonprofits. This static Space does not run real public lookup, IRS lookup, SAM lookup, scraping, API calls, or user-entered organization processing.</span>
</div>
<div class="legend" aria-label="Field status legend">
<span class="pill green">Green = found from reliable source</span>
<span class="pill yellow">Yellow = inferred draft, confirm it</span>
<span class="pill red">Red = missing/private fact</span>
<span class="pill lock">Black lock = human-only certification/signature/submission</span>
</div>
</div>
<aside class="panel context-panel" aria-label="Start organization form">
<h2>Start with an organization</h2>
<p id="nextActionText">Next recommended action: build the made-up Cedar Ridge demo profile, then review grant fit before preparing a packet.</p>
<div class="org-form">
<label>Made-up demo organization
<input id="orgName" value="Cedar Ridge Heritage Museum">
</label>
<label>Website
<input id="orgWebsite" value="https://cedar-ridge-heritage.example">
</label>
<label>State
<select id="orgState">
<option value="OR" selected>OR</option>
<option value="CA">CA</option>
<option value="MA">MA</option>
<option value="NY">NY</option>
<option value="VT">VT</option>
</select>
</label>
<button id="buildProfileBtn" type="button">Build demo profile</button>
</div>
</aside>
</div>
</header>
<nav aria-label="Page sections">
<div class="wrap">
<a href="#what-this-demo-is">Demo vs Full Stack</a>
<a href="#flow">Flow</a>
<a href="#profile">Profile</a>
<a href="#missing">Missing Questions</a>
<a href="#fit-review">Fit Review</a>
<a href="#packet">Packet Preview</a>
<a href="#all-grants">Search All Grants</a>
</div>
</nav>
<main class="wrap">
<section id="what-this-demo-is" class="section">
<article class="demo-explainer">
<h2>This Is A Demo, Not The Full Workbench</h2>
<div class="demo-copy">
<div>
<p><strong>This posted page is only a demo of the grant workbench.</strong> The grant listings and source links attached here are real, but this public page mostly shows the discovery and workflow preview layer.</p>
<p>The full grant workbench is meant to take an organization's basic information, project details, budget notes, documents, past application language, and grant instructions, then turn that material into a guided application plan.</p>
<p>In the full stack, the workbench can find likely-fit grants, break down what each application asks for, show what is missing, draft reusable answer blocks, prepare safe form fields where possible, and keep a checklist of what still needs human review.</p>
</div>
<div>
<ul>
<li><strong>Why it saves time:</strong> nonprofits do not have to start from a blank page, hunt through PDFs, or retype the same organization details over and over.</li>
<li><strong>What this demo cannot do:</strong> it does not submit grants, certify anything, guarantee eligibility, or replace official grant instructions.</li>
<li><strong>Important limitation:</strong> the public version is incomplete without the rest of the private stack behind it, so treat it as a preview, not the finished application system.</li>
<li><strong>Need this for your nonprofit?</strong> I can build a grant-fit packet using your real organization details and public grant sources.</li>
</ul>
</div>
</div>
</article>
</section>
<section id="flow" class="section">
<div class="section-head">
<div>
<div class="section-kicker">Progressive disclosure</div>
<h2>Guided Review Flow</h2>
</div>
<p>Each step separates sourced facts, draft assumptions, missing facts, and approvals so the dense grant workflow stays legible.</p>
</div>
<div class="grid-3">
<article id="stepProfile" class="card step active">
<h3>1. Build org profile</h3>
<p>Simulate public-source facts and mark confidence by field.</p>
<span class="pill blue">Waiting for demo profile</span>
</article>
<article id="stepQuestions" class="card step">
<h3>2. Ask only missing facts</h3>
<p>Collect private or certifiable facts the machine should not invent.</p>
<span id="questionReadiness" class="pill red">4 missing questions</span>
</article>
<article id="stepPacket" class="card step">
<h3>3. Draft packet, lock signoff</h3>
<p>Prepare draft answers, attachments, and human-only approvals.</p>
<span id="packetReadiness" class="pill lock">Human certification locked</span>
</article>
</div>
</section>
<section id="profile" class="section">
<div class="section-head">
<div>
<div class="section-kicker">Evidence layer</div>
<h2>Simulated Public Profile</h2>
</div>
<p>Cedar Ridge Heritage Museum is a made-up organization used only to demonstrate how the Grant Workbench handles an organization profile. A future private/local version can use a real nonprofit's details and real public sources.</p>
</div>
<div id="sourceCards" class="source-grid hidden"></div>
</section>
<section id="missing" class="section">
<div class="section-head">
<div>
<div class="section-kicker">Human-only facts</div>
<h2>Missing Questions</h2>
</div>
<p id="missingIntro">Once the demo profile is built, the system asks only for missing/private facts it cannot certify.</p>
</div>
<div id="questionCards" class="question-grid hidden"></div>
<div class="toolbar">
<button id="answerQuestionsBtn" type="button" class="secondary">Answer missing questions</button>
<button id="preparePacketBtn" type="button">Prepare packet</button>
</div>
</section>
<section id="fit-review" class="section">
<div class="section-head">
<div>
<div class="section-kicker">Rank before drafting</div>
<h2>Grant Fit Review</h2>
</div>
<p>The sample matches keep money, deadline, readiness, intended use, and next action together so review can happen without flattening the underlying grant data.</p>
</div>
<div class="quality-grid" aria-label="Grant fit signals">
<div class="quality-item"><span>Best immediate fit</span><strong>Community history microgrant</strong></div>
<div class="quality-item"><span>Main constraint</span><strong>Budget and match facts need human confirmation</strong></div>
<div class="quality-item"><span>Data boundary</span><strong>Fit examples use sample profile data; full database remains separate</strong></div>
</div>
<div id="grantBoard" class="grant-board" aria-live="polite"></div>
</section>
<section id="packet" class="section">
<div class="section-head">
<div>
<div class="section-kicker">Draft surface</div>
<h2>Application Packet Preview</h2>
</div>
<p>Draft-safe fields can be gathered, mapped, and filled. Certification, signatures, and submission stay with a human.</p>
</div>
<div id="packetPanel" class="hidden">
<div id="packetRows" class="packet-grid"></div>
<h3>Human signoff checklist</h3>
<div id="lockRows" class="lock-grid"></div>
<div class="toolbar">
<button id="exportPacketBtn" type="button" class="secondary">Export demo packet JSON</button>
<button id="printPacketBtn" type="button" class="dark">Print / Save PDF</button>
</div>
</div>
</section>
<section id="all-grants" class="section">
<div class="section-head">
<div>
<div class="section-kicker">Underlying database</div>
<h2>Search All Grants</h2>
</div>
<p>Use the attached database when you need breadth. Use the fit review above when you need a decision-ready shortlist.</p>
</div>
<div class="grid-2">
<article class="panel">
<h3>Attached Grant DB</h3>
<div class="metrics">
<div class="metric"><span>Total rows</span><strong>1,912</strong></div>
<div class="metric"><span>Federal rows</span><strong>1,234</strong></div>
<div class="metric"><span>State sources</span><strong>627</strong></div>
<div class="metric"><span>Due dates</span><strong>769</strong></div>
</div>
<div class="toolbar">
<a id="grantDatabaseHtmlLink" class="button" href="all_grants_database.html">Open all_grants_database.html</a>
<a id="grantDatabaseCsvLink" class="button secondary" href="all_grants_database.csv" download>Download all_grants_database.csv</a>
</div>
</article>
<article class="data-note">
<h3>Database caveat</h3>
<p>Federal rows are Grants.gov opportunities. State rows include official grant/funding source pages and portals, not a perfect parse of every individual opportunity for every state.</p>
<div class="status-line">
<span class="pill blue">Official links retained</span>
<span class="pill yellow">State rows vary by source</span>
<span class="pill red">Verify before acting</span>
</div>
</article>
</div>
<article class="card" style="margin-top:16px">
<h3>Included real grant data</h3>
<p>The Space includes the real source snapshot behind the demo: the full grant universe, Grants.gov federal rows, state grant source rows, state directory rows, source gaps, summary, and public source receipts with local cache paths removed.</p>
<div class="toolbar">
<a class="button secondary" href="data/grant_universe.csv" download>Full CSV</a>
<a class="button secondary" href="data/grant_universe.jsonl" download>Full JSONL</a>
<a class="button secondary" href="data/federal_grants_gov_opportunities.csv" download>Federal rows</a>
<a class="button secondary" href="data/state_grant_sources.csv" download>State sources</a>
<a class="button secondary" href="data/source_receipts_public.csv" download>Public receipts</a>
</div>
</article>
</section>
<section id="caveats" class="section">
<h2>Public Demo Boundaries</h2>
<div class="grid-2">
<article class="card">
<h3>What the demo does</h3>
<p>Shows the target interface: org profile, missing questions, grant ranking, draft field map, attachments, export, and human signoff.</p>
</article>
<article class="card">
<h3>What the demo does not do</h3>
<p>No real organization lookup, no IRS/SAM verification, no real form submission, and no auto-certification.</p>
</article>
</div>
</section>
</main>
<footer class="wrap">
Generated UTC: 2026-05-14T16:15:14+00:00. Grant DB source snapshot UTC: 2026-05-14T14:03:13+00:00.
</footer>
<script>
const profileFields = [{"label": "Organization name", "value": "Cedar Ridge Heritage Museum", "status": "green", "meaning": "Found from reliable demo source", "source": "Demo profile website profile"}, {"label": "Mission", "value": "Preserve and share Cedar Ridge mill-town history through collections, oral histories, and school programs.", "status": "green", "meaning": "Found from reliable demo source", "source": "Demo profile about page"}, {"label": "Project fit", "value": "Mill Town Memory Lab: oral histories, collections care, free tours, and light gallery stabilization.", "status": "yellow", "meaning": "Inferred draft, confirm before use", "source": "Synthesized from demo project notes"}, {"label": "EIN", "value": "Not shown in public demo", "status": "red", "meaning": "Missing/private fact required from human", "source": "Human must provide or verify privately"}, {"label": "UEI/SAM status", "value": "Not shown in public demo", "status": "red", "meaning": "Missing/private fact required from human", "source": "Human must provide or verify privately"}, {"label": "Certification and signature", "value": "Human-only", "status": "lock", "meaning": "Black lock: certification/signature/submission", "source": "Authorized representative only"}];
const missingQuestions = [{"id": "project_scope", "question": "Which one project should this packet prepare first?", "hint": "Use one bounded project, not the whole wish list."}, {"id": "budget_confirmed", "question": "What budget total and match amount can leadership certify?", "hint": "Budget documents stay human-confirmed in the public demo."}, {"id": "sam_status", "question": "Is the organization registered in SAM.gov or only planning state/private grants?", "hint": "No SAM lookup runs in this static Space."}, {"id": "board_approval", "question": "Who is authorized to approve and submit the final application?", "hint": "Submission authority is always locked for human signoff."}];
const packetRows = [{"field": "Applicant name", "draft": "Cedar Ridge Heritage Museum", "status": "green", "note": "Safe to draft from the demo public profile."}, {"field": "Project title", "draft": "Mill Town Memory Lab", "status": "yellow", "note": "Drafted from demo notes; human should confirm final title."}, {"field": "Project summary", "draft": "A draft-safe summary for oral histories, collections care, free school access, and light stabilization.", "status": "yellow", "note": "Useful starting language, not a certified final narrative."}, {"field": "Budget total", "draft": "Needs human-confirmed budget document", "status": "red", "note": "The machine can map numbers, but a person must certify them."}, {"field": "Signature and certifications", "draft": "Locked for authorized representative", "status": "lock", "note": "Never auto-filled or submitted by the public demo."}];
const certificationLocks = [{"item": "Legal applicant certification", "owner": "Authorized representative", "status": "Black lock"}, {"item": "Budget approval", "owner": "Treasurer or board reviewer", "status": "Black lock"}, {"item": "SAM/UEI attestation if federal", "owner": "Authorized representative", "status": "Black lock"}, {"item": "Final submission", "owner": "Human submitter", "status": "Black lock"}];
const demoGrants = [{"name": "Cedar Ridge Community History Microgrant", "funder": "Cedar Ridge Community Foundation", "due": "2026-06-12", "status": "Drafting", "amount": "$7,500", "use": "Weekend oral-history recording booth and volunteer transcription stipends.", "next": "Tighten the one-page project summary and attach a board support note."}, {"name": "North Valley Collections Care Fund", "funder": "North Valley Heritage Trust", "due": "2026-07-01", "status": "Ready to verify", "amount": "$18,000", "use": "Archival shelving, rehousing supplies, and a part-time collections technician.", "next": "Confirm current eligibility language and quote expiration dates."}, {"name": "Riverbend Arts Access Program", "funder": "Riverbend Cultural Council", "due": "2026-08-15", "status": "Watchlist", "amount": "$12,000", "use": "Free family history days, bilingual exhibit labels, and school tour materials.", "next": "Decide whether the access budget belongs in this cycle or next cycle."}, {"name": "Old Mill Heritage Capital Match", "funder": "Old Mill Preservation League", "due": "2026-10-30", "status": "Needs match", "amount": "$35,000", "use": "Exterior drainage fixes and visitor entry stabilization for a sample mill gallery.", "next": "Build a match plan and choose which capital items are phase-one only."}];
const answered = new Set();
const sourceCards = document.querySelector("#sourceCards");
const questionCards = document.querySelector("#questionCards");
const packetPanel = document.querySelector("#packetPanel");
const packetMount = document.querySelector("#packetRows");
const lockMount = document.querySelector("#lockRows");
const grantBoard = document.querySelector("#grantBoard");
const questionReadiness = document.querySelector("#questionReadiness");
const packetReadiness = document.querySelector("#packetReadiness");
const nextActionText = document.querySelector("#nextActionText");
function statusClass(status) {
if (status === "green") return "green";
if (status === "yellow") return "yellow";
if (status === "red") return "red";
if (status === "lock") return "lock";
return "blue";
}
function statusText(status) {
if (status === "green") return "Green: found from reliable source";
if (status === "yellow") return "Yellow: inferred draft, needs confirmation";
if (status === "red") return "Red: missing/private fact required";
if (status === "lock") return "Black lock: human-only certification/signature/submission";
return status;
}
function makeCard(className, title, body, pill, footer) {
const card = document.createElement("article");
card.className = className;
const h = document.createElement("h3");
h.textContent = title;
const p = document.createElement("p");
p.textContent = body;
const tag = document.createElement("span");
tag.className = "pill " + pill.className;
tag.textContent = pill.text;
card.appendChild(h);
card.appendChild(p);
card.appendChild(tag);
if (footer) {
const small = document.createElement("p");
small.className = "field-source";
small.textContent = footer;
card.appendChild(small);
}
return card;
}
function fitStatusClass(status) {
if (status === "Drafting") return "green";
if (status === "Ready to verify") return "blue";
if (status === "Watchlist") return "steel";
if (status === "Needs match") return "yellow";
return "violet";
}
function renderGrantBoard() {
grantBoard.innerHTML = "";
demoGrants.forEach((grant) => {
const card = document.createElement("article");
card.className = "grant-card";
const h = document.createElement("h3");
h.textContent = grant.name;
const funder = document.createElement("p");
funder.textContent = grant.funder;
const tag = document.createElement("span");
tag.className = "pill " + fitStatusClass(grant.status);
tag.textContent = grant.status;
const meta = document.createElement("div");
meta.className = "grant-meta";
meta.innerHTML = "<span>Amount<strong>" + grant.amount + "</strong></span>" +
"<span>Due<strong>" + grant.due + "</strong></span>";
const use = document.createElement("p");
use.textContent = grant.use;
const next = document.createElement("p");
next.className = "grant-next";
next.textContent = "Next action: " + grant.next;
card.appendChild(h);
card.appendChild(funder);
card.appendChild(tag);
card.appendChild(meta);
card.appendChild(use);
card.appendChild(next);
grantBoard.appendChild(card);
});
}
function buildProfile() {
sourceCards.innerHTML = "";
profileFields.forEach((field) => {
sourceCards.appendChild(makeCard(
"source-card",
field.label,
field.value,
{className: statusClass(field.status), text: statusText(field.status)},
field.source
));
});
sourceCards.classList.remove("hidden");
document.querySelector("#stepProfile").classList.add("done");
document.querySelector("#stepQuestions").classList.add("active");
nextActionText.textContent = "Next recommended action: answer the missing facts, then check whether the grant shortlist is ready for packet drafting.";
renderQuestions();
document.querySelector("#profile").scrollIntoView({behavior: "smooth", block: "start"});
}
function renderQuestions() {
questionCards.innerHTML = "";
missingQuestions.forEach((item) => {
const card = document.createElement("article");
card.className = "card";
const h = document.createElement("h3");
h.textContent = item.question;
const hint = document.createElement("p");
hint.textContent = item.hint;
const textarea = document.createElement("textarea");
textarea.id = "answer_" + item.id;
textarea.placeholder = "Demo answer";
textarea.addEventListener("input", () => {
if (textarea.value.trim()) answered.add(item.id);
else answered.delete(item.id);
updateReadiness();
});
card.appendChild(h);
card.appendChild(hint);
card.appendChild(textarea);
questionCards.appendChild(card);
});
questionCards.classList.remove("hidden");
updateReadiness();
}
function updateReadiness() {
const missing = missingQuestions.length - answered.size;
questionReadiness.textContent = missing === 0 ? "Ready for draft packet" : `${missing} missing/private questions`;
questionReadiness.className = "pill " + (missing === 0 ? "green" : answered.size ? "yellow" : "red");
}
function answerQuestions() {
missingQuestions.forEach((item, index) => {
const box = document.querySelector("#answer_" + item.id);
if (box && !box.value.trim()) {
box.value = index === 0 ? "Prepare the Mill Town Memory Lab first." : "Demo response for human review.";
answered.add(item.id);
}
});
document.querySelector("#stepQuestions").classList.add("done");
updateReadiness();
nextActionText.textContent = "Next recommended action: prepare the packet and keep final certification locked for a human.";
}
function preparePacket() {
if (sourceCards.classList.contains("hidden")) buildProfile();
packetMount.innerHTML = "";
packetRows.forEach((row) => {
packetMount.appendChild(makeCard(
"source-card",
row.field,
row.draft,
{className: statusClass(row.status), text: statusText(row.status)},
row.note
));
});
lockMount.innerHTML = "";
certificationLocks.forEach((row) => {
lockMount.appendChild(makeCard(
"source-card",
row.item,
row.owner,
{className: "lock", text: row.status},
"Human approval required"
));
});
packetPanel.classList.remove("hidden");
document.querySelector("#stepPacket").classList.add("active", "done");
packetReadiness.textContent = "Draft packet prepared, certification locked";
packetReadiness.className = "pill lock";
nextActionText.textContent = "Next recommended action: review exported draft fields against funder instructions before any real-world use.";
document.querySelector("#packet").scrollIntoView({behavior: "smooth", block: "start"});
}
function exportPacket() {
const answers = {};
missingQuestions.forEach((item) => {
const box = document.querySelector("#answer_" + item.id);
answers[item.id] = box ? box.value.trim() : "";
});
const packet = {
exported_utc: new Date().toISOString(),
demo_profile: true,
org_first_flow: true,
no_real_lookup_in_space: true,
organization: document.querySelector("#orgName").value,
website: document.querySelector("#orgWebsite").value,
state: document.querySelector("#orgState").value,
simulated_profile: profileFields,
missing_question_answers: answers,
ranked_demo_grants: demoGrants,
draft_packet_fields: packetRows,
human_only_certification: certificationLocks
};
const blob = new Blob([JSON.stringify(packet, null, 2)], {type: "application/json"});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "cedar_ridge_org_first_demo_packet.json";
link.click();
URL.revokeObjectURL(url);
}
document.querySelector("#buildProfileBtn").addEventListener("click", buildProfile);
document.querySelector("#answerQuestionsBtn").addEventListener("click", answerQuestions);
document.querySelector("#preparePacketBtn").addEventListener("click", preparePacket);
document.querySelector("#exportPacketBtn").addEventListener("click", exportPacket);
document.querySelector("#printPacketBtn").addEventListener("click", () => window.print());
renderGrantBoard();
</script>
</body>
</html>