diff --git a/apps/website/src/content.config.ts b/apps/website/src/content.config.ts
index 372b3f84f..d9e8b0817 100644
--- a/apps/website/src/content.config.ts
+++ b/apps/website/src/content.config.ts
@@ -9,6 +9,7 @@ const blog = defineCollection({
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
author: z.string().default('4gray'),
+ featured: z.boolean().default(false),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
diff --git a/apps/website/src/content/blog/blog-components-demo.mdx b/apps/website/src/content/blog/blog-components-demo.mdx
new file mode 100644
index 000000000..638ce22a8
--- /dev/null
+++ b/apps/website/src/content/blog/blog-components-demo.mdx
@@ -0,0 +1,207 @@
+---
+title: Blog Components Demo (MDX)
+description: Reusable MDX components for IPTVnator blog posts including alerts, CTA buttons, and terminal command blocks.
+pubDate: 2026-02-15
+author: 4gray
+tags:
+- blog
+- mdx
+- docs
+draft: true
+---
+
+import Alert from '../../components/blog/Alert.astro';
+import PostButton from '../../components/blog/PostButton.astro';
+import TerminalBlock from '../../components/blog/TerminalBlock.astro';
+import StepRail from '../../components/blog/StepRail.astro';
+import LinkCards from '../../components/blog/LinkCards.astro';
+import IconFeatureGrid from '../../components/blog/IconFeatureGrid.astro';
+import ReleaseMeta from '../../components/blog/ReleaseMeta.astro';
+import StatusPill from '../../components/blog/StatusPill.astro';
+import ChangeTable from '../../components/blog/ChangeTable.astro';
+import CopyCommand from '../../components/blog/CopyCommand.astro';
+import ImageGallery from '../../components/blog/ImageGallery.astro';
+import Spoiler from '../../components/blog/Spoiler.astro';
+import FaqAccordion from '../../components/blog/FaqAccordion.astro';
+
+This post demonstrates reusable UI components you can use in `.mdx` blog posts.
+
+## Alert panel
+
+
+ Use alerts to highlight important notes without breaking the reading flow.
+
+
+
+ Back up playlists, credentials, and portal URLs before installing major updates.
+
+
+## Release meta
+
+
+
+## Status pills
+
+
+
+
+
+
+
+
+
+## Terminal code block
+
+
+$ xattr -c /Applications/IPTVnator.app
+
+
+## Copy command
+
+
+
+## Button / CTA
+
+
+
+
+
+## Step rail
+
+
+
+## Link cards
+
+
+
+## Icon feature grid
+
+
+
+## Change table
+
+
+
+## Image gallery
+
+
+
+## Spoiler block
+
+
+ If launch issues persist, verify the app path and run `xattr -c` on the exact `.app` bundle location.
+
+
+## FAQ accordion
+
+
+
+## When to use MDX
+
+- Use `.md` for plain posts
+- Use `.mdx` when you want custom components, richer callouts, or advanced layout blocks
diff --git a/apps/website/src/content/blog/epg-guide.md b/apps/website/src/content/blog/epg-guide.mdx
similarity index 82%
rename from apps/website/src/content/blog/epg-guide.md
rename to apps/website/src/content/blog/epg-guide.mdx
index eaf37a827..abfa216c3 100644
--- a/apps/website/src/content/blog/epg-guide.md
+++ b/apps/website/src/content/blog/epg-guide.mdx
@@ -1,7 +1,7 @@
---
title: Understanding the Electronic Program Guide
description: Learn how IPTVnator uses XMLTV data to display current and upcoming programs alongside your live TV channels.
-pubDate: 2026-02-15
+pubDate: 2026-02-14
author: 4gray
heroImage: /iptvnator/blog/epg.png
tags:
@@ -10,12 +10,19 @@ tags:
draft: false
---
+import Alert from '../../components/blog/Alert.astro';
+import PostButton from '../../components/blog/PostButton.astro';
+
One of the most useful features in IPTVnator is the built-in Electronic Program Guide (EPG). Here is how it works and how to set it up.
## What is EPG?
The Electronic Program Guide displays a schedule of current and upcoming TV programs for your channels. IPTVnator supports EPG data in the **XMLTV format**, which is the standard used by most IPTV providers.
+
+ Most providers expose EPG as `.xml` or compressed `.xml.gz` files.
+
+
## Setting up EPG
To enable EPG in IPTVnator:
@@ -41,8 +48,11 @@ IPTVnator matches EPG data to channels using the `tvg-id` attribute from your M3
## Tips
-- EPG files can be large โ IPTVnator caches parsed data in the database (Electron)
+- EPG files can be large โ IPTVnator caches parsed data in the database (Electron)
- You can add multiple EPG sources per playlist
- The EPG automatically refreshes periodically to keep the schedule up to date
-Check the [GitHub repository](https://github.com/4gray/iptvnator) for more information on supported EPG formats.
+
diff --git a/apps/website/src/content/blog/macos-arm-app-damaged-fix.mdx b/apps/website/src/content/blog/macos-arm-app-damaged-fix.mdx
new file mode 100644
index 000000000..e4f77e75d
--- /dev/null
+++ b/apps/website/src/content/blog/macos-arm-app-damaged-fix.mdx
@@ -0,0 +1,47 @@
+---
+title: macOS ARM Users - Fix for "App is Damaged" Error
+description: If IPTVnator shows "app is damaged and can't be opened" on Apple Silicon Macs, run one Terminal command to remove quarantine and launch safely.
+pubDate: 2026-02-15
+author: 4gray
+heroImage: /iptvnator/blog/macos-app-damaged.jpg
+tags:
+- macos
+- troubleshooting
+- installation
+draft: false
+---
+
+import Alert from '../../components/blog/Alert.astro';
+import PostButton from '../../components/blog/PostButton.astro';
+import TerminalBlock from '../../components/blog/TerminalBlock.astro';
+
+If you are on macOS ARM (Apple Silicon) and IPTVnator shows **"app is damaged and can't be opened"**, use this quick fix.
+
+## TL;DR
+
+
+$ xattr -c /Applications/IPTVnator.app
+
+
+## Why this happens
+
+IPTVnator is currently not code-signed for macOS. After download, Gatekeeper adds a quarantine attribute, and macOS can block launch with a generic "damaged" message.
+
+To distribute a fully signed and notarized macOS app, Apple requires an active Apple Developer subscription (currently **$99/year**). As an open-source project, IPTVnator does not currently use that signing pipeline.
+
+## What the command does
+
+The command above removes the quarantine attribute from the app bundle:
+
+- It does **not** modify IPTVnator binaries
+- It tells your Mac to trust the app you intentionally downloaded
+- It is the standard local workaround for unsigned apps
+
+
+ Only run this command for apps you trust and downloaded from a trusted source.
+
+
+
diff --git a/apps/website/src/content/blog/v0-18-release-notes.mdx b/apps/website/src/content/blog/v0-18-release-notes.mdx
new file mode 100644
index 000000000..26fd361e2
--- /dev/null
+++ b/apps/website/src/content/blog/v0-18-release-notes.mdx
@@ -0,0 +1,137 @@
+---
+title: v0.18 - Release Notes
+description: IPTVnator v0.18 brings a new playlist switcher UI, large EPG improvements, Xtream and Stalker updates, and multiple UX fixes across the app.
+featured: true
+pubDate: 2026-01-14
+author: 4gray
+heroImage: /iptvnator/blog/iptvnator-v18.jpeg
+tags:
+- release
+- release-notes
+- v0.18
+draft: false
+---
+
+import Alert from '../../components/blog/Alert.astro';
+import LinkCards from '../../components/blog/LinkCards.astro';
+import ReleaseMeta from '../../components/blog/ReleaseMeta.astro';
+import StatusPill from '../../components/blog/StatusPill.astro';
+import ChangeTable from '../../components/blog/ChangeTable.astro';
+
+IPTVnator **v0.18** is out.
+
+
+
+## Support the project
+
+- **GitHub Sponsors**: [https://github.com/sponsors/4gray](https://github.com/sponsors/4gray)
+- **Buy me a coffee**: [https://ko-fi.com/4gray](https://ko-fi.com/4gray)
+- **USDT (TRC20)**: `TLz5rTS7QTxYGfRH1Pm6Hs9fewqstDM47c`
+
+
+
+## Summary
+
+
+
+## General
+
+
+
+- New playlist switcher UI: a unified and cleaner way to switch between playlists
+- Global favorites moved to the header for quicker access
+- Sort and filter options merged into a single header menu
+
+## M3U
+
+
+
+- Improved EPG download and parsing to better handle large EPG files
+- Added a popup dialog showing EPG download progress
+- Restored quality selector for the Video.js player
+
+## Xtream & Stalker
+
+
+
+- Improved look and feel of the details view for VODs and TV shows
+- Fixed opening favorite items with embedded players
+
+## Xtream
+
+
+
+- Category management: hide/show categories
+- Display item counts per category
+- New separate view for recently added items
+- Recently added items are shown when no category is selected
+- Fixed empty categories issue
+- Restored import progress counter
+
+## Stalker
+
+
+
+- Added support for playlists that require device IDs and signatures
+- Some playlists are still not supported; more testing data is needed
+
+
+ Please back up your playlists, credentials, URLs, and other important data.
+
+
+## Download
+
+
diff --git a/apps/website/src/content/blog/welcome.md b/apps/website/src/content/blog/welcome.mdx
similarity index 68%
rename from apps/website/src/content/blog/welcome.md
rename to apps/website/src/content/blog/welcome.mdx
index aacc852e1..fb0a66b36 100644
--- a/apps/website/src/content/blog/welcome.md
+++ b/apps/website/src/content/blog/welcome.mdx
@@ -9,7 +9,14 @@ tags:
draft: false
---
-We are excited to launch the official IPTVnator website and developer blog!
+import Alert from '../../components/blog/Alert.astro';
+import PostButton from '../../components/blog/PostButton.astro';
+
+We are excited to launch the official IPTVnator website and developer blog.
+
+
+ You now have one place for releases, tutorials, troubleshooting guides, and project updates.
+
## What is IPTVnator?
@@ -26,4 +33,9 @@ This blog will be the home for:
## Get involved
-IPTVnator is open source and we welcome contributions! Check out our [GitHub repository](https://github.com/4gray/iptvnator) to get started.
+IPTVnator is open source and we welcome contributions.
+
+
diff --git a/apps/website/src/pages/blog/[...slug].astro b/apps/website/src/pages/blog/[...slug].astro
index a69a8c737..6e585a294 100644
--- a/apps/website/src/pages/blog/[...slug].astro
+++ b/apps/website/src/pages/blog/[...slug].astro
@@ -3,9 +3,10 @@ import { getCollection, render } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
export async function getStaticPaths() {
+ const includeDrafts = import.meta.env.DEV || import.meta.env.PUBLIC_INCLUDE_DRAFTS === 'true';
const posts = await getCollection('blog');
return posts
- .filter((post) => !post.data.draft)
+ .filter((post) => includeDrafts || !post.data.draft)
.map((post) => ({
params: { slug: post.id },
props: post,
diff --git a/apps/website/src/pages/blog/index.astro b/apps/website/src/pages/blog/index.astro
index 9a354a7a8..2b2f03f7d 100644
--- a/apps/website/src/pages/blog/index.astro
+++ b/apps/website/src/pages/blog/index.astro
@@ -4,11 +4,14 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
import FeaturedBlogPost from '../../components/FeaturedBlogPost.astro';
import BlogPostCard from '../../components/BlogPostCard.astro';
+const includeDrafts = import.meta.env.DEV || import.meta.env.PUBLIC_INCLUDE_DRAFTS === 'true';
+
const posts = (await getCollection('blog'))
- .filter((post) => !post.data.draft)
+ .filter((post) => includeDrafts || !post.data.draft)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
-const [featuredPost, ...remainingPosts] = posts;
+const featuredPost = posts.find((post) => post.data.featured) ?? posts[0];
+const remainingPosts = featuredPost ? posts.filter((post) => post.id !== featuredPost.id) : [];
---
!post.data.draft)
- .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
- .slice(0, 3);
+const includeDrafts = import.meta.env.DEV || import.meta.env.PUBLIC_INCLUDE_DRAFTS === 'true';
-const [featuredPost, ...otherPosts] = posts;
+const allPosts = (await getCollection('blog'))
+ .filter((post) => includeDrafts || !post.data.draft)
+ .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
+
+const featuredPost = allPosts.find((post) => post.data.featured) ?? allPosts[0];
+const otherPosts = featuredPost ? allPosts.filter((post) => post.id !== featuredPost.id).slice(0, 2) : [];
+const posts = featuredPost ? [featuredPost, ...otherPosts] : allPosts.slice(0, 3);
---