Files
xReleaseAsistant/BKP/xDependencies_1.4.21.html
T
saherelm 18fe431973 - add backups.
- add workspace.
- complete galaxy chart Search.
2026-09-17 20:38:08 +03:30

1184 lines
52 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>مدیریت محصولات</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--primary-dark: #0f2341;
--accent: #c9a961;
--accent-dark: #a8893f;
--accent-light: #e0c789;
--bg: #f5f7fa;
--board-bg: #e2e8f0;
--card-bg: #ffffff;
--panel-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--text-light: #718096;
--border: #cbd5e0;
--border-light: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--info: #3182ce;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.08), 0 10px 20px rgba(26, 54, 93, 0.1);
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.15);
--radius: 10px;
--radius-sm: 6px;
--sidebar-width: 280px;
--header-height: 64px;
--tabs-height: 48px;
--footer-height: 36px;
}
[data-theme="dark"] {
--primary: #2c5282;
--primary-light: #3b6fb5;
--primary-dark: #1a365d;
--accent: #e0c789;
--accent-dark: #c9a961;
--accent-light: #f0d9a0;
--bg: #1a202c;
--board-bg: #2d3748;
--card-bg: #2d3748;
--panel-bg: #1f2937;
--text: #e2e8f0;
--text-muted: #cbd5e0;
--text-light: #a0aec0;
--border: #4a5568;
--border-light: #374151;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.4);
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.2);
--shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.5);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
height: 100vh;
overflow: hidden;
font-size: 14px;
display: flex;
flex-direction: column;
transition: background 0.3s, color 0.3s;
}
.app-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 0 20px;
height: var(--header-height);
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 3px solid var(--accent);
box-shadow: var(--shadow);
z-index: 100;
flex-shrink: 0;
position: relative;
overflow: hidden;
}
.app-header::before {
content: '';
position: absolute;
top: -50%;
left: -10%;
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(201, 169, 97, 0.15) 0%, transparent 70%);
border-radius: 50%;
pointer-events: none;
}
.app-brand { display: flex; align-items: center; gap: 14px; position: relative; z-index: 1; }
.app-logo {
width: 44px; height: 44px; background: var(--accent); border-radius: 10px;
display: flex; align-items: center; justify-content: center; font-size: 22px;
font-weight: 800; color: var(--primary-dark); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); flex-shrink: 0;
}
.app-brand-text { display: flex; flex-direction: column; gap: 2px; }
.app-title { font-size: 16px; font-weight: 800; letter-spacing: -0.3px; color: white; }
.app-subtitle { font-size: 11px; opacity: 0.85; color: rgba(255, 255, 255, 0.9); }
.btn {
background: linear-gradient(135deg, var(--primary-light), var(--primary));
color: white; border: none; padding: 8px 14px; border-radius: var(--radius-sm);
cursor: pointer; font-family: inherit; font-size: 13px; font-weight: 600;
display: inline-flex; align-items: center; gap: 6px; transition: all 0.2s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); white-space: nowrap;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); filter: brightness(1.1); }
.btn:active { transform: translateY(0); }
.btn-accent { background: linear-gradient(135deg, var(--accent), var(--accent-dark)); color: var(--primary-dark); font-weight: 700; }
.btn-ghost { background: rgba(26, 54, 93, 0.08); border: 1px solid var(--border); color: var(--primary); font-weight: 600; }
.btn-ghost:hover { background: rgba(26, 54, 93, 0.15); border-color: var(--primary-light); }
[data-theme="dark"] .btn-ghost { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.25); color: white; }
[data-theme="dark"] .btn-ghost:hover { background: rgba(255, 255, 255, 0.2); }
.btn-danger { background: linear-gradient(135deg, var(--danger), #c53030); color: white; }
.btn-sm { padding: 4px 10px; font-size: 11px; }
.btn-xs { padding: 2px 6px; font-size: 10px; }
.btn-icon {
width: 32px; height: 32px; padding: 0;
display: inline-flex; align-items: center; justify-content: center;
font-size: 14px;
}
.app-main { flex: 1; display: flex; overflow: hidden; position: relative; flex-direction: row; }
.sidebar {
order: -1; width: var(--sidebar-width); background: var(--panel-bg);
border-right: 1px solid var(--border); display: flex; flex-direction: column;
transition: width 0.3s ease; box-shadow: var(--shadow); overflow: hidden;
flex-shrink: 0; z-index: 50;
}
.sidebar.collapsed { width: 0; overflow: hidden; }
.sidebar-header {
background: linear-gradient(135deg, var(--primary), var(--primary-light)); color: white;
padding: 12px; display: flex; align-items: center; justify-content: space-between;
border-bottom: 2px solid var(--accent); min-height: 48px;
}
.sidebar-title { font-size: 13px; font-weight: 700; display: flex; align-items: center; gap: 8px; white-space: nowrap; overflow: hidden; }
.sidebar.collapsed .sidebar-title { display: none; }
.sidebar-body { flex: 1; overflow-y: auto; padding: 8px; }
.sidebar.collapsed .sidebar-body { display: none; }
.sidebar-group { margin-bottom: 6px; border: 1px solid var(--border-light); border-radius: var(--radius-sm); overflow: hidden; background: var(--card-bg); }
.sidebar-group-header {
background: linear-gradient(135deg, var(--primary), var(--primary-light)); color: white;
padding: 10px 12px; cursor: pointer; display: flex; align-items: center;
justify-content: space-between; font-size: 13px; font-weight: 700;
user-select: none; transition: all 0.2s; position: relative;
}
.sidebar-group-header:hover { filter: brightness(1.1); }
.sidebar-group-header .group-icon { display: flex; align-items: center; gap: 8px; flex: 1; overflow: hidden; }
.sidebar-group-header .group-arrow { transition: transform 0.3s; font-size: 12px; flex-shrink: 0; }
.sidebar-group.collapsed .group-arrow { transform: rotate(90deg); }
.sidebar-group.collapsed .sidebar-group-body { display: none; }
.sidebar-group-body { padding: 6px; display: flex; flex-direction: column; gap: 4px; }
.sidebar-btn {
background: var(--card-bg); border: 1px solid var(--border-light); color: var(--text);
padding: 8px 10px; border-radius: var(--radius-sm); cursor: pointer; font-family: inherit;
font-size: 12px; font-weight: 500; display: flex; align-items: center; gap: 8px;
transition: all 0.2s; text-align: right; width: 100%; position: relative;
}
.sidebar-btn:hover { background: linear-gradient(135deg, var(--primary), var(--primary-light)); color: white; border-color: var(--primary); transform: translateX(-2px); }
.sidebar-btn .btn-icon-emoji { font-size: 15px; width: 20px; text-align: center; flex-shrink: 0; }
.sidebar-btn .btn-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
[data-tooltip] { position: relative; }
[data-tooltip]::after {
content: attr(data-tooltip); position: absolute; right: calc(100% + 10px); top: 50%;
transform: translateY(-50%); background: var(--primary-dark); color: white;
padding: 8px 12px; border-radius: 6px; font-size: 11px; font-weight: 500;
white-space: normal; opacity: 0; pointer-events: none; transition: opacity 0.2s;
z-index: 1000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); max-width: 250px; line-height: 1.5;
}
[data-tooltip]::before {
content: ''; position: absolute; right: calc(100% + 5px); top: 50%;
transform: translateY(-50%); border: 5px solid transparent;
border-left-color: var(--primary-dark); opacity: 0; pointer-events: none;
transition: opacity 0.2s; z-index: 1000;
}
[data-tooltip]:hover::after, [data-tooltip]:hover::before { opacity: 1; }
[data-theme="dark"] [data-tooltip]::after { background: var(--accent-dark); color: var(--primary-dark); }
[data-theme="dark"] [data-tooltip]::before { border-left-color: var(--accent-dark); }
.tabs-bar {
background: linear-gradient(135deg, #0f1724 0%, #1a2a44 100%);
border-bottom: 2px solid var(--accent);
display: flex;
align-items: center;
padding: 0 12px;
min-height: var(--tabs-height);
box-shadow: var(--shadow-sm);
flex-shrink: 0;
gap: 10px;
position: relative;
flex-wrap: wrap;
}
.toolbar-controls { display: flex; gap: 6px; margin-left: 8px; }
.toolbar-icon-btn {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.15);
color: white;
width: 34px; height: 34px; border-radius: 6px; cursor: pointer;
font-size: 15px; display: flex; align-items: center; justify-content: center;
transition: all 0.2s; padding: 0;
}
.toolbar-icon-btn:hover { background: var(--accent); color: var(--primary-dark); border-color: var(--accent); transform: translateY(-1px); }
.toolbar-search { flex: 1; min-width: 180px; max-width: 320px; position: relative; display: flex; align-items: center; }
.search-icon { position: absolute; right: 10px; font-size: 13px; opacity: 0.7; pointer-events: none; }
.search-input {
width: 100%; padding: 7px 32px 7px 12px;
background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px; color: white; font-family: inherit; font-size: 13px; transition: all 0.2s;
}
.search-input::placeholder { color: rgba(255, 255, 255, 0.5); }
.search-input:focus { outline: none; background: rgba(255, 255, 255, 0.12); border-color: var(--accent); box-shadow: 0 0 0 2px rgba(201, 169, 97, 0.2); }
.toolbar-filters { display: flex; gap: 8px; }
.filter-dropdown {
position: relative; display: flex; align-items: center; gap: 6px;
background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 6px; padding: 0 10px; transition: all 0.2s;
}
.filter-dropdown:hover { border-color: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.12); }
.filter-icon { font-size: 13px; opacity: 0.9; }
.filter-select {
background: transparent; border: none; color: white; font-family: inherit;
font-size: 12px; font-weight: 600; padding: 7px 4px; cursor: pointer; outline: none; min-width: 110px;
}
.filter-select option { background: var(--primary-dark); color: white; }
.toolbar-stats { display: flex; gap: 12px; margin-right: auto; padding-right: 8px; border-right: 1px solid rgba(255, 255, 255, 0.1); }
.stat-item { display: flex; align-items: center; gap: 4px; font-size: 11px; color: rgba(255, 255, 255, 0.85); white-space: nowrap; }
.stat-icon { font-size: 13px; }
.stat-label { font-weight: 500; }
.stat-value {
font-weight: 800; color: var(--accent); background: rgba(201, 169, 97, 0.15);
padding: 2px 7px; border-radius: 10px; font-size: 11px; min-width: 22px; text-align: center;
}
@media (max-width: 900px) {
.tabs-bar { flex-wrap: wrap; padding: 8px; gap: 8px; }
.toolbar-stats { order: 3; width: 100%; margin-right: 0; padding-right: 0; border-right: none; border-top: 1px solid rgba(255, 255, 255, 0.1); padding-top: 8px; justify-content: center; }
.toolbar-search { order: 2; }
}
.board-area { flex: 1; overflow: auto; padding: 24px; background: var(--board-bg); transition: background 0.3s; }
.modal-overlay { position: fixed; inset: 0; background: rgba(26, 54, 93, 0.6); backdrop-filter: blur(3px); display: none; align-items: center; justify-content: center; z-index: 1000; padding: 20px; animation: fadeIn 0.2s; }
.modal-overlay.active { display: flex; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.modal { background: var(--panel-bg); color: var(--text); border-radius: 12px; width: 100%; max-width: 600px; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; box-shadow: var(--shadow-lg); animation: slideUp 0.25s; }
.modal-lg { max-width: 800px; }
.modal-xl { max-width: 1000px; }
@keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
.modal-header { background: linear-gradient(135deg, var(--primary), var(--primary-light)); color: white; padding: 16px 20px; display: flex; align-items: center; justify-content: space-between; border-bottom: 3px solid var(--accent); }
.modal-title { font-size: 16px; font-weight: 700; display: flex; align-items: center; gap: 10px; }
.modal-close { background: rgba(255, 255, 255, 0.15); border: none; color: white; width: 30px; height: 30px; border-radius: 50%; cursor: pointer; font-size: 16px; transition: all 0.2s; }
.modal-close:hover { background: var(--danger); }
.modal-body { padding: 20px; overflow-y: auto; flex: 1; }
.modal-footer { padding: 14px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-start; gap: 8px; background: var(--border-light); }
.form-group { margin-bottom: 16px; }
.form-label { display: block; font-size: 13px; font-weight: 600; color: var(--primary); margin-bottom: 6px; }
[data-theme="dark"] .form-label { color: var(--accent-light); }
.form-label .required { color: var(--danger); }
.form-input, .form-textarea, .form-select { width: 100%; padding: 9px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); font-family: inherit; font-size: 13px; background: var(--card-bg); color: var(--text); transition: all 0.2s; }
.form-input:focus, .form-textarea:focus, .form-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(201, 169, 97, 0.15); }
.form-textarea { resize: vertical; min-height: 70px; line-height: 1.6; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.webhook-section { background: var(--border-light); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px; margin-top: 12px; }
.webhook-section-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.webhook-section-title { font-size: 13px; font-weight: 700; color: var(--primary); display: flex; align-items: center; gap: 6px; }
[data-theme="dark"] .webhook-section-title { color: var(--accent-light); }
.webhook-toggle { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-muted); }
.switch { position: relative; display: inline-block; width: 36px; height: 20px; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider { position: absolute; cursor: pointer; inset: 0; background: var(--border); transition: 0.3s; border-radius: 20px; }
.slider::before { position: absolute; content: ""; height: 14px; width: 14px; right: 3px; bottom: 3px; background: white; transition: 0.3s; border-radius: 50%; }
input:checked + .slider { background: var(--success); }
input:checked + .slider::before { transform: translateX(-16px); }
.webhook-status { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 8px 10px; background: var(--card-bg); border-radius: 4px; font-size: 11px; color: var(--text-muted); }
.webhook-status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-muted); flex-shrink: 0; }
.webhook-status-dot.success { background: var(--success); }
.webhook-status-dot.error { background: var(--danger); }
.webhook-status-dot.pending { background: var(--warning); animation: pulse 1.5s infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.toast-container { position: fixed; bottom: 20px; left: 20px; z-index: 2000; display: flex; flex-direction: column; gap: 8px; }
.toast { background: var(--primary); color: white; padding: 12px 18px; border-radius: 8px; box-shadow: var(--shadow-lg); font-size: 13px; display: flex; align-items: center; gap: 10px; animation: slideInLeft 0.3s; min-width: 250px; }
.toast.success { background: var(--success); }
.toast.error { background: var(--danger); }
.toast.warning { background: var(--warning); }
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-30px); } to { opacity: 1; transform: translateX(0); } }
.app-footer { background: linear-gradient(135deg, var(--primary), var(--primary-light)); color: white; padding: 8px 20px; font-size: 11px; display: flex; justify-content: space-between; align-items: center; border-top: 2px solid var(--accent); flex-shrink: 0; }
.footer-brand { color: var(--accent); font-weight: 700; }
.form-input[readonly], .form-textarea[readonly] { background: var(--border-light); color: var(--text-muted); cursor: not-allowed; opacity: 0.85; border-style: dashed; }
.readonly-badge { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; font-weight: 600; color: var(--text-muted); background: var(--border-light); border: 1px solid var(--border); padding: 2px 8px; border-radius: 10px; margin-right: 6px; }
.readonly-badge::before { content: '🔒'; font-size: 10px; }
.readonly-info { background: linear-gradient(to left, #ebf8ff, #e6f6ff); border: 1px solid #90cdf4; border-right: 4px solid var(--info); padding: 10px 14px; border-radius: 6px; font-size: 11px; color: var(--text-muted); margin-bottom: 14px; display: flex; align-items: center; gap: 8px; }
[data-theme="dark"] .readonly-info { background: rgba(49, 130, 206, 0.12); border-color: rgba(49, 130, 206, 0.3); border-right-color: var(--info); }
/* Galaxy Chart Styles */
#galaxy-chart-container {
width: 100%;
height: 600px;
background: radial-gradient(ellipse at center, #0a0e27 0%, #000000 100%);
border-radius: 8px;
overflow: hidden;
position: relative;
}
.galaxy-node {
cursor: pointer;
transition: all 0.3s;
}
.galaxy-node:hover {
filter: brightness(1.3);
}
.galaxy-link {
stroke-opacity: 0.6;
transition: all 0.3s;
}
.galaxy-link:hover {
stroke-opacity: 1;
stroke-width: 3;
}
.galaxy-label {
font-family: 'Vazirmatn', sans-serif;
font-size: 10px;
fill: #ffffff;
pointer-events: none;
text-anchor: middle;
text-shadow: 0 0 3px rgba(0,0,0,0.8);
}
.galaxy-tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 10px 14px;
border-radius: 6px;
font-size: 12px;
pointer-events: none;
z-index: 1000;
max-width: 250px;
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
border: 1px solid rgba(255,255,255,0.2);
}
.galaxy-legend {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 12px 16px;
border-radius: 8px;
color: white;
font-size: 11px;
}
.galaxy-legend-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
}
.galaxy-legend-color {
width: 14px;
height: 14px;
border-radius: 50%;
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
</style>
</head>
<body>
<header class="app-header">
<div class="app-brand">
<div class="app-logo"></div>
<div class="app-brand-text">
<div class="app-title" id="headerTitle">مدیریت محصولات</div>
<div class="app-subtitle" id="headerSubtitle">مدیریت نسخه‌ها و نیازمندی‌های محصولات</div>
</div>
</div>
</header>
<div class="tabs-bar" id="advancedToolbar">
<div class="toolbar-controls">
<button class="toolbar-icon-btn" id="sidebarToggle" title="باز/بسته کردن سایدبار">
<span id="sidebarIcon"></span>
</button>
<button class="toolbar-icon-btn" id="themeToggle" title="تغییر تم">
<span id="themeIcon">🌙</span>
</button>
</div>
<div class="toolbar-search">
<span class="search-icon">🔍</span>
<input type="text" class="search-input" id="toolbarSearch" placeholder="جستجو...">
</div>
<div class="toolbar-filters">
<div class="filter-dropdown">
<span class="filter-icon">🏷️</span>
<select class="filter-select" id="filterType">
<option value="">همه انواع</option>
<option value="backend">بک‌اند</option>
<option value="frontend">فرانت‌اند</option>
<option value="service">میکروسرویس</option>
<option value="database">پایگاه داده</option>
<option value="mobile">موبایل</option>
<option value="infrastructure">زیرساخت</option>
</select>
</div>
<div class="filter-dropdown">
<span class="filter-icon">🌐</span>
<select class="filter-select" id="filterEnvironment">
<option value="">همه محیط‌ها</option>
</select>
</div>
</div>
<div class="toolbar-stats">
<div class="stat-item">
<span class="stat-icon"></span>
<span class="stat-label">کل:</span>
<span class="stat-value" id="statTotal">0</span>
</div>
<div class="stat-item">
<span class="stat-icon"></span>
<span class="stat-label">محصول:</span>
<span class="stat-value" id="statProjects">0</span>
</div>
<div class="stat-item">
<span class="stat-icon"></span>
<span class="stat-label">پروژه:</span>
<span class="stat-value" id="statSubProjects">0</span>
</div>
<div class="stat-item">
<span class="stat-icon">🔗</span>
<span class="stat-label">وابستگی:</span>
<span class="stat-value" id="statDependencies">0</span>
</div>
</div>
</div>
<div class="app-main">
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="sidebar-title">️ ابزارها</div>
</div>
<div class="sidebar-body" id="sidebarBody"></div>
</aside>
<main class="board-area" id="boardArea">
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--text-muted);">
<div style="font-size: 80px; opacity: 0.3; margin-bottom: 20px;"></div>
<h2 style="font-size: 20px; color: var(--primary); margin-bottom: 10px;">ناحیه اصلی</h2>
<p>محتوای اصلی برنامه در این ناحیه نمایش داده می‌شود</p>
</div>
</main>
</div>
<footer class="app-footer" id="appFooter">
<div>
<span class="footer-brand">طراح و توسعه‌دهنده:</span> هادی خزاعی اصل
<span style="margin: 0 10px;">|</span>
<span class="footer-brand">شرکت:</span> رایان پرداز کاوش
</div>
<div>
<span class="footer-brand">نسخه:</span> 1.4.21
</div>
</footer>
<!-- Settings Modal -->
<div class="modal-overlay" id="settingsModal">
<div class="modal modal-lg">
<div class="modal-header">
<div class="modal-title"><span>⚙️</span><span>تنظیمات محصول</span></div>
<button class="modal-close" data-close-modal>✕</button>
</div>
<div class="modal-body">
<div class="readonly-info">
<span>ℹ️</span>
<span>اطلاعات پایه محصول فقط قابل مشاهده هستند و از طریق فایل پیکربندی قابل تغییر می‌باشند.</span>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">عنوان محصول <span class="readonly-badge">فقط خواندنی</span></label>
<input type="text" class="form-input" id="settingProjectTitle" readonly>
</div>
<div class="form-group">
<label class="form-label">نسخه <span class="readonly-badge">فقط خواندنی</span></label>
<input type="text" class="form-input" id="settingVersion" readonly>
</div>
</div>
<div class="form-group">
<label class="form-label">توضیحات <span class="readonly-badge">فقط خواندنی</span></label>
<textarea class="form-textarea" id="settingDescription" readonly></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">توسعه‌دهنده <span class="readonly-badge">فقط خواندنی</span></label>
<input type="text" class="form-input" id="settingDeveloper" readonly>
</div>
<div class="form-group">
<label class="form-label">شرکت <span class="readonly-badge">فقط خواندنی</span></label>
<input type="text" class="form-input" id="settingCompany" readonly>
</div>
</div>
<div class="webhook-section">
<div class="webhook-section-header">
<div class="webhook-section-title"><span>🔗</span><span>تنظیمات وب‌هوک</span></div>
<div class="webhook-toggle">
<span>فعال‌سازی</span>
<label class="switch">
<input type="checkbox" id="settingWebhookEnabled">
<span class="slider"></span>
</label>
</div>
</div>
<div class="form-group">
<label class="form-label">آدرس URL وب‌هوک <span class="required">*</span></label>
<input type="url" class="form-input" id="settingWebhookUrl" dir="ltr" placeholder="https://example.com/webhook">
</div>
<div class="form-group">
<label class="form-label">شناسه کلاینت (Client ID) <span class="required">*</span></label>
<input type="text" class="form-input" id="settingWebhookClientId" dir="ltr" placeholder="client-xxxx-xxxx">
</div>
<div class="webhook-status">
<span class="webhook-status-dot" id="webhookStatusDot"></span>
<span id="webhookStatusText">وضعیت: غیرفعال</span>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-accent" id="saveSettingsBtn">💾 ذخیره</button>
<button class="btn btn-ghost" data-close-modal>بستن</button>
</div>
</div>
</div>
<!-- Galaxy Chart Modal -->
<div class="modal-overlay" id="galaxyChartModal">
<div class="modal modal-xl">
<div class="modal-header">
<div class="modal-title"><span>🌌</span><span>نمودار کهکشانی محصولات و وابستگی‌ها</span></div>
<button class="modal-close" data-close-modal></button>
</div>
<div class="modal-body" style="padding: 0;">
<div id="galaxy-chart-container"></div>
</div>
<div class="modal-footer">
<button class="btn btn-accent" onclick="resetGalaxyZoom()">🔍 بازنشانی نما</button>
<button class="btn btn-ghost" data-close-modal>بستن</button>
</div>
</div>
</div>
<div class="toast-container" id="toastContainer"></div>
<script>
const projectData = {
projectId: '550e8400-e29b-41d4-a716-446655440000',
projectName: 'xDependencies',
projectTitle: 'مدیریت محصولات',
description: 'مدیریت نسخه‌ها و نیازمندی‌های محصولات',
version: '1.4.21',
developer: 'هادی خزاعی اصل',
company: 'رایان پرداز کاوش',
metadata: {
createdAt: '2026-09-13T00:00:00Z',
updatedAt: '2026-09-14T00:00:00Z',
tags: ['release-management', 'architecture', 'product-management'],
customFields: {}
},
webhook: { url: '', clientId: '', enabled: false },
toolGroups: [
{
id: 'product-management', title: 'محصول', icon: '📁', description: 'مدیریت چرخه حیات محصول',
tools: [
{ id: 'create-product', title: 'ایجاد', icon: '➕', action: 'createProduct', shortcut: null, description: 'ایجاد یک محصول جدید' },
{ id: 'edit-product', title: 'ویرایش', icon: '✏️', action: 'editProduct', shortcut: null, description: 'ویرایش اطلاعات محصول' },
{ id: 'product-settings', title: 'تنظیمات', icon: '⚙️', action: 'openProductSettings', shortcut: null, description: 'پیکربندی تنظیمات' },
{ id: 'save-product', title: 'ذخیره', icon: '💾', action: 'saveProduct', shortcut: null, description: 'دانلود داده‌های محصول' },
{ id: 'load-project', title: 'بارگذاری', icon: '📂', action: 'loadProduct', shortcut: null, description: 'بارگذاری از فایل JSON' },
],
},
{
id: 'management', title: 'مدیریت', icon: '⚙️', description: 'ابزارهای مدیریت پیکربندی',
tools: [
{ id: 'manage-envs', title: 'مدیریت محیط‌ها', icon: '🌍', action: 'manageEnvironments', shortcut: null, description: 'مدیریت محیط‌های عملیاتی' },
{ id: 'manage-products', title: 'مدیریت محصولات', icon: '📦', action: 'manageProducts', shortcut: null, description: 'مدیریت سلسله‌مراتب' }
]
},
{
id: 'reports', title: 'گزارش‌ها', icon: '📊', description: 'تحلیل و گزارش‌گیری',
tools: [
{ id: 'view-reports', title: 'نمایش گزارش‌ها', icon: '📈', action: 'viewReports', shortcut: null, description: 'آمار کلی محصول' },
{ id: 'dependency-graph', title: 'نمودار وابستگی', icon: '🕸️', action: 'showDependencyGraph', shortcut: null, description: 'نمایش بصری وابستگی‌ها' },
{ id: 'galaxy-graph', title: 'نمودار کهکشانی', icon: '🌌', action: 'showGalaxyGraph', shortcut: null, description: 'نمایش نمودار کهکشانی محصولات و روابط' },
],
},
],
appData: {
environments: [
{ id: 'env-prod-001', name: 'Production', description: 'محیط عملیاتی اصلی' },
{ id: 'env-stage-002', name: 'Staging', description: 'محیط پیش از تولید' },
{ id: 'env-dev-003', name: 'Development', description: 'محیط توسعه' },
],
products: [
{
id: 'prod-1001', title: 'سامانه فروشگاه آنلاین', description: 'پلتفرم جامع فروش محصولات',
metadata: { createdAt: '2026-09-01T00:00:00Z', owner: 'تیم توسعه محصول', customFields: {} },
projects: [
{
id: 'proj-1001-01', title: 'Backend API Service', description: 'سرویس بک‌اند اصلی',
type: 'backend', repositoryUrl: '',
metadata: { framework: '.NET 8', language: 'C#' },
dependencies: [],
lastDeployedVersions: [
{ environmentId: 'env-prod-001', version: '2.3.1', deploymentDate: '2026-09-10T14:30:00Z' },
{ environmentId: 'env-stage-002', version: '2.4.0-rc.1', deploymentDate: '2026-09-12T09:00:00Z' },
],
},
{
id: 'proj-1001-02', title: 'Frontend Web Application', description: 'اپلیکیشن فرانت‌اند',
type: 'frontend', repositoryUrl: '',
metadata: { framework: 'Angular 18', language: 'TypeScript' },
dependencies: ['proj-1001-01'],
lastDeployedVersions: [
{ environmentId: 'env-prod-001', version: '3.1.0', deploymentDate: '2026-09-11T10:00:00Z' },
],
},
{
id: 'proj-1001-03', title: 'Payment Gateway Service', description: 'سرویس درگاه پرداخت',
type: 'service', repositoryUrl: '',
metadata: { framework: '.NET 8', language: 'C#' },
dependencies: ['proj-1001-01'],
lastDeployedVersions: [
{ environmentId: 'env-prod-001', version: '1.2.0', deploymentDate: '2026-09-08T16:00:00Z' },
],
},
],
},
],
},
};
const VALID_PROJECT_TYPES = [
{ value: 'backend', label: 'بک‌اند (Backend)', icon: '🖥️' },
{ value: 'frontend', label: 'فرانت‌اند (Frontend)', icon: '🌐' },
{ value: 'service', label: 'میکروسرویس (Service)', icon: '⚙️' },
{ value: 'database', label: 'پایگاه داده (Database)', icon: '️' },
{ value: 'mobile', label: 'موبایل (Mobile)', icon: '📱' },
{ value: 'infrastructure', label: 'زیرساخت (Infrastructure)', icon: '🏗️' }
];
const TYPE_COLORS = {
backend: '#3182ce',
frontend: '#38a169',
service: '#dd6b20',
database: '#97266d',
mobile: '#553c9a',
infrastructure: '#9c4221'
};
function validateAppDataStructure(data) {
if (!data || typeof data !== 'object') return false;
if (!Array.isArray(data.environments)) return false;
if (!Array.isArray(data.products)) return false;
return true;
}
const ModalManager = {
open(id) { const m = document.getElementById(id); if (m) m.classList.add('active'); },
close(id) { const m = document.getElementById(id); if (m) m.classList.remove('active'); },
closeAll() { document.querySelectorAll('.modal-overlay.active').forEach(m => m.classList.remove('active')); },
init() {
document.querySelectorAll('[data-close-modal]').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const modal = e.target.closest('.modal-overlay');
if (modal) modal.classList.remove('active');
});
});
document.querySelectorAll('.modal-overlay').forEach(overlay => {
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.classList.remove('active');
});
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') this.closeAll();
});
}
};
const ToolbarManager = {
currentFilters: { query: '', typeFilter: '', envFilter: '' },
init() {
this.populateEnvironmentFilter();
this.bindEvents();
this.updateStats();
},
populateEnvironmentFilter() {
const select = document.getElementById('filterEnvironment');
if (!select) return;
const envs = projectData.appData.environments || [];
select.innerHTML = '<option value="">همه محیط‌ها</option>' +
envs.map(e => `<option value="${e.id}">${e.name}</option>`).join('');
},
bindEvents() {
const searchInput = document.getElementById('toolbarSearch');
const filterType = document.getElementById('filterType');
const filterEnv = document.getElementById('filterEnvironment');
if (searchInput) searchInput.addEventListener('input', () => { this.applyFilters(); });
if (filterType) filterType.addEventListener('change', () => { this.applyFilters(); });
if (filterEnv) filterEnv.addEventListener('change', () => { this.applyFilters(); });
},
applyFilters() {
this.currentFilters = {
query: (document.getElementById('toolbarSearch')?.value || '').trim().toLowerCase(),
typeFilter: document.getElementById('filterType')?.value || '',
envFilter: document.getElementById('filterEnvironment')?.value || ''
};
this.updateStats();
},
updateStats() {
const products = projectData.appData.products || [];
let totalProjects = 0;
let totalDeps = 0;
products.forEach(p => {
const projects = p.projects || [];
projects.forEach(pr => {
totalProjects++;
totalDeps += (pr.dependencies || []).length;
});
});
const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
set('statTotal', products.length + totalProjects);
set('statProjects', products.length);
set('statSubProjects', totalProjects);
set('statDependencies', totalDeps);
}
};
const ActionRegistry = {
openProductSettings() { openSettingsModal(); },
manageEnvironments() { showToast('مدیریت محیط‌ها', 'info'); },
manageProducts() { showToast('مدیریت محصولات', 'info'); },
saveProduct() {
projectData.metadata.updatedAt = new Date().toISOString();
const jsonString = JSON.stringify(projectData.appData, null, 2);
const blob = new Blob([jsonString], { type: 'application/json;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
a.download = `${projectData.projectName}_v${projectData.version}_${timestamp}.json`;
document.body.appendChild(a); a.click(); document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast(`💾 داده‌های محصول دانلود شد`, 'success', 4000);
},
loadProduct() {
const input = document.createElement('input');
input.type = 'file'; input.accept = '.json,application/json';
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => {
try {
const loaded = JSON.parse(ev.target.result);
if (!validateAppDataStructure(loaded)) return showToast('❌ ساختار فایل نامعتبر است', 'error', 5000);
projectData.appData = loaded;
projectData.metadata.updatedAt = new Date().toISOString();
updateProductUI();
renderToolGroups();
ToolbarManager.populateEnvironmentFilter();
ToolbarManager.updateStats();
showToast(` محصول بارگذاری شد`, 'success', 4000);
} catch (err) { showToast('❌ خطا در خواندن فایل: ' + err.message, 'error', 5000); }
};
reader.readAsText(file);
});
input.click();
},
createProduct() { showToast('ایجاد محصول جدید', 'info'); },
editProduct() { showToast('ویرایش محصول', 'info'); },
viewReports() {
const p = projectData.appData.products.length;
const s = projectData.appData.products.reduce((x, p) => x + p.projects.length, 0);
const e = projectData.appData.environments.length;
showToast(`📊 محصولات: ${p} | پروژه‌ها: ${s} | محیط‌ها: ${e}`, 'info', 5000);
},
showDependencyGraph() { showToast('️ نمودار وابستگی', 'info', 3000); },
showGalaxyGraph() { showGalaxyChartModal(); },
defaultAction(tool) { showToast(`ابزار: ${tool.title}`, 'info', 3000); }
};
function showToast(message, type = 'info', duration = 3000) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' };
toast.innerHTML = `<span>${icons[type] || 'ℹ️'}</span><span>${message}</span>`;
container.appendChild(toast);
setTimeout(() => {
toast.style.transition = 'opacity 0.3s, transform 0.3s';
toast.style.opacity = '0';
toast.style.transform = 'translateX(-30px)';
setTimeout(() => toast.remove(), 300);
}, duration);
}
function openSettingsModal() {
document.getElementById('settingProjectTitle').value = projectData.projectTitle;
document.getElementById('settingVersion').value = projectData.version;
document.getElementById('settingDescription').value = projectData.description;
document.getElementById('settingDeveloper').value = projectData.developer;
document.getElementById('settingCompany').value = projectData.company;
document.getElementById('settingWebhookUrl').value = projectData.webhook.url;
document.getElementById('settingWebhookClientId').value = projectData.webhook.clientId;
document.getElementById('settingWebhookEnabled').checked = projectData.webhook.enabled;
ModalManager.open('settingsModal');
}
// Galaxy Chart Functions
let galaxySimulation = null;
function showGalaxyChartModal() {
ModalManager.open('galaxyChartModal');
setTimeout(() => renderGalaxyChart(), 100);
}
function resetGalaxyZoom() {
if (galaxySimulation) {
const svg = d3.select('#galaxy-chart-container svg');
svg.transition().duration(750).call(
d3.zoom().transform,
d3.zoomIdentity
);
}
}
function renderGalaxyChart() {
const container = document.getElementById('galaxy-chart-container');
if (!container) return;
// Clear previous chart
container.innerHTML = '';
const width = container.clientWidth;
const height = container.clientHeight;
// Prepare data
const nodes = [];
const links = [];
const nodeMap = new Map();
// Create nodes for products
projectData.appData.products.forEach((product, productIndex) => {
const productId = product.id;
const productNode = {
id: productId,
name: product.title,
type: 'product',
group: productIndex + 1,
size: 30,
color: '#c9a961'
};
nodes.push(productNode);
nodeMap.set(productId, productNode);
// Create nodes for projects within product
(product.projects || []).forEach((project, projectIndex2) => {
const projectNodeId = `${productId}-${project.id}`;
const projectNode = {
id: projectNodeId,
name: project.title,
type: project.type,
group: productIndex + 1,
size: 20,
color: TYPE_COLORS[project.type] || '#718096',
parent: productId
};
nodes.push(projectNode);
nodeMap.set(projectNodeId, projectNode);
// Create link from product to project
links.push({
source: productId,
target: projectNodeId,
value: 2
});
// Create links for dependencies
(project.dependencies || []).forEach(depId => {
const targetNodeId = `${productId}-${depId}`;
if (nodeMap.has(targetNodeId)) {
links.push({
source: projectNodeId,
target: targetNodeId,
value: 1
});
}
});
});
});
// Create SVG
const svg = d3.select('#galaxy-chart-container')
.append('svg')
.attr('width', width)
.attr('height', height)
.attr('viewBox', [0, 0, width, height]);
// Add zoom behavior
const g = svg.append('g');
const zoom = d3.zoom()
.scaleExtent([0.1, 4])
.on('zoom', (event) => {
g.attr('transform', event.transform);
});
svg.call(zoom);
// Add arrow markers
svg.append('defs').selectAll('marker')
.data(['end'])
.join('marker')
.attr('id', String)
.attr('viewBox', '0 -5 10 10')
.attr('refX', 25)
.attr('refY', 0)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('path')
.attr('fill', 'rgba(255,255,255,0.6)')
.attr('d', 'M0,-5L10,0L0,5');
// Create force simulation
galaxySimulation = d3.forceSimulation(nodes)
.force('link', d3.forceLink(links).id(d => d.id).distance(100))
.force('charge', d3.forceManyBody().strength(-400))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collision', d3.forceCollide().radius(d => d.size + 10));
// Draw links
const link = g.append('g')
.attr('class', 'links')
.selectAll('line')
.data(links)
.join('line')
.attr('class', 'galaxy-link')
.attr('stroke', 'rgba(255,255,255,0.4)')
.attr('stroke-width', d => Math.sqrt(d.value))
.attr('marker-end', 'url(#end)');
// Draw nodes
const node = g.append('g')
.attr('class', 'nodes')
.selectAll('circle')
.data(nodes)
.join('circle')
.attr('class', 'galaxy-node')
.attr('r', d => d.size)
.attr('fill', d => d.color)
.attr('stroke', '#fff')
.attr('stroke-width', 2)
.call(drag(galaxySimulation));
// Add labels
const labels = g.append('g')
.attr('class', 'labels')
.selectAll('text')
.data(nodes)
.join('text')
.attr('class', 'galaxy-label')
.attr('dx', 0)
.attr('dy', d => d.size + 15)
.text(d => d.name);
// Add tooltip
const tooltip = d3.select('body').append('div')
.attr('class', 'galaxy-tooltip')
.style('opacity', 0);
node.on('mouseover', (event, d) => {
tooltip.transition().duration(200).style('opacity', .9);
tooltip.html(`
<strong>${d.name}</strong><br/>
نوع: ${d.type === 'product' ? 'محصول' : d.type}<br/>
${d.parent ? `محصول والد: ${projectData.appData.products.find(p => p.id === d.parent)?.title || ''}` : ''}
`)
.style('left', (event.pageX + 10) + 'px')
.style('top', (event.pageY - 28) + 'px');
})
.on('mouseout', () => {
tooltip.transition().duration(500).style('opacity', 0);
});
// Update positions on tick
galaxySimulation.on('tick', () => {
link
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
node
.attr('cx', d => d.x)
.attr('cy', d => d.y);
labels
.attr('x', d => d.x)
.attr('y', d => d.y);
});
// Add legend
addGalaxyLegend(svg, width, height);
}
function addGalaxyLegend(svg, width, height) {
const legendData = [
{ label: 'محصول', color: '#c9a961' },
{ label: 'بک‌اند', color: '#3182ce' },
{ label: 'فرانت‌اند', color: '#38a169' },
{ label: 'میکروسرویس', color: '#dd6b20' },
{ label: 'پایگاه داده', color: '#97266d' },
{ label: 'موبایل', color: '#553c9a' },
{ label: 'زیرساخت', color: '#9c4221' }
];
const legend = svg.append('g')
.attr('class', 'galaxy-legend')
.attr('transform', `translate(20, 20)`);
legend.append('rect')
.attr('width', 140)
.attr('height', legendData.length * 25 + 10)
.attr('fill', 'rgba(0,0,0,0.7)')
.attr('rx', 8);
legendData.forEach((item, i) => {
const itemGroup = legend.append('g')
.attr('transform', `translate(10, ${i * 25 + 10})`);
itemGroup.append('circle')
.attr('r', 7)
.attr('fill', item.color);
itemGroup.append('text')
.attr('x', 15)
.attr('y', 4)
.attr('fill', 'white')
.attr('font-size', '11px')
.text(item.label);
});
}
function drag(simulation) {
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
return d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended);
}
function handleToolSelect(tool, group) {
const actionHandler = ActionRegistry[tool.action] || ActionRegistry.defaultAction;
actionHandler(tool, group);
}
const themeToggle = document.getElementById('themeToggle');
const themeIcon = document.getElementById('themeIcon');
const htmlEl = document.documentElement;
const savedTheme = localStorage.getItem('theme') || 'light';
htmlEl.setAttribute('data-theme', savedTheme);
if (themeIcon) themeIcon.textContent = savedTheme === 'dark' ? '☀️' : '🌙';
if (themeToggle) {
themeToggle.addEventListener('click', () => {
const current = htmlEl.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
htmlEl.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
if (themeIcon) themeIcon.textContent = next === 'dark' ? '☀️' : '';
});
}
const sidebarToggle = document.getElementById('sidebarToggle');
const sidebar = document.getElementById('sidebar');
const sidebarIcon = document.getElementById('sidebarIcon');
function updateSidebarIcon() { if (!sidebarIcon) return; sidebarIcon.textContent = sidebar.classList.contains('collapsed') ? '☰' : '✕'; }
if (sidebarToggle) { sidebarToggle.addEventListener('click', () => { sidebar.classList.toggle('collapsed'); updateSidebarIcon(); }); }
updateSidebarIcon();
const sidebarBody = document.getElementById('sidebarBody');
function renderToolGroups() {
sidebarBody.innerHTML = '';
projectData.toolGroups.forEach(group => {
const groupEl = document.createElement('div');
groupEl.className = 'sidebar-group';
const headerEl = document.createElement('div');
headerEl.className = 'sidebar-group-header';
if (group.description) headerEl.setAttribute('data-tooltip', group.description);
headerEl.innerHTML = `<div class="group-icon"><span>${group.icon || ''}</span><span>${group.title}</span></div><span class="group-arrow">▼</span>`;
const bodyEl = document.createElement('div');
bodyEl.className = 'sidebar-group-body';
group.tools.forEach(tool => {
const btn = document.createElement('button');
btn.className = 'sidebar-btn';
btn.title = tool.description;
btn.innerHTML = `<span class="btn-icon-emoji">${tool.icon || '🔧'}</span><span class="btn-title">${tool.title}</span>`;
btn.addEventListener('click', () => handleToolSelect(tool, group));
bodyEl.appendChild(btn);
});
headerEl.addEventListener('click', () => groupEl.classList.toggle('collapsed'));
groupEl.appendChild(headerEl);
groupEl.appendChild(bodyEl);
sidebarBody.appendChild(groupEl);
});
}
function updateProductUI() {
document.title = projectData.projectTitle;
document.getElementById('headerTitle').textContent = projectData.projectTitle;
document.getElementById('headerSubtitle').textContent = projectData.description;
document.getElementById('appFooter').innerHTML = `<div><span class="footer-brand">طراح و توسعه‌دهنده:</span> ${projectData.developer}<span style="margin: 0 10px;">|</span><span class="footer-brand">شرکت:</span> ${projectData.company}</div><div><span class="footer-brand">نسخه:</span> ${projectData.version}</div>`;
}
ModalManager.init();
renderToolGroups();
updateProductUI();
ToolbarManager.init();
setTimeout(() => showToast(`${projectData.projectTitle} (نسخه ${projectData.version}) آماده است`, 'success'), 500);
</script>
</body>
</html>