diff --git a/src/lib/cards/index.ts b/src/lib/cards/index.ts
index 7c9b775..14e3399 100644
--- a/src/lib/cards/index.ts
+++ b/src/lib/cards/index.ts
@@ -38,6 +38,7 @@ import { CountdownCardDefinition } from './utilities/CountdownCard';
import { SpotifyCardDefinition } from './media/SpotifyCard';
import { AppleMusicCardDefinition } from './media/AppleMusicCard';
import { SoundCloudCardDefinition } from './media/SoundCloudCard';
+import { BandcampCardDefinition } from './media/BandcampCard';
import { ButtonCardDefinition } from './utilities/ButtonCard';
import { GuestbookCardDefinition } from './social/GuestbookCard';
import { FriendsCardDefinition } from './social/FriendsCard';
@@ -109,6 +110,7 @@ export const AllCardDefinitions = [
SpotifyCardDefinition,
AppleMusicCardDefinition,
SoundCloudCardDefinition,
+ BandcampCardDefinition,
// Model3DCardDefinition
FriendsCardDefinition,
GitHubContributorsCardDefinition,
diff --git a/src/lib/cards/media/BandcampCard/BandcampCard.svelte b/src/lib/cards/media/BandcampCard/BandcampCard.svelte
new file mode 100644
index 0000000..ce721c2
--- /dev/null
+++ b/src/lib/cards/media/BandcampCard/BandcampCard.svelte
@@ -0,0 +1,28 @@
+
+
+{#if src}
+
+
+
+{:else}
+
+ Missing Bandcamp URL
+
+{/if}
diff --git a/src/lib/cards/media/BandcampCard/CreateBandcampCardModal.svelte b/src/lib/cards/media/BandcampCard/CreateBandcampCardModal.svelte
new file mode 100644
index 0000000..7694c18
--- /dev/null
+++ b/src/lib/cards/media/BandcampCard/CreateBandcampCardModal.svelte
@@ -0,0 +1,49 @@
+
+
+
+ Enter a Bandcamp album or track URL
+ {
+ if (e.key === 'Enter' && checkUrl()) oncreate();
+ }}
+ />
+
+ {#if errorMessage}
+ {errorMessage}
+ {/if}
+
+
+
+
+
+
diff --git a/src/lib/cards/media/BandcampCard/index.ts b/src/lib/cards/media/BandcampCard/index.ts
new file mode 100644
index 0000000..94ab49c
--- /dev/null
+++ b/src/lib/cards/media/BandcampCard/index.ts
@@ -0,0 +1,47 @@
+import type { CardDefinition } from '../../types';
+import BandcampCard from './BandcampCard.svelte';
+import CreateBandcampCardModal from './CreateBandcampCardModal.svelte';
+import { isBandcampUrl } from './shared';
+
+const cardType = 'bandcamp-embed';
+
+export const BandcampCardDefinition = {
+ type: cardType,
+ contentComponent: BandcampCard,
+ creationModalComponent: CreateBandcampCardModal,
+ createNew: (item) => {
+ item.cardType = cardType;
+ item.cardData = {};
+ item.w = 4;
+ item.mobileW = 8;
+ item.h = 5;
+ item.mobileH = 10;
+ },
+
+ onUrlHandler: (url, item) => {
+ if (!isBandcampUrl(url)) return null;
+
+ item.cardData.href = url;
+
+ item.w = 4;
+ item.mobileW = 8;
+ item.h = 5;
+ item.mobileH = 10;
+
+ return item;
+ },
+
+ urlHandlerPriority: 2,
+
+ canChange: (item) => isBandcampUrl(item.cardData?.href),
+ change: (item) => item,
+
+ name: 'Bandcamp Embed',
+ canResize: true,
+ minW: 3,
+ minH: 4,
+
+ keywords: ['music', 'album', 'track', 'bandcamp', 'audio', 'artist'],
+ groups: ['Media'],
+ icon: ``
+} as CardDefinition & { type: typeof cardType };
diff --git a/src/lib/cards/media/BandcampCard/shared.ts b/src/lib/cards/media/BandcampCard/shared.ts
new file mode 100644
index 0000000..7400223
--- /dev/null
+++ b/src/lib/cards/media/BandcampCard/shared.ts
@@ -0,0 +1,26 @@
+// Bandcamp's EmbeddedPlayer resolves a public album/track URL server-side via
+// its `url=` param, so no scraping/ID lookup is needed (same approach as the
+// first-party bluesky social-app embed player).
+
+// Match bandcamp.com subdomain stores only (matching bluesky's behavior).
+// Custom domains are intentionally excluded to avoid hijacking unrelated
+// `/album/` or `/track/` URLs.
+const BANDCAMP_HOST = /^[a-z\d][a-z\d-]{2,}[a-z\d]\.bandcamp\.com$/i;
+
+export function isBandcampUrl(url: string | undefined): boolean {
+ if (!url) return false;
+ try {
+ const u = new URL(url);
+ if (!BANDCAMP_HOST.test(u.hostname)) return false;
+ const kind = u.pathname.split('/')[1];
+ return kind === 'album' || kind === 'track';
+ } catch {
+ return false;
+ }
+}
+
+export function bandcampPlayerUri(url: string): string {
+ return `https://bandcamp.com/EmbeddedPlayer/url=${encodeURIComponent(
+ url
+ )}/size=large/bgcol=333333/linkcol=0f91ff/tracklist=true/transparent=true/`;
+}