Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,23 @@ Validate structure + referential integrity:
node scripts/validate.mjs
```

## Learning Map explorer

This repository includes a dependency-free browser explorer that can track learning progress privately on one device. It supports multiple child profiles, `learning` and `known` states, evidence-based assessments, subject/age/search filters, and dimming or hiding concepts a child already knows.

Two complementary views share the same local profile:

- **Graph** visualizes all prerequisite relationships and opens the evidence checklist for any concept.
- **Logbook** summarizes known, learning, assessed, and not-yet-started concepts; suggests next topics whose hard prerequisites are complete; shows subject journeys; and keeps a chronological activity trail.

```bash
npm run serve
```

Then open [http://localhost:4173/explorer/](http://localhost:4173/explorer/). A child's name is requested once and substituted automatically into every assessment prompt. Profiles, progress, and the most recent 500 logbook activities are versioned and stored only in the browser's `localStorage`; the explorer makes no network requests after loading the taxonomy JSON. Use **Manage** beside the profile picker to explicitly edit a name, delete a profile, or reset its progress and activity trail.

The explorer is intentionally a static ES-module application with no build step. Its persistence layer lives in `explorer/src/profile-store.js`, independently of the graph renderer, so a server-backed profile adapter can replace it later without changing the taxonomy data or graph interaction.

## License

This dataset is **multi-licensed** — read this before you use or redistribute it.
Expand Down
159 changes: 159 additions & 0 deletions explorer/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Explore the Marble Skill Taxonomy and privately track a child's learning progress in this browser."
/>
<title>Marble Learning Map</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<header class="app-header">
<a class="brand" href="../" aria-label="Marble Skill Taxonomy home">
<span class="brand-mark" aria-hidden="true">M</span>
<span>
<strong>Learning Map</strong>
<small>Marble Skill Taxonomy</small>
</span>
</a>

<nav class="view-switch" aria-label="Explorer view">
<button id="graph-mode" class="active" type="button" aria-pressed="true">
<span aria-hidden="true">✦</span> Graph
</button>
<button id="logbook-mode" type="button" aria-pressed="false">
<span aria-hidden="true">▤</span> Logbook
</button>
</nav>

<div class="profile-controls">
<label class="sr-only" for="profile-select">Child profile</label>
<select id="profile-select" aria-label="Child profile"></select>
<button id="add-profile" class="icon-button" type="button" title="Add a child" aria-label="Add a child">+</button>
<button id="manage-profile" class="button secondary" type="button">Manage</button>
</div>
</header>

<main id="workspace" class="workspace">
<section id="graph-view" class="graph-shell" aria-label="Interactive learning graph">
<div class="toolbar">
<div class="search-container">
<label class="search-field">
<span class="sr-only">Search concepts</span>
<svg aria-hidden="true" viewBox="0 0 24 24"><path d="m21 21-4.4-4.4m2.4-5.1a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z" /></svg>
<input id="search" type="search" placeholder="Search 1,590 concepts…" autocomplete="off" aria-controls="search-results" aria-expanded="false" />
</label>
<div id="search-results" class="search-results" role="listbox" aria-label="Concept search results" hidden></div>
</div>

<label>
<span>Subject</span>
<select id="subject-filter"><option value="">All subjects</option></select>
</label>

<label>
<span>Age</span>
<select id="age-filter"><option value="">All ages</option></select>
</label>

<label>
<span>Known concepts</span>
<select id="mastery-filter">
<option value="dim">Dim</option>
<option value="show">Show normally</option>
<option value="hide">Hide</option>
<option value="only">Show only known</option>
</select>
</label>
</div>

<div class="summary-bar" aria-live="polite">
<div class="progress-copy">
<strong id="profile-greeting">Learning overview</strong>
<span id="progress-summary">Loading the taxonomy…</span>
</div>
<div class="progress-track" role="progressbar" aria-label="Concepts known" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<span id="progress-bar"></span>
</div>
<div class="legend" aria-label="Graph legend">
<span><i class="dot untracked"></i>Not started</span>
<span><i class="dot learning"></i>Learning</span>
<span><i class="dot mastered"></i>Known</span>
<span><i class="dot assessed"></i>Assessed</span>
</div>
</div>

<div id="canvas-wrap" class="canvas-wrap">
<canvas id="graph" aria-label="Concept graph. Select concepts using the search or graph."></canvas>
<div class="graph-actions">
<button id="zoom-in" class="icon-button glass" type="button" aria-label="Zoom in">+</button>
<button id="zoom-out" class="icon-button glass" type="button" aria-label="Zoom out">−</button>
<button id="reset-view" class="icon-button glass reset-view" type="button" aria-label="Fit graph to view" title="Fit graph to view">⌂</button>
</div>
<p class="graph-hint">Drag to move · Scroll to zoom · Select a dot to inspect</p>
<div id="empty-state" class="empty-state" hidden>
<strong>No concepts match these filters</strong>
<button id="clear-filters" class="button secondary" type="button">Clear filters</button>
</div>
</div>
</section>

<aside id="details" class="details-panel" aria-label="Concept details">
<div id="details-empty" class="details-empty">
<div class="empty-orbit" aria-hidden="true"><span></span></div>
<h1>Pick a concept</h1>
<p>Select any point in the graph to see what it means, how to assess it, and what it unlocks.</p>
</div>
<div id="details-content" hidden></div>
</aside>

<section id="logbook-view" class="logbook-view" aria-label="Learning logbook" hidden>
<div id="logbook-content" class="logbook-content"></div>
</section>
</main>

<dialog id="profile-dialog">
<form method="dialog" class="dialog-card">
<div class="dialog-heading">
<div>
<p class="eyebrow">Local profile</p>
<h2 id="dialog-title">Add a child</h2>
</div>
<button class="icon-button" value="cancel" aria-label="Close">×</button>
</div>
<p class="privacy-note">Profiles and learning progress stay in this browser. Nothing is uploaded.</p>
<p id="name-once-note" class="name-once-note">Tell us the name once. Every assessment question will use it automatically.</p>
<div id="managed-profile-summary" class="managed-profile-summary" hidden>
<span id="managed-profile-avatar" class="profile-avatar" aria-hidden="true"></span>
<span>
<strong id="managed-profile-name"></strong>
<small>Name is filled into every learning prompt automatically.</small>
</span>
<button id="edit-profile-name" type="button" class="button secondary">Edit name</button>
</div>
<label id="profile-name-field" class="field">
<span>Child's name</span>
<input id="profile-name" maxlength="60" required autocomplete="off" placeholder="Child's first name" />
</label>
<div id="profile-dialog-actions" class="dialog-actions">
<button value="cancel" class="button secondary">Cancel</button>
<button id="save-profile" value="default" class="button primary">Save profile</button>
</div>
<div id="profile-danger" class="danger-zone" hidden>
<p><strong>Progress controls</strong><br /><span id="danger-copy"></span></p>
<div>
<button id="reset-progress" type="button" class="button danger">Reset progress</button>
<button id="delete-profile" type="button" class="button text-danger">Delete profile</button>
</div>
</div>
</form>
</dialog>

<div id="toast" class="toast" role="status" aria-live="polite"></div>
<div id="celebration" class="celebration" aria-hidden="true"></div>
<script type="module" src="./src/app.js"></script>
</body>
</html>
Loading