Index: src/middleware/auth.js
===================================================================
--- src/middleware/auth.js	(revision ccbd6a67197d7fdb179b5f427667654b00df27ae)
+++ src/middleware/auth.js	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
@@ -2,4 +2,6 @@
  * Auth middleware
  */
+
+import db from '../config/database.js';
 
 /**
@@ -42,2 +44,25 @@
   next();
 }
+
+// Mag de ingelogde user de HUIDIGE site (res.locals.site) beheren? god altijd;
+// anders alleen de owner van die site. Gebruikt voor site-gescopete beheerroutes
+// die een artiest via /user/<eigen-slug>/admin/... bereikt (res.locals.site is dan
+// z'n eigen site; een vreemde slug levert een andere site -> 403).
+export function requireSiteManager(req, res, next) {
+  if (!req.session?.user) return loginRedirect(req, res);
+  const u = req.session.user;
+  if (u.role === 'god') return next();
+  const site = res.locals.site;
+  if (site && site.owner_id === u.id) return next();
+  return res.status(403).send('Geen toegang tot deze site.');
+}
+
+// Idem, maar de site wordt bepaald door de :slug-parameter (bv. site-edit).
+export function requireSiteManagerBySlug(req, res, next) {
+  if (!req.session?.user) return loginRedirect(req, res);
+  const u = req.session.user;
+  if (u.role === 'god') return next();
+  const site = db.prepare('SELECT owner_id FROM sites WHERE slug = ?').get(req.params.slug);
+  if (site && site.owner_id === u.id) return next();
+  return res.status(403).send('Geen toegang tot deze site.');
+}
Index: src/middleware/render.js
===================================================================
--- src/middleware/render.js	(revision ccbd6a67197d7fdb179b5f427667654b00df27ae)
+++ src/middleware/render.js	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
@@ -13,4 +13,5 @@
 import { fileURLToPath } from 'url';
 import ejs from 'ejs';
+import db from '../config/database.js';
 import PermissionsService from '../services/PermissionsService.js';
 import { PLATFORMS as PLATFORMS_CATALOG } from '../services/PlatformIcons.js';
@@ -36,7 +37,14 @@
   const isPartial = req.headers['hx-request'] === 'true' || req.query.partial === '1';
 
+  // Bezit deze (niet-god) user een eigen site? Bepaalt of 'ie een "Beheer"-
+  // ingang ziet (artiest-zelfbeheer). god ziet beheer sowieso (op rol).
+  const _u = req.session?.user || null;
+  const userOwnsSite = !!(_u && _u.role !== 'god' &&
+    db.prepare('SELECT 1 FROM sites WHERE owner_id = ? LIMIT 1').get(_u.id));
+
   // Common locals
   const locals = {
-    user: req.session?.user || null,
+    user: _u,
+    userOwnsSite,
     site: data.site || res.locals.site || null,
     audioTracks: data.audioTracks || res.locals.audioTracks || [],
Index: src/routes/admin-comments.js
===================================================================
--- src/routes/admin-comments.js	(revision ccbd6a67197d7fdb179b5f427667654b00df27ae)
+++ src/routes/admin-comments.js	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
@@ -15,9 +15,9 @@
 import db from '../config/database.js';
 import { renderPage } from '../middleware/render.js';
-import { requireGod } from '../middleware/auth.js';
+import { requireSiteManager } from '../middleware/auth.js';
 
 const router = express.Router();
 
-router.get('/', requireGod, (req, res) => {
+router.get('/', requireSiteManager, (req, res) => {
   const site = res.locals.site;
   if (!site) return res.status(404).send('No site');
@@ -61,4 +61,5 @@
   const site = res.locals.site;
   if (!site) return res.status(404).send('No site');
+  const base = res.locals.siteUrlBase || ''; // /user/<slug> in hub-artiestcontext, anders ''
 
   const row = db.prepare(`
@@ -67,14 +68,14 @@
   `).get(req.params.id, site.id);
 
-  if (!row) return res.redirect('/admin/comments?error=Not+found');
+  if (!row) return res.redirect(base + '/admin/comments?error=Not+found');
 
   db.prepare(
     'UPDATE comments SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
   ).run(status, req.params.id);
-  res.redirect('/admin/comments?success=' + encodeURIComponent('Comment ' + status));
+  res.redirect(base + '/admin/comments?success=' + encodeURIComponent('Comment ' + status));
 }
 
-router.post('/:id/approve', requireGod, (req, res) => setStatus(req, res, 'approved'));
-router.post('/:id/reject',  requireGod, (req, res) => setStatus(req, res, 'rejected'));
+router.post('/:id/approve', requireSiteManager, (req, res) => setStatus(req, res, 'approved'));
+router.post('/:id/reject',  requireSiteManager, (req, res) => setStatus(req, res, 'rejected'));
 
 export default router;
Index: src/routes/admin-sites.js
===================================================================
--- src/routes/admin-sites.js	(revision ccbd6a67197d7fdb179b5f427667654b00df27ae)
+++ src/routes/admin-sites.js	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
@@ -22,5 +22,5 @@
 import db from '../config/database.js';
 import { renderPage } from '../middleware/render.js';
-import { requireGod } from '../middleware/auth.js';
+import { requireGod, requireAuth, requireSiteManagerBySlug } from '../middleware/auth.js';
 import ThemeService from '../services/ThemeService.js';
 import { listPlatforms, PLATFORMS } from '../services/PlatformIcons.js';
@@ -82,5 +82,5 @@
 // holds the URL string in `profile_photo` — this endpoint just stores the
 // file and hands back a URL that the form can paste into the input field.
-router.post('/upload-photo', requireGod, (req, res) => {
+router.post('/upload-photo', requireAuth, (req, res) => {
   photoUpload.single('photo')(req, res, (err) => {
     if (err) return res.status(400).json({ ok: false, error: err.message });
@@ -218,5 +218,5 @@
 
 // ==================== EDIT (form) ====================
-router.get('/:slug/edit', requireGod, (req, res) => {
+router.get('/:slug/edit', requireSiteManagerBySlug, (req, res) => {
   const site = db.prepare('SELECT * FROM sites WHERE slug = ?').get(req.params.slug);
   if (!site) return res.redirect('/admin/sites?error=Not+found');
@@ -242,5 +242,5 @@
 
 // ==================== SAVE ====================
-router.post('/:slug/save', requireGod, (req, res) => {
+router.post('/:slug/save', requireSiteManagerBySlug, (req, res) => {
   const site = db.prepare('SELECT id FROM sites WHERE slug = ?').get(req.params.slug);
   if (!site) return res.redirect('/admin/sites?error=Not+found');
Index: src/routes/admin.js
===================================================================
--- src/routes/admin.js	(revision ccbd6a67197d7fdb179b5f427667654b00df27ae)
+++ src/routes/admin.js	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
@@ -8,10 +8,32 @@
 import db from '../config/database.js';
 import { renderPage } from '../middleware/render.js';
-import { requireGod } from '../middleware/auth.js';
+import { requireAuth } from '../middleware/auth.js';
 import { getTenancy } from '../services/SettingsService.js';
 
 const router = express.Router();
 
-router.get('/', requireGod, (req, res) => {
+router.get('/', requireAuth, (req, res) => {
+  const user = req.session.user;
+
+  // Niet-god: een artiest die een eigen site bezit krijgt een "Mijn PrutFolio"-
+  // dashboard, gescopet op z'n eigen site. Bezit 'ie geen site -> geen beheer.
+  if (user.role !== 'god') {
+    const mySite = db.prepare(
+      'SELECT * FROM sites WHERE owner_id = ? ORDER BY created_at ASC LIMIT 1'
+    ).get(user.id);
+    if (!mySite) return res.status(403).send('Geen beheer beschikbaar voor dit account.');
+
+    const mine = {
+      posts: db.prepare("SELECT COUNT(*) AS c FROM posts WHERE site_id = ?").get(mySite.id).c,
+      published: db.prepare("SELECT COUNT(*) AS c FROM posts WHERE site_id = ? AND status = 'published'").get(mySite.id).c,
+    };
+    return renderPage(req, res, 'pages/my-site', {
+      pageTitle: 'Mijn PrutFolio',
+      bodyClass: 'on-admin',
+      mySite,
+      mine,
+    });
+  }
+
   const tenancy = getTenancy();
 
Index: src/views/pages/admin-comments.ejs
===================================================================
--- src/views/pages/admin-comments.ejs	(revision ccbd6a67197d7fdb179b5f427667654b00df27ae)
+++ src/views/pages/admin-comments.ejs	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
@@ -26,13 +26,13 @@
             <strong><%= c.author_username %></strong>
             <% if (c.parent_comment_id) { %><span class="muted">(reply)</span><% } %>
-            on <a href="/<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a>
+            on <a href="<%= siteUrlBase %>/<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a>
             <span class="muted">&middot; <%= formatDateTime(c.created_at) %></span>
           </div>
           <blockquote class="moderation-content"><%= c.content %></blockquote>
           <div class="moderation-actions">
-            <form method="post" action="/admin/comments/<%= c.id %>/approve" style="display:inline">
+            <form method="post" action="<%= siteUrlBase %>/admin/comments/<%= c.id %>/approve" style="display:inline">
               <button type="submit" class="btn btn-primary">Approve</button>
             </form>
-            <form method="post" action="/admin/comments/<%= c.id %>/reject" style="display:inline">
+            <form method="post" action="<%= siteUrlBase %>/admin/comments/<%= c.id %>/reject" style="display:inline">
               <button type="submit" class="btn btn-danger">Reject</button>
             </form>
@@ -55,5 +55,5 @@
           <tr class="status-<%= c.status %>">
             <td><%= c.author_username %></td>
-            <td><a href="/<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a></td>
+            <td><a href="<%= siteUrlBase %>/<%= c.post_slug %>"><%= c.post_title || c.post_slug %></a></td>
             <td>
               <span class="status-pill status-pill--<%= c.status %>"><%= c.status %></span>
Index: src/views/pages/hub-home.ejs
===================================================================
--- src/views/pages/hub-home.ejs	(revision ccbd6a67197d7fdb179b5f427667654b00df27ae)
+++ src/views/pages/hub-home.ejs	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
@@ -5,5 +5,5 @@
     <div class="hub-nav-inner container">
       <% if (user) { %>
-        <% if (user.role === 'god' || user.role === 'admin') { %>
+        <% if (user.role === 'god' || user.role === 'admin' || (typeof userOwnsSite !== 'undefined' && userOwnsSite)) { %>
           <a class="hub-nav-link" href="/admin">Beheer</a>
         <% } %>
Index: src/views/pages/my-site.ejs
===================================================================
--- src/views/pages/my-site.ejs	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
+++ src/views/pages/my-site.ejs	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
@@ -0,0 +1,34 @@
+<div class="container admin-page">
+  <p><a href="/" class="btn">&larr; Overzicht</a></p>
+  <h1>Mijn PrutFolio</h1>
+  <p class="admin-tagline"><%= mySite.title %></p>
+
+  <nav class="admin-quick-links" aria-label="Beheer-snelkoppelingen">
+    <a href="/user/<%= mySite.slug %>/posts/new" class="btn">✍️ Nieuwe post</a>
+    <a href="/admin/sites/<%= mySite.slug %>/edit" class="btn">🎨 Uiterlijk</a>
+    <a href="/user/<%= mySite.slug %>/admin/comments" class="btn">💬 Reacties</a>
+    <a href="/user/<%= mySite.slug %>" class="btn">👁️ Bekijk mijn site</a>
+    <a href="/account" class="btn">👤 Account</a>
+  </nav>
+
+  <div class="admin-stats">
+    <div class="stat-card"><div class="stat-num"><%= mine.posts %></div><div class="stat-label">Posts</div></div>
+    <div class="stat-card"><div class="stat-num"><%= mine.published %></div><div class="stat-label">Gepubliceerd</div></div>
+  </div>
+</div>
+
+<style>
+.admin-page { max-width: 1000px; margin: 3rem auto; padding: 0 1rem; }
+.admin-page h1 { font-family: var(--font-display, serif); font-size: 2rem; margin: 0 0 0.25rem; }
+.admin-tagline { color: var(--ink-muted); margin: 0 0 2rem; }
+.admin-quick-links {
+  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: 0.5rem; margin: 0 0 1.5rem; padding: 0;
+}
+.admin-quick-links .btn { justify-content: center; padding: 0.7rem 0.9rem; font-weight: 600; text-align: center; }
+.admin-quick-links .btn:hover { background: color-mix(in srgb, var(--accent) 8%, var(--paper-2)); border-color: var(--accent); }
+.admin-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; }
+.stat-card { background: var(--paper-2); border: 1px solid var(--rule); border-radius: 8px; padding: 1rem 1.25rem; text-align: center; }
+.stat-num { font-size: 2rem; font-weight: 700; color: var(--accent); line-height: 1; font-family: var(--font-display, serif); }
+.stat-label { color: var(--ink-muted); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
+</style>
Index: src/views/partials/topnav.ejs
===================================================================
--- src/views/partials/topnav.ejs	(revision ccbd6a67197d7fdb179b5f427667654b00df27ae)
+++ src/views/partials/topnav.ejs	(revision 8cb1dc7b23142c8a8ec44f7659178b51a2da4ddf)
@@ -109,5 +109,5 @@
                 <span>Account</span>
               </a>
-              <% if (user.role === 'god' || user.role === 'admin') { %>
+              <% if (user.role === 'god' || user.role === 'admin' || (typeof userOwnsSite !== 'undefined' && userOwnsSite)) { %>
                 <a href="/admin" role="menuitem" class="udi">
                   <svg class="udi-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3h7v7H3z"/><path d="M14 3h7v7h-7z"/><path d="M14 14h7v7h-7z"/><path d="M3 14h7v7H3z"/></svg>
@@ -116,7 +116,7 @@
               <% } %>
               <% if (_canPost) { %>
-                <a href="/posts/new" role="menuitem" class="udi"
-                   hx-get="/posts/new?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
-                   hx-push-url="/posts/new" hx-indicator="#pcms-loading">
+                <a href="<%= _siteUrlBase %>/posts/new" role="menuitem" class="udi"
+                   hx-get="<%= _siteUrlBase %>/posts/new?partial=1" hx-target="#pcms-main" hx-swap="innerHTML"
+                   hx-push-url="<%= _siteUrlBase %>/posts/new" hx-indicator="#pcms-loading">
                   <svg class="udi-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>
                   <span>Nieuwe post</span>
