<?php
/**
 * Colex AI Interface - TraderAlign
 * 
 * AI-powered trading analytics assistant
 * All data dynamically generated from real trading data
 */

// Ensure this file is accessed through the router
if (!isset($auth) || !isset($db)) {
    http_response_code(500);
    die('Direct access not permitted.');
}

// Ensure user is authenticated
if (!$auth->isLoggedIn()) {
    http_response_code(302);
    header('Location: /traderalign/sign-in');
    exit;
}

$userId = $auth->getUserId();

// Same authoritative user_profiles row every other page reads for identity
// and local time (display_name, timezone — both set via Settings/onboarding)
// — Colex must never fall back to a page-specific default while a real
// value exists. Name resolution goes through the one shared
// resolveDisplayName() (see RenderHelpers.php), not a second copy of the
// same fallback check.
$colexDisplayName = 'Trader';
$colexTimezone = null;
try {
    $colexProfileRow = $db->selectById('user_profiles', $userId, 'user_id');
    $colexDisplayName = resolveDisplayName($colexProfileRow);
    if ($colexProfileRow && !empty($colexProfileRow['timezone'])) {
        $colexTimezone = $colexProfileRow['timezone'];
    }
} catch (\Throwable $e) {
    error_log("colex: failed to read display_name/timezone: " . $e->getMessage());
}

require_once __DIR__ . '/../api/colex/usage-engine.php';
$allowanceStatus = getColexAllowanceStatus($db, $userId);
$allowanceForFrontend = formatAllowanceForFrontend($allowanceStatus);

$progressStagesPath = __DIR__ . '/../api/colex/progress-stages.json';
$progressStages = file_exists($progressStagesPath)
    ? (json_decode(file_get_contents($progressStagesPath), true) ?: [])
    : [];

// Optional page/module context: other TraderAlign pages can link here with
// e.g. /colex?context=journal so the progress experience starts in the
// right workflow before any message-level signal (attachment, suggested
// prompt) exists. Validated against known task types; anything else (or
// absent) is simply ignored, no error.
$pageContextParam = isset($_GET['context']) ? strtolower(trim($_GET['context'])) : null;
$colexPageContext = ($pageContextParam && isset($progressStages[$pageContextParam])) ? $pageContextParam : null;

// Get period from query params or default to current week
$period = isset($_GET['period']) ? $_GET['period'] : 'current_week';
$customDateFrom = isset($_GET['date_from']) ? $_GET['date_from'] : null;
$customDateTo = isset($_GET['date_to']) ? $_GET['date_to'] : null;

require_once __DIR__ . '/../src/ColexHelpers.php';

$dateRange = calculateDateRange($period, $customDateFrom, $customDateTo);
$periodStats = getPeriodStats($db, $userId, $dateRange['from'], $dateRange['to']);
$greeting = generateGreeting($periodStats, $period, $dateRange, $colexDisplayName, $colexTimezone);
$contextCards = generateContextCards($periodStats);
$suggestedPrompts = generateSuggestedPrompts($periodStats, $period, $dateRange);


// Fetch conversations, grouped into a Pinned section plus date-based sections.
// Requires the colex_conversations table to have an `is_pinned TINYINT(1) DEFAULT 0` column.
function getConversations($db, $userId) {
    $conversations = [];
    
    try {
        $rawConversations = $db->raw("
            SELECT id, title, created_at, is_pinned
            FROM colex_conversations 
            WHERE user_id = ? 
            ORDER BY is_pinned DESC, created_at DESC 
            LIMIT 100
        ", [$userId]);
        
        $now = new DateTime();
        $today = $now->format('Y-m-d');
        $yesterday = (clone $now)->modify('-1 day')->format('Y-m-d');
        $sevenDaysAgo = (clone $now)->modify('-7 days')->format('Y-m-d');
        $thirtyDaysAgo = (clone $now)->modify('-30 days')->format('Y-m-d');
        
        foreach ($rawConversations as $conv) {
            $conv['is_pinned'] = !empty($conv['is_pinned']);
            
            if ($conv['is_pinned']) {
                $section = 'Pinned';
            } else {
                $convDate = (new DateTime($conv['created_at']))->format('Y-m-d');
                
                if ($convDate === $today) {
                    $section = 'Today';
                } elseif ($convDate === $yesterday) {
                    $section = 'Yesterday';
                } elseif ($convDate >= $sevenDaysAgo) {
                    $section = 'Previous 7 Days';
                } elseif ($convDate >= $thirtyDaysAgo) {
                    $section = 'Previous 30 Days';
                } else {
                    $section = 'Older';
                }
            }
            
            $conv['section'] = $section;
            $conversations[] = $conv;
        }
        
    } catch (Exception $e) {
        error_log("Error fetching conversations: " . $e->getMessage());
        $conversations = [];
    }
    
    return $conversations;
}

$conversations = getConversations($db, $userId);

/**
 * The app's global icon nav (public/sidebar.php) previously never appeared
 * on Colex — this page has always been a fully self-contained shell with
 * its own conversation-list "sidebar" (a completely different thing: a
 * collapsible panel, class="sidebar" id="sidebar", styled from this file's
 * own :root variables, not tokens.css). Reusing sidebar.php's markup
 * directly would collide with that existing `.sidebar` class and break
 * both. Instead: require sidebar.php exactly like every other page does
 * (so it can never drift out of sync with Dashboard/Journal/Reports/etc.),
 * capture its output, and retarget only the outer <nav> wrapper to a new,
 * uniquely-named class (`.app-global-nav`, styled below to match the same
 * fixed 4rem icon-strip look every other page already uses, reusing
 * Colex's own color variables so it fits this page's theme). The trailing
 * strategy_id-carrying <script> sidebar.php also emits is deliberately
 * NOT included — Colex has no strategy-scoped links to carry a
 * strategy_id onto, and that script's own `.sidebar a` selector would
 * otherwise risk matching Colex's unrelated conversation-list elements.
 */
ob_start();
require __DIR__ . '/sidebar.php';
$globalNavHtml = '';
if (preg_match('/<nav class="sidebar">.*?<\/nav>/s', ob_get_clean(), $navMatch)) {
    $globalNavHtml = str_replace('<nav class="sidebar">', '<nav class="app-global-nav">', $navMatch[0]);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Colex AI - TraderAlign</title>
    <?php
    // 2026-08-19 security fix (CSRF): this page's own regex-based nav
    // capture (see the big comment above) only extracts the <nav>...</nav>
    // element from sidebar.php's output — the CSRF meta tag + fetch()
    // wrapper script sidebar.php now also renders (right before its own
    // <nav>) would otherwise be silently dropped here, which would have
    // broken every one of Colex's own real fetch() calls (chat, delete/
    // pin-conversation, upload-screenshot) the moment centralized CSRF
    // enforcement turned on. Rendered directly, independent of the capture.
    ?>
    <?= Csrf::metaTag() ?>
    <script src="/traderalign/assets/js/csrf.js"></script>
    <link rel="icon" type="image/png" sizes="32x32" href="/traderalign/assets/favicon/favicon-32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="/traderalign/assets/favicon/favicon-16.png">
    <link rel="apple-touch-icon" sizes="180x180" href="/traderalign/assets/favicon/apple-touch-icon.png">
    <link rel="shortcut icon" href="/traderalign/assets/favicon/favicon.ico">
    <link rel="stylesheet" href="/traderalign/assets/css/brand.css">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
    <style>
        :root {
            --bg-root: #0d0d0d;
            --bg-sidebar: #121212;
            --bg-main: #0d0d0d;
            --bg-elevated: #1a1a1a;
            --bg-hover: #1f1f1f;
            --bg-active: #262626;
            --bg-input: #1a1a1a;
            
            --border-thin: #262626;
            --border-default: #333333;
            --border-hover: #404040;
            --border-focus: #525252;
            
            --text-primary: #ececec;
            --text-secondary: #9d9d9d;
            --text-tertiary: #6b6b6b;
            --text-quaternary: #4a4a4a;
            
            --green: #4ade80;
            --red: #f87171;
            --yellow: #facc15;
            
            --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
            --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
            
            --text-2xs: 0.6875rem;
            --text-xs: 0.75rem;
            --text-sm: 0.8125rem;
            --text-base: 0.875rem;
            --text-lg: 1rem;
            --text-xl: 1.125rem;
            --text-2xl: 1.5rem;
            
            --font-normal: 400;
            --font-medium: 500;
            --font-semibold: 600;
            
            --sp-1: 0.25rem;
            --sp-2: 0.5rem;
            --sp-3: 0.75rem;
            --sp-4: 1rem;
            --sp-5: 1.25rem;
            --sp-6: 1.5rem;
            --sp-8: 2rem;
            
            --radius-sm: 0.375rem;
            --radius-md: 0.5rem;
            --radius-lg: 0.625rem;
            --radius-xl: 0.75rem;
            --radius-2xl: 1rem;
            --radius-full: 9999px;
            
            --transition-fast: 120ms cubic-bezier(0.4, 0, 0.2, 1);
            --transition-base: 180ms cubic-bezier(0.4, 0, 0.2, 1);
            --transition-sidebar: 280ms cubic-bezier(0.4, 0, 0.2, 1);
            
            --sidebar-expanded: 260px;
            --sidebar-collapsed: 68px;
        }

        *, *::before, *::after {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: var(--font-sans);
            background-color: var(--bg-root);
            color: var(--text-primary);
            height: 100vh;
            display: flex;
            overflow: hidden;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            font-size: var(--text-sm);
            line-height: 1.5;
            letter-spacing: -0.01em;
            margin-left: 4rem; /* reserves space for the fixed .app-global-nav strip below */
        }

        /* App-wide icon nav (Dashboard/Checklist/Drafts/Journal/Reports/
           Calculator/Strategy/Colex AI/Sign Out) — same fixed 4rem strip
           every other page shows. Deliberately NOT named .sidebar — that
           class already means Colex's own conversation-list panel below.
           See the PHP block above <!DOCTYPE html> for how this markup is
           sourced from public/sidebar.php.

           Colors are hardcoded to the EXACT values assets/tokens.css
           defines for --sidebar/--sidebar-border/--sidebar-primary/
           --foreground/--foreground-dim/--background-surface/--popover
           (the same variables every other page's own .sidebar CSS reads),
           not Colex's own --bg-sidebar/--text-tertiary/--green theme
           variables. Colex never links tokens.css (real risk of an
           unrelated global-selector collision across this page's own
           carefully-tuned theme — not attempted), so those two color sets
           genuinely differ (#181C28 vs. this page's own #121212) and using
           Colex's own vars made the nav visibly change color depending on
           which page you were on. Hardcoding here keeps the nav's
           background/border/text pixel-identical to every other page,
           regardless of which page's own theme happens to be active — if
           tokens.css's sidebar colors are ever changed, update both places. */
        .app-global-nav {
            width: 4rem;
            background-color: #181C28;
            border-right: 1px solid #2A2E39;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding-top: 1rem;
            gap: 0.25rem;
            position: fixed;
            top: 0; left: 0; bottom: 0;
            z-index: 40;
        }
        .app-global-nav a {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 2.75rem;
            height: 2.75rem;
            border-radius: 0.375rem;
            color: #787B86;
            text-decoration: none;
            position: relative;
            transition: background .15s ease, color .15s ease;
        }
        .app-global-nav a:hover {
            background-color: #1A1A1C;
            color: #D1D4DC;
        }
        .app-global-nav a.active {
            background-color: #1A1A1C;
            color: #60A5FA;
        }
        .app-global-nav a .tt {
            position: absolute;
            left: 3.5rem;
            background-color: #2A2E39;
            color: #D1D4DC;
            font-size: 0.75rem;
            padding: 0.25rem 0.625rem;
            border-radius: 0.25rem;
            white-space: nowrap;
            pointer-events: none;
            opacity: 0;
            transition: opacity .15s ease;
            z-index: 41;
        }
        .app-global-nav a:hover .tt {
            opacity: 1;
        }

        .sidebar {
            width: var(--sidebar-expanded);
            min-width: var(--sidebar-expanded);
            background-color: var(--bg-sidebar);
            display: flex;
            flex-direction: column;
            height: 100vh;
            user-select: none;
            border-right: 1px solid var(--border-thin);
            transition: width var(--transition-sidebar), min-width var(--transition-sidebar);
            overflow: hidden;
            position: relative;
            z-index: 10;
        }

        .sidebar.collapsed {
            width: var(--sidebar-collapsed);
            min-width: var(--sidebar-collapsed);
        }

        .sidebar-header {
            padding: var(--sp-3);
            flex-shrink: 0;
            display: flex;
            flex-direction: column;
            gap: var(--sp-2);
            transition: padding var(--transition-sidebar);
        }

        .sidebar.collapsed .sidebar-header {
            padding: var(--sp-3) var(--sp-2);
        }

        .sidebar-header-top {
            display: flex;
            align-items: center;
            justify-content: space-between;
            min-height: 2.25rem;
        }

        /* Collapsed: the action buttons (search/collapse) are hidden, leaving
           the logo area as the row's only child — center it instead of
           letting `space-between` push it flush left, so it lines up with
           the centered icon column (new chat/search/pinned) beneath it. */
        .sidebar.collapsed .sidebar-header-top {
            justify-content: center;
        }

        .sidebar-logo-area {
            display: flex;
            align-items: center;
            justify-content: flex-start;
            cursor: pointer;
            border-radius: var(--radius-md);
            padding: 0.25rem var(--sp-2);
            margin-left: -0.25rem;
            transition: all var(--transition-base);
            position: relative;
            text-decoration: none;
            min-height: 2.25rem;
        }

        .sidebar-logo-area:hover {
            background-color: var(--bg-hover);
        }

        .sidebar-logo-area:active {
            background-color: var(--bg-active);
        }

        .logo-text {
            font-size: var(--text-lg);
            font-weight: var(--font-semibold);
            color: var(--text-primary);
            letter-spacing: -0.02em;
            white-space: nowrap;
            transition: opacity var(--transition-base), max-width var(--transition-base);
            opacity: 1;
            max-width: 200px;
            overflow: hidden;
        }

        .logo-text .logo-dot {
            color: var(--text-tertiary);
            font-weight: var(--font-normal);
        }

        .sidebar:not(.collapsed) .sidebar-logo-area:hover .logo-text {
            opacity: 1;
        }

        /* Collapsed: the logo area becomes a single icon button, sized and
           centered exactly like the collapsed-nav buttons (new chat/search/
           pinned) directly beneath it, so the expand icon lands in the same
           column instead of floating off to the side. */
        .sidebar.collapsed .sidebar-logo-area {
            width: 2.5rem;
            height: 2.5rem;
            padding: 0;
            margin-left: 0;
            justify-content: center;
        }

        /* Expand icon: hidden while expanded, rendered in normal flow
           (centered by .sidebar-logo-area's own flexbox, same as a
           collapsed-nav-btn's own SVG) when collapsed — no absolute
           positioning, so it can never visually drift from its real
           clickable box. */
        .sidebar-open-icon {
            display: none;
            align-items: center;
            justify-content: center;
            width: 1.25rem;
            height: 1.25rem;
            color: var(--text-tertiary);
            transition: color var(--transition-fast);
        }

        .sidebar.collapsed .sidebar-open-icon {
            display: flex;
        }

        .sidebar-logo-area:hover .sidebar-open-icon {
            color: var(--text-primary);
        }

        .sidebar-open-icon svg {
            width: 1.25rem;
            height: 1.25rem;
            stroke-width: 2;
        }

        .sidebar-actions {
            display: flex;
            align-items: center;
            gap: var(--sp-1);
            flex-shrink: 0;
        }

        .sidebar.collapsed .sidebar-actions {
            display: none;
        }

        .sidebar-icon-btn {
            width: 2.25rem;
            height: 2.25rem;
            border-radius: var(--radius-md);
            background: none;
            border: none;
            color: var(--text-tertiary);
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all var(--transition-fast);
            flex-shrink: 0;
            padding: 0;
        }

        .sidebar-icon-btn:hover {
            background-color: var(--bg-hover);
            color: var(--text-primary);
        }

        .sidebar-icon-btn:active {
            background-color: var(--bg-active);
            transform: scale(0.95);
        }

        .sidebar-icon-btn svg {
            width: 1.25rem;
            height: 1.25rem;
            stroke-width: 2;
        }

        .new-chat-btn {
            display: flex;
            align-items: center;
            gap: var(--sp-2);
            width: 100%;
            padding: 0.5625rem var(--sp-3);
            background-color: transparent;
            color: var(--text-secondary);
            border: 1px solid var(--border-default);
            border-radius: var(--radius-md);
            font-size: var(--text-sm);
            font-weight: var(--font-normal);
            cursor: pointer;
            transition: all var(--transition-fast);
            font-family: var(--font-sans);
            letter-spacing: -0.01em;
            white-space: nowrap;
            overflow: hidden;
        }

        .new-chat-btn:hover {
            background-color: var(--bg-hover);
            border-color: var(--border-hover);
            color: var(--text-primary);
        }

        .new-chat-btn:active {
            background-color: var(--bg-active);
            transform: scale(0.995);
        }

        .new-chat-btn svg {
            width: 1rem;
            height: 1rem;
            flex-shrink: 0;
            opacity: 0.7;
            stroke-width: 2;
        }

        .new-chat-btn:hover svg {
            opacity: 1;
        }

        .new-chat-btn .btn-text {
            transition: opacity var(--transition-base), max-width var(--transition-base);
            opacity: 1;
            max-width: 200px;
        }

        /* Collapsed sidebar: hide the header's New Chat button entirely.
           The collapsed-nav block below already renders a single New Chat icon,
           so keeping this one too produced two "+" icons stacked on top of
           each other. Only the collapsed-nav icon should remain. */
        .sidebar.collapsed .sidebar-header .new-chat-btn {
            display: none;
        }

        .conversations-container {
            flex: 1;
            overflow-y: auto;
            overflow-x: hidden;
            padding: 0 var(--sp-2) var(--sp-2);
            transition: opacity var(--transition-base);
        }

        .sidebar.collapsed .conversations-container {
            opacity: 0;
            pointer-events: none;
            position: absolute;
        }

        .conversations-container::-webkit-scrollbar {
            width: 4px;
        }

        .conversations-container::-webkit-scrollbar-track {
            background: transparent;
        }

        .conversations-container::-webkit-scrollbar-thumb {
            background: var(--border-default);
            border-radius: 2px;
        }

        .conversations-container::-webkit-scrollbar-thumb:hover {
            background: var(--border-hover);
        }

        /* Sidebar search */
        .sidebar-search-wrap {
            position: relative;
            display: flex;
            align-items: center;
        }

        .sidebar-search-wrap svg {
            position: absolute;
            left: 0.625rem;
            width: 0.9rem;
            height: 0.9rem;
            color: var(--text-quaternary);
            stroke-width: 2;
            pointer-events: none;
        }

        .sidebar-search-input {
            width: 100%;
            padding: 0.5rem 0.625rem 0.5rem 1.875rem;
            background-color: var(--bg-input);
            color: var(--text-primary);
            border: 1px solid var(--border-default);
            border-radius: var(--radius-md);
            font-size: var(--text-xs);
            font-family: var(--font-sans);
            outline: none;
            transition: border-color var(--transition-fast);
        }

        .sidebar-search-input::placeholder {
            color: var(--text-tertiary);
        }

        .sidebar-search-input:focus {
            border-color: var(--border-focus);
        }

        .sidebar.collapsed .sidebar-search-wrap {
            display: none;
        }

        .conversation-item.search-hidden {
            display: none !important;
        }

        .conversation-section.search-hidden {
            display: none !important;
        }

        /* Collapsed sidebar navigation */
        .collapsed-nav {
            display: none;
            flex-direction: column;
            align-items: center;
            gap: var(--sp-1);
            padding: var(--sp-2) 0;
            flex: 1;
        }

        .sidebar.collapsed .collapsed-nav {
            display: flex;
        }

        .collapsed-nav-btn {
            width: 2.5rem;
            height: 2.5rem;
            border-radius: var(--radius-md);
            background: none;
            border: none;
            color: var(--text-tertiary);
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all var(--transition-fast);
            padding: 0;
            position: relative;
        }

        .collapsed-nav-btn:hover {
            background-color: var(--bg-hover);
            color: var(--text-primary);
        }

        .collapsed-nav-btn:active {
            background-color: var(--bg-active);
            transform: scale(0.94);
        }

        .collapsed-nav-btn svg {
            width: 1.25rem;
            height: 1.25rem;
            stroke-width: 2;
        }

        .conversation-section {
            margin-top: var(--sp-4);
        }

        .conversation-section:first-child {
            margin-top: var(--sp-1);
        }

        .section-label {
            font-size: var(--text-2xs);
            font-weight: var(--font-medium);
            color: var(--text-quaternary);
            padding: var(--sp-1) var(--sp-3);
            margin-bottom: 0;
            letter-spacing: 0.03em;
            text-transform: none;
            display: flex;
            align-items: center;
            gap: 0.25rem;
        }

        .section-label.pinned-label {
            color: var(--yellow);
        }

        .section-label.pinned-label svg {
            width: 0.75rem;
            height: 0.75rem;
            stroke-width: 2;
        }

        .conversation-item {
            position: relative;
            padding: 0.5rem var(--sp-3);
            margin: 0;
            border-radius: var(--radius-md);
            cursor: pointer;
            transition: background-color var(--transition-fast);
            display: flex;
            align-items: center;
            min-height: 2.125rem;
        }

        .conversation-item:hover {
            background-color: var(--bg-hover);
        }

        .conversation-item:active {
            background-color: var(--bg-active);
        }

        .conversation-item.active {
            background-color: var(--bg-active);
        }

        .conversation-item.active .conv-title {
            color: var(--text-primary);
        }

        .conversation-item .pin-indicator {
            width: 0.75rem;
            height: 0.75rem;
            color: var(--yellow);
            flex-shrink: 0;
            margin-right: var(--sp-2);
            stroke-width: 2;
        }

        .conversation-item .conv-title {
            font-size: var(--text-sm);
            color: var(--text-secondary);
            font-weight: var(--font-normal);
            line-height: 1.35;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            flex: 1;
            min-width: 0;
            letter-spacing: -0.01em;
            transition: color var(--transition-fast);
            padding-right: var(--sp-6);
        }

        .conversation-item:hover .conv-title {
            color: var(--text-primary);
        }

        .conversation-item .delete-conv-btn {
            position: absolute;
            right: var(--sp-2);
            width: 1.625rem;
            height: 1.625rem;
            border-radius: var(--radius-sm);
            background: none;
            border: none;
            color: var(--text-quaternary);
            cursor: pointer;
            display: none;
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
            transition: all var(--transition-fast);
            padding: 0;
            z-index: 1;
        }

        .conversation-item:hover .delete-conv-btn {
            display: flex;
        }

        .conversation-item .delete-conv-btn:hover {
            color: var(--red);
            background-color: rgba(248, 113, 113, 0.08);
        }

        .conversation-item .delete-conv-btn svg {
            width: 0.875rem;
            height: 0.875rem;
            stroke-width: 2;
        }

        .sidebar-empty-state {
            padding: var(--sp-8) var(--sp-4);
            text-align: center;
            color: var(--text-quaternary);
            font-size: var(--text-xs);
            line-height: 1.6;
        }

        /* =============================================
           CONTEXT MENU (right-click / long-press to pin)
           ============================================= */
        .context-menu {
            position: fixed;
            min-width: 160px;
            background-color: var(--bg-elevated);
            border: 1px solid var(--border-default);
            border-radius: var(--radius-md);
            padding: var(--sp-1);
            box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
            z-index: 1200;
            display: none;
        }

        .context-menu.active {
            display: block;
        }

        .context-menu-item {
            display: flex;
            align-items: center;
            gap: var(--sp-2);
            width: 100%;
            padding: 0.5rem var(--sp-3);
            background: none;
            border: none;
            border-radius: var(--radius-sm);
            color: var(--text-primary);
            font-size: var(--text-sm);
            font-family: var(--font-sans);
            cursor: pointer;
            text-align: left;
        }

        .context-menu-item:hover {
            background-color: var(--bg-hover);
        }

        .context-menu-item svg {
            width: 0.9375rem;
            height: 0.9375rem;
            stroke-width: 2;
            flex-shrink: 0;
        }

        /* =============================================
           CONFIRMATION MODAL
           ============================================= */
        .modal-overlay {
            position: fixed;
            inset: 0;
            background-color: rgba(0, 0, 0, 0.6);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 1000;
            opacity: 0;
            pointer-events: none;
            transition: opacity var(--transition-base);
        }

        .modal-overlay.active {
            opacity: 1;
            pointer-events: all;
        }

        .modal-dialog {
            background-color: var(--bg-elevated);
            border: 1px solid var(--border-default);
            border-radius: var(--radius-xl);
            padding: var(--sp-6);
            max-width: 400px;
            width: 90%;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
        }

        .modal-title {
            font-size: var(--text-lg);
            font-weight: var(--font-semibold);
            color: var(--text-primary);
            margin-bottom: var(--sp-3);
        }

        .modal-message {
            font-size: var(--text-sm);
            color: var(--text-secondary);
            line-height: 1.6;
            margin-bottom: var(--sp-6);
        }

        .modal-actions {
            display: flex;
            justify-content: flex-end;
            gap: var(--sp-3);
        }

        .modal-btn {
            padding: 0.5rem 1rem;
            border-radius: var(--radius-md);
            font-size: var(--text-sm);
            font-weight: var(--font-medium);
            cursor: pointer;
            transition: all var(--transition-fast);
            font-family: var(--font-sans);
            border: none;
        }

        .modal-btn-cancel {
            background-color: var(--bg-hover);
            color: var(--text-primary);
        }

        .modal-btn-cancel:hover {
            background-color: var(--bg-active);
        }

        .modal-btn-delete {
            background-color: var(--red);
            color: #fff;
        }

        .modal-btn-delete:hover {
            background-color: #ef4444;
        }

        .modal-btn-delete:active {
            background-color: #dc2626;
        }

        /* =============================================
           NOTIFICATION TOAST
           ============================================= */
        .toast {
            position: fixed;
            bottom: var(--sp-6);
            left: 50%;
            transform: translateX(-50%) translateY(100%);
            background-color: var(--bg-elevated);
            border: 1px solid var(--border-default);
            border-radius: var(--radius-md);
            padding: var(--sp-3) var(--sp-4);
            font-size: var(--text-sm);
            color: var(--text-primary);
            z-index: 1100;
            opacity: 0;
            transition: all var(--transition-base);
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
            pointer-events: none;
        }

        .toast.visible {
            opacity: 1;
            transform: translateX(-50%) translateY(0);
            pointer-events: all;
        }

        .toast.success {
            border-color: var(--green);
        }

        .toast.error {
            border-color: var(--red);
        }

        .main-chat {
            flex: 1;
            display: flex;
            flex-direction: column;
            height: 100vh;
            background-color: var(--bg-main);
            min-width: 0;
        }

        .chat-header {
            padding: var(--sp-3) var(--sp-6);
            border-bottom: 1px solid var(--border-thin);
            background-color: var(--bg-sidebar);
            flex-shrink: 0;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: var(--sp-3);
            flex-wrap: wrap;
        }

        .context-selector {
            display: flex;
            align-items: center;
            gap: var(--sp-3);
            flex-wrap: wrap;
        }

        /* Colex AI allowance badge — "Colex AI · XX% AI credits remaining".
           Deliberately shows only a percentage and a color, never a unit
           count, token count, or anything provider-specific. */
        .allowance-badge {
            display: flex;
            align-items: center;
            gap: 0.4rem;
            padding: 0.4375rem 0.75rem;
            background-color: var(--bg-elevated);
            border: 1px solid var(--border-default);
            border-radius: var(--radius-md);
            font-size: var(--text-xs);
            color: var(--text-secondary);
            white-space: nowrap;
        }

        .allowance-badge .allowance-dot {
            width: 0.5rem;
            height: 0.5rem;
            border-radius: var(--radius-full);
            flex-shrink: 0;
        }

        .allowance-badge.allowance-green .allowance-dot { background-color: var(--green); }
        .allowance-badge.allowance-yellow .allowance-dot { background-color: #facc15; }
        .allowance-badge.allowance-red .allowance-dot { background-color: var(--red); }

        .allowance-badge.allowance-red .allowance-percent { color: var(--red); font-weight: var(--font-medium); }

        /* Exhausted-state banner shown above the input bar. Conversation
           history above it stays fully visible/scrollable — only sending
           new messages is blocked. */
        .allowance-exhausted-banner {
            display: flex;
            flex-direction: column;
            gap: var(--sp-2);
            padding: var(--sp-4);
            margin: 0 var(--sp-6) var(--sp-3);
            background-color: var(--bg-elevated);
            border: 1px solid var(--red);
            border-radius: var(--radius-lg);
        }

        .allowance-exhausted-banner .banner-title {
            font-size: var(--text-sm);
            font-weight: var(--font-semibold);
            color: var(--text-primary);
        }

        .allowance-exhausted-banner .banner-message {
            font-size: var(--text-xs);
            color: var(--text-secondary);
            line-height: 1.5;
        }

        .allowance-exhausted-banner .banner-actions {
            display: flex;
            gap: var(--sp-2);
            margin-top: var(--sp-1);
        }

        .allowance-exhausted-banner .banner-btn {
            padding: 0.5rem 1rem;
            border-radius: var(--radius-md);
            font-size: var(--text-xs);
            font-weight: var(--font-medium);
            cursor: pointer;
            border: none;
            font-family: var(--font-sans);
        }

        .allowance-exhausted-banner .banner-btn-primary {
            background-color: #ffffff;
            color: #0d0d0d;
        }

        .allowance-exhausted-banner .banner-btn-primary:hover {
            background-color: #e5e5e5;
        }

        .input-bar-container.input-locked .input-bar {
            opacity: 0.5;
            pointer-events: none;
        }

        /* =============================================
           COLEX PROGRESS EXPERIENCE
           Branded multi-stage progress shown while waiting for a
           response, replacing any provider-native "Thinking..." style
           indicator. Stages come from progress-stages.json.
           ============================================= */
        .colex-progress {
            display: flex;
            flex-direction: column;
            gap: 0.5rem;
            padding: var(--sp-2) 0;
        }

        .progress-stage {
            display: flex;
            align-items: center;
            gap: 0.625rem;
            font-size: var(--text-sm);
        }

        .progress-stage.completed {
            color: var(--text-quaternary);
        }

        .progress-stage.current {
            color: var(--text-primary);
            font-weight: var(--font-medium);
        }

        .progress-stage .stage-icon {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 1rem;
            flex-shrink: 0;
        }

        .progress-stage.completed .stage-icon {
            color: var(--green);
            font-size: var(--text-xs);
        }

        .progress-pulse {
            width: 0.5rem;
            height: 0.5rem;
            border-radius: var(--radius-full);
            background-color: var(--text-primary);
            animation: progress-pulse-anim 1.2s infinite ease-in-out;
        }

        @keyframes progress-pulse-anim {
            0%, 100% { opacity: 0.3; transform: scale(0.8); }
            50% { opacity: 1; transform: scale(1); }
        }

        .period-dropdown {
            padding: 0.4375rem 0.75rem;
            background-color: var(--bg-elevated);
            color: var(--text-primary);
            border: 1px solid var(--border-default);
            border-radius: var(--radius-md);
            font-size: var(--text-xs);
            font-weight: var(--font-normal);
            cursor: pointer;
            outline: none;
            font-family: var(--font-sans);
            transition: border-color var(--transition-fast);
            -webkit-appearance: none;
            appearance: none;
        }

        .period-dropdown:hover {
            border-color: var(--border-hover);
        }

        .period-dropdown:focus {
            border-color: var(--border-focus);
        }

        .date-range-display {
            font-size: var(--text-2xs);
            color: var(--text-tertiary);
            font-family: var(--font-mono);
            background-color: var(--bg-elevated);
            padding: 0.4375rem 0.75rem;
            border-radius: var(--radius-md);
            letter-spacing: -0.01em;
        }

        .chat-messages {
            flex: 1;
            overflow-y: auto;
            padding: var(--sp-6);
            display: flex;
            flex-direction: column;
            gap: var(--sp-6);
        }

        .chat-messages::-webkit-scrollbar {
            width: 4px;
        }

        .chat-messages::-webkit-scrollbar-track {
            background: transparent;
        }

        /* Empty-conversation state: greeting/cards/prompts previously sat
           pinned to the top of .chat-messages with all of .chat-messages'
           flex:1 space left empty below them, before the composer even
           further down — reading as a canyon of dead space, "more like a
           form than a polished AI chat interface." Pull the welcome
           content down to sit just above the composer instead, but ONLY
           while there's no real conversation yet (#messageHistory has no
           children) — the instant a message renders, this margin has
           nothing left to auto-consume and the block reverts to normal
           top-of-scroll flow, so a real conversation is never affected.
           Degrades safely on a browser without :has() support: the block
           just keeps today's default top-aligned position. */
        .welcome-block {
            display: flex;
            flex-direction: column;
            gap: var(--sp-6);
            /* Centered reading column (2026-08-10, per direct report) —
               .chat-messages itself stays full-width (its own scrollbar
               needs to stay at the true pane edge), but its CONTENT was
               unconstrained, so on a wide viewport the assistant's plain-
               text replies (max-width:85% of a 1000px+ pane) stretched
               almost edge-to-edge, reading as pinned to the far left
               rather than part of a centered conversation — the same
               max-width + centered-column convention chatgpt.com/
               claude.ai both use. #messageHistory (below) gets the exact
               same width/centering so a real conversation's user/
               assistant bubbles line up with this column, not just the
               empty-state greeting. */
            width: 100%;
            max-width: 56rem;
            margin: 0 auto;
        }

        .chat-messages:not(:has(#messageHistory > *)) .welcome-block {
            margin-top: auto;
        }

        #messageHistory {
            width: 100%;
            max-width: 56rem;
            margin: 0 auto;
            display: flex;
            flex-direction: column;
            gap: var(--sp-6);
        }

        .chat-messages::-webkit-scrollbar-thumb {
            background: var(--border-default);
            border-radius: 2px;
        }

        .greeting {
            font-size: var(--text-2xl);
            font-weight: var(--font-semibold);
            color: var(--text-primary);
            letter-spacing: -0.02em;
            line-height: 1.3;
        }

        .greeting-subtitle {
            font-size: var(--text-base);
            color: var(--text-secondary);
            line-height: 1.6;
            max-width: 580px;
        }

        .context-cards {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: var(--sp-3);
        }

        .context-card {
            background-color: var(--bg-sidebar);
            border: 1px solid var(--border-thin);
            border-radius: var(--radius-lg);
            padding: var(--sp-4);
            transition: border-color var(--transition-fast);
        }

        .context-card:hover {
            border-color: var(--border-hover);
        }

        .card-label {
            font-size: var(--text-2xs);
            color: var(--text-tertiary);
            font-weight: var(--font-medium);
            letter-spacing: 0.03em;
            margin-bottom: var(--sp-1);
        }

        .card-value {
            font-size: var(--text-xl);
            font-weight: var(--font-semibold);
            font-family: var(--font-mono);
            color: var(--text-primary);
            letter-spacing: -0.02em;
        }

        .card-value.positive { color: var(--green); }
        .card-value.negative { color: var(--red); }

        .suggested-prompts {
            display: flex;
            flex-wrap: wrap;
            gap: var(--sp-2);
        }

        .prompt-chip {
            padding: 0.5rem 1rem;
            background-color: var(--bg-sidebar);
            border: 1px solid var(--border-thin);
            border-radius: var(--radius-2xl);
            font-size: var(--text-sm);
            color: var(--text-secondary);
            cursor: pointer;
            transition: all var(--transition-fast);
            font-family: var(--font-sans);
            line-height: 1.4;
        }

        .prompt-chip:hover {
            border-color: var(--border-hover);
            color: var(--text-primary);
            background-color: var(--bg-hover);
        }

        .input-bar-container {
            padding: var(--sp-4) var(--sp-6) var(--sp-6);
            flex-shrink: 0;
        }

        .input-bar {
            /* Restructured (2026-08-10, per explicit correction): +/send
               are now position:absolute overlays anchored to this
               container, and .message-input spans the FULL width of the
               pill edge-to-edge underneath them (not a flex row where the
               textarea's own scrollbar sits nested BEFORE the button).
               This means the textarea's scrollbar — when a long message
               overflows — renders at THIS element's own outer border,
               i.e. past/after the send button in left-to-right reading
               order, "closing off" the whole composer the same way a
               page scrollbar closes off a page — matching the explicit
               reference behavior (send button first, scrollbar last,
               never nested between the text and the button). */
            position: relative;
            background-color: var(--bg-input);
            border: 1px solid var(--border-default);
            border-radius: var(--radius-2xl);
            transition: all var(--transition-base);
        }

        .input-bar:focus-within {
            border-color: var(--border-focus);
            background-color: var(--bg-elevated);
        }

        .plus-btn {
            position: absolute;
            left: 0.625rem;
            bottom: 0.4375rem;
            z-index: 1;
            background: none;
            border: none;
            cursor: pointer;
            width: 2.5rem;
            height: 2.5rem;
            border-radius: var(--radius-full);
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all var(--transition-fast);
            flex-shrink: 0;
            color: var(--text-secondary);
            padding: 0;
        }

        .plus-btn:hover {
            background-color: var(--bg-hover);
            color: var(--text-primary);
        }

        .plus-btn:active {
            background-color: var(--bg-active);
            transform: scale(0.94);
        }

        .plus-btn svg {
            width: 1.5rem;
            height: 1.5rem;
            stroke-width: 2;
        }

        .message-input {
            /* Full-width, edge-to-edge block (was a flex:1 sibling of the
               buttons) — the +/mic/send controls now float on top of
               this element's own left/right padding zones via
               position:absolute (see .plus-btn/.input-actions-right
               below), so this box's true right border IS .input-bar's
               outer border, and its scrollbar (when content overflows)
               renders there — past the send button, not before it. */
            display: block;
            width: 100%;
            box-sizing: border-box;
            background: none;
            border: none;
            color: var(--text-primary);
            font-size: var(--text-base);
            outline: none;
            font-family: var(--font-sans);
            resize: none;
            /* Left clears the + button, right clears the mic/send
               button — text never runs under either, even though the
               box itself spans the full pill width. */
            padding: 0.6875rem 3.75rem 0.6875rem 3.5rem;
            /* Compact at rest (rows="1" + this line-height ≈ one line
               tall, plus the padding above); the input-listener below
               grows it with content up to this cap, then scrolls
               internally instead of the composer continuing to grow. */
            min-height: calc(1.5 * var(--text-base) + 1.375rem);
            max-height: 136px;
            line-height: 1.5;
            letter-spacing: -0.01em;
            /* Subtle, integrated scrollbar for long pasted messages —
               same treatment as .chat-messages' own scrollbar, not the
               raw browser default.
               NOTE: the page itself has no scrollbar of its own
               (body{overflow:hidden}, an intentional fixed-viewport
               app-shell layout, not a scrolling document); the
               conversation history's own separate, already-existing
               scrollbar (#chatMessages) sits at the outer edge of the
               chat pane, well above and architecturally independent of
               the composer — unaffected by anything here. */
            scrollbar-width: thin;
            scrollbar-color: var(--border-default) transparent;
        }

        .message-input::-webkit-scrollbar {
            width: 3px;
        }

        .message-input::-webkit-scrollbar-track {
            background: transparent;
        }

        .message-input::-webkit-scrollbar-thumb {
            background: var(--border-default);
            border-radius: 2px;
        }

        .message-input::placeholder {
            color: var(--text-tertiary);
        }

        .input-actions-right {
            position: absolute;
            right: 0.75rem;
            bottom: 0.4375rem;
            z-index: 1;
            display: flex;
            align-items: center;
            gap: var(--sp-1);
            flex-shrink: 0;
        }

        .input-icon-btn {
            background: none;
            border: none;
            cursor: pointer;
            width: 2.5rem;
            height: 2.5rem;
            border-radius: var(--radius-full);
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all var(--transition-fast);
            flex-shrink: 0;
            color: var(--text-tertiary);
            padding: 0;
        }

        .input-icon-btn:hover {
            background-color: var(--bg-hover);
            color: var(--text-primary);
        }

        .input-icon-btn:active {
            transform: scale(0.94);
        }

        .input-icon-btn.recording {
            color: var(--red);
        }

        .input-icon-btn.recording svg {
            animation: mic-pulse 1.2s infinite ease-in-out;
        }

        @keyframes mic-pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.4; }
        }

        .input-icon-btn svg {
            width: 1.25rem;
            height: 1.25rem;
            stroke-width: 2;
        }

        .send-btn {
            width: 2.5rem;
            height: 2.5rem;
            border-radius: var(--radius-full);
            border: none;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all var(--transition-fast);
            flex-shrink: 0;
            padding: 0;
            background-color: #ffffff;
            color: #0d0d0d;
        }

        .send-btn:hover {
            background-color: #e5e5e5;
            transform: scale(1.03);
        }

        .send-btn:active {
            transform: scale(0.94);
            background-color: #d4d4d4;
        }

        .send-btn:disabled {
            opacity: 0;
            pointer-events: none;
        }

        .send-btn svg {
            width: 1.25rem;
            height: 1.25rem;
            stroke-width: 2.5;
        }

        .user-message {
            display: flex;
            justify-content: flex-end;
        }

        .user-message-content {
            background-color: var(--bg-elevated);
            border: 1px solid var(--border-thin);
            color: var(--text-primary);
            padding: var(--sp-3) var(--sp-4);
            border-radius: var(--radius-xl);
            max-width: 70%;
            font-size: var(--text-base);
            line-height: 1.6;
            word-wrap: break-word;
        }

        .assistant-message {
            display: flex;
            justify-content: flex-start;
        }

        .assistant-message-content {
            background-color: transparent;
            padding: 0;
            max-width: 85%;
            font-size: var(--text-base);
            line-height: 1.7;
            color: var(--text-primary);
        }

        .loading-indicator {
            display: flex;
            gap: var(--sp-1);
            padding: var(--sp-4) 0;
        }

        .loading-dot {
            width: 0.375rem;
            height: 0.375rem;
            background-color: var(--text-tertiary);
            border-radius: var(--radius-full);
            animation: dot-pulse 1.4s infinite ease-in-out;
        }

        .loading-dot:nth-child(2) { animation-delay: 0.2s; }
        .loading-dot:nth-child(3) { animation-delay: 0.4s; }

        @keyframes dot-pulse {
            0%, 80%, 100% { opacity: 0.2; transform: scale(0.8); }
            40% { opacity: 1; transform: scale(1); }
        }

        .screenshot-attachment {
            margin-top: var(--sp-2);
            border-radius: var(--radius-md);
            overflow: hidden;
            border: 1px solid var(--border-thin);
        }

        .screenshot-attachment img {
            display: block;
            width: 100%;
            max-height: 280px;
            object-fit: cover;
            border-radius: var(--radius-md);
            cursor: pointer;
            transition: opacity var(--transition-fast);
        }

        .screenshot-attachment img:hover {
            opacity: 0.85;
        }

        .upload-progress {
            display: flex;
            align-items: center;
            gap: var(--sp-2);
            padding: var(--sp-3);
            background-color: var(--bg-elevated);
            border: 1px solid var(--border-thin);
            border-radius: var(--radius-md);
            color: var(--text-secondary);
            font-size: var(--text-xs);
        }

        .upload-progress .loading-indicator {
            padding: 0;
            display: inline-flex;
            gap: 0.25rem;
        }

        .ai-response-card {
            background-color: var(--bg-sidebar);
            border: 1px solid var(--border-thin);
            border-radius: var(--radius-lg);
            padding: var(--sp-5);
            margin-top: var(--sp-4);
        }

        .card-title {
            font-size: var(--text-base);
            font-weight: var(--font-semibold);
            margin-bottom: var(--sp-4);
            color: var(--text-primary);
        }

        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
            gap: var(--sp-4);
        }

        .stat-item {
            display: flex;
            flex-direction: column;
            gap: var(--sp-1);
        }

        .stat-label {
            font-size: var(--text-2xs);
            color: var(--text-tertiary);
            font-weight: var(--font-medium);
            letter-spacing: 0.03em;
        }

        .stat-number {
            font-size: var(--text-lg);
            font-weight: var(--font-semibold);
            font-family: var(--font-mono);
        }

        .stat-number.positive { color: var(--green); }
        .stat-number.negative { color: var(--red); }

        .stat-description {
            font-size: var(--text-xs);
            color: var(--text-secondary);
            line-height: 1.5;
        }

        .hidden { display: none !important; }

        .custom-date-container {
            display: flex;
            gap: var(--sp-2);
            align-items: center;
        }

        .date-input {
            padding: 0.375rem 0.625rem;
            background-color: var(--bg-elevated);
            color: var(--text-primary);
            border: 1px solid var(--border-default);
            border-radius: var(--radius-sm);
            font-size: var(--text-2xs);
            font-family: var(--font-mono);
            outline: none;
            transition: border-color var(--transition-fast);
        }

        .date-input:focus {
            border-color: var(--border-focus);
        }
    </style>
</head>
<body>
    <?= $globalNavHtml ?>
    <!-- 2026-08-19 security fix: the captured nav above includes a Sign
         Out link with onclick="return traderalignSignOut(event)" (see
         sidebar.php), but the regex capture that produced $globalNavHtml
         only grabs the <nav>...</nav> element itself, not the function
         definition sidebar.php also renders right after it — defined here
         instead so this page's own Sign Out link actually works, same
         real POST-based sign-out every other page now uses. -->
    <script>
    function traderalignSignOut(event) {
        if (event) event.preventDefault();
        fetch('/traderalign/api/auth/sign-out', { method: 'POST' })
            .catch(function () {})
            .then(function () { window.location.href = '/traderalign/sign-in'; });
        return false;
    }
    </script>
    <!-- =============================================
         SIDEBAR
         ============================================= -->
    <div class="sidebar" id="sidebar">
        <div class="sidebar-header">
            <div class="sidebar-header-top">
                <!-- Logo area -->
                <div class="sidebar-logo-area" id="logoArea" title="TraderAlign Home" onclick="handleLogoClick(event)">
                    <!-- Wordmark intentionally blank in the expanded state (see .logo-full) -->
                    <span class="logo-full logo-text"></span>
                    <!-- Expand icon, shown only when collapsed, in normal flow so it
                         lines up with the collapsed-nav icon column beneath it. -->
                    <span class="sidebar-open-icon">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
                            <line x1="15" y1="3" x2="15" y2="21"></line>
                        </svg>
                    </span>
                </div>
                
                <!-- Action buttons (expanded only) -->
                <div class="sidebar-actions">
                    <button class="sidebar-icon-btn" title="Search conversations" onclick="focusSearch()">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <circle cx="11" cy="11" r="8"></circle>
                            <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
                        </svg>
                    </button>
                    <button class="sidebar-icon-btn" title="Collapse sidebar" onclick="toggleSidebar()">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
                            <line x1="9" y1="3" x2="9" y2="21"></line>
                        </svg>
                    </button>
                </div>
            </div>
            
            <!-- New chat button (expanded: full button, collapsed: hidden - collapsed-nav has its own icon) -->
            <button class="new-chat-btn" onclick="createNewConversation()">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <line x1="12" y1="5" x2="12" y2="19"></line>
                    <line x1="5" y1="12" x2="19" y2="12"></line>
                </svg>
                <span class="btn-text">New chat</span>
            </button>

            <!-- Search conversations (expanded only) -->
            <div class="sidebar-search-wrap">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="11" cy="11" r="8"></circle>
                    <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
                </svg>
                <input
                    type="text"
                    class="sidebar-search-input"
                    id="conversationSearch"
                    placeholder="Search conversations"
                    oninput="filterConversations(this.value)"
                >
            </div>
        </div>
        
        <!-- Conversations list (expanded state) -->
        <div class="conversations-container" id="conversationsList">
            <?php
            $sectionOrder = ['Pinned', 'Today', 'Yesterday', 'Previous 7 Days', 'Previous 30 Days', 'Older'];
            $groupedConversations = [];
            
            foreach ($conversations as $conv) {
                $groupedConversations[$conv['section']][] = $conv;
            }
            
            if (!empty($conversations)):
                foreach ($sectionOrder as $section):
                    if (!empty($groupedConversations[$section])):
            ?>
                <div class="conversation-section" data-section="<?php echo htmlspecialchars($section); ?>">
                    <div class="section-label<?php echo $section === 'Pinned' ? ' pinned-label' : ''; ?>">
                        <?php if ($section === 'Pinned'): ?>
                        <svg viewBox="0 0 24 24" fill="currentColor" stroke="none">
                            <path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2z"></path>
                        </svg>
                        <?php endif; ?>
                        <?php echo htmlspecialchars($section); ?>
                    </div>
                    <?php foreach ($groupedConversations[$section] as $conv): 
                        $title = !empty($conv['title']) ? $conv['title'] : 'New conversation';
                        $isActive = isset($_GET['conversation_id']) && $_GET['conversation_id'] === $conv['id'];
                        $isPinned = !empty($conv['is_pinned']);
                    ?>
                        <div class="conversation-item <?php echo $isActive ? 'active' : ''; ?>"
                             data-conversation-id="<?php echo htmlspecialchars($conv['id']); ?>"
                             data-title="<?php echo htmlspecialchars(strtolower($title)); ?>"
                             data-pinned="<?php echo $isPinned ? '1' : '0'; ?>"
                             onclick="switchConversation('<?php echo htmlspecialchars($conv['id']); ?>')"
                             oncontextmenu="showContextMenu(event, '<?php echo htmlspecialchars($conv['id']); ?>', <?php echo $isPinned ? 'true' : 'false'; ?>); return false;">
                            <span class="conv-title"><?php echo htmlspecialchars($title); ?></span>
                            <button class="delete-conv-btn" onclick="event.stopPropagation(); deleteConversation('<?php echo htmlspecialchars($conv['id']); ?>')" title="Delete conversation">
                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                    <polyline points="3 6 5 6 21 6"></polyline>
                                    <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
                                </svg>
                            </button>
                        </div>
                    <?php endforeach; ?>
                </div>
            <?php 
                    endif;
                endforeach;
            else:
            ?>
                <div class="sidebar-empty-state" id="sidebarEmptyState">
                    No conversations yet.<br>Start a new chat to begin.
                </div>
            <?php endif; ?>
        </div>
        
        <!-- Collapsed sidebar navigation - ONE icon per action, no duplicates -->
        <div class="collapsed-nav">
            <!-- New Chat -->
            <button class="collapsed-nav-btn" title="New chat" onclick="createNewConversation()">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <line x1="12" y1="5" x2="12" y2="19"></line>
                    <line x1="5" y1="12" x2="19" y2="12"></line>
                </svg>
            </button>
            <!-- Search Conversations -->
            <button class="collapsed-nav-btn" title="Search conversations" onclick="focusSearch()">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <circle cx="11" cy="11" r="8"></circle>
                    <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
                </svg>
            </button>
            <!-- Pinned Conversations -->
            <button class="collapsed-nav-btn" title="Pinned conversations" onclick="showPinned()">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2z"></path>
                </svg>
            </button>
            <!-- Note: a separate "Recent Conversations" icon was removed here -
                 the conversation list itself already IS the recent list, so a
                 second entry point for the same data was redundant. -->
        </div>
    </div>

    <!-- =============================================
         CONTEXT MENU (Pin / Unpin)
         ============================================= -->
    <div class="context-menu" id="contextMenu">
        <button class="context-menu-item" id="pinMenuItem" onclick="togglePinFromMenu()">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2z"></path>
            </svg>
            <span id="pinMenuLabel">Pin</span>
        </button>
    </div>

    <!-- =============================================
         CONFIRMATION MODAL
         ============================================= -->
    <div class="modal-overlay" id="deleteModal">
        <div class="modal-dialog">
            <div class="modal-title">Delete conversation?</div>
            <div class="modal-message">Are you sure you want to permanently delete this conversation? This action cannot be undone.</div>
            <div class="modal-actions">
                <button class="modal-btn modal-btn-cancel" onclick="closeDeleteModal()">Cancel</button>
                <button class="modal-btn modal-btn-delete" id="confirmDeleteBtn">Delete</button>
            </div>
        </div>
    </div>

    <!-- =============================================
         NOTIFICATION TOAST
         ============================================= -->
    <div class="toast" id="toast"></div>

    <!-- =============================================
         MAIN CHAT
         ============================================= -->
    <div class="main-chat">
        <div class="chat-header">
            <div class="context-selector">
                <select class="period-dropdown" id="periodSelector" onchange="updatePeriod(this.value)">
                    <option value="current_week" <?php echo $period === 'current_week' ? 'selected' : ''; ?>>Current Week</option>
                    <option value="current_month" <?php echo $period === 'current_month' ? 'selected' : ''; ?>>Current Month</option>
                    <option value="current_quarter" <?php echo $period === 'current_quarter' ? 'selected' : ''; ?>>Current Quarter</option>
                    <option value="current_year" <?php echo $period === 'current_year' ? 'selected' : ''; ?>>Current Year</option>
                    <option value="previous_week" <?php echo $period === 'previous_week' ? 'selected' : ''; ?>>Previous Week</option>
                    <option value="previous_month" <?php echo $period === 'previous_month' ? 'selected' : ''; ?>>Previous Month</option>
                    <option value="previous_quarter" <?php echo $period === 'previous_quarter' ? 'selected' : ''; ?>>Previous Quarter</option>
                    <option value="previous_year" <?php echo $period === 'previous_year' ? 'selected' : ''; ?>>Previous Year</option>
                    <option value="custom" <?php echo $period === 'custom' ? 'selected' : ''; ?>>Custom Date Range</option>
                </select>
                
                <div class="date-range-display" id="dateRangeDisplay">
                    <?php echo htmlspecialchars($dateRange['display']); ?>
                </div>
                
                <div id="customDateContainer" class="custom-date-container <?php echo $period !== 'custom' ? 'hidden' : ''; ?>">
                    <input type="date" class="date-input" id="dateFrom" value="<?php echo htmlspecialchars($customDateFrom ?? ''); ?>" onchange="updateCustomDates()">
                    <span style="color: var(--text-tertiary); font-size: var(--text-2xs);">to</span>
                    <input type="date" class="date-input" id="dateTo" value="<?php echo htmlspecialchars($customDateTo ?? ''); ?>" onchange="updateCustomDates()">
                </div>
            </div>

            <div class="allowance-badge allowance-<?php echo htmlspecialchars($allowanceForFrontend['color']); ?>" id="allowanceBadge">
                <span class="allowance-dot"></span>
                <span>Colex AI · <span class="allowance-percent" id="allowancePercent"><?php echo (int)$allowanceForFrontend['remaining_percent']; ?>%</span> AI credits remaining</span>
            </div>
        </div>

        <div class="chat-messages" id="chatMessages">
            <div class="welcome-block" id="welcomeBlock">
                <div class="greeting" id="greetingTitle"><?php echo htmlspecialchars($greeting['title']); ?></div>
                <div class="greeting-subtitle" id="greetingSubtitle"><?php echo htmlspecialchars($greeting['subtitle']); ?></div>

                <div class="context-cards" id="contextCards">
                    <?php foreach ($contextCards as $card): ?>
                    <div class="context-card">
                        <div class="card-label"><?php echo htmlspecialchars($card['label']); ?></div>
                        <div class="card-value <?php echo $card['color_class'] ?? ''; ?>">
                            <?php echo htmlspecialchars($card['value']); ?>
                        </div>
                    </div>
                    <?php endforeach; ?>
                </div>

                <div class="suggested-prompts" id="suggestedPrompts">
                    <?php foreach ($suggestedPrompts as $prompt): ?>
                    <div class="prompt-chip" onclick="sendPrompt('<?php echo htmlspecialchars(addslashes($prompt['text'])); ?>', '<?php echo htmlspecialchars(addslashes($prompt['task_type'] ?? '')); ?>')">
                        <?php echo htmlspecialchars($prompt['text']); ?>
                    </div>
                    <?php endforeach; ?>
                </div>
            </div>

            <div id="messageHistory"></div>
        </div>

        <div class="allowance-exhausted-banner <?php echo $allowanceForFrontend['exhausted'] ? '' : 'hidden'; ?>" id="allowanceExhaustedBanner">
            <div class="banner-title">Monthly Colex AI allowance used</div>
            <div class="banner-message" id="allowanceExhaustedMessage">
                You've used this month's Colex AI allowance. It resets on <?php echo htmlspecialchars($allowanceForFrontend['reset_date']); ?>. The rest of TraderAlign (your Journal, Reports, Dashboard, and everything else) keeps working normally, and this conversation is still here to read any time.
            </div>
            <div class="banner-actions">
                <button class="banner-btn banner-btn-primary" onclick="window.location.href='/traderalign/settings#billing'">Upgrade plan</button>
            </div>
        </div>

        <div class="input-bar-container <?php echo $allowanceForFrontend['exhausted'] ? 'input-locked' : ''; ?>" id="inputBarContainer">
            <div class="input-bar">
                <button class="plus-btn" id="plusBtn" title="Upload chart or screenshot" onclick="document.getElementById('fileUpload').click()">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                        <line x1="12" y1="5" x2="12" y2="19"></line>
                        <line x1="5" y1="12" x2="19" y2="12"></line>
                    </svg>
                </button>
                
                <textarea 
                    class="message-input" 
                    id="messageInput" 
                    placeholder="<?php echo $allowanceForFrontend['exhausted'] ? 'Monthly allowance used: see above' : 'Ask Colex...'; ?>"
                    rows="1"
                    onkeydown="handleKeyDown(event)"
                    oninput="handleInputChange()"
                    <?php echo $allowanceForFrontend['exhausted'] ? 'disabled' : ''; ?>
                ></textarea>
                
                <div class="input-actions-right">
                    <button class="input-icon-btn" id="micBtn" title="Voice input" onclick="handleVoice()">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
                            <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
                            <line x1="12" y1="19" x2="12" y2="23"></line>
                            <line x1="8" y1="23" x2="16" y2="23"></line>
                        </svg>
                    </button>
                    
                    <button class="send-btn hidden" id="sendBtn" title="Send message" onclick="sendMessage()">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                            <line x1="12" y1="19" x2="12" y2="5"></line>
                            <polyline points="5 12 12 5 19 12"></polyline>
                        </svg>
                    </button>
                </div>
            </div>
        </div>
        
        <input type="file" id="fileUpload" accept="image/*" style="display: none;" onchange="handleFileUpload(this)">
    </div>

    <script>
        let currentConversationId = <?php echo isset($_GET['conversation_id']) ? "'" . addslashes($_GET['conversation_id']) . "'" : 'null'; ?>;
        let currentPeriod = '<?php echo addslashes($period); ?>';
        let currentDateFrom = <?php echo $customDateFrom ? "'" . addslashes($customDateFrom) . "'" : 'null'; ?>;
        let currentDateTo = <?php echo $customDateTo ? "'" . addslashes($customDateTo) . "'" : 'null'; ?>;
        let isLoading = false;
        let sidebarCollapsed = false;
        let pendingDeleteId = null;
        let contextMenuConversationId = null;
        let contextMenuIsPinned = false;
        let recognition = null;
        let isRecording = false;

        // Colex's branded progress stages, loaded server-side from
        // progress-stages.json — adding/editing a task type there needs no
        // JS change, this just reads whatever's in the file.
        const COLEX_PROGRESS_STAGES = <?php echo json_encode($progressStages, JSON_UNESCAPED_SLASHES); ?>;
        const COLEX_FALLBACK_TASK_TYPE = <?php echo json_encode($progressStages['_meta']['fallback_type'] ?? 'general'); ?>;
        const COLEX_PAGE_CONTEXT = <?php echo json_encode($colexPageContext); ?>; // e.g. 'journal' if opened from the Journal page — null otherwise
        let lastColexTaskType = null; // which workflow this conversation has settled into, if any; reset on conversation switch

        document.addEventListener('DOMContentLoaded', function() {
            if (currentConversationId) {
                loadConversationMessages(currentConversationId);
            }
            
            const textarea = document.getElementById('messageInput');
            textarea.addEventListener('input', function() {
                this.style.height = 'auto';
                this.style.height = Math.min(this.scrollHeight, 136) + 'px';
            });
            
            updateInputButtons();
            updateLogoTooltip();

            // Pull live stats for the current period on load. The server
            // already renders a snapshot on first paint, but this keeps the
            // cards fresh (and is the same code path used when the period
            // selector changes below).
            fetchStats(currentPeriod, currentDateFrom, currentDateTo);
        });

        /* =============================================
           LIVE CONTEXT CARDS (Total Trades / Active
           Accounts / Period P&L / Behavior Alerts)
           ============================================= */
        function fetchStats(period, dateFrom, dateTo) {
            const params = new URLSearchParams({ period: period });
            if (period === 'custom' && dateFrom && dateTo) {
                params.set('date_from', dateFrom);
                params.set('date_to', dateTo);
            }

            return fetch('/traderalign/api/colex/stats?' + params.toString())
                .then(function(response) { return response.json(); })
                .then(function(data) {
                    if (data.success) {
                        renderStats(data);
                    } else {
                        console.error('Failed to load stats:', data.error);
                    }
                })
                .catch(function(error) {
                    console.error('Error fetching stats:', error);
                });
        }

        function renderStats(data) {
            const titleEl = document.getElementById('greetingTitle');
            const subtitleEl = document.getElementById('greetingSubtitle');
            if (titleEl) titleEl.textContent = data.greeting.title;
            if (subtitleEl) subtitleEl.textContent = data.greeting.subtitle;

            const dateRangeEl = document.getElementById('dateRangeDisplay');
            if (dateRangeEl && data.date_range) {
                dateRangeEl.textContent = data.date_range.display;
            }

            const cardsContainer = document.getElementById('contextCards');
            if (cardsContainer && Array.isArray(data.cards)) {
                let cardsHtml = '';
                data.cards.forEach(function(card) {
                    const colorClass = card.color_class || '';
                    cardsHtml += '<div class="context-card"><div class="card-label">' + escapeHtml(card.label) + '</div><div class="card-value ' + escapeHtml(colorClass) + '">' + escapeHtml(String(card.value)) + '</div></div>';
                });
                cardsContainer.innerHTML = cardsHtml;
            }

            const promptsContainer = document.getElementById('suggestedPrompts');
            if (promptsContainer && Array.isArray(data.suggested_prompts)) {
                let promptsHtml = '';
                data.suggested_prompts.forEach(function(prompt) {
                    const taskType = prompt.task_type ? escapeJsString(prompt.task_type) : '';
                    promptsHtml += '<div class="prompt-chip" onclick="sendPrompt(\'' + escapeJsString(prompt.text) + '\', \'' + taskType + '\')">' + escapeHtml(prompt.text) + '</div>';
                });
                promptsContainer.innerHTML = promptsHtml;
            }
        }

        function escapeJsString(str) {
            return String(str)
                .replace(/\\/g, '\\\\')
                .replace(/'/g, "\\'")
                .replace(/\n/g, '\\n')
                .replace(/\r/g, '');
        }

        function updateLogoTooltip() {
            const logoArea = document.getElementById('logoArea');
            const sidebar = document.getElementById('sidebar');
            
            if (sidebar.classList.contains('collapsed')) {
                logoArea.setAttribute('title', 'Open sidebar');
            } else {
                logoArea.setAttribute('title', 'TraderAlign Home');
            }
        }

        function handleLogoClick(event) {
            const sidebar = document.getElementById('sidebar');
            
            if (sidebar.classList.contains('collapsed')) {
                // Collapsed: click expands sidebar
                toggleSidebar();
            } else {
                // Expanded: click refreshes page (ChatGPT behavior)
                window.location.reload();
            }
        }

        function toggleSidebar() {
            const sidebar = document.getElementById('sidebar');
            sidebarCollapsed = !sidebarCollapsed;
            
            if (sidebarCollapsed) {
                sidebar.classList.add('collapsed');
            } else {
                sidebar.classList.remove('collapsed');
            }
            
            // Update tooltip after state change
            setTimeout(updateLogoTooltip, 50);
        }

        function focusSearch() {
            if (sidebarCollapsed) {
                toggleSidebar();
            }
            setTimeout(function() {
                const input = document.getElementById('conversationSearch');
                if (input) input.focus();
            }, 300);
        }

        function showPinned() {
            if (sidebarCollapsed) {
                toggleSidebar();
            }
            setTimeout(function() {
                const pinnedSection = document.querySelector('.conversation-section[data-section="Pinned"]');
                if (pinnedSection) {
                    pinnedSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
                } else {
                    showToast('No pinned conversations yet. Right-click a conversation to pin it.', 'success');
                }
            }, 300);
        }

        /* =============================================
           SEARCH CONVERSATIONS
           ============================================= */
        function filterConversations(query) {
            const normalized = query.trim().toLowerCase();
            const items = document.querySelectorAll('.conversation-item');
            const sections = document.querySelectorAll('.conversation-section');
            
            items.forEach(function(item) {
                const title = item.getAttribute('data-title') || '';
                if (!normalized || title.indexOf(normalized) !== -1) {
                    item.classList.remove('search-hidden');
                } else {
                    item.classList.add('search-hidden');
                }
            });
            
            sections.forEach(function(section) {
                const visibleItems = section.querySelectorAll('.conversation-item:not(.search-hidden)');
                if (visibleItems.length === 0) {
                    section.classList.add('search-hidden');
                } else {
                    section.classList.remove('search-hidden');
                }
            });
        }

        function handleInputChange() {
            updateInputButtons();
        }

        function updateInputButtons() {
            const messageInput = document.getElementById('messageInput');
            const micBtn = document.getElementById('micBtn');
            const sendBtn = document.getElementById('sendBtn');
            const hasText = messageInput.value.trim().length > 0;
            
            if (hasText) {
                micBtn.classList.add('hidden');
                sendBtn.classList.remove('hidden');
            } else {
                micBtn.classList.remove('hidden');
                sendBtn.classList.add('hidden');
            }
        }

        function updatePeriod(period) {
            currentPeriod = period;
            const customContainer = document.getElementById('customDateContainer');
            
            if (period === 'custom') {
                customContainer.classList.remove('hidden');
                // Wait for both date inputs before fetching - updateCustomDates() handles it.
                return;
            }
            
            customContainer.classList.add('hidden');
            currentDateFrom = null;
            currentDateTo = null;
            
            const url = new URL(window.location);
            url.searchParams.set('period', period);
            url.searchParams.delete('date_from');
            url.searchParams.delete('date_to');
            window.history.pushState({}, '', url.toString());
            
            fetchStats(currentPeriod, currentDateFrom, currentDateTo);
        }

        function updateCustomDates() {
            const dateFrom = document.getElementById('dateFrom').value;
            const dateTo = document.getElementById('dateTo').value;
            
            if (dateFrom && dateTo) {
                currentDateFrom = dateFrom;
                currentDateTo = dateTo;
                
                const url = new URL(window.location);
                url.searchParams.set('period', 'custom');
                url.searchParams.set('date_from', dateFrom);
                url.searchParams.set('date_to', dateTo);
                window.history.pushState({}, '', url.toString());
                
                fetchStats('custom', dateFrom, dateTo);
            }
        }

        function createNewConversation() {
            fetch('/traderalign/api/colex/chat', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    action: 'create_conversation',
                    period: currentPeriod,
                    date_from: currentDateFrom,
                    date_to: currentDateTo
                })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    currentConversationId = data.conversation_id;
                    const url = new URL(window.location);
                    url.searchParams.set('conversation_id', data.conversation_id);
                    window.location = url.toString();
                }
            })
            .catch(error => console.error('Error creating conversation:', error));
        }

        function switchConversation(conversationId) {
            currentConversationId = conversationId;
            lastColexTaskType = null;
            
            document.querySelectorAll('.conversation-item').forEach(function(item) {
                item.classList.remove('active');
            });
            const activeItem = document.querySelector('[data-conversation-id="' + conversationId + '"]');
            if (activeItem) activeItem.classList.add('active');
            
            loadConversationMessages(conversationId);
            
            const url = new URL(window.location);
            url.searchParams.set('conversation_id', conversationId);
            window.history.pushState({}, '', url.toString());
        }

        /* =============================================
           DELETE CONVERSATION WITH CONFIRMATION MODAL
           ============================================= */
        function deleteConversation(conversationId) {
            pendingDeleteId = conversationId;
            const modal = document.getElementById('deleteModal');
            modal.classList.add('active');
        }

        function closeDeleteModal() {
            const modal = document.getElementById('deleteModal');
            modal.classList.remove('active');
            pendingDeleteId = null;
        }

        function showToast(message, type) {
            const toast = document.getElementById('toast');
            toast.textContent = message;
            toast.className = 'toast ' + type + ' visible';
            
            setTimeout(function() {
                toast.classList.remove('visible');
            }, 3000);
        }

        /* =============================================
           COLEX AI ALLOWANCE UI
           Keeps the header badge and the locked-input state in sync with
           whatever usage_status the server last returned. Never shows
           units, tokens, or anything provider-specific — status is
           already reduced to {remaining_percent, color, exhausted,
           reset_date} server-side before it ever reaches this code.
           ============================================= */
        function updateAllowanceUI(status) {
            if (!status) return;

            const badge = document.getElementById('allowanceBadge');
            const percentEl = document.getElementById('allowancePercent');
            if (badge && percentEl) {
                badge.className = 'allowance-badge allowance-' + status.color;
                percentEl.textContent = status.remaining_percent + '%';
            }

            const banner = document.getElementById('allowanceExhaustedBanner');
            const messageEl = document.getElementById('allowanceExhaustedMessage');
            const inputContainer = document.getElementById('inputBarContainer');
            const messageInput = document.getElementById('messageInput');

            if (status.exhausted) {
                if (banner) {
                    banner.classList.remove('hidden');
                    if (messageEl) {
                        messageEl.textContent = "You've used this month's Colex AI allowance. It resets on "
                            + status.reset_date
                            + ". The rest of TraderAlign (your Journal, Reports, Dashboard, and everything else) keeps working normally, and this conversation is still here to read any time.";
                    }
                }
                if (inputContainer) inputContainer.classList.add('input-locked');
                if (messageInput) {
                    messageInput.disabled = true;
                    messageInput.placeholder = 'Monthly allowance used: see above';
                }
            } else {
                if (banner) banner.classList.add('hidden');
                if (inputContainer) inputContainer.classList.remove('input-locked');
                if (messageInput) {
                    messageInput.disabled = false;
                    messageInput.placeholder = 'Ask Colex...';
                }
            }
        }

        function removeConversationFromSidebar(conversationId) {
            const item = document.querySelector('[data-conversation-id="' + conversationId + '"]');
            if (item) {
                const section = item.closest('.conversation-section');
                item.remove();
                if (section && section.querySelectorAll('.conversation-item').length === 0) {
                    section.remove();
                }
            }
            
            if (currentConversationId === conversationId) {
                currentConversationId = null;
                const url = new URL(window.location);
                url.searchParams.delete('conversation_id');
                window.history.pushState({}, '', url.toString());
                document.getElementById('messageHistory').innerHTML = '';
            }
            
            const remainingItems = document.querySelectorAll('.conversation-item');
            if (remainingItems.length === 0) {
                const container = document.getElementById('conversationsList');
                container.innerHTML = '<div class="sidebar-empty-state" id="sidebarEmptyState">No conversations yet.<br>Start a new chat to begin.</div>';
            }
        }

        document.getElementById('confirmDeleteBtn').addEventListener('click', function() {
            if (!pendingDeleteId) return;
            
            const conversationId = pendingDeleteId;
            closeDeleteModal();
            
            fetch('/traderalign/api/colex/delete-conversation', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    conversation_id: conversationId
                })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    removeConversationFromSidebar(conversationId);
                    showToast('Conversation deleted', 'success');
                } else {
                    showToast(data.error || 'Failed to delete conversation', 'error');
                }
            })
            .catch(function(error) {
                console.error('Error deleting conversation:', error);
                showToast('Failed to delete conversation', 'error');
            });
        });

        // Close modal on overlay click
        document.getElementById('deleteModal').addEventListener('click', function(e) {
            if (e.target === this) {
                closeDeleteModal();
            }
        });

        // Close modal on Escape key
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Escape') {
                closeDeleteModal();
                closeContextMenu();
            }
        });

        /* =============================================
           PIN / UNPIN CONTEXT MENU (right-click, or
           press-and-hold on touch devices)
           ============================================= */
        function showContextMenu(event, conversationId, isPinned) {
            event.preventDefault();
            
            contextMenuConversationId = conversationId;
            contextMenuIsPinned = isPinned;
            
            const menu = document.getElementById('contextMenu');
            document.getElementById('pinMenuLabel').textContent = isPinned ? 'Unpin' : 'Pin';
            
            const x = event.clientX;
            const y = event.clientY;
            const menuWidth = 180;
            const menuHeight = 50;
            const left = Math.min(x, window.innerWidth - menuWidth - 8);
            const top = Math.min(y, window.innerHeight - menuHeight - 8);
            
            menu.style.left = left + 'px';
            menu.style.top = top + 'px';
            menu.classList.add('active');
        }

        function closeContextMenu() {
            document.getElementById('contextMenu').classList.remove('active');
            contextMenuConversationId = null;
        }

        document.addEventListener('click', function(e) {
            const menu = document.getElementById('contextMenu');
            if (menu.classList.contains('active') && !menu.contains(e.target)) {
                closeContextMenu();
            }
        });

        // Press-and-hold support for touch devices
        (function() {
            let holdTimer = null;
            document.getElementById('conversationsList').addEventListener('touchstart', function(e) {
                const item = e.target.closest('.conversation-item');
                if (!item) return;
                holdTimer = setTimeout(function() {
                    const conversationId = item.getAttribute('data-conversation-id');
                    const isPinned = item.getAttribute('data-pinned') === '1';
                    const touch = e.touches[0];
                    showContextMenu({
                        preventDefault: function() {},
                        clientX: touch.clientX,
                        clientY: touch.clientY
                    }, conversationId, isPinned);
                }, 550);
            }, { passive: true });
            
            ['touchend', 'touchmove', 'touchcancel'].forEach(function(evt) {
                document.getElementById('conversationsList').addEventListener(evt, function() {
                    if (holdTimer) {
                        clearTimeout(holdTimer);
                        holdTimer = null;
                    }
                }, { passive: true });
            });
        })();

        function togglePinFromMenu() {
            if (!contextMenuConversationId) return;
            
            const conversationId = contextMenuConversationId;
            const newPinnedState = !contextMenuIsPinned;
            closeContextMenu();
            
            fetch('/traderalign/api/colex/pin-conversation', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    conversation_id: conversationId,
                    pinned: newPinnedState
                })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    showToast(newPinnedState ? 'Conversation pinned' : 'Conversation unpinned', 'success');
                    // Simplest reliable way to reflect the new section grouping
                    location.reload();
                } else {
                    showToast(data.error || 'Failed to update pin status', 'error');
                }
            })
            .catch(function(error) {
                console.error('Error toggling pin:', error);
                showToast('Failed to update pin status', 'error');
            });
        }

        function loadConversationMessages(conversationId) {
            fetch('/traderalign/api/colex/chat?action=get_messages&conversation_id=' + conversationId)
                .then(response => response.json())
                .then(data => {
                    if (data.success) renderMessages(data.messages);
                })
                .catch(error => console.error('Error loading messages:', error));
        }

        function renderMessages(messages) {
            const container = document.getElementById('messageHistory');
            container.innerHTML = '';
            
            messages.forEach(function(message) {
                if (message.role === 'user') {
                    let contentHtml = escapeHtml(message.content);
                    
                    if (message.cards) {
                        try {
                            const cards = typeof message.cards === 'string' ? JSON.parse(message.cards) : message.cards;
                            if (cards && cards.screenshot_url) {
                                contentHtml += '<div class="screenshot-attachment">';
                                contentHtml += '<img src="' + escapeHtml(cards.screenshot_url) + '" alt="Trading Screenshot" onclick="window.open(\'' + escapeHtml(cards.screenshot_url) + '\', \'_blank\')" loading="lazy">';
                                contentHtml += '</div>';
                            }
                        } catch (e) {}
                    }
                    
                    container.innerHTML += '<div class="user-message"><div class="user-message-content">' + contentHtml + '</div></div>';
                } else {
                    let cardsHtml = '';
                    if (message.cards) {
                        try {
                            const cards = typeof message.cards === 'string' ? JSON.parse(message.cards) : message.cards;
                            if (cards && cards.stats) {
                                cardsHtml = renderStatsCard(cards);
                            }
                        } catch (e) {}
                    }
                    
                    container.innerHTML += '<div class="assistant-message"><div class="assistant-message-content"><div>' + escapeHtml(message.content) + '</div>' + cardsHtml + '</div></div>';
                }
            });
            
            document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
        }

        function renderStatsCard(cards) {
            let html = '<div class="ai-response-card"><div class="card-title">Performance Summary</div><div class="stats-grid">';
            
            for (const key in cards.stats) {
                const stat = cards.stats[key];
                const colorClass = stat.direction === 'positive' ? 'positive' : (stat.direction === 'negative' ? 'negative' : '');
                html += '<div class="stat-item"><div class="stat-label">' + escapeHtml(stat.label) + '</div><div class="stat-number ' + colorClass + '">' + escapeHtml(stat.value) + '</div>';
                if (stat.description) {
                    html += '<div class="stat-description">' + escapeHtml(stat.description) + '</div>';
                }
                html += '</div>';
            }
            
            return html + '</div></div>';
        }

        function sendPrompt(promptText, taskType) {
            document.getElementById('messageInput').value = promptText;
            updateInputButtons();
            sendMessage(null, taskType || null);
        }

        /* =============================================
           COLEX PROGRESS EXPERIENCE
           Detects what kind of request this is and cycles through a
           matching branded stage sequence while waiting for a response —
           never a provider-native "Thinking..."/"Fathoming..." message.
           Entirely data-driven from COLEX_PROGRESS_STAGES (see
           progress-stages.json); adding a task type there is enough, no
           changes needed here.
           ============================================= */
        /**
         * Resolves which branded progress workflow to show. Checked in
         * order of confidence — keyword matching is the last resort, used
         * only when nothing more concrete is available:
         *   1. An attachment (a chart image today; a future document/voice
         *      attachment would plug in the same way) — the strongest
         *      possible signal, since it states outright what kind of
         *      work this is.
         *   2. An explicit request type — e.g. the user clicked a
         *      suggested-prompt chip that already declares what it's
         *      about (see task_type in generateSuggestedPrompts()), so
         *      there's nothing to infer.
         *   3. Conversation continuity — if this thread already settled
         *      into a workflow, stay there rather than re-guessing.
         *   4. Page/module context — which part of TraderAlign the user
         *      opened Colex from (see COLEX_PAGE_CONTEXT), for the first
         *      message of a fresh conversation.
         *   5. Keyword matching — fallback only.
         *
         * This only decides which progress stages animate in the chat
         * while waiting — it's never sent to the backend and has no
         * bearing on what Colex actually reasons about or replies with.
         */
        function detectColexTaskType(options) {
            options = options || {};

            if (options.hasAttachment && COLEX_PROGRESS_STAGES.image) {
                return 'image';
            }

            if (options.explicitTaskType && COLEX_PROGRESS_STAGES[options.explicitTaskType]) {
                return options.explicitTaskType;
            }

            if (lastColexTaskType && COLEX_PROGRESS_STAGES[lastColexTaskType]) {
                return lastColexTaskType;
            }

            if (COLEX_PAGE_CONTEXT && COLEX_PROGRESS_STAGES[COLEX_PAGE_CONTEXT]) {
                return COLEX_PAGE_CONTEXT;
            }

            // Fallback only — no stronger contextual signal was available.
            const lower = (options.message || '').toLowerCase();
            for (const type in COLEX_PROGRESS_STAGES) {
                if (type === '_meta' || type === COLEX_FALLBACK_TASK_TYPE) continue;
                const cfg = COLEX_PROGRESS_STAGES[type];
                if (!cfg || !Array.isArray(cfg.keywords)) continue;
                for (const kw of cfg.keywords) {
                    if (kw && lower.includes(kw)) return type;
                }
            }

            return COLEX_FALLBACK_TASK_TYPE;
        }

        function renderColexProgress(containerId, stages, currentIndex) {
            const container = document.getElementById(containerId);
            if (!container) return; // response already replaced this node

            let html = '<div class="colex-progress">';
            for (let i = 0; i <= currentIndex && i < stages.length; i++) {
                if (i < currentIndex) {
                    html += '<div class="progress-stage completed"><span class="stage-icon">&#10003;</span><span class="stage-text">'
                        + escapeHtml(stages[i]) + '</span></div>';
                } else {
                    html += '<div class="progress-stage current"><span class="stage-icon"><span class="progress-pulse"></span></span><span class="stage-text">'
                        + escapeHtml(stages[i]) + '</span></div>';
                }
            }
            html += '</div>';
            container.innerHTML = html;
        }

        /**
         * Starts cycling a container through a task type's branded stages.
         * Advances roughly every 1.5s; holds (and keeps pulsing) on the
         * final stage if the real response takes longer than the sequence
         * — it never loops back to the start or disappears early. Returns
         * the interval ID so the caller can stop it once the actual
         * response arrives.
         */
        function startColexProgress(containerId, taskType) {
            const cfg = COLEX_PROGRESS_STAGES[taskType] || COLEX_PROGRESS_STAGES[COLEX_FALLBACK_TASK_TYPE];
            const stages = (cfg && cfg.stages && cfg.stages.length) ? cfg.stages : ['Working on it...'];

            let currentIndex = 0;
            renderColexProgress(containerId, stages, currentIndex);

            return setInterval(function() {
                if (currentIndex < stages.length - 1) {
                    currentIndex++;
                    renderColexProgress(containerId, stages, currentIndex);
                }
            }, 1500);
        }

        function stopColexProgress(intervalId) {
            if (intervalId) clearInterval(intervalId);
        }

        function sendMessage(screenshotUrl, explicitTaskType) {
            const input = document.getElementById('messageInput');
            if (input.disabled) return; // allowance exhausted — input-locked state
            const message = input.value.trim();
            if (!message && !screenshotUrl) return;
            if (isLoading) return;
            
            isLoading = true;
            document.getElementById('sendBtn').disabled = true;
            
            const messageContainer = document.getElementById('messageHistory');
            
            let userContentHtml = escapeHtml(message || 'Screenshot uploaded');
            if (screenshotUrl) {
                userContentHtml += '<div class="screenshot-attachment">';
                userContentHtml += '<img src="' + escapeHtml(screenshotUrl) + '" alt="Trading Screenshot" onclick="window.open(\'' + escapeHtml(screenshotUrl) + '\', \'_blank\')" loading="lazy">';
                userContentHtml += '</div>';
            }
            
            const userBubbleId = 'user-msg-' + Date.now();
            messageContainer.innerHTML += '<div class="user-message" id="' + userBubbleId + '"><div class="user-message-content">' + userContentHtml + '</div></div>';
            
            const loadingId = 'loading-' + Date.now();
            const progressContainerId = 'colex-progress-' + loadingId;
            messageContainer.innerHTML += '<div class="assistant-message" id="' + loadingId + '"><div class="assistant-message-content"><div id="' + progressContainerId + '"></div></div></div>';

            const taskType = detectColexTaskType({
                hasAttachment: !!screenshotUrl,
                explicitTaskType: explicitTaskType,
                message: message
            });
            lastColexTaskType = taskType;
            const progressIntervalId = startColexProgress(progressContainerId, taskType);
            
            document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
            input.value = '';
            input.style.height = 'auto';
            updateInputButtons();
            
            const payload = {
                action: 'send_message',
                message: message || 'I uploaded a trading screenshot',
                period: currentPeriod,
                date_from: currentDateFrom,
                date_to: currentDateTo,
                conversation_id: currentConversationId
            };
            
            if (screenshotUrl) {
                payload.screenshot_url = screenshotUrl;
            }
            
            fetch('/traderalign/api/colex/chat', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(payload)
            })
            .then(response => response.json())
            .then(data => {
                stopColexProgress(progressIntervalId);
                const loadingElement = document.getElementById(loadingId);
                if (loadingElement) loadingElement.remove();
                
                if (data.success) {
                    if (!currentConversationId && data.conversation_id) {
                        currentConversationId = data.conversation_id;
                        const url = new URL(window.location);
                        url.searchParams.set('conversation_id', data.conversation_id);
                        window.history.pushState({}, '', url.toString());
                        location.reload();
                        return;
                    }
                    
                    let cardsHtml = data.cards && data.cards.stats ? renderStatsCard(data.cards) : '';
                    
                    messageContainer.innerHTML += '<div class="assistant-message"><div class="assistant-message-content"><div>' + escapeHtml(data.response) + '</div>' + cardsHtml + '</div></div>';

                    updateAllowanceUI(data.usage_status);
                } else if (data.error === 'allowance_exhausted') {
                    // The message never actually sent — undo the optimistic
                    // bubble and reflect the now-locked state immediately.
                    const userBubble = document.getElementById(userBubbleId);
                    if (userBubble) userBubble.remove();
                    updateAllowanceUI(data.usage_status);
                } else {
                    // Never surface a provider/technical error string —
                    // always a branded, generic message.
                    messageContainer.innerHTML += '<div class="assistant-message"><div class="assistant-message-content"><div>Colex encountered an issue. Please try again.</div></div></div>';
                }
                
                document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
                isLoading = false;
                document.getElementById('sendBtn').disabled = false;
            })
            .catch(function(error) {
                stopColexProgress(progressIntervalId);
                const loadingElement = document.getElementById(loadingId);
                if (loadingElement) loadingElement.remove();
                messageContainer.innerHTML += '<div class="assistant-message"><div class="assistant-message-content"><div>Colex encountered an issue. Please try again.</div></div></div>';
                document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
                isLoading = false;
                document.getElementById('sendBtn').disabled = false;
            });
        }

        function handleKeyDown(event) {
            if (event.key === 'Enter' && !event.shiftKey) {
                event.preventDefault();
                sendMessage();
            }
        }

        function handleFileUpload(input) {
            if (input.files.length === 0) return;
            
            const file = input.files[0];
            
            const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
            if (!allowedTypes.includes(file.type)) {
                alert('Please upload an image file (JPEG, PNG, GIF, or WebP).');
                input.value = '';
                return;
            }
            
            if (file.size > 10 * 1024 * 1024) {
                alert('File is too large. Maximum size is 10MB.');
                input.value = '';
                return;
            }
            
            const messageContainer = document.getElementById('messageHistory');
            const uploadId = 'upload-' + Date.now();
            messageContainer.innerHTML += '<div class="user-message" id="' + uploadId + '"><div class="user-message-content"><div class="upload-progress">Uploading image... <div class="loading-indicator" style="display: inline-flex; padding: 0;"><div class="loading-dot"></div><div class="loading-dot"></div><div class="loading-dot"></div></div></div></div></div>';
            document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
            
            const formData = new FormData();
            formData.append('screenshot', file);
            
            fetch('/traderalign/api/colex/upload-screenshot', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                const uploadElement = document.getElementById(uploadId);
                if (uploadElement) uploadElement.remove();
                
                if (data.success) {
                    sendMessage(data.url);
                } else {
                    alert('Upload failed: ' + (data.error || 'Unknown error'));
                }
            })
            .catch(function(error) {
                const uploadElement = document.getElementById(uploadId);
                if (uploadElement) uploadElement.remove();
                alert('Upload failed. Please check your connection and try again.');
                console.error('Upload error:', error);
            });
            
            input.value = '';
        }

        /* =============================================
           VOICE INPUT (Web Speech API)
           ============================================= */
        function handleVoice() {
            const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
            
            if (!SpeechRecognition) {
                showToast('Voice input not supported in this browser', 'error');
                return;
            }
            
            const micBtn = document.getElementById('micBtn');
            
            if (isRecording) {
                if (recognition) recognition.stop();
                return;
            }
            
            recognition = new SpeechRecognition();
            recognition.lang = 'en-US';
            recognition.interimResults = true;
            recognition.continuous = false;
            recognition.maxAlternatives = 1;
            
            const input = document.getElementById('messageInput');
            const baseText = input.value ? input.value.trim() + ' ' : '';
            
            recognition.onstart = function() {
                isRecording = true;
                micBtn.classList.add('recording');
                micBtn.title = 'Stop recording';
            };
            
            recognition.onresult = function(event) {
                let transcript = '';
                for (let i = 0; i < event.results.length; i++) {
                    transcript += event.results[i][0].transcript;
                }
                input.value = baseText + transcript;
                input.style.height = 'auto';
                input.style.height = Math.min(input.scrollHeight, 136) + 'px';
                updateInputButtons();
            };
            
            recognition.onerror = function(event) {
                if (event.error === 'not-allowed' || event.error === 'permission-denied') {
                    showToast('Microphone access was denied', 'error');
                } else if (event.error !== 'aborted' && event.error !== 'no-speech') {
                    showToast('Voice input error: ' + event.error, 'error');
                }
            };
            
            recognition.onend = function() {
                isRecording = false;
                micBtn.classList.remove('recording');
                micBtn.title = 'Voice input';
                updateInputButtons();
            };
            
            try {
                recognition.start();
            } catch (e) {
                showToast('Could not start voice input', 'error');
            }
        }

        function escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }
    </script>
</body>
</html>