Files
foil-cap-theories/generate_presentation.js
m.elgin 79acf6c317 update
2026-06-30 14:42:26 +03:00

376 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const path = require('path');
const pptxgen = require('pptxgenjs');
const base = __dirname;
const outPath = path.join(base, 'prezentaciya-vampiricheskoe-tenevoe-pravitelstvo.pptx');
const assets = {
title: path.join(base, 'assets', 'generated', '01-title-food-chain.png'),
medieval: path.join(base, 'assets', 'generated', '02-medieval-origin.png'),
classes: path.join(base, 'assets', 'generated', '03-vampire-classes.png'),
failed: path.join(base, 'assets', 'generated', '04-failed-infection.png'),
recognize: path.join(base, 'assets', 'generated', '05-recognize-vampire.png'),
blood: path.join(base, 'assets', 'generated', '06-blood-economy.png'),
artificial: path.join(base, 'assets', 'generated', '07-artificial-blood.png'),
market: path.join(base, 'assets', 'generated', '08-hidden-donor-market.png'),
donorHouse: path.join(base, 'assets', 'generated', '09-donor-house.png'),
etiquette: path.join(base, 'assets', 'generated', '10-blood-etiquette.png'),
zones: path.join(base, 'assets', 'generated', '11-feeding-zones-map.png'),
chain: path.join(base, 'assets', 'generated', '12-food-chain-chart.png')
};
const pptx = new pptxgen();
pptx.layout = 'LAYOUT_WIDE';
pptx.author = 'Hermes Agent';
pptx.company = 'Nous Research';
pptx.subject = 'Псевдонаучная теория о гемозависимой элите';
pptx.title = 'Пищевая цепочка: от тебя до теневого правительства';
pptx.lang = 'ru-RU';
pptx.theme = {
headFontFace: 'Georgia',
bodyFontFace: 'Aptos',
lang: 'ru-RU'
};
const C = {
bgDark: '130B0D',
bgLight: 'F3EADF',
card: 'FFF7EE',
red: '7A1623',
red2: 'A42A37',
text: '24181A',
cream: 'F5E6D3',
gold: 'C7A56A',
muted: '705A5D',
white: 'FFFFFF',
ink: '2A1A1D',
brown: '4A3327'
};
function footer(slide, n, dark = false) {
slide.addText(String(n).padStart(2, '0'), {
x: 12.42, y: 7.02, w: 0.4, h: 0.18,
fontSize: 10, color: dark ? 'B89B82' : C.muted,
align: 'right', margin: 0
});
}
function baseTextSlide(slide, title, tag = 'Псевдонаучная реконструкция') {
slide.background = { color: C.bgLight };
slide.addShape(pptx.ShapeType.rect, { x: 0, y: 0, w: 13.333, h: 0.42, fill: { color: C.red }, line: { color: C.red } });
slide.addText(title, { x: 0.62, y: 0.58, w: 8.8, h: 0.38, fontFace: 'Georgia', fontSize: 26, bold: true, color: C.ink, margin: 0 });
slide.addText(tag, { x: 10.0, y: 0.62, w: 2.65, h: 0.18, fontSize: 10, color: C.red2, italic: true, align: 'right', margin: 0 });
}
function addBullets(slide, items, opts = {}) {
const text = [];
items.forEach((item) => text.push({ text: item, options: { bullet: { indent: 14 }, breakLine: true } }));
slide.addText(text, {
x: opts.x ?? 0.9,
y: opts.y ?? 1.6,
w: opts.w ?? 5.2,
h: opts.h ?? 4.4,
fontSize: opts.fontSize ?? 18,
color: opts.color ?? C.text,
paraSpaceAfterPt: opts.paraSpaceAfterPt ?? 8,
margin: 0.04,
valign: 'top'
});
}
function addCard(slide, x, y, w, h, title, body, accent = C.red2) {
slide.addShape(pptx.ShapeType.roundRect, { x, y, w, h, rectRadius: 0.06, fill: { color: C.card }, line: { color: accent, pt: 1.2 } });
slide.addText(title, { x: x + 0.18, y: y + 0.16, w: w - 0.36, h: 0.24, fontSize: 17, bold: true, color: C.red, margin: 0 });
slide.addText(body, { x: x + 0.18, y: y + 0.52, w: w - 0.36, h: h - 0.7, fontSize: 14.6, color: C.text, margin: 0.02, valign: 'mid' });
}
function addCallout(slide, text, opts = {}) {
slide.addShape(pptx.ShapeType.roundRect, {
x: opts.x ?? 0.9,
y: opts.y ?? 5.82,
w: opts.w ?? 11.5,
h: opts.h ?? 0.58,
rectRadius: 0.05,
fill: { color: opts.fill ?? C.red },
line: { color: opts.fill ?? C.red }
});
slide.addText(text, {
x: (opts.x ?? 0.9) + 0.14,
y: (opts.y ?? 5.82) + 0.13,
w: (opts.w ?? 11.5) - 0.28,
h: 0.23,
align: 'center', color: C.white,
fontSize: opts.fontSize ?? 20,
bold: true, italic: opts.italic ?? true, margin: 0
});
}
function fullImage(slide, imagePath) {
slide.background = { color: C.bgDark };
slide.addImage({ path: imagePath, x: 0, y: 0, w: 13.333, h: 7.5 });
}
function lightPlaque(slide, text, x, y, w, h, fontSize = 12, opts = {}) {
slide.addText(text, {
x, y, w, h,
fontFace: opts.fontFace ?? 'Georgia',
fontSize,
bold: opts.bold ?? false,
italic: opts.italic ?? false,
color: opts.color ?? C.brown,
align: opts.align ?? 'center',
valign: opts.valign ?? 'mid',
margin: opts.margin ?? 0.02,
breakLine: true
});
}
function darkPlaque(slide, text, x, y, w, h, fontSize = 12, opts = {}) {
slide.addText(text, {
x, y, w, h,
fontFace: opts.fontFace ?? 'Georgia',
fontSize,
bold: opts.bold ?? false,
italic: opts.italic ?? false,
color: opts.color ?? C.cream,
align: opts.align ?? 'center',
valign: opts.valign ?? 'mid',
margin: opts.margin ?? 0.02,
breakLine: true
});
}
// 1 title
{
const s = pptx.addSlide();
fullImage(s, assets.title);
s.addShape(pptx.ShapeType.rect, { x: 0, y: 0, w: 13.333, h: 7.5, fill: { color: '120A0D', transparency: 34 }, line: { color: '120A0D', transparency: 100 } });
s.addShape(pptx.ShapeType.roundRect, { x: 0.72, y: 0.82, w: 6.95, h: 5.25, rectRadius: 0.06, fill: { color: '1B1114', transparency: 15 }, line: { color: C.red2, pt: 1.1, transparency: 20 } });
s.addText('Пищевая цепочка:\nот тебя до теневого правительства', { x: 1.0, y: 1.15, w: 6.3, h: 1.5, color: C.white, fontFace: 'Georgia', fontSize: 24, bold: true, margin: 0 });
s.addText('Расширенная версия: типы вампиров,\nкачественная кровь и скрытый рынок доноров', { x: 1.04, y: 2.95, w: 6.0, h: 0.75, color: C.cream, fontSize: 17.5, italic: true, margin: 0 });
s.addText('Клуб шапочек из фольги\nПсевдонаучный доклад в академическом стиле', { x: 1.04, y: 4.0, w: 4.9, h: 0.8, color: 'E0D0C4', fontSize: 16, margin: 0 });
footer(s, 1, true);
}
// 2 hypothesis
{
const s = pptx.addSlide();
baseTextSlide(s, 'Гипотеза исследования');
addCard(s, 0.88, 1.55, 5.35, 2.15, 'Базовая идея', 'Теневое правительство — это не просто политическая сеть, а долгоживущая гемозависимая каста, превратившая власть в механизм стабильного питания.');
addBullets(s, [
'вирусное происхождение касты;',
'долголетие как источник стратегического преимущества;',
'кровь как ресурс, валюта и предмет селекции;',
'власть как надстройка над пищевой цепочкой.'
], { x: 7.05, y: 1.72, w: 5.0, h: 3.6, fontSize: 18 });
addCallout(s, 'Ключевая формула: власть = контроль кормовой инфраструктуры');
footer(s, 2);
}
// 3 virus
{
const s = pptx.addSlide();
baseTextSlide(s, 'Sanguis Noctis Virus (SNV-12)');
addBullets(s, [
'латентная вирус-индуцированная вампиризация;',
'хроническая антропогематофагия;',
'гемозависимый постчеловеческий синдром;',
'обратимая фототолерантность после кормления.'
], { x: 0.88, y: 1.7, w: 5.0, h: 3.8, fontSize: 18 });
addCard(s, 6.8, 1.55, 5.3, 3.9, 'Ключевые эффекты', 'SNV-12 не делает человека мистическим существом мгновенно. Он перестраивает обменные пути, усиливает регенерацию, замедляет старение и формирует зависимость от человеческой крови как от носителя редких сигнальных молекул.');
addCallout(s, 'Вампиризм здесь — не проклятие, а вирусная адаптация', { fill: C.red2 });
footer(s, 3);
}
// 4 medieval origin
{
const s = pptx.addSlide();
fullImage(s, assets.medieval);
darkPlaque(s, 'XIIXIV века', 5.05, 0.38, 3.2, 0.34, 18, { bold: true });
lightPlaque(s, 'войны и миграции', 1.18, 6.24, 2.25, 0.34, 12);
lightPlaque(s, 'антисанитария', 3.63, 6.24, 2.15, 0.34, 12);
lightPlaque(s, 'эпидемии и массовая смертность', 6.05, 6.24, 3.0, 0.34, 12);
lightPlaque(s, 'аристократические линии', 9.35, 6.24, 2.8, 0.34, 12);
darkPlaque(s, 'Средневековье создаёт идеальную среду для скрытого патогена', 2.55, 6.83, 8.2, 0.26, 16, { italic: true, bold: true });
footer(s, 4, true);
}
// 5 classes
{
const s = pptx.addSlide();
fullImage(s, assets.classes);
lightPlaque(s, 'нестабилен, часто голоден', 0.45, 5.93, 3.42, 0.4, 12);
lightPlaque(s, 'контроль только формируется', 0.45, 6.36, 3.42, 0.38, 11.5);
lightPlaque(s, 'устойчив, скрытен, дисциплинирован', 4.62, 5.93, 3.42, 0.4, 11.6);
lightPlaque(s, 'столетнее накопление опыта', 4.62, 6.36, 3.42, 0.38, 11.3);
lightPlaque(s, 'редкое питание, максимальная селекция', 8.95, 5.93, 3.45, 0.4, 11.3);
lightPlaque(s, 'биологическая верхушка касты', 8.95, 6.36, 3.45, 0.38, 11.5);
footer(s, 5, true);
}
// 6 failed infection
{
const s = pptx.addSlide();
fullImage(s, assets.failed);
lightPlaque(s, 'объект: носитель SNV-12\nс несовместимым фенотипом', 0.52, 0.8, 3.47, 1.16, 12.5, { bold: true });
lightPlaque(s, 'сосудистый\nперегрев', 4.48, 1.74, 0.78, 0.28, 10.8);
lightPlaque(s, 'гемолиз', 4.47, 2.7, 0.78, 0.28, 10.8);
lightPlaque(s, 'системный\nколлапс', 4.47, 3.54, 0.78, 0.3, 10.5);
lightPlaque(s, 'рост температуры\nдо критического порога', 1.2, 4.0, 2.8, 0.28, 10.8);
lightPlaque(s, 'утрата стабильности\nкровяной среды', 0.9, 6.05, 3.1, 0.38, 12);
lightPlaque(s, 'Неудачное обращение объясняется\nтермогемолитическим коллапсом.', 4.35, 5.45, 3.8, 1.1, 13, { bold: true });
footer(s, 6, true);
}
// 7 recognize vampire
{
const s = pptx.addSlide();
fullImage(s, assets.recognize);
lightPlaque(s, 'слегка увеличенные\nклыки', 0.42, 2.06, 2.38, 0.46, 12.4);
lightPlaque(s, 'медленное\nстарение', 0.42, 3.58, 2.38, 0.46, 12.4);
lightPlaque(s, 'слишком ровная\nкожа', 0.42, 5.14, 2.38, 0.46, 12.2);
lightPlaque(s, 'тёмный расширенный\nзрачок', 10.53, 2.06, 2.38, 0.46, 12.2);
lightPlaque(s, 'интерес к крови\nи травмам', 10.53, 3.58, 2.38, 0.46, 12.2);
lightPlaque(s, 'закрытые клубы\nи тайные круги', 10.53, 5.14, 2.38, 0.46, 12.0);
footer(s, 7, true);
}
// 8 blood economy
{
const s = pptx.addSlide();
fullImage(s, assets.blood);
lightPlaque(s, 'базовое\nподдержание', 1.26, 1.84, 2.05, 0.36, 12.2);
lightPlaque(s, 'норма для\nпериферии', 1.26, 3.33, 2.05, 0.36, 12.2);
lightPlaque(s, 'усиление\nспособностей', 1.26, 4.82, 2.05, 0.36, 12.0);
lightPlaque(s, 'ресурс для элиты\nи ритуалов', 1.26, 6.3, 2.05, 0.36, 11.6);
lightPlaque(s, 'минимальные\nтребования', 10.16, 2.0, 1.62, 0.28, 10.4);
lightPlaque(s, 'устойчивый\nрацион', 10.16, 3.48, 1.62, 0.28, 10.4);
lightPlaque(s, 'для старших\nадаптантов', 10.16, 4.98, 1.62, 0.28, 10.2);
lightPlaque(s, 'только для\nдревних', 10.16, 6.47, 1.62, 0.28, 10.2);
lightPlaque(s, 'дефицит создаёт власть', 0.78, 6.72, 2.2, 0.24, 11.2, { bold: true });
lightPlaque(s, 'чем старше древний,\nтем выше требования', 10.38, 6.72, 2.2, 0.34, 10.5, { bold: true });
footer(s, 8, true);
}
// 9 artificial blood
{
const s = pptx.addSlide();
fullImage(s, assets.artificial);
lightPlaque(s, 'высокая\nвариативность', 4.78, 1.58, 1.55, 0.28, 10.2);
lightPlaque(s, 'выражены', 4.78, 2.54, 1.55, 0.28, 10.2);
lightPlaque(s, 'уникален', 4.78, 3.48, 1.55, 0.28, 10.2);
lightPlaque(s, 'активна', 4.78, 4.42, 1.55, 0.28, 10.2);
lightPlaque(s, 'есть', 4.78, 5.47, 1.55, 0.28, 10.2);
lightPlaque(s, 'полное', 4.78, 6.33, 1.55, 0.28, 10.2);
lightPlaque(s, 'сглажен', 10.2, 1.58, 1.55, 0.28, 10.2);
lightPlaque(s, 'почти нет', 10.2, 2.54, 1.55, 0.28, 10.2);
lightPlaque(s, 'стёрт', 10.2, 3.48, 1.55, 0.28, 10.2);
lightPlaque(s, 'низкая', 10.2, 4.42, 1.55, 0.28, 10.2);
lightPlaque(s, 'нет', 10.2, 5.47, 1.55, 0.28, 10.2);
lightPlaque(s, 'минимальное', 10.2, 6.33, 1.55, 0.28, 10.2);
footer(s, 9, true);
}
// 10 hidden donor market
{
const s = pptx.addSlide();
fullImage(s, assets.market);
darkPlaque(s, 'Тайный рынок доноров', 5.35, 2.06, 2.1, 0.28, 16, { bold: true });
darkPlaque(s, 'Элитные\nшколы', 0.95, 1.95, 1.35, 0.26, 10.6);
darkPlaque(s, 'Закрытые\nклубы', 2.78, 1.95, 1.35, 0.26, 10.6);
darkPlaque(s, 'Донорские\nдома', 9.0, 1.95, 1.48, 0.26, 10.4);
darkPlaque(s, 'Династии', 10.85, 1.95, 1.45, 0.26, 10.6);
darkPlaque(s, 'Клиники', 0.75, 4.13, 1.9, 0.22, 10.8);
darkPlaque(s, 'Медицинский\nотбор', 2.95, 4.13, 1.9, 0.22, 10.0);
lightPlaque(s, 'лаунджи для\nудержания доноров', 5.55, 4.88, 2.42, 0.34, 10.5);
darkPlaque(s, 'Биобанки', 9.02, 4.13, 1.9, 0.22, 10.8);
darkPlaque(s, 'Архивы и\nдосье', 11.18, 4.13, 1.72, 0.22, 10.0);
darkPlaque(s, 'доноры', 0.62, 6.86, 1.05, 0.2, 9.8);
darkPlaque(s, 'анкеты', 2.62, 6.86, 1.05, 0.2, 9.8);
darkPlaque(s, 'отбор', 4.83, 6.86, 1.05, 0.2, 9.8);
darkPlaque(s, 'пробирки', 6.93, 6.86, 1.05, 0.2, 9.8);
darkPlaque(s, 'архив', 9.13, 6.86, 1.05, 0.2, 9.8);
darkPlaque(s, 'сердце рынка', 11.28, 6.86, 1.18, 0.2, 9.4);
footer(s, 10, true);
}
// 11 donor house
{
const s = pptx.addSlide();
fullImage(s, assets.donorHouse);
darkPlaque(s, 'Донорский дом', 0.82, 0.52, 4.95, 0.4, 22, { bold: true });
darkPlaque(s, 'санаторий, пансион и селекционный узел\nпод одной крышей', 0.92, 1.0, 4.7, 0.44, 12.8, { italic: true });
darkPlaque(s, 'отбор', 7.15, 6.52, 1.1, 0.22, 10.5);
darkPlaque(s, 'обучение', 9.26, 6.52, 1.1, 0.22, 10.5);
darkPlaque(s, 'медицина', 11.32, 6.52, 1.1, 0.22, 10.5);
darkPlaque(s, 'селекция', 12.45, 6.52, 0.65, 0.22, 9.5);
footer(s, 11, true);
}
// 12 blood etiquette
{
const s = pptx.addSlide();
fullImage(s, assets.etiquette);
lightPlaque(s, 'Кровяной этикет элиты', 3.95, 0.42, 5.38, 0.42, 22, { bold: true });
darkPlaque(s, 'оценка донора', 3.05, 6.86, 2.05, 0.22, 11.2, { bold: true });
darkPlaque(s, 'ритуал насыщения', 5.36, 6.86, 2.3, 0.22, 11.2, { bold: true });
darkPlaque(s, 'учёт качества', 7.96, 6.86, 2.1, 0.22, 11.2, { bold: true });
darkPlaque(s, 'документы допуска', 10.45, 6.86, 2.15, 0.22, 10.6, { bold: true });
footer(s, 12, true);
}
// 13 feeding zones
{
const s = pptx.addSlide();
fullImage(s, assets.zones);
lightPlaque(s, 'Геополитика кормовых зон', 3.62, 0.34, 5.9, 0.34, 20, { bold: true });
lightPlaque(s, 'резервуары\nнаселения', 0.68, 3.62, 1.8, 0.26, 10.4);
lightPlaque(s, 'морские\nкоридоры', 0.68, 4.07, 1.8, 0.26, 10.4);
lightPlaque(s, 'авиационные\nузлы', 0.68, 4.53, 1.8, 0.26, 10.2);
lightPlaque(s, 'убежища\nэлиты', 0.68, 4.99, 1.8, 0.26, 10.4);
lightPlaque(s, 'эксперименты\nи селекция', 0.68, 5.45, 1.8, 0.26, 10.0);
lightPlaque(s, 'карта показывает не государства,\nа контуры биологического снабжения', 4.72, 6.88, 3.95, 0.36, 11.6, { bold: true });
footer(s, 13, true);
}
// 14 food chain chart
{
const s = pptx.addSlide();
fullImage(s, assets.chain);
lightPlaque(s, 'Пищевая цепочка: от тебя до теневого правительства', 1.82, 0.28, 9.72, 0.34, 17.5, { bold: true });
darkPlaque(s, 'человек', 0.32, 1.8, 1.48, 0.2, 10.2);
darkPlaque(s, 'наблюдение', 2.45, 1.8, 1.48, 0.2, 10.2);
darkPlaque(s, 'сортировка', 4.64, 1.8, 1.48, 0.2, 10.2);
darkPlaque(s, 'логистика', 6.82, 1.8, 1.48, 0.2, 10.2);
darkPlaque(s, 'встреча\nэлиты', 9.02, 1.8, 1.48, 0.2, 9.8);
darkPlaque(s, 'досье', 11.18, 1.8, 1.48, 0.2, 10.2);
darkPlaque(s, 'гемарх', 12.08, 1.8, 0.95, 0.2, 10.0);
lightPlaque(s, 'массовая\nдонорская база', 0.33, 4.92, 1.7, 0.42, 10.3);
lightPlaque(s, 'отбор по\nбиометрии', 4.0, 6.16, 1.85, 0.3, 10.2);
lightPlaque(s, 'качество важнее\nколичества', 7.67, 6.16, 1.95, 0.3, 10.0);
lightPlaque(s, 'верхушка\nспроса', 11.32, 4.92, 1.6, 0.42, 10.4);
footer(s, 14, true);
}
// 15 conclusion
{
const s = pptx.addSlide();
s.background = { color: C.bgDark };
s.addShape(pptx.ShapeType.rect, { x: 0, y: 0, w: 13.333, h: 7.5, fill: { color: C.bgDark }, line: { color: C.bgDark } });
s.addText('Вывод', { x: 0.92, y: 0.92, w: 3.0, h: 0.5, color: C.white, fontFace: 'Georgia', fontSize: 28, bold: true, margin: 0 });
s.addText('Если теория верна, вершина мировой политики — это не кабинет и не трон, а хорошо организованная столовая премиум-класса, где человечество давно числится в меню.', { x: 0.95, y: 2.0, w: 8.15, h: 1.9, fontSize: 24, color: C.cream, margin: 0.02, valign: 'mid' });
s.addShape(pptx.ShapeType.roundRect, { x: 9.15, y: 1.6, w: 2.85, h: 3.4, rectRadius: 0.08, fill: { color: '2A1419' }, line: { color: C.gold, pt: 1.3 } });
s.addText('Ключевые\nформулы', { x: 9.72, y: 2.0, w: 1.75, h: 0.6, align: 'center', fontSize: 20, bold: true, color: C.white, margin: 0 });
s.addText('• власть = кормовая логистика\n• кровь = валюта\n• элита скрывает не власть, а рацион', { x: 9.45, y: 2.9, w: 2.25, h: 1.25, fontSize: 15.5, color: C.cream, margin: 0.02 });
s.addText('Жанр: академически поданная псевдонаука для клуба шапочек из фольги', { x: 1.0, y: 6.45, w: 7.4, h: 0.22, fontSize: 13, color: 'D2B8A5', italic: true, margin: 0 });
footer(s, 15, true);
}
async function main() {
await pptx.writeFile({ fileName: outPath });
}
main().catch((err) => {
console.error(err.stack || err.message || String(err));
process.exit(1);
});