diff --git a/20260111204228-rss_articles.sync-conflict-20260530-154400-VT6366A.org b/20260111204228-rss_articles.sync-conflict-20260530-154400-VT6366A.org new file mode 100755 index 0000000..0e24cde --- /dev/null +++ b/20260111204228-rss_articles.sync-conflict-20260530-154400-VT6366A.org @@ -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 Isn’t 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: + diff --git a/20260421100223-dailies.org b/20260421100223-dailies.org index ebe5e77..979f201 100755 --- a/20260421100223-dailies.org +++ b/20260421100223-dailies.org @@ -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]] diff --git a/20260505143034-deen_moc.org b/20260505143034-deen_moc.org index 04beb4a..8e4e4ae 100755 --- a/20260505143034-deen_moc.org +++ b/20260505143034-deen_moc.org @@ -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/ diff --git a/20260601093154-cross_site_scripting_xss.org b/20260601093154-cross_site_scripting_xss.org new file mode 100755 index 0000000..cfbb126 --- /dev/null +++ b/20260601093154-cross_site_scripting_xss.org @@ -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. diff --git a/20260601134732-naats.org b/20260601134732-naats.org new file mode 100755 index 0000000..28c5332 --- /dev/null +++ b/20260601134732-naats.org @@ -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 God’s oath, +But in your heart there is no love for God. +You are a Muslim, but only in name, +There is a Qur’an 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.** + diff --git a/20260602101857-sql_joins.org b/20260602101857-sql_joins.org new file mode 100755 index 0000000..bfd69f6 --- /dev/null +++ b/20260602101857-sql_joins.org @@ -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 + diff --git a/Books/20250724230557-books_org_agenda.org b/Books/20250724230557-books_org_agenda.org index e074df6..b9192b2 100755 --- a/Books/20250724230557-books_org_agenda.org +++ b/Books/20250724230557-books_org_agenda.org @@ -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: diff --git a/Career Concepts/20260101010049-concepts.org b/Career Concepts/20260101010049-concepts.org index f31e8fd..370c209 100755 --- a/Career Concepts/20260101010049-concepts.org +++ b/Career Concepts/20260101010049-concepts.org @@ -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+ diff --git a/Career Concepts/20260527152602-microlise_xss_rom_pentest.org b/Career Concepts/20260527152602-microlise_xss_rom_pentest.org index 10a4bb1..9d4b51b 100755 --- a/Career Concepts/20260527152602-microlise_xss_rom_pentest.org +++ b/Career Concepts/20260527152602-microlise_xss_rom_pentest.org @@ -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 diff --git a/Misc/20260307233451-recipes_cookbook.org b/Misc/20260307233451-recipes_cookbook.org index 60b9fc8..f84fc86 100755 --- a/Misc/20260307233451-recipes_cookbook.org +++ b/Misc/20260307233451-recipes_cookbook.org @@ -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: to record when you made it, and :TIPS: your tip text to add notes. # Multiple :COOKED: lines are supported. These are preserved across regenerations. diff --git a/Misc/20260402135410-z_resources.org b/Misc/20260402135410-z_resources.org index 7a1bd46..8fc35fe 100755 --- a/Misc/20260402135410-z_resources.org +++ b/Misc/20260402135410-z_resources.org @@ -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 diff --git a/all-files.json b/all-files.json index 72daec8..ebc3481 100755 --- a/all-files.json +++ b/all-files.json @@ -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" } ] } diff --git a/all-files.org b/all-files.org index 86c53b6..957aac7 100755 --- a/all-files.org +++ b/all-files.org @@ -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]] diff --git a/assets/scripts/script.js b/assets/scripts/script.js index 87dd421..4cb791d 100755 --- a/assets/scripts/script.js +++ b/assets/scripts/script.js @@ -17,6 +17,7 @@ document.addEventListener('DOMContentLoaded', () => { initMediaFixes(document); initReadingProgress(); hookSearchResults(); + initHomeDashboard(); }); /* ========================================================= @@ -93,14 +94,14 @@ function rearrangeDOM() { function buildNoteToolbar(toolbar) { toolbar.innerHTML = ` - - + +
- + +
-
- + `; } @@ -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: '\u2315', title: 'Search', id: 'rail-search', action: 'search' }, + { 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' }); - panelEl.innerHTML = ` - -
- `; - sidebar.appendChild(panelEl); + const panelEl = el('div', { class: 'sidebar-panel', 'data-panel': panel }); + panelEl.innerHTML = ` + + + `; + body.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')); - }); + panelEl.querySelector('.sidebar-close-btn').addEventListener('click', closeSidebar); - buildFileTree(panelEl.querySelector('#file-tree')); + 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 = ''; + 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 = ` + ${escHtml(moc.title)} + ${moc.pinned ? 'start' : ''} + `; + 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 = ''; + return; + } + + const list = el('div', { class: 'recent-list' }); + recent.forEach(item => { + const row = el('button', { class: 'recent-item', type: 'button' }); + row.innerHTML = ` + ${escHtml(item.title)} + ${formatRelativeTime(item.visitedAt)} + `; + row.addEventListener('click', () => { + closeSidebar(); + openNote(item.pathname, item.title); + }); + list.appendChild(row); + }); + container.appendChild(list); +} + +function buildBrowsePanel(container) { + container.innerHTML = ` +
+ +
+
+ `; + + const filterInput = container.querySelector('.tree-filter'); + const treeRoot = container.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 = ''; 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 icon = el('span', { class: 'tree-icon' }); - icon.textContent = '\u25BE '; + 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 = 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 = ` +
+

Zettelgarten

+

A private library of notes, maps, and wandering thoughts.

+
+ +

Start here

+
+ `; + + 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 = ` + ${escHtml(moc.title)} + ${moc.pinned ? 'start' : ''} + `; + 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 = ` diff --git a/assets/styles/media.css b/assets/styles/media.css index 2d33bbc..1a35478 100755 --- a/assets/styles/media.css +++ b/assets/styles/media.css @@ -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 ---- */ diff --git a/assets/styles/style.css b/assets/styles/style.css index da0540f..435ef64 100755 --- a/assets/styles/style.css +++ b/assets/styles/style.css @@ -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; diff --git a/daily/2026-05-29.org b/daily/2026-05-29.org index 5ed66b7..3124f6c 100755 --- a/daily/2026-05-29.org +++ b/daily/2026-05-29.org @@ -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/ diff --git a/daily/2026-06-01.org b/daily/2026-06-01.org new file mode 100755 index 0000000..b01cc87 --- /dev/null +++ b/daily/2026-06-01.org @@ -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 + diff --git a/daily/2026-06-02.org b/daily/2026-06-02.org new file mode 100755 index 0000000..940cc0c --- /dev/null +++ b/daily/2026-06-02.org @@ -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 diff --git a/generate_sidebar_tree.py b/generate_sidebar_tree.py index 0048221..bd66653 100755 --- a/generate_sidebar_tree.py +++ b/generate_sidebar_tree.py @@ -1,132 +1,113 @@ """ 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": [ + "mocs": [ + { "title": "Brain MOC", "url": "/20241210233721-brain_moc.html", "pinned": true }, + ... + ], + "tree": [ { - "label": "java", - "files": [ - { "title": "Java MOC", "url": "/20250402185735-java_moc.html" }, - ... - ] + "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.""" - title_pattern = re.compile(r"^#\+title:\s*(.+)", re.IGNORECASE | re.MULTILINE) - category_pattern = re.compile(r"^#\+category:\s*(.+)", re.IGNORECASE | re.MULTILINE) - id_pattern = re.compile(r"^:ID:\s*(.+)", re.MULTILINE) + """Extract title, filetags, and id from an Org file.""" + title_pattern = re.compile(r"^#\+title:\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: content = file_path.read_text(encoding="utf-8", errors="ignore") except OSError: return {} - title_match = title_pattern.search(content) - category_match = category_pattern.search(content) - id_match = id_pattern.search(content) + title_match = title_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, - "id": id_match.group(1).strip() if id_match else None, + "title": title_match.group(1).strip() if title_match else "Untitled", + "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", - } + url = f"/{relative}.html" + title = meta["title"] + pinned = ( + title.strip().lower() in MOC_PINNED_TITLES + or url.endswith(PINNED_URL_SUFFIX) + ) + mocs.append({"title": title, "url": url, "pinned": pinned}) - 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()) - ) - 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) - - - files_count, folder_count = count_nodes(output["tree"]) - - print(f" sidebar-tree.json generated ({folder_count} folders, {files_count} files)") - print(f" → {out_file}") + mocs.sort(key=lambda m: (not m["pinned"], m["title"].lower())) + return mocs - -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 - insert_node(tree, list(folder_parts), entry) + folder_parts = relative.parts[:-1] + if not folder_parts: + root_files.append(entry) + else: + insert_node(tree, list(folder_parts), entry) + + if root_files: + root_files.sort(key=lambda f: f["title"].lower()) + tree.insert(0, {"label": "Garden", "children": [], "files": root_files}) - # Sort everything 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 main(): - base_dir = Path.home() / "master-folder" / "org_files" / "org_roam" - sidebar_tree = base_dir / "output" / "assets" / "sidebar-tree.json" +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) diff --git a/lisp/build.el b/lisp/build.el index 5529e81..436afe9 100755 --- a/lisp/build.el +++ b/lisp/build.el @@ -310,6 +310,10 @@ window.mvOpen=function(el){ "
@@ -353,7 +357,6 @@ window.mvOpen=function(el){
-
" diff --git a/orgroam-build.log b/orgroam-build.log old mode 100644 new mode 100755 index adcda67..eb75ddb --- a/orgroam-build.log +++ b/orgroam-build.log @@ -37,14 +37,14 @@ If you don’t 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