<?php

declare(strict_types=1);

/**
 * Shared chrome + styling for the public community forum pages (home.php,
 * question.php). Self-contained: depends only on the app's DB facade + core
 * Markdown/SEO helpers (already bootstrapped by _runtime.php) and one icon
 * stylesheet. Theme-aware (light default, dark via prefers-color-scheme).
 */

function forum_base(): string
{
    return defined('APP_BASE') ? (string) APP_BASE : '';
}

function forum_e(mixed $s): string
{
    return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');
}

/** Relative "time ago" for a DATETIME string. */
function forum_ago(?string $dt): string
{
    if (!$dt) return '';
    $ts = strtotime($dt);
    if ($ts === false) return '';
    $d = time() - $ts;
    if ($d < 60)      return 'just now';
    if ($d < 3600)    return floor($d / 60) . 'm ago';
    if ($d < 86400)   return floor($d / 3600) . 'h ago';
    if ($d < 2592000) return floor($d / 86400) . 'd ago';
    return date('M j, Y', $ts);
}

/** Author initials for the avatar chip. */
function forum_initials(string $name): string
{
    $name = trim($name);
    if ($name === '') return '?';
    $parts = preg_split('/\s+/', $name);
    $a = mb_substr($parts[0], 0, 1);
    $b = count($parts) > 1 ? mb_substr($parts[count($parts) - 1], 0, 1) : '';
    return mb_strtoupper($a . $b);
}

/** A Gravatar URL for an email (retina size), or '' when there's no usable email. */
function forum_gravatar_url(?string $email, int $size): string
{
    $email = strtolower(trim((string) $email));
    if ($email === '' || strpos($email, '@') === false) {
        return '';
    }
    return 'https://www.gravatar.com/avatar/' . md5($email) . '?s=' . max(16, $size) . '&d=404';
}

/**
 * An avatar span: the member's Gravatar layered over their initials. When no
 * Gravatar exists the image 404s, removes itself, and the initials show through
 * — no upload, no storage, no server round-trip. $size is the retina pixel size.
 */
function forum_avatar(string $initials, ?string $email, int $size = 48, string $cls = ''): string
{
    $out = '<span class="fx-ava' . ($cls !== '' ? ' ' . $cls : '') . '">';
    $g = forum_gravatar_url($email, $size);
    if ($g !== '') {
        $out .= '<img class="fx-ava-img" src="' . forum_e($g) . '" alt="" loading="lazy" onerror="this.remove()">';
    }
    return $out . forum_e($initials) . '</span>';
}

/** Reputation → tier [label, css-class]. */
function forum_level(int $rep): array
{
    if ($rep >= 2000) return ['Expert', 'exp'];
    if ($rep >= 500)  return ['Trusted', 'trust'];
    if ($rep >= 100)  return ['Contributor', 'contrib'];
    if ($rep >= 10)   return ['Member', 'member'];
    return ['New', 'new'];
}

/** An author chip: avatar + name linked to their profile + a reputation pill. */
function forum_userlink(?string $username, ?string $display, int $rep, string $b, bool $showRep = true, ?string $email = null): string
{
    $display = trim((string) $display) !== '' ? (string) $display : (trim((string) $username) !== '' ? (string) $username : 'Anonymous');
    $inner = forum_avatar(forum_initials($display), $email, 48) . '<span class="fx-uname">' . forum_e($display) . '</span>';
    if ($showRep && $rep > 0) {
        [, $cls] = forum_level($rep);
        $inner .= '<span class="fx-lvl ' . $cls . '" title="' . $rep . ' reputation">' . number_format($rep) . '</span>';
    }
    if (trim((string) $username) !== '') {
        return '<a class="fx-user" href="' . $b . '/u/' . rawurlencode((string) $username) . '">' . $inner . '</a>';
    }
    return '<span class="fx-user">' . $inner . '</span>';
}

/** A short plain-text excerpt from Markdown body. */
function forum_excerpt(string $md, int $len = 180): string
{
    $t = preg_replace('/[#>*_`~\-]+/', ' ', $md);       // strip common md marks
    $t = preg_replace('/\[([^\]]*)\]\([^)]*\)/', '$1', (string) $t); // links → text
    $t = trim(preg_replace('/\s+/', ' ', (string) $t));
    return mb_strlen($t) > $len ? mb_substr($t, 0, $len - 1) . '…' : $t;
}

/** A search form (GET → /search). */
function forum_searchbox(string $q, string $b): void
{
    echo '<form class="fx-search" method="get" action="' . $b . '/search" role="search">'
       . '<i class="bi bi-search"></i>'
       . '<input type="search" name="q" value="' . forum_e($q) . '" placeholder="Search questions…" aria-label="Search the community" autocomplete="off">'
       . '<button type="submit">Search</button></form>';
}

/** Render one question as a list card (shared by the home + search results). */
function forum_qcard(array $q, array $tags, string $b): void
{
    $id = (int) $q['id'];
    $slug = (string) ($q['slug'] ?? '');
    $answered = ((int) ($q['answer_count'] ?? 0) > 0 && !empty($q['accepted_answer_id']));
    echo '<article class="fx-card"><div class="fx-stats">'
       . '<div class="fx-stat"><b>' . (int) ($q['vote_score'] ?? 0) . '</b><span>votes</span></div>'
       . '<div class="fx-stat ' . ($answered ? 'answered' : '') . '"><b>' . (int) ($q['answer_count'] ?? 0) . ($answered ? ' <i class="bi bi-check-lg"></i>' : '') . '</b><span>answers</span></div>'
       . '</div><div class="fx-main">'
       . '<h2 class="fx-title"><a href="' . $b . '/q/' . $id . '/' . forum_e($slug) . '">' . forum_e($q['title']) . '</a></h2>'
       . '<p class="fx-excerpt">' . forum_e(forum_excerpt((string) ($q['body_md'] ?? ''))) . '</p><div class="fx-meta">';
    if (!empty($q['cat_name'])) {
        echo '<span class="fx-cat">' . forum_e($q['cat_name']) . '</span>';
    }
    if ($tags) {
        echo '<span class="fx-tags">';
        foreach ($tags as $t) { echo '<span class="fx-tag">' . forum_e($t) . '</span>'; }
        echo '</span>';
    }
    echo forum_userlink($q['author_username'] ?? null, $q['author'] ?? null, (int) ($q['author_rep'] ?? 0), $b, true, $q['author_email'] ?? null)
       . '<span><i class="bi bi-clock"></i> ' . forum_e(forum_ago($q['last_activity_at'] ?? null)) . '</span></div></div></article>';
}

/** Is a visitor signed in (to the app's auth accounts)? */
function forum_logged_in(): bool
{
    return \PpPhp\Core\Security::IsLoggedIn();
}

/** CSRF token for the forum's write actions. */
function forum_csrf(): string
{
    return \PpPhp\Core\Security::CSRF()->Token();
}

/**
 * The signed-in visitor's FORUM identity — a `qa_users` id. Auth accounts live
 * in the app's users table; forum content (authors, votes) keys on `qa_users`,
 * so we resolve/mirror a profile by username on first use and cache it in the
 * session. Returns null for anonymous visitors.
 */
function forum_author_id(): ?int
{
    if (!\PpPhp\Core\Security::IsLoggedIn()) {
        return null;
    }
    if (isset($_SESSION['__forum_author_id'])) {
        return ((int) $_SESSION['__forum_author_id']) ?: null;
    }
    $u = \PpPhp\Core\Security::CurrentUserData() ?: [];
    $username = trim((string) ($u['username'] ?? ''));
    if ($username === '') {
        return null;
    }
    $id = dbValue('SELECT id FROM qa_users WHERE username = ?', [$username]);
    if (!$id) {
        try {
            \PpPhp\Core\DB::Insert('qa_users', [
                'username'     => $username,
                'email'        => (string) ($u['email'] ?? ''),
                'display_name' => (string) ($u['fullname'] ?? $username),
                'role'         => 'member',
                'status'       => 'active',
            ]);
            $id = dbValue('SELECT id FROM qa_users WHERE username = ?', [$username]);
        } catch (\Throwable $e) {
            return null;
        }
    }
    $_SESSION['__forum_author_id'] = (int) $id;
    return ((int) $id) ?: null;
}

/** The signed-in visitor's username (for profile links), or '' when anonymous. */
function forum_current_username(): string
{
    if (!\PpPhp\Core\Security::IsLoggedIn()) {
        return '';
    }
    $u = \PpPhp\Core\Security::CurrentUserData() ?: [];
    return trim((string) ($u['username'] ?? ''));
}

/**
 * The signed-in visitor's nav identity — [username, display, rep] — for the
 * account dropdown. Ensures a qa_users mirror exists (so /u/{username} resolves)
 * and is cached per request. Returns username='' for anonymous visitors.
 */
function forum_nav_user(): array
{
    static $u = null;
    if ($u !== null) {
        return $u;
    }
    $un = forum_current_username();
    $u = ['username' => $un, 'display' => $un, 'rep' => 0, 'email' => ''];
    if ($un !== '') {
        forum_author_id(); // find-or-create the qa_users mirror (cached in session)
        $row = dbRow('SELECT display_name, reputation, email FROM qa_users WHERE username = ?', [$un]);
        if ($row) {
            $u['display'] = trim((string) $row['display_name']) !== '' ? (string) $row['display_name'] : $un;
            $u['rep']     = (int) $row['reputation'];
            $u['email']   = (string) ($row['email'] ?? '');
        }
    }
    return $u;
}

/* ───────────────────────── Settings (key/value) ─────────────────────────
 * A tiny self-healing settings store (qa_settings) for admin-editable options
 * like the marketing-site link and the anti-bot registration lock. Created on
 * first use so no migration/seed is required. */
function forum_settings_table(): void
{
    static $done = false;
    if ($done) return;
    $done = true;
    try {
        \PpPhp\Core\DB::Query(
            'CREATE TABLE IF NOT EXISTS qa_settings (skey VARCHAR(64) PRIMARY KEY, sval TEXT NULL, updated_at DATETIME NULL)'
        );
    } catch (\Throwable $e) { /* read paths degrade to defaults */ }
}

function forum_setting(string $key, string $default = ''): string
{
    forum_settings_table();
    try {
        $v = dbValue('SELECT sval FROM qa_settings WHERE skey = ?', [$key]);
    } catch (\Throwable $e) {
        $v = null;
    }
    return $v === null ? $default : (string) $v;
}

function forum_set_setting(string $key, string $val): void
{
    forum_settings_table();
    try {
        if (dbValue('SELECT 1 FROM qa_settings WHERE skey = ?', [$key]) !== null) {
            \PpPhp\Core\DB::Query('UPDATE qa_settings SET sval = ?, updated_at = ? WHERE skey = ?', [$val, gmdate('Y-m-d H:i:s'), $key]);
        } else {
            \PpPhp\Core\DB::Insert('qa_settings', ['skey' => $key, 'sval' => $val, 'updated_at' => gmdate('Y-m-d H:i:s')]);
        }
    } catch (\Throwable $e) { /* non-fatal */ }
}

/** The admin-editable link back to the marketing site (label + URL). */
function forum_marketing(): array
{
    $url = trim(forum_setting('marketing_url', 'https://ppradhan.com'));
    $label = trim(forum_setting('marketing_label', 'Explore PPPHP')) ?: 'Explore PPPHP';
    // Only http(s) links are rendered — never javascript:/data: etc.
    if ($url !== '' && !preg_match('#^https?://#i', $url)) $url = '';
    return ['url' => $url, 'label' => $label];
}

/** Are new registrations locked (anti-bot switch flipped by an admin)? */
function forum_registration_locked(): bool
{
    return forum_setting('registration_locked', '0') === '1';
}

/* ───────────────────────── Moderation ───────────────────────── */

/** Is the signed-in visitor a forum admin / moderator? */
function forum_is_admin(): bool
{
    $un = forum_current_username();
    if ($un !== '') {
        $role = (string) (dbValue('SELECT role FROM qa_users WHERE username = ?', [$un]) ?? '');
        if (in_array($role, ['admin', 'superadmin', 'moderator'], true)) return true;
    }
    try { return \PpPhp\Core\Security::IsAdmin(); } catch (\Throwable $e) { return false; }
}

/** The signed-in visitor's forum account status ('active'|'suspended'|…), '' if anon. */
function forum_user_status(): string
{
    $un = forum_current_username();
    if ($un === '') return '';
    return (string) (dbValue('SELECT status FROM qa_users WHERE username = ?', [$un]) ?? 'active');
}

/** True when the signed-in visitor is suspended (blocked from posting/voting). */
function forum_is_suspended(): bool
{
    return forum_user_status() === 'suspended';
}

/* ───────────────────────── Pagination ─────────────────────────
 * A windowed pager (‹ 1 … 4 5 6 … 20 ›) that preserves the current query
 * params. $extra holds the non-page params (cat, sort, q) to carry across. */
function forum_pagination(int $page, int $perPage, int $total, string $baseUrl, array $extra = []): void
{
    $pages = (int) ceil($total / max(1, $perPage));
    if ($pages <= 1) return;
    $page = max(1, min($page, $pages));
    $mk = static function (int $p) use ($baseUrl, $extra): string {
        $qs = $extra; $qs['page'] = $p;
        return $baseUrl . '?' . http_build_query($qs);
    };
    echo '<nav class="fx-pager" aria-label="Pagination">';
    echo $page > 1
        ? '<a class="fx-pg" href="' . forum_e($mk($page - 1)) . '" rel="prev" aria-label="Previous page"><i class="bi bi-chevron-left"></i></a>'
        : '<span class="fx-pg is-dis"><i class="bi bi-chevron-left"></i></span>';
    $win = 2; $start = max(1, $page - $win); $end = min($pages, $page + $win);
    if ($start > 1) {
        echo '<a class="fx-pg" href="' . forum_e($mk(1)) . '">1</a>';
        if ($start > 2) echo '<span class="fx-pg-gap">…</span>';
    }
    for ($i = $start; $i <= $end; $i++) {
        echo $i === $page
            ? '<span class="fx-pg is-cur" aria-current="page">' . $i . '</span>'
            : '<a class="fx-pg" href="' . forum_e($mk($i)) . '">' . $i . '</a>';
    }
    if ($end < $pages) {
        if ($end < $pages - 1) echo '<span class="fx-pg-gap">…</span>';
        echo '<a class="fx-pg" href="' . forum_e($mk($pages)) . '">' . $pages . '</a>';
    }
    echo $page < $pages
        ? '<a class="fx-pg" href="' . forum_e($mk($page + 1)) . '" rel="next" aria-label="Next page"><i class="bi bi-chevron-right"></i></a>'
        : '<span class="fx-pg is-dis"><i class="bi bi-chevron-right"></i></span>';
    echo '</nav>';
}

/**
 * The finalized PPPHP brand mark — the crystal "PP" monogram (same faceted,
 * gem-cut treatment as the console + brand-lockup): crystal gradient (sky →
 * indigo / lilac → violet), glassy sheen, a slow crystal glint that sweeps
 * across, sharp edge highlight and two diamond sparkles at the letter joints.
 * Inlined SMIL so it animates on this standalone host with no extra CSS; the
 * shine/sparkles are hidden under prefers-reduced-motion (see shell CSS).
 * Ids are randomised per render so multiple instances never collide.
 */
function forum_brandmark(): string
{
    $u  = 'fxl' . bin2hex(random_bytes(3));
    $p1 = 'M 90 70 h 100 c 55.23 0 100 44.77 100 100 c 0 55.23 -44.77 100 -100 100 h -50 v 60 c 0 16.57 -13.43 30 -30 30 c -16.57 0 -30 -13.43 -30 -30 V 70 z M 150 120 v 100 h 40 c 27.61 0 50 -22.39 50 -50 c 0 -27.61 -22.39 -50 -50 -50 h -40 z';
    $p2 = 'M 170 140 h 100 c 55.23 0 100 44.77 100 100 c 0 55.23 -44.77 100 -100 100 h -50 v 60 c 0 16.57 -13.43 30 -30 30 c -16.57 0 -30 -13.43 -30 -30 V 140 z M 230 190 v 100 h 40 c 27.61 0 50 -22.39 50 -50 c 0 -27.61 -22.39 -50 -50 -50 h -40 z';
    return '<svg class="fx-logo pp-brand" viewBox="0 0 400 400" width="34" height="34" role="img" aria-label="PPPHP" xmlns="http://www.w3.org/2000/svg">'
        . '<defs>'
        . '<linearGradient id="' . $u . 'b" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#7dd3fc"/><stop offset="42%" stop-color="#4f7cf7"/><stop offset="100%" stop-color="#4338ca"/></linearGradient>'
        . '<linearGradient id="' . $u . 'p" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#e9d5ff"/><stop offset="50%" stop-color="#a855f7"/><stop offset="100%" stop-color="#6d28d9"/></linearGradient>'
        . '<linearGradient id="' . $u . 's" x1="0%" y1="0%" x2="70%" y2="100%"><stop offset="0%" stop-color="#ffffff" stop-opacity=".6"/><stop offset="38%" stop-color="#ffffff" stop-opacity="0"/><stop offset="100%" stop-color="#ffffff" stop-opacity="0"/></linearGradient>'
        . '<linearGradient id="' . $u . 'f" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="49%" stop-color="#ffffff" stop-opacity="0"/><stop offset="52%" stop-color="#ffffff" stop-opacity=".55"/><stop offset="56%" stop-color="#ffffff" stop-opacity="0"/><stop offset="72%" stop-color="#ffffff" stop-opacity="0"/><stop offset="74%" stop-color="#ffffff" stop-opacity=".26"/><stop offset="77%" stop-color="#ffffff" stop-opacity="0"/></linearGradient>'
        . '<linearGradient id="' . $u . 'g" x1="0" y1="0" x2="1" y2="0"><stop offset="0" stop-color="#ffffff" stop-opacity="0"/><stop offset=".40" stop-color="#ffffff" stop-opacity="0"/><stop offset=".47" stop-color="#eaf6ff" stop-opacity=".95"/><stop offset=".52" stop-color="#ffffff" stop-opacity=".95"/><stop offset=".59" stop-color="#ffffff" stop-opacity="0"/><stop offset=".70" stop-color="#ffffff" stop-opacity="0"/><stop offset=".76" stop-color="#ffffff" stop-opacity=".5"/><stop offset=".82" stop-color="#ffffff" stop-opacity="0"/><stop offset="1" stop-color="#ffffff" stop-opacity="0"/></linearGradient>'
        . '<clipPath id="' . $u . 'c"><path d="' . $p1 . '" fill-rule="evenodd"/><path d="' . $p2 . '" fill-rule="evenodd"/></clipPath>'
        . '</defs>'
        . '<path d="' . $p1 . '" fill="url(#' . $u . 'b)" fill-rule="evenodd"/>'
        . '<path d="' . $p1 . '" fill="url(#' . $u . 'f)" fill-rule="evenodd"/>'
        . '<path d="' . $p1 . '" fill="url(#' . $u . 's)" fill-rule="evenodd"/>'
        . '<path d="' . $p2 . '" fill="url(#' . $u . 'p)" fill-rule="evenodd"/>'
        . '<path d="' . $p2 . '" fill="url(#' . $u . 'f)" fill-rule="evenodd"/>'
        . '<path d="' . $p2 . '" fill="url(#' . $u . 's)" fill-rule="evenodd"/>'
        . '<path d="' . $p2 . '" fill="none" stroke="#ffffff" stroke-opacity=".42" stroke-width="2.5" fill-rule="evenodd"/>'
        . '<g clip-path="url(#' . $u . 'c)"><polygon class="pp-shine" points="30,-20 130,-20 70,420 -30,420" fill="url(#' . $u . 'g)" opacity="0">'
        . '<animateTransform attributeName="transform" type="translate" values="-120 0; 470 0; 470 0" keyTimes="0; 0.42; 1" dur="4.8s" repeatCount="indefinite"/>'
        . '<animate attributeName="opacity" values="0; 0.95; 0.95; 0; 0" keyTimes="0; 0.06; 0.36; 0.42; 1" dur="4.8s" repeatCount="indefinite"/></polygon></g>'
        . '<g class="pp-spark"><path fill="#ffffff" d="M150 98 L158 120 L150 142 L142 120 Z"/><circle fill="#ffffff" opacity=".9" cx="150" cy="120" r="2.4"/><animate attributeName="opacity" values="0.72; 1; 0.72" dur="3.4s" repeatCount="indefinite"/></g>'
        . '<g class="pp-spark pp-spark--b"><path fill="#ffffff" d="M230 168 L238 190 L230 212 L222 190 Z"/><circle fill="#ffffff" opacity=".9" cx="230" cy="190" r="2.4"/><animate attributeName="opacity" values="0.72; 1; 0.72" dur="3.4s" begin="1.2s" repeatCount="indefinite"/></g>'
        . '</svg>';
}

/** The PPPHP favicon — the crystal monogram (static, crisp at tab size) as an inline SVG data-URI. */
function forum_favicon_link(): string
{
    $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><defs>'
        . '<linearGradient id="b" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#7dd3fc"/><stop offset="42%" stop-color="#4f7cf7"/><stop offset="100%" stop-color="#4338ca"/></linearGradient>'
        . '<linearGradient id="p" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#e9d5ff"/><stop offset="50%" stop-color="#a855f7"/><stop offset="100%" stop-color="#6d28d9"/></linearGradient></defs>'
        . '<path d="M 90 70 h 100 c 55.23 0 100 44.77 100 100 c 0 55.23 -44.77 100 -100 100 h -50 v 60 c 0 16.57 -13.43 30 -30 30 c -16.57 0 -30 -13.43 -30 -30 V 70 z M 150 120 v 100 h 40 c 27.61 0 50 -22.39 50 -50 c 0 -27.61 -22.39 -50 -50 -50 h -40 z" fill="url(#b)" fill-rule="evenodd"/>'
        . '<path d="M 170 140 h 100 c 55.23 0 100 44.77 100 100 c 0 55.23 -44.77 100 -100 100 h -50 v 60 c 0 16.57 -13.43 30 -30 30 c -16.57 0 -30 -13.43 -30 -30 V 140 z M 230 190 v 100 h 40 c 27.61 0 50 -22.39 50 -50 c 0 -27.61 -22.39 -50 -50 -50 h -40 z" fill="url(#p)" fill-rule="evenodd"/>'
        . '<path fill="#ffffff" d="M150 98 L158 120 L150 142 L142 120 Z"/><path fill="#ffffff" d="M230 168 L238 190 L230 212 L222 190 Z"/></svg>';
    return '<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,' . rawurlencode($svg) . '">';
}

/** The navbar account dropdown — user menu when signed in, sign-in menu otherwise. */
function forum_account_menu(string $b): string
{
    if (forum_logged_in()) {
        $nu  = forum_nav_user();
        $un  = (string) $nu['username'];
        $nm  = (string) $nu['display'];
        $rep = (int) $nu['rep'];
        $eml = (string) ($nu['email'] ?? '');
        $lbl = $nm !== '' ? $nm : $un;
        $prof = $b . '/u/' . rawurlencode($un);
        $repLine = $rep > 0 ? ' · <b>' . number_format($rep) . '</b> rep' : '';
        return '<div class="fx-menu">'
            . '<button type="button" class="fx-menu-btn fx-userbtn" aria-haspopup="true" aria-expanded="false" aria-label="Account menu">'
                . forum_avatar(forum_initials($lbl), $eml, 64, 'fx-ava-btn') . '<i class="bi bi-chevron-down fx-caret"></i></button>'
            . '<div class="fx-menu-pop" role="menu">'
                . '<a class="fx-menu-head" href="' . $prof . '" role="menuitem">'
                    . forum_avatar(forum_initials($lbl), $eml, 96, 'fx-ava-lg')
                    . '<span class="fx-menu-id"><span class="fx-menu-name">' . forum_e($nm !== '' ? $nm : $un) . '</span>'
                    . '<span class="fx-menu-user">@' . forum_e($un) . $repLine . '</span></span></a>'
                . '<div class="fx-menu-sep"></div>'
                . '<a class="fx-menu-item" href="' . $prof . '" role="menuitem"><i class="bi bi-person"></i> My profile</a>'
                . '<a class="fx-menu-item" href="' . $b . '/ask" role="menuitem"><i class="bi bi-plus-circle"></i> Ask a question</a>'
                . (forum_is_admin()
                    ? '<div class="fx-menu-sep"></div><a class="fx-menu-item" href="' . $b . '/admin" role="menuitem"><i class="bi bi-shield-lock"></i> Moderation</a>'
                    : '')
                . '<div class="fx-menu-sep"></div>'
                . '<a class="fx-menu-item danger" href="' . $b . '/logout" role="menuitem"><i class="bi bi-box-arrow-right"></i> Sign out</a>'
            . '</div></div>';
    }
    return '<div class="fx-menu">'
        . '<button type="button" class="fx-menu-btn fx-login" aria-haspopup="true" aria-expanded="false" aria-label="Account menu">'
            . '<i class="bi bi-person-circle"></i><span class="fx-login-t"> Sign in</span><i class="bi bi-chevron-down fx-caret"></i></button>'
        . '<div class="fx-menu-pop" role="menu">'
            . '<a class="fx-menu-item" href="' . $b . '/login" role="menuitem"><i class="bi bi-box-arrow-in-right"></i> Sign in</a>'
            . '<a class="fx-menu-item" href="' . $b . '/register" role="menuitem"><i class="bi bi-person-plus"></i> Create account</a>'
        . '</div></div>';
}

function forum_head(string $title, string $metaExtra = ''): void
{
    $b = forum_base();
    echo '<!DOCTYPE html><html lang="en"><head>';
    // Apply the saved theme before paint (no flash of the wrong theme).
    echo '<script>(function(){try{var t=localStorage.getItem("fx-theme");if(t)document.documentElement.setAttribute("data-theme",t);}catch(e){}})();</script>';
    echo '<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
    echo '<title>' . $title . '</title>' . $metaExtra . forum_favicon_link();
    echo '<link rel="stylesheet" href="' . $b . '/assets/icons/bootstrap-icons.css">';
    echo '<style>' . forum_css() . '</style></head><body>';
    echo '<header class="fx-top"><div class="fx-top-in">'
        . '<a class="fx-brand" href="' . $b . '/">' . forum_brandmark() . '<span class="fx-brand-t">Community</span></a>'
        . '<nav class="fx-nav">'
        . '<a class="fx-nav-q fx-nav-home" href="' . $b . '/" data-tip="All questions" aria-label="All questions"><i class="bi bi-list-ul"></i><span> Questions</span></a>'
        . '<form class="fx-navsearch" method="get" action="' . $b . '/search" role="search"><i class="bi bi-search"></i><input type="search" name="q" placeholder="Search questions…" aria-label="Search the community"></form>'
        . '<button type="button" class="fx-navsearch-ico" id="fxSearchToggle" data-tip="Search" aria-label="Search" aria-expanded="false" aria-controls="fxSearchRow"><i class="bi bi-search"></i></button>'
        . '<a class="fx-ask" href="' . $b . '/ask" data-tip="Ask a question" aria-label="Ask a question"><i class="bi bi-plus-lg"></i><span class="fx-ask-t"> Ask Question</span></a>'
        . '<button type="button" class="fx-theme" id="fxTheme" data-tip="Toggle theme" aria-label="Toggle light or dark theme"><i class="bi bi-moon-stars"></i></button>'
        . forum_account_menu($b)
        . '</nav></div>'
        . '<div class="fx-searchrow" id="fxSearchRow"><form method="get" action="' . $b . '/search" role="search"><i class="bi bi-search"></i>'
        . '<input type="search" name="q" placeholder="Search questions…" aria-label="Search the community" autocomplete="off">'
        . '<button type="submit" aria-label="Search"><i class="bi bi-arrow-right"></i></button></form></div>'
        . '</header><main class="fx-wrap">';
}

/**
 * Emit the unified-analytics beacon so this forum's traffic appears in a PPPHP
 * Visitor console (the primary). Opt-in and self-contained: does nothing until an
 * `analytics_site` id is set in the forum admin. The collector URL defaults to the
 * main licence server; both are admin-editable. Same accurate, cookieless tracking
 * the marketing site gets.
 */
function forum_analytics_beacon(): void
{
    $site = trim(forum_setting('analytics_site', ''));
    if ($site === '' || !preg_match('/^st_[a-z0-9]+$/i', $site)) return;
    $url = trim(forum_setting('analytics_url', 'https://ppphp.ppradhan.com/api/analytics/collect'));
    if (!preg_match('#^https?://#i', $url)) return;
    $src = preg_replace('#/api/analytics/collect/?$#i', '', $url) . '/assets/js/pp-analytics.js';
    echo '<script>window.__PP_ANALYTICS={url:' . json_encode($url, JSON_UNESCAPED_SLASHES)
       . ',site:' . json_encode($site) . '};</script>'
       . '<script src="' . forum_e($src) . '" defer></script>';
}

function forum_foot(): void
{
    $b = forum_base();
    $mk = forum_marketing();
    echo '</main><footer class="fx-foot"><div class="fx-foot-in">'
        . '<span>Powered by <strong>PPPHP</strong></span>'
        . '<div class="fx-foot-links">'
        .   '<a href="' . $b . '/">All questions</a>'
        .   (forum_is_admin() ? '<a href="' . $b . '/admin"><i class="bi bi-shield-lock"></i> Admin</a>' : '')
        .   ($mk['url'] !== '' ? '<a class="fx-foot-cta" href="' . forum_e($mk['url']) . '" target="_blank" rel="noopener">' . forum_e($mk['label']) . ' <i class="bi bi-box-arrow-up-right"></i></a>' : '')
        . '</div>'
        . '</div></footer>';
    echo '<script>(function(){var b=document.getElementById("fxTheme");if(!b)return;'
        . 'function cur(){return document.documentElement.getAttribute("data-theme")||(matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light");}'
        . 'function ico(){b.innerHTML=cur()==="dark"?"<i class=\'bi bi-sun\'></i>":"<i class=\'bi bi-moon-stars\'></i>";}'
        . 'ico();b.addEventListener("click",function(){var n=cur()==="dark"?"light":"dark";document.documentElement.setAttribute("data-theme",n);try{localStorage.setItem("fx-theme",n);}catch(e){}ico();});})();</script>';
    // Account dropdown(s): click to toggle, close on outside-click / Escape.
    echo '<script>(function(){'
        . 'function shut(){document.querySelectorAll(".fx-menu.open").forEach(function(m){m.classList.remove("open");var t=m.querySelector(".fx-menu-btn");if(t)t.setAttribute("aria-expanded","false");});}'
        . 'document.querySelectorAll(".fx-menu > .fx-menu-btn").forEach(function(btn){btn.addEventListener("click",function(e){e.stopPropagation();var m=btn.parentNode,was=m.classList.contains("open");shut();if(!was){m.classList.add("open");btn.setAttribute("aria-expanded","true");}});});'
        . 'document.addEventListener("click",shut);'
        . 'document.addEventListener("keydown",function(e){if(e.key==="Escape")shut();});'
        . '})();</script>';
    // Mobile search: tap the icon to reveal a full-width search row below the navbar.
    echo '<script>(function(){var t=document.getElementById("fxSearchToggle"),r=document.getElementById("fxSearchRow");if(!t||!r)return;'
        . 't.addEventListener("click",function(e){e.stopPropagation();var open=r.classList.toggle("open");t.setAttribute("aria-expanded",open?"true":"false");if(open){var i=r.querySelector("input");if(i)i.focus();}});'
        . 'document.addEventListener("keydown",function(e){if(e.key==="Escape"&&r.classList.contains("open")){r.classList.remove("open");t.setAttribute("aria-expanded","false");}});'
        . '})();</script>';
    forum_analytics_beacon();
    echo '</body></html>';
}

/** Minimal, community-branded chrome for the auth pages (login/register/verify). */
function forum_auth_head(string $title, string $metaExtra = ''): void
{
    $b = forum_base();
    echo '<!DOCTYPE html><html lang="en"><head>';
    echo '<script>(function(){try{var t=localStorage.getItem("fx-theme");if(t)document.documentElement.setAttribute("data-theme",t);}catch(e){}})();</script>';
    echo '<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">';
    echo '<title>' . $title . '</title>' . $metaExtra . forum_favicon_link();
    echo '<link rel="stylesheet" href="' . $b . '/assets/icons/bootstrap-icons.css">';
    echo '<style>' . forum_css() . '</style></head><body class="fx-authbody">';
    echo '<main class="fx-auth">'
        . '<a class="fx-auth-brand" href="' . $b . '/">' . forum_brandmark() . '<span>PPPHP <b>Community</b></span></a>';
}

function forum_auth_foot(string $backText = 'Back to the community'): void
{
    $b = forum_base();
    echo '<p class="fx-auth-back"><a href="' . $b . '/"><i class="bi bi-arrow-left"></i> ' . forum_e($backText) . '</a></p></main>';
    forum_analytics_beacon();
    echo '</body></html>';
}

/**
 * A rich, on-brand HTML verification email (bulletproof table layout, inline
 * styles). Sent via the platform mailer; returns its result array. When SMTP
 * isn't configured it returns ['success'=>false] and the caller degrades.
 */
function forum_send_verify_email(string $to, string $name, string $url, string $hoursLabel): array
{
    $e = static fn($s): string => htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');
    $name = $name !== '' ? $name : 'there';
    $html = '<div style="margin:0;padding:32px 14px;background:#eef0f6;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;">'
        . '<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td align="center">'
        . '<table role="presentation" width="480" cellpadding="0" cellspacing="0" style="max-width:480px;width:100%;background:#ffffff;border-radius:16px;overflow:hidden;box-shadow:0 6px 24px rgba(16,24,40,.08);">'
        . '<tr><td style="background:linear-gradient(120deg,#4f46e5,#7c3aed);padding:26px 40px;">'
        . '<span style="display:inline-block;width:34px;height:34px;border-radius:9px;background:rgba(255,255,255,.16);color:#fff;font-weight:800;text-align:center;line-height:34px;font-size:15px;vertical-align:middle;">PP</span>'
        . '<span style="color:#fff;font-weight:800;font-size:17px;margin-left:10px;vertical-align:middle;">PPPHP Community</span></td></tr>'
        . '<tr><td style="padding:32px 40px 6px;"><h1 style="margin:0;font-size:22px;color:#1b2733;font-weight:800;">Confirm your email</h1></td></tr>'
        . '<tr><td style="padding:6px 40px 0;color:#4b5563;font-size:15px;line-height:1.6;">Hi ' . $e($name) . ', welcome to the PPPHP Community! Please confirm your email address to activate your account and start asking &amp; answering questions.</td></tr>'
        . '<tr><td style="padding:26px 40px;" align="center"><a href="' . $e($url) . '" style="display:inline-block;background:#4f46e5;color:#ffffff;font-weight:700;font-size:15px;text-decoration:none;padding:14px 30px;border-radius:10px;">Verify my email</a></td></tr>'
        . '<tr><td style="padding:0 40px;color:#6b7280;font-size:13px;line-height:1.6;">This link expires in ' . $e($hoursLabel) . '. If you didn\'t create an account, you can safely ignore this email.</td></tr>'
        . '<tr><td style="padding:14px 40px 30px;color:#9aa3af;font-size:12px;word-break:break-all;">Button not working? Paste this link into your browser:<br>' . $e($url) . '</td></tr>'
        . '</table>'
        . '<p style="color:#9aa3af;font-size:12px;margin:16px 0 0;">&copy; PPPHP Community &middot; ppradhan.com</p>'
        . '</td></tr></table></div>';
    $text = "Hi {$name},\n\nWelcome to the PPPHP Community! Confirm your email to activate your account:\n\n{$url}\n\nThis link expires in {$hoursLabel}. If you didn't sign up, ignore this email.\n\n— PPPHP Community";
    return pp_send_email($to, 'Confirm your email — PPPHP Community', $text, ['htmlbody' => $html]);
}

function forum_css(): string
{
    return <<<'CSS'
:root{
  --fx-bg:#f4f5f7; --fx-surface:#ffffff; --fx-border:#e3e6eb; --fx-text:#1b2733;
  --fx-muted:#66707a; --fx-accent:#4f46e5; --fx-accent-soft:#eef0fe;
  --fx-green:#118a53; --fx-green-soft:#e5f6ee; --fx-chip:#eef1f4; --fx-shadow:0 1px 2px rgba(16,24,40,.06),0 1px 3px rgba(16,24,40,.08);
}
:root[data-theme="dark"]{
  --fx-bg:#0f141a; --fx-surface:#161d26; --fx-border:#26303c; --fx-text:#e6ebf1;
  --fx-muted:#8a97a5; --fx-accent:#8b8bf7; --fx-accent-soft:#1e2140; --fx-green:#41c98a; --fx-green-soft:#12291f; --fx-chip:#1e2731; --fx-shadow:none;
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
  --fx-bg:#0f141a; --fx-surface:#161d26; --fx-border:#26303c; --fx-text:#e6ebf1;
  --fx-muted:#8a97a5; --fx-accent:#8b8bf7; --fx-accent-soft:#1e2140; --fx-green:#41c98a; --fx-green-soft:#12291f; --fx-chip:#1e2731; --fx-shadow:none;
}}
*{box-sizing:border-box}
body{margin:0;min-height:100vh;min-height:100dvh;display:flex;flex-direction:column;background:var(--fx-bg);color:var(--fx-text);font:15px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;transition:background-color .25s ease,color .25s ease}
a{color:inherit;text-decoration:none}
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--fx-accent);outline-offset:2px}
/* instant tooltips */
[data-tip]{position:relative}
[data-tip]:hover::after{content:attr(data-tip);position:absolute;top:calc(100% + 9px);left:50%;transform:translateX(-50%);background:var(--fx-text);color:var(--fx-surface);font-size:12px;font-weight:600;letter-spacing:.01em;padding:5px 10px;border-radius:7px;white-space:nowrap;z-index:70;pointer-events:none;box-shadow:0 6px 18px rgba(0,0,0,.22)}
[data-tip]:hover::before{content:"";position:absolute;top:calc(100% + 3px);left:50%;transform:translateX(-50%);border:6px solid transparent;border-bottom-color:var(--fx-text);z-index:70;pointer-events:none}
.fx-top{position:sticky;top:0;z-index:20;background:var(--fx-surface);border-bottom:1px solid var(--fx-border)}
.fx-top-in{max-width:960px;margin:0 auto;padding:0 18px;height:58px;display:flex;align-items:center;justify-content:space-between}
.fx-brand{display:flex;align-items:center;gap:9px;font-weight:800;font-size:17px;letter-spacing:-.01em}
.fx-logo{width:34px;height:34px;display:block;flex:0 0 auto;filter:drop-shadow(0 1px 3px rgba(79,60,246,.28))}
@media (prefers-reduced-motion:reduce){.fx-logo .pp-shine,.fx-logo .pp-spark{display:none}}
.fx-brand-t{white-space:nowrap}
.fx-nav{display:flex;align-items:center;gap:6px}
.fx-nav a{padding:8px 12px;border-radius:8px;color:var(--fx-muted);font-weight:600;font-size:14px}
.fx-nav a:hover{color:var(--fx-text);background:var(--fx-chip)}
.fx-ask{color:#fff!important;background:var(--fx-accent);}
.fx-ask:hover{background:var(--fx-accent)!important;filter:brightness(1.05)}
.fx-login{color:var(--fx-text)!important}
.fx-theme{border:1px solid var(--fx-border);background:var(--fx-surface);color:var(--fx-muted);width:36px;height:36px;border-radius:9px;display:grid;place-items:center;cursor:pointer;font-size:16px;padding:0;flex:0 0 auto}
.fx-theme:hover{color:var(--fx-accent);border-color:var(--fx-accent)}
/* account dropdown */
.fx-menu{position:relative;display:flex;align-items:center}
.fx-menu-btn{display:inline-flex;align-items:center;gap:6px;background:none;border:0;cursor:pointer;color:var(--fx-text);font:inherit;font-weight:600;font-size:14px;padding:5px 8px;border-radius:9px;line-height:1}
.fx-menu-btn:hover{background:var(--fx-chip)}
.fx-caret{font-size:12px;color:var(--fx-muted);transition:transform .15s}
.fx-menu.open .fx-caret{transform:rotate(180deg)}
.fx-ava-btn{width:30px;height:30px;font-size:12px}
.fx-menu-pop{position:absolute;right:0;top:calc(100% + 9px);min-width:232px;background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:13px;box-shadow:0 12px 34px rgba(0,0,0,.16);padding:6px;display:none;z-index:60}
.fx-menu.open .fx-menu-pop{display:block;animation:fxpop .14s ease}
@keyframes fxpop{from{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:none}}
.fx-menu-pop .fx-menu-head{display:flex;align-items:center;gap:11px;padding:10px 12px;border-radius:9px;color:var(--fx-text)}
.fx-menu-pop .fx-menu-head:hover{background:var(--fx-chip)}
.fx-ava-lg{width:40px;height:40px;font-size:16px;flex:0 0 auto}
.fx-menu-id{display:flex;flex-direction:column;min-width:0}
.fx-menu-name{font-weight:700;font-size:14.5px;color:var(--fx-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:150px}
.fx-menu-user{font-size:12.5px;color:var(--fx-muted)}
.fx-menu-user b{color:var(--fx-accent);font-weight:700}
.fx-menu-sep{height:1px;background:var(--fx-border);margin:5px 4px}
.fx-menu-pop .fx-menu-item{display:flex;align-items:center;gap:11px;padding:9px 12px;border-radius:9px;color:var(--fx-text);font-size:14px;font-weight:600}
.fx-menu-pop .fx-menu-item i{font-size:16px;color:var(--fx-muted);width:18px;text-align:center}
.fx-menu-pop .fx-menu-item:hover{background:var(--fx-chip);color:var(--fx-text)}
.fx-menu-pop .fx-menu-item:hover i{color:var(--fx-accent)}
.fx-menu-pop .fx-menu-item.danger:hover{background:rgba(214,69,69,.12);color:#d64545}
.fx-menu-pop .fx-menu-item.danger:hover i{color:#d64545}
@media(max-width:640px){
  .fx-top-in{padding:0 10px;height:54px}
  .fx-nav{gap:6px}
  .fx-brand{font-size:15.5px;gap:8px}
  .fx-logo{width:30px;height:30px}
  .fx-nav-home{display:none}                         /* brand already links home */
  .fx-nav-q span,.fx-ask-t,.fx-login-t{display:none} /* icon-only nav */
  .fx-nav-q,.fx-login{padding:8px 9px;font-size:17px}
  .fx-ask{padding:8px 11px}
  .fx-theme{width:36px;height:36px}
  /* tooltips below icons can clip at screen edges — nudge the last ones in */
  .fx-login[data-tip]:hover::after{left:auto;right:0;transform:none}
  .fx-login[data-tip]:hover::before{left:auto;right:14px;transform:none}
}
@media(max-width:360px){ .fx-brand-t{display:none} }  /* only the very smallest: logo alone */
.fx-wrap{flex:1 0 auto;width:100%;max-width:960px;margin:0 auto;padding:26px 18px 60px}
.fx-headrow{display:flex;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:14px;margin-bottom:20px}
.fx-headrow .fx-sub{margin-bottom:0}
.fx-h1{font-size:26px;font-weight:800;letter-spacing:-.02em;margin:0 0 2px}
.fx-sub{color:var(--fx-muted);margin:0 0 22px}
@media(max-width:520px){.fx-sorts{width:100%}.fx-sort{flex:1;justify-content:center;padding:8px 6px}}
.fx-cats{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 22px}
.fx-catchip{background:var(--fx-surface);border:1px solid var(--fx-border);color:var(--fx-muted);border-radius:999px;padding:6px 14px;font-size:13px;font-weight:600;transition:all .12s}
.fx-catchip:hover{border-color:var(--fx-accent);color:var(--fx-accent)}
.fx-catchip.on{background:var(--fx-accent);border-color:var(--fx-accent);color:#fff}
.fx-search{display:flex;align-items:center;gap:0;background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:11px;padding:0 4px 0 14px;margin:0 0 20px;box-shadow:var(--fx-shadow)}
.fx-search>i{color:var(--fx-muted);font-size:16px;flex:0 0 auto}
.fx-search input{flex:1;min-width:0;border:none;background:none;outline:none;color:var(--fx-text);font:inherit;font-size:15px;padding:13px 12px}
.fx-search button{background:var(--fx-accent);color:#fff;border:none;border-radius:8px;padding:9px 18px;font-weight:700;font-size:14px;cursor:pointer;margin:5px;flex:0 0 auto}
.fx-search:focus-within{border-color:var(--fx-accent);box-shadow:0 0 0 3px var(--fx-accent-soft)}
.fx-related{margin-top:34px}
.fx-related h3{font-size:13px;font-weight:800;margin:0 0 12px;color:var(--fx-muted);text-transform:uppercase;letter-spacing:.04em}
.fx-related a{display:flex;justify-content:space-between;gap:12px;padding:11px 15px;background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:10px;margin-bottom:8px;color:var(--fx-accent);font-weight:600;font-size:14px;transition:border-color .12s}
.fx-related a:hover{border-color:var(--fx-accent)}
.fx-related a .m{color:var(--fx-muted);font-weight:400;font-size:12.5px;white-space:nowrap;flex:0 0 auto}
.fx-list{display:flex;flex-direction:column;gap:12px}
.fx-card{display:flex;gap:16px;background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:14px;padding:16px 18px;box-shadow:var(--fx-shadow);transition:border-color .15s,transform .15s,background-color .25s ease}
.fx-card:hover{border-color:var(--fx-accent);transform:translateY(-1px)}
.fx-stats{display:flex;flex-direction:column;gap:8px;min-width:66px;text-align:center}
.fx-stat{border-radius:9px;padding:6px 4px;background:var(--fx-chip)}
.fx-stat b{display:block;font-size:16px;font-weight:800;line-height:1.1}
.fx-stat span{font-size:11px;color:var(--fx-muted)}
.fx-stat.answered{background:var(--fx-green-soft);color:var(--fx-green)}
.fx-stat.answered b,.fx-stat.answered span{color:var(--fx-green)}
.fx-main{flex:1;min-width:0}
.fx-title{font-size:17px;font-weight:700;letter-spacing:-.01em;margin:0 0 5px;color:var(--fx-accent)}
.fx-card:hover .fx-title{text-decoration:underline}
.fx-excerpt{color:var(--fx-muted);font-size:14px;margin:0 0 11px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.fx-meta{display:flex;align-items:center;flex-wrap:wrap;gap:8px 12px;font-size:12.5px;color:var(--fx-muted)}
.fx-tags{display:flex;flex-wrap:wrap;gap:6px}
.fx-tag{background:var(--fx-accent-soft);color:var(--fx-accent);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:600}
.fx-cat{background:var(--fx-chip);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:600;color:var(--fx-muted)}
.fx-author{display:inline-flex;align-items:center;gap:6px}
.fx-ava{position:relative;overflow:hidden;display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:linear-gradient(135deg,var(--fx-accent),#7c5cff);color:#fff;font-size:10px;font-weight:700;flex:0 0 auto}
.fx-ava-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.fx-empty{text-align:center;padding:70px 20px;color:var(--fx-muted)}
/* author chip + reputation pill */
.fx-user{display:inline-flex;align-items:center;gap:6px;color:var(--fx-muted)}
.fx-user .fx-uname{font-weight:600;color:var(--fx-text)}
a.fx-user:hover .fx-uname{color:var(--fx-accent)}
.fx-lvl{font-size:11px;font-weight:800;line-height:1;padding:3px 6px;border-radius:5px;background:var(--fx-accent-soft);color:var(--fx-accent)}
.fx-lvl.trust{background:var(--fx-green-soft);color:var(--fx-green)}
.fx-lvl.exp{background:rgba(217,119,6,.16);color:#b45309}
/* sort tabs */
.fx-sorts{display:inline-flex;gap:2px;background:var(--fx-chip);border:1px solid var(--fx-border);border-radius:10px;padding:3px}
.fx-sort{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:8px;font-size:13.5px;font-weight:600;color:var(--fx-muted)}
.fx-sort:hover{color:var(--fx-text)}
.fx-sort.on{background:var(--fx-surface);color:var(--fx-accent);box-shadow:var(--fx-shadow)}
/* profile */
.fx-profhead{display:flex;align-items:center;gap:20px;background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:16px;padding:26px 28px;box-shadow:var(--fx-shadow);margin-bottom:26px}
.fx-ava-prof{width:76px;height:76px;font-size:30px;font-weight:800}
.fx-profinfo{flex:1;min-width:0}
.fx-profname{font-size:24px;font-weight:800;letter-spacing:-.02em;margin:0;line-height:1.2}
.fx-profuser{color:var(--fx-muted);font-size:14px;margin:2px 0 0}
.fx-profbio{margin:10px 0 0;color:var(--fx-text);font-size:14.5px;line-height:1.6}
.fx-profstats{display:flex;gap:26px;margin-top:14px}
.fx-profstat b{display:block;font-size:22px;font-weight:800;letter-spacing:-.01em;line-height:1.1}
.fx-profstat span{font-size:12px;color:var(--fx-muted);text-transform:uppercase;letter-spacing:.04em}
.fx-badges{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:26px}
.fx-badge{display:inline-flex;align-items:center;gap:7px;background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:999px;padding:7px 14px;font-size:13px;font-weight:600;box-shadow:var(--fx-shadow)}
.fx-badge i{font-size:15px;color:var(--fx-accent)}
.fx-badge.gold i{color:#d99206}
.fx-profsec-h{font-size:16px;font-weight:800;letter-spacing:-.01em;margin:0 0 12px}
.fx-plist{display:flex;flex-direction:column;gap:2px;margin-bottom:26px}
.fx-prow{display:flex;align-items:center;gap:14px;padding:13px 16px;border:1px solid var(--fx-border);border-radius:11px;background:var(--fx-surface)}
.fx-prow:hover{border-color:var(--fx-accent)}
.fx-prow .sc{min-width:44px;text-align:center;flex-shrink:0}
.fx-prow .sc b{display:block;font-size:16px;font-weight:800;line-height:1}
.fx-prow .sc span{font-size:11px;color:var(--fx-muted)}
.fx-prow .sc.ok b{color:var(--fx-green)}
.fx-prow .tt{flex:1;min-width:0;font-weight:600;color:var(--fx-accent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fx-prow .dt{flex-shrink:0;font-size:12px;color:var(--fx-muted)}
/* question page */
.fx-back{display:inline-flex;align-items:center;gap:6px;color:var(--fx-muted);font-weight:600;font-size:13px;margin-bottom:14px}
.fx-back:hover{color:var(--fx-accent)}
.fx-qhead{background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:14px;padding:22px 24px;box-shadow:var(--fx-shadow)}
.fx-qtitle{font-size:24px;font-weight:800;letter-spacing:-.02em;margin:0 0 12px;line-height:1.3}
.fx-qbar{display:flex;flex-wrap:wrap;gap:8px 18px;color:var(--fx-muted);font-size:13px;border-bottom:1px solid var(--fx-border);padding-bottom:14px;margin-bottom:18px}
.fx-qbar b{color:var(--fx-text)}
.fx-qrow{display:flex;gap:18px;align-items:flex-start}
.fx-body{font-size:15.5px;line-height:1.75}
.fx-body p{margin:0 0 14px}
.fx-body h1,.fx-body h2,.fx-body h3{letter-spacing:-.01em;margin:22px 0 10px}
.fx-body pre{background:var(--fx-chip);border:1px solid var(--fx-border);border-radius:10px;padding:14px 16px;overflow:auto;font-size:13.5px}
.fx-body code{background:var(--fx-chip);border-radius:5px;padding:1px 6px;font-size:.9em}
.fx-body pre code{background:none;padding:0}
.fx-body blockquote{margin:0 0 14px;padding:2px 16px;border-left:3px solid var(--fx-accent);color:var(--fx-muted)}
.fx-body a{color:var(--fx-accent);text-decoration:underline}
.fx-body ul,.fx-body ol{margin:0 0 14px;padding-left:22px}
.fx-answers-h{font-size:19px;font-weight:800;letter-spacing:-.01em;margin:34px 0 14px}
.fx-answer{display:flex;gap:18px;background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:14px;padding:20px 22px;box-shadow:var(--fx-shadow);margin-bottom:14px}
.fx-answer.accepted{border-color:var(--fx-green);box-shadow:0 0 0 1px var(--fx-green) inset,var(--fx-shadow)}
.fx-vote{display:flex;flex-direction:column;align-items:center;gap:3px;min-width:52px;color:var(--fx-muted)}
.fx-vote .n{font-size:20px;font-weight:800;color:var(--fx-text)}
.fx-vote .lbl{font-size:11px}
.fx-vb{border:1px solid var(--fx-border);background:var(--fx-surface);color:var(--fx-muted);width:36px;height:32px;border-radius:8px;display:grid;place-items:center;cursor:pointer;font-size:17px;line-height:1;transition:all .12s;padding:0}
.fx-vb:hover{border-color:var(--fx-accent);color:var(--fx-accent)}
.fx-vb.up.on{background:var(--fx-accent);border-color:var(--fx-accent);color:#fff}
.fx-vb.down.on{background:#d64545;border-color:#d64545;color:#fff}
.fx-acceptbtn{margin-top:5px;border:1px solid var(--fx-border);background:var(--fx-surface);color:var(--fx-muted);width:34px;height:34px;border-radius:50%;display:grid;place-items:center;cursor:pointer;font-size:18px;transition:all .12s;padding:0}
.fx-acceptbtn:hover:not([disabled]){border-color:var(--fx-green);color:var(--fx-green)}
.fx-acceptbtn.on{background:var(--fx-green);border-color:var(--fx-green);color:#fff}
.fx-acceptbtn[disabled]{cursor:default}
.fx-accepted{display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:var(--fx-green-soft);color:var(--fx-green);font-size:17px;margin-top:6px}
.fx-toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);background:var(--fx-text);color:var(--fx-surface);padding:11px 18px;border-radius:10px;font-size:14px;font-weight:600;box-shadow:0 6px 24px rgba(0,0,0,.2);opacity:0;transition:opacity .2s,transform .2s;z-index:50;pointer-events:none}
.fx-toast.show{opacity:1;transform:translateX(-50%) translateY(-4px)}
.fx-abody{flex:1;min-width:0}
.fx-acredit{display:flex;justify-content:flex-end;margin-top:16px}
.fx-credit{display:inline-flex;align-items:center;gap:9px;background:var(--fx-accent-soft);border-radius:10px;padding:9px 13px;font-size:13px}
.fx-credit .nm{font-weight:700;color:var(--fx-accent)}
.fx-credit-when{color:var(--fx-muted);font-size:12.5px}
.fx-cta{margin:30px 0 0;text-align:center;background:var(--fx-surface);border:1px dashed var(--fx-border);border-radius:14px;padding:26px}
.fx-cta a{display:inline-block;margin-top:10px;background:var(--fx-accent);color:#fff;font-weight:700;padding:10px 20px;border-radius:9px}
.fx-card-form{background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:14px;padding:24px 26px;box-shadow:var(--fx-shadow)}
.fx-field{margin-bottom:18px}
.fx-field label{display:block;font-weight:700;font-size:14px;margin-bottom:6px}
.fx-field .hint{font-weight:400;color:var(--fx-muted);font-size:12.5px;margin-left:6px}
.fx-input,.fx-select,.fx-textarea{width:100%;background:var(--fx-bg);border:1px solid var(--fx-border);border-radius:10px;padding:11px 13px;color:var(--fx-text);font:inherit;font-size:15px;transition:border-color .12s}
.fx-input:focus,.fx-select:focus,.fx-textarea:focus{outline:none;border-color:var(--fx-accent);box-shadow:0 0 0 3px var(--fx-accent-soft)}
.fx-textarea{min-height:180px;resize:vertical;line-height:1.6;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}
.fx-tagbox{display:flex;flex-wrap:wrap;gap:7px;align-items:center;background:var(--fx-bg);border:1px solid var(--fx-border);border-radius:10px;padding:8px 10px;min-height:46px;cursor:text}
.fx-tagbox:focus-within{border-color:var(--fx-accent);box-shadow:0 0 0 3px var(--fx-accent-soft)}
.fx-taginput{flex:1;min-width:120px;border:none;background:none;outline:none;color:var(--fx-text);font:inherit;font-size:15px;padding:3px 2px}
.fx-tagpill{display:inline-flex;align-items:center;gap:6px;background:var(--fx-accent-soft);color:var(--fx-accent);border-radius:6px;padding:4px 6px 4px 9px;font-size:13px;font-weight:600;line-height:1}
.fx-tagpill button{border:none;background:none;color:var(--fx-accent);cursor:pointer;font-size:15px;line-height:1;padding:0 2px;opacity:.7}
.fx-tagpill button:hover{opacity:1}
.fx-row2{display:flex;gap:14px}.fx-row2>*{flex:1}
.fx-submit{background:var(--fx-accent);color:#fff;font-weight:700;font-size:15px;border:none;border-radius:10px;padding:12px 22px;cursor:pointer;transition:filter .12s}
.fx-submit:hover{filter:brightness(1.06)}
.fx-formerr{background:#fdecec;color:#b42318;border:1px solid #f5c2c0;border-radius:10px;padding:11px 14px;font-size:14px;margin-bottom:16px}
@media (prefers-color-scheme:dark){.fx-formerr{background:#2a1414;color:#f2a49e;border-color:#5a2020}}
.fx-formok{background:var(--fx-green-soft);color:var(--fx-green);border:1px solid var(--fx-green);border-radius:10px;padding:11px 14px;font-size:14px;margin-bottom:16px}
.fx-2fa-mark{width:52px;height:52px;margin:0 auto 14px;display:grid;place-items:center;border-radius:50%;background:var(--fx-accent-soft);color:var(--fx-accent);font-size:24px}
.fx-2fa-input{text-align:center;font-size:22px;font-weight:700;letter-spacing:.35em;font-variant-numeric:tabular-nums}
.fx-2fa-input::placeholder{letter-spacing:.35em;font-weight:400}
.fx-2fa-alt{display:flex;flex-direction:column;align-items:center;gap:2px;margin-top:18px}
.fx-2fa-alt a,.fx-2fa-alt button{background:none;border:0;cursor:pointer;color:var(--fx-muted);font:inherit;font-size:13px;font-weight:600;padding:7px 10px;display:inline-flex;align-items:center;gap:7px}
.fx-2fa-alt a:hover,.fx-2fa-alt button:hover{color:var(--fx-accent)}
.fx-2fa-quit:hover{color:#d64545 !important}
.fx-answerbox{margin-top:30px;background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:14px;padding:22px 24px;box-shadow:var(--fx-shadow)}
.fx-answerbox h3{margin:0 0 14px;font-size:18px;font-weight:800}
.fx-mdhint{color:var(--fx-muted);font-size:12.5px;margin:8px 0 0}
/* auth pages */
.fx-authbody{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;min-height:100dvh;padding:34px 16px;background:radial-gradient(1100px 460px at 50% -8%,var(--fx-accent-soft),transparent),var(--fx-bg)}
.fx-auth{width:100%;max-width:420px;display:flex;flex-direction:column;align-items:center}
.fx-auth-brand{display:flex;align-items:center;gap:10px;font-size:19px;font-weight:700;margin-bottom:22px;letter-spacing:-.01em;color:var(--fx-text)}
.fx-auth-brand .fx-logo{width:38px;height:38px}
.fx-auth-card{width:100%;background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:18px;padding:30px 30px 26px;box-shadow:0 12px 44px rgba(16,24,40,.12)}
.fx-auth-card h1{font-size:23px;font-weight:800;letter-spacing:-.02em;margin:0 0 4px;text-align:center}
.fx-auth-sub{color:var(--fx-muted);text-align:center;margin:0 0 22px;font-size:14.5px}
.fx-auth-alt{text-align:center;color:var(--fx-muted);font-size:14px;margin-top:18px}
.fx-auth-alt a{color:var(--fx-accent);font-weight:700}
.fx-auth-back{margin:18px 0 0;font-size:13px}
.fx-auth-back a{color:var(--fx-muted);font-weight:600}.fx-auth-back a:hover{color:var(--fx-accent)}
.fx-auth-note{display:flex;align-items:center;gap:7px;justify-content:center;color:var(--fx-muted);font-size:12.5px;margin-top:16px}
.fx-checkrow{display:flex;align-items:center;justify-content:space-between;margin:2px 0 6px}
.fx-checkrow label{display:flex;align-items:center;gap:7px;color:var(--fx-muted);font-size:13.5px;cursor:pointer}
.fx-checkrow a{color:var(--fx-accent);font-weight:600;font-size:13.5px}
.fx-submit.full{width:100%;justify-content:center;margin-top:4px}
.fx-ok{background:var(--fx-green-soft);color:var(--fx-green);border:1px solid var(--fx-green);border-radius:10px;padding:11px 14px;font-size:14px;margin-bottom:16px}
.fx-authbody .fx-field{margin-bottom:15px}
.fx-hp{position:absolute!important;left:-9999px;width:1px;height:1px;overflow:hidden}
/* navbar inline search */
.fx-navsearch{display:flex;align-items:center;background:var(--fx-bg);border:1px solid var(--fx-border);border-radius:9px;padding:0 6px 0 11px;height:38px;width:210px;transition:border-color .12s,width .18s}
.fx-navsearch:focus-within{border-color:var(--fx-accent);width:240px}
.fx-navsearch i{color:var(--fx-muted);font-size:14px}
.fx-navsearch input{flex:1;min-width:0;border:none;background:none;outline:none;color:var(--fx-text);font:inherit;font-size:14px;padding:0 8px}
.fx-navsearch-ico{display:none;border:1px solid var(--fx-border);background:var(--fx-surface);color:var(--fx-muted);width:38px;height:38px;border-radius:9px;place-items:center;cursor:pointer;padding:0;font-size:15px;flex:0 0 auto}
.fx-navsearch-ico:hover,.fx-navsearch-ico[aria-expanded="true"]{color:var(--fx-accent);border-color:var(--fx-accent)}
/* full-width expandable search row (mobile) */
.fx-searchrow{display:none;border-top:1px solid var(--fx-border);padding:10px 14px;background:var(--fx-surface)}
.fx-searchrow form{display:flex;align-items:center;gap:8px;background:var(--fx-bg);border:1px solid var(--fx-border);border-radius:10px;padding:0 8px 0 12px;height:44px}
.fx-searchrow form:focus-within{border-color:var(--fx-accent)}
.fx-searchrow i{color:var(--fx-muted);font-size:16px}
.fx-searchrow input{flex:1;min-width:0;border:none;background:none;outline:none;color:var(--fx-text);font:inherit;font-size:16px;padding:0 4px}
.fx-searchrow button{flex:0 0 auto;width:34px;height:34px;border:0;border-radius:8px;background:var(--fx-accent);color:#fff;display:grid;place-items:center;cursor:pointer;font-size:15px}
/* mobile: hide the inline box, show the toggle + expandable row (after base rules so these win) */
@media(max-width:760px){
  .fx-navsearch{display:none}
  .fx-navsearch-ico{display:grid}
  .fx-searchrow.open{display:block;animation:fxslide .16s ease}
}
@keyframes fxslide{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.fx-foot{border-top:1px solid var(--fx-border);background:var(--fx-surface);flex-shrink:0}
.fx-foot-in{max-width:960px;margin:0 auto;padding:20px 18px;display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;color:var(--fx-muted);font-size:13px}
.fx-foot-links{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.fx-foot-links a{color:var(--fx-muted);font-weight:600}
.fx-foot-links a:hover{color:var(--fx-accent)}
.fx-foot-cta{display:inline-flex;align-items:center;gap:7px;background:linear-gradient(120deg,#4f46e5,#7c3aed);color:#fff!important;padding:8px 15px;border-radius:9px;font-weight:700;font-size:12.5px;box-shadow:0 4px 14px rgba(79,70,229,.28);transition:transform .12s,box-shadow .12s}
.fx-foot-cta:hover{color:#fff!important;transform:translateY(-1px);box-shadow:0 7px 20px rgba(79,70,229,.42)}
/* pagination */
.fx-pager{display:flex;justify-content:center;align-items:center;gap:6px;margin:30px 0 4px;flex-wrap:wrap}
.fx-pg{display:grid;place-items:center;min-width:38px;height:38px;padding:0 11px;border:1px solid var(--fx-border);border-radius:9px;background:var(--fx-surface);color:var(--fx-text);font-weight:700;font-size:14px;box-shadow:var(--fx-shadow);transition:border-color .12s,color .12s}
a.fx-pg:hover{border-color:var(--fx-accent);color:var(--fx-accent)}
.fx-pg.is-cur{background:var(--fx-accent);border-color:var(--fx-accent);color:#fff;cursor:default}
.fx-pg.is-dis{opacity:.4;pointer-events:none}
.fx-pg-gap{color:var(--fx-muted);padding:0 3px}
/* suspended banner */
.fx-suspended{display:flex;align-items:center;gap:10px;background:#fdecec;color:#b42318;border:1px solid #f5c2c0;border-radius:11px;padding:12px 16px;margin:0 0 18px;font-size:14px;font-weight:600}
:root[data-theme="dark"] .fx-suspended{background:#2a1414;color:#f2a49e;border-color:#5a2020}
/* admin dashboard */
.fx-admin-grid{display:flex;flex-direction:column;gap:18px}
.fx-admin-card{background:var(--fx-surface);border:1px solid var(--fx-border);border-radius:14px;padding:20px 22px;box-shadow:var(--fx-shadow)}
.fx-admin-card h2{font-size:16px;font-weight:800;margin:0 0 4px;display:flex;align-items:center;gap:8px}
.fx-admin-card h2 i{color:var(--fx-accent)}
.fx-admin-card .fx-sub{margin:0 0 16px}
.fx-mod-grid{display:grid;grid-template-columns:1fr;gap:20px}
@media(min-width:820px){.fx-mod-grid{grid-template-columns:1fr 1fr}}
.fx-mod-h{font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:var(--fx-muted);font-weight:800;margin:0 0 8px}
.fx-mod-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.fx-mod-item{display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid var(--fx-border)}
.fx-mod-item:first-child{border-top:0}
.fx-mod-main{flex:1;min-width:0}
.fx-mod-main a{display:block;font-weight:600;font-size:13.5px;color:var(--fx-text);text-decoration:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fx-mod-main a:hover{color:var(--fx-accent)}
.fx-mod-meta{font-size:11.5px;color:var(--fx-muted)}
.fx-mod-item.is-gone .fx-mod-main a{color:var(--fx-muted);text-decoration:line-through}
.fx-mod-item.is-gone{opacity:.72}
.fx-btn-sm{padding:5px 9px;font-size:12px;flex:0 0 auto}
.fx-utable-wrap{overflow-x:auto}
.fx-utable{width:100%;border-collapse:collapse;font-size:14px;min-width:520px}
.fx-utable th{text-align:left;color:var(--fx-muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.04em;padding:8px 10px;border-bottom:1px solid var(--fx-border)}
.fx-utable td{padding:10px;border-bottom:1px solid var(--fx-border);vertical-align:middle}
.fx-utable tr:last-child td{border-bottom:0}
.fx-ustatus{display:inline-block;padding:3px 9px;border-radius:6px;font-size:12px;font-weight:700}
.fx-ustatus.active{background:var(--fx-green-soft);color:var(--fx-green)}
.fx-ustatus.suspended{background:rgba(214,69,69,.14);color:#d64545}
.fx-btn{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--fx-border);background:var(--fx-surface);color:var(--fx-text);border-radius:8px;padding:7px 12px;font:inherit;font-weight:700;font-size:13px;cursor:pointer}
.fx-btn:hover{border-color:var(--fx-accent);color:var(--fx-accent)}
.fx-btn.danger:hover{border-color:#d64545;color:#d64545}
.fx-btn.primary{background:var(--fx-accent);border-color:var(--fx-accent);color:#fff}
.fx-btn.primary:hover{filter:brightness(1.06);color:#fff}
.fx-toggle-row{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:12px 0;border-top:1px solid var(--fx-border)}
.fx-toggle-row:first-of-type{border-top:0}
.fx-toggle-row .t b{display:block;font-size:14px}
.fx-toggle-row .t span{font-size:12.5px;color:var(--fx-muted)}
.fx-admin-filter{display:flex;gap:10px;margin-bottom:14px;flex-wrap:wrap}
.fx-flash{background:var(--fx-green-soft);color:var(--fx-green);border:1px solid var(--fx-green);border-radius:10px;padding:11px 14px;font-size:14px;font-weight:600;margin-bottom:18px}
@media(max-width:560px){.fx-card{padding:14px}.fx-stats{min-width:54px}.fx-qtitle{font-size:20px}.fx-answer{padding:16px}}
CSS;
}
