<?php
// dashboard.php — restore header/search layout; keep FILE-parity list & AES decrypt
session_start();
include 'db.php';
use MongoDB\BSON\UTCDateTime;

/** bytes -> human readable */
function format_bytes($bytes, $precision = 2){
    if ($bytes > 0){
        $i = floor(log($bytes, 1024));
        $sizes = ['B','KB','MB','GB','TB'];
        return sprintf('%.' . (int)$precision . 'f %s', $bytes / pow(1024,$i), $sizes[$i]);
    }
    return '0 B';
}

$stats = [
    'db_size' => 'N/A',
    'db_name' => isset($dbname) ? $dbname : 'N/A',
    'tables' => [
        'REPOSITORY' => ['count'=>0],
        'PROJECT'    => ['count'=>0],
        'COMPONENT'  => ['count'=>0],
        'FILE'       => ['count'=>0],
    ],
];
$error_message = null;
$search_query = isset($_GET['search_query']) ? trim((string)$_GET['search_query']) : '';
$file_results = [];

try {
    // db stats & counts
    try {
        $dbStats = $db->command(['dbStats'=>1])->toArray()[0];
        if (isset($dbStats['dataSize'])) $stats['db_size'] = format_bytes((int)$dbStats['dataSize']);
    } catch (\Throwable $e) { /* ignore */ }

    $stats['tables']['REPOSITORY']['count'] = $db->REPOSITORY->countDocuments([]);
    $stats['tables']['PROJECT']['count']    = $db->PROJECT->countDocuments([]);
    $stats['tables']['COMPONENT']['count']  = $db->COMPONENT->countDocuments([]);
    $stats['tables']['FILE']['count']       = $db->FILE->countDocuments([]);

    // pipeline for files
    $pipeline = [];
    if ($search_query !== ''){
        $kw = preg_split('/\s+/', $search_query, -1, PREG_SPLIT_NO_EMPTY);
        $and = [];
        foreach ($kw as $k){ $and[] = ['FILE_NAME' => new MongoDB\BSON\Regex($k,'i')]; }
        if ($and) $pipeline[] = ['$match' => ['$and'=>$and]];
    }
    $pipeline[] = ['$sort'=>['CREATED_AT'=>-1]];
    $pipeline[] = ['$limit'=>200];

    $pipeline[] = ['$lookup'=>[
        'from'=>'COMPONENT','localField'=>'COMPONENT_ID','foreignField'=>'COMPONENT_ID','as'=>'component_docs'
    ]];
    $pipeline[] = ['$unwind'=>'$component_docs'];
    $pipeline[] = ['$lookup'=>[
        'from'=>'PROJECT','localField'=>'component_docs.PROJECT_ID','foreignField'=>'PROJECT_ID','as'=>'project_docs'
    ]];
    $pipeline[] = ['$unwind'=>'$project_docs'];
    $pipeline[] = ['$lookup'=>[
        'from'=>'REPOSITORY','localField'=>'project_docs.REPOSITORY_ID','foreignField'=>'REPOSITORY_ID','as'=>'repository_docs'
    ]];
    $pipeline[] = ['$unwind'=>'$repository_docs'];

    $pipeline[] = ['$project'=>[
        '_id'=>0,
        'FILE_ID'           => '$_id',
        'FILE_NAME'         => '$FILE_NAME',
        'CODE'              => '$CODE',
        'CREATED_AT'        => '$CREATED_AT',
        'REPOSITORY_ID'     => '$repository_docs._id',
        'REPOSITORY_NAME'   => '$repository_docs.REPOSITORY_NAME',
        'PROJECT_ID'        => '$project_docs._id',
        'PROJECT_NAME'      => '$project_docs.PROJECT_NAME',
        'COMPONENT_ID'      => '$component_docs._id',
        'COMPONENT_NAME'    => '$component_docs.COMPONENT_NAME',
    ]];

    $file_results = $db->FILE->aggregate($pipeline)->toArray();

} catch (\Throwable $e){
    $error_message = '数据库查询失败：' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
}

// SVG Icons (Using the original ones for consistency)
$icons = [
    'db_size'    => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>',
    'REPOSITORY' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line></svg>',
    'PROJECT'    => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>',
    'COMPONENT'  => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>',
    'FILE'       => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>'
];
?>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Dashboard</title>
<style>
/* === RESTORED STYLES START === */
:root {
    --bg-color: #090a0f; --panel-bg-color: rgba(26, 26, 26, 0.6); --border-color: rgba(255, 255, 255, 0.1);
    --text-primary: #f0f0f0; --text-secondary: #a0a0a0; --accent-color: #00aaff;
    --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --border-radius: 16px; /* Renamed from --radius for consistency */
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
    background-color: transparent; color: var(--text-primary); font-family: var(--font-family);
    padding: 1.5rem; animation: fadeIn 0.5s ease-out forwards;
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

.section-title {
    font-size: 1.2em; font-weight: 600; letter-spacing: -0.5px; margin: 0 0 1.5rem 0;
    padding-bottom: 0.75rem; border-bottom: 1px solid var(--border-color); color: var(--text-primary);
}

.search-container { margin-bottom: 2.5rem; } /* Increased bottom margin */
.search-form { display: flex; gap: 0.75rem; }
.search-form input {
    flex-grow: 1; padding: 0.75rem 1rem; font-size: 1em; background-color: rgba(0, 0, 0, 0.3);
    color: var(--text-primary); border: 1px solid var(--border-color); border-radius: 12px;
    transition: border-color 0.3s, box-shadow 0.3s;
}
.search-form input:focus { outline: none; border-color: var(--accent-color); box-shadow: 0 0 10px rgba(0, 170, 255, 0.5); }
.search-form button {
    padding: 0.75rem 1.5rem; font-size: 1em; font-weight: 600; cursor: pointer;
    background-color: #222E3C; color: white; border: none; border-radius: 12px;
    transition: background-color 0.3s, transform 0.2s;
}
.search-form button:hover { background-color: #2E3B4A; transform: translateY(-2px); }

.compact-summary-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1.5rem;
    margin-bottom: 2.5rem;
    background-color: var(--panel-bg-color);
    border: 1px solid var(--border-color);
    border-radius: var(--border-radius);
    padding: 1.5rem;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}
.summary-item { display: flex; align-items: center; gap: 1rem; }
.summary-item svg { width: 32px; height: 32px; color: var(--accent-color); flex-shrink: 0; }
.summary-item div { display: flex; flex-direction: column; }
.summary-value { font-size: 1.6em; font-weight: 600; line-height: 1.1; color: var(--text-primary); }
.summary-label { font-size: 0.9em; color: var(--text-secondary); }
.error-message { padding: 2rem; text-align: center; background-color: rgba(255, 0, 0, 0.1); border: 1px solid rgba(255, 0, 0, 0.3); color: #ffcccc; border-radius: var(--border-radius); }
/* === RESTORED STYLES END === */


/* === UNTOUCHED LIST STYLES START === */
.file-list-container{background:var(--panel-bg-color);border:1px solid var(--border-color);border-radius:var(--border-radius);padding:1rem;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);}
.file-list-header,.file-item{display:grid;grid-template-columns:60px 1fr 3.5fr 160px;gap:1rem;align-items:center;padding:.75rem 1rem;border-bottom:1px solid var(--border-color);}
.file-list-header{font-weight:600;color:var(--text-secondary);text-transform:uppercase;font-size:.85em;letter-spacing:.5px;}
.file-item:last-child{border-bottom:none;}
.file-item:hover{background:rgba(0,170,255,.08);}
.file-id{font-family:'SF Mono','Fira Code',monospace;font-size:.92em;color:#66d9ff;}
.file-path{display:flex;align-items:center;gap:.25rem;flex-wrap:wrap;font-size:.95em;word-break:break-all;}
.file-path a{text-decoration:none}
.file-path .repo{color:#66d9ff;}
.file-path .proj{color:#a6e22e;}
.file-path .comp{color:#ff79c6;}
.file-path .name{color:var(--text-primary);font-weight:500;}
.file-path .sep{color:var(--text-secondary);margin:0 2px;}
.code-content{white-space:pre-wrap;word-break:break-word;cursor:pointer;max-height:200px;overflow-y:auto;padding:.5rem;border-radius:8px;background:rgba(0,0,0,.25);border:1px solid var(--border-color);font-family:'SF Mono','Fira Code',monospace;font-size:.9em;}
.code-content.highlighted{background:rgba(0,170,255,.2);}
.code-content::-webkit-scrollbar{width:8px;}
.code-content::-webkit-scrollbar-track{background:rgba(0,0,0,.2);border-radius:4px;}
.code-content::-webkit-scrollbar-thumb{background-color:var(--accent-color);border-radius:4px;border:2px solid transparent;background-clip:content-box;}
.file-timestamp{font-size:.9em;color:var(--text-secondary);cursor:context-menu;}
.no-results{text-align:center;padding:2.5rem;color:var(--text-secondary);}

/* Right-click menu & decrypt modal (does not affect form layout) */
.context-menu{display:none;position:absolute;z-index:1000;background:#1e1e1e;border:1px solid var(--border-color);border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.5);padding:8px;min-width:150px;}
.context-menu.show{display:block;}
.context-menu-item{padding:10px 15px;color:var(--text-primary);cursor:pointer;font-size:.95em;border-radius:8px;}
.context-menu-item:hover{background:rgba(0,170,255,.15);}
.context-menu-item.delete{color:#E74C3C;}
.context-menu-item.delete:hover{background:rgba(231,76,60,.2);color:#ff8a80;}

.pw-modal-backdrop{position:fixed;inset:0;display:none;align-items:flex-start;justify-content:center;padding-top:10px;z-index:2000;background:rgba(0,0,0,.55);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);}
.pw-modal-backdrop.show{display:flex;}
.pw-modal{width:min(520px,94vw);background:#111926;border:1px solid var(--border-color);border-radius:16px;color:var(--text-primary);padding:22px;box-shadow:0 10px 40px rgba(0,0,0,.65);}
.pw-title{font-size:1.1rem;font-weight:700;margin-bottom:6px;}
.pw-desc{font-size:.92rem;color:var(--text-secondary);margin-bottom:14px;}
.pw-input{width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--border-color);background:rgba(0,0,0,.3);color:var(--text-primary);}
.pw-actions{display:flex;gap:10px;margin-top:14px;}
.pw-btn{flex:1;padding:10px 12px;border:none;border-radius:10px;background:#222E3C;color:white;font-weight:600;cursor:pointer;}
.pw-btn.secondary{background:#2E3B4A;}
.pw-error{color:#ff8a80;display:none;margin-top:8px;font-size:.85rem;}
/* === UNTOUCHED LIST STYLES END === */
</style>
</head>
<body>

<?php if ($error_message): ?>
    <div class="error-message"><?php echo $error_message; ?></div>
<?php else: ?>

    <!-- === RESTORED HTML STRUCTURE START === -->
    <div class="search-container">
        <h2 class="section-title">Enterprise Search &amp; Audit Log</h2>
        <form class="search-form" method="get" action="dashboard.php">
            <input type="text" name="search_query" placeholder="通过多个关键词搜索文件名..." value="<?php echo htmlspecialchars($search_query, ENT_QUOTES, 'UTF-8'); ?>">
            <button type="submit">搜索</button>
        </form>
    </div>

    <div class="compact-summary-grid">
        <div class="summary-item">
            <?php echo $icons['db_size']; ?>
            <div>
                <span class="summary-value"><?php echo htmlspecialchars($stats['db_size'], ENT_QUOTES, 'UTF-8'); ?></span>
                <span class="summary-label">Database: <?php echo htmlspecialchars($stats['db_name'], ENT_QUOTES, 'UTF-8'); ?></span>
            </div>
        </div>
        <div class="summary-item">
            <?php echo $icons['REPOSITORY']; ?>
            <div>
                <span class="summary-value"><?php echo htmlspecialchars($stats['tables']['REPOSITORY']['count'], ENT_QUOTES, 'UTF-8'); ?></span>
                <span class="summary-label">Repositories</span>
            </div>
        </div>
        <div class="summary-item">
            <?php echo $icons['PROJECT']; ?>
            <div>
                <span class="summary-value"><?php echo htmlspecialchars($stats['tables']['PROJECT']['count'], ENT_QUOTES, 'UTF-8'); ?></span>
                <span class="summary-label">Projects</span>
            </div>
        </div>
        <div class="summary-item">
            <?php echo $icons['COMPONENT']; ?>
            <div>
                <span class="summary-value"><?php echo htmlspecialchars($stats['tables']['COMPONENT']['count'], ENT_QUOTES, 'UTF-8'); ?></span>
                <span class="summary-label">Components</span>
            </div>
        </div>
        <div class="summary-item">
            <?php echo $icons['FILE']; ?>
            <div>
                <span class="summary-value"><?php echo htmlspecialchars($stats['tables']['FILE']['count'], ENT_QUOTES, 'UTF-8'); ?></span>
                <span class="summary-label">Files</span>
            </div>
        </div>
    </div>
    <!-- === RESTORED HTML STRUCTURE END === -->


    <!-- === UNTOUCHED LIST AREA START === -->
    <div class="file-list-container">
        <div class="file-list-header">
            <div>ID</div><div>文件路径</div><div>代码内容</div><div>时间戳</div>
        </div>

        <?php if (!empty($file_results)): foreach ($file_results as $row):
            $repoIdStr = isset($row['REPOSITORY_ID']) ? (string)$row['REPOSITORY_ID'] : '';
            $projIdStr = isset($row['PROJECT_ID']) ? (string)$row['PROJECT_ID'] : '';
            $compIdStr = isset($row['COMPONENT_ID']) ? (string)$row['COMPONENT_ID'] : '';
            $fileIdStr = isset($row['FILE_ID']) ? (string)$row['FILE_ID'] : '';

            $enc_payload = '';
            if (isset($row['CODE']) && $row['CODE'] instanceof \MongoDB\BSON\Binary){
                $enc_payload = str_replace(["\r","\n"], '', $row['CODE']->getData());
            } elseif (is_string($row['CODE'] ?? null)){
                $enc_payload = trim((string)$row['CODE']);
            }

            $repo_link = "PROJECT.php?repository_id=" . urlencode($repoIdStr);
            $proj_link = "COMPONENT.php?repository_id=" . urlencode($repoIdStr) . "&project_id=" . urlencode($projIdStr);
            $comp_link = "FILE.php?repository_id=" . urlencode($repoIdStr) . "&project_id=" . urlencode($projIdStr) . "&component_id=" . urlencode($compIdStr);
        ?>
        <div class="file-item">
            <div class="file-id"><?= htmlspecialchars(substr($fileIdStr,-6),ENT_QUOTES,'UTF-8') ?></div>
            <div class="file-path">
                <a class="repo" href="<?= $repo_link ?>" target="contentFrame"><?= htmlspecialchars($row['REPOSITORY_NAME'] ?? '',ENT_QUOTES,'UTF-8') ?></a><span class="sep">/</span>
                <a class="proj" href="<?= $proj_link ?>" target="contentFrame"><?= htmlspecialchars($row['PROJECT_NAME'] ?? '',ENT_QUOTES,'UTF-8') ?></a><span class="sep">/</span>
                <a class="comp" href="<?= $comp_link ?>" target="contentFrame"><?= htmlspecialchars($row['COMPONENT_NAME'] ?? '',ENT_QUOTES,'UTF-8') ?></a><span class="sep">/</span>
                <span class="name"><?= htmlspecialchars($row['FILE_NAME'] ?? '',ENT_QUOTES,'UTF-8') ?></span>
            </div>
            <div class="code-content" data-enc="<?= htmlspecialchars($enc_payload,ENT_QUOTES,'UTF-8') ?>" onclick="copyToClipboard(this)">•••• encrypted ••••</div>
            <div class="file-timestamp" oncontextmenu="showContextMenu(event,'<?= htmlspecialchars($fileIdStr,ENT_QUOTES,'UTF-8') ?>')">
                <?php
                if (isset($row['CREATED_AT']) && $row['CREATED_AT'] instanceof UTCDateTime){
                    $dt = $row['CREATED_AT']->toDateTime();
                    $dt->setTimezone(new DateTimeZone(date_default_timezone_get()));
                    echo $dt->format('Y-m-d H:i:s');
                }
                ?>
            </div>
        </div>
        <?php endforeach; else: ?>
            <div class="no-results"><?= $search_query !== '' ? '未找到匹配结果。' : '暂无数据。' ?></div>
        <?php endif; ?>
    </div>
    <!-- === UNTOUCHED LIST AREA END === -->
<?php endif; ?>

<!-- === UNTOUCHED MODALS & JS START === -->
<div id="pwBackdrop" class="pw-modal-backdrop" aria-hidden="true">
    <div class="pw-modal" role="dialog" aria-modal="true" aria-labelledby="pwTitle">
        <div id="pwTitle" class="pw-title">Enter decryption password</div>
        <div class="pw-desc">The password is required to decrypt stored code for this session.</div>
        <input id="pwInput" class="pw-input" type="password" placeholder="Password" autocomplete="new-password">
        <div class="pw-actions">
            <button id="pwConfirm" class="pw-btn">Continue</button>
            <button id="pwCancel" class="pw-btn secondary" type="button">Later</button>
        </div>
        <div id="pwError" class="pw-error">Password required.</div>
    </div>
</div>

<div id="contextMenu" class="context-menu">
    <div id="deleteMenuItem" class="context-menu-item delete">Delete</div>
</div>

<script>
// copy (left click)
function copyToClipboard(el){
    if(!el) return;
    const range=document.createRange(); range.selectNodeContents(el);
    const sel=window.getSelection(); sel.removeAllRanges(); sel.addRange(range);
    try{ document.execCommand('copy'); }catch(e){}
    sel.removeAllRanges();
    el.classList.add('highlighted'); setTimeout(()=>el.classList.remove('highlighted'), 350);
}

// context menu
const contextMenu=document.getElementById('contextMenu');
const deleteMenuItem=document.getElementById('deleteMenuItem');
function showContextMenu(e,fileId){
    e.preventDefault();
    contextMenu.style.top=e.pageY+'px'; contextMenu.style.left=e.pageX+'px';
    contextMenu.classList.add('show');
    deleteMenuItem.onclick=()=>{ contextMenu.classList.remove('show'); window.location.href='removefile.php?file_id='+encodeURIComponent(fileId); };
}
window.addEventListener('click',()=>{ if(contextMenu.classList.contains('show')) contextMenu.classList.remove('show'); });

// AES-GCM decrypt (matches FILE: PBKDF2 100000)
const enc=new TextEncoder(), dec=new TextDecoder();
function b64decode(str){ const bin=atob((str||'').replace(/[\r\n\s]/g,'')); const out=new Uint8Array(bin.length); for(let i=0;i<bin.length;i++) out[i]=bin.charCodeAt(i); return out.buffer; }
async function deriveKey(pw,salt){
    const km=await crypto.subtle.importKey('raw',enc.encode(pw),'PBKDF2',false,['deriveKey']);
    return crypto.subtle.deriveKey({name:'PBKDF2',salt,iterations:100000,hash:'SHA-256'}, km, {name:'AES-GCM',length:256}, false, ['encrypt','decrypt']);
}
async function aesDecrypt(packet,password){
    if(typeof packet!=='string'||!packet.startsWith('v1$')) throw new Error('bad packet');
    const parts=packet.split('$'); if(parts.length!==4) throw new Error('bad format');
    const salt=new Uint8Array(b64decode(parts[1]));
    const iv  =new Uint8Array(b64decode(parts[2]));
    const ct  =new Uint8Array(b64decode(parts[3]));
    const key =await deriveKey(password,salt);
    const pt  =await crypto.subtle.decrypt({name:'AES-GCM',iv},key,ct);
    return dec.decode(pt);
}

// session password (once only)
const PW_KEY='code_pw_session';
const pwBackdrop=document.getElementById('pwBackdrop');
const pwInput=document.getElementById('pwInput');
const pwConfirm=document.getElementById('pwConfirm');
const pwCancel=document.getElementById('pwCancel');
const pwError=document.getElementById('pwError');
function havePassword(){ return !!sessionStorage.getItem(PW_KEY); }
function getPassword(){ return sessionStorage.getItem(PW_KEY) || ''; }
function openPwModal(){ pwBackdrop.classList.add('show'); pwBackdrop.setAttribute('aria-hidden','false'); setTimeout(()=>{ try{ pwInput.focus(); }catch(_){} },0); }
function closePwModal(){ pwBackdrop.classList.remove('show'); pwBackdrop.setAttribute('aria-hidden','true'); }

pwConfirm && pwConfirm.addEventListener('click',()=>{
    const v=pwInput.value.trim();
    if(!v){ pwError.style.display='block'; return; }
    pwError.style.display='none';
    sessionStorage.setItem(PW_KEY,v);
    closePwModal(); decryptAll();
});
pwCancel && pwCancel.addEventListener('click',()=>{ closePwModal(); });

async function decryptAll(){
    const blocks=document.querySelectorAll('.code-content');
    const pw=getPassword();
    for(const el of blocks){
        const packet=el.getAttribute('data-enc')||'';
        if(!packet){ el.textContent=''; continue; }
        try{ el.textContent = await aesDecrypt(packet,pw); }
        catch(e){ el.textContent='•••• encrypted ••••'; }
    }
}
window.addEventListener('load',()=>{
    const hasEnc=document.querySelector('.code-content[data-enc]')!==null;
    if(hasEnc && !havePassword()) openPwModal();
    else if(hasEnc) decryptAll();
});
</script>
<!-- === UNTOUCHED MODALS & JS END === -->

</body>
</html>
