From 9d3d33251af065e168e6674f02c00128728e7d3a Mon Sep 17 00:00:00 2001 From: organic-CPU Date: Sun, 12 Jul 2026 10:15:22 +0200 Subject: [PATCH] bloggg --- js/blog.js | 49 ++++++++++++++++++++++++++----------------------- styles.css | 29 ++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/js/blog.js b/js/blog.js index 97c501e..bc5f288 100644 --- a/js/blog.js +++ b/js/blog.js @@ -1,18 +1,16 @@ -// blog.js – Loads and renders blog posts with simple "Read more" toggle for long entries +// blog.js – Loads blog posts and uses a gradient fade for long entries const BLOG_DIR = 'blog/'; const MANIFEST = BLOG_DIR + 'manifest.json'; // ---------- helpers ---------- -// crude HTML parser for directory listings (when manifest is missing) function parseIndexHtmlList(html) { const hrefs = []; const re = /href="([^"]+)"/g; let m; while ((m = re.exec(html)) !== null) { const name = m[1]; - // accept typical text file extensions (and no extension) if (name.match(/\.(txt|md|text)$/i) || !name.includes('.')) { if (!name.endsWith('/')) hrefs.push(name); } @@ -20,7 +18,6 @@ function parseIndexHtmlList(html) { return hrefs; } -// fetch list of blog post filenames (manifest first, fallback to directory listing) async function loadPostList() { try { const r = await fetch(MANIFEST, { cache: 'no-cache' }); @@ -39,7 +36,6 @@ async function loadPostList() { return []; } -// fetch a single post file async function fetchPost(filename) { const res = await fetch(BLOG_DIR + filename, { cache: 'no-cache' }); if (!res.ok) throw new Error(`Failed to fetch ${filename}`); @@ -50,9 +46,7 @@ async function fetchPost(filename) { function renderPost(filename, content) { const lines = content.split('\n'); - if (lines.length < 3) { - return null; // malformed - } + if (lines.length < 3) return null; const dateTime = lines[0].trim(); const title = lines[1].trim(); const bodyLines = lines.slice(2); @@ -75,31 +69,41 @@ function renderPost(filename, content) { const isLong = body.length > 300; - // Body container (always created, but hidden for long posts) + // Container for the body (with optional fade) + const wrapper = document.createElement('div'); + wrapper.className = 'blog-body-wrapper'; + + // The actual text const bodyDiv = document.createElement('div'); bodyDiv.className = 'blog-body'; bodyDiv.style.whiteSpace = 'pre-line'; bodyDiv.textContent = body; + wrapper.appendChild(bodyDiv); - if (!isLong) { - // Short post – show body directly - postDiv.appendChild(bodyDiv); - } else { - // Long post – hide body initially, add toggle button - bodyDiv.style.display = 'none'; - postDiv.appendChild(bodyDiv); + // If long, add collapsed class and a toggle button + if (isLong) { + wrapper.classList.add('collapsed'); - // Toggle button const toggleBtn = document.createElement('button'); toggleBtn.className = 'blog-toggle'; toggleBtn.textContent = 'Read more'; - postDiv.appendChild(toggleBtn); toggleBtn.addEventListener('click', function() { - const isHidden = bodyDiv.style.display === 'none'; - bodyDiv.style.display = isHidden ? 'block' : 'none'; - this.textContent = isHidden ? 'Show less' : 'Read more'; + const isCollapsed = wrapper.classList.contains('collapsed'); + if (isCollapsed) { + wrapper.classList.remove('collapsed'); + this.textContent = 'Show less'; + } else { + wrapper.classList.add('collapsed'); + this.textContent = 'Read more'; + } }); + + postDiv.appendChild(wrapper); + postDiv.appendChild(toggleBtn); + } else { + // Short post – no collapse, no button + postDiv.appendChild(wrapper); } return postDiv; @@ -120,8 +124,7 @@ async function initBlog() { return; } - files.sort(); // alphabetical -> chronological - + files.sort(); feed.innerHTML = ''; for (const file of files) { diff --git a/styles.css b/styles.css index e60d1a3..09e0a27 100644 --- a/styles.css +++ b/styles.css @@ -31,13 +31,39 @@ a:hover { color:#991b1b; text-decoration:underline; } .blog-title { margin:0 0 8px 0; font-size:1.5rem; } .blog-meta { margin:0 0 16px 0; color:#7f1d1d; font-size:0.95rem; } +/* ----- Blog body with fade-out ----- */ +.blog-body-wrapper { + position: relative; + margin-bottom: 0.5rem; + /* No max-height by default (full) */ +} + .blog-body { white-space: pre-line; - margin: 0 0 1em 0; + margin: 0; } .blog-body p { margin:0 0 1em 0; } .blog-body p:last-child { margin-bottom:0; } +/* When collapsed, limit height and show gradient fade */ +.blog-body-wrapper.collapsed { + max-height: 300px; /* ~6 lines, adjust as needed */ + overflow: hidden; + /* The gradient overlay is done via ::after */ +} + +.blog-body-wrapper.collapsed::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 80px; /* fade height */ + background: linear-gradient(to bottom, transparent, #fff); + pointer-events: none; /* allow clicking through to the button */ + /* If your blog background is not white, change #fff to match */ +} + /* Toggle button */ .blog-toggle { background: none; @@ -53,6 +79,7 @@ a:hover { color:#991b1b; text-decoration:underline; } text-decoration: underline; } +/* Other existing styles (photography, modal, etc.) */ .blog-attachment-list { margin:16px 0 0 0; padding:0; list-style:none; } .blog-attachment-list li { margin:0.5em 0; } .blog-attachment-list a { color:#dc2626; text-decoration:none; }