archiving
Some checks failed
Build Roam Site / build (push) Has been cancelled

This commit is contained in:
2026-06-03 14:12:58 +01:00
parent a758ae1793
commit ac1d3839ba
22 changed files with 1180 additions and 285 deletions

View File

@@ -0,0 +1,41 @@
:PROPERTIES:
:ID: 3a8274ef-f2fe-486d-9caf-0c56982b9917
:END:
#+title: RSS Articles
#+filetags: :research:articles:
* [[https://blog.pragmaticengineer.com/downdetector-and-the-real-cost-of-no-upstream-dependencies/][Downdetector and the real cost of no upstream dependencies]]
:PROPERTIES:
:FEED: The Pragmatic Engineer
:MINIFLUX_ID: 5250
:READ_AT: 2026-01-11 21:29
:END:
* [[https://coffeeandjunk.substack.com/p/loneliness][Loneliness Isnt Just Something We Endure]]
:PROPERTIES:
:FEED: The Sunday Wisdom
:MINIFLUX_ID: 5363
:READ_AT: 2026-01-12 09:26
:END:
* [[https://bigthink.com/technology-innovation/dormant-volcanoes-copper-mining][What are dormant volcanoes good for? Copper mining]]
:PROPERTIES:
:FEED: Big Think
:MINIFLUX_ID: 5389
:READ_AT: 2026-01-12 11:14
:END:
* [[https://coffeeandjunk.substack.com/p/genius][Where Have All the Geniuses Gone?]]
:PROPERTIES:
:FEED: The Sunday Wisdom
:MINIFLUX_ID: 5358
:READ_AT: 2026-01-25 22:52
:END:
* [[https://coffeeandjunk.substack.com/p/forever][Not Everything Has to Be Timeless]]
:PROPERTIES:
:FEED: The Sunday Wisdom
:MINIFLUX_ID: 5361
:READ_AT: 2026-02-10 17:48
:END:

View File

@@ -23,3 +23,5 @@ All daily journal entries, linked automatically.
** [[file:daily/2026-05-22.org][2026-05-22]]
** [[file:daily/2026-05-28.org][2026-05-28]]
** [[file:daily/2026-05-29.org][2026-05-29]]
** [[file:daily/2026-06-01.org][2026-06-01]]
** [[file:daily/2026-06-02.org][2026-06-02]]

View File

@@ -6,5 +6,7 @@
[[id:b4da3cfd-f7d8-46aa-b3d7-a231178c06b6][Dawrah]]
[[id:a68223f1-5d80-4ee1-8676-1be9dc742d0e][Naats]]
* Articles:
https://siblingsofilm.com/directions-for-dawrah/

View File

@@ -0,0 +1,36 @@
:PROPERTIES:
:ID: 01c89142-7e14-42b2-bd01-743656908fd2
:END:
#+title: Cross Site Scripting (XSS)
#+filetags: :career:security:notes:
* Definition:
- Cross Site Scripting (XSS) is a type of security vulnerability typically found in web applications. It allows attackers to inject malicious scripts into content from otherwise trusted websites.
* Types of XSS:
** 1. Stored XSS / Persistent XSS / Type 2 XSS:
The malicious script is permanently stored on the target server, such as in a database, message forum, visitor log, comment field, etc. When a user requests the stored information, the malicious script is served as part of the web page.
Example: An attacker posts a comment containing a malicious script on a blog. When other users view the comment, the script executes in their browsers, potentially stealing their cookies or performing other malicious actions.
** 2. Reflected XSS / Non-Persistent XSS / Type 1 XSS:
The malicious script is reflected off a web server, such as in an error message, search result, or any other response that includes some or all of the input sent to the server as part of the request. Reflected XSS is delivered to victims via another route, such as in an email or a third-party web site.
Example: An attacker crafts a URL that includes a malicious script. When a user clicks on the link, the script is reflected off the server and executed in the user's browser.
** 3. DOM-based XSS:
The vulnerability exists in the client-side code rather than the server-side code. The malicious script is executed as a result of modifying the DOM (Document Object Model) environment in the victim's browser, which causes the client-side code to execute in an unintended manner.
Example: An attacker manipulates the URL of a web page to include a malicious script. When the page processes the URL, it executes the script in the user's browser.
* Prevention:
- Input Validation: Ensure that all user input is properly validated and sanitized before being processed or stored. This includes using whitelisting techniques to allow only expected input.
- Output Encoding: Encode output to ensure that any potentially malicious scripts are rendered harmless when displayed in the browser. This can be done using functions that convert special characters to their corresponding HTML entities.
- Content Security Policy (CSP): Implement a Content Security Policy to restrict the sources from which scripts can be loaded and executed. This can help mitigate the impact of XSS attacks.
- Use Security Libraries: Utilize security libraries and frameworks that provide built-in protection against XSS vulnerabilities.

98
20260601134732-naats.org Executable file
View File

@@ -0,0 +1,98 @@
:PROPERTIES:
:ID: a68223f1-5d80-4ee1-8676-1be9dc742d0e
:END:
#+title: Naats
* Tu Hai Banda Khuda Ka Khuda Ki Qasam ┇ Jameel Hansrot
https://www.youtube.com/watch?v=U5ZT8Kc0W3w&list=RDU5ZT8Kc0W3w&start_radio=1
** Urdu lyrics:
Tu Hai Banda Khuda Ka Khuda Ki Qasam
Tere Dil Mein Khuda Ki Mohabbat Nahi
Tu Musalman Hai Lekin Sirf Naam Ka
Ghar Mein Quran Hai Lekin Tilawat Nahi
Kaam Bhoole Se Neyki Ka Karta Nahi
Jiska Banda Hai Tu Uss Se Darta Nahi
Aanay Wali Qiyaamat Se Dar Be Khabar
Tu Samajhta Hai Shayad Qiyaamat Nahi
Dono Alam Ke Maalik Wohi Hai Wohi
Jiske Qabze Mein Hai Mawt Aur Zindagi
Hai Rustam Na Daare Sikandar Kaha
Uske Aagay Kisi Ki Hukoomat Nahi
Tujhko Maalik Ne Dunya Mein Peyda Kiya
Kaha Farishto Pe Teri Hukoomat Nahi
Tujh Ko Maalik Ne Dunya Mein Sab Kuch Diya
Dil Diya Aql Di Phir Na Sajdah Kiya
** English
You are a servant of God, by Gods oath,
But in your heart there is no love for God.
You are a Muslim, but only in name,
There is a Quran in your house, but no recitation.
You never do a good deed, even by mistake,
You do not fear the One whose servant you are.
O unaware one, fear the coming Day of Judgment,
You perhaps think that Judgment Day will never come.
He alone is the Master of both worlds,
In His control are death and life.
Where are Rustam, Darius, and Alexander now?
Before Him, no one has any rule or power.
Your Master created you in this world,
Did He not give you honour above the angels?
Your Master gave you everything in this world,
He gave you a heart and intellect, yet you did not prostrate.
* Ek Baar Madinay Mein Ho Jaye Mera Jana | Jameel Hansrot
https://www.youtube.com/watch?v=H_7MWxvw0h4&list=RDH_7MWxvw0h4&start_radio=1
** Urdu lyrics:
Ek Baar Madine Me Ho Jaye Mere Jaana
Phir Aur Na Kuch Maange Sarkaar Ka Deewana
Pal Pal Mera Dil Tadpe Din Raat Kare Zaari
Kab Aau Madine Me Kab Aye Meri Baari
Kab Jaake Mai Dekhuga Darbaar Woh Shahana
Is Aas Par Jeeta Hu Ek Roaz Bulaye Ge
Aur Gumbad E Khazra Ka Deedar Karayege
Phir Pesh Karuga Mai Ashko Bhara Nazrana
Bechain Nighahon Ko Deedaar Ataa Kar Do
Daman Mera Khushiyon Se Yaa Shah E Umam Bhar Do
Aabad Khuda Rakhe Aaqa Tera Maikhana
Itni Si Tamana Hai Ho Jaye Agar Poori
Jaha Dekhu Madina Mai Ho Jaye Jo Manzoori
Bin Deed Kiye Shaha Mar Jaye Na Deewana
Ek Baar Madine Me Ho Jaye Mere Jaana
Phir Aur Na Kuch Maange Sarkaar Ka Deewana
** English
**If only I could go to Madinah once, my beloved,**
**Then this devotee of the Master would ask for nothing more.**
Every moment my heart aches, day and night it keeps crying,
When will I come to Madinah? When will my turn arrive?
When will I finally go and see that majestic court?
I live with this hope that one day I will be called,
And I will be granted the sight of the Green Dome.
Then I will present my offering filled with tears.
Grant my restless eyes the blessing of seeing you,
O Leader of all nations, fill my empty hands with happiness.
May God keep your blessed sanctuary flourishing forever, O Master.
My only wish is this: if it could be fulfilled,
Wherever I look, may I see Madinah, if it is accepted.
O King, may this devotee not die before seeing you.
**If only I could go to Madinah once, my beloved,**
**Then this devotee of the Master would ask for nothing more.**

17
20260602101857-sql_joins.org Executable file
View File

@@ -0,0 +1,17 @@
:PROPERTIES:
:ID: de3bef27-81c9-49d1-85bb-edceb9a80e65
:END:
#+title: SQL Joins
#+filetags: :sql:joins:database:
* The SQL JOIN Clause
The JOIN clause is used to combine rows from two or more tables, based on a related column between them.
Here are the different types of JOINs in SQL:
**(INNER) JOIN**: Returns only rows that have matching values in both tables
**LEFT (OUTER) JOIN**: Returns all rows from the left table, and only the matched rows from the right table
**RIGHT (OUTER) JOIN**: Returns all rows from the right table, and only the matched rows from the left table
**FULL (OUTER) JOIN**: Returns all rows when there is a match in either the left or right table

View File

@@ -7,7 +7,7 @@
#+filetags: :books:org:index:
#+DATE: 2025-05-19
#+STARTUP: content
#+PROPERTY: GENERATED_AT 2026-05-29T00:00:01.183286
#+PROPERTY: GENERATED_AT 2026-06-03T00:00:01.630629
* Career
** Clean Code_ A Handbook of Agile Software Craftsmanship - Robert C. Martin :Read:

View File

@@ -56,6 +56,8 @@ Some core programming concepts that are essential for software development. The
- [[id:b4858f6a-b05c-47f2-972e-905e0bb2c352][Database Permissions, Roles, and Accounts]]
- [[id:de3bef27-81c9-49d1-85bb-edceb9a80e65][SQL Joins]]
* Misc
- [[id:faa7f193-5af6-4a2f-a73e-540f833a7fd0][Windows Services]]
@@ -63,6 +65,10 @@ Some core programming concepts that are essential for software development. The
- [[id:e717c252-0e15-4403-898f-93163dd1b147][Dynamic Link Library (DLL)]]
* Security:
- [[id:01c89142-7e14-42b2-bd01-743656908fd2][Cross Site Scripting (XSS)]]
* Concepts to learn and talk about (cs and maths)
- +xor+
- +big O notation+

View File

@@ -4,6 +4,10 @@
#+title: XSS Pentesting Report Fix
#+filetags: :microlise:security:notes:
* Links:
- [[id:01c89142-7e14-42b2-bd01-743656908fd2][Cross Site Scripting (XSS)]]
* Metadata
- Name :: Understanding Session XSS

View File

@@ -6,7 +6,7 @@
#+AUTHOR: Auto-generated
#+filetags: :recipes:cooking:org:index:
#+STARTUP: content
#+PROPERTY: GENERATED_AT 2026-05-29T00:00:01.969551
#+PROPERTY: GENERATED_AT 2026-06-03T00:00:02.594221
# How to add notes: under any recipe heading, add :COOKED: <date> to record when you made it, and :TIPS: your tip text to add notes.
# Multiple :COOKED: lines are supported. These are preserved across regenerations.

View File

@@ -8,3 +8,24 @@
- https://www.reddit.com/r/decaf/comments/1j2fpj9/lots_of_ppl_unmotivated_off_caffeine_we_need_to/
- https://islamqa.org/hanafi/qibla-hanafi/35639/shared-ownership-schemes/
- https://www.islamicfinanceguru.com/articles/shared-equity-schemes-alternative-islamic-mortgages
* Interesting things to readup:
** https://en.wikipedia.org/wiki/2024_CrowdStrike-related_IT_outages
** https://fortune.com/2025/07/23/ai-coding-tool-replit-wiped-database-called-it-a-catastrophic-failure/
** https://www.noahzender.com/ideas#all-ideas
** https://shvetsm.github.io/posts/the-best-engineers-write-less-code/
** https://css-tricks.com/technical-writing-in-the-ai-age/
** https://www.elenaverna.com/p/youll-lose-your-job-in-202
** https://performance.dev/how-is-linear-so-fast-a-technical-breakdown
** https://www.sqlshack.com/debugging-stored-procedures-sql-server-management-studio-ssms/
** https://techcrunch.com/2026/05/27/tech-ceos-are-apparently-suffering-from-ai-psychosis/
** https://spin.atomicobject.com/using-agents-growth/
* Server resources:
** https://search.brave.com/search?q=docmost
* Repos
** https://github.com/0-AI-UG/cate

View File

@@ -92,6 +92,16 @@
"id": "648c487d-d218-404a-b427-d186dc4c265d",
"title": "2026-05-29",
"url": "/daily/2026-05-29.html"
},
{
"id": "8a813115-3d7f-4f37-80fd-e9a4465e4347",
"title": "2026-06-01",
"url": "/daily/2026-06-01.html"
},
{
"id": "3f42a3fb-d64c-4d0c-95bc-5ac747b14f65",
"title": "2026-06-02",
"url": "/daily/2026-06-02.html"
}
],
"A": [
@@ -273,6 +283,11 @@
"id": "22737796-6D31-49D5-84F2-F7BC73E45144",
"title": "Concepts",
"url": "/Career Concepts/20260101010049-concepts.html"
},
{
"id": "01c89142-7e14-42b2-bd01-743656908fd2",
"title": "Cross Site Scripting (XSS)",
"url": "/20260601093154-cross_site_scripting_xss.html"
}
],
"D": [
@@ -656,6 +671,11 @@
}
],
"N": [
{
"id": "a68223f1-5d80-4ee1-8676-1be9dc742d0e",
"title": "Naats",
"url": "/20260601134732-naats.html"
},
{
"id": "0880f089-a5ad-49cd-8ce3-f020a5941313",
"title": "Networking MOC",
@@ -775,7 +795,7 @@
{
"id": "3a8274ef-f2fe-486d-9caf-0c56982b9917",
"title": "RSS Articles",
"url": "/Misc/20260111204228-rss_articles.html"
"url": "/20260111204228-rss_articles.sync-conflict-20260530-154400-VT6366A.html"
}
],
"S": [
@@ -829,6 +849,11 @@
"title": "Solid Principles Examples",
"url": "/Career Concepts/20260101012731-solid_principles_examples.html"
},
{
"id": "de3bef27-81c9-49d1-85bb-edceb9a80e65",
"title": "SQL Joins",
"url": "/20260602101857-sql_joins.html"
},
{
"id": "acba0d25-08db-4784-8cc2-fe5c437ba723",
"title": "stanford_marshmallow_experiment",
@@ -1002,7 +1027,7 @@
{
"id": "b2ac09aa-e888-48d3-8357-2292f9b2526c",
"title": "XSS Pentesting Report Fix",
"url": "/20260527152602-microlise_xss_rom_pentest.html"
"url": "/Career Concepts/20260527152602-microlise_xss_rom_pentest.html"
}
]
}

View File

@@ -24,6 +24,8 @@ This file is auto-generated. Do not edit manually.
- [[id:5c89425f-2483-456f-8925-5a558d6a0868][2026-05-22]]
- [[id:99abd6e2-d9fc-423b-8930-c843f368557f][2026-05-28]]
- [[id:648c487d-d218-404a-b427-d186dc4c265d][2026-05-29]]
- [[id:8a813115-3d7f-4f37-80fd-e9a4465e4347][2026-06-01]]
- [[id:3f42a3fb-d64c-4d0c-95bc-5ac747b14f65][2026-06-02]]
* A
- [[id:effa2bf5-8b04-42d3-b9c1-17160dc4ddc2][Actual]]
@@ -65,6 +67,7 @@ This file is auto-generated. Do not edit manually.
- [[id:dd55d635-59de-4ed9-8ff0-423782c2e0ae][clean-code]]
- [[id:43afdfdd-e1fe-4e3a-a6f6-0d99b253fc8a][Cloudflare]]
- [[id:22737796-6D31-49D5-84F2-F7BC73E45144][Concepts]]
- [[id:01c89142-7e14-42b2-bd01-743656908fd2][Cross Site Scripting (XSS)]]
* D
- [[id:7b8ba1b0-a00e-4fc1-b75e-55ba0e2c3bb7][Daily Notes MOC]]
@@ -159,6 +162,7 @@ This file is auto-generated. Do not edit manually.
- [[id:fdb8fa52-0c9d-4332-9f32-53bc6fee24b9][MVP and MVT]]
* N
- [[id:a68223f1-5d80-4ee1-8676-1be9dc742d0e][Naats]]
- [[id:0880f089-a5ad-49cd-8ce3-f020a5941313][Networking MOC]]
- [[id:a087da71-bcfb-4ddf-9565-b82113d5d27f][neuroplasticity]]
- [[id:56f39be4-1108-4020-be5a-1f3a0fbf96fa][Nextcloud]]

View File

@@ -17,6 +17,7 @@ document.addEventListener('DOMContentLoaded', () => {
initMediaFixes(document);
initReadingProgress();
hookSearchResults();
initHomeDashboard();
});
/* =========================================================
@@ -93,14 +94,14 @@ function rearrangeDOM() {
function buildNoteToolbar(toolbar) {
toolbar.innerHTML = `
<button class="toolbar-btn" id="tb-back" title="Go back" aria-label="Go back">\u2190 back</button>
<button class="toolbar-btn" id="tb-forward" title="Go forward" aria-label="Go forward">forward \u2192</button>
<button class="toolbar-btn toolbar-btn-icon" id="tb-back" title="Go back" aria-label="Go back">\u2190</button>
<button class="toolbar-btn toolbar-btn-icon" id="tb-forward" title="Go forward" aria-label="Go forward">\u2192</button>
<div class="toolbar-sep"></div>
<button class="toolbar-btn" id="tb-toc" title="Toggle table of contents">contents</button>
<button class="toolbar-btn toolbar-btn-icon" id="tb-toc" title="Toggle table of contents" aria-label="Toggle table of contents">\u2261</button>
<button class="toolbar-btn toolbar-btn-icon" id="tb-backlinks" title="Toggle backlinks panel" aria-label="Toggle backlinks panel">\u29C9</button>
<div class="toolbar-spacer"></div>
<span class="note-graph-pos" id="note-graph-pos"></span>
<div class="toolbar-sep"></div>
<button class="toolbar-btn" id="tb-backlinks" title="Toggle backlinks panel">backlinks</button>
<button class="toolbar-btn toolbar-btn-icon" id="tb-close-all" title="Close all notes" aria-label="Close all notes">\u2715</button>
`;
}
@@ -108,19 +109,49 @@ function buildNoteToolbar(toolbar) {
SIDEBAR RAIL
========================================================= */
const RECENT_KEY = 'zg-recent-notes';
const RECENT_MAX = 12;
const FOLDER_STATE_KEY = 'zg-folder-state';
let sidebarDataCache = null;
async function fetchSidebarData() {
if (sidebarDataCache) return sidebarDataCache;
try {
const res = await fetch(`/assets/sidebar-tree.json?v=${Date.now()}`, { cache: 'no-store' });
if (res.ok) sidebarDataCache = await res.json();
} catch (_) {}
return sidebarDataCache || { mocs: [], tree: [] };
}
function closeSidebar() {
const sidebar = document.getElementById('sidebar');
if (!sidebar) return;
sidebar.classList.remove('expanded', 'mobile-open');
sidebar.dataset.activePanel = '';
sidebar.querySelectorAll('.rail-btn').forEach(b => b.classList.remove('active'));
document.getElementById('sidebar-backdrop')?.classList.remove('visible');
}
function initSidebarRail() {
const sidebar = document.getElementById('sidebar');
if (!sidebar) return;
const rail = el('div', { id: 'sidebar-rail', class: 'sidebar-rail' });
const body = el('div', { id: 'sidebar-body', class: 'sidebar-body' });
sidebar.append(rail, body);
const railBtns = [
{ label: '\u2630', title: 'All notes', id: 'rail-tree', panel: 'tree' },
{ label: '\u2B21', title: 'Atlas', id: 'rail-atlas', panel: 'atlas' },
{ label: '\u25F7', title: 'Recent', id: 'rail-recent', panel: 'recent' },
{ label: '\u2630', title: 'Browse', id: 'rail-browse', panel: 'browse' },
{ label: '\u2315', title: 'Search', id: 'rail-search', action: 'search' },
];
railBtns.forEach(({ label, title, id, panel, action }) => {
const btn = el('button', { class: 'rail-btn', id, title, 'aria-label': title });
btn.textContent = label;
sidebar.appendChild(btn);
rail.appendChild(btn);
btn.addEventListener('click', () => {
if (action === 'search') {
@@ -131,13 +162,12 @@ function initSidebarRail() {
}
return;
}
const isExpanded = sidebar.classList.contains('expanded');
const samePanel = sidebar.dataset.activePanel === panel;
if (isExpanded && samePanel) {
sidebar.classList.remove('expanded');
sidebar.dataset.activePanel = '';
btn.classList.remove('active');
closeSidebar();
} else {
sidebar.classList.add('expanded');
sidebar.dataset.activePanel = panel;
@@ -148,57 +178,142 @@ function initSidebarRail() {
});
});
// Mobile backdrop
let backdrop = document.getElementById('sidebar-backdrop');
if (!backdrop) {
backdrop = el('div', { id: 'sidebar-backdrop' });
document.body.appendChild(backdrop);
}
backdrop.addEventListener('click', () => {
sidebar.classList.remove('expanded', 'mobile-open');
backdrop.classList.remove('visible');
sidebar.querySelectorAll('.rail-btn').forEach(b => b.classList.remove('active'));
});
backdrop.addEventListener('click', closeSidebar);
initSidebarToggle();
}
function showSidebarPanel(panel) {
const sidebar = document.getElementById('sidebar');
sidebar.querySelectorAll('.sidebar-panel').forEach(p => p.remove());
const body = document.getElementById('sidebar-body');
if (!body) return;
body.innerHTML = '';
if (panel === 'tree') {
const panelEl = el('div', { class: 'sidebar-panel' });
const panelEl = el('div', { class: 'sidebar-panel', 'data-panel': panel });
panelEl.innerHTML = `
<div class="sidebar-header">
<span class="sidebar-header-label">all notes</span>
<span class="sidebar-header-label">${panel}</span>
<button class="sidebar-close-btn" aria-label="Close sidebar">\u00D7</button>
</div>
<div class="sidebar-panel-body" id="sidebar-panel-body"></div>
`;
body.appendChild(panelEl);
panelEl.querySelector('.sidebar-close-btn').addEventListener('click', closeSidebar);
const container = panelEl.querySelector('#sidebar-panel-body');
if (panel === 'atlas') buildAtlasPanel(container);
else if (panel === 'recent') buildRecentPanel(container);
else if (panel === 'browse') buildBrowsePanel(container);
}
async function buildAtlasPanel(container) {
const data = await fetchSidebarData();
const mocs = data.mocs || [];
if (!mocs.length) {
container.innerHTML = '<p class="sidebar-empty">No MOC hubs found.</p>';
return;
}
const grid = el('div', { class: 'moc-grid' });
mocs.forEach(moc => {
const card = el('button', {
class: 'moc-card' + (moc.pinned ? ' moc-card-pinned' : ''),
type: 'button',
});
card.innerHTML = `
<span class="moc-card-title">${escHtml(moc.title)}</span>
${moc.pinned ? '<span class="moc-card-badge">start</span>' : ''}
`;
card.addEventListener('click', () => {
closeSidebar();
openNote(normalisePathname(moc.url), moc.title);
});
grid.appendChild(card);
});
container.appendChild(grid);
}
function getRecentNotes() {
try {
return JSON.parse(localStorage.getItem(RECENT_KEY) || '[]');
} catch (_) {
return [];
}
}
function trackRecentNote(pathname, title) {
if (!pathname) return;
const entry = { pathname, title, visitedAt: Date.now() };
let recent = getRecentNotes().filter(r => r.pathname !== pathname);
recent.unshift(entry);
localStorage.setItem(RECENT_KEY, JSON.stringify(recent.slice(0, RECENT_MAX)));
const panel = document.querySelector('.sidebar-panel[data-panel="recent"] #sidebar-panel-body');
if (panel) buildRecentPanel(panel);
}
function formatRelativeTime(ts) {
const diff = Date.now() - ts;
const mins = Math.floor(diff / 60000);
if (mins < 1) return 'just now';
if (mins < 60) return `${mins}m ago`;
const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs}h ago`;
const days = Math.floor(hrs / 24);
if (days < 7) return `${days}d ago`;
return new Date(ts).toLocaleDateString();
}
function buildRecentPanel(container) {
const recent = getRecentNotes();
container.innerHTML = '';
if (!recent.length) {
container.innerHTML = '<p class="sidebar-empty">No notes visited yet.</p>';
return;
}
const list = el('div', { class: 'recent-list' });
recent.forEach(item => {
const row = el('button', { class: 'recent-item', type: 'button' });
row.innerHTML = `
<span class="recent-item-title">${escHtml(item.title)}</span>
<span class="recent-item-time">${formatRelativeTime(item.visitedAt)}</span>
`;
row.addEventListener('click', () => {
closeSidebar();
openNote(item.pathname, item.title);
});
list.appendChild(row);
});
container.appendChild(list);
}
function buildBrowsePanel(container) {
container.innerHTML = `
<div class="tree-filter-wrap">
<input type="search" class="tree-filter" placeholder="Filter notes\u2026" aria-label="Filter notes" />
</div>
<div id="file-tree"></div>
`;
sidebar.appendChild(panelEl);
panelEl.querySelector('.sidebar-close-btn').addEventListener('click', () => {
sidebar.classList.remove('expanded');
sidebar.dataset.activePanel = '';
sidebar.querySelectorAll('.rail-btn').forEach(b => b.classList.remove('active'));
});
const filterInput = container.querySelector('.tree-filter');
const treeRoot = container.querySelector('#file-tree');
buildFileTree(panelEl.querySelector('#file-tree'));
}
filterInput.addEventListener('input', () => filterTree(treeRoot, filterInput.value.trim()));
buildFileTree(treeRoot);
}
async function buildFileTree(container) {
const currentPath = location.pathname;
let treeData = null;
try {
const res = await fetch(`/assets/sidebar-tree.json?v=${Date.now()}`, { cache: 'no-store' });
if (res.ok) {
const json = await res.json();
if (Array.isArray(json.tree)) treeData = json.tree;
}
} catch (_) {}
const data = await fetchSidebarData();
let treeData = Array.isArray(data.tree) ? data.tree : null;
if (treeData) {
renderTree(treeData, container, currentPath);
@@ -207,45 +322,120 @@ async function buildFileTree(container) {
const groups = await buildGroupsFromSearchIndex();
if (!groups?.length) {
const empty = el('div', { class: 'tree-file' });
empty.style.cssText = 'opacity:.4;font-size:.7rem;padding:8px 14px;';
empty.textContent = 'no notes found';
container.appendChild(empty);
container.innerHTML = '<p class="sidebar-empty">No notes found.</p>';
return;
}
const fallbackTree = groups.map(g => ({ label: g.label, children: [], files: g.files }));
renderTree(fallbackTree, container, currentPath);
}
function renderTree(nodes, container, currentPath) {
function getFolderState() {
try {
return JSON.parse(localStorage.getItem(FOLDER_STATE_KEY) || '{}');
} catch (_) {
return {};
}
}
function setFolderState(key, isOpen) {
const state = getFolderState();
state[key] = isOpen;
localStorage.setItem(FOLDER_STATE_KEY, JSON.stringify(state));
}
function countFilesInNode(node) {
let count = (node.files || []).length;
for (const child of node.children || []) {
count += countFilesInNode(child);
}
return count;
}
function nodeContainsPath(node, pathname, prefix = '') {
const folderKey = prefix ? `${prefix}/${node.label}` : node.label;
for (const f of node.files || []) {
if (normalisePathname(f.url) === pathname) return true;
}
for (const child of node.children || []) {
if (nodeContainsPath(child, pathname, folderKey)) return true;
}
return false;
}
function renderTree(nodes, container, currentPath, prefix = '') {
nodes.forEach(node => {
const folderEl = el('div', { class: 'tree-folder', 'data-folder': node.label });
const folderKey = prefix ? `${prefix}/${node.label}` : node.label;
const fileCount = countFilesInNode(node);
const containsActive = nodeContainsPath(node, currentPath, prefix);
const folderState = getFolderState();
const defaultOpen = containsActive || folderState[folderKey] === true;
const isOpen = defaultOpen;
const folderEl = el('div', {
class: 'tree-folder',
'data-folder': node.label,
'data-folder-key': folderKey,
});
const icon = el('span', { class: 'tree-icon' });
icon.textContent = '\u25BE ';
icon.textContent = isOpen ? '\u25BE ' : '\u25B8 ';
const labelSpan = el('span', { class: 'tree-folder-label' });
labelSpan.textContent = node.label;
folderEl.append(icon, labelSpan);
const badge = el('span', { class: 'tree-folder-count' });
badge.textContent = String(fileCount);
folderEl.append(icon, labelSpan, badge);
const childrenWrap = el('div', { class: 'tree-children' });
if (!isOpen) childrenWrap.style.display = 'none';
const searchTerms = [node.label.toLowerCase()];
(node.files || []).forEach(f => {
const pathname = normalisePathname(f.url);
searchTerms.push(f.title.toLowerCase());
childrenWrap.appendChild(makeFileItem({ title: f.title, pathname }, currentPath));
});
if (node.children?.length) renderTree(node.children, childrenWrap, currentPath);
if (node.children?.length) {
renderTree(node.children, childrenWrap, currentPath, folderKey);
}
folderEl.addEventListener('click', () => {
const isOpen = childrenWrap.style.display !== 'none';
childrenWrap.style.display = isOpen ? 'none' : '';
icon.textContent = isOpen ? '\u25B8 ' : '\u25BE ';
const nowOpen = childrenWrap.style.display === 'none';
childrenWrap.style.display = nowOpen ? '' : 'none';
icon.textContent = nowOpen ? '\u25BE ' : '\u25B8 ';
setFolderState(folderKey, nowOpen);
});
folderEl.dataset.searchText = searchTerms.join(' ');
childrenWrap.dataset.searchText = searchTerms.join(' ');
container.appendChild(folderEl);
container.appendChild(childrenWrap);
});
}
function filterTree(container, query) {
const q = query.toLowerCase();
container.querySelectorAll('.tree-folder, .tree-children, .tree-file').forEach(el => {
if (!q) {
el.classList.remove('tree-filtered-out');
return;
}
const text = (el.dataset.searchText || el.textContent || '').toLowerCase();
el.classList.toggle('tree-filtered-out', !text.includes(q));
});
if (q) {
container.querySelectorAll('.tree-children').forEach(wrap => {
wrap.style.display = '';
const folder = wrap.previousElementSibling;
if (folder?.classList.contains('tree-folder')) {
const icon = folder.querySelector('.tree-icon');
if (icon) icon.textContent = '\u25BE ';
}
});
}
}
async function buildGroupsFromSearchIndex() {
try {
const res = await fetch('/search-index.json');
@@ -267,14 +457,17 @@ function makeFileItem({ title, pathname }, currentPath) {
const fileEl = el('div', {
class: 'tree-file' + (pathname === currentPath ? ' active' : ''),
'data-pathname': pathname,
'data-search-text': title.toLowerCase(),
});
const iconSpan = el('span', { class: 'tree-icon' });
iconSpan.textContent = '\u00B7 ';
const titleSpan = el('span', { class: 'tree-file-title' });
titleSpan.textContent = title;
fileEl.appendChild(iconSpan);
fileEl.appendChild(document.createTextNode(title));
fileEl.appendChild(titleSpan);
fileEl.title = title;
fileEl.addEventListener('click', () => {
document.getElementById('sidebar')?.classList.remove('expanded');
closeSidebar();
openNote(pathname, title);
});
return fileEl;
@@ -378,6 +571,7 @@ history.replaceState(
document.getElementById('tb-forward')?.addEventListener('click', goForward);
document.getElementById('tb-backlinks')?.addEventListener('click', toggleBacklinkPanel);
document.getElementById('tb-toc')?.addEventListener('click', toggleTOC);
document.getElementById('tb-close-all')?.addEventListener('click', closeAllNotes);
// Backlink panel close
document.getElementById('bp-close')?.addEventListener('click', () => {
@@ -548,6 +742,11 @@ function activateNote(id, skipBrowserWrite = true) {
const area = document.getElementById('content-area');
if (area) area.scrollTop = 0;
const current = noteHistory[historyIndex];
if (current) {
trackRecentNote(current.pathname, current.label);
}
updateBreadcrumb();
updateToolbar();
@@ -567,6 +766,18 @@ function goForward() {
window.history.forward();
}
function closeAllNotes() {
if (noteHistory.length <= 1) return;
const root = noteHistory[0];
noteHistory.slice(1).forEach(n => {
if (!n.isRoot) n.paneEl.remove();
});
noteHistory.splice(1);
historyIndex = 0;
activateNote(root.id, true);
history.replaceState(makeHistoryState(root), '', historyUrlFor(root));
}
function updateBreadcrumb() {
const trail = document.getElementById('breadcrumb-trail');
if (!trail) return;
@@ -934,9 +1145,9 @@ function initSidebarToggle() {
e.preventDefault();
const nowExpanded = sidebar.classList.toggle('expanded');
if (nowExpanded && !sidebar.dataset.activePanel) {
sidebar.dataset.activePanel = 'tree';
showSidebarPanel('tree');
sidebar.querySelector('#rail-tree')?.classList.add('active');
sidebar.dataset.activePanel = 'atlas';
showSidebarPanel('atlas');
sidebar.querySelector('#rail-atlas')?.classList.add('active');
}
if (!nowExpanded) {
sidebar.dataset.activePanel = '';
@@ -1285,6 +1496,51 @@ function normalisePathname(p) {
// Lazy sidebar builder stub (built on demand)
async function buildSidebar() {}
async function initHomeDashboard() {
const pathname = normalisePathname(location.pathname);
if (pathname !== '/' && !pathname.endsWith('/index.html')) return;
const content = document.querySelector('#content');
if (!content || content.querySelector('.home-dashboard')) return;
const data = await fetchSidebarData();
const mocs = data.mocs || [];
if (!mocs.length) return;
const dash = el('div', { class: 'home-dashboard' });
dash.innerHTML = `
<header class="home-hero">
<h2 class="home-hero-title">Zettelgarten</h2>
<p class="home-hero-sub">A private library of notes, maps, and wandering thoughts.</p>
</header>
<div class="home-quick-links">
<button type="button" class="home-quick-link" data-action="search">Search <kbd>/</kbd></button>
<a class="home-quick-link" href="/all-files.html">All files</a>
</div>
<h3 class="home-section-label">Start here</h3>
<div class="home-moc-grid"></div>
`;
const grid = dash.querySelector('.home-moc-grid');
mocs.forEach(moc => {
const card = el('a', {
class: 'moc-card home-moc-card' + (moc.pinned ? ' moc-card-pinned' : ''),
href: moc.url,
});
card.innerHTML = `
<span class="moc-card-title">${escHtml(moc.title)}</span>
${moc.pinned ? '<span class="moc-card-badge">start</span>' : ''}
`;
grid.appendChild(card);
});
dash.querySelector('[data-action="search"]')?.addEventListener('click', () => {
window.openSearchModal?.();
});
content.insertBefore(dash, content.firstChild);
}
// Pane-out animation keyframe
const _s = document.createElement('style');
_s.textContent = `

View File

@@ -88,14 +88,12 @@
========================================================= */
@media (max-width: 900px) {
#sidebar {
width: 0;
border-right: none;
overflow: hidden;
#sidebar:not(.expanded):not(.mobile-open) {
width: var(--rail-w);
}
.fullwidth {
max-width: calc(100vw - 2rem);
max-width: calc(100vw - var(--rail-w) - 2rem);
}
}
@@ -172,22 +170,22 @@
position: fixed;
top: 0;
left: 0;
width: 80vw;
max-width: 300px;
width: min(92vw, var(--sidebar-w));
height: 100dvh;
z-index: 400;
border-right: 1px solid var(--border-mid);
overflow-y: auto;
overflow-x: hidden;
overflow: hidden;
transform: translateX(-100%);
transition: transform var(--dur-mid) var(--ease-out),
box-shadow var(--dur-mid) var(--ease-out);
box-shadow: none;
}
#sidebar.mobile-open {
#sidebar.mobile-open,
#sidebar.expanded {
transform: translateX(0);
box-shadow: 4px 0 32px rgba(0, 0, 0, 0.5);
width: min(92vw, var(--sidebar-w)) !important;
}
#sidebar-backdrop {
@@ -218,12 +216,7 @@
#content-area {
overflow: visible;
height: auto;
min-height: calc(100dvh - var(--header-h) - var(--tab-h));
background-attachment: scroll;
}
#content-area::before {
display: none;
min-height: calc(100dvh - var(--header-h) - var(--toolbar-h));
}
/* ---- Note content ---- */

View File

@@ -2,7 +2,7 @@
ZETTELKASTEN GARDEN style.css
========================================================= */
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,500;0,600;1,500&family=DM+Mono:wght@400;500&family=Inter:wght@400;500;600&family=Noto+Sans+Arabic:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,500;0,600;1,500&family=DM+Mono:wght@400;500&family=Spectral:ital,wght@0,300;0,400;0,500;1,300;1,400&family=Noto+Sans+Arabic:wght@400;700&display=swap');
/* =========================================================
TOKENS
@@ -10,25 +10,28 @@
:root {
--header-h: 48px;
--toolbar-h: 36px;
--rail-w: 52px;
--panel-w: 260px;
--content-max: none;
--content-pad: clamp(1.5rem, 4vw, 4.5rem);
--toolbar-h: 32px;
--rail-w: 48px;
--panel-w: 280px;
--sidebar-w: calc(var(--rail-w) + var(--panel-w));
--content-max: min(72rem, calc(100vw - var(--rail-w) - 2.5rem));
--content-pad: clamp(1.25rem, 2.5vw, 2.25rem);
/* Dark academia: ink, vellum, forest green, oxblood, and aged brass */
--bg: #11100c;
--page-bg: #090907;
--pane-bg: #15130f;
--surface: #1e1a14;
--surface-2: #29231a;
/* Scriptorium: ink, vellum, oxblood, antique gold */
--bg: #0f0e0b;
--page-bg: #070605;
--pane-bg: #0a0908;
--reading-surface: #12100d;
--surface: #1a1712;
--surface-2: #242019;
--fg: #e8dfcd;
--fg-dim: #b5a68f;
--fg-faint: #766a58;
--fg-ghost: #43382b;
--accent: #b68b45;
--accent: #c4a35a;
--accent-2: #6b2d3c;
--accent-dim: color-mix(in oklab, var(--accent) 35%, transparent);
--accent-sub: color-mix(in oklab, var(--accent) 10%, transparent);
--accent-glow:color-mix(in oklab, var(--accent) 6%, transparent);
@@ -38,13 +41,16 @@
--heading-3: #a8604f;
--link: #c7a763;
--link-hover:#f1e2c2;
--tag-bg: #251f16;
--tag-bg: #1f1a14;
--tag-fg: var(--accent);
--border: #332c20;
--border: #2e2820;
--border-2: #4a3d29;
--code-bg: #0c0c09;
--border-mid: #3a3228;
--chip-bg: var(--surface);
--code-bg: #080807;
--font-body: 'Inter', 'Noto Sans Arabic', system-ui, sans-serif;
--font-body: 'Spectral', 'Noto Sans Arabic', Georgia, serif;
--font-ui: system-ui, -apple-system, sans-serif;
--font-mono: 'DM Mono', 'Fira Mono', monospace;
--font-serif: 'Cormorant Garamond', Georgia, serif;
@@ -58,24 +64,28 @@
--r4:1rem; --r5:1.5rem; --r6:2rem; --r8:3rem;
}
/* Light theme */
/* Light theme — daylight reading room */
[data-theme="light"] {
--bg: #f3eee3;
--page-bg: #e9e0cf;
--pane-bg: #f7f1e6;
--bg: #f5f0e6;
--page-bg: #ebe4d6;
--pane-bg: #f0ebe1;
--reading-surface: #f7f1e6;
--surface: #efe5d4;
--surface-2: #e3d5be;
--fg: #211c15;
--fg-dim: #625744;
--fg-faint: #998b72;
--fg-ghost: #d2c3a8;
--accent: #80602f;
--fg: #1c1812;
--fg-dim: #5c5040;
--fg-faint: #8a7d68;
--fg-ghost: #c8baa3;
--accent: #8a6b32;
--accent-2: #7e3a45;
--border: #d4c4a7;
--border-2: #b9a57f;
--border-mid:#c4b08e;
--chip-bg: #e8dcc8;
--code-bg: #e8dcc8;
--tag-bg: #dfd0b5;
--link: #684f28;
--heading: #211c15;
--heading: #1c1812;
--heading-2: #5b4929;
--heading-3: #7e4438;
}
@@ -130,11 +140,32 @@ body, p, li, td, th, span, div {
.banner-logo {
height: 26px; width: 26px;
border-radius: 50%; object-fit: cover;
opacity: 0.85; flex-shrink: 0;
opacity: 0.9; flex-shrink: 0;
transition: opacity var(--dur-fast);
}
.banner-logo:hover { opacity: 1; }
.banner-brand {
display: inline-flex;
align-items: center;
gap: 0.55rem;
color: var(--heading);
text-decoration: none;
border: none;
padding: 0;
flex-shrink: 0;
}
.banner-brand:hover {
color: var(--accent);
background: transparent;
}
.banner-title {
font-family: var(--font-serif);
font-size: 1.15rem;
font-weight: 500;
letter-spacing: 0.02em;
}
/* Breadcrumb trail */
#breadcrumb-trail {
display: flex;
@@ -191,7 +222,7 @@ body, p, li, td, th, span, div {
height: 30px;
padding: 0 0.45rem 0 0.7rem;
font-size: 0.78rem;
font-family: var(--font-body);
font-family: var(--font-ui);
color: var(--fg-dim);
background: var(--surface);
border: 1px solid var(--border);
@@ -390,14 +421,13 @@ body, p, li, td, th, span, div {
width: var(--rail-w);
flex-shrink: 0;
background:
linear-gradient(90deg, color-mix(in oklab, #233327 18%, transparent), transparent 78%),
linear-gradient(180deg, color-mix(in oklab, var(--accent-2) 8%, transparent), transparent 40%),
linear-gradient(90deg, color-mix(in oklab, #1a2e1f 22%, transparent), transparent 90%),
var(--bg);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
align-items: center;
padding: var(--r3) 0;
gap: var(--r2);
flex-direction: row;
align-items: stretch;
overflow: hidden;
transition: width var(--dur-mid) var(--ease-out);
z-index: 100;
@@ -405,29 +435,60 @@ body, p, li, td, th, span, div {
}
#sidebar.expanded {
width: var(--panel-w);
align-items: flex-start;
overflow-y: auto;
width: var(--sidebar-w);
}
.sidebar-rail {
width: var(--rail-w);
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
padding: var(--r3) 0;
gap: var(--r2);
border-right: 1px solid color-mix(in oklab, var(--accent) 12%, var(--border));
background: color-mix(in oklab, var(--surface) 40%, transparent);
}
.sidebar-body {
display: none;
flex: 1;
min-width: 0;
overflow: hidden;
}
#sidebar.expanded .sidebar-body {
display: flex;
flex-direction: column;
}
/* Rail icon buttons */
.rail-btn {
width: 36px; height: 36px;
border-radius: 8px;
width: 34px; height: 34px;
border-radius: 6px;
background: transparent;
border: none;
border: 1px solid transparent;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--fg-faint);
font-size: 1.05rem;
font-size: 1rem;
flex-shrink: 0;
transition: color var(--dur-fast), background var(--dur-fast);
transition: color var(--dur-fast), background var(--dur-fast), border-color var(--dur-fast), box-shadow var(--dur-fast);
position: relative;
}
.rail-btn:hover { color: var(--fg); background: var(--accent-sub); }
.rail-btn.active { color: var(--accent); background: var(--accent-glow); }
.rail-btn:hover {
color: var(--fg);
background: var(--accent-sub);
border-color: color-mix(in oklab, var(--accent) 20%, transparent);
}
.rail-btn.active {
color: var(--accent);
background: var(--accent-glow);
border-color: var(--accent-dim);
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent) 15%, transparent);
}
.rail-btn[title]:not(:disabled):hover::after {
content: attr(title);
@@ -447,8 +508,31 @@ body, p, li, td, th, span, div {
z-index: 500;
}
.sidebar-panel { display: none; width: 100%; flex-direction: column; }
#sidebar.expanded .sidebar-panel { display: flex; }
.sidebar-panel {
display: flex;
width: 100%;
flex: 1;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.sidebar-panel-body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding-bottom: 12px;
scrollbar-width: thin;
scrollbar-color: var(--border-2) transparent;
}
.sidebar-empty {
padding: 1.25rem 14px;
color: var(--fg-faint);
font-size: 0.78rem;
font-style: italic;
text-align: center;
}
.sidebar-header {
display: flex;
@@ -481,7 +565,133 @@ body, p, li, td, th, span, div {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding-bottom: 12px;
padding-bottom: 4px;
}
.tree-filter-wrap {
padding: 6px 10px 8px;
flex-shrink: 0;
}
.tree-filter {
width: 100%;
padding: 5px 8px;
font-size: 0.75rem;
font-family: var(--font-ui);
color: var(--fg);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
outline: none;
transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.tree-filter:focus {
border-color: var(--accent-dim);
box-shadow: 0 0 0 2px var(--accent-sub);
}
.tree-filter::placeholder { color: var(--fg-faint); }
.tree-filtered-out { display: none !important; }
/* MOC cards (Atlas + home) */
.moc-grid,
.home-moc-grid {
display: flex;
flex-direction: column;
gap: 6px;
padding: 4px 8px 8px;
}
.moc-card {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
padding: 8px 10px;
text-align: left;
background: var(--surface);
border: 1px solid var(--border);
border-left: 2px solid color-mix(in oklab, var(--accent) 50%, transparent);
border-radius: 4px;
cursor: pointer;
color: var(--fg);
text-decoration: none;
transition: transform var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast), box-shadow var(--dur-fast);
}
.moc-card:hover {
background: var(--surface-2);
border-color: var(--accent-dim);
border-left-color: var(--accent);
transform: translateY(-1px);
box-shadow: 0 4px 16px color-mix(in oklab, var(--accent) 8%, transparent);
}
.moc-card-pinned {
border-left-color: var(--accent);
background: color-mix(in oklab, var(--accent) 5%, var(--surface));
}
.moc-card-title {
font-family: var(--font-serif);
font-size: 0.88rem;
font-weight: 500;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.moc-card-badge {
flex-shrink: 0;
font-family: var(--font-mono);
font-size: 0.55rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent);
padding: 2px 5px;
border: 1px solid var(--accent-dim);
border-radius: 3px;
}
/* Recent list */
.recent-list {
display: flex;
flex-direction: column;
gap: 2px;
padding: 4px 6px;
}
.recent-item {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
width: 100%;
padding: 7px 10px;
text-align: left;
background: transparent;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
color: var(--fg-dim);
transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.recent-item:hover {
background: var(--accent-sub);
color: var(--fg);
border-color: color-mix(in oklab, var(--accent) 15%, transparent);
}
.recent-item-title {
font-size: 0.78rem;
line-height: 1.35;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
.recent-item-time {
font-family: var(--font-mono);
font-size: 0.62rem;
color: var(--fg-faint);
letter-spacing: 0.02em;
}
/* Firefox */
@@ -512,7 +722,7 @@ body, p, li, td, th, span, div {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 10px 4px 12px;
padding: 4px 8px 4px 10px;
font-size: 0.68rem;
cursor: pointer;
color: var(--fg-faint);
@@ -520,32 +730,43 @@ body, p, li, td, th, span, div {
margin: 2px 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
font-weight: 500;
font-family: var(--font-mono);
letter-spacing: 0.06em;
letter-spacing: 0.04em;
text-transform: uppercase;
transition: background var(--dur-fast), color var(--dur-fast);
}
.tree-folder:hover { background: var(--accent-sub); color: var(--fg); }
.tree-folder-count {
margin-left: auto;
font-size: 0.58rem;
color: var(--fg-ghost);
flex-shrink: 0;
padding-left: 4px;
}
.tree-file {
/* display: flex; */
display: flex;
align-items: center;
gap: 6px;
padding: 3px 10px 3px 12px;
padding: 3px 8px 3px 10px;
font-size: 0.76rem;
cursor: pointer;
color: var(--fg-dim);
border-radius: 3px;
margin: 1px 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
transition: background var(--dur-fast), color var(--dur-fast);
}
.tree-file-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.tree-file:hover { background: var(--accent-sub); color: var(--fg); }
.tree-file.active {
background: color-mix(in oklab, var(--accent) 14%, transparent);
@@ -578,10 +799,10 @@ body, p, li, td, th, span, div {
#note-toolbar {
display: flex;
align-items: center;
gap: var(--r2);
padding: 4px var(--r4);
gap: var(--r1);
padding: 2px var(--r3);
height: var(--toolbar-h);
background: color-mix(in oklab, var(--bg) 92%, #233327 8%);
background: transparent;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
font-size: 0.75rem;
@@ -591,6 +812,7 @@ body, p, li, td, th, span, div {
.toolbar-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
padding: 3px 8px;
border-radius: 4px;
@@ -603,6 +825,12 @@ body, p, li, td, th, span, div {
transition: all var(--dur-fast);
white-space: nowrap;
}
.toolbar-btn-icon {
width: 26px;
height: 26px;
padding: 0;
font-size: 0.85rem;
}
.toolbar-btn:hover:not(:disabled) {
border-color: var(--border-2);
color: var(--fg);
@@ -623,10 +851,11 @@ body, p, li, td, th, span, div {
.toolbar-spacer { flex: 1; }
.note-graph-pos {
color: var(--fg-faint);
font-size: 0.68rem;
letter-spacing: 0.03em;
max-width: 200px;
color: var(--fg-ghost);
font-size: 0.62rem;
letter-spacing: 0.04em;
font-style: italic;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -647,36 +876,45 @@ body, p, li, td, th, span, div {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
background-color: var(--pane-bg);
background-image:
linear-gradient(180deg, color-mix(in oklab, #233327 12%, transparent), transparent 22rem),
linear-gradient(90deg, color-mix(in oklab, var(--accent) 7%, transparent) 0 1px, transparent 1px),
repeating-linear-gradient(
180deg,
transparent, transparent 27px,
color-mix(in oklab, var(--fg) 2%, transparent) 27px,
color-mix(in oklab, var(--fg) 2%, transparent) 28px
);
background:
radial-gradient(ellipse 80% 60% at 50% 0%, color-mix(in oklab, var(--accent) 4%, transparent), transparent 70%),
var(--pane-bg);
position: relative;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
background-attachment: local;
}
#content-area::-webkit-scrollbar { width: 5px; }
#content-area::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
#content-area::before {
content: '';
position: absolute;
top: 0; left: 52px; width: 1px; height: 100%;
background: color-mix(in oklab, var(--accent) 8%, transparent);
pointer-events: none;
z-index: 0;
.tab-content {
display: none;
min-height: 100%;
position: relative;
z-index: 1;
padding: var(--r4) var(--r2) var(--r6);
}
#sidebar.expanded ~ #main .tab-content,
body:has(#sidebar.expanded) .tab-content {
--content-max: min(68rem, calc(100vw - var(--sidebar-w) - 2rem));
}
.tab-content { display: none; min-height: 100%; position: relative; z-index: 1; }
.tab-content.active { display: block; }
.tab-content > #content,
.tab-content > .tab-content-inner {
margin: 0 auto;
width: 100%;
max-width: var(--content-max);
padding: var(--r6) var(--content-pad) calc(var(--r8) * 1.5);
background: var(--reading-surface);
border: 1px solid var(--border);
border-top: 1px solid color-mix(in oklab, var(--accent) 25%, var(--border));
border-radius: 2px;
box-shadow:
0 1px 0 color-mix(in oklab, var(--accent) 8%, transparent),
0 24px 64px color-mix(in oklab, #000 35%, transparent);
}
/* =========================================================
NOTE CONTENT
========================================================= */
@@ -685,7 +923,89 @@ body, p, li, td, th, span, div {
margin: 0 auto;
width: 100%;
max-width: var(--content-max);
padding: var(--r6) var(--content-pad) calc(var(--r8) * 2.5);
}
/* =========================================================
HOME DASHBOARD
========================================================= */
.home-dashboard {
margin-bottom: var(--r8);
padding-bottom: var(--r6);
border-bottom: 1px solid var(--border);
}
.home-hero-title {
font-family: var(--font-serif);
font-size: 2rem;
font-weight: 500;
color: var(--heading);
margin: 0 0 var(--r2);
letter-spacing: 0.02em;
}
.home-hero-sub {
margin: 0 0 var(--r5);
color: var(--fg-dim);
font-size: 1rem;
font-style: italic;
max-width: 36ch;
}
.home-quick-links {
display: flex;
flex-wrap: wrap;
gap: var(--r2);
margin-bottom: var(--r6);
}
.home-quick-link {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 5px 12px;
font-size: 0.75rem;
font-family: var(--font-mono);
color: var(--fg-dim);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: border-color var(--dur-fast), color var(--dur-fast), background var(--dur-fast);
}
.home-quick-link:hover {
color: var(--accent);
border-color: var(--accent-dim);
background: var(--accent-glow);
}
.home-quick-link kbd {
font-size: 0.65rem;
padding: 1px 4px;
border: 1px solid var(--border-2);
border-radius: 3px;
color: var(--fg-faint);
}
.home-section-label {
font-family: var(--font-mono);
font-size: 0.62rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--fg-faint);
margin: 0 0 var(--r3);
font-weight: 500;
}
.home-moc-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
gap: 8px;
padding: 0;
}
.home-moc-card {
text-decoration: none;
}
/* =========================================================
@@ -1076,8 +1396,8 @@ li ul li::before { content: "\00B7"; font-weight: normal; color: var(--fg-faint)
blockquote {
margin: var(--r5) 0;
padding: var(--r4) var(--r5);
border-left: 2px solid var(--accent);
background: var(--accent-glow);
border-left: 3px solid var(--accent-2);
background: color-mix(in oklab, var(--accent-2) 6%, transparent);
border-radius: 0 6px 6px 0;
color: var(--fg-dim);
font-family: var(--font-serif);
@@ -1383,15 +1703,16 @@ pre.src {
========================================================= */
#postamble, footer {
color: var(--fg-faint);
color: var(--fg-ghost);
padding: var(--r2) var(--r5);
border-top: 1px solid var(--border);
background: var(--bg);
border-top: 1px solid color-mix(in oklab, var(--border) 60%, transparent);
background: var(--page-bg);
text-align: center;
font-size: 0.68rem;
font-size: 0.62rem;
font-family: var(--font-mono);
letter-spacing: 0.03em;
flex-shrink: 0;
opacity: 0.7;
}
#postamble a, footer a { color: var(--fg-faint); }
#postamble a:hover, footer a:hover { color: var(--fg); }
@@ -1433,7 +1754,7 @@ pre.src {
@media (max-width: 768px) {
:root {
--rail-w: 0px;
--rail-w: 48px;
--content-pad: 1rem;
}
#sidebar {
@@ -1445,14 +1766,26 @@ pre.src {
width: 0;
}
#sidebar.mobile-open,
#sidebar.expanded { width: var(--panel-w) !important; }
#sidebar.expanded { width: min(92vw, var(--sidebar-w)) !important; }
#backlink-panel.open { width: 200px; }
#breadcrumb-trail { display: none; }
#note-toolbar { flex-wrap: wrap; height: auto; }
#note-toolbar { flex-wrap: wrap; height: auto; min-height: var(--toolbar-h); }
/* Code blocks on mobile */
.tab-content {
padding: var(--r3) var(--r2) var(--r6);
}
.tab-content > #content,
.tab-content > .tab-content-inner {
padding: var(--r4) var(--content-pad) var(--r6);
box-shadow: 0 8px 32px color-mix(in oklab, #000 25%, transparent);
}
.home-moc-grid {
grid-template-columns: 1fr;
}
.org-src-container > pre,
pre.src {
font-size: 0.75rem;

View File

@@ -3,9 +3,16 @@
:END:
#+title: 2026-05-29
* <2026-05-29 Fri> TODO
** TODO [#A] Replicate the PDEF and document it
** TODO [#B] Investigate the fix for the PDEF
** TODO [#C] A few more test cases for the XSS report
** TODO [#A] Training
** TODO Read 20 pages
** TODO Class
** DONE [#A] Replicate the PDEF and document it
** DONE [#B] Investigate the fix for the PDEF
** [#C] A few more test cases for the XSS report
** DONE [#A] Training
** DONE [#B] Record the demo showcasing the triage results
** DONE [#B] Complete follows data... competency
** DONE Read 20 pages
** DONE Class
* https://github.com/0-AI-UG/cate
* https://performance.dev/how-is-linear-so-fast-a-technical-breakdown
* https://www.sqlshack.com/debugging-stored-procedures-sql-server-management-studio-ssms/
* https://techcrunch.com/2026/05/27/tech-ceos-are-apparently-suffering-from-ai-psychosis/
* https://spin.atomicobject.com/using-agents-growth/

11
daily/2026-06-01.org Executable file
View File

@@ -0,0 +1,11 @@
:PROPERTIES:
:ID: 8a813115-3d7f-4f37-80fd-e9a4465e4347
:END:
#+title: 2026-06-01
* <2026-06-01 Mon> TODO:
** DONE Finalise and finish the PDEF
** DONE Continue working on the XSS Rom issue (try documenting a fix)
** DONE Hifz class
** Read 5 pages
** DONE Class

10
daily/2026-06-02.org Executable file
View File

@@ -0,0 +1,10 @@
:PROPERTIES:
:ID: 3f42a3fb-d64c-4d0c-95bc-5ac747b14f65
:END:
#+title: 2026-06-02
* <2026-06-02 Tue> TODO:
** TODO Work on fix for the xss issue
** TODO Try finding the tests associated to the 2 files needing to be changed for XSS
** TODO Hifz class
** TODO Read 5 pages
** TODO Class

View File

@@ -1,39 +1,57 @@
"""
generate_sidebar_tree.py
Generates sidebar-tree.json from your org-roam directory.
Groups notes by #+category: property.
Notes without a category go into "uncategorised".
Generates sidebar-tree.json from the org-roam directory.
Output shape:
{
"groups": [
{
"label": "java",
"files": [
{ "title": "Java MOC", "url": "/20250402185735-java_moc.html" },
"mocs": [
{ "title": "Brain MOC", "url": "/20241210233721-brain_moc.html", "pinned": true },
...
]
],
"tree": [
{
"label": "Articles",
"children": [],
"files": [{ "title": "...", "url": "/Articles/....html" }]
},
...
{
"label": "uncategorised",
"files": [ ... ]
}
]
],
"exclude": [".packages"]
}
"""
import json
import re
from pathlib import Path
from collections import defaultdict
EXCLUDE_DIRS = {".packages", ".git", ".cursor"}
EXCLUDE_FILES = {"index.org", "all-files.org"}
MOC_PINNED_TITLES = {"brain moc"}
PINNED_URL_SUFFIX = "brain_moc.html"
def should_skip(file_path: Path, org_dir: Path) -> bool:
"""Return True if this org file should be excluded from the sidebar."""
try:
relative = file_path.relative_to(org_dir)
except ValueError:
return True
if file_path.name in EXCLUDE_FILES:
return True
for part in relative.parts[:-1]:
if part in EXCLUDE_DIRS or part.startswith("."):
return True
return False
def get_org_metadata(file_path: Path) -> dict:
"""Extract title, category (and optionally id) from an Org file."""
"""Extract title, filetags, and id from an Org file."""
title_pattern = re.compile(r"^#\+title:\s*(.+)", re.IGNORECASE | re.MULTILINE)
category_pattern = re.compile(r"^#\+category:\s*(.+)", re.IGNORECASE | re.MULTILINE)
tags_pattern = re.compile(r"^#\+filetags:\s*(.+)", re.IGNORECASE | re.MULTILINE)
id_pattern = re.compile(r"^:ID:\s*(.+)", re.MULTILINE)
try:
@@ -42,90 +60,53 @@ def get_org_metadata(file_path: Path) -> dict:
return {}
title_match = title_pattern.search(content)
category_match = category_pattern.search(content)
tags_match = tags_pattern.search(content)
id_match = id_pattern.search(content)
tags_raw = tags_match.group(1).strip() if tags_match else ""
tags = {t.strip(":").lower() for t in re.findall(r":[^:\s]+:", tags_raw)}
return {
"title": title_match.group(1).strip() if title_match else "Untitled",
"category": category_match.group(1).strip() if category_match else None,
"tags": tags,
"id": id_match.group(1).strip() if id_match else None,
}
def generate_sidebar_tree(org_dir: Path, out_file: Path) -> None:
"""Generate sidebar-tree.json grouped by #+category:."""
print(" Generating sidebar-tree.json...")
def is_moc(meta: dict) -> bool:
return "moc" in meta.get("tags", set())
org_dir = org_dir.expanduser().resolve()
out_file = out_file.expanduser()
# Collect all .org files, skip lock/temp files
org_files = [
f for f in org_dir.rglob("*.org")
if not re.search(r"/\.#[^/]+\.org$", str(f))
]
def build_mocs(org_dir: Path) -> list[dict]:
"""Collect MOC notes tagged with :moc:."""
mocs = []
groups: dict[str, list[dict]] = defaultdict(list)
for file_path in org_files:
meta = get_org_metadata(file_path)
if not meta:
for file_path in org_dir.rglob("*.org"):
if re.search(r"/\.#[^/]+\.org$", str(file_path)):
continue
if should_skip(file_path, org_dir):
continue
meta = get_org_metadata(file_path)
if not meta or not is_moc(meta):
continue
# Build the URL the same way as search-index.json
try:
relative = file_path.relative_to(org_dir).with_suffix("")
except ValueError:
continue
entry = {
"title": meta["title"],
"url": f"/{relative}.html",
}
category = (meta["category"] or "").strip().lower() or "uncategorised"
groups[category].append(entry)
# Sort files within each group alphabetically by title
for cat in groups:
groups[cat].sort(key=lambda e: e["title"].lower())
# Sort groups alphabetically, but always put "uncategorised" last
sorted_labels = sorted(
groups.keys(),
key=lambda k: (k == "uncategorised", k.lower())
url = f"/{relative}.html"
title = meta["title"]
pinned = (
title.strip().lower() in MOC_PINNED_TITLES
or url.endswith(PINNED_URL_SUFFIX)
)
output = {
"tree": build_tree(org_dir)
}
out_file.parent.mkdir(parents=True, exist_ok=True)
with open(out_file, "w", encoding="utf-8") as f:
json.dump(output, f, ensure_ascii=False, indent=2)
mocs.append({"title": title, "url": url, "pinned": pinned})
mocs.sort(key=lambda m: (not m["pinned"], m["title"].lower()))
return mocs
files_count, folder_count = count_nodes(output["tree"])
print(f" sidebar-tree.json generated ({folder_count} folders, {files_count} files)")
print(f"{out_file}")
def count_nodes(nodes):
total_files = 0
total_folders = 0
if not nodes:
return 0, 0 # ✅ IMPORTANT BASE CASE
for node in nodes:
total_folders += 1
total_files += len(node.get("files", []))
child_files, child_folders = count_nodes(node.get("children", []))
total_files += child_files
total_folders += child_folders
return total_files, total_folders
def build_tree(org_dir: Path) -> list:
"""Build nested folder tree based on filesystem structure."""
@@ -135,8 +116,6 @@ def build_tree(org_dir: Path) -> list:
return
label = parts[0]
# Find or create folder
node = next((n for n in nodes if n["label"] == label), None)
if not node:
node = {"label": label, "children": [], "files": []}
@@ -148,13 +127,14 @@ def build_tree(org_dir: Path) -> list:
insert_node(node["children"], parts[1:], entry)
tree = []
root_files = []
org_files = [
f for f in org_dir.rglob("*.org")
if not re.search(r"/\.#[^/]+\.org$", str(f))
]
for file_path in org_dir.rglob("*.org"):
if re.search(r"/\.#[^/]+\.org$", str(file_path)):
continue
if should_skip(file_path, org_dir):
continue
for file_path in org_files:
meta = get_org_metadata(file_path)
if not meta:
continue
@@ -169,10 +149,16 @@ def build_tree(org_dir: Path) -> list:
"url": f"/{relative.with_suffix('')}.html",
}
folder_parts = relative.parts[:-1] # ignore filename
folder_parts = relative.parts[:-1]
if not folder_parts:
root_files.append(entry)
else:
insert_node(tree, list(folder_parts), entry)
# Sort everything
if root_files:
root_files.sort(key=lambda f: f["title"].lower())
tree.insert(0, {"label": "Garden", "children": [], "files": root_files})
def sort_tree(nodes):
nodes.sort(key=lambda n: n["label"].lower())
for node in nodes:
@@ -180,14 +166,54 @@ def build_tree(org_dir: Path) -> list:
sort_tree(node["children"])
sort_tree(tree)
return tree
def count_nodes(nodes):
total_files = 0
total_folders = 0
if not nodes:
return 0, 0
for node in nodes:
total_folders += 1
total_files += len(node.get("files", []))
child_files, child_folders = count_nodes(node.get("children", []))
total_files += child_files
total_folders += child_folders
return total_files, total_folders
def generate_sidebar_tree(org_dir: Path, out_file: Path) -> None:
"""Generate sidebar-tree.json with MOC hubs and folder tree."""
print(" Generating sidebar-tree.json...")
org_dir = org_dir.expanduser().resolve()
out_file = out_file.expanduser()
output = {
"mocs": build_mocs(org_dir),
"tree": build_tree(org_dir),
"exclude": sorted(EXCLUDE_DIRS),
}
out_file.parent.mkdir(parents=True, exist_ok=True)
with open(out_file, "w", encoding="utf-8") as f:
json.dump(output, f, ensure_ascii=False, indent=2)
files_count, folder_count = count_nodes(output["tree"])
print(
f" sidebar-tree.json generated "
f"({len(output['mocs'])} mocs, {folder_count} folders, {files_count} files)"
)
print(f"{out_file}")
def main():
base_dir = Path.home() / "master-folder" / "org_files" / "org_roam"
sidebar_tree = base_dir / "output" / "assets" / "sidebar-tree.json"
generate_sidebar_tree(base_dir, sidebar_tree)

View File

@@ -310,6 +310,10 @@ window.mvOpen=function(el){
"
<div class=\"banner-header\">
<div class=\"banner-left\">
<a href=\"/index.html\" class=\"banner-brand\">
<img src=\"/assets/icons/icons8-z-key-64.png\" alt=\"\" class=\"banner-logo\" width=\"26\" height=\"26\" />
<span class=\"banner-title\">Zettelgarten</span>
</a>
</div>
<div class=\"banner-search\">
@@ -353,7 +357,6 @@ window.mvOpen=function(el){
<div class=\"banner-actions\">
<button id=\"theme-toggle\" aria-label=\"Toggle theme\" title=\"Toggle light/dark\"></button>
<button id=\"close-all\">close all</button>
</div>
</div>
"

6
orgroam-build.log Normal file → Executable file
View File

@@ -37,14 +37,14 @@ If you dont use the package package manager but still get
this warning, then your chosen package manager likely has a
similar defect.
Loading /home/zaine/master-folder/org_files/org_roam/lisp/macros.el (source)...
Starting full rebuild at 2026-05-29 08:34:51
Starting full rebuild at 2026-05-29 17:08:40
🎬 Scanning /home/zaine/master-folder/org_files/org_roam/assets/ for .mov files…
(no .mov files found)
🧩 Generating all-files.org from JSON…
✅ all-files.org generated
✅ Full rebuild complete in 16.29 seconds
✅ Full rebuild complete in 15.65 seconds
Generating the JSON output 2...
/usr/bin/python3 generate_sidebar_tree.py
Generating sidebar-tree.json...
sidebar-tree.json generated (11 folders, 190 files)
sidebar-tree.json generated (11 mocs, 10 folders, 195 files)
→ /home/zaine/master-folder/org_files/org_roam/output/assets/sidebar-tree.json