Decorvius


upcoming single

    About

    Decorvius is the solo project of independent Canadian songwriter Mihan Han. Drawing from his diverse musical interests in pop, classical, and anime/game soundtracks, he crafts eclectic genre-blending songs in collaboration with artists and musicians from around the world.

    Music

    Merch

    Blog

    /** * RSS-based Substack embed (client-side) * - Finds elements matching `.substack-feed-embed` or `#substack-feed-embed` * - Pulls configuration from data-* attributes with sensible defaults * - Fetches the Substack RSS (via rss2json) and renders simple cards * * Optional global defaults: * window.SubstackFeedWidget = { substackUrl: '', posts: 3, showImages: true, showDates: true }; */ (function () { const globalDefaults = (window.SubstackFeedWidget || {}); const defaults = { posts: 3, showImages: true, showDates: true, ...globalDefaults }; const containers = document.querySelectorAll('.substack-feed-embed, #substack-feed-embed'); if (!containers.length) return; const fmtDate = (iso) => { try { /* Use reader’s locale; tweak if you want a fixed one */ return new Intl.DateTimeFormat(undefined, { year: 'numeric', month: 'short', day: '2-digit' }).format(new Date(iso)); } catch { return ''; } }; const normalizeUrl = (u) => { if (!u) return ''; return /^https?:\/\//i.test(u) ? u : `https://${u}`; }; const truncate = (str, max = 150) => (str && str.length > max ? `${str.slice(0, max)}…` : (str || '')); const buildCard = (post, opts) => { const a = document.createElement('div'); a.className = 'substack-card'; const wrap = document.createElement('div'); wrap.className = 'substack-post'; if (opts.showImages) { const imgUrl = post.image; if (imgUrl) { const imgA = document.createElement('a'); imgA.target = '_blank'; imgA.rel = 'noopener'; imgA.href = post.link; const img = document.createElement('img'); img.className = 'substack-thumbnail'; img.loading = 'lazy'; img.sizes = '568px'; img.alt = post.title || 'Substack post image'; img.src = imgUrl; wrap.appendChild(imgA); imgA.appendChild(img); } } const body = document.createElement('div'); const header = document.createElement('a'); header.target = '_blank'; header.rel = 'noopener'; header.href = post.link; header.className = 'substack-title'; header.textContent = post.title || 'Untitled'; body.appendChild(header); if (opts.showDates && post.pubDate) { const pDate = document.createElement('a'); pDate.target = '_blank'; pDate.rel = 'noopener'; pDate.href = post.link; pDate.className = 'substack-date'; pDate.textContent = fmtDate(post.pubDate); body.appendChild(pDate); } const descriptionWrapper = document.createElement('p'); const description = document.createElement('span'); const readMore = document.createElement('a'); readMore.target = '_blank'; readMore.rel = 'noopener'; readMore.href = post.link; readMore.textContent = "(Read More)"; descriptionWrapper.className = 'substack-desc'; description.textContent = truncate(post.body, 160) + "..."; body.appendChild(descriptionWrapper); descriptionWrapper.appendChild(description); descriptionWrapper.appendChild(readMore); wrap.appendChild(body); a.appendChild(wrap); return a; }; const fetchWithTimeout = (url, ms = 10000) => { const ctl = new AbortController(); const t = setTimeout(() => ctl.abort(), ms); return fetch(url, { signal: ctl.signal }).finally(() => clearTimeout(t)); }; containers.forEach(async (container) => { const cfg = { posts: parseInt(container.getAttribute('data-posts'), 10) || defaults.posts, showImages: (container.getAttribute('data-show-images') || `${defaults.showImages}`) !== 'false', showDates: (container.getAttribute('data-show-dates') || `${defaults.showDates}`) !== 'false' }; const feedBase = normalizeUrl(cfg.substackUrl); const rssUrl = `https://decorvius-feed.decorvius.workers.dev/`; /* Minimal skeleton while loading (optional) */ container.innerHTML = '
    Loading…
    '; try { const res = await fetchWithTimeout(rssUrl, 12000); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); if (data.status !== 'ok' || !Array.isArray(data.items)) { throw new Error(data.message || 'Failed to parse feed'); } const posts = data.items.slice(0, Math.max(1, cfg.posts)); if (!posts.length) { container.innerHTML = '

    No posts available.

    '; return; } /* Clear and render */ container.innerHTML = ''; posts.forEach((post) => { container.appendChild(buildCard(post, cfg)); }); } catch (err) { console.error('Substack feed error:', err); container.innerHTML = '

    Could not load posts. Please try again later.

    '; } }); })();

    Subscribe

    Sign up to the mailing list for updates, deals, and exclusive content.