-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
363 lines (313 loc) · 16.2 KB
/
script.js
File metadata and controls
363 lines (313 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
document.addEventListener("DOMContentLoaded", function() {
const filterButtons = document.querySelectorAll(".filter_buttons button");
const filterableCards = document.querySelectorAll(".filterable_cards .card");
const tags = document.querySelectorAll(".tag");
const downloadButtons = document.querySelectorAll(".download-btn");
const cardImages = document.querySelectorAll(".card_img");
const modal = document.getElementById("imageModal");
const modalImage = document.getElementById("modalImage");
const closeBtn = document.querySelector(".close");
const zoomInBtn = document.getElementById("zoomIn");
const zoomOutBtn = document.getElementById("zoomOut");
const resetZoomBtn = document.getElementById("resetZoom");
let currentScale = 1;
const scaleStep = 0.2;
const maxScale = 3;
const minScale = 0.5;
// تابع اصلی برای فیلتر کردن کارتها
const filterCards = (filterValue) => {
// بهروزرسانی دکمههای فیلتر
const prevActive = document.querySelector(".filter_buttons .active");
if (prevActive) prevActive.classList.remove("active");
// پیدا کردن دکمه مربوطه و فعال کردن آن
const targetButton = Array.from(filterButtons).find(button =>
button.dataset.name === filterValue
);
if (targetButton) {
targetButton.classList.add("active");
}
// فیلتر کردن کارتها
filterableCards.forEach(card => {
const cardTags = card.dataset.tags.split(',');
if (filterValue === "all" || cardTags.includes(filterValue)) {
card.classList.remove("hide");
} else {
card.classList.add("hide");
}
});
};
// اضافه کردن event listener برای دکمههای فیلتر
filterButtons.forEach(button => {
button.addEventListener("click", (e) => {
filterCards(e.target.dataset.name);
});
});
// اضافه کردن event listener برای تگها
tags.forEach(tag => {
tag.addEventListener("click", (e) => {
e.stopPropagation(); // جلوگیری از انتشار رویداد
const tagName = e.target.dataset.name;
// اگر تگ مربوطه از قبل انتخاب نشده باشد
const btn = document.querySelector(`.filter_buttons button[data-name="${tagName}"]`);
const isTagAlreadyActive = btn ? btn.classList.contains("active") : false;
if (!isTagAlreadyActive) {
filterCards(tagName);
}
});
});
// اضافه کردن event listener برای دکمههای دانلود
downloadButtons.forEach(button => {
button.addEventListener("click", function(e) {
e.stopPropagation();
const fileUrl = this.dataset.url;
const fileTitle = this.dataset.title;
// شبیهسازی فرآیند دانلود
const originalHTML = this.innerHTML;
this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> در حال دانلود...';
this.disabled = true;
// ایجاد لینک دانلود بدون باز کردن تب جدید
const downloadLink = document.createElement('a');
downloadLink.href = fileUrl;
downloadLink.style.display = 'none';
// اضافه کردن لینک به صفحه و کلیک کردن روی آن
document.body.appendChild(downloadLink);
setTimeout(() => {
try {
downloadLink.click();
} catch (error) {
// هیچ پیامی نمایش داده نمیشود
}
// حذف لینک از صفحه
document.body.removeChild(downloadLink);
this.innerHTML = originalHTML;
this.disabled = false;
}, 1000);
});
});
// نمایش تصویر در مودال با کلیک روی تصویر کارت (همان رفتار نسخهی 1)
cardImages.forEach(imageContainer => {
imageContainer.addEventListener("click", function() {
const img = this.querySelector('img');
const imageSrc = img.getAttribute('data-src') || img.src;
modalImage.src = imageSrc;
modal.style.display = "block";
currentScale = 1;
modalImage.style.transform = `scale(${currentScale})`;
document.body.style.overflow = "hidden"; // جلوگیری از اسکرول صفحه
});
});
// بستن مودال
closeBtn.addEventListener("click", function() {
modal.style.display = "none";
document.body.style.overflow = "auto"; // فعال کردن مجدد اسکرول
});
// بستن مودال با کلیک خارج از تصویر
modal.addEventListener("click", function(e) {
if (e.target === modal) {
modal.style.display = "none";
document.body.style.overflow = "auto"; // فعال کردن مجدد اسکرول
}
});
// بزرگ کردن تصویر (modal)
zoomInBtn.addEventListener("click", function() {
if (currentScale < maxScale) {
currentScale += scaleStep;
modalImage.style.transform = `scale(${currentScale})`;
}
});
// کوچک کردن تصویر (modal)
zoomOutBtn.addEventListener("click", function() {
if (currentScale > minScale) {
currentScale -= scaleStep;
modalImage.style.transform = `scale(${currentScale})`;
}
});
// بازنشانی زوم (modal)
resetZoomBtn.addEventListener("click", function() {
currentScale = 1;
modalImage.style.transform = `scale(${currentScale})`;
});
// بستن مودال با کلید ESC
document.addEventListener("keydown", function(e) {
if (e.key === "Escape" && modal.style.display === "block") {
modal.style.display = "none";
document.body.style.overflow = "auto"; // فعال کردن مجدد اسکرول
}
});
/* -----------------------------
end of original ver1 logic
-----------------------------*/
});
/* -----------------------------
Latest font tester logic (added on top of ver1; doesn't modify ver1 behavior)
This block loads fonts list via jsDelivr (fonts.json), populates the select
alphabetically, and handles tester controls (wght, size, line-height, ss01, tnum, lang).
-----------------------------*/
document.addEventListener("DOMContentLoaded", function() {
const vf = {
wght: document.getElementById('wght'),
size: document.getElementById('size'),
lh: document.getElementById('lh'),
wghtValue: document.getElementById('wghtValue'),
sizeValue: document.getElementById('sizeValue'),
lhValue: document.getElementById('lhValue'),
sampleInput: document.getElementById('sampleInput'),
sample: document.getElementById('vfSample'),
fontSelect: document.getElementById('vfFontSelect'),
resetBtn: document.getElementById('resetVf'),
langSelect: document.getElementById('vfLangSelect'),
ss01: document.getElementById('ss01'),
tnum: document.getElementById('tnum')
};
const sampleTexts = {
fa: 'فاصلهٔ بین خط کرسی هر سطر تا خط کرسی سطر بعدی در هر متن را فاصلهٔ بین سطر یا پایه حروف میگویند. دلیل آن که کلمهٔ متن را بکار میبریم این است که مقدار این فاصله را میتوان هنگام حروفچینی متن متناسب با نوع قلم آن تغییر داد. این فاصله نقش مهمی را هنگام خواندن ایفا میکند.\nاعداد: ۰۱۲۳۴۵۶۷۸۹',
ar: 'المسافة بين خط الأساس لكل سطر وخط الأساس للسطر التالي في أي نص تُسمى مسافة السطر أو المسافة الأساسية للحروف. والسبب في استخدامنا كلمة نص هو أنه يمكن تعديل مقدار هذه المسافة أثناء تنضيد النص حسب نوع الخط. تلعب هذه المسافة دورًا مهمًا أثناء القراءة.\nالأرقام: ٠١٢٣٤٥٦٧٨٩',
ku: 'دووری نێوان خەت کرسی هەموو ڕیزێک و خەت کرسی ڕیزە داهاتوو لە هەر دەقێکدا دووری نێوان ڕیزە یان بەیدە هەروەها ناونراوە. هۆکاری بەکارهێنانی وشەی دەق ئەوەیە کە مەقداری ئەم دووریا دەکرێت لەکاتی نوسینەوەی مەکۆ پێشنیار بکرێت بە پەیوەندی گۆڕینی فۆنت. ئەم دووریە شاندێکی گرنگ دەکات کاتێک دەتوانرێت خوێندراو.\nژمارەکان: ٠١٢٣٤٥٦٧٨٩',
en: 'The distance between the baseline of each line and the baseline of the next line in any text is called the line spacing or leading. The reason we use the word "text" is that the amount of this spacing can be adjusted during typesetting according to the font type. This spacing plays an important role in reading.\nDigits: 0123456789'
};
// jsDelivr-hosted fonts JSON (sample repo). Update to actual path if needed.
const fontsJsonUrl = 'https://cdn.jsdelivr.net/gh/AzadQalam/sample-fonts@main/fonts.json';
const fallbackFonts = [
{ name: 'AQNormalSans-VF', family: 'AQNormalSans-VF', css: '' } // No CSS needed as it's loaded via @font-face
];
function ensureCssLoaded(href) {
if (!href) return Promise.resolve();
if (document.querySelector(`link[data-font-href="${href}"]`)) return Promise.resolve();
return new Promise((resolve) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.setAttribute('data-font-href', href);
link.onload = () => resolve();
link.onerror = () => resolve();
document.head.appendChild(link);
});
}
async function loadFontsList() {
let fonts = fallbackFonts;
try {
const res = await fetch(fontsJsonUrl, { cache: 'force-cache' });
if (res.ok) {
const json = await res.json();
if (Array.isArray(json) && json.length) {
fonts = json.map(f => ({
name: f.name || f.family || f,
family: f.family || f.name || f,
css: f.css || ''
}));
}
}
} catch (e) {
// fallback
}
fonts.sort((a, b) => a.name.localeCompare(b.name, 'fa', { sensitivity: 'base' }));
// populate select
vf.fontSelect.innerHTML = '';
fonts.forEach(f => {
const opt = document.createElement('option');
opt.textContent = f.name;
opt.value = f.family;
if (f.css) opt.setAttribute('data-css', f.css);
vf.fontSelect.appendChild(opt);
});
const prefer = 'AQNormalSans-VF';
const preferredOption = Array.from(vf.fontSelect.options).find(o => o.value === prefer);
if (preferredOption) vf.fontSelect.value = prefer;
else if (vf.fontSelect.options.length) vf.fontSelect.selectedIndex = 0;
const initialOpt = vf.fontSelect.options[vf.fontSelect.selectedIndex];
const initialCss = initialOpt ? initialOpt.dataset.css : '';
await ensureCssLoaded(initialCss);
applyVfSettings();
}
const applyVfSettings = () => {
const chosenFont = vf.fontSelect.value;
vf.sample.style.fontFamily = chosenFont ? `'${chosenFont}', sans-serif` : "'AQNormalSans-VF', sans-serif";
const wght = parseFloat(vf.wght.value || 100);
const size = parseFloat(vf.size.value || 18);
const lh = parseFloat(vf.lh.value || 1.5);
vf.wghtValue.textContent = Math.round(wght);
vf.sizeValue.textContent = Math.round(size);
vf.lhValue.textContent = lh.toFixed(2);
vf.sample.style.fontVariationSettings = `'wght' ${wght}`;
vf.sample.style.fontSize = `${size}px`;
vf.sample.style.lineHeight = `${lh}`;
const features = [];
if (vf.ss01.checked) features.push(`'ss01' 1`);
if (vf.tnum.checked) features.push(`'tnum' 1`);
vf.sample.style.fontFeatureSettings = features.join(', ');
vf.sample.style.textAlign = 'right';
const lang = vf.langSelect.value;
if (lang === 'ar' || lang === 'fa' || lang === 'ku') vf.sample.setAttribute('dir', 'rtl');
else vf.sample.setAttribute('dir', 'ltr');
};
// bootstrap
loadFontsList();
vf.fontSelect.addEventListener('change', async function() {
const opt = this.options[this.selectedIndex];
const css = opt ? opt.dataset.css : '';
await ensureCssLoaded(css);
applyVfSettings();
});
vf.wght.addEventListener('input', applyVfSettings);
vf.size.addEventListener('input', applyVfSettings);
vf.lh.addEventListener('input', applyVfSettings);
vf.ss01.addEventListener('change', applyVfSettings);
vf.tnum.addEventListener('change', applyVfSettings);
vf.langSelect.addEventListener('change', function() {
const val = this.value;
const currentTextarea = vf.sampleInput.value || '';
const defaultForLang = sampleTexts[val];
const knownSamples = Object.values(sampleTexts);
if (!currentTextarea || knownSamples.includes(currentTextarea)) {
vf.sampleInput.value = defaultForLang;
vf.sample.textContent = defaultForLang;
} else {
vf.sample.textContent = currentTextarea;
}
applyVfSettings();
});
vf.sampleInput.addEventListener('input', function() {
vf.sample.textContent = this.value;
});
vf.sample.addEventListener('input', function() {
vf.sampleInput.value = this.textContent;
});
vf.resetBtn.addEventListener('click', function() {
vf.wght.value = 100;
vf.size.value = 18;
vf.lh.value = 1.5;
vf.ss01.checked = false;
vf.tnum.checked = false;
const prefer = Array.from(vf.fontSelect.options).find(o => o.value === 'AQNormalSans-VF');
if (prefer) vf.fontSelect.value = 'AQNormalSans-VF';
else if (vf.fontSelect.options.length) vf.fontSelect.selectedIndex = 0;
vf.langSelect.value = 'fa';
vf.sampleInput.value = sampleTexts['fa'];
vf.sample.textContent = vf.sampleInput.value;
const opt = vf.fontSelect.options[vf.fontSelect.selectedIndex];
const css = opt ? opt.dataset.css : '';
ensureCssLoaded(css).then(applyVfSettings);
});
if (vf.sampleInput.value) vf.sample.textContent = vf.sampleInput.value;
/* -----------------------------
Add fold/unfold toggle logic for the tester (minimal; does not change other behavior)
-----------------------------*/
const vfToggleBtn = document.getElementById('vfToggleBtn');
const vfWrapper = document.getElementById('vfWrapper');
if (vfToggleBtn && vfWrapper) {
vfToggleBtn.addEventListener('click', function() {
const expanded = this.getAttribute('aria-expanded') === 'true';
if (!expanded) {
vfWrapper.classList.remove('collapsed');
this.setAttribute('aria-expanded', 'true');
this.textContent = 'بستن تست';
// reapply settings after unfold
setTimeout(() => { applyVfSettings(); const focusTarget = document.getElementById('wght'); if (focusTarget) focusTarget.focus(); }, 250);
} else {
vfWrapper.classList.add('collapsed');
this.setAttribute('aria-expanded', 'false');
this.textContent = 'تست آنلاین تمام فونت ها';
}
});
}
});