My name is Anna Möller and these are some of my photo projects.
Sorry, but nothing was found. Please try a search with different keywords.
Sorry, but nothing was found. Please try a search with different keywords.
Sorry, but nothing was found. Please try a search with different keywords.
Sorry, but nothing was found. Please try a search with different keywords.
Sorry, but nothing was found. Please try a search with different keywords.
Twenty Twenty-Five
email@example.com
+1 555 349 1806
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>WWS CALI 0.1 — Vault</title>
<style>
:root{
--bg:#0d0f14; --card:#121623; --ink:#e9eef7; --muted:#aab3c5;
--line:#26304a; --soft:#0f1320; --btn:#1a2240; --btn2:#15203a;
--danger:#4a1f1f; --ok:#1f4a2b; --warn:#4a3b1f;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}
body{ margin:0; background:var(--bg); color:var(--ink); }
header{ padding:16px 18px; border-bottom:1px solid var(--line); }
h1{ margin:0 0 6px; font-size:18px; letter-spacing:.3px; }
.sub{ margin:0; font-size:13px; color:var(--muted); }
main{ max-width:1200px; margin:0 auto; padding:16px 18px 40px; }
.tabs{ display:flex; gap:8px; flex-wrap:wrap; margin:12px 0 14px; }
.tabbtn{
border:1px solid var(--line); background:var(--btn2); color:var(--ink);
padding:10px 12px; border-radius:12px; cursor:pointer; font-weight:650;
}
.tabbtn.active{ background:var(--btn); }
.grid{ display:grid; grid-template-columns:1fr; gap:12px; }
@media(min-width:980px){ .grid{ grid-template-columns:420px 1fr; } }
.card{
background:var(--card); border:1px solid var(--line);
border-radius:16px; padding:14px; box-shadow:0 10px 28px rgba(0,0,0,.25);
}
h2{ margin:0 0 10px; font-size:15px; }
label{ display:block; font-size:12px; color:var(--muted); margin:10px 0 6px; }
input, textarea, select{
width:100%; box-sizing:border-box; padding:10px;
border-radius:12px; border:1px solid var(--line);
background:var(--soft); color:var(--ink); outline:none;
}
textarea{ min-height:84px; resize:vertical; }
.row{ display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.row3{ display:grid; grid-template-columns:1fr 1fr 1fr; gap:10px; }
.btnbar{ display:flex; gap:10px; flex-wrap:wrap; margin-top:12px; }
button{
border:1px solid var(--line); background:var(--btn2); color:var(--ink);
padding:10px 12px; border-radius:12px; cursor:pointer; font-weight:650;
}
button.primary{ background:var(--btn); }
button.danger{ background:var(--danger); border-color:#6a2e2e; }
button.ok{ background:var(--ok); border-color:#2c6a3d; }
button.warn{ background:var(--warn); border-color:#6a542c; }
hr{ border:0; border-top:1px solid var(--line); margin:14px 0; }
.muted{ color:var(--muted); font-size:12px; }
.pill{ display:inline-block; padding:4px 10px; border-radius:999px; border:1px solid var(--line); background:rgba(255,255,255,.03); font-size:12px; color:var(--muted); }
table{ width:100%; border-collapse:collapse; }
th,td{ text-align:left; padding:10px 8px; border-bottom:1px solid var(--line); vertical-align:top; }
th{ color:var(--muted); font-size:12px; font-weight:650; }
tr:hover{ background:rgba(255,255,255,.03); }
.right{ text-align:right; }
.small{ font-size:12px; color:var(--muted); }
.hidden{ display:none; }
.mono{ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.twoColNotes{ display:grid; grid-template-columns:1fr; gap:10px; }
@media(min-width:980px){ .twoColNotes{ grid-template-columns:1fr 1fr; } }
.checkgrid{ display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.checkline{ display:flex; align-items:center; gap:10px; padding:8px 10px; border:1px solid var(--line); border-radius:12px; background:rgba(255,255,255,.02); }
.checkline input{ width:auto; }
</style>
</head>
<body>
<header>
<h1>WWS CALI 0.1 — Vault</h1>
<p class="sub">Offline-first calendar, routine log, and WWS publishing rhythm. Export JSON to move devices. Optional server sync later.</p>
</header>
<main>
<div class="tabs">
<button class="tabbtn active" data-tab="today">Today</button>
<button class="tabbtn" data-tab="calendar">Calendar</button>
<button class="tabbtn" data-tab="wws">WWS Weekly</button>
<button class="tabbtn" data-tab="export">Export / Import</button>
<button class="tabbtn" data-tab="sync">Sync (optional)</button>
</div>
<!-- TODAY -->
<section id="tab-today">
<div class="grid">
<div class="card">
<h2>Daily Page</h2>
<div class="row">
<div>
<label>Date</label>
<input id="d_date" type="date" />
</div>
<div>
<label>Wake time</label>
<input id="d_wake" type="time" />
</div>
</div>
<div class="row">
<div>
<label>Sleep time (night before)</label>
<input id="d_sleep" type="time" />
</div>
<div>
<label>One word for inner weather</label>
<input id="d_weather" placeholder="e.g., clear / heavy / sharp" />
</div>
</div>
<hr />
<h2>Foundation Completed</h2>
<div class="checkgrid">
<label class="checkline"><input type="checkbox" id="d_med" /> Meditation (20–30 mins)</label>
<label class="checkline"><input type="checkbox" id="d_move" /> Movement (yoga / weights)</label>
<label class="checkline"><input type="checkbox" id="d_ski" /> Ski walk & training (60–90 mins)</label>
<label class="checkline"><input type="checkbox" id="d_house" /> House baseline (kitchen / toilets / quick vacuum)</label>
<label class="checkline"><input type="checkbox" id="d_admin" /> Emails + 3 priorities chosen</label>
<label class="checkline"><input type="checkbox" id="d_balance" /> Balance kept (no overload)</label>
</div>
<hr />
<h2>Direction</h2>
<label>The one thing that matters today</label>
<input id="d_one" placeholder="One clean sentence." />
<div class="row">
<div>
<label>Support action 1</label>
<input id="d_sup1" />
</div>
<div>
<label>Support action 2</label>
<input id="d_sup2" />
</div>
</div>
<hr />
<h2>Energy & Nourishment</h2>
<div class="row">
<div>
<label>Feeding window honoured (3–6pm)</label>
<select id="d_feed">
<option value="">Select</option>
<option>Yes</option>
<option>No</option>
</select>
</div>
<div>
<label>Minerals taken</label>
<select id="d_mins">
<option value="">Select</option>
<option>Yes</option>
<option>No</option>
</select>
</div>
</div>
<label>Supplements today</label>
<div class="checkgrid">
<label class="checkline"><input type="checkbox" id="d_algae" /> Algae (every other day)</label>
<label class="checkline"><input type="checkbox" id="d_camu" /> Camu camu</label>
<label class="checkline"><input type="checkbox" id="d_thistle" /> Milk thistle</label>
<label class="checkline"><input type="checkbox" id="d_clove" /> Clove water</label>
</div>
<label>Body response after food</label>
<select id="d_bodyresp">
<option value="">Select</option>
<option>Light</option>
<option>Neutral</option>
<option>Heavy</option>
<option>Inflamed</option>
</select>
<hr />
<div class="twoColNotes">
<div>
<h2>Shadow Check</h2>
<label>Where did I notice resistance today</label>
<textarea id="d_shadow"></textarea>
<label>What story tried to run me</label>
<textarea id="d_story"></textarea>
</div>
<div>
<h2>Record</h2>
<label>What actually moved today</label>
<textarea id="d_moved"></textarea>
<label>What stayed unresolved (leave it here)</label>
<textarea id="d_unres"></textarea>
<label>One thing I did well (fact, not praise)</label>
<textarea id="d_well"></textarea>
</div>
</div>
<div class="btnbar">
<button class="primary" id="saveDaily">Save Daily</button>
<button id="loadDaily">Load</button>
<button class="warn" id="resetDaily">Reset fields</button>
</div>
<div class="muted" style="margin-top:10px;">
This page is a record, not a judgement.
</div>
</div>
<div class="card">
<h2>Next 30 days (Red-line first)</h2>
<div class="row3">
<div>
<label>Filter</label>
<select id="f_cat">
<option value="All" selected>All</option>
<option>High Court</option>
<option>District Court</option>
<option>Legal Admin</option>
<option>WWS Publishing</option>
<option>Income / Organic Road Show</option>
<option>Family / Care</option>
<option>Utilities / Civil Bills</option>
<option>Other</option>
</select>
</div>
<div>
<label>Priority</label>
<select id="f_pri">
<option value="All" selected>All</option>
<option>Red-line</option>
<option>High</option>
<option>Normal</option>
<option>Low</option>
</select>
</div>
<div>
<label>Search</label>
<input id="f_q" placeholder="title, tags..." />
</div>
</div>
<div class="btnbar">
<button class="ok" id="seedCore">Seed Feb 2026 court dates</button>
</div>
<div style="overflow:auto; margin-top:10px;">
<table>
<thead>
<tr>
<th style="min-width:110px;">Date</th>
<th style="min-width:120px;">Category</th>
<th>Title</th>
<th style="min-width:90px;">Priority</th>
</tr>
</thead>
<tbody id="nextRows"></tbody>
</table>
</div>
<div class="small" id="nextCount"></div>
</div>
</div>
</section>
<!-- CALENDAR -->
<section id="tab-calendar" class="hidden">
<div class="grid">
<div class="card">
<h2>Add / Edit Event</h2>
<label>Title</label>
<input id="e_title" placeholder="e.g., High Court — injunction hearing" />
<div class="row">
<div>
<label>Date</label>
<input id="e_date" type="date" />
</div>
<div>
<label>Time (optional)</label>
<input id="e_time" type="time" />
</div>
</div>
<div class="row">
<div>
<label>Category</label>
<select id="e_cat">
<option>High Court</option>
<option>District Court</option>
<option>Legal Admin</option>
<option>WWS Publishing</option>
<option>Income / Organic Road Show</option>
<option>Family / Care</option>
<option>Utilities / Civil Bills</option>
<option>Other</option>
</select>
</div>
<div>
<label>Priority</label>
<select id="e_pri">
<option>Red-line</option>
<option>High</option>
<option selected>Normal</option>
<option>Low</option>
</select>
</div>
</div>
<label>Tags (comma separated)</label>
<input id="e_tags" placeholder="Pepper, affidavit, service, WWS, outreach" />
<label>Notes / Strategy</label>
<textarea id="e_notes" placeholder="Key objective, prep requirements, what must happen before the date."></textarea>
<div class="row">
<div>
<label>Reminder days before (optional)</label>
<input id="e_rem" type="number" min="0" placeholder="e.g., 14" />
</div>
<div>
<label>Event ID</label>
<input id="e_id" disabled class="mono" />
</div>
</div>
<div class="btnbar">
<button class="primary" id="saveEvent">Save Event</button>
<button id="clearEvent">Clear</button>
</div>
<div class="muted" style="margin-top:10px;">
Tip: Use Priority = Red-line for anything that could end in eviction or custody loss of home, or jail risk.
</div>
</div>
<div class="card">
<h2>All Events</h2>
<div class="row3">
<div>
<label>Filter category</label>
<select id="c_cat">
<option value="All" selected>All</option>
<option>High Court</option>
<option>District Court</option>
<option>Legal Admin</option>
<option>WWS Publishing</option>
<option>Income / Organic Road Show</option>
<option>Family / Care</option>
<option>Utilities / Civil Bills</option>
<option>Other</option>
</select>
</div>
<div>
<label>Priority</label>
<select id="c_pri">
<option value="All" selected>All</option>
<option>Red-line</option>
<option>High</option>
<option>Normal</option>
<option>Low</option>
</select>
</div>
<div>
<label>Search</label>
<input id="c_q" placeholder="search..." />
</div>
</div>
<div style="overflow:auto; margin-top:10px;">
<table>
<thead>
<tr>
<th style="min-width:110px;">Date</th>
<th style="min-width:120px;">Category</th>
<th>Title</th>
<th style="min-width:90px;">Priority</th>
<th style="min-width:120px;" class="right">Actions</th>
</tr>
</thead>
<tbody id="allRows"></tbody>
</table>
</div>
<div class="small" id="allCount"></div>
</div>
</div>
</section>
<!-- WWS -->
<section id="tab-wws" class="hidden">
<div class="grid">
<div class="card">
<h2>WWS Weekly (Mon → Sun)</h2>
<div class="muted">This is your publishing engine. Keep it simple. Keep it moving.</div>
<hr />
<label>Week starting (Monday)</label>
<input id="w_monday" type="date" />
<label>Book title / working title</label>
<input id="w_title" placeholder="e.g., Working With Shadow — Volume X" />
<label>One sentence intent</label>
<input id="w_intent" placeholder="This week’s book says..." />
<hr />
<h2>Weekly steps</h2>
<div class="checkgrid">
<label class="checkline"><input type="checkbox" id="w_mon" /> Monday: intent + outline</label>
<label class="checkline"><input type="checkbox" id="w_tue" /> Tuesday: writing</label>
<label class="checkline"><input type="checkbox" id="w_wed" /> Wednesday: writing</label>
<label class="checkline"><input type="checkbox" id="w_thu" /> Thursday: edit + tighten</label>
<label class="checkline"><input type="checkbox" id="w_fri" /> Friday: format + metadata</label>
<label class="checkline"><input type="checkbox" id="w_sat" /> Saturday: publish + site update</label>
<label class="checkline"><input type="checkbox" id="w_sun" /> Sunday: review + next theme</label>
</div>
<label>Notes</label>
<textarea id="w_notes"></textarea>
<div class="btnbar">
<button class="primary" id="saveWeek">Save Week</button>
<button id="loadWeek">Load</button>
<button class="warn" id="resetWeek">Reset</button>
</div>
</div>
<div class="card">
<h2>Weekly minerals check (Sunday)</h2>
<div class="muted">Ten minutes. Objective. No drama.</div>
<hr />
<label>Week ending (Sunday)</label>
<input id="m_sunday" type="date" />
<div class="checkgrid">
<label class="checkline"><input type="checkbox" id="m_hyd" /> Hydration consistent</label>
<label class="checkline"><input type="checkbox" id="m_dig" /> Digestion steady</label>
<label class="checkline"><input type="checkbox" id="m_energy" /> Energy stable</label>
<label class="checkline"><input type="checkbox" id="m_sleep" /> Sleep respected</label>
</div>
<label>Algae cadence used (Mon/Wed/Fri/Sun)</label>
<select id="m_algae">
<option value="">Select</option>
<option>Yes</option>
<option>No</option>
<option>Mixed</option>
</select>
<label>Notes (what worked / what did not)</label>
<textarea id="m_notes"></textarea>
<div class="btnbar">
<button class="primary" id="saveMinerals">Save Minerals</button>
<button id="loadMinerals">Load</button>
</div>
</div>
</div>
</section>
<!-- EXPORT -->
<section id="tab-export" class="hidden">
<div class="card">
<h2>Export / Import JSON</h2>
<div class="muted">Use export/import to move your vault between devices without a server.</div>
<div class="btnbar">
<button class="primary" id="doExport">Export</button>
<button id="doImport">Import</button>
<button class="danger" id="wipeAll">Wipe local vault</button>
</div>
<label>JSON</label>
<textarea id="jsonBox" class="mono" placeholder="Export appears here. Paste JSON here to import."></textarea>
</div>
</section>
<!-- SYNC -->
<section id="tab-sync" class="hidden">
<div class="card">
<h2>Optional Sync (server storage)</h2>
<div class="muted">
If you want access anywhere on any device, you need a small endpoint on your private domain.
This UI can push/pull JSON to that endpoint. You can add the endpoint later.
</div>
<hr />
<label>Endpoint URL</label>
<input id="s_url" placeholder="e.g., https://yourdomain.com/cali-sync.php" />
<label>Access token (simple shared secret)</label>
<input id="s_token" placeholder="A long random token" />
<div class="btnbar">
<button class="ok" id="pushServer">Push vault to server</button>
<button class="primary" id="pullServer">Pull vault from server</button>
</div>
<div class="muted" style="margin-top:10px;">
Notes: This is basic. For stronger security, you would use real authentication and HTTPS only.
</div>
</div>
</section>
</main>
<script>
/* ---------------------------
Storage and data model
----------------------------*/
const KEY = "wws_cali_v01";
const state = {
version: "0.1",
events: [],
daily: {}, // daily[YYYY-MM-DD] = {fields...}
wwsWeeks: {}, // wwsWeeks[YYYY-MM-DD Monday] = {fields...}
mineralsWeeks: {}, // mineralsWeeks[YYYY-MM-DD Sunday] = {fields...}
sync: { url: "", token: "" }
};
function loadState(){
const raw = localStorage.getItem(KEY);
if (!raw) return;
try{
const obj = JSON.parse(raw);
Object.assign(state, obj);
}catch(e){}
}
function saveState(){
localStorage.setItem(KEY, JSON.stringify(state));
}
function uid(){
return "e_" + Math.random().toString(16).slice(2) + "_" + Date.now().toString(16);
}
function tagsParse(s){
return (s||"").split(",").map(x=>x.trim()).filter(Boolean);
}
function priWeight(p){
return ({ "Red-line":0, "High":1, "Normal":2, "Low":3 }[p] ?? 9);
}
function sortEvents(list){
return [...list].sort((a,b)=>{
const aw=priWeight(a.priority), bw=priWeight(b.priority);
if (aw!==bw) return aw-bw;
const ad=(a.date||"") + (a.time||"");
const bd=(b.date||"") + (b.time||"");
return ad.localeCompare(bd);
});
}
function todayISO(){
const d = new Date();
const yyyy = d.getFullYear();
const mm = String(d.getMonth()+1).padStart(2,"0");
const dd = String(d.getDate()).padStart(2,"0");
return `${yyyy}-${mm}-${dd}`;
}
function $(id){ return document.getElementById(id); }
/* ---------------------------
Tabs
----------------------------*/
document.querySelectorAll(".tabbtn").forEach(btn=>{
btn.addEventListener("click", ()=>{
document.querySelectorAll(".tabbtn").forEach(b=>b.classList.remove("active"));
btn.classList.add("active");
const tab = btn.dataset.tab;
document.querySelectorAll("main section[id^='tab-']").forEach(s=>s.classList.add("hidden"));
$("tab-"+tab).classList.remove("hidden");
renderAll();
});
});
/* ---------------------------
Daily page
----------------------------*/
function dailyCollect(){
const date = $("d_date").value;
if(!date) return null;
return {
date,
wake: $("d_wake").value,
sleep: $("d_sleep").value,
weather: $("d_weather").value.trim(),
foundation: {
med: $("d_med").checked,
move: $("d_move").checked,
ski: $("d_ski").checked,
house: $("d_house").checked,
admin: $("d_admin").checked,
balance: $("d_balance").checked
},
direction: {
one: $("d_one").value.trim(),
sup1: $("d_sup1").value.trim(),
sup2: $("d_sup2").value.trim()
},
nourish: {
feed: $("d_feed").value,
minerals: $("d_mins").value,
algae: $("d_algae").checked,
camu: $("d_camu").checked,
thistle: $("d_thistle").checked,
clove: $("d_clove").checked,
bodyresp: $("d_bodyresp").value
},
shadow: $("d_shadow").value.trim(),
story: $("d_story").value.trim(),
moved: $("d_moved").value.trim(),
unresolved: $("d_unres").value.trim(),
didwell: $("d_well").value.trim(),
updatedAt: new Date().toISOString()
};
}
function dailyFill(obj){
$("d_date").value = obj.date || "";
$("d_wake").value = obj.wake || "";
$("d_sleep").value = obj.sleep || "";
$("d_weather").value = obj.weather || "";
$("d_med").checked = !!obj.foundation?.med;
$("d_move").checked = !!obj.foundation?.move;
$("d_ski").checked = !!obj.foundation?.ski;
$("d_house").checked = !!obj.foundation?.house;
$("d_admin").checked = !!obj.foundation?.admin;
$("d_balance").checked = !!obj.foundation?.balance;
$("d_one").value = obj.direction?.one || "";
$("d_sup1").value = obj.direction?.sup1 || "";
$("d_sup2").value = obj.direction?.sup2 || "";
$("d_feed").value = obj.nourish?.feed || "";
$("d_mins").value = obj.nourish?.minerals || "";
$("d_algae").checked = !!obj.nourish?.algae;
$("d_camu").checked = !!obj.nourish?.camu;
$("d_thistle").checked = !!obj.nourish?.thistle;
$("d_clove").checked = !!obj.nourish?.clove;
$("d_bodyresp").value = obj.nourish?.bodyresp || "";
$("d_shadow").value = obj.shadow || "";
$("d_story").value = obj.story || "";
$("d_moved").value = obj.moved || "";
$("d_unres").value = obj.unresolved || "";
$("d_well").value = obj.didwell || "";
}
$("saveDaily").addEventListener("click", ()=>{
const obj = dailyCollect();
if(!obj){ alert("Choose a date first."); return; }
state.daily[obj.date] = obj;
saveState();
alert("Saved.");
});
$("loadDaily").addEventListener("click", ()=>{
const date = $("d_date").value;
if(!date){ alert("Choose a date first."); return; }
const obj = state.daily[date];
if(!obj){ alert("No saved daily page for that date."); return; }
dailyFill(obj);
});
$("resetDaily").addEventListener("click", ()=>{
const d = $("d_date").value;
dailyFill({date:d});
});
/* ---------------------------
Events
----------------------------*/
function eventCollect(){
const title = $("e_title").value.trim();
const date = $("e_date").value;
if(!title || !date) return null;
const existingId = $("e_id").value.trim();
const id = existingId || uid();
return {
id,
title,
date,
time: $("e_time").value,
category: $("e_cat").value,
priority: $("e_pri").value,
tags: tagsParse($("e_tags").value),
notes: $("e_notes").value.trim(),
reminderDays: $("e_rem").value ? Number($("e_rem").value) : "",
updatedAt: new Date().toISOString(),
createdAt: existingId ? (state.events.find(x=>x.id===id)?.createdAt || new Date().toISOString()) : new Date().toISOString()
};
}
function eventFill(e){
$("e_title").value = e.title || "";
$("e_date").value = e.date || "";
$("e_time").value = e.time || "";
$("e_cat").value = e.category || "Other";
$("e_pri").value = e.priority || "Normal";
$("e_tags").value = (e.tags||[]).join(", ");
$("e_notes").value = e.notes || "";
$("e_rem").value = (e.reminderDays === "" || e.reminderDays == null) ? "" : String(e.reminderDays);
$("e_id").value = e.id || "";
}
$("saveEvent").addEventListener("click", ()=>{
const e = eventCollect();
if(!e){ alert("Title + Date are required."); return; }
const idx = state.events.findIndex(x=>x.id===e.id);
if(idx >= 0) state.events[idx] = e; else state.events.push(e);
saveState();
eventFill({});
renderAll();
});
$("clearEvent").addEventListener("click", ()=> eventFill({}));
function seedCoreDates(){
const seeds = [
{
title:"District Court — burglary charge",
date:"2026-02-16",
time:"",
category:"District Court",
priority:"Red-line",
tags:["Bray","appearance","defence"],
notes:"Red-line. Pack documents day before. Short morning. No heavy decisions after court.",
reminderDays:14
},
{
title:"High Court — eviction injunction",
date:"2026-02-17",
time:"",
category:"High Court",
priority:"Red-line",
tags:["injunction","eviction","affidavit"],
notes:"Red-line. Preserve position. Keep points prepared and short. Track affidavit service date.",
reminderDays:14
}
];
for(const s of seeds){
// avoid duplicates by title+date
const exists = state.events.some(e=>e.title===s.title && e.date===s.date);
if(!exists) state.events.push({ id:uid(), createdAt:new Date().toISOString(), updatedAt:new Date().toISOString(), ...s });
}
saveState();
renderAll();
}
$("seedCore").addEventListener("click", seedCoreDates);
/* ---------------------------
WWS weekly + minerals weekly
----------------------------*/
function weekCollect(){
const monday = $("w_monday").value;
if(!monday) return null;
return {
monday,
title: $("w_title").value.trim(),
intent: $("w_intent").value.trim(),
checks: {
mon:$("w_mon").checked, tue:$("w_tue").checked, wed:$("w_wed").checked,
thu:$("w_thu").checked, fri:$("w_fri").checked, sat:$("w_sat").checked, sun:$("w_sun").checked
},
notes: $("w_notes").value.trim(),
updatedAt: new Date().toISOString()
};
}
function weekFill(w){
$("w_monday").value = w.monday || "";
$("w_title").value = w.title || "";
$("w_intent").value = w.intent || "";
$("w_mon").checked = !!w.checks?.mon;
$("w_tue").checked = !!w.checks?.tue;
$("w_wed").checked = !!w.checks?.wed;
$("w_thu").checked = !!w.checks?.thu;
$("w_fri").checked = !!w.checks?.fri;
$("w_sat").checked = !!w.checks?.sat;
$("w_sun").checked = !!w.checks?.sun;
$("w_notes").value = w.notes || "";
}
$("saveWeek").addEventListener("click", ()=>{
const w = weekCollect();
if(!w){ alert("Choose the Monday date."); return; }
state.wwsWeeks[w.monday] = w;
saveState();
alert("Saved.");
});
$("loadWeek").addEventListener("click", ()=>{
const monday = $("w_monday").value;
if(!monday){ alert("Choose the Monday date."); return; }
const w = state.wwsWeeks[monday];
if(!w){ alert("No saved week for that Monday."); return; }
weekFill(w);
});
$("resetWeek").addEventListener("click", ()=>{
const m = $("w_monday").value;
weekFill({monday:m});
});
function mineralsCollect(){
const sunday = $("m_sunday").value;
if(!sunday) return null;
return {
sunday,
hyd: $("m_hyd").checked,
dig: $("m_dig").checked,
energy: $("m_energy").checked,
sleep: $("m_sleep").checked,
algae: $("m_algae").value,
notes: $("m_notes").value.trim(),
updatedAt: new Date().toISOString()
};
}
function mineralsFill(m){
$("m_sunday").value = m.sunday || "";
$("m_hyd").checked = !!m.hyd;
$("m_dig").checked = !!m.dig;
$("m_energy").checked = !!m.energy;
$("m_sleep").checked = !!m.sleep;
$("m_algae").value = m.algae || "";
$("m_notes").value = m.notes || "";
}
$("saveMinerals").addEventListener("click", ()=>{
const m = mineralsCollect();
if(!m){ alert("Choose the Sunday date."); return; }
state.mineralsWeeks[m.sunday] = m;
saveState();
alert("Saved.");
});
$("loadMinerals").addEventListener("click", ()=>{
const sunday = $("m_sunday").value;
if(!sunday){ alert("Choose the Sunday date."); return; }
const m = state.mineralsWeeks[sunday];
if(!m){ alert("No saved minerals week for that Sunday."); return; }
mineralsFill(m);
});
/* ---------------------------
Export / Import
----------------------------*/
$("doExport").addEventListener("click", ()=>{
$("jsonBox").value = JSON.stringify({ exportedAt:new Date().toISOString(), ...state }, null, 2);
});
$("doImport").addEventListener("click", ()=>{
try{
const obj = JSON.parse($("jsonBox").value);
if(!obj || !obj.version || !obj.events) throw new Error("Bad format");
// keep only known keys
state.version = obj.version;
state.events = Array.isArray(obj.events) ? obj.events : [];
state.daily = obj.daily || {};
state.wwsWeeks = obj.wwsWeeks || {};
state.mineralsWeeks = obj.mineralsWeeks || {};
state.sync = obj.sync || state.sync;
saveState();
renderAll();
alert("Imported.");
}catch(e){
alert("Import failed: " + e.message);
}
});
$("wipeAll").addEventListener("click", ()=>{
if(!confirm("Wipe local vault? This cannot be undone.")) return;
localStorage.removeItem(KEY);
location.reload();
});
/* ---------------------------
Sync (optional endpoint)
Server expects:
- GET returns JSON state
- POST stores JSON state
Header: X-Token: <token>
----------------------------*/
function syncFill(){
$("s_url").value = state.sync.url || "";
$("s_token").value = state.sync.token || "";
}
$("pushServer").addEventListener("click", async ()=>{
state.sync.url = $("s_url").value.trim();
state.sync.token = $("s_token").value.trim();
saveState();
if(!state.sync.url || !state.sync.token){ alert("Set URL + token."); return; }
try{
const res = await fetch(state.sync.url, {
method:"POST",
headers:{
"Content-Type":"application/json",
"X-Token": state.sync.token
},
body: JSON.stringify(state)
});
if(!res.ok) throw new Error("HTTP " + res.status);
alert("Pushed to server.");
}catch(e){
alert("Push failed: " + e.message);
}
});
$("pullServer").addEventListener("click", async ()=>{
state.sync.url = $("s_url").value.trim();
state.sync.token = $("s_token").value.trim();
saveState();
if(!state.sync.url || !state.sync.token){ alert("Set URL + token."); return; }
try{
const res = await fetch(state.sync.url, {
method:"GET",
headers:{ "X-Token": state.sync.token }
});
if(!res.ok) throw new Error("HTTP " + res.status);
const obj = await res.json();
if(!obj || !obj.version || !obj.events) throw new Error("Bad data from server");
state.version = obj.version;
state.events = Array.isArray(obj.events) ? obj.events : [];
state.daily = obj.daily || {};
state.wwsWeeks = obj.wwsWeeks || {};
state.mineralsWeeks = obj.mineralsWeeks || {};
// keep sync settings
saveState();
renderAll();
alert("Pulled from server.");
}catch(e){
alert("Pull failed: " + e.message);
}
});
/* ---------------------------
Rendering tables
----------------------------*/
function renderNext30(){
const cat = $("f_cat")?.value || "All";
const pri = $("f_pri")?.value || "All";
const q = ($("f_q")?.value || "").toLowerCase();
const now = new Date();
const max = new Date(); max.setDate(max.getDate() + 30);
let list = state.events.filter(e=>{
if(!e.date) return false;
const d = new Date(e.date + "T00:00:00");
return d >= new Date(now.toDateString()) && d <= max;
});
if(cat !== "All") list = list.filter(e=>e.category===cat);
if(pri !== "All") list = list.filter(e=>e.priority===pri);
if(q) list = list.filter(e=> (e.title + " " + (e.tags||[]).join(" ")).toLowerCase().includes(q));
list = sortEvents(list);
const tb = $("nextRows");
tb.innerHTML = "";
for(const e of list){
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${e.date}${e.time ? " " + e.time : ""}</td>
<td>${e.category}</td>
<td><div style="font-weight:700;">${escapeHtml(e.title)}</div><div class="small">${escapeHtml((e.tags||[]).join(", "))}</div></td>
<td>${e.priority}</td>
`;
tb.appendChild(tr);
}
$("nextCount").textContent = `${list.length} shown`;
}
function renderAllEvents(){
const cat = $("c_cat")?.value || "All";
const pri = $("c_pri")?.value || "All";
const q = ($("c_q")?.value || "").toLowerCase();
let list = [...state.events];
if(cat !== "All") list = list.filter(e=>e.category===cat);
if(pri !== "All") list = list.filter(e=>e.priority===pri);
if(q) list = list.filter(e=>{
const blob = (e.title + " " + e.category + " " + e.priority + " " + (e.tags||[]).join(" ") + " " + (e.notes||"")).toLowerCase();
return blob.includes(q);
});
list = sortEvents(list);
const tb = $("allRows");
tb.innerHTML = "";
for(const e of list){
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${e.date}${e.time ? " " + e.time : ""}</td>
<td>${e.category}</td>
<td><div style="font-weight:700;">${escapeHtml(e.title)}</div><div class="small">${escapeHtml((e.tags||[]).join(", "))}</div></td>
<td>${e.priority}</td>
<td class="right">
<button data-act="edit" data-id="${e.id}">Edit</button>
<button data-act="del" data-id="${e.id}" class="danger">Del</button>
</td>
`;
tb.appendChild(tr);
}
$("allCount").textContent = `${list.length} shown / ${state.events.length} total`;
tb.addEventListener("click", (ev)=>{
const btn = ev.target.closest("button");
if(!btn) return;
const id = btn.getAttribute("data-id");
const act = btn.getAttribute("data-act");
const idx = state.events.findIndex(x=>x.id===id);
if(idx < 0) return;
if(act==="edit"){
eventFill(state.events[idx]);
// switch to calendar tab (form is there)
document.querySelectorAll(".tabbtn").forEach(b=>b.classList.remove("active"));
document.querySelector(".tabbtn[data-tab='calendar']").classList.add("active");
document.querySelectorAll("main section[id^='tab-']").forEach(s=>s.classList.add("hidden"));
$("tab-calendar").classList.remove("hidden");
window.scrollTo({top:0, behavior:"smooth"});
}
if(act==="del"){
if(!confirm("Delete this event?")) return;
state.events.splice(idx,1);
saveState();
renderAll();
}
}, { once:true });
}
function escapeHtml(s){
return (s||"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">");
}
function renderAll(){
if(!$("d_date").value) $("d_date").value = todayISO();
if(!$("w_monday").value) $("w_monday").value = "";
if(!$("m_sunday").value) $("m_sunday").value = "";
renderNext30();
renderAllEvents();
syncFill();
}
$("f_cat").addEventListener("change", renderNext30);
$("f_pri").addEventListener("change", renderNext30);
$("f_q").addEventListener("input", renderNext30);
$("c_cat").addEventListener("change", renderAllEvents);
$("c_pri").addEventListener("change", renderAllEvents);
$("c_q").addEventListener("input", renderAllEvents);
/* ---------------------------
Boot
----------------------------*/
loadState();
$("d_date").value = todayISO();
renderAll();
</script>
</body>
</html>