Prompt utilisé pour régénérer cette page :
Page: Lifemap — Timeline Visualization for Life Events
Category: sandbox
Description: "Timeline visualization for life events and periods"
Icon: "timeline-outline"
Tags: ["timeline", "visualization", "life-events", "personal"]
Status: ["work-in-progress"]
Front matter (index.md):
---
title: "Lifemap"
description: "Timeline visualization for life events and periods"
icon: "timeline-outline"
tags: ["timeline", "visualization", "life-events", "personal"]
status: ["work-in-progress"]
---
Directory structure:
- index.md — page content (HTML UI)
- default.js — page controller (LifemapApp class)
- lifemap.lib.js — reusable engine library (Lifemap module v1.0.0)
- default.scss — SCSS styles
HTML structure (index.md body):
div.lifemap-app wrapping two panels:
1. div.timeline-panel:
- section.container.visual.size-800.ratio-16-9.canvas-contain
- svg#lifemap-svg (xmlns="http://www.w3.org/2000/svg") — empty SVG, populated by JS
2. div.sidebar with 3 cards:
Card 1 — "Timeline" (h3):
- div.stats-grid (2-column) with 4 stats:
- Events (#stat-events, default "0")
- Periods (#stat-periods, default "0")
- Start (#stat-start, default "--")
- End (#stat-end, default "--")
Card 2 — "Add Event" (h3):
- control-row: label "Label" + input[type=text]#input-label (placeholder "Event name")
- control-row: label "Category" + select#input-category with 8 options:
work, move, education, family, health, hobby, travel, other
- control-row: label "Start Date" + input[type=date]#input-start
- control-row: label "End Date (optional)" + input[type=date]#input-end
- div.btn-row: button#btn-add "Add Event"
Card 3 — "Data" (h3):
- div.btn-row: button#btn-export "Export JSON" + button#btn-import "Import JSON"
- input[type=file]#file-input (accept=".json", class="hidden")
- div.btn-row: button#btn-clear "Clear All"
=== default.js (page controller) ===
Imports: ./lifemap.lib.js (as Lifemap)
STORAGE_KEY: 'lifemap-events'
DEFAULT_DATA: extensive JSON string with version:1, 8 default categories, ~50 events:
- Education: 1 entry (engineering school 1992-1995)
- Work: 5 positions spanning 1995-present (Junior dev Lyon, Developer Paris, Project lead Nantes, CTO Bordeaux, Freelance consultant)
- Moves: 10 entries (Lyon, Paris, Nantes, Bordeaux, Toulouse)
- Family: 3 events (wedding 2003, 2 children)
- Travel: 24 annual trips (2000-2024, countries: Spain, Italy, Greece, Morocco, Portugal, Croatia, Tunisia, Ireland, Norway, Turkey, Scotland, Japan, Iceland, Thailand, USA, Canada, Mexico, Vietnam, Peru, New Zealand, Corsica, Tanzania, South Korea, Colombia)
Class LifemapApp:
- Constructor: creates Lifemap.Categories() + Lifemap.Store({onChange}), gets SVG element, loads data, binds UI, initial render + stats update.
- bindUI(): btn-add → handleAddEvent(), btn-export → handleExportJSON(), btn-import → triggers hidden file-input click, file-input change → handleImportJSON(), btn-clear → handleClear(). Window resize → render().
- onStoreChange(): saves to storage, re-renders, updates stats.
- loadFromStorage(): temporarily detaches onChange during import. Tries localStorage first, falls back to DEFAULT_DATA. Uses Lifemap.json.import().
- saveToStorage(): persists via Lifemap.json.export() to localStorage.
- render(): gets SVG bounding rect, calls Lifemap.svg.export() with dimensions, strips <svg> wrapper tags, sets svg.innerHTML to inner content.
- updateStats(): calls store.stats(), displays points/periods counts, formatted start/end dates (en-US, year+month).
- handleAddEvent(): reads form inputs, validates label + start required, validates end > start. Calls store.add(), clears form.
- handleExportJSON(): creates Blob download link for JSON, triggers click, revokes URL.
- handleImportJSON(e): reads file via FileReader, calls Lifemap.json.import(), alerts count, resets file input.
- handleClear(): confirm dialog, then store.clear().
Entry point: const lifemapApp = new LifemapApp();
=== lifemap.lib.js (engine library v1.0.0) ===
Module with frozen public API: Lifemap.Store, Lifemap.Categories, Lifemap.json.{export,import}, Lifemap.svg.export.
SVGNS constant: 'http://www.w3.org/2000/svg'.
Class LifeEvent:
- Properties: id, label, category, start (Date), end (Date|null).
- isPeriod(): returns true if end !== null.
- toJSON(): serializes with YYYY-MM-DD dates via formatDate().
Class CategoryRegistry (Map-based):
- 8 default categories with colors: work=#3498db, move=#e67e22, education=#9b59b6, family=#e91e63, health=#27ae60, hobby=#f39c12, travel=#00bcd4, other=#95a5a6.
- Methods: add(key, {color, label}), remove(key), get(key) (fallback to 'other'), color(key), has(key), all() → [{key,color,label}], keys(), size getter.
Class EventStore (Map-based, auto-increment _nextId):
- Constructor({onChange}): optional mutation callback.
- Methods: add({label,category,start,end,id}), remove(id), get(id), all() (sorted by start), filter(predicate), clear(), size getter, stats() → {total,periods,points,start,end}.
- _syncNextId(): recalculates _nextId from max numeric ID.
- _notify(): fires onChange on mutation.
JSON format: { version: 1, categories: {key: {color,label}}, events: [{id,label,category,start,end}] }
- exportJSON(store, categories): builds JSON string with indent=2.
- importJSON(store, categories, jsonString): validates, imports categories, imports events (requires label+start). Returns count.
SVG export (pure function, no DOM):
- SVG_DEFAULTS: width=800, height=400, margins (40/20/30/20), rowHeight=28, rowGap=6, fontSize=12, system-ui font, axis/tick/label colors, padding=30 days.
- exportSVG(store, categories, options): merges options with defaults. Empty state: centered text "No events to display".
- getDateRange(events, paddingDays): finds min/max dates, adds padding in ms.
- buildAxis(opts, minDate, maxDate, timeScale): horizontal axis line + year ticks + year labels.
- buildEvents(events, categories, opts, minDate, timeScale, maxRows): periods as rounded rects (rx=3, opacity=0.85, label if width>40px). Points as diamond polygons (size=8) with adjacent label. Row assignment: index % maxRows.
- Utilities: formatDate(Date→YYYY-MM-DD), r(n→round 2dp), esc(str→XML escape: &<>").
=== default.scss ===
Layout: .lifemap-app = flex, wrap, center, full width, gap var(--layout-spacing).
.timeline-panel: flex column, center, gap 0.75rem.
#lifemap-svg: 100% width/height, secondary bg, 1px border, 8px radius.
.sidebar: flex column, gap 0.875rem, min-width 280px, max-width 320px.
.card: surface bg, 1px border, 8px radius, padding. h3: 0.8rem uppercase muted.
.stats-grid: 2-column grid. .stat-label: 0.7rem muted uppercase. .stat-value: 1.05rem bold tabular-nums.
.btn-row: flex, gap 0.5rem, margin-top 0.5rem.
button: secondary bg, surface border, 6px radius, transitions. &.active: primary. &.danger: error color/border, hover inverts.
.control-row: flex column, 0.25rem gap, 0.5rem margin-bottom. label: 0.78rem muted.
input[type=text/date], select: secondary bg, surface border, 6px radius, full width, focus outline primary.
input[type=file]: same styling, pointer cursor.
SVG elements: .timeline-axis (2px stroke), .timeline-tick (1px), .timeline-label (10px fill).
.event-bar: rx/ry 4, pointer, hover opacity 0.8. .event-label: 11px, pointer-events none.
.event-marker: pointer, hover scale 1.2.
Category colors: .category-work=#3498db, -move=#e67e22, -education=#9b59b6, -family=#e91e63, -health=#27ae60, -hobby=#f39c12, -travel=#00bcd4, -other=#95a5a6.
.hidden: display none.
Page entièrement générée et maintenue par IA, sans intervention humaine.