Index: .env.example
===================================================================
--- .env.example	(revision 89d6536e83e69795d54a8021febe03e9773e6c40)
+++ .env.example	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
@@ -4,2 +4,18 @@
 DATABASE_PATH=./storage/database.sqlite
 MEDIA_PATH=./storage/media
+
+# ── Google login (OAuth2) ──────────────────────────────────────────
+# Inloggen gaat volledig via Google. Maak een OAuth-client aan in de
+# Google Cloud Console (APIs & Services -> Credentials -> OAuth client ID,
+# type "Web application") en vul hieronder in. Zonder deze waarden boot de
+# app gewoon, maar toont de loginpagina "nog niet geconfigureerd".
+#
+# GOOGLE_REDIRECT_URI moet EXACT overeenkomen met een "Authorized redirect
+# URI" in de Google-client, en is per site het eigen domein + /auth/google/callback.
+GOOGLE_CLIENT_ID=
+GOOGLE_CLIENT_SECRET=
+GOOGLE_REDIRECT_URI=https://example.com/auth/google/callback
+
+# Het Google-e-mailadres dat owner/admin (god) is op deze instance.
+# De eerste gebruiker die inlogt wordt sowieso god als de DB nog leeg is.
+ADMIN_EMAIL=
Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision 89d6536e83e69795d54a8021febe03e9773e6c40)
+++ src/config/database.js	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
@@ -39,4 +39,6 @@
   ensureColumn('users', 'reset_token', 'TEXT');
   ensureColumn('users', 'reset_token_expires', 'DATETIME');
+  // Google OAuth: koppel een Google-account aan een user (login via Google).
+  ensureColumn('users', 'google_sub', 'TEXT');
   // Site-level moderation toggle. 'trust' = auto-approve, 'moderate' = pending until reviewed.
   ensureColumn('sites', 'comments_moderation_mode', "TEXT DEFAULT 'trust'");
Index: src/config/google.js
===================================================================
--- src/config/google.js	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
+++ src/config/google.js	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
@@ -0,0 +1,56 @@
+// Google OAuth2 (per-instance). Raw via de ingebouwde fetch — geen passport-dep.
+// Config via env (per instance, in .env):
+//   GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
+//   GOOGLE_REDIRECT_URI = https://<dit-domein>/auth/google/callback
+//   ADMIN_EMAIL         = de Google-mail die owner/admin (god) is op deze instance
+// Niet geconfigureerd? Dan booten we gewoon door; /auth/google meldt netjes
+// "nog niet geconfigureerd" i.p.v. te crashen.
+
+const CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
+const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '';
+const REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI || '';
+
+const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
+const TOKEN_URL = 'https://oauth2.googleapis.com/token';
+const USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo';
+
+export function googleConfigured() {
+  return !!(CLIENT_ID && CLIENT_SECRET && REDIRECT_URI);
+}
+
+export function authorizeUrl(state) {
+  const p = new URLSearchParams({
+    client_id: CLIENT_ID,
+    redirect_uri: REDIRECT_URI,
+    response_type: 'code',
+    scope: 'openid email profile',
+    state,
+    access_type: 'online',
+    prompt: 'select_account',
+  });
+  return `${AUTH_URL}?${p.toString()}`;
+}
+
+export async function exchangeCode(code) {
+  const body = new URLSearchParams({
+    code,
+    client_id: CLIENT_ID,
+    client_secret: CLIENT_SECRET,
+    redirect_uri: REDIRECT_URI,
+    grant_type: 'authorization_code',
+  });
+  const r = await fetch(TOKEN_URL, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+    body,
+  });
+  if (!r.ok) throw new Error(`Google token-exchange faalde: ${r.status} ${await r.text().catch(() => '')}`);
+  return r.json(); // { access_token, id_token, ... }
+}
+
+// Returns { sub, email, email_verified, name, picture }.
+export async function fetchUserinfo(accessToken) {
+  const r = await fetch(USERINFO_URL, { headers: { Authorization: `Bearer ${accessToken}` } });
+  if (!r.ok) throw new Error(`Google userinfo faalde: ${r.status}`);
+  return r.json();
+}
Index: src/routes/account.js
===================================================================
--- src/routes/account.js	(revision 89d6536e83e69795d54a8021febe03e9773e6c40)
+++ src/routes/account.js	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
@@ -17,5 +17,4 @@
 import fs from 'fs';
 import { fileURLToPath } from 'url';
-import bcrypt from 'bcryptjs';
 import multer from 'multer';
 import { v4 as uuid } from 'uuid';
@@ -84,28 +83,6 @@
 // longer read or written.
 
-// ==================== CHANGE PASSWORD ====================
-router.post('/password', requireAuth, (req, res) => {
-  const { current, new_password, confirm } = req.body;
-  if (!current || !new_password || !confirm) {
-    return res.redirect('/account?error=' + encodeURIComponent('All password fields required'));
-  }
-  if (new_password.length < 8) {
-    return res.redirect('/account?error=' + encodeURIComponent('New password must be at least 8 characters'));
-  }
-  if (new_password !== confirm) {
-    return res.redirect('/account?error=' + encodeURIComponent('New passwords do not match'));
-  }
-
-  const row = db.prepare('SELECT password_hash FROM users WHERE id = ?').get(req.session.user.id);
-  if (!row || !bcrypt.compareSync(current, row.password_hash)) {
-    return res.redirect('/account?error=' + encodeURIComponent('Current password is incorrect'));
-  }
-
-  const newHash = bcrypt.hashSync(new_password, 10);
-  db.prepare('UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
-    .run(newHash, req.session.user.id);
-
-  res.redirect('/account?success=' + encodeURIComponent('Password changed'));
-});
+// Wachtwoord-wijzigen is verwijderd: inloggen gaat sinds de Google-OAuth-migratie
+// volledig via Google, er is geen wachtwoord meer om te wijzigen.
 
 // ==================== UPLOAD AVATAR ====================
Index: src/routes/auth.js
===================================================================
--- src/routes/auth.js	(revision 89d6536e83e69795d54a8021febe03e9773e6c40)
+++ src/routes/auth.js	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
@@ -1,270 +1,127 @@
 import express from 'express';
 import crypto from 'crypto';
-import bcrypt from 'bcryptjs';
 import { v4 as uuid } from 'uuid';
 import db from '../config/database.js';
 import { renderPage } from '../middleware/render.js';
-import { loginLimiter, registerLimiter } from '../middleware/rate-limit.js';
 import { safeNext } from '../middleware/auth.js';
+import { googleConfigured, authorizeUrl, exchangeCode, fetchUserinfo } from '../config/google.js';
 
 const router = express.Router();
+const ADMIN_EMAIL = (process.env.ADMIN_EMAIL || '').trim().toLowerCase();
 
-// ==================== LOGIN ====================
+// ==================== LOGIN (alleen Google) ====================
+// Het oude username/wachtwoord-systeem is verwijderd; inloggen gaat via Google.
 router.get('/login', (req, res) => {
   const next = safeNext(req.query.next) || '';
   if (req.session.user) return res.redirect(next || '/');
   renderPage(req, res, 'pages/auth-login', {
-    pageTitle: 'Login',
+    pageTitle: 'Inloggen',
     bodyClass: 'on-special',
-    error: null,
-    success: req.query.success || null,
-    username: '',
+    googleReady: googleConfigured(),
+    error: req.query.error || null,
     next,
   });
 });
 
-router.post('/login', loginLimiter, (req, res) => {
-  const { username, password } = req.body;
-  const next = safeNext(req.body.next) || '';
-
-  if (!username || !password) {
-    res.status(400);
-    return renderPage(req, res, 'pages/auth-login', {
-      pageTitle: 'Login',
-      bodyClass: 'on-special',
-      error: 'Username and password required',
-      username: username || '',
-      next,
-    });
+// Start de Google OAuth-flow.
+router.get('/google', (req, res) => {
+  if (!googleConfigured()) {
+    return res.redirect('/auth/login?error=' + encodeURIComponent('Google-login is op deze site nog niet geconfigureerd.'));
   }
-
-  const user = db.prepare(
-    'SELECT * FROM users WHERE username = ? OR email = ?'
-  ).get(username, username);
-
-  if (!user || !bcrypt.compareSync(password, user.password_hash)) {
-    res.status(401);
-    return renderPage(req, res, 'pages/auth-login', {
-      pageTitle: 'Login',
-      bodyClass: 'on-special',
-      error: 'Invalid credentials',
-      username: username,
-      next,
-    });
-  }
-
-  req.session.user = {
-    id: user.id,
-    username: user.username,
-    email: user.email,
-    role: user.role,
-    avatar_url: user.avatar_url,
-    palette: user.palette,
-    theme: user.theme,
-  };
-
-  res.redirect(next || '/');
+  const next = safeNext(req.query.next) || '';
+  const state = crypto.randomBytes(16).toString('hex');
+  req.session.oauthState = state;
+  req.session.oauthNext = next;
+  res.redirect(authorizeUrl(state));
 });
 
-// ==================== REGISTER ====================
-router.get('/register', (req, res) => {
-  const next = safeNext(req.query.next) || '';
-  if (req.session.user) return res.redirect(next || '/');
-  renderPage(req, res, 'pages/auth-register', {
-    pageTitle: 'Register',
-    bodyClass: 'on-special',
-    error: null,
-    username: '',
-    email: '',
-    next,
-  });
-});
+// Leid een geldige, unieke username af uit naam/e-mail (schema vereist username).
+function uniqueUsername(base) {
+  let u = String(base || 'luisteraar').toLowerCase().replace(/[^a-z0-9_-]/g, '').slice(0, 28);
+  if (u.length < 3) u = 'luisteraar';
+  let candidate = u, n = 1;
+  while (db.prepare('SELECT 1 FROM users WHERE username = ?').get(candidate)) {
+    candidate = (u.slice(0, 26) + n).slice(0, 32);
+    n++;
+  }
+  return candidate;
+}
 
-router.post('/register', registerLimiter, (req, res) => {
-  const { username, email, password } = req.body;
-  const next = safeNext(req.body.next) || '';
+// Google callback: wissel code in, haal profiel, vind-of-maak user (op e-mail),
+// zet de sessie. ADMIN_EMAIL bepaalt wie owner/admin (god) is.
+router.get('/google/callback', async (req, res) => {
+  const fail = (msg) => res.redirect('/auth/login?error=' + encodeURIComponent(msg));
+  try {
+    const { code, state } = req.query;
+    if (!code || !state || state !== req.session.oauthState) {
+      return fail('Login afgebroken of ongeldige sessie.');
+    }
+    const next = safeNext(req.session.oauthNext) || '';
+    delete req.session.oauthState;
+    delete req.session.oauthNext;
 
-  // Validation
-  if (!username || !email || !password) {
-    return renderPage(req, res, 'pages/auth-register', {
-      pageTitle: 'Register',
-      bodyClass: 'on-special',
-      error: 'All fields required',
-      username: username || '',
-      email: email || '',
-      next,
-    });
+    const tok = await exchangeCode(String(code));
+    const info = await fetchUserinfo(tok.access_token);
+    const email = (info.email || '').trim().toLowerCase();
+    if (!email || info.email_verified === false) {
+      return fail('Geen geverifieerd Google-e-mailadres ontvangen.');
+    }
+
+    const isAdmin = !!ADMIN_EMAIL && email === ADMIN_EMAIL;
+    let user = db.prepare('SELECT * FROM users WHERE LOWER(email) = ?').get(email);
+
+    if (!user) {
+      const userCount = db.prepare('SELECT COUNT(*) AS c FROM users').get().c;
+      const role = (isAdmin || userCount === 0) ? 'god' : 'member';
+      const userId = uuid();
+      const username = uniqueUsername(info.name || email.split('@')[0]);
+      // password_hash is NOT NULL in het schema; we zetten een onbruikbare
+      // sentinel (er is geen wachtwoord-login meer).
+      db.prepare(`
+        INSERT INTO users (id, username, email, password_hash, role, avatar_url, theme, palette, google_sub)
+        VALUES (?, ?, ?, '!google-oauth', ?, ?, 'dark', 'sage', ?)
+      `).run(userId, username, info.email || email, role, info.picture || null, info.sub || null);
+
+      // Eerste/admin-user krijgt een persoonlijke site (zoals de oude flow), maar
+      // alleen als er nog geen site bestaat.
+      if (role === 'god' && !db.prepare('SELECT 1 FROM sites LIMIT 1').get()) {
+        const siteId = uuid();
+        db.prepare(`
+          INSERT INTO sites (id, slug, title, description, owner_id, palette, accent, language)
+          VALUES (?, ?, ?, ?, ?, 'sage', '#c2410c', 'nl')
+        `).run(siteId, username.toLowerCase(), username + "'s Site", 'Welkom', userId);
+        db.prepare(`INSERT INTO site_members (site_id, user_id, role) VALUES (?, ?, 'admin')`).run(siteId, userId);
+      }
+      user = db.prepare('SELECT * FROM users WHERE id = ?').get(userId);
+    } else {
+      // Bestaande user (gekoppeld op e-mail): koppel Google-id + avatar als die
+      // nog ontbreken, en promoveer naar god als dit ADMIN_EMAIL is.
+      db.prepare(`
+        UPDATE users
+        SET google_sub = COALESCE(google_sub, ?),
+            avatar_url = COALESCE(avatar_url, ?),
+            updated_at = CURRENT_TIMESTAMP
+        WHERE id = ?
+      `).run(info.sub || null, info.picture || null, user.id);
+      if (isAdmin && user.role !== 'god') {
+        db.prepare("UPDATE users SET role = 'god' WHERE id = ?").run(user.id);
+        user.role = 'god';
+      }
+    }
+
+    req.session.user = {
+      id: user.id,
+      username: user.username,
+      email: user.email,
+      role: user.role,
+      avatar_url: user.avatar_url,
+      palette: user.palette,
+      theme: user.theme,
+    };
+    res.redirect(next || '/');
+  } catch (e) {
+    console.error('[auth/google/callback]', e.message);
+    fail('Google-login mislukt. Probeer opnieuw.');
   }
-
-  if (!/^[a-z0-9_-]{3,32}$/i.test(username)) {
-    return renderPage(req, res, 'pages/auth-register', {
-      pageTitle: 'Register',
-      bodyClass: 'on-special',
-      error: 'Username: 3-32 characters, letters/numbers/underscore/dash only',
-      username, email, next,
-    });
-  }
-
-  if (password.length < 8) {
-    return renderPage(req, res, 'pages/auth-register', {
-      pageTitle: 'Register',
-      bodyClass: 'on-special',
-      error: 'Password must be at least 8 characters',
-      username, email, next,
-    });
-  }
-
-  // Check uniqueness
-  const existing = db.prepare(
-    'SELECT id FROM users WHERE username = ? OR email = ?'
-  ).get(username, email);
-
-  if (existing) {
-    return renderPage(req, res, 'pages/auth-register', {
-      pageTitle: 'Register',
-      bodyClass: 'on-special',
-      error: 'Username or email already taken',
-      username, email, next,
-    });
-  }
-
-  // Create user
-  const userId = uuid();
-  const hash = bcrypt.hashSync(password, 10);
-
-  // First user becomes god
-  const userCount = db.prepare('SELECT COUNT(*) as c FROM users').get().c;
-  const role = userCount === 0 ? 'god' : 'member';
-
-  db.prepare(`
-    INSERT INTO users (id, username, email, password_hash, role, theme, palette)
-    VALUES (?, ?, ?, ?, ?, 'dark', 'sage')
-  `).run(userId, username, email, hash, role);
-
-  // First user gets a personal site auto-created
-  if (userCount === 0) {
-    const siteId = uuid();
-    const siteSlug = username.toLowerCase();
-    db.prepare(`
-      INSERT INTO sites (id, slug, title, description, owner_id, palette, accent, language)
-      VALUES (?, ?, ?, ?, ?, 'sage', '#c2410c', 'nl')
-    `).run(siteId, siteSlug, username + "'s Site", 'Welcome to my site', userId);
-    
-    db.prepare(`
-      INSERT INTO site_members (site_id, user_id, role) VALUES (?, ?, 'admin')
-    `).run(siteId, userId);
-  }
-
-  req.session.user = {
-    id: userId, username, email, role,
-    palette: 'sage', theme: 'dark',
-  };
-
-  res.redirect(next || '/');
-});
-
-// ==================== PASSWORD RESET (request) ====================
-router.get('/reset-request', (req, res) => {
-  if (req.session.user) return res.redirect('/');
-  renderPage(req, res, 'pages/auth-reset-request', {
-    pageTitle: 'Reset password',
-    bodyClass: 'on-special',
-    error: null,
-    sent: false,
-    devResetUrl: null,
-  });
-});
-
-router.post('/reset-request', registerLimiter, (req, res) => {
-  const email = (req.body.email || '').trim().toLowerCase();
-  let devResetUrl = null;
-
-  if (email) {
-    const user = db.prepare('SELECT id, email FROM users WHERE LOWER(email) = ?').get(email);
-    if (user) {
-      // Generate token (32-byte hex, ~10 min expiry)
-      const token = crypto.randomBytes(32).toString('hex');
-      const expires = new Date(Date.now() + 30 * 60 * 1000).toISOString(); // 30 min
-      db.prepare('UPDATE users SET reset_token = ?, reset_token_expires = ? WHERE id = ?')
-        .run(token, expires, user.id);
-
-      // No email sender yet — log the reset URL so dev can use it.
-      const proto = req.headers['x-forwarded-proto'] || req.protocol || 'http';
-      const host = req.headers['x-forwarded-host'] || req.get('host');
-      const url = `${proto}://${host}/auth/reset/${token}`;
-      console.log(`[password-reset] ${email} -> ${url}`);
-      if (process.env.NODE_ENV !== 'production') devResetUrl = url;
-    }
-  }
-
-  // Anti-enumeration: same response regardless of whether the email exists.
-  renderPage(req, res, 'pages/auth-reset-request', {
-    pageTitle: 'Reset password',
-    bodyClass: 'on-special',
-    error: null,
-    sent: true,
-    devResetUrl,
-  });
-});
-
-// ==================== PASSWORD RESET (apply) ====================
-router.get('/reset/:token', (req, res) => {
-  const row = db.prepare(`
-    SELECT id, username FROM users
-    WHERE reset_token = ? AND reset_token_expires > datetime('now')
-  `).get(req.params.token);
-
-  if (!row) {
-    return renderPage(req, res, 'pages/auth-reset', {
-      pageTitle: 'Reset password',
-      bodyClass: 'on-special',
-      error: 'This reset link is invalid or has expired.',
-      token: null,
-      username: null,
-    });
-  }
-
-  renderPage(req, res, 'pages/auth-reset', {
-    pageTitle: 'Reset password',
-    bodyClass: 'on-special',
-    error: null,
-    token: req.params.token,
-    username: row.username,
-  });
-});
-
-router.post('/reset/:token', (req, res) => {
-  const { new_password, confirm } = req.body;
-  const row = db.prepare(`
-    SELECT id, username FROM users
-    WHERE reset_token = ? AND reset_token_expires > datetime('now')
-  `).get(req.params.token);
-
-  const renderError = (msg) => renderPage(req, res, 'pages/auth-reset', {
-    pageTitle: 'Reset password',
-    bodyClass: 'on-special',
-    error: msg,
-    token: row ? req.params.token : null,
-    username: row ? row.username : null,
-  });
-
-  if (!row) return renderError('This reset link is invalid or has expired.');
-  if (!new_password || new_password.length < 8) {
-    return renderError('Password must be at least 8 characters');
-  }
-  if (new_password !== confirm) {
-    return renderError('Passwords do not match');
-  }
-
-  const hash = bcrypt.hashSync(new_password, 10);
-  db.prepare(`
-    UPDATE users
-    SET password_hash = ?, reset_token = NULL, reset_token_expires = NULL,
-        updated_at = CURRENT_TIMESTAMP
-    WHERE id = ?
-  `).run(hash, row.id);
-
-  // Force login (don't auto-log them in — let them prove ownership)
-  res.redirect('/auth/login?success=' + encodeURIComponent('Password reset — please log in.'));
 });
 
@@ -273,5 +130,4 @@
   req.session.destroy(() => res.redirect('/'));
 });
-
 router.post('/logout', (req, res) => {
   req.session.destroy(() => res.redirect('/'));
Index: src/views/pages/account.ejs
===================================================================
--- src/views/pages/account.ejs	(revision 89d6536e83e69795d54a8021febe03e9773e6c40)
+++ src/views/pages/account.ejs	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
@@ -5,5 +5,5 @@
     <div>
       <h1>Account</h1>
-      <p class="ax-tagline">Profiel en wachtwoord.</p>
+      <p class="ax-tagline">Profiel en avatar.</p>
     </div>
   </header>
@@ -65,26 +65,8 @@
   </section>
 
-  <%# ── PASSWORD ──────────────────────────────────────────── %>
+  <%# ── INLOG ─────────────────────────────────────────────── %>
   <section class="ax-card">
-    <div class="ax-card-title">Wachtwoord wijzigen</div>
-    <form action="/account/password" method="post" class="ax-form">
-      <label class="ax-field">
-        <span>Huidig wachtwoord</span>
-        <input type="password" name="current" required autocomplete="current-password">
-      </label>
-      <div class="ax-row ax-row-2">
-        <label class="ax-field">
-          <span>Nieuw wachtwoord <small>(min 8 tekens)</small></span>
-          <input type="password" name="new_password" required minlength="8" autocomplete="new-password">
-        </label>
-        <label class="ax-field">
-          <span>Bevestig nieuw wachtwoord</span>
-          <input type="password" name="confirm" required minlength="8" autocomplete="new-password">
-        </label>
-      </div>
-      <div class="ax-form-actions">
-        <button type="submit" class="ax-btn ax-btn-primary">🔒 Wachtwoord wijzigen</button>
-      </div>
-    </form>
+    <div class="ax-card-title">Inloggen</div>
+    <p class="ax-tagline" style="margin:0">Je bent ingelogd via Google (<%= account.email %>). Er is geen apart wachtwoord.</p>
   </section>
 
Index: src/views/pages/auth-login.ejs
===================================================================
--- src/views/pages/auth-login.ejs	(revision 89d6536e83e69795d54a8021febe03e9773e6c40)
+++ src/views/pages/auth-login.ejs	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
@@ -1,23 +1,21 @@
 <div class="container auth-page">
-  <h1>Login</h1>
-  <% if (typeof success !== 'undefined' && success) { %><div class="alert alert-success"><%= success %></div><% } %>
+  <h1>Inloggen</h1>
   <% if (error) { %><div class="alert alert-error"><%= error %></div><% } %>
-  <form method="post" action="/auth/login" class="auth-form">
-    <% if (typeof next !== 'undefined' && next) { %>
-      <input type="hidden" name="next" value="<%= next %>">
+
+  <div class="auth-form auth-google">
+    <% if (googleReady) { %>
+      <p class="auth-intro">Log in met je Google-account om mee te praten.</p>
+      <a class="btn btn-google" href="/auth/google<%= (typeof next !== 'undefined' && next) ? '?next=' + encodeURIComponent(next) : '' %>">
+        <svg class="g-icon" width="18" height="18" viewBox="0 0 18 18" aria-hidden="true">
+          <path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62z"/>
+          <path fill="#34A853" d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.33A9 9 0 0 0 9 18z"/>
+          <path fill="#FBBC05" d="M3.97 10.72a5.4 5.4 0 0 1 0-3.44V4.95H.96a9 9 0 0 0 0 8.1l3.01-2.33z"/>
+          <path fill="#EA4335" d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58A9 9 0 0 0 .96 4.95l3.01 2.33C4.68 5.16 6.66 3.58 9 3.58z"/>
+        </svg>
+        <span>Inloggen met Google</span>
+      </a>
+    <% } else { %>
+      <p class="auth-intro">Inloggen via Google is op deze site nog niet ingesteld.</p>
     <% } %>
-    <label>
-      <span>Username or email</span>
-      <input type="text" name="username" value="<%= username %>" required autofocus autocomplete="username">
-    </label>
-    <label>
-      <span>Password</span>
-      <input type="password" name="password" required autocomplete="current-password">
-    </label>
-    <button type="submit" class="btn btn-primary">Login</button>
-    <p class="auth-link">No account?
-      <a href="/auth/register<%= (typeof next !== 'undefined' && next) ? '?next=' + encodeURIComponent(next) : '' %>">Register</a>
-    </p>
-    <p class="auth-link auth-link-muted"><a href="/auth/reset-request">Forgot password?</a></p>
-  </form>
+  </div>
 </div>
Index: src/views/pages/welcome.ejs
===================================================================
--- src/views/pages/welcome.ejs	(revision 89d6536e83e69795d54a8021febe03e9773e6c40)
+++ src/views/pages/welcome.ejs	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
@@ -5,8 +5,7 @@
   <% if (!user) { %>
     <div class="welcome-actions">
-      <a href="/auth/register" class="btn btn-primary">Create your account</a>
-      <a href="/auth/login" class="btn">Already have one? Login</a>
+      <a href="/auth/login" class="btn btn-primary">Inloggen met Google</a>
     </div>
-    <p class="welcome-note">The first user becomes <strong>god</strong> and gets a personal site auto-created.</p>
+    <p class="welcome-note">De eerste gebruiker die inlogt wordt <strong>god</strong> en krijgt automatisch een eigen site.</p>
   <% } else { %>
     <p>Hi <%= user.username %>! No site is configured yet.</p>
Index: src/views/partials/shared-styles.ejs
===================================================================
--- src/views/partials/shared-styles.ejs	(revision 89d6536e83e69795d54a8021febe03e9773e6c40)
+++ src/views/partials/shared-styles.ejs	(revision c80e78bc18fad68fd4deed4bd94cd0b89ca32b1d)
@@ -117,4 +117,12 @@
 .auth-link { text-align: center; color: var(--ink-muted); font-size: 0.9rem; margin: 1rem 0 0; }
 .auth-link a { color: var(--accent); }
+.auth-google { align-items: stretch; }
+.auth-intro { text-align: center; color: var(--ink-muted); font-size: 0.95rem; margin: 0 0 1rem; }
+.btn-google {
+  justify-content: center; gap: 0.6rem; padding: 0.7rem; font-size: 1rem;
+  background: #fff; color: #1f1f1f; border: 1px solid #dadce0;
+}
+.btn-google:hover { background: #f7f8f8; border-color: #dadce0; }
+.btn-google .g-icon { flex-shrink: 0; }
 
 /* HTMX loading indicator */
