Files
2026-10-11 22:24:54 +05:00

144 lines
5.1 KiB
JavaScript

// Патч: под словесным знаком «НЕОН» появляется подпись «(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("</a>") || !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 (
<a
href="#home"
className={cn(
"group inline-flex flex-col font-display text-xl font-extrabold tracking-[0.18em] sm:text-2xl",
className,
)}
>
<span className="flex">
<span className="text-glow-violet text-white transition-all duration-300 group-hover:text-fuchsia-200">
НЕ
</span>
<span className="text-glow-cyan text-white transition-all duration-300 group-hover:text-cyan-200">
ОН
</span>
</span>
{showFormerName && (
<span className="mt-1 text-[9px] font-semibold tracking-[0.32em] text-mist/45 transition-colors duration-300 group-hover:text-mist/75">
(ex Shelby)
</span>
)}
</a>
);
}`;
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 (/<Logo\b/.test(footer)) {
console.log("FOOTER_USES_SHARED_LOGO");
} else {
console.log("FOOTER_NEEDS_MANUAL: no <Logo usage; wordmark occurrences:");
let idx = footer.indexOf("ОН");
let n = 0;
while (idx !== -1 && n < 5) {
console.log(
"--- @" + idx + ": " + JSON.stringify(footer.slice(Math.max(0, idx - 140), idx + 180)),
);
idx = footer.indexOf("ОН", idx + 1);
n++;
}
}
console.log("PATCH_OK");