Sada8888 commited on
Commit
4491a03
·
verified ·
1 Parent(s): 84ce7bf

Create templates/tarahi-khoneh.html

Browse files
Files changed (1) hide show
  1. templates/tarahi-khoneh.html +636 -0
templates/tarahi-khoneh.html ADDED
@@ -0,0 +1,636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>طراح دکوراسیون هوشمند آلفا</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800;900&display=swap');
9
+
10
+ :root {
11
+ --app-font: 'Vazirmatn', sans-serif; --app-bg: #F8F9FC; --panel-bg: #FFFFFF;
12
+ --panel-border: #EAEFF7; --input-bg: #F6F8FB; --input-border: #E1E7EF;
13
+ --text-primary: #1A202C; --text-secondary: #626F86; --text-tertiary: #8A94A6;
14
+ --accent-primary: #4A6CFA; --accent-primary-hover: #3553D6; --accent-primary-glow: rgba(74, 108, 250, 0.25);
15
+ --accent-secondary: #0FD4A8; --accent-secondary-hover: #0DA986; --accent-secondary-glow: rgba(15, 212, 168, 0.2);
16
+ --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03); --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
17
+ --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
18
+ --shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05);
19
+ --radius-card: 24px; --radius-btn: 14px; --radius-input: 12px;
20
+ --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
21
+ }
22
+
23
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
24
+ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
25
+ @keyframes rotate-loader-orbital { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
26
+ @keyframes orbit-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
27
+ @keyframes satellite-pulse-1 { from { transform: scale(0.7) translateX(-50%); opacity: 0.6; } to { transform: scale(1.1) translateX(-50%); opacity: 1; } }
28
+ @keyframes satellite-pulse-2 { from { transform: scale(0.7) translateY(-50%); opacity: 0.6; } to { transform: scale(1.1) translateY(-50%); opacity: 1; } }
29
+
30
+ @keyframes draw-path { to { stroke-dashoffset: 0; } }
31
+ @keyframes core-pulse { 0%, 100% { transform: scale(0.9); opacity: 0.8; } 50% { transform: scale(1.1); opacity: 1; } }
32
+ @keyframes sparkle-rise { from { transform: translateY(0) scale(0.5); opacity: 1; } to { transform: translateY(-25px) scale(1.2); opacity: 0; } }
33
+
34
+ @keyframes float { 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-10px); } }
35
+ @keyframes slideInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
36
+
37
+ html { scroll-behavior: smooth; }
38
+ body {
39
+ font-family: var(--app-font); direction: rtl; background-color: var(--app-bg);
40
+ color: var(--text-primary); font-size: 16px; line-height: 1.8; margin: 0;
41
+ padding: 2.5rem 1rem; min-height: 100vh; display: flex; justify-content: center;
42
+ align-items: flex-start; overflow-x: hidden;
43
+ background-image: radial-gradient(var(--text-tertiary) 0.5px, transparent 0.5px);
44
+ background-size: 20px 20px; background-position: -10px -10px;
45
+ }
46
+ .app-container { max-width: 680px; width: 95%; margin: 0 auto; }
47
+
48
+ .app-header { padding: 0.5rem 0 2.5rem 0; text-align: center; margin-bottom: 1.5rem; animation: fadeIn 0.8s 0.2s ease-out backwards; }
49
+
50
+ .header-logo { width: 90px; height: 90px; margin: 0 auto 1.5rem auto; }
51
+ .header-logo svg { width: 100%; height: 100%; overflow: visible; }
52
+ .header-logo .house-outline-path {
53
+ stroke: url(#logo-gradient); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill: none;
54
+ stroke-dasharray: 400; stroke-dashoffset: 400; animation: draw-path 2.5s cubic-bezier(0.65, 0, 0.35, 1) forwards;
55
+ }
56
+ .header-logo .magic-core {
57
+ fill: url(#logo-gradient); transform-origin: center; opacity: 0;
58
+ filter: drop-shadow(0 0 8px var(--accent-secondary-glow)); animation: core-pulse 3s ease-in-out 2.5s infinite;
59
+ }
60
+ .header-logo .sparkle { fill: var(--accent-secondary); opacity: 0; animation: sparkle-rise 2s ease-out 3s infinite; }
61
+ .header-logo .sparkle:nth-of-type(2) { animation-delay: 3.5s; }
62
+ .header-logo .sparkle:nth-of-type(3) { animation-delay: 4s; }
63
+
64
+ .app-header h1 { font-size: 2.5em; font-weight: 900; margin: 0 0 0.8rem 0; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px; }
65
+ .app-header p { font-size: 1.05em; color: var(--text-secondary); margin-top: 0; opacity: 0.9; font-weight: 400; line-height: 1.7; }
66
+ .main-content { position:relative; padding: 3rem; background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-xl); border: 1px solid var(--panel-border); animation: fadeIn 0.8s 0.4s ease-out backwards; }
67
+
68
+ .form-group { margin-bottom: 2.2rem; }
69
+ label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.1em; margin-bottom: 1rem; }
70
+ label .label-icon { width: 24px; height: 24px; color: var(--accent-primary); }
71
+
72
+ textarea { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset; font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 120px; resize: vertical; }
73
+ textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg); }
74
+
75
+ .generate-btn { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; padding: 1.1rem 1.5rem; font-size: 1.25em; font-weight: 800; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px var(--accent-secondary-glow); }
76
+ .generate-btn:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px var(--accent-secondary-glow); }
77
+ .generate-btn:disabled { background: var(--text-tertiary); cursor: not-allowed; box-shadow: none; color: var(--text-secondary); transform: none; }
78
+ .generate-btn .spinner { width: 20px; height: 20px; border: 3px solid rgba(255, 255, 255, 0.4); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; display: none;}
79
+
80
+ .upload-area { border: 2px dashed var(--input-border); border-radius: var(--radius-input); padding: 2rem; text-align: center; cursor: pointer; transition: var(--transition-smooth); background-color: var(--input-bg); }
81
+ .upload-area:hover, .upload-area.drag-over { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 15px var(--accent-primary-glow); }
82
+ .upload-icon svg { width: 48px; height: 48px; color: var(--accent-primary); margin-bottom: 1rem; stroke-width: 1.5; opacity: 0.8; }
83
+ .upload-area p { margin: 0; color: var(--text-secondary); font-weight: 500; }
84
+ .upload-area p strong { color: var(--text-primary); }
85
+
86
+ #file-preview { display: none; align-items: center; justify-content: space-between; padding: 1rem 1.2rem; background-color: var(--input-bg); border-radius: var(--radius-input); margin-top: 1rem; animation: fadeIn 0.3s; border: 1px solid var(--panel-border); }
87
+ #file-info { display: flex; align-items: center; gap: 1rem; overflow: hidden; }
88
+ #image-preview { width: 60px; height: 60px; border-radius: 12px; object-fit: cover; border: 2px solid var(--panel-border); flex-shrink: 0; }
89
+ #file-name { font-weight: 600; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
90
+ #remove-file-btn { background: none; border: none; color: var(--text-tertiary); cursor: pointer; font-size: 1.8rem; transition: all 0.2s; flex-shrink: 0; line-height: 1; padding: 0 0.5rem; }
91
+ #remove-file-btn:hover { color: #e53e3e; transform: scale(1.1); }
92
+
93
+ .output-section { margin-top: 3rem; display: flex; align-items: center; justify-content: center; flex-direction: column; min-height: 220px; position: relative; box-sizing: border-box; padding: 2rem; background-color: var(--input-bg); border-radius: var(--radius-card); border: 2px dashed var(--input-border); box-shadow: var(--shadow-sm) inset; transition: var(--transition-smooth); }
94
+ .output-section.has-content { background-color: var(--panel-bg); border: 1px solid var(--panel-border); box-shadow: var(--shadow-lg); padding: 1rem; min-height: auto; }
95
+ .status-message { font-weight: 500; color: var(--text-secondary); text-align: center; font-size: 1.1em; line-height: 1.9; }
96
+
97
+ .loading-animation-wrapper { display: none; flex-direction: column; align-items: center; justify-content: center; gap: 1.8rem; width: 100%; }
98
+ .orbital-loader { width: 110px; height: 110px; position: relative; animation: rotate-loader-orbital 10s linear infinite; }
99
+ .orbit { position: absolute; top: 50%; left: 50%; border: 2px dashed rgba(74, 108, 250, 0.35); border-radius: 50%; transform-origin: center center; }
100
+ .orbit:nth-child(1) { width: 65px; height: 65px; margin: -32.5px 0 0 -32.5px; animation: orbit-spin 3.8s linear infinite; }
101
+ .orbit:nth-child(2) { width: 95px; height: 95px; margin: -47.5px 0 0 -47.5px; animation: orbit-spin 4.8s linear infinite reverse; }
102
+ .orbit .satellite { position: absolute; width: 10px; height: 10px; border-radius: 50%; background-color: var(--accent-primary); box-shadow: 0 0 8px var(--accent-primary), 0 0 12px var(--accent-secondary); }
103
+ .orbit:nth-child(1) .satellite { top: 50%; left: -5px; background-color: var(--accent-secondary); animation: satellite-pulse-2 1.4s 0.2s ease-in-out infinite alternate; }
104
+ .orbit:nth-child(2) .satellite { bottom: -5px; right: 50%; animation: satellite-pulse-1 1.4s 0.4s ease-in-out infinite alternate;}
105
+ .loading-text { font-size: 1.2em; font-weight: 700; color: var(--text-primary); text-align: center; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
106
+
107
+ #result-content { display: none; width: 100%; text-align: center; animation: fadeIn 0.5s ease-out; }
108
+ #result-content h2 { color: var(--text-primary); border-bottom: 2px solid var(--panel-border); padding-bottom: 1rem; margin-bottom: 1rem; font-size: 1.5em; }
109
+ #result-image { max-width: 100%; border-radius: var(--radius-card); border: 2px solid var(--accent-secondary); padding: 5px; box-shadow: var(--shadow-lg); cursor: pointer; transition: transform 0.2s ease; }
110
+ #result-image:hover { transform: scale(1.02); }
111
+
112
+ #result-content a.download-btn {
113
+ display: none; align-items: center; justify-content: center; gap: 10px;
114
+ margin-top: 1.5rem; padding: 0.9rem 1.8rem; font-size: 1.1em; font-weight: 700;
115
+ background: linear-gradient(95deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
116
+ color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer;
117
+ transition: var(--transition-smooth);
118
+ box-shadow: 0 5px 10px -2px var(--accent-primary-glow), 0 5px 10px -2px var(--accent-secondary-glow);
119
+ text-decoration: none;
120
+ }
121
+ #result-content a.download-btn:hover {
122
+ transform: translateY(-4px) scale(1.03);
123
+ box-shadow: 0 7px 18px -3px var(--accent-primary-glow), 0 7px 18px -3px var(--accent-secondary-glow);
124
+ }
125
+ #result-content a.download-btn svg { width: 20px; height: 20px; }
126
+
127
+ .status-message.error { color: #c53030; font-weight: 600; }
128
+
129
+ :root {
130
+ --guide-bg: rgba(255, 255, 255, 0.98);
131
+ --guide-border: rgba(102, 126, 234, 0.2);
132
+ --guide-text-title: #2d3748;
133
+ --guide-text-body: #4a5568;
134
+ --guide-accent: #667eea;
135
+ --primary-gradient-guide: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
136
+ --success-gradient-guide: linear-gradient(135deg, #56ab2f 0%, #a8e063 100%);
137
+ --radius-md-guide: 12px;
138
+ --radius-lg-guide: 20px;
139
+ }
140
+ .ip-reset-guide-container {
141
+ text-align: right; background: var(--guide-bg); backdrop-filter: blur(10px); padding: 40px; border-radius: var(--radius-lg-guide);
142
+ box-shadow: var(--shadow-xl); border: 1px solid var(--guide-border); animation: slideInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1) both;
143
+ width: 100%; position: relative; overflow: hidden; box-sizing: border-box;
144
+ }
145
+ .ip-reset-guide-container::before {
146
+ content: ''; position: absolute; top: 0; left: 0; right: 0; height: 5px; background: var(--primary-gradient-guide);
147
+ }
148
+ .guide-header { display: flex; align-items: center; margin-bottom: 30px; }
149
+ .guide-header-icon { width: 60px; height: 60px; margin-left: 20px; animation: float 3s ease-in-out infinite; }
150
+ .guide-header h2 { font-size: 1.5rem; color: var(--guide-text-title); font-weight: 700; margin: 0; }
151
+ .guide-header p { color: var(--guide-text-body); font-size: 0.8rem; margin-top: 5px; margin-bottom: 0; }
152
+ .guide-content { font-size: 0.95rem; color: var(--guide-text-body); line-height: 1.8; }
153
+ .info-card { background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); border: 1px solid rgba(102, 126, 234, 0.2); border-radius: var(--radius-md-guide); padding: 20px; margin: 20px 0; position: relative; overflow: hidden; }
154
+ .info-card p { font-size: 0.85rem; line-height: 1.7; margin: 0; }
155
+ .info-card::before { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: var(--primary-gradient-guide); }
156
+ .info-card-header { display: flex; align-items: center; margin-bottom: 12px; }
157
+ .info-card-icon { width: 24px; height: 24px; margin-left: 12px; }
158
+ .info-card-title { font-weight: 600; color: var(--guide-text-title); font-size: 1rem; }
159
+ .summary-section { margin-top: 25px; padding: 20px; border-radius: var(--radius-md-guide); background: linear-gradient(135deg, #56ab2f15 0%, #a8e06315 100%); border: 1px solid rgba(86, 171, 47, 0.2); position: relative; overflow: hidden; }
160
+ .summary-section::before { content: ''; position: absolute; top: 0; right: 0; width: 4px; height: 100%; background: var(--success-gradient-guide); }
161
+ .summary-header { display: flex; align-items: center; margin-bottom: 10px; }
162
+ .summary-icon { width: 24px; height: 24px; margin-left: 10px; }
163
+ .summary-title { font-weight: 600; color: #2f5a33; font-size: 1rem; }
164
+ .summary-text { color: #2f5a33; font-size: 0.9rem; line-height: 1.7; }
165
+ .video-button-container { text-align: center; margin: 25px 0 10px 0; }
166
+ .elegant-video-button { display: inline-flex; align-items: center; justify-content: center; padding: 7px 18px; background-color: #f0f2f5; color: var(--guide-accent); border: 1px solid #e2e8f0; text-decoration: none; border-radius: var(--radius-md-guide); font-weight: 600; font-size: 0.8rem; cursor: pointer; font-family: inherit; transition: all 0.3s ease; box-shadow: var(--shadow-sm); }
167
+ .elegant-video-button:hover { background: var(--primary-gradient-guide); color: white; border-color: transparent; transform: translateY(-2px); box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3); }
168
+ .elegant-video-button-icon { width: 16px; height: 16px; margin-left: 8px; fill: currentColor; }
169
+ .guide-actions { display: flex; gap: 15px; margin-top: 30px; padding-top: 25px; border-top: 1px solid #e2e8f0; }
170
+ .action-button { padding: 14px 24px; border: none; border-radius: var(--radius-md-guide); font-size: 0.95rem; font-weight: 600; cursor: pointer; flex: 1; transition: var(--transition-smooth); position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; }
171
+ .action-button-icon { width: 20px; height: 20px; margin-right: 0; margin-left: 8px; }
172
+ .back-button { background: white; color: var(--guide-text-body); border: 2px solid #e2e8f0; flex: 0.4; }
173
+ .back-button:hover { background: #f7fafc; border-color: var(--guide-accent); transform: translateY(-2px); box-shadow: var(--shadow-md); }
174
+ .retry-button { background: var(--primary-gradient-guide); color: white; flex: 0.6; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); }
175
+ .retry-button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); }
176
+
177
+ .examples-section { margin-top: 4rem; animation: fadeIn 0.8s 0.8s ease-out backwards; }
178
+ .examples-section h2 { text-align: center; font-size: 1.8em; font-weight: 800; margin-bottom: 2.5rem; color: var(--text-primary); }
179
+ .examples-list { display: flex; flex-direction: column; align-items: center; gap: 1.5rem; }
180
+ .example-card { background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: var(--shadow-md); border: 2px solid var(--panel-border); cursor: pointer; transition: var(--transition-smooth); display: flex; align-items: center; gap: 1.5rem; padding: 1rem; width: 100%; max-width: 550px; box-sizing: border-box; }
181
+ .example-card:hover { border-color: var(--accent-primary); box-shadow: var(--shadow-lg); transform: translateY(-5px); }
182
+ .example-card img { width: 100px; height: 100px; object-fit: cover; border-radius: var(--radius-input); flex-shrink: 0; }
183
+ .example-prompt { font-size: 0.9rem; color: var(--text-secondary); line-height: 1.7; flex-grow: 1; }
184
+
185
+ .fullscreen-modal {
186
+ display: none; position: fixed; z-index: 1000; top: 0; left: 0;
187
+ width: 100%; height: 100%; background-color: rgba(26, 32, 44, 0.9);
188
+ justify-content: center; align-items: center; animation: fadeIn 0.3s ease;
189
+ }
190
+ .fullscreen-image {
191
+ max-width: 90%; max-height: 90%; object-fit: contain;
192
+ border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.5);
193
+ }
194
+ .close-fullscreen {
195
+ position: absolute; top: 20px; right: 35px; color: #fff; font-size: 40px;
196
+ font-weight: bold; cursor: pointer; transition: color 0.2s, transform 0.2s;
197
+ }
198
+ .close-fullscreen:hover { color: var(--text-tertiary); transform: scale(1.1); }
199
+
200
+ @media (max-width: 600px) {
201
+ body { padding: 1.5rem 1rem; }
202
+ .main-content { padding: 1.5rem; }
203
+ .app-header h1 { font-size: 2em; }
204
+ .example-card { gap: 1rem; }
205
+ .example-card img { width: 80px; height: 80px; }
206
+ }
207
+ </style>
208
+ </head>
209
+ <body>
210
+ <div class="app-container">
211
+ <header class="app-header">
212
+ <div class="header-logo">
213
+ <svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
214
+ <defs>
215
+ <linearGradient id="logo-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
216
+ <stop offset="0%" stop-color="var(--accent-primary)" />
217
+ <stop offset="100%" stop-color="var(--accent-secondary)" />
218
+ </linearGradient>
219
+ </defs>
220
+ <g>
221
+ <path class="house-outline-path" d="M32 4 L 60 28 V 60 H 4 V 28 L 32 4 Z M 22 36 H 42 V 60 H 22 Z M 46 22 L 46 12 L 54 12 L 54 26" />
222
+ <circle class="magic-core" cx="32" cy="40" r="4" />
223
+ <circle class="sparkle" cx="32" cy="28" r="2" />
224
+ <circle class="sparkle" cx="32" cy="28" r="2" />
225
+ <circle class="sparkle" cx="32" cy="28" r="1.5" />
226
+ </g>
227
+ </svg>
228
+ </div>
229
+ <h1>طراح دکوراسیون هوشمند آلفا</h1>
230
+ <p>اتاق رویایی خود را تصور کنید، ما آن را برای شما خلق می‌کنیم. یک عکس از فضای خود آپلود کرده و سبک دلخواهتان را توصیف کنید.</p>
231
+ </header>
232
+
233
+ <main class="main-content" id="main-content">
234
+ <form onsubmit="return false;">
235
+ <div class="form-group">
236
+ <label>
237
+ <span class="label-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg></span>
238
+ تصویر فضای شما
239
+ </label>
240
+ <label class="upload-area" id="upload-area">
241
+ <div class="upload-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg></div>
242
+ <p>عکس خود را اینجا بکشید یا برای <strong>انتخاب فایل</strong> کلیک کنید</p>
243
+ </label>
244
+ <input type="file" id="image-upload" accept="image/*" style="display: none;">
245
+ <div id="file-preview">
246
+ <div id="file-info">
247
+ <img id="image-preview" src="#" alt="پیش‌نمایش" />
248
+ <span id="file-name"></span>
249
+ </div>
250
+ <button type="button" id="remove-file-btn" title="حذف فایل">×</button>
251
+ </div>
252
+ </div>
253
+
254
+ <div class="form-group">
255
+ <label for="prompt-input">
256
+ <span class="label-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0L12 2.69z"></path></svg></span>
257
+ سبک و توضیحات دلخواه
258
+ </label>
259
+ <textarea id="prompt-input" rows="4" placeholder="مثال: یک اتاق نشیمن به سبک اسکاندیناوی با چوب روشن، گیاهان زیاد و یک کاناپه کرم رنگ">یک اتاق نشیمن مدرن با مبلمان مینیمال، رنگ‌های خنثی و نور طبیعی زیاد</textarea>
260
+ </div>
261
+
262
+ <button type="submit" id="submit-btn" class="generate-btn">
263
+ <span class="btn-text">✨ طراحی کن!</span>
264
+ <div class="spinner"></div>
265
+ </button>
266
+ </form>
267
+
268
+ <div id="output-section" class="output-section">
269
+ <div id="status-message" class="status-message">نتیجه طراحی شما در اینجا نمایش داده خواهد شد.</div>
270
+
271
+ <div id="loading-animation" class="loading-animation-wrapper">
272
+ <div class="orbital-loader">
273
+ <div class="orbit"><div class="satellite"></div></div>
274
+ <div class="orbit"><div class="satellite"></div></div>
275
+ </div>
276
+ <p id="loading-text" class="loading-text"></p>
277
+ </div>
278
+
279
+ <div id="result-content">
280
+ <h2>نتیجه طراحی شما:</h2>
281
+ <img id="result-image" src="#" alt="تصویر طراحی شده" />
282
+ <a href="#" id="download-btn" class="download-btn">
283
+ <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 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
284
+ <span>دانلود تصویر</span>
285
+ </a>
286
+ </div>
287
+ </div>
288
+ </main>
289
+
290
+ <section class="examples-section">
291
+ <h2>چند نمونه برای ایده گرفتن</h2>
292
+ <div class="examples-list">
293
+ <div class="example-card" data-image-url="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/bedroom_1.jpg?download=true" data-prompt="یک اتاق خواب شیک به سبک آرت دکو، با یک تخت کینگ‌سایز بزرگ با روتختی طرح هندسی، یک صندلی راحتی مخملی لوکس و یک میز پاتختی آینه‌ای که شکوه اتاق را منعکس می‌کند. آثار هنری الهام گرفته از آرت دکو، حسی از زرق و برق به فضا می‌افزایند."><img src="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/bedroom_1.jpg?download=true" alt="نمونه اتاق خواب آرت دکو"><p class="example-prompt">اتاق خواب شیک به سبک آرت دکو، با تخت کینگ‌سایز، صندلی راحتی مخملی و میز پاتختی آینه‌ای.</p></div>
294
+ <div class="example-card" data-image-url="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/living_room_1.jpg?download=true" data-prompt="یک اتاق نشیمن شیک با زیبایی‌شناسی مدرن اواسط قرن، که یک میز قهوه‌خوری چوبی کلاسیک در مرکز آن قرار دارد، یک ساعت دیواری خورشیدی نمادین آن را تکمیل کرده و یک فرش پرزدار دنج در زیر پا، فضایی گرم و دلنشین ایجاد می‌کند."><img src="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/living_room_1.jpg?download=true" alt="نمونه اتاق نشیمن مدرن"><p class="example-prompt">اتاق نشیمن مدرن اواسط قرن، با میز قهوه‌خوری چوبی، ساعت خورشیدی و فرش پرزدار دنج.</p></div>
295
+ <div class="example-card" data-image-url="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/dinning_room_1.jpg?download=true" data-prompt="یک اتاق غذاخوری دنج که جوهره جذابیت روستیک را با یک میز چوبی محکم به سبک خانه‌های روستایی در مرکز خود به تصویر می‌کشد و با ترکیبی التقاطی از صندلی‌های ناهماهنگ احاطه شده است."><img src="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/dinning_room_1.jpg?download=true" alt="نمونه اتاق غذاخوری روستیک"><p class="example-prompt">اتاق غذاخوری دنج به سبک روستیک، با میز چوبی محکم و صندلی‌های ناهماهنگ و التقاطی.</p></div>
296
+ <div class="example-card" data-image-url="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/dinning_room_3.jpg?download=true" data-prompt="یک اتاق غذاخوری که مظهر ظرافت معاصر است و توسط یک میز ناهارخوری شیک و مینیمال به همراه صندلی‌های مدرن و زیبا شکل گرفته است. وسایل روشنایی هنری در بالا یک نقطه کانونی ایجاد می‌کنند."><img src="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/dinning_room_3.jpg?download=true" alt="نمونه غذاخوری معاصر"><p class="example-prompt">اتاق غذاخوری با ظرافت معاصر، میز ناهارخوری مینیمال، صندلی‌های مدرن و روشنایی هنری.</p></div>
297
+ <div class="example-card" data-image-url="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/image_1.jpg?download=true" data-prompt="یک اتاق خواب مستر پر زرق و برق به سبک هالیوود ریجنسی، با یک تاج تخت باشکوه، مبلمان آینه‌ای که ظرافت را منعکس می‌کند، پارچه‌های مجلل با بافت‌های غنی و تزئینات طلایی باشکوه برای لمس لوکس بودن."><img src="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/image_1.jpg?download=true" alt="نمونه اتاق خواب هالیوودی"><p class="example-prompt">اتاق خواب مستر به سبک هالیوود ریجنسی، با تاج تخت باشکوه، مبلمان آینه‌ای و تزئینات طلایی.</p></div>
298
+ <div class="example-card" data-image-url="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/bedroom_2.jpg?download=true" data-prompt="اتاق خوابی که جذابیت سبک روستایی فرانسوی را با یک تخت نرم با روکش پارچه‌ای، دیوارهایی مزین به کاغذ دیواری گلدار و یک کمد لباس چوبی قدیمی به نمایش می‌گذارد. یک لوستر کریستالی درخشش گرم و دلنشینی را بر فضا می‌تاباند."><img src="https://huggingface.co/spaces/MykolaL/StableDesign/resolve/main/imgs/bedroom_2.jpg?download=true" alt="نمونه اتاق خواب فرانسوی"><p class="example-prompt">اتاق خوابی به سبک روستایی فرانسوی، با تخت پارچه‌ای، کاغذ دیواری گلدار و کمد چوبی قدیمی.</p></div>
299
+ </div>
300
+ </section>
301
+ </div>
302
+
303
+ <div id="fullscreen-modal" class="fullscreen-modal">
304
+ <span id="close-fullscreen" class="close-fullscreen">&times;</span>
305
+ <img id="fullscreen-image" class="fullscreen-image" src="#" alt="نمایش تمام صفحه">
306
+ </div>
307
+
308
+ <script type="module">
309
+ const mainContent = document.getElementById('main-content');
310
+ const imageUpload = document.getElementById('image-upload');
311
+ const uploadArea = document.getElementById('upload-area');
312
+ const filePreview = document.getElementById('file-preview');
313
+ const imagePreview = document.getElementById('image-preview');
314
+ const fileNameSpan = document.getElementById('file-name');
315
+ const removeFileBtn = document.getElementById('remove-file-btn');
316
+ const promptInput = document.getElementById('prompt-input');
317
+ const submitBtn = document.getElementById('submit-btn');
318
+ const btnText = submitBtn.querySelector('.btn-text');
319
+ const btnSpinner = submitBtn.querySelector('.spinner');
320
+ const outputSection = document.getElementById('output-section');
321
+ const statusMessage = document.getElementById('status-message');
322
+ const loadingAnimation = document.getElementById('loading-animation');
323
+ const loadingText = document.getElementById('loading-text');
324
+ const resultContent = document.getElementById('result-content');
325
+ const resultImage = document.getElementById('result-image');
326
+ const exampleCards = document.querySelectorAll('.example-card');
327
+ const downloadBtn = document.getElementById('download-btn');
328
+ const fullscreenModal = document.getElementById('fullscreen-modal');
329
+ const fullscreenImage = document.getElementById('fullscreen-image');
330
+ const closeFullscreenBtn = document.getElementById('close-fullscreen');
331
+
332
+ let uploadedFile = null;
333
+ let currentRunId = '';
334
+ let pollInterval = null;
335
+ let emptyActionCounter = 0;
336
+
337
+ const GPU_QUOTA_ERROR_HTML = `
338
+ <div class="ip-reset-guide-container">
339
+ <div class="guide-header"><svg class="guide-header-icon" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <defs><lineargradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color: #667eea; stop-opacity: 1;"></stop><stop offset="100%" style="stop-color: #764ba2; stop-opacity: 1;"></stop></lineargradient></defs> <circle cx="50" cy="50" r="45" fill="url(#grad1)" opacity="0.1"></circle><circle cx="50" cy="50" r="35" fill="none" stroke="url(#grad1)" stroke-width="2" opacity="0.3"></circle><path d="M35 50 L45 60 L65 40" stroke="url(#grad1)" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"></path><circle cx="65" cy="35" r="8" fill="#fee140"></circle><path d="M62 35 L68 35 M65 32 L65 38" stroke="white" stroke-width="2" stroke-linecap="round"></path> </svg>
340
+ <div>
341
+ <h2>یک قدم تا ساخت تصاویر جدید</h2>
342
+ <p>نیازمند تغییر نقطه دستیابی</p>
343
+ </div>
344
+ </div>
345
+ <div class="guide-content">
346
+ <div class="info-card">
347
+ <div class="info-card-header"><svg class="info-card-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" fill="#667eea" opacity="0.2"></path><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke="#667eea" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg> <span class="info-card-title">راه حل سریع</span></div>
348
+ <p>طبق ویدیو آموزشی پایین بین نقطه دستیابی جابجا شوید تلاش مجدد بزنید تا تصاویر مجدداً تولید بشه.</p>
349
+ </div>
350
+ <div class="summary-section">
351
+ <div class="summary-header"><svg class="summary-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="#56ab2f" opacity="0.2"></circle><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" stroke="#56ab2f" stroke-width="2"></path><path d="M9 12l2 2 4-4" stroke="#56ab2f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg> <span class="summary-title">خلاصه راهنما</span></div>
352
+ <div class="summary-text">هربار که این صفحه را مشاهده کردید: از اینترنت سیم‌کارت استفاده کنید، VPN را خاموش کرده و طبق ویدیو آموزشی پایین نقطه دستیابی رو تغییر دهید. «تلاش مجدد» کلیک کنید. با این روش ساده می‌توانید به صورت نامحدود تصویر بسازید! ☘️</div>
353
+ </div>
354
+ <div class="video-button-container"><button id="tutorialLinkBtn" class="elegant-video-button"> <svg class="elegant-video-button-icon" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24"><path d="M8 5v14l11-7z"></path></svg> <span>دیدن ویدیو آموزشی استفاده نامحدود</span> </button></div>
355
+ </div>
356
+ <div class="guide-actions">
357
+ <button class="action-button back-button"> <svg class="action-button-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M19 12H5M12 19l-7-7 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg> <span>بازگشت</span> </button>
358
+ <button class="action-button retry-button"> <svg class="action-button-icon" viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M23 4v6h-6M1 20v-6h6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path></svg> <span>تلاش مجدد</span> </button>
359
+ </div>
360
+ </div>`;
361
+
362
+ const setUIState = (state) => {
363
+ submitBtn.disabled = state === 'loading';
364
+ if (state === 'loading') {
365
+ btnText.style.display = 'none';
366
+ btnSpinner.style.display = 'inline-block';
367
+ outputSection.classList.remove('has-content');
368
+ statusMessage.style.display = 'none';
369
+ resultContent.style.display = 'none';
370
+ loadingAnimation.style.display = 'flex';
371
+ } else {
372
+ btnText.style.display = 'inline-block';
373
+ btnSpinner.style.display = 'none';
374
+ btnText.textContent = "✨ طراحی کن!";
375
+ if (state !== 'idle') {
376
+ loadingAnimation.style.display = 'none';
377
+ outputSection.classList.add('has-content');
378
+ }
379
+ }
380
+ };
381
+
382
+ const handleFileSelect = (file) => {
383
+ if (file && file.type.startsWith('image/')) {
384
+ uploadedFile = file;
385
+ const reader = new FileReader();
386
+ reader.onload = (e) => { imagePreview.src = e.target.result; };
387
+ reader.readAsDataURL(uploadedFile);
388
+ fileNameSpan.textContent = file.name;
389
+ uploadArea.style.display = 'none';
390
+ filePreview.style.display = 'flex';
391
+ } else {
392
+ uploadedFile = null;
393
+ alert("لطفاً یک فایل تصویر معتبر انتخاب کنید.");
394
+ }
395
+ };
396
+
397
+ async function uploadFileToDocker(file, suffix) {
398
+ const formData = new FormData();
399
+ const ext = file.name.split('.').pop().toLowerCase();
400
+ formData.append('run_id', `${currentRunId}_${suffix}`);
401
+ formData.append('ext', ext);
402
+ formData.append('file', file);
403
+
404
+ const response = await fetch('/api/webhook/upload', { method: 'POST', body: formData });
405
+ if (!response.ok) throw new Error("خطا در آپلود فایل.");
406
+ return ext;
407
+ }
408
+
409
+ function safeBase64Encode(str) {
410
+ return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => {
411
+ return String.fromCharCode('0x' + p1);
412
+ }));
413
+ }
414
+
415
+ const applyExample = async (card) => {
416
+ const imageUrl = card.dataset.imageUrl;
417
+ const promptText = card.dataset.prompt;
418
+ promptInput.value = "در حال بارگذاری نمونه...";
419
+ try {
420
+ const response = await fetch(imageUrl);
421
+ const blob = await response.blob();
422
+ const fileName = imageUrl.split('/').pop().split('?')[0];
423
+ const file = new File([blob], fileName, { type: blob.type });
424
+ handleFileSelect(file);
425
+ promptInput.value = promptText;
426
+ mainContent.scrollIntoView({ behavior: 'smooth', block: 'start' });
427
+ } catch (error) {
428
+ console.error("Failed to load example image:", error);
429
+ alert("مشکلی در بارگذاری تصویر نمونه پیش آمد. لطفاً اتصال اینترنت خود را بررسی کنید.");
430
+ promptInput.value = "";
431
+ }
432
+ };
433
+
434
+ exampleCards.forEach(card => {
435
+ card.addEventListener('click', () => applyExample(card));
436
+ });
437
+
438
+ uploadArea.addEventListener('click', () => imageUpload.click());
439
+ imageUpload.addEventListener('change', (e) => handleFileSelect(e.target.files[0]));
440
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
441
+ uploadArea.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); }, false);
442
+ });
443
+ ['dragenter', 'dragover'].forEach(eventName => uploadArea.addEventListener(eventName, () => uploadArea.classList.add('drag-over')));
444
+ ['dragleave', 'drop'].forEach(eventName => uploadArea.addEventListener(eventName, () => uploadArea.classList.remove('drag-over')));
445
+ uploadArea.addEventListener('drop', e => handleFileSelect(e.dataTransfer.files[0]));
446
+
447
+ removeFileBtn.addEventListener('click', () => {
448
+ uploadedFile = null;
449
+ imageUpload.value = '';
450
+ uploadArea.style.display = 'block';
451
+ filePreview.style.display = 'none';
452
+ promptInput.value = "یک اتاق نشیمن مدرن با مبلمان مینیمال، رنگ‌های خنثی و نور طبیعی زیاد";
453
+ });
454
+
455
+ const retryLastAttempt = () => {
456
+ submitBtn.click();
457
+ };
458
+
459
+ const hideGpuErrorGuide = () => {
460
+ statusMessage.innerHTML = 'نتیجه طراحی شما در اینجا نمایش داده خواهد شد.';
461
+ statusMessage.classList.remove('error');
462
+ outputSection.classList.remove('has-content');
463
+ outputSection.style.padding = '';
464
+ outputSection.style.border = '';
465
+ outputSection.style.background = '';
466
+ };
467
+
468
+ const handleErrorState = (error) => {
469
+ setUIState('error');
470
+ statusMessage.style.display = 'block';
471
+ statusMessage.classList.add('error');
472
+
473
+ const errorMessage = String(error.message || error).toLowerCase();
474
+
475
+ if (errorMessage.includes("gpu") || errorMessage.includes("quota") || errorMessage.includes("exceeded") || errorMessage.includes("limit") || errorMessage.includes("capacity")) {
476
+ outputSection.style.padding = '0';
477
+ outputSection.style.border = 'none';
478
+ outputSection.style.background = 'transparent';
479
+ statusMessage.innerHTML = GPU_QUOTA_ERROR_HTML;
480
+
481
+ statusMessage.querySelector('.back-button').addEventListener('click', hideGpuErrorGuide);
482
+ statusMessage.querySelector('.retry-button').addEventListener('click', retryLastAttempt);
483
+ statusMessage.querySelector('#tutorialLinkBtn').addEventListener('click', () => {
484
+ const tutorialUrl = '#/nav/online/news/getSingle/1149635/eyJpdiI6IjhHVGhPQWJwb3E0cjRXbnFWTW5BaUE9PSIsInZhbHVlIjoiS1V0dTdvT21wbXAwSXZaK1RCTG1pVXZqdlFJa1hXV1RKa2FLem9zU3pXMjd5MmlVOGc2YWY0NVdNR3h3Smp1aSIsIm1hYyI6IjY1NTA5ZDYzMjAzMTJhMGQyMWQ4NjA4ZDgyNGZjZDVlY2MyNjdiMjA2NWYzOWRjY2M4ZmVjYWRlMWNlMWQ3ODEiLCJ0YWciOiIifQ==/21135210';
485
+ parent.postMessage({ type: 'NAVIGATE_TO_URL', url: tutorialUrl }, '*');
486
+ });
487
+ } else {
488
+ statusMessage.innerHTML = `<div style="padding: 1rem;"><b>خطای ارتباط!</b><br>مشکلی در برقراری ارتباط با سرور پیش آمده است. لطفاً چند لحظه دیگر امتحان کنید.</div>`;
489
+ }
490
+ };
491
+
492
+ function startPolling(runId) {
493
+ if (pollInterval) clearInterval(pollInterval);
494
+ emptyActionCounter = 0;
495
+
496
+ pollInterval = setInterval(async () => {
497
+ try {
498
+ // بررسی مستقیم برای یافتن نتیجه تصویر خروجی
499
+ try {
500
+ const imgCheck = await fetch(`/static/images/${runId}.png`, { method: 'HEAD' });
501
+ if (imgCheck.ok) {
502
+ clearInterval(pollInterval);
503
+ setUIState('success');
504
+ resultImage.src = `/static/images/${runId}.png`;
505
+ downloadBtn.style.display = 'inline-flex';
506
+ resultContent.style.display = 'block';
507
+ return;
508
+ }
509
+ } catch(e) {}
510
+
511
+ // بررسی فعال بودن رانرهای گیت‌هاب جهت تغییر وضعیت متن
512
+ const activeRes = await fetch('/api/actions/active');
513
+ const activeData = await activeRes.json();
514
+ const hasActiveActions = activeData.status === 'success' && activeData.actions.length > 0;
515
+
516
+ const statusRes = await fetch(`/api/status/${runId}`);
517
+ const statusData = await statusRes.json();
518
+
519
+ if (statusData.status === 'ready') {
520
+ const urlLower = statusData.url.toLowerCase();
521
+ if (urlLower.endsWith('.png') || urlLower.endsWith('.jpg') || urlLower.endsWith('.jpeg')) {
522
+
523
+ if (hasActiveActions) {
524
+ emptyActionCounter = 0;
525
+ loadingText.textContent = "تلاش رانر قبلی به مشکل خورد. در صف تلاش با ران�� موازی جدید...";
526
+ return;
527
+ } else {
528
+ emptyActionCounter++;
529
+ if (emptyActionCounter < 4) {
530
+ loadingText.textContent = "در حال بازنشانی و تخصیص رانر جدید (لطفا صبور باشید)...";
531
+ return;
532
+ }
533
+ }
534
+
535
+ clearInterval(pollInterval);
536
+ handleErrorState(new Error("GPU quota exceeded"));
537
+ }
538
+ } else {
539
+ if (hasActiveActions) {
540
+ emptyActionCounter = 0;
541
+ loadingText.textContent = "هوش مصنوعی در حال چیدمان و دکوراسیون سه بعدی محیط شما...";
542
+ } else {
543
+ loadingText.textContent = "در انتظار تخصیص رانر پردازشی آزاد...";
544
+ }
545
+ }
546
+ } catch (e) {
547
+ console.error("Error polling:", e);
548
+ }
549
+ }, 4000);
550
+ }
551
+
552
+ submitBtn.addEventListener('click', async () => {
553
+ if (!uploadedFile || promptInput.value.trim() === "") {
554
+ alert("لطفاً هم تصویر را آپلود کنید و هم متن توضیحات را وارد نمایید.");
555
+ return;
556
+ }
557
+
558
+ setUIState('loading');
559
+ currentRunId = 'design' + Math.random().toString(36).substring(2, 14);
560
+
561
+ try {
562
+ loadingText.textContent = "در حال آپلود و فشرده‌سازی تصویر فضا...";
563
+ const imgExt = await uploadFileToDocker(uploadedFile, 'design_img');
564
+
565
+ loadingText.textContent = "ارسال درخواست و تخصیص موقعیت در صف پردازش...";
566
+
567
+ const b64Prompt = safeBase64Encode(promptInput.value.trim());
568
+ const designPayload = `DESIGNCONFIG_userRunId_${currentRunId}_imgExt_${imgExt}_prompt_${b64Prompt}`;
569
+
570
+ const response = await fetch('/api/generate', {
571
+ method: 'POST',
572
+ headers: { 'Content-Type': 'application/json' },
573
+ body: JSON.stringify({
574
+ prompt: designPayload,
575
+ width: 1024,
576
+ height: 1024,
577
+ action_name: 'tarahi-khoneh'
578
+ })
579
+ });
580
+
581
+ const data = await response.json();
582
+
583
+ if (data.status === 'success') {
584
+ startPolling(data.run_id);
585
+ } else {
586
+ throw new Error(data.message || 'خطا در ثبت نوبت صف.');
587
+ }
588
+
589
+ } catch (error) {
590
+ console.error("An error occurred:", error);
591
+ handleErrorState(error);
592
+ }
593
+ });
594
+
595
+ resultImage.addEventListener('click', () => {
596
+ if (resultImage.src && resultImage.src !== window.location.href + '#') {
597
+ fullscreenImage.src = resultImage.src;
598
+ fullscreenModal.style.display = 'flex';
599
+ }
600
+ });
601
+
602
+ const closeModal = () => { fullscreenModal.style.display = 'none'; };
603
+ closeFullscreenBtn.addEventListener('click', closeModal);
604
+ fullscreenModal.addEventListener('click', (e) => { if (e.target === fullscreenModal) { closeModal(); } });
605
+
606
+ downloadBtn.addEventListener('click', async (event) => {
607
+ const imageUrl = resultImage.src;
608
+ if (!imageUrl || imageUrl.endsWith('#')) return;
609
+
610
+ if (window.parent && window.parent !== window) {
611
+ event.preventDefault();
612
+ window.parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: imageUrl }, '*');
613
+ } else {
614
+ event.preventDefault();
615
+ try {
616
+ const response = await fetch(imageUrl);
617
+ const blob = await response.blob();
618
+ const objectUrl = URL.createObjectURL(blob);
619
+ const a = document.createElement('a');
620
+ a.href = objectUrl;
621
+ a.download = `alpha-design-${Date.now()}.png`;
622
+ document.body.appendChild(a);
623
+ a.click();
624
+ document.body.removeChild(a);
625
+ URL.revokeObjectURL(objectUrl);
626
+ } catch (e) {
627
+ console.error("Direct download failed:", e);
628
+ alert("خطا در دانلود مستقیم فایل.");
629
+ }
630
+ }
631
+ });
632
+
633
+ setUIState('idle');
634
+ </script>
635
+ </body>
636
+ </html>