// Патч: под словесным знаком «НЕОН» появляется подпись «(ex Shelby)» —
// прежнее название бара. Подпись живёт до конца 2026 года: с 1 января 2027
// клиент прячет её сам (useEffect), пересборка не требуется.
// Скрипт не идемпотентен: повторный запуск откажется работать.
import { readFileSync, writeFileSync, copyFileSync } from "node:fs";
const HEADER = "src/components/Header.tsx";
const FOOTER = "src/components/Footer.tsx";
const die = (msg) => {
console.error("PATCH_FAIL: " + msg);
process.exit(1);
};
// ---------- Header.tsx ----------
let src = readFileSync(HEADER, "utf8");
copyFileSync(HEADER, HEADER + ".bak");
const MARK = "export function Logo(";
const first = src.indexOf(MARK);
if (first === -1) die("Logo declaration not found in Header.tsx");
if (src.indexOf(MARK, first + 1) !== -1) die("Logo declared more than once");
// конец списка параметров: скобочный баланс от первой ( после MARK
let i = src.indexOf("(", first);
let parenDepth = 0;
for (; i < src.length; i++) {
if (src[i] === "(") parenDepth++;
else if (src[i] === ")") {
parenDepth--;
if (parenDepth === 0) {
i++;
break;
}
}
}
// пропускаем пробелы до тела функции
while (i < src.length && /\s/.test(src[i])) i++;
if (src[i] !== "{") die("Logo body brace not found");
// тело функции: баланс фигурных скобок
let depth = 0;
let end = -1;
for (; i < src.length; i++) {
if (src[i] === "{") depth++;
else if (src[i] === "}") {
depth--;
if (depth === 0) {
end = i;
break;
}
}
}
if (end === -1) die("Logo body end not found");
const original = src.slice(first, end + 1);
if (!original.includes("") || !original.includes("ОН")) {
die("Logo body does not look like the wordmark: " + JSON.stringify(original.slice(0, 240)));
}
console.log("LOGO_FOUND: " + original.length + " chars, href=" + (original.includes('#home') ? "#home" : "?"));
const replacement = `const FORMER_NAME_DEADLINE = new Date("2027-01-01T00:00:00").getTime();
export function Logo({ className }: { className?: string }) {
// «(ex Shelby)» — прежнее название бара; подпись видна до конца 2026 года,
// после новогодней ночи клиент прячет её сам, пересборка не требуется.
// Изначально true, чтобы пререндеренный HTML и первая клиентская отрисовка
// совпадали (без hydration-мисматча); эффект убирает надпись после дедлайна.
const [showFormerName, setShowFormerName] = useState(true);
useEffect(() => {
if (Date.now() >= FORMER_NAME_DEADLINE) setShowFormerName(false);
}, []);
return (
НЕ
ОН
{showFormerName && (
(ex Shelby)
)}
);
}`;
src = src.slice(0, first) + replacement + src.slice(end + 1);
// импорты useState/useEffect, если их ещё нет до объявления Logo
const beforeLogo = src.slice(0, src.indexOf("export function Logo"));
if (!/\buseEffect\b/.test(beforeLogo)) {
if (/import \{[^}]*\} from "react";/.test(src)) {
src = src.replace(/import \{([^}]*)\} from "react";/, (m, names) => {
const names2 = names
.split(",")
.map((s) => s.trim())
.filter(Boolean);
for (const n of ["useState", "useEffect"]) {
if (!names2.includes(n)) names2.push(n);
}
return `import { ${names2.join(", ")} } from "react";`;
});
} else {
src = `import { useEffect, useState } from "react";\n` + src;
}
console.log("REACT_IMPORT_PATCHED");
} else {
console.log("REACT_IMPORT_ALREADY_THERE");
}
writeFileSync(HEADER, src);
console.log("HEADER_PATCHED");
// ---------- Footer.tsx ----------
const footer = readFileSync(FOOTER, "utf8");
if (/