reduce bounce rate
This commit is contained in:
@@ -20,17 +20,10 @@ const { categories, activeCategorySlug } = Astro.props;
|
||||
>
|
||||
All
|
||||
</a>
|
||||
<a
|
||||
class={activeCategorySlug === "__pages" ? "active" : ""}
|
||||
href="/blog/pages"
|
||||
data-umami-event="click"
|
||||
data-umami-event-target_id="blog.subnav.pages"
|
||||
data-umami-event-placement="blog.subnav"
|
||||
data-umami-event-target_url="/blog/pages"
|
||||
>
|
||||
Pages
|
||||
</a>
|
||||
{categories.map((c) => (
|
||||
|
||||
{categories
|
||||
.filter((c) => c.slug !== "uncategorized")
|
||||
.map((c) => (
|
||||
<a
|
||||
class={activeCategorySlug === c.slug ? "active" : ""}
|
||||
href={`/blog/category/${c.slug}`}
|
||||
|
||||
@@ -35,26 +35,55 @@ const umamiType =
|
||||
: undefined;
|
||||
|
||||
const umamiTitle = umamiType ? truncate(item.title, 160) : undefined;
|
||||
|
||||
// Determine if card should open modal (youtube/podcast) or link normally (other sources)
|
||||
const isModalTrigger = item.source === "youtube" || item.source === "podcast";
|
||||
const mode = isModalTrigger ? "modal" : "link";
|
||||
|
||||
// Build link attrs based on mode
|
||||
const linkAttrs = isModalTrigger
|
||||
? {
|
||||
// Modal trigger: pass all item data via data-* attributes
|
||||
"data-item-id": item.id,
|
||||
"data-item-source": item.source,
|
||||
"data-item-url": item.url,
|
||||
"data-item-title": item.title,
|
||||
"data-item-summary": item.summary || "",
|
||||
"data-item-published-at": item.publishedAt,
|
||||
"data-item-thumbnail-url": item.thumbnailUrl || "",
|
||||
"data-item-audio-url": item.source === "podcast" ? item.audioUrl || "" : "",
|
||||
"data-item-views": item.metrics?.views?.toString() || "",
|
||||
// Umami tracking: media_preview instead of outbound_click
|
||||
"data-umami-event": "media_preview",
|
||||
"data-umami-event-target_id": targetId,
|
||||
"data-umami-event-placement": placement,
|
||||
"data-umami-event-title": umamiTitle,
|
||||
"data-umami-event-type": umamiType,
|
||||
"data-umami-event-source": item.source,
|
||||
}
|
||||
: {
|
||||
// Normal link: outbound_click tracking
|
||||
"data-umami-event": "outbound_click",
|
||||
"data-umami-event-target_id": targetId,
|
||||
"data-umami-event-placement": placement,
|
||||
"data-umami-event-target_url": item.url,
|
||||
"data-umami-event-title": umamiTitle,
|
||||
"data-umami-event-type": umamiType,
|
||||
"data-umami-event-domain": domain || "unknown",
|
||||
"data-umami-event-source": item.source,
|
||||
"data-umami-event-ui_placement": "content_card",
|
||||
};
|
||||
---
|
||||
|
||||
<StandardCard
|
||||
href={item.url}
|
||||
href={isModalTrigger ? undefined : item.url}
|
||||
title={item.title}
|
||||
summary={item.summary}
|
||||
imageUrl={item.thumbnailUrl}
|
||||
dateLabel={dateLabel}
|
||||
viewsLabel={item.metrics?.views !== undefined ? `${item.metrics.views.toLocaleString()} views` : undefined}
|
||||
sourceLabel={item.source}
|
||||
isExternal={true}
|
||||
linkAttrs={{
|
||||
"data-umami-event": "outbound_click",
|
||||
"data-umami-event-target_id": targetId,
|
||||
"data-umami-event-placement": placement,
|
||||
"data-umami-event-target_url": item.url,
|
||||
"data-umami-event-title": umamiTitle,
|
||||
"data-umami-event-type": umamiType,
|
||||
"data-umami-event-domain": domain || "unknown",
|
||||
"data-umami-event-source": item.source,
|
||||
"data-umami-event-ui_placement": "content_card",
|
||||
}}
|
||||
isExternal={!isModalTrigger}
|
||||
mode={mode}
|
||||
linkAttrs={linkAttrs}
|
||||
/>
|
||||
|
||||
398
site/src/components/MediaModal.astro
Normal file
398
site/src/components/MediaModal.astro
Normal file
@@ -0,0 +1,398 @@
|
||||
---
|
||||
import { LINKS } from "../lib/links";
|
||||
---
|
||||
|
||||
<dialog id="media-modal" aria-modal="true" aria-labelledby="media-modal-title">
|
||||
<div class="media-modal-content">
|
||||
<div class="media-modal-header">
|
||||
<div class="media-modal-header-left">
|
||||
<img class="media-modal-thumb" alt="" loading="lazy" />
|
||||
<div class="media-modal-title-wrap">
|
||||
<div class="media-modal-kicker"></div>
|
||||
<h2 id="media-modal-title" class="media-modal-title"></h2>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="media-modal-close" aria-label="Close" data-close-method="button">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="media-modal-embed-container" data-embed-kind="">
|
||||
<div class="media-modal-embed-placeholder">
|
||||
<a class="media-modal-embed-fallback" target="_blank" rel="noopener noreferrer"></a>
|
||||
</div>
|
||||
<iframe
|
||||
class="media-modal-embed"
|
||||
title="Media embed"
|
||||
width="100%"
|
||||
frameborder="0"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
<audio class="media-modal-audio" controls></audio>
|
||||
</div>
|
||||
|
||||
<div class="media-modal-body">
|
||||
<p class="media-modal-description"></p>
|
||||
<div class="media-modal-meta">
|
||||
<span class="media-modal-date"></span>
|
||||
<span class="media-modal-views"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="media-modal-ctas">
|
||||
<a class="cta primary media-modal-cta-follow" target="_blank" rel="me noopener noreferrer"></a>
|
||||
<a class="cta media-modal-cta-view" target="_blank" rel="me noopener noreferrer"></a>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script is:inline define:vars={{ youtubeChannelUrl: LINKS.youtubeChannel, podcastUrl: LINKS.podcast }}>
|
||||
(function() {
|
||||
const dialog = document.getElementById("media-modal");
|
||||
const dialogContent = dialog.querySelector(".media-modal-content");
|
||||
const kickerEl = dialog.querySelector(".media-modal-kicker");
|
||||
const thumbEl = dialog.querySelector(".media-modal-thumb");
|
||||
const titleEl = dialog.querySelector(".media-modal-title");
|
||||
const embedContainer = dialog.querySelector(".media-modal-embed-container");
|
||||
const embedPlaceholder = dialog.querySelector(".media-modal-embed-placeholder");
|
||||
const embedFallback = dialog.querySelector(".media-modal-embed-fallback");
|
||||
const iframe = dialog.querySelector(".media-modal-embed");
|
||||
const audio = dialog.querySelector(".media-modal-audio");
|
||||
const descriptionEl = dialog.querySelector(".media-modal-description");
|
||||
const dateEl = dialog.querySelector(".media-modal-date");
|
||||
const viewsEl = dialog.querySelector(".media-modal-views");
|
||||
const followCta = dialog.querySelector(".media-modal-cta-follow");
|
||||
const viewCta = dialog.querySelector(".media-modal-cta-view");
|
||||
const closeBtn = dialog.querySelector(".media-modal-close");
|
||||
|
||||
let triggerElement = null;
|
||||
let currentTargetId = null;
|
||||
|
||||
// Extract video ID from YouTube URL
|
||||
function extractYoutubeId(url) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
// Handle youtube.com/watch?v=ID
|
||||
if (u.hostname.includes("youtube.com") && u.searchParams.has("v")) {
|
||||
return u.searchParams.get("v");
|
||||
}
|
||||
// Handle youtu.be/ID
|
||||
if (u.hostname.includes("youtu.be")) {
|
||||
return u.pathname.slice(1).split("/")[0];
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract Spotify episode ID from URL
|
||||
function extractSpotifyEpisodeId(input) {
|
||||
try {
|
||||
const u = new URL(String(input));
|
||||
if (!u.hostname.includes("spotify.com")) return null;
|
||||
const match = u.pathname.match(/\/episode\/([^/?]+)/);
|
||||
return match ? match[1] : null;
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractSpotifyEpisodeIdFromGuid(guid) {
|
||||
const s = String(guid || "").trim();
|
||||
if (!s) return null;
|
||||
|
||||
// spotify:episode:EPISODE_ID
|
||||
if (s.startsWith("spotify:episode:")) {
|
||||
const parts = s.split(":");
|
||||
return parts.length >= 3 ? parts[2] : null;
|
||||
}
|
||||
|
||||
// URL form
|
||||
const fromUrl = extractSpotifyEpisodeId(s);
|
||||
if (fromUrl) return fromUrl;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getEmbed(source, itemUrl, itemId) {
|
||||
if (source === "youtube") {
|
||||
const videoId = extractYoutubeId(itemUrl);
|
||||
if (!videoId) return null;
|
||||
return {
|
||||
kind: "youtube",
|
||||
src: `https://www.youtube.com/embed/${videoId}?rel=0&modestbranding=1`,
|
||||
};
|
||||
}
|
||||
|
||||
if (source === "podcast") {
|
||||
const episodeId =
|
||||
extractSpotifyEpisodeId(itemUrl) ||
|
||||
extractSpotifyEpisodeIdFromGuid(itemId) ||
|
||||
extractSpotifyEpisodeId(itemId);
|
||||
if (!episodeId) return null;
|
||||
return {
|
||||
kind: "spotify",
|
||||
src: `https://open.spotify.com/embed/episode/${episodeId}`,
|
||||
height: 232,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add UTM parameters to URL
|
||||
function withUtm(url, utmParams) {
|
||||
const u = new URL(url);
|
||||
for (const [key, value] of Object.entries(utmParams)) {
|
||||
if (value) u.searchParams.set(key, value);
|
||||
}
|
||||
return u.toString();
|
||||
}
|
||||
|
||||
function withYoutubeSubscribePrompt(url) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
u.searchParams.set("sub_confirmation", "1");
|
||||
return u.toString();
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop playback by blanking the iframe
|
||||
function stopPlayback() {
|
||||
iframe.src = "about:blank";
|
||||
embedPlaceholder.style.display = "block";
|
||||
embedContainer.dataset.embedKind = "";
|
||||
iframe.style.display = "";
|
||||
embedFallback.style.display = "none";
|
||||
embedFallback.removeAttribute("href");
|
||||
audio.pause();
|
||||
audio.removeAttribute("src");
|
||||
audio.style.display = "none";
|
||||
thumbEl.removeAttribute("src");
|
||||
thumbEl.style.display = "none";
|
||||
kickerEl.textContent = "";
|
||||
iframe.removeAttribute("allow");
|
||||
iframe.removeAttribute("height");
|
||||
}
|
||||
|
||||
// Close modal
|
||||
function closeModal(method) {
|
||||
if (!dialog.open) return;
|
||||
|
||||
stopPlayback();
|
||||
dialog.close();
|
||||
|
||||
// Emit media_preview_close event if Umami is available
|
||||
if (typeof window.umami !== "undefined" && currentTargetId) {
|
||||
window.umami.track("media_preview_close", {
|
||||
target_id: currentTargetId,
|
||||
close_method: method || "unknown"
|
||||
});
|
||||
}
|
||||
|
||||
// Restore focus to trigger element
|
||||
if (triggerElement) {
|
||||
triggerElement.focus();
|
||||
triggerElement = null;
|
||||
}
|
||||
|
||||
currentTargetId = null;
|
||||
}
|
||||
|
||||
// Open and populate modal
|
||||
function openModal(cardEl) {
|
||||
// Store trigger for focus return
|
||||
triggerElement = cardEl;
|
||||
|
||||
// Read data attributes
|
||||
const itemId = cardEl.dataset.itemId;
|
||||
const source = cardEl.dataset.itemSource;
|
||||
const url = cardEl.dataset.itemUrl;
|
||||
const audioUrl = cardEl.dataset.itemAudioUrl;
|
||||
const title = cardEl.dataset.itemTitle;
|
||||
const summary = cardEl.dataset.itemSummary;
|
||||
const publishedAt = cardEl.dataset.itemPublishedAt;
|
||||
const thumbnailUrl = cardEl.dataset.itemThumbnailUrl;
|
||||
const views = cardEl.dataset.itemViews;
|
||||
|
||||
// Store target ID for close tracking
|
||||
currentTargetId = cardEl.dataset.umamiEventTargetId;
|
||||
|
||||
kickerEl.textContent = source === "podcast" ? "Podcast" : source === "youtube" ? "Video" : "";
|
||||
if (source === "podcast" && thumbnailUrl) {
|
||||
thumbEl.src = thumbnailUrl;
|
||||
thumbEl.style.display = "block";
|
||||
} else {
|
||||
thumbEl.removeAttribute("src");
|
||||
thumbEl.style.display = "none";
|
||||
}
|
||||
|
||||
// Populate title
|
||||
titleEl.textContent = title || "";
|
||||
|
||||
// Populate description
|
||||
descriptionEl.textContent = summary || "";
|
||||
|
||||
// Populate date
|
||||
if (publishedAt) {
|
||||
const d = new Date(publishedAt);
|
||||
const formatted = Number.isFinite(d.valueOf())
|
||||
? d.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" })
|
||||
: "";
|
||||
dateEl.textContent = formatted;
|
||||
dateEl.style.display = formatted ? "" : "none";
|
||||
} else {
|
||||
dateEl.style.display = "none";
|
||||
}
|
||||
|
||||
// Populate views
|
||||
if (views && views !== "") {
|
||||
const viewsNum = parseInt(views, 10);
|
||||
viewsEl.textContent = `${viewsNum.toLocaleString()} views`;
|
||||
viewsEl.style.display = "";
|
||||
} else {
|
||||
viewsEl.style.display = "none";
|
||||
}
|
||||
|
||||
// Construct embed
|
||||
const embed = getEmbed(source, url, itemId);
|
||||
if (embed) {
|
||||
embedContainer.dataset.embedKind = embed.kind;
|
||||
embedPlaceholder.style.display = "block";
|
||||
embedFallback.style.display = "none";
|
||||
embedFallback.removeAttribute("href");
|
||||
iframe.style.display = "";
|
||||
|
||||
if (embed.kind === "spotify") {
|
||||
iframe.setAttribute(
|
||||
"allow",
|
||||
"autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture",
|
||||
);
|
||||
if (embed.height) iframe.setAttribute("height", String(embed.height));
|
||||
} else {
|
||||
iframe.setAttribute(
|
||||
"allow",
|
||||
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
||||
);
|
||||
iframe.removeAttribute("height");
|
||||
}
|
||||
|
||||
iframe.src = embed.src;
|
||||
iframe.addEventListener("load", function onLoad() {
|
||||
embedPlaceholder.style.display = "none";
|
||||
iframe.removeEventListener("load", onLoad);
|
||||
});
|
||||
embedContainer.style.display = "block";
|
||||
} else {
|
||||
// No embed available
|
||||
if (source === "podcast") {
|
||||
embedContainer.style.display = "block";
|
||||
iframe.src = "about:blank";
|
||||
iframe.style.display = "none";
|
||||
|
||||
if (audioUrl) {
|
||||
embedContainer.dataset.embedKind = "audio";
|
||||
embedPlaceholder.style.display = "none";
|
||||
embedFallback.style.display = "none";
|
||||
audio.style.display = "block";
|
||||
audio.src = audioUrl;
|
||||
} else {
|
||||
embedContainer.dataset.embedKind = "fallback";
|
||||
embedPlaceholder.style.display = "flex";
|
||||
embedFallback.style.display = "inline-flex";
|
||||
embedFallback.href = url;
|
||||
embedFallback.textContent = "Listen on Spotify";
|
||||
audio.style.display = "none";
|
||||
}
|
||||
} else {
|
||||
embedContainer.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
const platform = source === "youtube" ? "youtube" : "spotify";
|
||||
const channelUrl = source === "youtube" ? youtubeChannelUrl : podcastUrl;
|
||||
|
||||
const followAction = source === "youtube" ? "subscribe" : "follow";
|
||||
const viewAction = source === "youtube" ? "view" : "listen";
|
||||
|
||||
const followLabel = source === "youtube" ? "Subscribe on YouTube" : "Follow on Spotify";
|
||||
const viewLabel = source === "youtube" ? "View on YouTube" : "Listen on Spotify";
|
||||
|
||||
const followBaseUrl = source === "youtube" ? withYoutubeSubscribePrompt(channelUrl) : channelUrl;
|
||||
|
||||
const followUrl = withUtm(followBaseUrl, {
|
||||
utm_source: "website",
|
||||
utm_medium: "cta",
|
||||
utm_campaign: "social-acquisition",
|
||||
utm_content: `${platform}:media_modal`,
|
||||
});
|
||||
followCta.href = followUrl;
|
||||
followCta.textContent = followLabel;
|
||||
followCta.setAttribute("data-umami-event", "cta_click");
|
||||
followCta.setAttribute("data-umami-event-target_id", `modal.cta.${followAction}.${platform}`);
|
||||
followCta.setAttribute("data-umami-event-placement", "media_modal");
|
||||
followCta.setAttribute("data-umami-event-platform", platform);
|
||||
followCta.setAttribute("data-umami-event-target_url", channelUrl);
|
||||
|
||||
const viewUrl = withUtm(url, {
|
||||
utm_source: "website",
|
||||
utm_medium: "cta",
|
||||
utm_campaign: "social-acquisition",
|
||||
utm_content: `${platform}:media_modal`,
|
||||
});
|
||||
viewCta.href = viewUrl;
|
||||
viewCta.textContent = viewLabel;
|
||||
viewCta.setAttribute("data-umami-event", "cta_click");
|
||||
viewCta.setAttribute("data-umami-event-target_id", `modal.cta.${viewAction}.${platform}`);
|
||||
viewCta.setAttribute("data-umami-event-placement", "media_modal");
|
||||
viewCta.setAttribute("data-umami-event-platform", platform);
|
||||
viewCta.setAttribute("data-umami-event-target_url", url);
|
||||
|
||||
// Open the dialog
|
||||
dialog.showModal();
|
||||
}
|
||||
|
||||
// Listen for clicks on modal-trigger cards
|
||||
document.addEventListener("click", function(e) {
|
||||
const card = e.target.closest("button.card[data-item-id]");
|
||||
if (card) {
|
||||
e.preventDefault();
|
||||
openModal(card);
|
||||
}
|
||||
});
|
||||
|
||||
// Close button
|
||||
closeBtn.addEventListener("click", function() {
|
||||
closeModal("button");
|
||||
});
|
||||
|
||||
// Backdrop click
|
||||
dialog.addEventListener("click", function(e) {
|
||||
if (e.target === dialog) {
|
||||
closeModal("backdrop");
|
||||
}
|
||||
});
|
||||
|
||||
// Escape key (native dialog handles the close, we hook into the close event)
|
||||
dialog.addEventListener("close", function() {
|
||||
// If dialog was closed but we didn't call closeModal explicitly (i.e., Escape key)
|
||||
if (dialog.open === false && currentTargetId) {
|
||||
stopPlayback();
|
||||
if (typeof window.umami !== "undefined") {
|
||||
window.umami.track("media_preview_close", {
|
||||
target_id: currentTargetId,
|
||||
close_method: "escape"
|
||||
});
|
||||
}
|
||||
if (triggerElement) {
|
||||
triggerElement.focus();
|
||||
triggerElement = null;
|
||||
}
|
||||
currentTargetId = null;
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
type Props = {
|
||||
href: string;
|
||||
href?: string;
|
||||
title: string;
|
||||
summary?: string;
|
||||
imageUrl?: string;
|
||||
@@ -9,6 +9,7 @@ type Props = {
|
||||
sourceLabel: string;
|
||||
isExternal?: boolean;
|
||||
linkAttrs?: Record<string, any>;
|
||||
mode?: "link" | "modal";
|
||||
};
|
||||
|
||||
const {
|
||||
@@ -21,6 +22,7 @@ const {
|
||||
sourceLabel,
|
||||
isExternal,
|
||||
linkAttrs,
|
||||
mode = "link",
|
||||
} = Astro.props;
|
||||
|
||||
function truncate(s: string, n: number) {
|
||||
@@ -33,14 +35,21 @@ function truncate(s: string, n: number) {
|
||||
|
||||
const summaryText = truncate(summary || "", 180);
|
||||
|
||||
const Element = mode === "modal" ? "button" : "a";
|
||||
const elementProps = mode === "modal"
|
||||
? { type: "button", ...linkAttrs }
|
||||
: {
|
||||
href,
|
||||
target: isExternal ? "_blank" : undefined,
|
||||
rel: isExternal ? "noopener noreferrer" : undefined,
|
||||
...linkAttrs
|
||||
};
|
||||
|
||||
---
|
||||
|
||||
<a
|
||||
<Element
|
||||
class="card"
|
||||
href={href}
|
||||
target={isExternal ? "_blank" : undefined}
|
||||
rel={isExternal ? "noopener noreferrer" : undefined}
|
||||
{...(linkAttrs || {})}
|
||||
{...elementProps}
|
||||
>
|
||||
<div class="card-media">
|
||||
{imageUrl ? (
|
||||
@@ -66,4 +75,4 @@ const summaryText = truncate(summary || "", 180);
|
||||
<span class={`pill pill-${sourceLabel}`}>{sourceLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Element>
|
||||
|
||||
@@ -12,6 +12,7 @@ export type ContentItem = {
|
||||
summary?: string;
|
||||
publishedAt: string; // ISO-8601
|
||||
thumbnailUrl?: string;
|
||||
audioUrl?: string;
|
||||
metrics?: ContentMetrics;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export function normalizePodcastFeedItems(items: any[], limit: number): ContentI
|
||||
const out = (items || []).slice(0, limit).map((it) => {
|
||||
const url = it.link || "";
|
||||
const id = (it.guid || it.id || url).toString();
|
||||
const audioUrl = (it.enclosure?.url || "").toString();
|
||||
const publishedAt = (it.isoDate || it.pubDate || new Date(0).toISOString()).toString();
|
||||
const summary = truncate(
|
||||
(it.contentSnippet ||
|
||||
@@ -44,6 +45,7 @@ export function normalizePodcastFeedItems(items: any[], limit: number): ContentI
|
||||
summary: summary || undefined,
|
||||
publishedAt: new Date(publishedAt).toISOString(),
|
||||
thumbnailUrl: (it.itunes?.image || undefined) as string | undefined,
|
||||
audioUrl: audioUrl || undefined,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const LINKS = {
|
||||
youtubeChannel: "https://www.youtube.com/santhoshj",
|
||||
instagramProfile: "https://www.instagram.com/santhoshjanan/",
|
||||
podcast: "https://podcasters.spotify.com/pod/show/irregularmind", // default; override in CTA props if needed
|
||||
podcast: "https://creators.spotify.com/pod/profile/the-irregular-mind/",
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import CtaLink from "../components/CtaLink.astro";
|
||||
import ContentCard from "../components/ContentCard.astro";
|
||||
import InstagramEmbed from "../components/InstagramEmbed.astro";
|
||||
import MediaModal from "../components/MediaModal.astro";
|
||||
import { readContentCache } from "../lib/content/cache";
|
||||
import {
|
||||
newestItems,
|
||||
@@ -240,4 +241,6 @@ const newYorkTime = new Date(cache.generatedAt).toLocaleString('en-US', options)
|
||||
<CtaLink platform="podcast" placement="footer_cta" url={LINKS.podcast} label="Podcast" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<MediaModal />
|
||||
</BaseLayout>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import ContentCard from "../components/ContentCard.astro";
|
||||
import MediaModal from "../components/MediaModal.astro";
|
||||
import { readContentCache } from "../lib/content/cache";
|
||||
import { podcastEpisodes } from "../lib/content/selectors";
|
||||
|
||||
@@ -35,4 +36,6 @@ const episodes = podcastEpisodes(cache).sort(
|
||||
)
|
||||
}
|
||||
</section>
|
||||
|
||||
<MediaModal />
|
||||
</BaseLayout>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import ContentCard from "../components/ContentCard.astro";
|
||||
import MediaModal from "../components/MediaModal.astro";
|
||||
import { readContentCache } from "../lib/content/cache";
|
||||
import { youtubeVideos } from "../lib/content/selectors";
|
||||
|
||||
@@ -35,4 +36,6 @@ const videos = youtubeVideos(cache).sort(
|
||||
)
|
||||
}
|
||||
</section>
|
||||
|
||||
<MediaModal />
|
||||
</BaseLayout>
|
||||
|
||||
Reference in New Issue
Block a user