Загрузка данных
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Звёздный глобус Ø1000 мм — 24 лепестка — J2000</title>
<style>
body {
margin: 0;
padding: 20px;
font-family: Arial, sans-serif;
background: #eeeeee;
color: #111;
}
.panel {
max-width: 1000px;
margin: 0 auto 20px;
padding: 20px;
background: white;
border-radius: 8px;
box-sizing: border-box;
}
h1, h2 {
margin-top: 0;
}
button {
padding: 12px 18px;
margin: 5px;
border: 1px solid #333;
background: white;
border-radius: 5px;
cursor: pointer;
font-size: 15px;
}
button:hover {
background: #eeeeee;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#status {
margin-top: 15px;
padding: 12px;
background: #f5f5f5;
font-family: monospace;
white-space: pre-line;
border-radius: 4px;
}
#preview {
overflow: auto;
text-align: center;
}
#preview svg {
width: 100%;
max-width: 500px;
height: auto;
background: white;
border: 1px solid #aaa;
}
.small {
font-size: 13px;
color: #555;
line-height: 1.5;
}
.stats {
margin-top: 15px;
font-size: 14px;
color: #333;
}
</style>
</head>
<body>
<div class="panel">
<h1>Звёздный глобус Ø1000 мм</h1>
<p>
<b>24 лепестка</b> ·
<b>J2000</b> ·
звёзды до <b>5.0m</b>
</p>
<p class="small">
Чёрные звёзды на белом фоне.
Каждый лепесток охватывает 15° прямого восхождения.
SVG имеют физические размеры в миллиметрах.
При печати необходимо использовать масштаб 100%.
</p>
<button id="downloadAllButton" onclick="downloadAll()" disabled>
Скачать 24 SVG
</button>
<button id="downloadZipButton" onclick="downloadZIP()" disabled>
Скачать ZIP
</button>
<div id="status">
Загрузка каталога...
</div>
<div id="stats" class="stats"></div>
</div>
<div class="panel">
<h2>Предпросмотр лепестка №1</h2>
<div id="preview"></div>
</div>
<script>
/* =========================================================
ОСНОВНЫЕ ПАРАМЕТРЫ
========================================================= */
const DIAMETER_MM = 1000;
const RADIUS_MM =
DIAMETER_MM / 2;
const NUMBER_OF_GORES = 24;
const GORE_DEGREES =
360 / NUMBER_OF_GORES;
const MAX_MAGNITUDE = 5.0;
const TAB_MM = 8;
const MARGIN_MM = 12;
/*
* Yale Bright Star Catalog.
*
* J2000 координаты.
*/
const CATALOG_URL =
"https://brettonw.github.io/YaleBrightStarCatalog/bsc5.json";
let stars = [];
let catalogLoaded = false;
/* =========================================================
ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
========================================================= */
function degToRad(deg) {
return deg * Math.PI / 180;
}
function radToDeg(rad) {
return rad * 180 / Math.PI;
}
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
function fmt(n) {
return Number(
Number(n).toFixed(3)
);
}
function setStatus(text) {
const element =
document.getElementById("status");
element.textContent = text;
}
function normalizeRA(ra) {
let value =
Number(ra) % 360;
if (value < 0) {
value += 360;
}
return value;
}
/*
* Угол в диапазоне [-180, +180).
*/
function signedAngle(deg) {
let x =
((deg + 180) % 360 + 360) % 360 - 180;
return x;
}
/* =========================================================
ПАРСИНГ RA
========================================================= */
/*
* Поддерживаются варианты:
*
* 12.345
* "12h 34m 56.7s"
* "12:34:56.7"
* "12 34 56.7"
*/
function parseRA(value) {
if (
value === undefined ||
value === null
) {
return null;
}
if (typeof value === "number") {
if (!Number.isFinite(value)) {
return null;
}
/*
* Готовое поле RA из bsc5.json
* обычно представлено как строка.
*
* Числовое значение трактуем
* как часы.
*/
if (value >= 0 && value <= 24) {
return value * 15;
}
return normalizeRA(value);
}
const text =
String(value)
.trim()
.replace(/h/gi, " ")
.replace(/m/gi, " ")
.replace(/s/gi, " ")
.replace(/:/g, " ")
.replace(/,/g, ".")
.replace(/\s+/g, " ");
const parts =
text.split(" ")
.filter(Boolean);
if (!parts.length) {
return null;
}
const h =
Number(parts[0]);
const m =
parts.length > 1
? Number(parts[1])
: 0;
const s =
parts.length > 2
? Number(parts[2])
: 0;
if (
!Number.isFinite(h) ||
!Number.isFinite(m) ||
!Number.isFinite(s)
) {
return null;
}
/*
* Если строка похожа на десятичные градусы
* отдельно это не определяем:
* для RA каталога используется часы.
*/
return normalizeRA(
15 * (
h +
m / 60 +
s / 3600
)
);
}
/* =========================================================
ПАРСИНГ DECLINATION
========================================================= */
/*
* Поддерживаются:
*
* +45 12 33
* -45 12 33
* +45° 12′ 33″
* +45:12:33
* числовое значение градусов
*/
function parseDec(value) {
if (
value === undefined ||
value === null
) {
return null;
}
if (typeof value === "number") {
if (!Number.isFinite(value)) {
return null;
}
if (
value < -90 ||
value > 90
) {
return null;
}
return value;
}
const text =
String(value)
.trim()
.replace(/°/g, " ")
.replace(/′/g, " ")
.replace(/″/g, " ")
.replace(/:/g, " ")
.replace(/,/g, ".")
.replace(/\s+/g, " ");
if (!text) {
return null;
}
const negative =
text.startsWith("-") ||
text.startsWith("−");
const cleaned =
text.replace(
/^[+\-−]/,
""
);
const parts =
cleaned.split(" ")
.filter(Boolean);
const d =
Number(parts[0]);
const m =
parts.length > 1
? Number(parts[1])
: 0;
const s =
parts.length > 2
? Number(parts[2])
: 0;
if (
!Number.isFinite(d) ||
!Number.isFinite(m) ||
!Number.isFinite(s)
) {
return null;
}
let dec =
Math.abs(d) +
Math.abs(m) / 60 +
Math.abs(s) / 3600;
if (negative) {
dec = -dec;
}
if (
dec < -90 ||
dec > 90
) {
return null;
}
return dec;
}
/* =========================================================
ПОЛУЧЕНИЕ RA ИЗ ЗАПИСИ
========================================================= */
function getRA(record) {
/*
* Самый надёжный вариант для bsc5.json:
* готовое поле RA.
*/
if (
record.RA !== undefined &&
record.RA !== null
) {
const ra =
parseRA(record.RA);
if (ra !== null) {
return ra;
}
}
/*
* Запасной вариант:
* исходные поля BSC.
*/
const h =
Number(record.RAh);
const m =
Number(record.RAm);
const s =
Number(record.RAs);
if (
Number.isFinite(h) &&
Number.isFinite(m) &&
Number.isFinite(s)
) {
return normalizeRA(
15 * (
h +
m / 60 +
s / 3600
)
);
}
return null;
}
/* =========================================================
ПОЛУЧЕНИЕ DECLINATION ИЗ ЗАПИСИ
========================================================= */
function getDec(record) {
/*
* Готовое поле Dec.
*/
if (
record.Dec !== undefined &&
record.Dec !== null
) {
const dec =
parseDec(record.Dec);
if (dec !== null) {
return dec;
}
}
/*
* Исходные поля BSC.
*
* В Yale BSC знак находится
* в поле DE-.
*/
let degrees =
Number(record.DEd);
let minutes =
Number(record.DEm);
let seconds =
Number(record.DEs);
if (
!Number.isFinite(degrees) ||
!Number.isFinite(minutes) ||
!Number.isFinite(seconds)
) {
return null;
}
let dec =
Math.abs(degrees) +
Math.abs(minutes) / 60 +
Math.abs(seconds) / 3600;
/*
* ВАЖНО:
* правильное поле BSC — DE-
*/
const sign =
record["DE-"];
if (
sign !== undefined &&
sign !== null
) {
const value =
String(sign)
.trim()
.toUpperCase();
if (
value === "-" ||
value === "−" ||
value === "S"
) {
dec = -dec;
}
}
else if (degrees < 0) {
dec = -dec;
}
return dec;
}
/* =========================================================
ЗАГРУЗКА КАТАЛОГА
========================================================= */
async function loadCatalog() {
if (catalogLoaded) {
return true;
}
setStatus(
"Загрузка Yale Bright Star Catalog..."
);
try {
const response =
await fetch(
CATALOG_URL,
{
cache: "no-cache"
}
);
if (!response.ok) {
throw new Error(
"HTTP " +
response.status +
" " +
response.statusText
);
}
const data =
await response.json();
if (!Array.isArray(data)) {
throw new Error(
"Каталог имеет неожиданный формат: " +
"ожидался JSON-массив."
);
}
const result = [];
for (const record of data) {
/*
* Vmag
*/
const mag =
Number(record.Vmag);
if (!Number.isFinite(mag)) {
continue;
}
if (
mag > MAX_MAGNITUDE
) {
continue;
}
/*
* RA
*/
const ra =
getRA(record);
if (ra === null) {
continue;
}
/*
* Dec
*/
const dec =
getDec(record);
if (dec === null) {
continue;
}
/*
* Защита от некорректных данных.
*/
if (
ra < 0 ||
ra >= 360 ||
dec < -90 ||
dec > 90
) {
continue;
}
result.push({
ra: ra,
dec: dec,
mag: mag,
hr: record.HR ?? null,
name: record.Common ?? null
});
}
stars = result;
if (!stars.length) {
throw new Error(
"После обработки каталога не осталось звёзд."
);
}
catalogLoaded = true;
setStatus(
"Каталог загружен.\n" +
"Всего записей в JSON: " +
data.length +
"\n" +
"Звёзд с V ≤ " +
MAX_MAGNITUDE.toFixed(1) +
"m: " +
stars.length
);
updateStats();
enableButtons();
renderPreview();
return true;
}
catch (error) {
console.error(
"Ошибка загрузки каталога:",
error
);
setStatus(
"ОШИБКА ЗАГРУЗКИ КАТАЛОГА\n\n" +
error.message +
"\n\n" +
"Если HTML открыт напрямую через file://,\n" +
"запустите его через локальный HTTP-сервер."
);
return false;
}
}
/* =========================================================
СТАТИСТИКА
========================================================= */
function updateStats() {
const element =
document.getElementById("stats");
const counts = [];
for (
let i = 0;
i < NUMBER_OF_GORES;
i++
) {
counts.push(
starsForGore(i).length
);
}
element.textContent =
"Распределение звёзд по лепесткам: " +
counts.join(" / ");
}
/* =========================================================
РАЗМЕР ЗВЕЗДЫ
========================================================= */
/*
* Размер звёзд уменьшен В 2 РАЗА
* относительно предыдущей версии.
*
* Примерные радиусы:
*
* 0m → 0.60 мм
* 1m → 0.50 мм
* 2m → 0.415 мм
* 3m → 0.335 мм
* 4m → 0.25 мм
* 5m → 0.165 мм
*
* Диаметры:
*
* 0m → 1.20 мм
* 1m → 1.00 мм
* 2m → 0.83 мм
* 3m → 0.67 мм
* 4m → 0.50 мм
* 5m → 0.33 мм
*
* Минимальный радиус оставлен 0.15 мм,
* чтобы самые слабые звёзды не исчезали
* полностью из SVG.
*/
function starRadius(mag) {
const brightness =
Math.max(
0,
Math.min(
5,
5 - mag
)
);
const originalRadius =
1.0 +
Math.pow(
brightness / 5,
0.65
) * 2.6;
/*
* Сначала предыдущий масштаб:
* originalRadius / 3
*
* Теперь дополнительно
* уменьшаем размер в 2 раза.
*/
const r =
originalRadius / 6;
return Math.max(
0.15,
Math.min(
0.6,
r
)
);
}
/* =========================================================
ЦЕНТР ЛЕПЕСТКА
========================================================= */
function goreCenterRA(index) {
return (
index *
GORE_DEGREES
);
}
/* =========================================================
ЗВЁЗДЫ ЛЕПЕСТКА
========================================================= */
function starsForGore(index) {
const center =
goreCenterRA(index);
const half =
GORE_DEGREES / 2;
const EPS =
1e-8;
return stars.filter(star => {
const delta =
signedAngle(
star.ra - center
);
/*
* Полуоткрытый интервал:
*
* [-7.5°, +7.5°)
*
* Благодаря этому звезда,
* лежащая точно на разрезе,
* попадает только в один лепесток.
*/
return (
delta >= -half - EPS &&
delta < half - EPS
);
});
}
/* =========================================================
ПРОЕКЦИЯ
========================================================= */
/*
* Практическая sinusoidal-gore развёртка.
*
* y = R * latitude
*
* x = R * cos(latitude) * sin(longitude)
*
* Здесь R — радиус готового глобуса.
*/
function projectPoint(
deltaRA,
dec
) {
const lambda =
degToRad(deltaRA);
const phi =
degToRad(dec);
const x =
RADIUS_MM *
Math.cos(phi) *
Math.sin(lambda);
const y =
RADIUS_MM *
phi;
return {
x: x,
y: y
};
}
/* =========================================================
ГРАНИЦА ЛЕПЕСТКА
========================================================= */
function goreBoundary() {
const half =
degToRad(
GORE_DEGREES / 2
);
const points = [];
/*
* Левая сторона:
* север → юг.
*/
for (
let i = 0;
i <= 360;
i++
) {
const phi =
Math.PI / 2 -
Math.PI * i / 360;
const x =
RADIUS_MM *
Math.cos(phi) *
Math.sin(-half);
const y =
RADIUS_MM *
phi;
points.push({
x: x,
y: y
});
}
/*
* Правая сторона:
* юг → север.
*/
for (
let i = 360;
i >= 0;
i--
) {
const phi =
Math.PI / 2 -
Math.PI * i / 360;
const x =
RADIUS_MM *
Math.cos(phi) *
Math.sin(half);
const y =
RADIUS_MM *
phi;
points.push({
x: x,
y: y
});
}
return points;
}
/* =========================================================
PATH
========================================================= */
function pointsToPath(points) {
if (!points.length) {
return "";
}
let d =
"M " +
fmt(points[0].x) +
" " +
fmt(points[0].y);
for (
let i = 1;
i < points.length;
i++
) {
d +=
" L " +
fmt(points[i].x) +
" " +
fmt(points[i].y);
}
d += " Z";
return d;
}
/* =========================================================
КЛАПАНЫ
========================================================= */
/*
* Создаёт внешний пунктирный контур клапана
* вдоль одной стороны лепестка.
*
* Клапаны не доходят до полюсов,
* чтобы не образовывать большие
* перекрытия в полюсах.
*/
function createTabPoints(side) {
const half =
degToRad(
GORE_DEGREES / 2
);
const points = [];
const poleCut =
18;
const start =
poleCut;
const end =
180 - poleCut;
for (
let i = start;
i <= end;
i += 2
) {
const phi =
Math.PI / 2 -
Math.PI * i / 180;
const boundaryX =
RADIUS_MM *
Math.cos(phi) *
Math.sin(
side === "left"
? -half
: half
);
const boundaryY =
RADIUS_MM *
phi;
const direction =
side === "left"
? -1
: 1;
points.push({
x:
boundaryX +
direction * TAB_MM,
y:
boundaryY
});
}
return points;
}
/* =========================================================
СКАЧИВАНИЕ ТЕКСТА
========================================================= */
function downloadText(
text,
filename
) {
const blob =
new Blob(
[text],
{
type:
"image/svg+xml;charset=utf-8"
}
);
const url =
URL.createObjectURL(blob);
const a =
document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(
() => {
URL.revokeObjectURL(url);
},
1000
);
}
/* =========================================================
SVG ОДНОГО ЛЕПЕСТКА
========================================================= */
function createGoreSVG(index) {
const starsHere =
starsForGore(index);
const halfWidth =
RADIUS_MM *
Math.sin(
degToRad(
GORE_DEGREES / 2
)
);
const goreWidth =
halfWidth * 2;
const totalWidth =
goreWidth +
TAB_MM * 2 +
MARGIN_MM * 2;
const totalHeight =
Math.PI *
RADIUS_MM +
MARGIN_MM * 2;
const viewMinX =
-totalWidth / 2;
const viewMinY =
-totalHeight / 2;
const boundary =
goreBoundary();
const boundaryPath =
pointsToPath(boundary);
let svg = "";
svg += `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="${fmt(totalWidth)}mm"
height="${fmt(totalHeight)}mm"
viewBox="${fmt(viewMinX)} ${fmt(viewMinY)} ${fmt(totalWidth)} ${fmt(totalHeight)}">
<rect
x="${fmt(viewMinX)}"
y="${fmt(viewMinY)}"
width="${fmt(totalWidth)}"
height="${fmt(totalHeight)}"
fill="white"/>
<g>
`;
/*
* Основная поверхность.
*/
svg += `<path
d="${boundaryPath}"
fill="white"
stroke="black"
stroke-width="0.35"
stroke-linejoin="round"/>`;
/*
* Звёзды.
*/
for (
const star of starsHere
) {
const delta =
signedAngle(
star.ra -
goreCenterRA(index)
);
const point =
projectPoint(
delta,
star.dec
);
const r =
starRadius(
star.mag
);
/*
* Не рисуем звезду,
* если из-за округления
* она оказалась вне поверхности.
*/
if (
Math.abs(point.x) >
halfWidth + 0.001
) {
continue;
}
svg += `<circle
cx="${fmt(point.x)}"
cy="${fmt(point.y)}"
r="${fmt(r)}"
fill="black"/>`;
}
/*
* Центральный меридиан УДАЛЁН.
*
* Пунктирная линия экватора
* остаётся ниже.
*/
/*
* Экватор.
*/
svg += `
<line
x1="${fmt(-halfWidth)}"
y1="0"
x2="${fmt(halfWidth)}"
y2="0"
stroke="black"
stroke-width="0.15"
stroke-dasharray="2,2"/>
`;
/*
* Полюса.
*/
const poleY =
Math.PI *
RADIUS_MM /
2;
svg += `
<circle
cx="0"
cy="${fmt(-poleY)}"
r="1.5"
fill="none"
stroke="black"
stroke-width="0.3"/>
<circle
cx="0"
cy="${fmt(poleY)}"
r="1.5"
fill="none"
stroke="black"
stroke-width="0.3"/>
`;
/*
* Клапаны.
*/
const leftTab =
createTabPoints("left");
const rightTab =
createTabPoints("right");
function tabPath(points) {
if (!points.length) {
return "";
}
let d =
"M " +
fmt(points[0].x) +
" " +
fmt(points[0].y);
for (
let i = 1;
i < points.length;
i++
) {
d +=
" L " +
fmt(points[i].x) +
" " +
fmt(points[i].y);
}
return d;
}
svg += `
<path
d="${tabPath(leftTab)}"
fill="none"
stroke="black"
stroke-width="0.25"
stroke-dasharray="3,2"/>
<path
d="${tabPath(rightTab)}"
fill="none"
stroke="black"
stroke-width="0.25"
stroke-dasharray="3,2"/>
`;
/*
* Номер лепестка.
*/
svg += `
<text
x="0"
y="${fmt(-poleY - 5)}"
text-anchor="middle"
font-family="Arial, sans-serif"
font-size="5"
fill="black">
GORE ${index + 1}
</text>
`;
/*
* RA диапазон.
*/
const raStart =
normalizeRA(
goreCenterRA(index) -
GORE_DEGREES / 2
);
const raEnd =
normalizeRA(
goreCenterRA(index) +
GORE_DEGREES / 2
);
svg += `
<text
x="0"
y="${fmt(poleY + 7)}"
text-anchor="middle"
font-family="Arial, sans-serif"
font-size="3.5"
fill="black">
RA ${formatRA(raStart)} — ${formatRA(raEnd)}
</text>
`;
/*
* Техническая информация.
*/
svg += `
<text
x="${fmt(viewMinX + 3)}"
y="${fmt(viewMinY + 5)}"
font-family="Arial, sans-serif"
font-size="2.5"
fill="black">
Ø1000 mm · J2000 · V≤${MAX_MAGNITUDE.toFixed(1)} · ${starsHere.length} stars
</text>
`;
svg += `
</g>
</svg>`;
return svg;
}
/* =========================================================
FORMAT RA
========================================================= */
function formatRA(degrees) {
const hours =
normalizeRA(degrees) / 15;
return (
hours.toFixed(2) +
"h"
);
}
/* =========================================================
PREVIEW
========================================================= */
function renderPreview() {
if (!stars.length) {
return;
}
const svg =
createGoreSVG(0);
const container =
document.getElementById(
"preview"
);
container.innerHTML =
svg;
}
/* =========================================================
АКТИВАЦИЯ КНОПОК
========================================================= */
function enableButtons() {
document.getElementById(
"downloadAllButton"
).disabled = false;
document.getElementById(
"downloadZipButton"
).disabled = false;
}
/* =========================================================
СКАЧАТЬ 24 SVG
========================================================= */
async function downloadAll() {
if (!catalogLoaded) {
const success =
await loadCatalog();
if (!success) {
return;
}
}
setStatus(
"Создание 24 SVG-файлов..."
);
for (
let i = 0;
i < NUMBER_OF_GORES;
i++
) {
const svg =
createGoreSVG(i);
const filename =
"star_globe_" +
String(i + 1).padStart(2, "0") +
"_of_24.svg";
downloadText(
svg,
filename
);
setStatus(
"Создание SVG...\n" +
"Лепесток " +
(i + 1) +
" из " +
NUMBER_OF_GORES
);
/*
* Небольшая задержка,
* чтобы браузер успевал
* обработать загрузки.
*/
await sleep(250);
}
setStatus(
"ГОТОВО\n\n" +
"Создано: 24 SVG\n" +
"Диаметр: 1000 мм\n" +
"Лепестков: 24\n" +
"Угол лепестка: 15°\n" +
"Система координат: J2000\n" +
"Предел звёзд: V ≤ " +
MAX_MAGNITUDE.toFixed(1) +
"m"
);
}
/* =========================================================
ЗАГРУЗКА JSZIP
========================================================= */
function loadScript(src) {
return new Promise(
(resolve, reject) => {
/*
* Если JSZip уже существует.
*/
if (
typeof JSZip !==
"undefined"
) {
resolve();
return;
}
const script =
document.createElement(
"script"
);
script.src = src;
script.async = true;
script.onload = () => {
resolve();
};
script.onerror = () => {
reject(
new Error(
"Не удалось загрузить JSZip."
)
);
};
document.head.appendChild(
script
);
}
);
}
/* =========================================================
ZIP
========================================================= */
async function downloadZIP() {
if (!catalogLoaded) {
const success =
await loadCatalog();
if (!success) {
return;
}
}
setStatus(
"Загрузка библиотеки ZIP..."
);
try {
await loadScript(
"https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"
);
setStatus(
"Создание ZIP-файла..."
);
const zip =
new JSZip();
for (
let i = 0;
i < NUMBER_OF_GORES;
i++
) {
const svg =
createGoreSVG(i);
const filename =
"star_globe_" +
String(i + 1).padStart(2, "0") +
"_of_24.svg";
zip.file(
filename,
svg
);
setStatus(
"Создание ZIP...\n" +
"Лепесток " +
(i + 1) +
" из " +
NUMBER_OF_GORES
);
}
const blob =
await zip.generateAsync({
type: "blob",
compression: "DEFLATE",
compressionOptions: {
level: 6
}
});
const url =
URL.createObjectURL(blob);
const a =
document.createElement("a");
a.href = url;
a.download =
"star_globe_1000mm_J2000_24_gores.zip";
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(
() => {
URL.revokeObjectURL(url);
},
2000
);
setStatus(
"ZIP-ФАЙЛ ГОТОВ\n\n" +
"Файл: " +
"star_globe_1000mm_J2000_24_gores.zip\n" +
"Лепестков: 24\n" +
"Диаметр: 1000 мм\n" +
"J2000\n" +
"V ≤ " +
MAX_MAGNITUDE.toFixed(1) +
"m"
);
}
catch (error) {
console.error(error);
setStatus(
"ОШИБКА СОЗДАНИЯ ZIP\n\n" +
error.message
);
}
}
/* =========================================================
ЗАПУСК
========================================================= */
loadCatalog();
</script>
</body>
</html>