<?php
/**
 * VFACE — Pseudonymous Identity Tool
 *
 * Create and verify persistent pseudonymous identities.
 * Backend: identicons-cli (Ch1ffr3punk algorithm) + PHP sodium (Ed25519)
 *
 * @version 1.0.0
 */

// ============================================
// CONFIGURATION
// ============================================

define('IDENTICONS_CLI', __DIR__ . '/identicons-cli');
define('MAX_UPLOAD_SIZE', 512 * 1024);
define('BLOG_ARTICLE', 'https://www.virebent.art/blog/vface_identicons.html');

// ============================================
// SECURITY
// ============================================

// Reset: redirect clean
if (isset($_GET['reset'])) {
    header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
    exit;
}

function sanitize(string $v, int $max = 255): string {
    $v = trim($v);
    if (mb_strlen($v) > $max) $v = mb_substr($v, 0, $max);
    return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $v);
}

/**
 * Clean a public key from any format to raw key content.
 * Handles: PEM (any type), SSH format, hex, base64, raw paste.
 */
function cleanPublicKey(string $raw): string {
    $key = trim($raw);
    if (empty($key)) return '';

    // Strip PEM headers/footers (-----BEGIN ... -----, -----END ... -----)
    $key = preg_replace('/-----BEGIN [A-Z\s]+-----/', '', $key);
    $key = preg_replace('/-----END [A-Z\s]+-----/', '', $key);

    // Strip SSH prefix (ssh-ed25519, ssh-rsa, ecdsa-sha2-nistp256, etc.)
    $key = preg_replace('/^(ssh-\S+|ecdsa-\S+)\s+/', '', trim($key));

    // Strip SSH trailing comment (user@host)
    $key = preg_replace('/\s+\S+@\S+\s*$/', '', $key);
    $key = preg_replace('/\s+[^\s=+\/]+\s*$/', '', $key);

    // Remove all whitespace and newlines
    $key = preg_replace('/\s+/', '', $key);

    return $key;
}

// ============================================
// IDENTICON GENERATION VIA CLI
// ============================================

function generateIdenticonBase64(string $input, int $size): ?string {
    if (!is_executable(IDENTICONS_CLI)) return null;
    $cmd = IDENTICONS_CLI . ' -input ' . escapeshellarg($input) .
           ' -size ' . intval($size) . ' -transparent -format base64 2>&1';
    $out = trim(shell_exec($cmd) ?? '');
    if (empty($out) || !preg_match('/^[A-Za-z0-9+\/=]+$/', $out)) return null;
    return $out;
}

// ============================================
// FACE HEADER FOLDING (RFC 2822)
// ============================================

function foldFaceHeader(string $base64): string {
    $maxFirst = 72 - strlen('Face: ');
    $maxCont  = 72 - 1;
    $lines = ['Face: ' . substr($base64, 0, $maxFirst)];
    $offset = $maxFirst;
    while ($offset < strlen($base64)) {
        $lines[] = ' ' . substr($base64, $offset, $maxCont);
        $offset += $maxCont;
    }
    return implode("\n", $lines);
}

// ============================================
// ED25519 KEYPAIR GENERATION
// ============================================

function generateKeypair(): array {
    $kp = sodium_crypto_sign_keypair();
    $sk = sodium_crypto_sign_secretkey($kp);
    $pk = sodium_crypto_sign_publickey($kp);
    $result = [
        'publicKey'  => base64_encode($pk),
        'secretKey'  => base64_encode($sk),
        'created'    => gmdate('c'),
    ];
    // Wipe sensitive material from memory
    sodium_memzero($sk);
    sodium_memzero($kp);
    return $result;
}

// ============================================
// ED25519 SIGNATURE VERIFICATION
// ============================================

function verifySignature(string $message, string $signatureB64, string $pubkeyB64): bool {
    $sig = base64_decode($signatureB64, true);
    $pk  = base64_decode($pubkeyB64, true);
    if ($sig === false || $pk === false) return false;
    if (strlen($sig) !== 64 || strlen($pk) !== 32) return false;
    return sodium_crypto_sign_verify_detached($sig, $message, $pk);
}

// ============================================
// STATIC IDENTITY PAGE GENERATOR
// ============================================

function buildIdentityPageHTML(
    string $username, string $email, string $pubkey,
    string $hash, string $identicon256, string $faceBase64
): string {
    $u = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');
    $e = htmlspecialchars($email, ENT_QUOTES, 'UTF-8');
    $p = htmlspecialchars($pubkey, ENT_QUOTES, 'UTF-8');
    $h = htmlspecialchars($hash, ENT_QUOTES, 'UTF-8');
    $faceHeader = htmlspecialchars(foldFaceHeader($faceBase64), ENT_QUOTES, 'UTF-8');
    $vfaceHeaders = htmlspecialchars(
        "From: {$username} <{$email}>\n" .
        "Ed25519-Pub: {$pubkey}\n" .
        "Ed25519-Sig: [sign message body with your private key]\n" .
        "Identity-Hash: {$hash}\n" .
        foldFaceHeader($faceBase64),
        ENT_QUOTES, 'UTF-8'
    );

    return <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VFACE Identity — {$u}</title>
<style>
body{font-family:-apple-system,sans-serif;background:#0d1117;color:#c9d1d9;max-width:600px;margin:2rem auto;padding:1rem;line-height:1.6}
h1{color:#58a6ff;font-size:1.4rem}
.id-card{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:1.5rem;margin:1.5rem 0}
.field{margin-bottom:1rem}
.label{color:#8b949e;font-size:0.8rem;text-transform:uppercase;letter-spacing:0.5px}
.value{font-family:monospace;font-size:0.9rem;word-break:break-all;margin-top:0.2rem}
.hash{color:#58a6ff}
.identicon{text-align:center;margin:1.5rem 0}
.identicon img{border:2px solid #30363d;border-radius:8px;image-rendering:pixelated}
.verify{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:1.5rem;margin-top:1.5rem;font-size:0.85rem;color:#8b949e}
.verify a{color:#58a6ff;text-decoration:none}
.verify a:hover{text-decoration:underline}
.copyable{background:#1c2129;border:1px solid #30363d;border-radius:4px;padding:0.5rem 0.7rem;margin:0.4rem 0;font-family:monospace;font-size:0.78rem;color:#c9d1d9;width:100%;resize:none;height:2.2rem;overflow:hidden;cursor:pointer;display:block;box-sizing:border-box}
.copyable:focus{height:auto;overflow:visible;outline:1px solid #58a6ff}
.copyable-hint{font-size:0.72rem;color:#8b949e;margin-top:0.2rem}
</style>
</head>
<body>
<h1>VFACE Identity</h1>
<div class="id-card">
<div class="identicon"><img src="data:image/png;base64,{$identicon256}" width="256" height="256" alt="Identicon"></div>
<div class="field"><div class="label">Username</div><div class="value">{$u}</div></div>
<div class="field"><div class="label">Email</div><div class="value">{$e}</div></div>
<div class="field"><div class="label">Public Key</div><div class="value">{$p}</div></div>
<div class="field"><div class="label">Identity Hash</div><div class="value hash">{$h}</div></div>
</div>
<div class="verify" style="margin-top:1.5rem;">
<strong>VFACE Headers for Usenet / Email:</strong><br>
<span class="copyable-hint">Copy these headers into your client. Replace the signature line after signing your message body.</span>
<pre style="background:#1c2129;border:1px solid #30363d;border-radius:4px;padding:0.8rem;margin:0.6rem 0;font-size:0.78rem;color:#c9d1d9;overflow-x:auto;white-space:pre;line-height:1.5">{$vfaceHeaders}</pre>
</div>
<div class="verify">
<strong>How to verify this identity:</strong><br><br>
1. Concatenate this string:<br>
<textarea readonly class="copyable" onclick="this.select()">{$u}|{$e}|{$p}</textarea>
<span class="copyable-hint">Click to select all — then copy</span><br>
2. Calculate SHA256 of that string — it should match the Identity Hash above.<br><br>
3. Go to <a href="https://identicons.virebent.art" rel="noopener">identicons.virebent.art</a> and use the Analyze tab to verify the identicon, or the Create tab to regenerate it from the same input.<br><br>
4. For signed messages: verify the Ed25519 signature with the public key above.<br><br>
5. Check the <code>.ots</code> file alongside this page for Bitcoin timestamp proof (<a href="https://opentimestamps.org" rel="noopener">opentimestamps.org</a>).
</div>
</body>
</html>
HTML;
}

// ============================================
// CH1FFR3PUNK PALETTES (for analysis)
// ============================================

const PRIMARY_PALETTE = [
    [0x00, 0xbf, 0x93], [0x2d, 0xcc, 0x70], [0x42, 0xe4, 0x53], [0xf1, 0xc4, 0x0f],
    [0xe6, 0x7f, 0x22], [0xff, 0x94, 0x4e], [0xe8, 0x4c, 0x3d], [0x35, 0x98, 0xdb],
    [0x9a, 0x59, 0xb5], [0xef, 0x3e, 0x96], [0xdf, 0x21, 0xb9], [0x7d, 0xc2, 0xd2],
    [0x16, 0xa0, 0x86], [0x27, 0xae, 0x61], [0x24, 0xc3, 0x33], [0x1c, 0xab, 0xbb],
];

const SECONDARY_PALETTE = [
    [0x34, 0x49, 0x5e], [0x95, 0xa5, 0xa5], [0xd2, 0x54, 0x00], [0xc1, 0x39, 0x2b],
    [0x29, 0x7f, 0xb8], [0x8d, 0x44, 0xad], [0xbe, 0x12, 0x7e], [0xe5, 0x23, 0x83],
    [0x27, 0xae, 0x61], [0x24, 0xc3, 0x33], [0xd9, 0xd9, 0x21], [0xf3, 0x9c, 0x11],
    [0xff, 0x55, 0x00], [0x1c, 0xab, 0xbb], [0x23, 0x23, 0x23], [0x7e, 0x8c, 0x8d],
];

const BACKGROUNDS = [
    [255, 255, 255], [243, 245, 247], [236, 240, 241],
];

function colorsMatch(array $a, array $b, int $tolerance = 5): bool {
    return abs($a['r'] - $b['r']) <= $tolerance &&
           abs($a['g'] - $b['g']) <= $tolerance &&
           abs($a['b'] - $b['b']) <= $tolerance;
}

function colorDistance(array $a, array $b): float {
    return sqrt(pow($a[0] - $b[0], 2) + pow($a[1] - $b[1], 2) + pow($a[2] - $b[2], 2));
}

function analyzeIdenticon(string $filePath): array {
    $result = [
        'valid' => false, 'dimensions' => null, 'cell_size' => null,
        'symmetric' => false, 'colors_found' => [],
        'primary_color' => null, 'secondary_color' => null,
        'background' => null, 'bg_match' => false,
        'primary_match' => false, 'secondary_match' => false,
        'palette_score' => 0, 'errors' => [], 'dataurl' => null,
    ];

    $info = @getimagesize($filePath);
    if ($info === false) { $result['errors'][] = 'Not a valid image file.'; return $result; }
    if ($info[2] !== IMAGETYPE_PNG) { $result['errors'][] = 'Image must be PNG format.'; return $result; }

    $width = $info[0]; $height = $info[1];
    $result['dimensions'] = "{$width}x{$height}";

    if ($width !== $height) { $result['errors'][] = 'Image is not square.'; return $result; }
    if ($width < 10) { $result['errors'][] = 'Image too small.'; return $result; }

    $spriteSize = 5;
    $cellSize = intdiv($width, $spriteSize);
    $margin = intdiv($width - $spriteSize * $cellSize, 2);
    $result['cell_size'] = $cellSize;

    $img = @imagecreatefrompng($filePath);
    if (!$img) { $result['errors'][] = 'Failed to load PNG.'; return $result; }

    if (!imageistruecolor($img)) { imagepalettetotruecolor($img); }
    imagealphablending($img, false);
    imagesavealpha($img, true);

    ob_start(); imagepng($img); $pngData = ob_get_clean();
    $result['dataurl'] = 'data:image/png;base64,' . base64_encode($pngData);

    $grid = []; $colorMap = [];
    for ($row = 0; $row < $spriteSize; $row++) {
        $grid[$row] = [];
        for ($col = 0; $col < $spriteSize; $col++) {
            $px = min($col * $cellSize + $margin + intdiv($cellSize, 2), $width - 1);
            $py = min($row * $cellSize + $margin + intdiv($cellSize, 2), $width - 1);
            $rgba = imagecolorat($img, $px, $py);
            $r = ($rgba >> 16) & 0xFF; $g = ($rgba >> 8) & 0xFF;
            $b = $rgba & 0xFF; $a = ($rgba >> 24) & 0x7F;
            $grid[$row][$col] = ['r' => $r, 'g' => $g, 'b' => $b, 'a' => $a];
            if ($a < 64) { $colorMap["{$r},{$g},{$b}"] = [$r, $g, $b]; }
        }
    }

    $symmetryOk = true;
    for ($row = 0; $row < 5; $row++) {
        for ($pair = 0; $pair < 2; $pair++) {
            if (!colorsMatch($grid[$row][$pair], $grid[$row][4 - $pair], 5)) {
                $symmetryOk = false; break 2;
            }
        }
    }
    $result['symmetric'] = $symmetryOk;
    $result['colors_found'] = array_values($colorMap);

    $bgColor = null; $fgColors = [];
    $cornerColor = $grid[0][0];

    foreach (BACKGROUNDS as $bg) {
        if (colorsMatch(['r'=>$bg[0],'g'=>$bg[1],'b'=>$bg[2],'a'=>0], $cornerColor, 5)) {
            $result['bg_match'] = true; $bgColor = $bg; break;
        }
    }

    $hasTransparent = false;
    for ($row = 0; $row < 5; $row++) {
        for ($col = 0; $col < 5; $col++) {
            if ($grid[$row][$col]['a'] >= 64) { $hasTransparent = true; break 2; }
        }
    }
    if ($hasTransparent) { $result['bg_match'] = true; $result['background'] = 'transparent'; }
    elseif ($bgColor) { $result['background'] = sprintf('#%02x%02x%02x', $bgColor[0], $bgColor[1], $bgColor[2]); }

    foreach ($colorMap as $key => $rgb) {
        if ($bgColor && colorsMatch(['r'=>$rgb[0],'g'=>$rgb[1],'b'=>$rgb[2],'a'=>0],
            ['r'=>$bgColor[0],'g'=>$bgColor[1],'b'=>$bgColor[2],'a'=>0], 5)) continue;
        $fgColors[] = $rgb;
    }

    foreach ($fgColors as $fg) {
        foreach (PRIMARY_PALETTE as $idx => $pal) {
            if (colorDistance($fg, $pal) < 10) {
                $result['primary_match'] = true;
                $result['primary_color'] = ['rgb'=>$fg, 'hex'=>sprintf('#%02x%02x%02x',$fg[0],$fg[1],$fg[2]), 'index'=>$idx];
                break 2;
            }
        }
    }

    foreach ($fgColors as $fg) {
        if ($result['primary_color'] && colorDistance($fg, $result['primary_color']['rgb']) < 10) continue;
        foreach (SECONDARY_PALETTE as $idx => $pal) {
            if (colorDistance($fg, $pal) < 10) {
                $result['secondary_match'] = true;
                $result['secondary_color'] = ['rgb'=>$fg, 'hex'=>sprintf('#%02x%02x%02x',$fg[0],$fg[1],$fg[2]), 'index'=>$idx];
                break 2;
            }
        }
    }

    $score = 0;
    if ($result['symmetric']) $score += 30;
    if ($result['bg_match']) $score += 20;
    if ($result['primary_match']) $score += 25;
    if ($result['secondary_match']) $score += 25;
    $result['palette_score'] = $score;

    $result['valid'] = ($result['symmetric'] && $result['bg_match'] &&
        $result['primary_match'] && $result['secondary_match'] &&
        count($colorMap) >= 2 && count($colorMap) <= 4);

    imagedestroy($img);
    return $result;
}

// ============================================
// PROCESS REQUESTS
// ============================================

$tab       = $_POST['tab'] ?? 'create';
$result    = null;
$verifyRes = null;
$anaResult = null;
$error     = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $tab = $_POST['tab'] ?? 'create';

        // ---- TAB: CREATE ----
        if ($tab === 'create') {
            $username = sanitize($_POST['username'] ?? '', 64);
            $email    = sanitize($_POST['email'] ?? '', 254);
            $keyMode  = $_POST['key_mode'] ?? 'generate';
            $rawPubkey = sanitize($_POST['pubkey'] ?? '', 8192);

            if (empty($username)) { $error = 'Pick a username.'; }
            elseif (empty($email)) { $error = 'Enter an email address.'; }
            elseif ($keyMode === 'existing' && empty($rawPubkey)) {
                $error = 'Paste your public key.';
            }

            if (!$error) {
                $keypair = null;
                if ($keyMode === 'generate') {
                    if (!function_exists('sodium_crypto_sign_keypair')) {
                        $error = 'Server does not support Ed25519 (sodium extension missing).';
                    } else {
                        $keypair = generateKeypair();
                        $pubkey = $keypair['publicKey'];
                    }
                } else {
                    $pubkey = cleanPublicKey($rawPubkey);
                    if (empty($pubkey)) {
                        $error = 'Could not extract a valid key from your input.';
                    }
                }
                $keyCleaned = isset($pubkey) && $rawPubkey !== $pubkey;
            }

            if (!$error) {
                $input = $username . '|' . $email . '|' . $pubkey;
                $hash  = hash('sha256', $input);
                $ico48  = generateIdenticonBase64($input, 48);
                $ico256 = generateIdenticonBase64($input, 256);

                if (!$ico48 || !$ico256) {
                    $error = 'Failed to generate identicon (binary not found).';
                } else {
                    $result = [
                        'username'   => $username,
                        'email'      => $email,
                        'pubkey'     => $pubkey,
                        'keypair'    => $keypair,
                        'keyCleaned' => $keyCleaned ?? false,
                        'input'      => $input,
                        'hash'       => $hash,
                        'ico48'      => $ico48,
                        'ico256'     => $ico256,
                        'face'       => foldFaceHeader($ico48),
                        'headers'    => "From: {$username} <{$email}>\n" .
                                        "Ed25519-Pub: {$pubkey}\n" .
                                        "Ed25519-Sig: [sign message body with your private key]\n" .
                                        "Identity-Hash: {$hash}\n" .
                                        foldFaceHeader($ico48),
                        'identityPage' => buildIdentityPageHTML(
                            $username, $email, $pubkey, $hash, $ico256, $ico48
                        ),
                    ];
                }
            }

        // ---- TAB: VERIFY ----
        } elseif ($tab === 'verify') {
            $vRawPubkey = sanitize($_POST['v_pubkey'] ?? '', 8192);
            $vSig     = sanitize($_POST['v_signature'] ?? '', 1024);
            $vBody    = $_POST['v_body'] ?? '';
            $vUser    = sanitize($_POST['v_username'] ?? '', 64);
            $vEmail   = sanitize($_POST['v_email'] ?? '', 254);
            $vFace    = sanitize($_POST['v_face'] ?? '', 8192);
            $vPubkey  = cleanPublicKey($vRawPubkey);

            if (empty($vPubkey) || empty($vSig) || empty($vBody)) {
                $error = 'Public key, signature, and message body are required.';
            }

            if (!$error) {
                $sigValid = verifySignature($vBody, $vSig, $vPubkey);

                $hashCheck = null;
                $icoCheck  = null;
                $icoGenerated = null;

                if (!empty($vUser) && !empty($vEmail)) {
                    $expectedInput = $vUser . '|' . $vEmail . '|' . $vPubkey;
                    $expectedHash  = hash('sha256', $expectedInput);
                    $hashCheck = $expectedHash;

                    $icoGenerated = generateIdenticonBase64($expectedInput, 48);

                    if (!empty($vFace) && $icoGenerated) {
                        $cleanFace = trim(preg_replace('/^Face:\s*/m', '', $vFace));
                        $cleanFace = preg_replace('/\s+/', '', $cleanFace);
                        $icoCheck = ($cleanFace === $icoGenerated);
                    }
                }

                $verifyRes = [
                    'sig_valid'     => $sigValid,
                    'hash_expected' => $hashCheck,
                    'ico_match'     => $icoCheck,
                    'ico_generated' => $icoGenerated,
                ];
            }

        // ---- TAB: ANALYZE ----
        } elseif ($tab === 'analyze') {
            if (!isset($_FILES['identicon_file']) || $_FILES['identicon_file']['error'] !== UPLOAD_ERR_OK) {
                $uploadErr = $_FILES['identicon_file']['error'] ?? UPLOAD_ERR_NO_FILE;
                $error = match ($uploadErr) {
                    UPLOAD_ERR_NO_FILE   => 'No file uploaded.',
                    UPLOAD_ERR_INI_SIZE,
                    UPLOAD_ERR_FORM_SIZE => 'File too large.',
                    default              => 'Upload failed (error code: ' . $uploadErr . ').',
                };
            } else {
                $file = $_FILES['identicon_file'];
                if ($file['size'] > MAX_UPLOAD_SIZE) {
                    $error = 'File exceeds maximum size of ' . (MAX_UPLOAD_SIZE / 1024) . ' KB.';
                } elseif ($file['size'] === 0) {
                    $error = 'Uploaded file is empty.';
                } else {
                    $finfo = new finfo(FILEINFO_MIME_TYPE);
                    $mime = $finfo->file($file['tmp_name']);
                    if ($mime !== 'image/png') {
                        $error = 'Only PNG files are accepted (detected: ' . htmlspecialchars($mime) . ').';
                    } else {
                        $anaResult = analyzeIdenticon($file['tmp_name']);
                        if (!empty($anaResult['errors'])) {
                            $error = implode(' ', $anaResult['errors']);
                        }

                        // Optional: compare with identity
                        $anaUser   = sanitize($_POST['ana_username'] ?? '', 64);
                        $anaEmail  = sanitize($_POST['ana_email'] ?? '', 254);
                        $anaRawKey = sanitize($_POST['ana_pubkey'] ?? '', 8192);
                        $anaKey    = cleanPublicKey($anaRawKey);

                        if (!empty($anaUser) && !empty($anaEmail) && !empty($anaKey) && $anaResult && !$error) {
                            $compareInput = $anaUser . '|' . $anaEmail . '|' . $anaKey;
                            $compareHash  = hash('sha256', $compareInput);

                            // Read raw uploaded file and base64 it
                            $uploadedB64 = base64_encode(file_get_contents($file['tmp_name']));

                            // Generate at both sizes and try matching
                            $compareIco48  = generateIdenticonBase64($compareInput, 48);
                            $compareIco256 = generateIdenticonBase64($compareInput, 256);

                            $match = ($compareIco48 && $uploadedB64 === $compareIco48) ||
                                     ($compareIco256 && $uploadedB64 === $compareIco256);

                            $dims = explode('x', $anaResult['dimensions'] ?? '48x48');
                            $showSize = (intval($dims[0]) > 48) ? 256 : 48;
                            $showIco = ($showSize === 256) ? $compareIco256 : $compareIco48;

                            $anaResult['comparison'] = [
                                'hash'      => $compareHash,
                                'generated' => $showIco,
                                'match'     => $match,
                            ];
                        }
                    }
                }
            }
        }
}

// Preserve form values
$fUser   = htmlspecialchars($_POST['username'] ?? '', ENT_QUOTES, 'UTF-8');
$fEmail  = htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES, 'UTF-8');
$fPubkey = htmlspecialchars($_POST['pubkey'] ?? '', ENT_QUOTES, 'UTF-8');
$fKeyMode = $_POST['key_mode'] ?? 'generate';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>VFACE — Your Identity, Cryptographically Yours</title>
<style>
    :root {
        --bg: #0d1117;
        --surface: #161b22;
        --surface2: #1c2129;
        --border: #30363d;
        --accent: #58a6ff;
        --accent-dim: #1f6feb;
        --text: #c9d1d9;
        --muted: #8b949e;
        --success: #3fb950;
        --error: #f85149;
        --warn: #d29922;
        --mono: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;
        --sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
    }
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
    body {
        font-family: var(--sans);
        background: var(--bg);
        color: var(--text);
        min-height: 100vh;
        padding: 2rem 1rem;
        line-height: 1.6;
    }
    .container { max-width: 740px; margin: 0 auto; }

    /* Hero */
    .hero {
        text-align: center;
        margin-bottom: 2rem;
        padding-bottom: 1.5rem;
        border-bottom: 1px solid var(--border);
    }
    .hero h1 { font-size: 2rem; font-weight: 700; }
    .hero h1 em { color: var(--accent); font-style: normal; }
    .hero .tagline {
        color: var(--muted);
        font-size: 1rem;
        margin-top: 0.5rem;
    }
    .hero .explain {
        color: var(--text);
        font-size: 0.92rem;
        margin-top: 1rem;
        max-width: 560px;
        margin-left: auto;
        margin-right: auto;
        line-height: 1.7;
    }
    .hero .learn-more {
        display: inline-block;
        margin-top: 0.8rem;
        color: var(--accent);
        text-decoration: none;
        font-size: 0.88rem;
    }
    .hero .learn-more:hover { text-decoration: underline; }
    .hero .reset-btn {
        display: inline-block;
        margin-top: 0.8rem;
        background: var(--error);
        color: #fff;
        text-decoration: none;
        font-size: 0.8rem;
        padding: 0.35rem 1rem;
        border-radius: 4px;
        transition: background 0.2s;
    }
    .hero .reset-btn:hover { background: #da3633; text-decoration: none; }

    /* Tabs */
    .tabs { display: flex; gap: 0; margin-bottom: 1.5rem; border-bottom: 2px solid var(--border); }
    .tab-btn {
        background: none; border: none; color: var(--muted);
        padding: 0.75rem 1.5rem; font-size: 0.95rem; font-family: var(--sans);
        cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -2px;
        transition: color 0.2s, border-color 0.2s;
    }
    .tab-btn:hover { color: var(--text); }
    .tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
    .tab-panel { display: none; }
    .tab-panel.active { display: block; }

    /* Card */
    .card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; }

    /* Form */
    .form-group { margin-bottom: 1.25rem; }
    .form-group label { display: block; color: var(--text); font-size: 0.85rem; font-weight: 500; margin-bottom: 0.4rem; }
    .form-group input[type="text"],
    .form-group input[type="email"],
    .form-group textarea {
        width: 100%; background: var(--bg); border: 1px solid var(--border);
        border-radius: 6px; color: var(--text); font-family: var(--mono);
        font-size: 0.9rem; padding: 0.6rem 0.8rem;
    }
    .form-group textarea { min-height: 120px; resize: vertical; }
    .form-group input:focus, .form-group textarea:focus {
        outline: none; border-color: var(--accent);
        box-shadow: 0 0 0 2px rgba(88,166,255,0.15);
    }
    .hint { color: var(--muted); font-size: 0.78rem; margin-top: 0.3rem; }

    /* Key mode selector */
    /* Key mode — radio style selector */
    .key-mode { display: flex; gap: 0.75rem; margin-bottom: 1.25rem; }
    .key-mode-btn {
        display: flex; align-items: center; gap: 0.5rem;
        background: none; border: 1px solid var(--border);
        border-radius: 6px; color: var(--muted);
        padding: 0.6rem 1rem; font-size: 0.85rem; font-family: var(--sans);
        cursor: pointer; transition: border-color 0.2s, color 0.2s; flex: 1;
    }
    .key-mode-btn::before {
        content: ''; width: 14px; height: 14px; border-radius: 50%;
        border: 2px solid var(--border); flex-shrink: 0;
        transition: border-color 0.2s, box-shadow 0.2s;
    }
    .key-mode-btn:hover { color: var(--text); border-color: var(--muted); }
    .key-mode-btn.active {
        color: var(--accent); border-color: var(--accent);
    }
    .key-mode-btn.active::before {
        border-color: var(--accent);
        box-shadow: inset 0 0 0 3px var(--accent);
    }

    .btn {
        display: inline-block; background: var(--accent-dim); color: #fff;
        border: none; border-radius: 6px; padding: 0.65rem 1.5rem;
        font-size: 0.95rem; font-family: var(--sans); cursor: pointer;
        transition: background 0.2s;
    }
    .btn:hover { background: var(--accent); }
    .btn-sm { padding: 0.4rem 1rem; font-size: 0.82rem; }
    .btn-outline {
        background: none; border: 1px solid var(--accent); color: var(--accent);
    }
    .btn-outline:hover { background: var(--accent-dim); color: #fff; }

    .error-box {
        background: rgba(248,81,73,0.1); border: 1px solid var(--error);
        color: var(--error); padding: 0.7rem 1rem; border-radius: 6px;
        margin-bottom: 1.25rem; font-size: 0.9rem;
    }

    /* Results */
    .result-section { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border); }
    .result-section h3 { font-size: 1rem; margin-bottom: 1rem; color: var(--accent); }

    .identicon-display { text-align: center; margin: 1rem 0; }
    .identicon-display img { border: 2px solid var(--border); border-radius: 8px; image-rendering: pixelated; }
    .identicon-display .size-label { color: var(--muted); font-size: 0.78rem; margin-top: 0.4rem; }
    .identicon-pair { display: flex; justify-content: center; gap: 2rem; flex-wrap: wrap; }

    .data-row { display: flex; gap: 0.5rem; margin-bottom: 0.6rem; align-items: baseline; }
    .data-label { color: var(--muted); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.5px; min-width: 110px; flex-shrink: 0; }
    .data-value { color: var(--text); font-family: var(--mono); font-size: 0.85rem; word-break: break-all; }
    .data-value.hash { color: var(--accent); }

    /* Copy blocks */
    .copy-wrap { position: relative; margin-bottom: 1rem; }
    .copy-block {
        background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
        padding: 1rem; padding-right: 4rem; font-family: var(--mono); font-size: 0.8rem;
        color: var(--text); white-space: pre; overflow-x: auto; line-height: 1.6; margin: 0;
    }
    .copy-btn {
        position: absolute; top: 0.5rem; right: 0.5rem;
        background: var(--accent-dim); color: #fff; border: none; border-radius: 4px;
        padding: 0.3rem 0.7rem; font-size: 0.78rem; font-family: var(--sans);
        cursor: pointer; transition: background 0.2s;
    }
    .copy-btn:hover { background: var(--accent); }
    .copy-btn.copied { background: var(--success); }

    /* Keypair warning */
    .key-warning {
        background: rgba(210,153,34,0.1); border: 1px solid var(--warn);
        border-radius: 6px; padding: 1rem; margin-bottom: 1.25rem; font-size: 0.88rem;
        color: var(--warn); line-height: 1.6;
    }
    .key-warning strong { color: #e6b422; }
    .key-notice {
        background: rgba(88,166,255,0.08); border: 1px solid var(--accent);
        border-radius: 6px; padding: 0.75rem 1rem; margin: 0.5rem 0 1rem;
        font-size: 0.82rem; color: var(--accent); line-height: 1.5;
    }

    /* Verify results */
    .verdict { padding: 0.8rem 1rem; border-radius: 6px; margin-bottom: 0.75rem; font-weight: 600; }
    .verdict.pass { background: rgba(63,185,80,0.12); border: 1px solid var(--success); color: var(--success); }
    .verdict.fail { background: rgba(248,81,73,0.12); border: 1px solid var(--error); color: var(--error); }
    .verdict.unknown { background: rgba(139,148,158,0.12); border: 1px solid var(--muted); color: var(--muted); }

    /* Steps */
    .steps { margin: 1.5rem 0; }
    .step { display: flex; gap: 1rem; margin-bottom: 1rem; align-items: flex-start; }
    .step-num {
        min-width: 28px; height: 28px; background: var(--accent-dim); color: #fff;
        border-radius: 50%; display: flex; align-items: center; justify-content: center;
        font-size: 0.8rem; font-weight: 700; flex-shrink: 0;
    }
    .step-text { font-size: 0.9rem; padding-top: 0.2rem; }
    .step-text strong { color: var(--text); }
    .step-text .dim { color: var(--muted); font-size: 0.82rem; }

    footer {
        text-align: center; margin-top: 2rem; padding-top: 1rem;
        border-top: 1px solid var(--border); color: var(--muted); font-size: 0.8rem;
    }
    footer a { color: var(--accent); text-decoration: none; }
    footer a:hover { text-decoration: underline; }

    /* Analyze tab */
    .check-list { list-style: none; margin: 1rem 0; }
    .check-list li { padding: 0.4rem 0; font-size: 0.9rem; }
    .check-list .pass::before { content: '\2713 '; color: var(--success); font-weight: 700; }
    .check-list .fail::before { content: '\2717 '; color: var(--error); font-weight: 700; }
    .color-swatch {
        display: inline-block; width: 16px; height: 16px;
        border-radius: 3px; border: 1px solid var(--border);
        vertical-align: middle; margin-right: 0.4rem;
    }
    .file-input-wrap input[type="file"] {
        width: 100%; background: var(--bg); border: 1px solid var(--border);
        border-radius: 6px; color: var(--text); padding: 0.6rem 0.8rem; font-size: 0.9rem;
    }
    .file-input-wrap input[type="file"]::file-selector-button {
        background: var(--accent-dim); color: #fff; border: none;
        border-radius: 4px; padding: 0.3rem 0.8rem; margin-right: 0.8rem;
        cursor: pointer; font-size: 0.85rem;
    }

    @media (max-width: 500px) {
        body { padding: 1rem 0.5rem; }
        .hero h1 { font-size: 1.5rem; }
        .identicon-pair { flex-direction: column; align-items: center; gap: 1rem; }
        .data-row { flex-direction: column; gap: 0.2rem; }
        .data-label { min-width: unset; }
    }
</style>
</head>
<body>
<div class="container">

<div class="hero">
    <h1><em>VFACE</em></h1>
    <div class="tagline">Your pseudonym, cryptographically yours.</div>
    <div class="explain">
        VFACE gives you a persistent identity that nobody can steal or fake.
        Pick a name, get a unique visual fingerprint tied to your key.
        Anyone can verify it's really you — without knowing who you are.
    </div>
    <a href="<?= BLOG_ARTICLE ?>" class="learn-more" rel="noopener">How does it work? Read the full explanation &rarr;</a>
    <br>
    <a href="?reset=1" class="reset-btn">Reset</a>
</div>

<div class="tabs">
    <button type="button" class="tab-btn <?= $tab === 'create' ? 'active' : '' ?>" data-tab="create">Create Identity</button>
    <button type="button" class="tab-btn <?= $tab === 'analyze' ? 'active' : '' ?>" data-tab="analyze">Analyze</button>
    <button type="button" class="tab-btn <?= $tab === 'verify' ? 'active' : '' ?>" data-tab="verify">Verify Signatures</button>
</div>

<?php if ($error): ?>
<div class="error-box"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>

<!-- ==================== CREATE TAB ==================== -->
<div id="panel-create" class="tab-panel <?= $tab === 'create' ? 'active' : '' ?>">
<div class="card">

    <?php if (!$result): ?>
    <form method="POST" autocomplete="off">
        <input type="hidden" name="tab" value="create">

        <div class="form-group">
            <label for="username">Pick a name</label>
            <input type="text" id="username" name="username" value="<?= $fUser ?>"
                   placeholder="Your pseudonym or real name" maxlength="64" required>
        </div>

        <div class="form-group">
            <label for="email">Email</label>
            <input type="email" id="email" name="email" value="<?= $fEmail ?>"
                   placeholder="Can be a real or fictional address" maxlength="254" required>
            <div class="hint">Use something@example.invalid if you want a fictional address.</div>
        </div>

        <label style="font-size:0.85rem; font-weight:500; margin-bottom:0.5rem; display:block;">How do you want to handle your signing key?</label>
        <div class="key-mode">
            <button type="button" class="key-mode-btn <?= $fKeyMode === 'generate' ? 'active' : '' ?>"
                    data-mode="generate">Generate new key</button>
            <button type="button" class="key-mode-btn <?= $fKeyMode === 'existing' ? 'active' : '' ?>"
                    data-mode="existing">I have a key (YubiKey / existing)</button>
        </div>
        <input type="hidden" name="key_mode" id="key_mode" value="<?= htmlspecialchars($fKeyMode) ?>">

        <div id="key-existing" class="form-group" style="<?= $fKeyMode === 'generate' ? 'display:none' : '' ?>">
            <label for="pubkey">Your public key</label>
            <textarea id="pubkey" name="pubkey" rows="3"
                      style="min-height:70px; font-size:0.82rem;"
                      placeholder="Paste your public key — PEM, SSH, base64, hex, yubicrypt, yubisigner..."><?= $fPubkey ?></textarea>
            <div class="hint">
                Works with any key type or format: Ed25519, RSA, PEM, SSH, hex strings.
                Headers like <code>-----BEGIN PUBLIC KEY-----</code> and SSH prefixes
                are stripped automatically.<br>
                <strong>YubiKey users</strong>: paste your public key — the private key
                stays in the hardware. This is the recommended setup.
                Compatible with <a href="https://github.com/Ch1ffr3punk/yubicrypt" rel="noopener">yubicrypt</a>
                and <a href="https://github.com/Ch1ffr3punk/yubisigner.git" rel="noopener">yubisigner</a>.
            </div>
        </div>

        <div id="key-generate" style="<?= $fKeyMode === 'existing' ? 'display:none' : '' ?>">
            <div class="hint" style="margin-bottom:1.25rem;">
                A fresh Ed25519 key pair will be generated for you.
                You'll download it as a JSON file — <strong>keep it safe</strong>, it's your identity.
            </div>
        </div>

        <button type="submit" class="btn">Create My Identity</button>
    </form>
    <?php endif; ?>

    <?php if ($result): ?>
    <div class="result-section">
        <h3>Your VFACE Identity</h3>

        <div class="identicon-pair">
            <div class="identicon-display">
                <img src="data:image/png;base64,<?= $result['ico256'] ?>"
                     alt="Your identicon" width="200" height="200">
                <div class="size-label">This is your face.</div>
            </div>
        </div>

        <div class="data-row">
            <span class="data-label">Name</span>
            <span class="data-value"><?= htmlspecialchars($result['username']) ?></span>
        </div>
        <div class="data-row">
            <span class="data-label">Email</span>
            <span class="data-value"><?= htmlspecialchars($result['email']) ?></span>
        </div>
        <div class="data-row">
            <span class="data-label">Public Key</span>
            <span class="data-value"><?= htmlspecialchars($result['pubkey']) ?></span>
        </div>

        <?php if ($result['keyCleaned']): ?>
        <div class="key-notice">
            Your key was normalized — headers, prefixes, and formatting were removed.
            The cleaned version above is what defines your identity.
            Always use this exact value when verifying.
        </div>
        <?php endif; ?>
        <div class="data-row">
            <span class="data-label">Identity Hash</span>
            <span class="data-value hash"><?= htmlspecialchars($result['hash']) ?></span>
        </div>

        <?php if ($result['keypair']): ?>
        <div class="key-warning">
            <strong>Save your key pair now.</strong> This is the only time you'll see your private key.
            Download the file below and store it somewhere safe. If you lose it, this identity is gone forever.
            Nobody — including this server — has a copy.
        </div>

        <div class="copy-wrap">
            <pre class="copy-block" id="keypair-json"><?= htmlspecialchars(json_encode([
                'algorithm' => 'Ed25519',
                'publicKey' => $result['keypair']['publicKey'],
                'secretKey' => $result['keypair']['secretKey'],
                'created'   => $result['keypair']['created'],
                'username'  => $result['username'],
                'email'     => $result['email'],
                'identityHash' => $result['hash'],
            ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) ?></pre>
            <button type="button" class="copy-btn" onclick="copyEl('keypair-json', this)">Copy</button>
        </div>
        <?php endif; ?>

        <h3 style="margin-top:1.5rem;">VFACE Headers</h3>
        <div class="hint" style="margin-bottom:0.8rem;">
            Copy these into your Usenet/email client.
            Replace the signature line after signing your message body.
        </div>
        <div class="copy-wrap">
            <pre class="copy-block" id="vface-headers"><?= htmlspecialchars($result['headers']) ?></pre>
            <button type="button" class="copy-btn" onclick="copyEl('vface-headers', this)">Copy</button>
        </div>

        <h3 style="margin-top:1.5rem;">What's next?</h3>
        <div class="steps">
            <div class="step">
                <div class="step-num">1</div>
                <div class="step-text">
                    <strong>Save your key pair</strong> (if generated above).
                    <div class="dim">Store the JSON file on an encrypted drive. Consider a YubiKey for maximum security.</div>
                </div>
            </div>
            <div class="step">
                <div class="step-num">2</div>
                <div class="step-text">
                    <strong>Publish your identity page.</strong>
                    <div class="dim">Host the static HTML page below on your website or .onion service. It's your public identity card.</div>
                </div>
            </div>
            <div class="step">
                <div class="step-num">3</div>
                <div class="step-text">
                    <strong>Timestamp it on Bitcoin.</strong>
                    <div class="dim">Run <code>ots stamp identity-page.html</code> to anchor your identity on the blockchain.
                    This proves when you first claimed this name — nobody can backdate a fake.</div>
                </div>
            </div>
            <div class="step">
                <div class="step-num">4</div>
                <div class="step-text">
                    <strong>Start signing messages.</strong>
                    <div class="dim">Use your private key to sign every message you send. Recipients verify with your public key.</div>
                </div>
            </div>
        </div>

        <h3 style="margin-top:1.5rem;">Download Identity Page</h3>
        <div class="hint" style="margin-bottom:0.8rem;">
            A ready-to-publish static HTML page. Upload it to your web server
            and timestamp it with OpenTimestamps for first-claim proof.
        </div>
        <div class="copy-wrap">
            <pre class="copy-block" id="identity-page" style="max-height:200px; overflow-y:auto; font-size:0.72rem;"><?= htmlspecialchars($result['identityPage']) ?></pre>
            <button type="button" class="copy-btn" onclick="copyEl('identity-page', this)">Copy</button>
        </div>

        <div style="margin-top:1rem; display:flex; gap:0.8rem; flex-wrap:wrap;">
            <a href="<?= BLOG_ARTICLE ?>" class="btn btn-sm btn-outline" rel="noopener">About VFACE &rarr;</a>
            <a href="https://www.virebent.art/blog/identicons.html" class="btn btn-sm btn-outline" rel="noopener">How identicons work &rarr;</a>
        </div>
    </div>
    <?php endif; ?>
</div>
</div>

<!-- ==================== ANALYZE TAB ==================== -->
<div id="panel-analyze" class="tab-panel <?= $tab === 'analyze' ? 'active' : '' ?>">
<div class="card">
    <div class="hint" style="margin-bottom:1.25rem; font-size:0.9rem;">
        Upload an identicon image to check if it's a valid
        <a href="https://github.com/Ch1ffr3punk/identicons" rel="noopener">Ch1ffr3punk</a>
        identicon. Optionally enter identity details to check if the image belongs
        to a specific identity.
    </div>

    <form method="POST" enctype="multipart/form-data">
        <input type="hidden" name="tab" value="analyze">
        <input type="hidden" name="MAX_FILE_SIZE" value="<?= MAX_UPLOAD_SIZE ?>">

        <div class="form-group">
            <label for="identicon_file">Upload Identicon (PNG)</label>
            <div class="file-input-wrap">
                <input type="file" id="identicon_file" name="identicon_file" accept="image/png" required>
            </div>
            <div class="hint">Any square PNG: 48&times;48, 256&times;256, etc. Max <?= MAX_UPLOAD_SIZE / 1024 ?> KB.</div>
        </div>

        <div style="margin:1.25rem 0; border-top:1px solid var(--border); padding-top:1.25rem;">
            <div class="hint" style="margin-bottom:0.8rem;">
                <strong>Optional</strong> — enter identity details to check if this identicon belongs to a specific person.
            </div>

            <div class="form-group">
                <label for="ana_username">Username</label>
                <input type="text" id="ana_username" name="ana_username"
                       value="<?= htmlspecialchars($_POST['ana_username'] ?? '') ?>"
                       placeholder="e.g. Gabx" maxlength="64">
            </div>
            <div class="form-group">
                <label for="ana_email">Email</label>
                <input type="email" id="ana_email" name="ana_email"
                       value="<?= htmlspecialchars($_POST['ana_email'] ?? '') ?>"
                       placeholder="e.g. user@example.invalid" maxlength="254">
            </div>
            <div class="form-group">
                <label for="ana_pubkey">Public key</label>
                <textarea id="ana_pubkey" name="ana_pubkey" rows="2"
                          style="min-height:50px; font-size:0.82rem;"
                          placeholder="Paste public key — any format"><?= htmlspecialchars($_POST['ana_pubkey'] ?? '') ?></textarea>
            </div>
        </div>

        <button type="submit" class="btn">Analyze Image</button>
    </form>

    <?php if ($anaResult): ?>
    <div class="result-section">
        <h3>Analysis</h3>

        <?php
        $score = $anaResult['palette_score'];
        if ($anaResult['valid']) {
            $verdictClass = 'pass';
            $verdictText  = 'Valid Ch1ffr3punk identicon (score: ' . $score . '/100)';
        } elseif ($score >= 50) {
            $verdictClass = 'unknown';
            $verdictText  = 'Partial match — some identicon properties detected (score: ' . $score . '/100)';
        } else {
            $verdictClass = 'fail';
            $verdictText  = 'Not a valid Ch1ffr3punk identicon (score: ' . $score . '/100)';
        }
        ?>

        <div class="verdict <?= $verdictClass ?>"><?= htmlspecialchars($verdictText) ?></div>

        <?php if ($anaResult['dataurl']): ?>
        <div class="identicon-display">
            <img src="<?= $anaResult['dataurl'] ?>" alt="Uploaded image"
                 width="128" height="128" style="image-rendering: pixelated;">
        </div>
        <?php endif; ?>

        <ul class="check-list">
            <li class="<?= $anaResult['symmetric'] ? 'pass' : 'fail' ?>">
                Horizontal symmetry (5&times;5 grid, mirrored columns)
            </li>
            <li class="<?= $anaResult['bg_match'] ? 'pass' : 'fail' ?>">
                Background: <?= $anaResult['background'] ? htmlspecialchars($anaResult['background']) : 'not recognized' ?>
            </li>
            <li class="<?= $anaResult['primary_match'] ? 'pass' : 'fail' ?>">
                Primary color:
                <?php if ($anaResult['primary_color']): ?>
                    <span class="color-swatch" style="background:<?= $anaResult['primary_color']['hex'] ?>"></span>
                    <?= $anaResult['primary_color']['hex'] ?> (palette index <?= $anaResult['primary_color']['index'] ?>)
                <?php else: ?>
                    no match in primary palette
                <?php endif; ?>
            </li>
            <li class="<?= $anaResult['secondary_match'] ? 'pass' : 'fail' ?>">
                Secondary color:
                <?php if ($anaResult['secondary_color']): ?>
                    <span class="color-swatch" style="background:<?= $anaResult['secondary_color']['hex'] ?>"></span>
                    <?= $anaResult['secondary_color']['hex'] ?> (palette index <?= $anaResult['secondary_color']['index'] ?>)
                <?php else: ?>
                    no match in secondary palette
                <?php endif; ?>
            </li>
        </ul>

        <div class="data-row">
            <span class="data-label">Dimensions</span>
            <span class="data-value"><?= htmlspecialchars($anaResult['dimensions']) ?></span>
        </div>
        <div class="data-row">
            <span class="data-label">Cell Size</span>
            <span class="data-value"><?= $anaResult['cell_size'] ?>px</span>
        </div>
        <div class="data-row">
            <span class="data-label">Unique Colors</span>
            <span class="data-value"><?= count($anaResult['colors_found']) ?></span>
        </div>

        <?php if (!empty($anaResult['comparison'])): ?>
        <div style="margin-top:1.5rem; padding-top:1rem; border-top:1px solid var(--border);">
            <h3>Identity Comparison</h3>
            <?php if ($anaResult['comparison']['match']): ?>
                <div class="verdict pass">This identicon matches the identity provided.</div>
            <?php else: ?>
                <div class="verdict fail">This identicon does NOT match the identity provided.</div>
            <?php endif; ?>
            <div class="data-row">
                <span class="data-label">Identity Hash</span>
                <span class="data-value hash"><?= htmlspecialchars($anaResult['comparison']['hash']) ?></span>
            </div>
            <?php if ($anaResult['comparison']['generated']): ?>
            <div class="identicon-pair" style="margin-top:1rem;">
                <div class="identicon-display">
                    <img src="<?= $anaResult['dataurl'] ?>" alt="Uploaded"
                         width="96" height="96" style="image-rendering:pixelated;">
                    <div class="size-label">Uploaded</div>
                </div>
                <div class="identicon-display">
                    <img src="data:image/png;base64,<?= $anaResult['comparison']['generated'] ?>"
                         alt="Expected" width="96" height="96" style="image-rendering:pixelated;">
                    <div class="size-label">Expected</div>
                </div>
            </div>
            <?php endif; ?>
        </div>
        <?php endif; ?>
    </div>
    <?php endif; ?>
</div>
</div>

<!-- ==================== VERIFY SIGNATURES TAB ==================== -->
<div id="panel-verify" class="tab-panel <?= $tab === 'verify' ? 'active' : '' ?>">
<div class="card">
    <div class="hint" style="margin-bottom:1.25rem; font-size:0.9rem;">
        <strong>Advanced</strong> — verify an Ed25519 signed message.
        Paste the details from the message headers to check if the signature is genuine.
    </div>

    <form method="POST" autocomplete="off">
        <input type="hidden" name="tab" value="verify">

        <div class="form-group">
            <label for="v_pubkey">Public key (from Ed25519-Pub header)</label>
            <textarea id="v_pubkey" name="v_pubkey" rows="2"
                      style="min-height:50px; font-size:0.82rem;"
                      placeholder="Paste the public key — any format"><?= htmlspecialchars($_POST['v_pubkey'] ?? '') ?></textarea>
        </div>

        <div class="form-group">
            <label for="v_signature">Signature (from Ed25519-Sig header)</label>
            <input type="text" id="v_signature" name="v_signature"
                   value="<?= htmlspecialchars($_POST['v_signature'] ?? '') ?>"
                   placeholder="base64 signature" maxlength="1024" required>
        </div>

        <div class="form-group">
            <label for="v_body">Message body (the signed text)</label>
            <textarea id="v_body" name="v_body" placeholder="Paste the exact message body that was signed"><?= htmlspecialchars($_POST['v_body'] ?? '') ?></textarea>
        </div>

        <div style="margin:1.25rem 0; border-top:1px solid var(--border); padding-top:1.25rem;">
            <div class="hint" style="margin-bottom:0.8rem;">
                <strong>Optional</strong> — add identity details for full VFACE verification.
            </div>

            <div class="form-group">
                <label for="v_username">Username (from From: header)</label>
                <input type="text" id="v_username" name="v_username"
                       value="<?= htmlspecialchars($_POST['v_username'] ?? '') ?>"
                       placeholder="Optional" maxlength="64">
            </div>

            <div class="form-group">
                <label for="v_email">Email (from From: header)</label>
                <input type="email" id="v_email" name="v_email"
                       value="<?= htmlspecialchars($_POST['v_email'] ?? '') ?>"
                       placeholder="Optional" maxlength="254">
            </div>

            <div class="form-group">
                <label for="v_face">Face header (base64 identicon)</label>
                <textarea id="v_face" name="v_face" style="min-height:60px; font-size:0.8rem;"
                          placeholder="Optional — paste the Face: header content"><?= htmlspecialchars($_POST['v_face'] ?? '') ?></textarea>
            </div>
        </div>

        <button type="submit" class="btn">Verify Signature</button>
    </form>

    <?php if ($verifyRes): ?>
    <div class="result-section">
        <h3>Verification Result</h3>

        <?php if ($verifyRes['sig_valid']): ?>
            <div class="verdict pass">Signature is valid — this message is authentic.</div>
        <?php else: ?>
            <div class="verdict fail">Signature is INVALID — this message may be forged.</div>
        <?php endif; ?>

        <?php if ($verifyRes['hash_expected']): ?>
            <div class="data-row">
                <span class="data-label">Identity Hash</span>
                <span class="data-value hash"><?= htmlspecialchars($verifyRes['hash_expected']) ?></span>
            </div>
        <?php endif; ?>

        <?php if ($verifyRes['ico_match'] !== null): ?>
            <?php if ($verifyRes['ico_match']): ?>
                <div class="verdict pass">Face header matches the expected identicon.</div>
            <?php else: ?>
                <div class="verdict fail">Face header does NOT match — possible impersonation.</div>
            <?php endif; ?>
        <?php endif; ?>

        <?php if ($verifyRes['ico_generated']): ?>
            <div class="identicon-display">
                <img src="data:image/png;base64,<?= $verifyRes['ico_generated'] ?>"
                     alt="Expected identicon" width="96" height="96" style="image-rendering:pixelated;">
                <div class="size-label">Expected identicon for this identity</div>
            </div>
        <?php endif; ?>
    </div>
    <?php endif; ?>
</div>
</div>

<footer>
    VFACE uses <a href="https://github.com/Ch1ffr3punk/identicons" rel="noopener">Ch1ffr3punk's identicons</a>
    &mdash; Compatible with <a href="https://github.com/Ch1ffr3punk/yubicrypt" rel="noopener">yubicrypt</a>
    / <a href="https://github.com/Ch1ffr3punk/yubisigner.git" rel="noopener">yubisigner</a>
    &mdash; Powered by <a href="https://doc.libsodium.org/" rel="noopener">libsodium</a>
    &mdash; Built by <a href="https://virebent.art" rel="noopener">virebent.art</a>
</footer>

</div>

<script>
(function() {
    'use strict';

    // Tab switching
    document.querySelectorAll('.tab-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
            var target = this.getAttribute('data-tab');
            document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
            this.classList.add('active');
            document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });
            var panel = document.getElementById('panel-' + target);
            if (panel) panel.classList.add('active');
        });
    });

    // Key mode toggle
    document.querySelectorAll('.key-mode-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
            var mode = this.getAttribute('data-mode');
            document.querySelectorAll('.key-mode-btn').forEach(function(b) { b.classList.remove('active'); });
            this.classList.add('active');
            document.getElementById('key_mode').value = mode;
            document.getElementById('key-existing').style.display = (mode === 'existing') ? '' : 'none';
            document.getElementById('key-generate').style.display = (mode === 'generate') ? '' : 'none';
        });
    });

    // Clipboard
    window.copyEl = function(id, btn) {
        var el = document.getElementById(id);
        if (!el) return;
        var text = el.textContent || el.innerText;
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(text).then(function() {
                btn.textContent = 'Copied';
                btn.classList.add('copied');
                setTimeout(function() { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000);
            });
        }
    };
})();
</script>
</body>
</html>
