This commit is contained in:
Zakhar 2026-05-21 12:49:45 +02:00
parent 1a5a10a854
commit ad851551d1
21 changed files with 958 additions and 1 deletions

2
.gitignore vendored
View File

@ -0,0 +1,2 @@
.idea
node_modules

27
client-websocket.js Normal file
View File

@ -0,0 +1,27 @@
(() => {
const socketUrl = 'ws://192.168.1.138:9878';
let socket = new WebSocket(socketUrl);
socket.addEventListener('close', () => {
const interAttemptTimeoutMilliseconds = 100;
const maxDisconnectedTimeMilliseconds = 3000;
const maxAttempts = Math.round(
maxDisconnectedTimeMilliseconds / interAttemptTimeoutMilliseconds,
);
let attempts = 0;
const reloadIfCanConnect = () => {
attempts++;
if (attempts > maxAttempts) {
console.error('Could not reconnect to dev server.');
return;
}
socket = new WebSocket(socketUrl);
socket.addEventListener('error', () => {
setTimeout(reloadIfCanConnect, interAttemptTimeoutMilliseconds);
});
socket.addEventListener('open', () => {
location.reload();
});
};
reloadIfCanConnect();
});
})();

78
dev-server.js Normal file
View File

@ -0,0 +1,78 @@
/** @file site/dev-server.js */
const http = require('http');
const fs = require('fs');
const path = require('path');
const WebSocket = require('ws');
const HTTP_PORT = 9876;
const WEBSOCKET_PORT = 9878;
const CLIENT_WEBSOCKET_CODE = fs.readFileSync(
path.join(__dirname, 'client-websocket.js'),
'utf8',
);
// Websocket server (for allowing browser and dev server to have 2-way communication)
// We don't even need to do anything except create the instance!
const wss = new WebSocket.Server({
port: WEBSOCKET_PORT,
});
wss.on('connection', () => {
console.log('Client connected');
});
/**
* @typedef {import('http').IncomingMessage} req
* @typedef {import('http').ServerResponse} res
*/
/** Use classic server-logic to serve a static file (e.g. default to 'index.html' etc)
* @param {string} route
* @param {res} res
* @returns {boolean} Whether or not the page exists and was served
*/
function serveStaticPageIfExists(route, res) {
// We don't care about performance for a dev server, so sync functions are fine.
// If the route exists it's either the exact file we want or the path to a directory
// in which case we'd serve up the 'index.html' file.
if (fs.existsSync(route)) {
if (fs.statSync(route).isDirectory()) {
return serveStaticPageIfExists(path.join(route, 'index.html'), res);
} else if (fs.statSync(route).isFile()) {
res.writeHead(200);
/** @type {string|Buffer} */
let file = fs.readFileSync(route);
if (route.endsWith('.html')) {
// Inject the client-side websocket code.
// This sounds fancier than it is; simply
// append the script to the end since
// browsers allow for tons of deviation
// from *technically correct* HTML.
file = `${file.toString()}\n\n<script>${CLIENT_WEBSOCKET_CODE}</script>`;
}
res.end(file);
return true;
}
}
return false;
}
/** General request handler and router
* @param {req} req
* @param {res} res
*/
const requestHandler = function (req, res) {
const method = req.method.toLowerCase();
if (method === 'get') {
// No need to ensure the route can't access other local files,
// since this is for development only.
const route = path.normalize(path.join(__dirname, req.url));
if (serveStaticPageIfExists(route, res)) {
return;
}
}
res.writeHead(404);
res.end();
};
const server = http.createServer(requestHandler);
server.listen(HTTP_PORT);

BIN
images/arrow-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
images/arrow-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
images/autobasket_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

BIN
images/autobasket_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

BIN
images/autobasket_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

BIN
images/balance-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
images/broc_meal_plan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
images/economy-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
images/google_play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

BIN
images/happy-salad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 KiB

BIN
images/laugh-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/rustore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

BIN
images/smile-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -6,8 +6,233 @@
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title> <title>Document</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Onest:wght@100..900&display=swap" rel="stylesheet">
<style>
body {background: #FCFFFB;padding:0;margin:0;font-family: 'Onest', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, sans-serif;}
.container {max-width: 1440px;margin: 0 auto;position: relative;width:100%;}
.text_secondary {color:#6D8982;}
h1 {font-size: 254px;padding:0;margin:0;}
h2 {font-size: 48px;text-transform: uppercase;}
h3 {font-size: 18px;color:#0C3B2E;font-weight: bold;padding:0;margin:0;}
p.description {font-size: 18px;color:#6d8982;}
a:focus {outline: none;}
#top_logo {font-size: 20px;line-height: 24px;font-weight: bold;color: #0C3B2E;display: flex;align-items: center;gap:6px;height:24px;margin-top:8px;}
#top_logo img {width: 24px;}
#top_line_container {position: fixed;top:0;width: 100%;background-color: #FCFFFB;user-select: none;z-index: 11;}
#top_line {position:relative;display: flex;flex-direction: row;justify-content: space-around;padding:18px 0;height:80px;box-sizing: border-box; border-bottom: 1px solid #e7ede7;}
#top_menu {display:flex;flex-direction:row;gap: 40px;margin-top:13px;height:18px;align-items: center;}
#top_menu a {color: #6D8982;font-size:14px;font-weight: 500;line-height: 17px;text-decoration: none;}
#top_menu a:hover {color: #5EA12D;}
#top_menu a:focus {outline: none;}
#try_button {background-color: #FFBA00;padding:13px 43px;font-weight: bold;border-radius: 100px;color:#0C3B2E;text-decoration: none;
display: flex;}
.page_container {width: 100%;height: 100vh;display: flex;flex-direction: column;align-items: center;justify-content: center;}
#page_index {}
#page_index_content {align-items: start;}
#page_index p {font-size: 18px;}
.container_row {display: flex;flex-direction: row;gap: 34px;align-items: flex-end}
.big_index_greeting {font-size:254px;font-weight: 900;line-height: 214px;color:#0c3b2e;text-transform: uppercase;letter-spacing: -0px;}
#page_index_store_btns {height:52px;display: flex;flex-direction: row;gap:30px;align-items: center;padding-top:40px;}
#page_index_store_btns a {display: flex;align-items: center;color:#6d8982;font-weight: bold;padding:14px 0;text-decoration: none;}
#page_index_store_btns img {height: 24px;margin-right: 12px;}
#page_autobasket {}
#page_autobasket_content {display: flex;flex-direction: column;gap: 64px;}
#page_autobasket_content_description {text-align: center;}
#page_autobasket_content_description p {font-size: 24px;}
#page_autobasket_images {display: flex;flex-direction: row;gap: 20px;justify-content: center;}
#page_autobasket_images div {display: flex;flex-direction: column; gap: 16px;max-width: 387px;text-align: center}
#page_autobasket_images img {max-width: 100%;}
#page_autobasket_images p {font-size: 18px;color:#6d8982;padding:0;margin:0;}
#page_meal_plan {}
#page_meal_plan_content {display: flex;flex-direction: row;gap:20px;padding:0 120px;justify-content: space-around;}
#page_meal_plan_content_description {display: flex;flex-direction: column;gap:40px;}
#page_meal_plan_content_description > p {font-size:24px;color:#6d8982;}
#page_meal_plan_broc {max-width:529px;}
#page_meal_plan_items {gap: 24px;display: flex;flex-direction: column;}
.page_meal_plan_item {display: flex;flex-direction: row;gap: 16px;max-width: 650px;}
.page_meal_plan_item p {font-size:18px;color:#6d8982;padding: 0;margin:0;}
.page_meal_plan_item img {width:54px;}
.page_meal_plan_item_text {display: flex;flex-direction: column;gap:8px;}
#page_how_it_works {}
#page_how_it_works_content {text-align: center;}
#page_how_it_works_step {}
#page_how_it_works_steps {display: flex;flex-direction: row;justify-content: space-between;margin-top:64px;}
#page_how_it_works_steps .step-image {width:80px;}
.page_how_it_works_steps_arrow {display: flex;justify-content: center;align-items: center;}
#page_how_it_works_step_1 {position: relative;}
#page_how_it_works_step_1_arrow {height:30px;align-self: center}
#page_how_it_works_step_2 {}
#page_how_it_works_step_2_arrow {height:30px;}
#page_how_it_works_step_3 {position: relative;align-items: end;display: flex;}
#page_how_it_works_step_3 img {position:absolute;width:240px;top:-170px;left:-30px;}
#page_how_it_works_info {background-color: #edfaae;width:100%;border-radius: 24px;position: relative;display: flex;flex-direction: row;box-sizing: border-box;height:320px;margin-top:60px;}
#page_how_it_works_info_image {position: relative;height:320px;margin-top:70px;}
#page_how_it_works_info_image img {position: absolute;max-width: 419px;left:-70px;top:-140px;}
#page_how_it_works_info_content {font-size: 24px;text-align: left;margin-left:340px;margin-top:25px;display: flex;flex-direction: column;justify-content: space-around;}
#page_how_it_works_info_content ul {align-self: center;justify-content: center;}
#page_how_it_works_info_content ul li::marker {padding-top:0;}
#page_how_it_works_info_content ul li {list-style-type: disc;padding-top:3px;padding-bottom:24px;}
#page_how_it_works_info_content ul li span {margin-top: -28px;display: flex;}
#page_footer {min-height: 136px;}
#page_footer > div {display: flex;align-items: center;justify-content: space-between;}
#page_footer a {color:#5EA12D;text-decoration: none;}
#bottom_logo {font-size: 20px;line-height: 24px;font-weight: bold;color: #0C3B2E;display: flex;align-items: center;gap:6px;height:24px;margin-top:8px;}
#bottom_logo img {width: 24px;}
</style>
</head> </head>
<body> <body>
<div id="top_line_container">
<div class="container">
<div id="top_line">
<div id="top_logo"><img src="/images/logo.png" alt="">Хватило</div>
<div id="top_menu">
<a href="#page_autobasket">Автокорзина</a>
<a href="#page_meal_plan">План питания</a>
<a href="#page_how_it_works">Список покупок</a>
<a href="#page_footer">Контакты</a>
</div>
<div><a href="https://hvatilo.ru/app/" id="try_button" target="_blank">Попробовать</a></div>
</div>
</div>
</div>
<div class="page_container" id="page_index">
<div id="page_index_content">
<h1 class="big_index_greeting">Хватит</h1>
<div class="container_row">
<div>
<p class="text_secondary">Персональный помощник <br> по питанию: баланс, <br> энергия, порядок <br>в холодильнике - <br> для всей семьи</p>
</div>
<div class="big_index_greeting">гадать</div>
</div>
<div id="page_index_store_btns">
<a href="https://play.google.com/store/apps/details?id=eu.zaek.hvatilo.twa&hl=ru" target="_blank" rel="noreferrer noopener"><img src="/images/google_play.png" alt="">Google play</a>
<a href="https://www.rustore.ru/catalog/app/eu.zaek.hvatilo.twa" target="_blank" rel="noreferrer noopener"><img src="/images/rustore.png" alt="">RuStore</a>
</div>
</div>
</div>
<div class="page_container" id="page_autobasket">
<div id="page_autobasket_content">
<div id="page_autobasket_content_description">
<h2>Автокорзина</h2>
<p>Вы выбираете, сколько человек в семье, указываете, на сколько дней нужны <br /> продукты — и приложение мгновенно собирает корзину с учётом порций.
<br> На выходе — чёткий список покупок, разбитый по отделам магазина</p>
</div>
<div id="page_autobasket_images">
<div>
<img src="/images/autobasket_1.png" alt="">
<div>
<h3>Никаких сомнений</h3>
<p>Забудьте про мучительные вопросы «А хватит ли?», «А что купить ещё?»</p>
</div>
</div>
<div>
<img src="/images/autobasket_2.png" alt="">
<div>
<h3>Контроль в телефоне</h3>
<p>Список всегда под рукой, можно вычёркивать по пути</p>
</div>
</div>
<div>
<img src="/images/autobasket_3.png" alt="">
<div>
<h3>Всё схвачено</h3>
<p>Мы уже учли, что есть в магазине</p>
</div>
</div>
</div>
</div>
</div>
<div class="page_container" id="page_meal_plan">
<div id="page_meal_plan_content" class="container">
<div id="page_meal_plan_content_description">
<h2>План питания</h2>
<p>
Добавляете любимые рецепты и указываете <br>
возможные замены для продуктов. Из готовых <br>
рецептов собираете план на день/неделю, планом <br>
можно делиться с другими пользователями!
</p>
<div id="page_meal_plan_items">
<div class="page_meal_plan_item">
<div><img src="/images/balance-icon.png" alt=""></div>
<div class="page_meal_plan_item_text"><h3>Баланс</h3><p>Вы сами выбираете блюда</p></div>
</div>
<div class="page_meal_plan_item">
<div><img src="/images/economy-icon.png" alt=""></div>
<div class="page_meal_plan_item_text"><h3>Экономия</h3><p>Замена без потери вкуса</p></div>
</div>
<div class="page_meal_plan_item">
<div><img src="/images/personalization-icon.png" alt=""></div>
<div class="page_meal_plan_item_text"><h3>Персонализация</h3><p>Общий план — муж/ребёнок/соседка по комнате знают, что готовить, без ваших объяснений</p></div>
</div>
</div>
</div>
<div>
<img id="page_meal_plan_broc" src="/images/broc_meal_plan.png" alt="">
</div>
</div>
</div>
<div class="page_container" id="page_how_it_works">
<div id="page_how_it_works_content" class="container">
<h2>Как это работает</h2>
<p class="description">
Добавляете любимые рецепты и указываете <br>
возможные замены для продуктов. Из готовых
</p>
<div id="page_how_it_works_steps">
<div id="page_how_it_works_step_1">
<img src="/images/smile-icon.png" class="step-image" alt="">
<p>Открываете сегодняшний <br> день в плане питания</p>
</div>
<div class="page_how_it_works_steps_arrow">
<img src="/images/arrow-1.png" alt="" id="page_how_it_works_step_1_arrow">
</div>
<div id="page_how_it_works_step_2">
<img src="/images/laugh-icon.png" class="step-image" alt="">
<p>Заменить ужин — пара кликов, <br>и рецепт встаёт в план <br>с новым списком покупок</p>
</div>
<div class="page_how_it_works_steps_arrow"><img src="/images/arrow-2.png" alt="" id="page_how_it_works_step_2_arrow"></div>
<div id="page_how_it_works_step_3">
<img src="/images/broc_meal_plan.png" alt="">
<p>Из списка продуктов можно <br> сформировать автокорзину <br>на недостающие</p>
</div>
</div>
<div id="page_how_it_works_info">
<div id="page_how_it_works_info_image"><img src="/images/happy-salad.png" alt=""></div>
<div id="page_how_it_works_info_content">
<ul>
<li>
<span>Отсутствие продукта в магазине не сломает приём пищи: <br> автокорзина подстроится под наличие</span>
</li>
<li>
&nbsp;
<span>В холодильнике только те продукты, которые вы успеете <br> съесть, в помойку отправится только упаковка</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="page_footer">
<div class="container container_row">
<div>
<div id="bottom_logo"><img src="/images/logo.png" alt="">Хватило</div>
&copy; 2026 &mdash; <a href="mailto:info@hvatilo.ru">info@hvatilo.ru</a> <br>
</div>
<div>
<a href="https://hvatilo.ru/app/offer" target="_blank">Пользовательское соглашение</a>
</div>
</div>
</div>
</body> </body>
</html> </html>

617
package-lock.json generated Normal file
View File

@ -0,0 +1,617 @@
{
"name": "marketing",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"http-server": "^14.1.1"
},
"devDependencies": {
"ws": "^8.20.1"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
"integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT"
},
"node_modules/follow-redirects": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
"integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"license": "MIT",
"bin": {
"he": "bin/he"
}
},
"node_modules/html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
"license": "MIT",
"dependencies": {
"whatwg-encoding": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"license": "MIT",
"dependencies": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/http-server": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz",
"integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==",
"license": "MIT",
"dependencies": {
"basic-auth": "^2.0.1",
"chalk": "^4.1.2",
"corser": "^2.0.1",
"he": "^1.2.0",
"html-encoding-sniffer": "^3.0.0",
"http-proxy": "^1.18.1",
"mime": "^1.6.0",
"minimist": "^1.2.6",
"opener": "^1.5.1",
"portfinder": "^1.0.28",
"secure-compare": "3.0.1",
"union": "~0.5.0",
"url-join": "^4.0.1"
},
"bin": {
"http-server": "bin/http-server"
},
"engines": {
"node": ">=12"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"license": "(WTFPL OR MIT)",
"bin": {
"opener": "bin/opener-bin.js"
}
},
"node_modules/portfinder": {
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz",
"integrity": "sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==",
"license": "MIT",
"dependencies": {
"async": "^3.2.6",
"debug": "^4.3.6"
},
"engines": {
"node": ">= 10.12"
}
},
"node_modules/qs": {
"version": "6.15.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
"integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"license": "MIT"
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/secure-compare": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
"integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==",
"license": "MIT"
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-weakmap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3",
"side-channel-map": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
"dependencies": {
"qs": "^6.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"license": "MIT"
},
"node_modules/whatwg-encoding": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/ws": {
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

8
package.json Normal file
View File

@ -0,0 +1,8 @@
{
"dependencies": {
"http-server": "^14.1.1"
},
"devDependencies": {
"ws": "^8.20.1"
}
}