<?php
$userId = $auth->requireAuth();

// Validation Workflow, same server-side gate as reports.php (the only
// place this page is ever linked from). A trader still validating a
// strategy must never reach the established-edge deep-analytics surface
// by direct URL either.
// 2026-08-17: this exact 4-line block was independently duplicated across
// reports.php/strategy.php/trader-profile.php/journal.php, now the single
// shared ValidationService::gateMainWorkflowOnly() implementation instead.
ValidationService::gateMainWorkflowOnly($userId, $db);

// ── Profile lookup (tier isn't gated on this page, but display_name and
// timezone are — same user_profiles row, one query, both fields). Name
// resolution (Settings/onboarding value, or the "Trader" fallback) goes
// through the one shared resolveDisplayName() — see RenderHelpers.php. ──
$displayName = 'Trader';
$userTimezone = null;
try {
    $profileRow = $db->selectById('user_profiles', $userId, 'user_id');
    $displayName = resolveDisplayName($profileRow);
    if ($profileRow && !empty($profileRow['timezone'])) {
        $userTimezone = $profileRow['timezone'];
    }
} catch (\Throwable $e) {
    error_log("trader-profile: failed to read display_name/timezone: " . $e->getMessage());
}

$period = $_GET['period'] ?? 'monthly';
if (!in_array($period, ['monthly', 'quarterly', 'annual'], true)) $period = 'monthly';

require_once __DIR__ . '/../src/TraderProfileService.php';
require_once __DIR__ . '/../src/TraderProfileCoaching.php';
require_once __DIR__ . '/../src/TraderProfileIllustrations.php';

// Resolved once, here, every image reference on this page (the <head>
// preload hint and the two <picture> elements in the body) reads from
// these two variables rather than re-deriving a path, so there is exactly
// one place (src/TraderProfileIllustrations.php) that knows the mapping
// from timeframe to artwork.
$heroImg = traderProfileIllustration($period, 'hero');
$emotionImg = traderProfileIllustration($period, 'emotion');

$profile = TraderProfileService::build($userId, $period, $displayName);

// ── Small page-local render helpers (mirrors reports.php's own pattern of
//    page-local helper functions rather than a shared component library) ──
function tpCoach(string $key, array $vars = []): string {
    return htmlspecialchars(traderProfileCoach($key, $vars));
}

function tpTierNote(string $tier): string {
    return match ($tier) {
        'none' => 'Not enough closed trades yet.',
        'single_trade' => 'Based on just one trade so far, a starting point, not a verdict.',
        'very_small_sample' => 'Still a very small sample, treat this loosely.',
        'small_sample' => 'A developing sample, getting more reliable.',
        'developing' => 'A solid, real sample.',
        'confident' => 'Backed by a strong sample.',
        default => '',
    };
}

function tpBar(?float $value, string $tier, string $coachKey): void {
    $pct = $value === null ? null : max(0, min(100, $value));
    ?>
    <div class="tp-bar-row">
        <?php if ($pct === null): ?>
            <div class="tp-bar-track"><div class="tp-bar-fill tp-bar-empty" style="width:100%"></div></div>
            <span class="tp-bar-val tp-bar-dim">Still learning</span>
        <?php else: ?>
            <div class="tp-bar-track"><div class="tp-bar-fill" style="width:<?= $pct ?>%"></div></div>
            <span class="tp-bar-val"><?= round($pct) ?></span>
        <?php endif; ?>
    </div>
    <p class="tp-bar-note" title="<?= $coachKey ? tpCoach($coachKey) : '' ?>"><?= htmlspecialchars(tpTierNote($tier)) ?></p>
    <?php
}

function tpEvidence(string $summary, string $html): void {
    echo '<details class="tp-evidence"><summary>' . htmlspecialchars($summary) . '</summary><div class="tp-evidence-body">' . $html . '</div></details>';
}

/**
 * Renders one illustration slot from the array traderProfileIllustration()
 * returns. WebP (two widths, via srcset) is the primary source; the <img>
 * fallback is the optimized PNG, for the rare browser without WebP
 * support. $sizes should describe how wide this slot actually renders
 * (see the two call sites below) so the browser can pick the right
 * candidate instead of always grabbing the largest one.
 */
function tpIllustrationPicture(array $img, string $sizes, string $loading, string $fetchPriority = ''): void {
    ?>
    <picture>
        <source type="image/webp"
            srcset="<?= htmlspecialchars($img['webp_small']) ?> 800w, <?= htmlspecialchars($img['webp_large']) ?> 1536w"
            sizes="<?= htmlspecialchars($sizes) ?>">
        <img src="<?= htmlspecialchars($img['png']) ?>" alt="<?= htmlspecialchars($img['alt']) ?>"
            width="<?= (int)$img['width'] ?>" height="<?= (int)$img['height'] ?>"
            loading="<?= htmlspecialchars($loading) ?>" decoding="async"
            <?= $fetchPriority !== '' ? 'fetchpriority="' . htmlspecialchars($fetchPriority) . '"' : '' ?>
            style="width:100%;height:100%;object-fit:cover;display:block">
    </picture>
    <?php
}

$periodTabs = ['monthly' => 'Monthly', 'quarterly' => 'Quarterly', 'annual' => 'Annual'];
$char = $profile['characters'];
$identity = $profile['identity'];
$narr = $profile['narrative'];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trader Profile - TraderAlign</title>
<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;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/traderalign/assets/tokens.css">
<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">
<!-- Hero illustration is the page's first large visual (a likely LCP
     element), preloaded from the SAME $heroImg the body's <picture>
     uses below, not a second hardcoded path. Modern browsers that
     support imagesrcset/imagesizes on <link rel=preload> use it to
     preload the correct responsive candidate; browsers that don't
     simply ignore the extra attributes and preload the fallback href. -->
<link rel="preload" as="image" href="<?= htmlspecialchars($heroImg['webp_large']) ?>"
    imagesrcset="<?= htmlspecialchars($heroImg['webp_small']) ?> 800w, <?= htmlspecialchars($heroImg['webp_large']) ?> 1536w"
    imagesizes="(max-width: 960px) 100vw, 750px" fetchpriority="high">
<style>
.dash-layout{display:flex;min-height:100vh}
.sidebar{width:4rem;background:var(--sidebar);border-right:1px solid var(--sidebar-border);display:flex;flex-direction:column;align-items:center;padding-top:1rem;gap:.25rem;position:fixed;top:0;left:0;bottom:0;z-index:40}
.sidebar a{display:flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border-radius:var(--radius-md);color:var(--foreground-dim);transition:background var(--t-fast),color var(--t-fast);position:relative}
.sidebar a:hover{background:var(--background-surface);color:var(--foreground)}
.sidebar a.active{background:var(--background-surface);color:var(--sidebar-primary)}
.sidebar a .tt{position:absolute;left:3.5rem;background:var(--popover);color:var(--foreground);font-size:.75rem;padding:.25rem .625rem;border-radius:var(--radius-sm);white-space:nowrap;pointer-events:none;opacity:0;transition:opacity var(--t-fast)}
.sidebar a:hover .tt{opacity:1}
.main{margin-left:4rem;flex:1;padding:2.5rem;max-width:1200px}
.back-link{font-size:.85rem;color:var(--primary);margin-bottom:1rem;display:inline-block;text-decoration:none}

/* ── Reused verbatim from reports.php's own conventions ── */
.section-card{background:var(--background-elevated);border:1px solid var(--border);border-radius:var(--radius-xl);padding:1.5rem;margin-bottom:1.25rem}
.section-card h3{font-size:1rem;font-weight:600;color:var(--foreground);margin-bottom:.75rem}
.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.75rem;margin-bottom:1rem}
.stat-tile{background:var(--background);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1rem;text-align:center;display:flex;flex-direction:column;align-items:center}
.stat-tile .label{font-size:.68rem;font-weight:500;text-transform:uppercase;letter-spacing:.04em;color:var(--foreground-dim)}
.stat-tile .value{font-family:var(--font-mono);font-size:1.3rem;font-weight:600;color:var(--foreground);margin-top:.15rem}
.stat-tile .sub{font-size:.65rem;color:var(--foreground-dim);margin-top:.25rem;line-height:1.35}
.positive{color:var(--positive)}.negative{color:var(--negative)}
.pill{display:inline-flex;align-items:center;padding:.15rem .5rem;border-radius:var(--radius-full);font-size:.65rem;font-weight:600;letter-spacing:.03em;text-transform:uppercase;white-space:nowrap}
.pill-green{background:color-mix(in srgb,var(--positive) 18%,transparent);color:var(--positive);border:1px solid color-mix(in srgb,var(--positive) 30%,transparent)}
.pill-amber{background:color-mix(in srgb,var(--warning) 18%,transparent);color:var(--warning);border:1px solid color-mix(in srgb,var(--warning) 30%,transparent)}
.pill-red{background:color-mix(in srgb,var(--negative) 18%,transparent);color:var(--negative);border:1px solid color-mix(in srgb,var(--negative) 30%,transparent)}
.pill-neutral{background:var(--background-surface);color:var(--foreground-dim);border:1px solid var(--border)}
.empty-state{text-align:center;padding:2.5rem;color:var(--foreground-dim);font-size:.85rem}
.period-tabs{display:flex;gap:.5rem}
.period-tab{padding:.45rem 1.1rem;border-radius:var(--radius-full);font-size:.78rem;font-weight:600;color:var(--foreground-dim);background:var(--background-elevated);border:1px solid var(--border);cursor:pointer;transition:all var(--t-fast);text-decoration:none}
.period-tab:hover{color:var(--foreground);border-color:color-mix(in srgb,var(--foreground) 30%,transparent)}
.period-tab.active{background:var(--primary);color:#fff;border-color:var(--primary)}

/* ── Trader Profile, page-specific layout, calm/premium/breathing room ──
   2026 refinement pass: spacing widened, Character bars and Emotional
   Pattern given more premium presentation. Layout/section order/reading
   flow deliberately untouched, polish only, per explicit instruction. ── */
.tp-header{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:1rem;margin-bottom:2.5rem}
.tp-greeting{font-size:1.9rem;font-weight:700;color:var(--foreground);letter-spacing:-.01em}
.tp-greeting-sub{color:var(--foreground-muted);margin-top:.65rem;font-size:.97rem;max-width:46rem;line-height:1.7}
.tp-period-meta{text-align:right;color:var(--foreground-dim);font-size:.78rem;margin-top:.5rem}

.tp-grid-hero{display:grid;grid-template-columns:1.7fr 1fr;gap:1.5rem;margin-bottom:2rem}
.tp-illustration{border-radius:var(--radius-xl);overflow:hidden;background:linear-gradient(160deg,color-mix(in srgb,var(--primary) 14%,var(--background-elevated)),var(--background-elevated));border:1px solid var(--border);display:flex;align-items:center;justify-content:center;color:var(--foreground-dim);font-size:.8rem;text-align:center}
.tp-illustration-hero{min-height:320px}
.tp-illustration-emotional{min-height:260px;width:100%}
.tp-illustration .tp-illustration-mark{opacity:.35;font-size:2rem;margin-bottom:.5rem}
/* Real artwork fills the same placeholder box exactly, no size/radius/
   spacing change from the approved design; <picture>/<img> just needs to
   behave like a block-level fill instead of centered placeholder text. */
.tp-illustration picture{display:block;width:100%;height:100%}

.tp-identity-card{background:linear-gradient(165deg,color-mix(in srgb,var(--primary) 10%,var(--background-elevated)),var(--background-elevated));border:1px solid var(--border);border-radius:var(--radius-xl);padding:1.85rem;display:flex;flex-direction:column;justify-content:center}
.tp-identity-eyebrow{display:flex;align-items:center;gap:.5rem;font-size:.72rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--foreground-dim);margin-bottom:.85rem}
.tp-identity-name{font-size:1.4rem;font-weight:700;color:var(--primary);margin-bottom:.8rem}
.tp-identity-line{color:var(--foreground-muted);font-size:.9rem;line-height:1.7}

.tp-row3{display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem;margin-bottom:2rem}
.tp-finding-card{border-radius:var(--radius-xl);padding:1.75rem;border:1px solid var(--border)}
.tp-finding-strength{background:color-mix(in srgb,var(--positive) 7%,var(--background-elevated))}
.tp-finding-blindspot{background:color-mix(in srgb,var(--warning) 7%,var(--background-elevated))}
.tp-finding-holdingback{background:color-mix(in srgb,#a78bfa 8%,var(--background-elevated))}
.tp-finding-eyebrow{display:flex;align-items:center;gap:.4rem;font-size:.72rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--foreground-dim);margin-bottom:1rem}
.tp-finding-headline{font-size:1.18rem;font-weight:700;color:var(--foreground);margin-bottom:.65rem;line-height:1.4}
.tp-finding-body{color:var(--foreground-muted);font-size:.86rem;line-height:1.65}
.tp-finding-spark{height:2.2rem;margin-top:.75rem;opacity:.5}

.tp-emotional-row{display:grid;grid-template-columns:1.4fr 1fr;gap:1.5rem;margin-bottom:2rem}
.tp-rung-list{position:relative;padding-left:1.55rem;margin-top:.25rem}
.tp-rung-list::before{content:'';position:absolute;left:.34rem;top:.5rem;bottom:.5rem;width:2px;background:linear-gradient(to bottom,var(--positive),var(--warning) 45%,var(--negative) 65%,var(--positive))}
.tp-rung{position:relative;padding:.9rem 0}
.tp-rung:first-child{padding-top:0}
.tp-rung:last-child{padding-bottom:0}
.tp-rung-dot{position:absolute;left:-1.55rem;top:1.05rem;width:.85rem;height:.85rem;border-radius:50%;flex-shrink:0;box-shadow:0 0 0 3px var(--background-elevated),0 0 0 4px currentColor;background:currentColor}
.tp-rung-dot.baseline{color:var(--positive)}
.tp-rung-dot.steady{color:var(--positive)}
.tp-rung-dot.shifting{color:var(--warning)}
.tp-rung-dot.escalating{color:#F97316}
.tp-rung-dot.breaking{color:var(--negative)}
.tp-rung-dot.reset{color:var(--positive)}
.tp-rung-dot.still_elevated{color:var(--warning)}
.tp-rung-label{font-size:.88rem;font-weight:600;color:var(--foreground)}
.tp-rung-sub{font-size:.77rem;color:var(--foreground-dim);margin-top:.2rem;line-height:1.5}
.tp-pressure-quote{font-style:italic;color:var(--foreground-muted);font-size:.9rem;line-height:1.6;border-left:3px solid var(--primary);padding-left:1rem;margin:1.25rem 0}
.tp-occurrence-note{font-size:.72rem;color:var(--foreground-dim);margin-top:1.25rem;background:var(--background);border:1px solid var(--border);border-radius:var(--radius-md);padding:.7rem .9rem;line-height:1.5}

.tp-condition-row{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:.75rem 0;border-top:1px solid var(--border);font-size:.82rem}
.tp-condition-row:first-child{border-top:none;padding-top:.25rem}
.tp-condition-label{color:var(--foreground);font-weight:500}
.tp-condition-rate{font-family:var(--font-mono);font-weight:600}

.tp-timeline{position:relative;padding-left:1.4rem}
.tp-timeline::before{content:'';position:absolute;left:.28rem;top:.4rem;bottom:.4rem;width:1px;background:var(--border)}
.tp-timeline-item{position:relative;padding-bottom:1.3rem}
.tp-timeline-item:last-child{padding-bottom:0}
.tp-timeline-dot{position:absolute;left:-1.4rem;top:.2rem;width:.55rem;height:.55rem;border-radius:50%;background:var(--border)}
.tp-timeline-item.current .tp-timeline-dot{background:var(--primary)}
.tp-timeline-month{font-size:.72rem;font-weight:600;color:var(--foreground-dim);text-transform:uppercase;letter-spacing:.03em}
.tp-timeline-text{font-size:.85rem;color:var(--foreground);margin-top:.2rem;line-height:1.55}

.tp-bar-row{display:flex;align-items:center;gap:.75rem;margin-top:.4rem}
.tp-bar-track{flex:1;height:10px;border-radius:999px;background:var(--background);overflow:hidden;box-shadow:inset 0 1px 2px rgba(0,0,0,.15)}
.tp-bar-fill{height:100%;border-radius:999px;background:linear-gradient(90deg,color-mix(in srgb,var(--primary) 85%,black),var(--primary));transition:width .3s ease}
.tp-bar-fill.tp-bar-empty{background:var(--border)}
.tp-bar-val{font-family:var(--font-mono);font-size:.82rem;font-weight:700;color:var(--foreground);min-width:1.8rem;text-align:right}
.tp-bar-dim{color:var(--foreground-dim);font-weight:500;min-width:auto}
.tp-bar-note{font-size:.68rem;color:var(--foreground-dim);margin-top:.3rem;cursor:help}
.tp-character-item{margin-bottom:1.6rem}
.tp-character-item:last-child{margin-bottom:0}
.tp-character-item .label{font-size:.84rem;font-weight:600;color:var(--foreground)}

.tp-focus-card{background:linear-gradient(165deg,color-mix(in srgb,var(--primary) 10%,var(--background-elevated)),var(--background-elevated));border:1px solid color-mix(in srgb,var(--primary) 40%,var(--border));border-radius:var(--radius-xl);padding:1.75rem}
.tp-focus-eyebrow{display:flex;align-items:center;gap:.5rem;font-size:.78rem;font-weight:600;color:var(--primary);margin-bottom:1rem}
.tp-focus-item{display:flex;align-items:flex-start;gap:.6rem;font-size:.87rem;color:var(--foreground);margin-bottom:.75rem;line-height:1.55}
.tp-focus-item:last-child{margin-bottom:0}
.tp-focus-check{color:var(--primary);flex-shrink:0;margin-top:.1rem}
.tp-focus-footer{margin-top:1.25rem;padding-top:1rem;border-top:1px solid var(--border);font-size:.78rem;color:var(--foreground-muted);font-style:italic}

/* A Letter to You — each timeframe gets its own quiet, photographic
   backdrop (an envelope/letter photo, see trader-profile-images/ +
   CLAUDE.md's Trader Profile illustration pipeline for how it's
   generated). The photos themselves are warm and fairly bright; the
   readability requirement here is absolute, so a near-opaque dark wash
   (matching this page's own --background/--background-elevated tones,
   just warmed a few degrees rather than pure gray, so the card still
   feels lit by the photo rather than having one painted over it) sits
   between the image and the text on every one. background-color is the
   fallback shown before the image loads or if it fails; background-image
   layers the wash + photo together so nothing needs its own z-index. */
.tp-letter{position:relative;background-color:var(--background-elevated);border:1px solid var(--border);border-radius:var(--radius-xl);padding:2.5rem;margin-bottom:1.25rem;background-size:cover;background-repeat:no-repeat}
.tp-letter-monthly{background-image:linear-gradient(165deg,rgba(10,9,8,.87) 0%,rgba(21,16,9,.82) 48%,rgba(10,9,8,.86) 100%),url('/traderalign/assets/illustrations/monthly-letter.png');background-position:center 42%}
.tp-letter-quarterly{background-image:linear-gradient(165deg,rgba(9,9,10,.82) 0%,rgba(19,15,10,.75) 48%,rgba(9,9,10,.81) 100%),url('/traderalign/assets/illustrations/quarterly-letter.png');background-position:center 48%}
.tp-letter-annual{background-image:linear-gradient(165deg,rgba(8,8,9,.78) 0%,rgba(16,13,10,.68) 48%,rgba(8,8,9,.77) 100%),url('/traderalign/assets/illustrations/annual-letter.png');background-position:center 38%}
@supports (background-image: image-set(url(a) type("image/webp"))) {
    .tp-letter-monthly{background-image:linear-gradient(165deg,rgba(10,9,8,.87) 0%,rgba(21,16,9,.82) 48%,rgba(10,9,8,.86) 100%),image-set(url('/traderalign/assets/illustrations/monthly-letter.webp') type("image/webp"))}
    .tp-letter-quarterly{background-image:linear-gradient(165deg,rgba(9,9,10,.82) 0%,rgba(19,15,10,.75) 48%,rgba(9,9,10,.81) 100%),image-set(url('/traderalign/assets/illustrations/quarterly-letter.webp') type("image/webp"))}
    .tp-letter-annual{background-image:linear-gradient(165deg,rgba(8,8,9,.78) 0%,rgba(16,13,10,.68) 48%,rgba(8,8,9,.77) 100%),image-set(url('/traderalign/assets/illustrations/annual-letter.webp') type("image/webp"))}
}
.tp-letter-eyebrow{display:flex;align-items:center;gap:.5rem;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--foreground-dim);margin-bottom:1.5rem}
.tp-letter-salutation{font-size:1.05rem;color:var(--foreground);margin-bottom:1.25rem;font-weight:600;text-shadow:0 1px 3px rgba(0,0,0,.4)}
.tp-letter-body{font-size:.98rem;color:var(--foreground);line-height:1.95;max-width:42rem;text-shadow:0 1px 3px rgba(0,0,0,.4)}
.tp-letter-body p{margin-bottom:1.15rem}
.tp-letter-sign{margin-top:1.5rem;color:var(--foreground-dim);font-size:.9rem;font-style:italic;text-shadow:0 1px 3px rgba(0,0,0,.4)}

.tp-evidence{margin-top:.95rem;font-size:.74rem}
.tp-evidence summary{cursor:pointer;color:var(--foreground-dim);font-weight:500}
.tp-evidence summary:hover{color:var(--foreground)}
.tp-evidence-body{margin-top:.5rem;padding:.65rem .8rem;background:var(--background);border:1px solid var(--border);border-radius:var(--radius-md);color:var(--foreground-muted);line-height:1.6}

@media(max-width:960px){.tp-grid-hero,.tp-row3,.tp-emotional-row{grid-template-columns:1fr}.main{padding:1.25rem}}
</style>
</head>
<body class="dash-layout">

<?php require __DIR__ . '/sidebar.php'; ?>

<main class="main">
    <a href="/traderalign/dashboard" class="back-link">← Back to Dashboard</a>

    <!-- ═══ 1. Greeting ═══ -->
    <div class="tp-header">
        <div>
            <h1 class="tp-greeting"><?= htmlspecialchars(greetingWord($userTimezone)) ?>, <?= htmlspecialchars($displayName) ?>.</h1>
            <p class="tp-greeting-sub"><?= htmlspecialchars($narr['greeting_subtext']) ?></p>
        </div>
        <div>
            <div class="period-tabs">
                <?php foreach ($periodTabs as $key => $tabLabel): ?>
                    <a href="?period=<?= $key ?>" class="period-tab <?= $period === $key ? 'active' : '' ?>"><?= $tabLabel ?></a>
                <?php endforeach; ?>
            </div>
            <p class="tp-period-meta">Period: <?= $profile['start']->format('M j, Y') ?> to <?= $profile['end']->format('M j, Y') ?></p>
        </div>
    </div>

    <?php if ($profile['total_trades'] === 0): ?>
        <div class="section-card empty-state" style="margin-bottom:1.75rem">No trades closed yet this <?= htmlspecialchars($profile['period_noun']) ?>, but the sections below that look further back in your history will still show what's real so far.</div>
    <?php endif; ?>

    <!-- ═══ 2. Hero illustration + Trading Identity ═══ -->
    <div class="tp-grid-hero">
        <div class="tp-illustration tp-illustration-hero">
            <?php tpIllustrationPicture($heroImg, '(max-width: 960px) 100vw, 750px', 'eager', 'high'); ?>
        </div>
        <div class="tp-identity-card">
            <div class="tp-identity-eyebrow">🛡 Your Trading Identity</div>
            <div class="tp-identity-name"><?= htmlspecialchars($identity['archetype']['name']) ?></div>
            <div class="tp-identity-line"><?= htmlspecialchars($identity['archetype']['line']) ?></div>
        </div>
    </div>

    <!-- ═══ 3. Who You Are Becoming ═══ -->
    <div class="tp-row3">
        <div class="tp-finding-card tp-finding-strength">
            <div class="tp-finding-eyebrow">★ Your Greatest Strength</div>
            <?php if ($narr['strength_card']): ?>
                <div class="tp-finding-headline positive"><?= htmlspecialchars($narr['strength_card']['headline']) ?></div>
                <p class="tp-finding-body"><?= htmlspecialchars($narr['strength_card']['body']) ?></p>
                <?php tpEvidence('Why we think this', htmlspecialchars($identity['strength']['label']) . ' score this ' . htmlspecialchars($profile['period_noun']) . ': <b>' . round($identity['strength']['value']) . '/100</b>.'); ?>
            <?php elseif (!empty($identity['strength_below_floor'])): ?>
                <?php /* §6.4 fix (2026-08-10), distinct from the "not enough trades" message below: there IS enough real data here, it's just that nothing has separated itself as a genuine strength yet (every scored area is below a real positive grade). Reusing the same fabricated-enthusiasm copy as a strong score would be exactly the bug this fix removes. */ ?>
                <p class="tp-finding-body">Nothing has clearly separated itself as a real strength yet this <?= htmlspecialchars($profile['period_noun']) ?>. Every area is still close together, and none of them is standing out. Keep executing and a real strength will emerge.</p>
            <?php else: ?>
                <p class="tp-finding-body">Not enough closed trades yet this <?= htmlspecialchars($profile['period_noun']) ?> to call out a real strength with confidence.</p>
            <?php endif; ?>
        </div>
        <div class="tp-finding-card tp-finding-blindspot">
            <div class="tp-finding-eyebrow">⚠ Your Biggest Blind Spot</div>
            <?php if ($narr['blind_spot_card']): ?>
                <div class="tp-finding-headline" style="color:var(--warning)"><?= htmlspecialchars($narr['blind_spot_card']['headline']) ?></div>
                <p class="tp-finding-body"><?= htmlspecialchars($narr['blind_spot_card']['body']) ?></p>
                <?php
                if ($identity['blind_spot']['type'] === 'risk_after_loss') {
                    $s = $identity['blind_spot']['signal'];
                    // §6.2 fix (2026-08-10), this evidence line always
                    // cited the risk% comparison, even when the flag
                    // fired purely off a classification-rate jump (in
                    // which case the risk% numbers shown here could look
                    // unremarkable, or even show a decrease, next to a
                    // "blind spot" headline about it), now cites
                    // whichever real signal(s) actually triggered.
                    $ralTrigger = $identity['blind_spot']['trigger'] ?? 'risk';
                    if ($ralTrigger === 'c_rate') {
                        tpEvidence('Why we think this', "Based on <b>{$s['baseline_n']}</b> trades with no active losing streak (discipline-flag rate <b>" . number_format($s['baseline_c_rate_pct'], 1) . "%</b>) vs. <b>{$s['post_loss_n']}</b> trades taken right after a loss (discipline-flag rate <b>" . number_format($s['post_loss_c_rate_pct'], 1) . "%</b>).");
                    } else {
                        $evidence = "Based on <b>{$s['baseline_n']}</b> trades with no active losing streak (avg risk <b>" . number_format($s['baseline_avg_risk_pct'], 2) . "%</b>) vs. <b>{$s['post_loss_n']}</b> trades taken right after a loss (avg risk <b>" . number_format($s['post_loss_avg_risk_pct'], 2) . "%</b>).";
                        if ($ralTrigger === 'both') {
                            $evidence .= " Discipline-flag rate also rose, from <b>" . number_format($s['baseline_c_rate_pct'], 1) . "%</b> to <b>" . number_format($s['post_loss_c_rate_pct'], 1) . "%</b>.";
                        }
                        tpEvidence('Why we think this', $evidence);
                    }
                } else {
                    tpEvidence('Why we think this', htmlspecialchars($identity['blind_spot']['label']) . ' score this ' . htmlspecialchars($profile['period_noun']) . ': <b>' . round($identity['blind_spot']['value']) . '/100</b>.');
                }
                ?>
            <?php else: ?>
                <p class="tp-finding-body">Not enough closed trades yet this <?= htmlspecialchars($profile['period_noun']) ?> to identify a real blind spot with confidence.</p>
            <?php endif; ?>
        </div>
        <div class="tp-finding-card tp-finding-holdingback">
            <div class="tp-finding-eyebrow">✕ What's Holding You Back</div>
            <?php if ($narr['holding_back_card']): ?>
                <div class="tp-finding-headline" style="color:#a78bfa"><?= htmlspecialchars($narr['holding_back_card']['headline']) ?></div>
                <p class="tp-finding-body"><?= htmlspecialchars($narr['holding_back_card']['body']) ?></p>
                <?php
                if ($identity['holding_back']['type'] === 'winners_losers') {
                    $s = $identity['holding_back']['signal'];
                    tpEvidence('Why we think this', "Based on <b>{$s['winners_n']}</b> winning trades (avg <b>" . round($s['avg_winner_capture_ratio'] * 100) . "%</b> of planned target captured) and <b>{$s['losers_n']}</b> losing trades (avg <b>" . round($s['avg_loser_realization_ratio'] * 100) . "%</b> of planned stop realized).");
                } else {
                    tpEvidence('Why we think this', htmlspecialchars($identity['holding_back']['label']) . ' score this ' . htmlspecialchars($profile['period_noun']) . ': <b>' . round($identity['holding_back']['value']) . '/100</b>.');
                }
                ?>
            <?php else: ?>
                <p class="tp-finding-body">Nothing specific enough to call out yet, keep journaling and this will fill in.</p>
            <?php endif; ?>
        </div>
    </div>

    <!-- ═══ 4. Your Emotional Pattern ═══ -->
    <div class="section-card" style="margin-bottom:1.25rem">
        <h3>💗 Your Emotional Pattern</h3>
        <p class="tp-finding-body" style="margin-bottom:1rem" title="<?= tpCoach('emotional.section') ?>">This isn't from a mood log, it's what actually happened to your risk sizing and trade quality as losses stacked up, trade by trade, across your real history. ⓘ</p>
        <?php if (!empty($profile['emotional_pattern']['available'])): ?>
        <div class="tp-emotional-row">
            <div>
                <div class="tp-rung-list">
                    <?php foreach ($profile['emotional_rungs'] as $rung): ?>
                        <div class="tp-rung">
                            <div class="tp-rung-dot <?= htmlspecialchars($rung['tier']) ?>"></div>
                            <div>
                                <div class="tp-rung-label"><?= htmlspecialchars($rung['label']) ?></div>
                                <div class="tp-rung-sub"><?= htmlspecialchars($rung['sub']) ?> (<?= $rung['n'] ?> trade<?= $rung['n'] === 1 ? '' : 's' ?><?= $rung['avg_risk_pct'] !== null ? ', avg risk ' . number_format($rung['avg_risk_pct'], 2) . '%' : '' ?>)</div>
                            </div>
                        </div>
                    <?php endforeach; ?>
                </div>
                <p class="tp-occurrence-note" title="<?= tpCoach('emotional.occurrences') ?>">This pattern appeared <b><?= $profile['emotional_pattern']['occurrences'] ?></b> times across your last <?= $profile['emotional_pattern']['lookback_trades'] ?> closed trades. ⓘ</p>
            </div>
            <div>
                <div class="tp-illustration tp-illustration-emotional">
                    <?php tpIllustrationPicture($emotionImg, '(max-width: 960px) 100vw, 450px', 'lazy'); ?>
                </div>
                <?php
                $pressureQuote = TraderProfileNarrative::pick([
                    "Nothing changes in the market. Only you change.",
                    "The market didn't get harder after your last loss. Something in you did.",
                ], $userId . 'pressure');
                ?>
                <p class="tp-pressure-quote">"<?= htmlspecialchars($pressureQuote) ?>"</p>
            </div>
        </div>
        <?php else: ?>
            <div class="empty-state">Not enough losing streaks in your history yet to map a real pattern here (found <?= $profile['emotional_pattern']['occurrences'] ?? 0 ?> so far, we look for at least 3). Keep journaling, this fills in with more history.</div>
        <?php endif; ?>
    </div>

    <!-- ═══ 5. When You Trade Best ═══ -->
    <div class="section-card" style="margin-bottom:1.25rem">
        <h3>⏱ When You Trade Best</h3>
        <p class="tp-finding-body" style="margin-bottom:.5rem" title="<?= tpCoach('best_conditions.section') ?>">Only real, meaningfully-different conditions show up here, nothing generic. ⓘ</p>
        <?php if (!empty($profile['best_conditions'])): ?>
            <?php foreach ($profile['best_conditions'] as $c): ?>
                <div class="tp-condition-row">
                    <span class="tp-condition-label"><?= $c['delta_pp'] > 0 ? '✓' : '✕' ?> <?= htmlspecialchars($c['label']) ?></span>
                    <span class="tp-condition-rate <?= $c['delta_pp'] > 0 ? 'positive' : 'negative' ?>">Win rate: <?= round($c['win_rate']) ?>%</span>
                </div>
            <?php endforeach; ?>
            <?php
            $worstCondition = null;
            foreach ($profile['best_conditions'] as $c) { if ($c['delta_pp'] < 0 && ($worstCondition === null || $c['delta_pp'] < $worstCondition['delta_pp'])) $worstCondition = $c; }
            if ($worstCondition):
            ?>
            <p class="tp-occurrence-note"><?= htmlspecialchars(TraderProfileNarrative::worstConditionSentence($worstCondition)) ?></p>
            <?php endif; ?>
        <?php elseif (!empty($profile['best_conditions_well_sampled'])): ?>
            <!-- §6.7 fix (2026-08-10), a real, well-sampled account with
                 no meaningfully-different condition is a genuine finding
                 (consistent performance across session/frequency/risk
                 band), not a data shortage, the old single empty-state
                 message claimed the opposite regardless of which was true. -->
            <div class="empty-state">Your performance has been genuinely consistent across sessions, trade frequency, and risk bands this <?= htmlspecialchars($profile['period_noun']) ?>. No single condition stands out as meaningfully better or worse than your overall average.</div>
        <?php else: ?>
            <div class="empty-state">Still gathering enough trades this <?= htmlspecialchars($profile['period_noun']) ?> to find your genuinely best conditions, nothing here yet is a big enough sample to trust.</div>
        <?php endif; ?>
    </div>

    <!-- ═══ 6 & 7. Growth Timeline + Trading Character ═══ -->
    <div class="tp-emotional-row">
        <div class="section-card" style="margin-bottom:0">
            <h3>📈 Your Growth Timeline</h3>
            <p class="tp-finding-body" style="margin-bottom:1rem" title="<?= tpCoach('growth_timeline.section') ?>">Look how far you've come: real behavioral changes, not achievements. ⓘ</p>
            <?php if (!empty($profile['growth_timeline']['available'])): ?>
                <div class="tp-timeline">
                    <?php foreach ($profile['growth_entries'] as $e): ?>
                        <div class="tp-timeline-item">
                            <div class="tp-timeline-dot"></div>
                            <div class="tp-timeline-month"><?= htmlspecialchars($e['month_label']) ?></div>
                            <div class="tp-timeline-text"><?= htmlspecialchars($e['line']) ?></div>
                        </div>
                    <?php endforeach; ?>
                    <?php
                    $cm = $profile['growth_timeline']['current_month'];
                    // §6.5 fix (2026-08-10), $cm is getGrowthTimeline()'s
                    // last QUALIFYING month within its lookback window
                    // (MIN_MONTH_TRADES+ trades), not necessarily this real
                    // calendar month, a trader who's gone quiet can have a
                    // real, several-months-old "current_month" here.
                    // "Today" only when it genuinely is; otherwise the node
                    // is honestly labeled with its own real month.
                    $isCurrentCalMonth = ($cm['ym'] === date('Y-m'));
                    ?>
                    <div class="tp-timeline-item current">
                        <div class="tp-timeline-dot"></div>
                        <div class="tp-timeline-month"><?= $isCurrentCalMonth ? 'Today' : htmlspecialchars($cm['month_label']) . ' (Most Recent)' ?></div>
                        <div class="tp-timeline-text">
                            <?php
                            $nf = $identity['next_focus'] ?? null;
                            if ($nf):
                                $nfLabel = match ($nf['type']) {
                                    'risk_after_loss' => 'staying calm and steady after a loss',
                                    'winners_losers' => 'letting your winners run to plan',
                                    'character' => strtolower($nf['label']),
                                    default => 'building consistency',
                                };
                            ?>
                                <!-- §6.5 fix (2026-08-10), $identity['next_focus'] is
                                     scoped to the page's currently-selected report
                                     period (Monthly/Quarterly/Annual), a genuinely
                                     different scope than this timeline node's own
                                     calendar month above, now disclosed rather than
                                     silently merged under one implied "today" scope. -->
                                Your next area of growth, based on your <?= htmlspecialchars($profile['period_noun']) ?> view: <?= htmlspecialchars($nfLabel) ?>.
                            <?php else: ?>
                                Keep journaling, your current focus will show up here as more trades close.
                            <?php endif; ?>
                        </div>
                    </div>
                </div>
            <?php else: ?>
                <div class="empty-state">Not enough monthly history yet (need at least 3 months with real trading activity) to show a real growth timeline.</div>
            <?php endif; ?>
        </div>
        <div class="section-card" style="margin-bottom:0">
            <h3>🧭 Trading Character</h3>
            <p class="tp-finding-body" style="margin-bottom:1rem">How you show up in the market, not your P&L.</p>
            <?php foreach ($char as $key => $c): ?>
                <div class="tp-character-item">
                    <div class="label"><?= htmlspecialchars($c['label']) ?></div>
                    <?php tpBar($c['value'], $c['tier'], 'character.' . $key); ?>
                </div>
            <?php endforeach; ?>
        </div>
    </div>

    <!-- ═══ 8. Before Your Next Trade ═══ -->
    <div class="tp-focus-card" style="margin-bottom:1.25rem">
        <div class="tp-focus-eyebrow">◎ Before Your Next Trade: a few reminders for you</div>
        <?php foreach ($narr['next_trade_reminders'] as $reminder): ?>
            <div class="tp-focus-item"><span class="tp-focus-check">✓</span><span><?= htmlspecialchars($reminder) ?></span></div>
        <?php endforeach; ?>
        <div class="tp-focus-footer" title="<?= tpCoach('next_trade.section') ?>">Do this, not for the profit. Do this to become your best version. ⓘ</div>
    </div>

    <!-- ═══ 9. A Letter to You ═══ Monthly excluded on purpose, per explicit
         instruction (2026-08-20): a month is too short a horizon for a
         reflective letter to feel meaningful rather than like noise — the
         letter only earns its place once a real quarter or year of
         distance has passed. Only the view is gated here; the underlying
         $profile['letter'*] computation in TraderProfileService::build()
         (including the letter grace-window carryover, which currently has
         nothing to do with which tab is active) is left completely
         untouched, per the same instruction not to alter any other data
         or functionality. -->
    <?php if ($period !== 'monthly'): ?>
    <div class="tp-letter tp-letter-<?= htmlspecialchars($period) ?>">
        <div class="tp-letter-eyebrow">✉ A Letter to You</div>
        <?php if ($profile['letter_in_grace_window']): ?>
            <p class="tp-occurrence-note" style="margin-bottom:1.1rem">This letter is about <b><?= htmlspecialchars($profile['letter_period_label']) ?></b>, which just wrapped up. It stays here until <b><?= htmlspecialchars($profile['letter_grace_deadline']->format('M j, g:i A')) ?></b>, giving you a real window to read it before <?= htmlspecialchars($profile['period_noun']) ?>'s own letter takes over.</p>
        <?php endif; ?>
        <p class="tp-letter-salutation">Dear <?= htmlspecialchars($displayName) ?>,</p>
        <div class="tp-letter-body">
            <?php foreach ($profile['letter'] as $line): ?>
                <p><?= htmlspecialchars($line) ?></p>
            <?php endforeach; ?>
        </div>
        <p class="tp-letter-sign">- TraderAlign</p>
    </div>
    <?php endif; ?>

</main>
</body>
</html>
