From 658969224c21b65c8d81d56d72ca4a8f18b14e2f Mon Sep 17 00:00:00 2001 From: "Kaziyeva.S" Date: Wed, 23 Sep 2026 10:22:14 +0000 Subject: [PATCH] wedding invitation with RSVP form --- .gitignore | 4 + index.html | 226 +++++++++++++++++++-------------------------------- package.json | 6 ++ script.js | 100 ++++++++++------------- server.js | 51 ++++++++++++ 5 files changed, 189 insertions(+), 198 deletions(-) create mode 100644 .gitignore create mode 100644 package.json create mode 100644 server.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3ff6e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +data.json +.vibe42-run.log +.vibe42-run.pid diff --git a/index.html b/index.html index 91f2a96..896fdb4 100644 --- a/index.html +++ b/index.html @@ -4,8 +4,8 @@ -Где поступать — навигатор специальностей - +Приглашение на свадьбу — 1 ноября + @@ -14,189 +14,131 @@
- Где поступать + Мы женимся! - Специальности - Список вузов + Подтвердить
-

Знай, куда подать документы

-

Сравни специальность, баллы, бюджетные места и стипендии — без путеводителя «спроси у старших».

-
-
- - -
-
- - -
- +
+ Приглашение + суббота + 1 ноября 2026
- -
-
- -
-
-
-

Как выбрать специальность

-

Пройди по шагам, и список вариантов сузится с сотен до трёх-пяти.

-
-
-
-
- - Шаг 1 -
-

Что не надоест через полгода

-

Список интересов и предметов-любимцев. Честнее любой анкеты «кем ты хочешь стать».

-
-
-
- - Шаг 2 -
-

Проверь баллы

-

Смотри проходные баллы 2025 года, а не «примерно сто с хвостиком». Здесь всё по цифрам.

-
-
средний балл по таблице75
-
-
-
- - Шаг 3 -
-

Выбери город и формат

-

Столица, родной город, бюджет или платно, стипендия, общежитие — всё в одном месте.

-
-
-
- - Шаг 4 -
-

Собери документы

-

Чек-лист: результат экзамена, направления, приоритеты. Без дедлайна в последний день.

-
+

+ Айсулу +  и  + Ержан +

+

просят вас разделить с ними этот день — банкет начнётся в 16:00.

+
+
-
+
-

Вузы и проходные баллы

-

Примерные цифры 2025 года: минимальный проходной балл, бюджетные места и стипендия.

-
-
- - - - - - - - - - - - -
ВузСпециальностьГородМин. баллБюджетСтипендия
-
-

Показаны все варианты.

-
-
- -
-
-
-

Что дальше

-

Короткий маршрут на лето — что и когда сделать.

+

Где и когда

+

Всё в одном месте — адрес, время и что взять с собой.

-

Май — выбор

-

Три варианта по приоритету. Не «все 10 вузов сразу», а список, с которым спокойно.

+
+

Ресторан «Хан Шатır»

+

Укажите полный адрес и карту, как только уточните — например, «г. Алматы, ул. ТОО «Хан», 5», с парковкой у входа.

-

Июнь — документы

-

Подача в приёмную комиссию, подтверждение документов и направление приоритетов.

+
+

16:00 — банкет

+

Прошу не опаздывать: торжественная часть проходит до 18:00, банкет до 23:00.

-

Июль — ответ

-

Дедлайн подачи заявления на зачисление обычно в конце июля. Проверь дважды дату.

+
+

Дресс-код

+

Смарт-кэжуал / вечерний. Приходите в удобной обуви — будет танцевать.

-
-
+
+
-

Частые вопросы

+

Подтвердите визит

+

До 25 октября, пожалуйста — ресторану нужно знать точное количество гостей.

-
-
-

Можно ли поступить по 2–3 специальностям?

-

Да, направление можно заявить в нескольких вузах одновременно. Приоритет отдаётся первому варианту, по которому пойдёшь на зачисление.

-
-
-

Балл ниже проходного — ничего?

-

Посмотри платное направление, смежные специальности и вузы из других городов: там проходной часто ниже на 10–20 баллов.

-
-
-

Сколько будет стипендия?

-

Социальная стипендия плюс академическая. Размер зависит от вуза и курса — в таблице указана базовая цифра на начало обучения.

-
-
-

А если не попаду никуда?

-

Останутся допнабор и платные направления. Это нормальная часть сезона, а не конец истории.

-
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +

+
-
+
-

Остался вопрос по конкретной ситуации?

-

Напиши — разберём твой балл, город и интересы, и подскажем 2–3 реальных варианта.

- Написать в Telegram +

Ждём вас 1 ноября!

+

Если появятся вопросы — напишите: +7 700 000 00 00 (организатор). До встречи!

- © 2026 Где поступать + 1 ноября 2026 · ресторан «Хан Шатır» - Данные примерные — сверься с официальным сайтом вуза + С любовью
diff --git a/package.json b/package.json new file mode 100644 index 0000000..b72dc26 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "name": "wedding-invite", + "private": true, + "type": "commonjs", + "scripts": { "start": "node server.js" } +} diff --git a/script.js b/script.js index 9f98a0f..49e3c83 100644 --- a/script.js +++ b/script.js @@ -1,61 +1,49 @@ -// Данные примерные: их легко заменить на реальные (массив UNIVERSITIES). -const UNIVERSITIES = [ - { name: "КазНУ им. аль-Фараби", specialty: "Прикладная математика", city: "Алматы", min: 162, budget: 85, scholarship: 14500 }, - { name: "КазНУ им. аль-Фараби", specialty: "Высшая математика в финансах", city: "Алматы", min: 159, budget: 40, scholarship: 14500 }, - { name: "КазНУ им. аль-Фараби", specialty: "Математика и анализ данных", city: "Алматы", min: 156, budget: 55, scholarship: 14000 }, - { name: "Eurasian National University", specialty: "Высшая математика", city: "Астана", min: 152, budget: 35, scholarship: 13500 }, - { name: "Kazakh-British Technical University", specialty: "Высшая математика и моделирование", city: "Астана", min: 155, budget: 30, scholarship: 13500 }, - { name: "КазНТУ им. Т. Рысқұлова", specialty: "Высшая математика", city: "Алматы", min: 148, budget: 68, scholarship: 12500 }, - { name: "АУ «Сарыарка»", specialty: "Высшая математика", city: "Караганда", min: 138, budget: 25, scholarship: 10000 }, - { name: "ЮЗГУ", specialty: "Математика и статистика", city: "Костанай", min: 132, budget: 30, scholarship: 9500 }, -]; +// Пригласительный: обратный отсчёт + форма RSVP (отправляет в /api/rsvp) +const WEDDING_DATE = new Date("2026-11-01T16:00:00"); -const body = document.getElementById("uni-body"); -const note = document.getElementById("uni-note"); - -function renderTable(rows) { - if (!body) return; - body.innerHTML = ""; - for (const r of rows) { - const tr = document.createElement("tr"); - tr.innerHTML = - '' + r.name + "" + - '' + r.specialty + "" + - '' + r.city + "" + - '' + r.min + "" + - '' + r.budget + "" + - '' + r.scholarship.toLocaleString("ru-RU") + " тг"; - body.appendChild(tr); +const cd = document.getElementById("countdown"); +if (cd) { + function tick() { + const diff = WEDDING_DATE - new Date(); + if (diff <= 0) { + cd.innerHTML = 'Сегодня наш день!'; + return; + } + const d = Math.floor(diff / 86400000); + const h = Math.floor(diff / 3600000) % 24; + const m = Math.floor(diff / 60000) % 60; + const s = Math.floor(diff / 1000) % 60; + cd.innerHTML = + cell(d, "дней") + cell(h, "часов") + cell(m, "минут") + cell(s, "секунд"); } - note.textContent = rows.length - ? "Показано вариантов: " + rows.length + "." - : "Ничего не нашлось — попробуй другой запрос или сбрось балл."; + function cell(n, lab) { + return '
' + n + "
" + lab + "
"; + } + tick(); + setInterval(tick, 1000); } -function fmt(v) { - return v == null ? "" : String(v).trim().toLowerCase(); -} - -document.getElementById("btn-search")?.addEventListener("click", function () { - const q = fmt(document.getElementById("f-specialty").value); - const score = parseInt(document.getElementById("f-score").value, 10); - let rows = UNIVERSITIES.filter(function (u) { - const okQ = !q || u.specialty.toLowerCase().includes(q) || u.name.toLowerCase().includes(q); - const okScore = isNaN(score) || u.min <= score; - return okQ && okScore; +const form = document.getElementById("rsvp-form"); +const msg = document.getElementById("rsvp-msg"); +if (form) { + form.addEventListener("submit", async function (e) { + e.preventDefault(); + msg.textContent = "Отправляю…"; + try { + const body = Object.fromEntries(new FormData(form).entries()); + const res = await fetch("api/rsvp", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + if (!res.ok) throw new Error("server " + res.status); + const attendance = form.attendance.value; + msg.textContent = attendance === "yes" + ? "Спасибо! До встречи 1 ноября." + : "Спасибо, что сообщили. Мы вас поздравим на расстоянии."; + form.reset(); + } catch (err) { + msg.textContent = "Не получилось отправить. Попробуйте ещё раз или напишите организатору напрямую."; + } }); - if (!isNaN(score)) rows = rows.slice().sort(function (a, b) { return a.min - b.min; }); - renderTable(rows); - document.getElementById("search-note").hidden = true; - document.getElementById("universities").scrollIntoView({ behavior: "smooth" }); -}); - -(function () { - const avg = Math.round(UNIVERSITIES.reduce(function (s, u) { return s + u.min; }, 0) / UNIVERSITIES.length); - const el = document.getElementById("prog-avg"); - const num = document.getElementById("prog-avg-num"); - if (el) el.style.width = Math.min(100, Math.round(avg / 2)) + "%"; - if (num) num.textContent = avg; -})(); - -renderTable(UNIVERSITIES); +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..2441b17 --- /dev/null +++ b/server.js @@ -0,0 +1,51 @@ +const http = require("http"); +const fs = require("fs"); +const path = require("path"); + +const PORT = process.env.PORT || 3000; +const DATA = path.join(__dirname, "data.json"); + +function readAll() { + try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return { rsvps: [] }; } +} +function writeAll(d) { + fs.writeFileSync(DATA, JSON.stringify(d, null, 2)); +} +function body(req) { + return new Promise((resolve) => { + let s = ""; + req.on("data", (c) => (s += c)); + req.on("end", () => { try { resolve(JSON.parse(s || "{}")); } catch (_) { resolve({}); } }); + }); +} + +http.createServer(async (req, res) => { + if (req.method === "POST" && req.url.split("?")[0] === "/api/rsvp") { + const item = await body(req); + const d = readAll(); + d.rsvps.push({ + id: Date.now(), + name: String(item.name || "").slice(0, 80), + guests: parseInt(item.guests, 10) || 1, + attendance: item.attendance === "no" ? "no" : "yes", + note: String(item.note || "").slice(0, 300), + createdAt: new Date().toISOString(), + }); + writeAll(d); + res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" }); + return res.end(JSON.stringify({ ok: true })); + } + if (req.method === "GET" && req.url.split("?")[0] === "/api/rsvp") { + res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" }); + return res.end(JSON.stringify(readAll().rsvps)); + } + let file = req.url === "/" ? "/index.html" : req.url.split("?")[0]; + const full = path.join(__dirname, file); + if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) { + const ext = path.extname(full); + const type = ext === ".css" ? "text/css" : ext === ".js" ? "application/javascript" : ext === ".svg" ? "image/svg+xml" : "text/html"; + res.writeHead(200, { "Content-Type": type + "; charset=utf-8" }); + return res.end(fs.readFileSync(full)); + } + res.writeHead(404); res.end("Not found"); +}).listen(PORT, () => console.log("Server on port " + PORT));