initial site
This commit is contained in:
commit
da5b259d06
167 files changed
+34157
No files matched your search
@@ -0,0 +1,143 @@
|
||||
// Патч: под словесным знаком «НЕОН» появляется подпись «(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");
|
||||
Reference in new issue
Block a user