const pptxgen = require("pptxgenjs"); const React = require("react"); const ReactDOMServer = require("react-dom/server"); const sharp = require("sharp"); const { FaBuilding, FaRobot, FaComments, FaCalendarAlt, FaAtom, FaShieldAlt, FaRocket, FaQuestion, FaSearch, FaUsers, FaBullhorn, FaLock, FaChartLine, FaHandshake, FaIndustry, FaGlobe, FaEnvelope, FaBell, FaMobileAlt, FaNewspaper, FaUserShield, FaMoneyBillWave, FaStar, FaCheckCircle, FaLightbulb, FaMapMarkedAlt, FaFileAlt } = require("react-icons/fa"); function renderIconSvg(IconComponent, color = "#000000", size = 256) { return ReactDOMServer.renderToStaticMarkup( React.createElement(IconComponent, { color, size: String(size) }) ); } async function iconToBase64Png(IconComponent, color, size = 256) { const svg = renderIconSvg(IconComponent, color, size); const pngBuffer = await sharp(Buffer.from(svg)).png().toBuffer(); return "image/png;base64," + pngBuffer.toString("base64"); } // Color palette const C = { darkBg: "0F172A", navyBg: "1E293B", primary: "0891B2", primaryLight: "22D3EE", accent: "06B6D4", lightBg: "F1F5F9", white: "FFFFFF", cardBg: "FFFFFF", textDark: "1E293B", textMuted: "64748B", textLight: "CBD5E1", green: "10B981", amber: "F59E0B", red: "EF4444", purple: "8B5CF6", indigo: "6366F1", tealDark: "065F46", }; const FONT_TITLE = "Georgia"; const FONT_BODY = "Calibri"; const makeShadow = () => ({ type: "outer", blur: 6, offset: 2, angle: 135, color: "000000", opacity: 0.12 }); function addSlideNumber(slide, num, total) { slide.addText(`${num} / ${total}`, { x: 8.8, y: 5.2, w: 1, h: 0.3, fontSize: 9, fontFace: FONT_BODY, color: C.textLight, align: "right" }); } function addSectionHeader(slide, icon, title, subtitle, num, total) { // Dark left panel slide.addShape(slide._slideLayout ? "rect" : "rect", {}); // We'll just build the full slide manually } async function createPresentation() { const pres = new pptxgen(); pres.layout = "LAYOUT_16x9"; pres.author = "Maciej Pienczyn"; pres.title = "NordaBiznes.pl — Prezentacja portalu"; const TOTAL = 14; // Pre-render icons const icons = {}; const iconMap = { building: [FaBuilding, C.white], robot: [FaRobot, C.white], comments: [FaComments, C.white], calendar: [FaCalendarAlt, C.white], atom: [FaAtom, C.white], shield: [FaShieldAlt, C.white], rocket: [FaRocket, C.white], question: [FaQuestion, C.white], search: [FaSearch, C.primary], users: [FaUsers, C.primary], bullhorn: [FaBullhorn, C.primary], lock: [FaLock, C.primary], chart: [FaChartLine, C.primary], handshake: [FaHandshake, C.primary], industry: [FaIndustry, C.primary], globe: [FaGlobe, C.primary], envelope: [FaEnvelope, C.primary], bell: [FaBell, C.primary], mobile: [FaMobileAlt, C.primary], newspaper: [FaNewspaper, C.primary], userShield: [FaUserShield, C.primary], money: [FaMoneyBillWave, C.primary], star: [FaStar, C.amber], check: [FaCheckCircle, C.green], lightbulb: [FaLightbulb, C.amber], map: [FaMapMarkedAlt, C.primary], file: [FaFileAlt, C.primary], // Dark bg versions buildingDark: [FaBuilding, C.primaryLight], robotDark: [FaRobot, C.primaryLight], commentsDark: [FaComments, C.primaryLight], calendarDark: [FaCalendarAlt, C.primaryLight], atomDark: [FaAtom, C.primaryLight], industryDark: [FaIndustry, C.primaryLight], shieldDark: [FaShieldAlt, C.primaryLight], rocketDark: [FaRocket, C.primaryLight], checkWhite: [FaCheckCircle, C.white], globeWhite: [FaGlobe, C.white], searchWhite: [FaSearch, C.white], lightbulbWhite: [FaLightbulb, C.white], handshakeWhite: [FaHandshake, C.white], }; for (const [key, [comp, color]] of Object.entries(iconMap)) { icons[key] = await iconToBase64Png(comp, `#${color}`, 256); } // Helper: colored icon circle function addIconCircle(slide, iconKey, x, y, size, bgColor) { slide.addShape(pres.shapes.OVAL, { x, y, w: size, h: size, fill: { color: bgColor } }); const pad = size * 0.25; slide.addImage({ data: icons[iconKey], x: x + pad, y: y + pad, w: size - pad * 2, h: size - pad * 2 }); } // Helper: content card with left accent function addCard(slide, x, y, w, h, accentColor) { slide.addShape(pres.shapes.RECTANGLE, { x, y, w, h, fill: { color: C.white }, shadow: makeShadow() }); slide.addShape(pres.shapes.RECTANGLE, { x, y, w: 0.06, h, fill: { color: accentColor || C.primary } }); } // Logo paths const nordaLogoPath = "/Users/maciejpi/claude/projects/active/nordabiz/static/img/logo-email.png"; const inpiLogoPath = "/tmp/inpi-logo.png"; // ============================================= // SLIDE 1: TYTUŁ // ============================================= let s = pres.addSlide(); s.background = { color: C.darkBg }; // Accent line top s.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.06, fill: { color: C.primary } }); // Norda compass logo — right side, large s.addImage({ path: nordaLogoPath, x: 7.5, y: 1.2, w: 2.0, h: 2.0, transparency: 15 }); s.addText("NordaBiznes.pl", { x: 0.8, y: 1.2, w: 6.5, h: 1.2, fontSize: 48, fontFace: FONT_TITLE, color: C.white, bold: true, margin: 0 }); s.addText("Platforma członków Izby NORDA", { x: 0.8, y: 2.3, w: 6.5, h: 0.6, fontSize: 22, fontFace: FONT_BODY, color: C.primaryLight, margin: 0 }); s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 3.2, w: 2, h: 0.04, fill: { color: C.primary } }); s.addText("Prezentacja dla członków Izby", { x: 0.8, y: 3.6, w: 5, h: 0.4, fontSize: 14, fontFace: FONT_BODY, color: C.textLight, margin: 0 }); s.addText("9 kwietnia 2026 | Urząd Miasta Wejherowo", { x: 0.8, y: 4.0, w: 5, h: 0.4, fontSize: 14, fontFace: FONT_BODY, color: C.textLight, margin: 0 }); s.addText("Maciej Pienczyn | InPi sp. z o.o.", { x: 0.8, y: 4.6, w: 5, h: 0.4, fontSize: 14, fontFace: FONT_BODY, color: C.textLight, margin: 0 }); // ============================================= // SLIDE 2: AGENDA // ============================================= s = pres.addSlide(); s.background = { color: C.lightBg }; s.addText("Co dziś pokażemy", { x: 0.8, y: 0.4, w: 8.4, h: 0.7, fontSize: 32, fontFace: FONT_TITLE, color: C.textDark, bold: true, margin: 0 }); s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.05, w: 1.2, h: 0.04, fill: { color: C.primary } }); const agendaItems = [ ["building", "Twoja firma w Izbie", "Profil, wizytówka, wyszukiwanie"], ["robot", "NordaGPT", "Asystent AI, który zna wszystkich"], ["comments", "Komunikacja", "Wiadomości, forum, ogłoszenia B2B"], ["calendar", "Kalendarz", "Wydarzenia, zapisy, przypomnienia"], ["atom", "PEJ i Kaszubia", "Elektrownia jądrowa, inwestycje"], ["shield", "Prywatność", "Kto widzi co, bezpieczeństwo"], ]; const startY = 1.4; for (let i = 0; i < agendaItems.length; i++) { const [icon, title, desc] = agendaItems[i]; const row = i; const yPos = startY + row * 0.65; addIconCircle(s, icon, 0.8, yPos + 0.05, 0.45, C.primary); s.addText(title, { x: 1.45, y: yPos, w: 3, h: 0.35, fontSize: 15, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0, valign: "middle" }); s.addText(desc, { x: 1.45, y: yPos + 0.3, w: 5, h: 0.3, fontSize: 12, fontFace: FONT_BODY, color: C.textMuted, margin: 0 }); } // Right side - stats addCard(s, 6.5, 1.4, 3, 3.5, C.primary); s.addText("Portal w liczbach", { x: 6.8, y: 1.6, w: 2.5, h: 0.35, fontSize: 14, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); const stats = [ ["150+", "firm członkowskich"], ["38", "uczestników spotkania"], ["57", "wydań od startu"], ["17", "kategorii branżowych"], ["6", "poziomów dostępu"], ]; for (let i = 0; i < stats.length; i++) { s.addText(stats[i][0], { x: 6.8, y: 2.15 + i * 0.52, w: 0.8, h: 0.4, fontSize: 20, fontFace: FONT_TITLE, color: C.primary, bold: true, margin: 0, valign: "middle" }); s.addText(stats[i][1], { x: 7.7, y: 2.15 + i * 0.52, w: 1.6, h: 0.4, fontSize: 12, fontFace: FONT_BODY, color: C.textMuted, margin: 0, valign: "middle" }); } addSlideNumber(s, 2, TOTAL); // ============================================= // SLIDE 3: PO CO POWSTAŁ PORTAL // ============================================= s = pres.addSlide(); s.background = { color: C.darkBg }; s.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.06, fill: { color: C.primary } }); s.addText("Po co powstał NordaBiznes?", { x: 0.8, y: 0.4, w: 8.4, h: 0.7, fontSize: 32, fontFace: FONT_TITLE, color: C.white, bold: true, margin: 0 }); const whyItems = [ [icons.handshakeWhite, "Łączyć firmy", "Znajdź partnera biznesowego w Izbie\nbez szukania numeru, maila czy pośredników"], [icons.searchWhite, "Pokazywać", "Twoja firma widoczna dla wszystkich\nczłonków — wizytówka, usługi, dane kontaktowe"], [icons.globeWhite, "Informować", "Aktualności, wydarzenia, PEJ,\ninwestycje na Kaszubach — wszystko w jednym"], [icons.lightbulbWhite, "Wspierać AI", "NordaGPT zna firmy w Izbie\ni pomaga znaleźć właściwego partnera"], ]; for (let i = 0; i < whyItems.length; i++) { const col = i % 2; const row = Math.floor(i / 2); const x = 0.8 + col * 4.5; const y = 1.5 + row * 1.8; s.addShape(pres.shapes.RECTANGLE, { x, y, w: 4, h: 1.5, fill: { color: C.navyBg }, shadow: makeShadow() }); s.addShape(pres.shapes.RECTANGLE, { x, y, w: 0.06, h: 1.5, fill: { color: C.primary } }); s.addImage({ data: whyItems[i][0], x: x + 0.3, y: y + 0.25, w: 0.4, h: 0.4 }); s.addText(whyItems[i][1], { x: x + 0.9, y: y + 0.15, w: 2.8, h: 0.4, fontSize: 16, fontFace: FONT_BODY, color: C.white, bold: true, margin: 0 }); s.addText(whyItems[i][2], { x: x + 0.9, y: y + 0.55, w: 2.8, h: 0.8, fontSize: 12, fontFace: FONT_BODY, color: C.textLight, margin: 0 }); } addSlideNumber(s, 3, TOTAL); // ============================================= // SLIDE 4: PROFIL FIRMY // ============================================= s = pres.addSlide(); s.background = { color: C.lightBg }; s.addText("Twoja firma na portalu", { x: 0.8, y: 0.4, w: 8.4, h: 0.7, fontSize: 32, fontFace: FONT_TITLE, color: C.textDark, bold: true, margin: 0 }); s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.05, w: 1.2, h: 0.04, fill: { color: C.primary } }); // Left column - what's on the profile addCard(s, 0.8, 1.4, 4.2, 3.6, C.primary); s.addText("Co jest na profilu?", { x: 1.1, y: 1.55, w: 3.5, h: 0.35, fontSize: 16, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); const profileItems = [ "Opis firmy, usługi, technologie", "Dane kontaktowe (telefon, email, adres)", "Strony internetowe (do 5)", "Social media (FB, IG, LinkedIn, YT)", "Godziny otwarcia (status na żywo)", "Dane z rejestrów: KRS / CEIDG", "Rekomendacje od innych firm", "Zarząd, wspólnicy, kapitał", ]; s.addText(profileItems.map((item, i) => ({ text: item, options: { bullet: true, breakLine: i < profileItems.length - 1, fontSize: 12, color: C.textDark } })), { x: 1.1, y: 2.0, w: 3.6, h: 2.8, fontFace: FONT_BODY, paraSpaceAfter: 4, margin: 0 }); // Right column - what you can do addCard(s, 5.4, 1.4, 4.2, 3.6, C.green); s.addText("Co możesz zrobić?", { x: 5.7, y: 1.55, w: 3.5, h: 0.35, fontSize: 16, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); const actionItems = [ "Edytuj profil swojej firmy", "Zarządzaj zespołem (role, uprawnienia)", "Kontroluj co jest widoczne publicznie", "Zaproś pracowników do portalu", "Sprawdź wskaźnik kompletności", "Pobierz dane z urzędowych rejestrów", "Zbieraj AI o swojej firmie z internetu", "Przeglądaj statystyki odwiedzin", ]; s.addText(actionItems.map((item, i) => ({ text: item, options: { bullet: true, breakLine: i < actionItems.length - 1, fontSize: 12, color: C.textDark } })), { x: 5.7, y: 2.0, w: 3.6, h: 2.8, fontFace: FONT_BODY, paraSpaceAfter: 4, margin: 0 }); addSlideNumber(s, 4, TOTAL); // ============================================= // SLIDE 5: WYSZUKIWARKA I KATEGORIE // ============================================= s = pres.addSlide(); s.background = { color: C.lightBg }; s.addText("Szukaj i znajdź", { x: 0.8, y: 0.4, w: 8.4, h: 0.7, fontSize: 32, fontFace: FONT_TITLE, color: C.textDark, bold: true, margin: 0 }); s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.05, w: 1.2, h: 0.04, fill: { color: C.primary } }); s.addText("Wyszukiwarka rozumie literówki, synonimy i skróty", { x: 0.8, y: 1.3, w: 8, h: 0.4, fontSize: 14, fontFace: FONT_BODY, color: C.textMuted, margin: 0 }); // Category cards const categories = [ ["IT i technologie", "Software, hosting,\nautomatyzacja", C.indigo], ["Budownictwo", "Projekty, wykonawstwo,\ninstalacje", C.amber], ["Usługi", "Finanse, prawo,\nkonsulting, HR", C.green], ["Produkcja", "Przemysł, CNC,\nmetalurgia", C.red], ]; for (let i = 0; i < categories.length; i++) { const x = 0.8 + i * 2.25; const [name, desc, color] = categories[i]; s.addShape(pres.shapes.RECTANGLE, { x, y: 1.9, w: 2.0, h: 1.4, fill: { color: C.white }, shadow: makeShadow() }); s.addShape(pres.shapes.RECTANGLE, { x, y: 1.9, w: 2.0, h: 0.06, fill: { color } }); s.addText(name, { x: x + 0.15, y: 2.1, w: 1.7, h: 0.35, fontSize: 13, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); s.addText(desc, { x: x + 0.15, y: 2.5, w: 1.7, h: 0.6, fontSize: 11, fontFace: FONT_BODY, color: C.textMuted, margin: 0 }); } // Bottom - search features addCard(s, 0.8, 3.6, 8.4, 1.5, C.primary); s.addText("Jak szukać?", { x: 1.1, y: 3.75, w: 3, h: 0.35, fontSize: 15, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); const searchFeatures = [ ["Po nazwie firmy", "Wpisz fragment nazwy — znajdzie nawet z literówką"], ["Po NIP lub REGON", "Dokładne wyszukiwanie po numerach rejestrowych"], ["Po kategorii", "Filtruj po branży i podkategorii"], ["Przez NordaGPT", "Opisz czego szukasz swoimi słowami"], ]; for (let i = 0; i < searchFeatures.length; i++) { const col = i % 2; const row = Math.floor(i / 2); const x = 1.1 + col * 4.1; const y = 4.15 + row * 0.45; s.addImage({ data: icons.check, x, y: y + 0.02, w: 0.2, h: 0.2 }); s.addText([ { text: searchFeatures[i][0] + " ", options: { bold: true, fontSize: 11 } }, { text: searchFeatures[i][1], options: { fontSize: 11, color: C.textMuted } } ], { x: x + 0.3, y, w: 3.5, h: 0.35, fontFace: FONT_BODY, color: C.textDark, margin: 0, valign: "middle" }); } addSlideNumber(s, 5, TOTAL); // ============================================= // SLIDE 6: NORDAGPT // ============================================= s = pres.addSlide(); s.background = { color: C.darkBg }; s.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.06, fill: { color: C.primary } }); addIconCircle(s, "robot", 0.8, 0.35, 0.55, C.primary); s.addText("NordaGPT — asystent AI Izby", { x: 1.5, y: 0.35, w: 7, h: 0.6, fontSize: 30, fontFace: FONT_TITLE, color: C.white, bold: true, margin: 0, valign: "middle" }); // What NordaGPT knows s.addText("Co wie NordaGPT?", { x: 0.8, y: 1.2, w: 4, h: 0.4, fontSize: 16, fontFace: FONT_BODY, color: C.primaryLight, bold: true, margin: 0 }); const gptKnows = [ "Wszystkie firmy członkowskie — usługi, dane", "Kto jest kim w Zarządzie i Radzie Izby", "Kalendarz wydarzeń i aktualności", "Projekt elektrowni jądrowej (PEJ)", "Inwestycje na Kaszubach (ZOPK)", "Ogłoszenia B2B i tematy na forum", ]; s.addText(gptKnows.map((item, i) => ({ text: item, options: { bullet: true, breakLine: i < gptKnows.length - 1, fontSize: 13, color: C.white } })), { x: 0.8, y: 1.7, w: 4.2, h: 2.5, fontFace: FONT_BODY, paraSpaceAfter: 5, margin: 0 }); // Right - example questions s.addText("Przykładowe pytania:", { x: 5.4, y: 1.2, w: 4, h: 0.4, fontSize: 16, fontFace: FONT_BODY, color: C.primaryLight, bold: true, margin: 0 }); const examples = [ '"Szukam firmy od instalacji elektrycznych"', '"Kto w Izbie zajmuje się budownictwem?"', '"Jakie są najbliższe wydarzenia?"', '"Co nowego w sprawie elektrowni jądrowej?"', '"Firma od IT z doświadczeniem w offshore"', ]; for (let i = 0; i < examples.length; i++) { s.addShape(pres.shapes.RECTANGLE, { x: 5.4, y: 1.7 + i * 0.5, w: 4.2, h: 0.4, fill: { color: C.navyBg } }); s.addText(examples[i], { x: 5.6, y: 1.7 + i * 0.5, w: 3.8, h: 0.4, fontSize: 11, fontFace: FONT_BODY, color: C.textLight, italic: true, margin: 0, valign: "middle" }); } // Bottom note about AI s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 4.5, w: 8.4, h: 0.7, fill: { color: "172554" } }); s.addText([ { text: "Odpowiedzi na żywo ", options: { fontSize: 13, color: C.white, bold: true } }, { text: " | ", options: { fontSize: 13, color: C.primaryLight } }, { text: "Propozycje kolejnych pytań ", options: { fontSize: 13, color: C.white, bold: true } }, { text: " | ", options: { fontSize: 13, color: C.primaryLight } }, { text: "Klikalne linki do firm", options: { fontSize: 13, color: C.white, bold: true } }, ], { x: 1.0, y: 4.5, w: 8, h: 0.7, fontFace: FONT_BODY, align: "center", valign: "middle", margin: 0 }); addSlideNumber(s, 6, TOTAL); // ============================================= // SLIDE 7: KOMUNIKACJA // ============================================= s = pres.addSlide(); s.background = { color: C.lightBg }; s.addText("Komunikacja w portalu", { x: 0.8, y: 0.4, w: 8.4, h: 0.7, fontSize: 32, fontFace: FONT_TITLE, color: C.textDark, bold: true, margin: 0 }); s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.05, w: 1.2, h: 0.04, fill: { color: C.primary } }); // Three columns const commCols = [ { title: "Wiadomości prywatne", color: C.primary, items: [ "Napisz do każdego członka Izby", "Bąbelki jak w WhatsApp / Teams", "Formatowanie tekstu, linki", "Załączniki: PDF, zdjęcia", "Potwierdzenie odczytania", "Wiadomości grupowe", ] }, { title: "Forum dyskusyjne", color: C.green, items: [ "Tematy z kategoriami", "Odpowiadaj, reaguj emoji", "Śledź tematy (powiadomienia)", "Wstawiaj zdjęcia i linki", "Oznaczaj @użytkowników", "Najlepsza odpowiedź", ] }, { title: "Tablica B2B", color: C.amber, items: [ "Ogłoszenia z ofertami usług", "\"Jestem zainteresowany\"", "Publiczne pytania / odpowiedzi", "Kto widział ogłoszenie?", "Kategorie: Partnerstwo, Okazja...", "Wysyłaj wiadomość z ogłoszenia", ] }, ]; for (let c = 0; c < commCols.length; c++) { const x = 0.8 + c * 3.1; const col = commCols[c]; addCard(s, x, 1.35, 2.8, 3.7, col.color); s.addText(col.title, { x: x + 0.25, y: 1.5, w: 2.3, h: 0.35, fontSize: 14, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); s.addText(col.items.map((item, i) => ({ text: item, options: { bullet: true, breakLine: i < col.items.length - 1, fontSize: 11, color: C.textDark } })), { x: x + 0.25, y: 2.0, w: 2.3, h: 2.8, fontFace: FONT_BODY, paraSpaceAfter: 4, margin: 0 }); } addSlideNumber(s, 7, TOTAL); // ============================================= // SLIDE 8: KALENDARZ // ============================================= s = pres.addSlide(); s.background = { color: C.lightBg }; s.addText("Kalendarz wydarzeń", { x: 0.8, y: 0.4, w: 8.4, h: 0.7, fontSize: 32, fontFace: FONT_TITLE, color: C.textDark, bold: true, margin: 0 }); s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.05, w: 1.2, h: 0.04, fill: { color: C.primary } }); // Features grid 2x3 const calFeatures = [ [icons.calendar, "Widok miesięczny", "Przejrzysta siatka z kolorowymi\noznaczeniami typów wydarzeń"], [icons.check, "Zapis jednym kliknięciem", "Zielony przycisk — zapisz się\nlub zrezygnuj w każdej chwili"], [icons.users, "Lista uczestników", "Zobacz kto idzie — klikalne\nplakietki z linkami do profili"], [icons.bell, "Przypomnienia", "Email dzień przed wydarzeniem,\npowiadomienie w portalu"], [icons.mobile, "Synchronizacja", "Eksport do iPhone (iCal)\ni Google Calendar"], [icons.map, "Lokalizacja", "Adresy prowadzą do Google Maps,\nwszystko klikalne"], ]; for (let i = 0; i < calFeatures.length; i++) { const col = i % 3; const row = Math.floor(i / 3); const x = 0.8 + col * 3.1; const y = 1.4 + row * 1.9; addCard(s, x, y, 2.8, 1.6, C.primary); s.addImage({ data: calFeatures[i][0], x: x + 0.25, y: y + 0.2, w: 0.35, h: 0.35 }); s.addText(calFeatures[i][1], { x: x + 0.25, y: y + 0.65, w: 2.3, h: 0.3, fontSize: 13, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); s.addText(calFeatures[i][2], { x: x + 0.25, y: y + 0.95, w: 2.3, h: 0.5, fontSize: 11, fontFace: FONT_BODY, color: C.textMuted, margin: 0 }); } addSlideNumber(s, 8, TOTAL); // ============================================= // SLIDE 9: PEJ // ============================================= s = pres.addSlide(); s.background = { color: C.darkBg }; s.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.06, fill: { color: C.amber } }); addIconCircle(s, "atom", 0.8, 0.35, 0.55, C.amber); s.addText("PEJ — Elektrownia Jądrowa", { x: 1.5, y: 0.35, w: 7, h: 0.6, fontSize: 30, fontFace: FONT_TITLE, color: C.white, bold: true, margin: 0, valign: "middle" }); s.addText("Dedykowana przestrzeń dla firm zainteresowanych największą inwestycją w regionie", { x: 0.8, y: 1.1, w: 8.4, h: 0.4, fontSize: 14, fontFace: FONT_BODY, color: C.textLight, margin: 0 }); // Two columns // Left - what's there s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.7, w: 4.2, h: 3.2, fill: { color: C.navyBg }, shadow: makeShadow() }); s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.7, w: 0.06, h: 3.2, fill: { color: C.amber } }); s.addText("Co znajdziesz?", { x: 1.1, y: 1.85, w: 3.5, h: 0.35, fontSize: 15, fontFace: FONT_BODY, color: C.amber, bold: true, margin: 0 }); const pejItems = [ "Aktualności o elektrowni jądrowej (66+ artykułów)", "Local Content — lista firm Izby dopasowanych do projektu", "Wskaźnik dopasowania firmy do przetargów PEJ", "Oś czasu kamieni milowych budowy", "Kontakty: PEJ, Bechtel, PAA, Ministerstwo", "Link do platformy zakupowej i rejestracji dostawców", ]; s.addText(pejItems.map((item, i) => ({ text: item, options: { bullet: true, breakLine: i < pejItems.length - 1, fontSize: 12, color: C.white } })), { x: 1.1, y: 2.3, w: 3.6, h: 2.4, fontFace: FONT_BODY, paraSpaceAfter: 4, margin: 0 }); // Right - Local Content s.addShape(pres.shapes.RECTANGLE, { x: 5.4, y: 1.7, w: 4.2, h: 3.2, fill: { color: C.navyBg }, shadow: makeShadow() }); s.addShape(pres.shapes.RECTANGLE, { x: 5.4, y: 1.7, w: 0.06, h: 3.2, fill: { color: C.green } }); s.addText("Local Content — Twoja szansa", { x: 5.7, y: 1.85, w: 3.5, h: 0.35, fontSize: 15, fontFace: FONT_BODY, color: C.green, bold: true, margin: 0 }); const lcItems = [ "System dopasowuje Twoją firmę do projektu PEJ", "Widoczne: typ współpracy, branża, opis", "Filtruj po: kategorii, typie powiązania", "Potencjalny dostawca / partner / beneficjent", "Im pełniejszy profil, tym lepsze dopasowanie", "NordaGPT odpowiada na pytania o PEJ", ]; s.addText(lcItems.map((item, i) => ({ text: item, options: { bullet: true, breakLine: i < lcItems.length - 1, fontSize: 12, color: C.white } })), { x: 5.7, y: 2.3, w: 3.6, h: 2.4, fontFace: FONT_BODY, paraSpaceAfter: 4, margin: 0 }); addSlideNumber(s, 9, TOTAL); // ============================================= // SLIDE 10: KASZUBIA / ZOPK // ============================================= s = pres.addSlide(); s.background = { color: C.lightBg }; s.addText("Kaszubia — Inwestycje regionalne", { x: 0.8, y: 0.4, w: 8.4, h: 0.7, fontSize: 30, fontFace: FONT_TITLE, color: C.textDark, bold: true, margin: 0 }); s.addText("Zielony Okręg Przemysłowy Kaszubia — monitoring szans biznesowych", { x: 0.8, y: 1.0, w: 8.4, h: 0.4, fontSize: 14, fontFace: FONT_BODY, color: C.textMuted, margin: 0 }); // Project cards - use darker shades for white text readability const projects = [ ["Energia", "Elektrownia jądrowa,\noffshore, OZE", "B45309"], ["Infrastruktura", "Drogi, kolej,\nport Gdynia", C.indigo], ["Przemysł", "Kongsberg, CNC,\nmetalurgia", C.red], ["Turystyka", "Żarnowiecki Ring,\nEnergy Velo", "047857"], ]; for (let i = 0; i < projects.length; i++) { const x = 0.8 + i * 2.25; const [name, desc, color] = projects[i]; s.addShape(pres.shapes.RECTANGLE, { x, y: 1.6, w: 2.0, h: 1.2, fill: { color }, shadow: makeShadow() }); s.addText(name, { x: x + 0.15, y: 1.7, w: 1.7, h: 0.35, fontSize: 15, fontFace: FONT_BODY, color: C.white, bold: true, margin: 0 }); s.addText(desc, { x: x + 0.15, y: 2.1, w: 1.7, h: 0.6, fontSize: 11, fontFace: FONT_BODY, color: C.white, margin: 0 }); } // Bottom - what's tracked addCard(s, 0.8, 3.1, 8.4, 2.1, C.primary); s.addText("Co monitorujemy?", { x: 1.1, y: 3.25, w: 4, h: 0.35, fontSize: 15, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); const zopkItems = [ [icons.newspaper, "Aktualności prasowe", "Automatyczne zbieranie i filtrowanie wiadomości przez AI"], [icons.chart, "Kamienie milowe", "Oś czasu z postępem kluczowych projektów w regionie"], [icons.handshake, "Dopasowanie firm", "System wskazuje które firmy Izby pasują do przetargów"], [icons.lightbulb, "Baza wiedzy AI", "NordaGPT odpowiada na pytania o inwestycje kaszubskie"], ]; for (let i = 0; i < zopkItems.length; i++) { const col = i % 2; const row = Math.floor(i / 2); const x = 1.1 + col * 4.1; const y = 3.7 + row * 0.65; s.addImage({ data: zopkItems[i][0], x, y: y + 0.05, w: 0.28, h: 0.28 }); s.addText([ { text: zopkItems[i][1] + " ", options: { bold: true, fontSize: 12 } }, { text: zopkItems[i][2], options: { fontSize: 11, color: C.textMuted } } ], { x: x + 0.4, y, w: 3.5, h: 0.5, fontFace: FONT_BODY, color: C.textDark, margin: 0, valign: "middle" }); } addSlideNumber(s, 10, TOTAL); // ============================================= // SLIDE 11: POWIADOMIENIA // ============================================= s = pres.addSlide(); s.background = { color: C.lightBg }; s.addText("Powiadomienia i aplikacja mobilna", { x: 0.8, y: 0.4, w: 8.4, h: 0.7, fontSize: 30, fontFace: FONT_TITLE, color: C.textDark, bold: true, margin: 0 }); s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.05, w: 1.2, h: 0.04, fill: { color: C.primary } }); // Notification channels const notifCols = [ { title: "W portalu", icon: "bell", color: C.primary, items: [ "Dzwoneczek z licznikiem", "Nowe wiadomości", "Odpowiedzi na forum", "Zapis na wydarzenie", "Badge \"Nowe\" na treściach", ] }, { title: "Email", icon: "envelope", color: C.green, items: [ "Nowa wiadomość prywatna", "Odpowiedź na śledzony temat", "Przypomnienie o wydarzeniu", "Wniosek członkowski", "Link do rezygnacji", ] }, { title: "Telefon", icon: "mobile", color: C.purple, items: [ "Zainstaluj jak aplikację", "Powiadomienia na iPhone i Android", "Pełna funkcjonalność", "Działa bez internetu", "Ikona na pulpicie telefonu", ] }, ]; for (let c = 0; c < notifCols.length; c++) { const x = 0.8 + c * 3.1; const col = notifCols[c]; addCard(s, x, 1.35, 2.8, 3.5, col.color); addIconCircle(s, col.icon, x + 0.25, 1.5, 0.4, col.color); s.addText(col.title, { x: x + 0.8, y: 1.5, w: 1.7, h: 0.4, fontSize: 15, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0, valign: "middle" }); s.addText(col.items.map((item, i) => ({ text: item, options: { bullet: true, breakLine: i < col.items.length - 1, fontSize: 11, color: C.textDark } })), { x: x + 0.25, y: 2.1, w: 2.3, h: 2.5, fontFace: FONT_BODY, paraSpaceAfter: 5, margin: 0 }); } addSlideNumber(s, 11, TOTAL); // ============================================= // SLIDE 12: PRYWATNOŚĆ I BEZPIECZEŃSTWO // ============================================= s = pres.addSlide(); s.background = { color: C.darkBg }; s.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.06, fill: { color: C.green } }); addIconCircle(s, "shield", 0.8, 0.35, 0.55, C.green); s.addText("Prywatność i bezpieczeństwo", { x: 1.5, y: 0.35, w: 7, h: 0.6, fontSize: 30, fontFace: FONT_TITLE, color: C.white, bold: true, margin: 0, valign: "middle" }); // Privacy features const privacyItems = [ ["Twoje dane pod kontrolą", "Decydujesz co jest widoczne — telefon, email,\nsekcje profilu. Ukrywasz jednym kliknięciem.", C.green], ["6 poziomów dostępu", "Gość → Użytkownik → Członek → Pracownik →\nKierownik → Administrator. Każdy widzi tylko to, co powinien.", C.primary], ["Tylko dla członków Izby", "NordaGPT, wiadomości, B2B, dane kontaktowe\n— dostępne wyłącznie po zalogowaniu.", C.amber], ["Ochrona danych osobowych", "PESEL, numery kart, IBAN — automatycznie\nukrywane. Blokowanie niechcianych kontaktów.", C.purple], ]; for (let i = 0; i < privacyItems.length; i++) { const col = i % 2; const row = Math.floor(i / 2); const x = 0.8 + col * 4.5; const y = 1.3 + row * 1.8; s.addShape(pres.shapes.RECTANGLE, { x, y, w: 4.0, h: 1.5, fill: { color: C.navyBg }, shadow: makeShadow() }); s.addShape(pres.shapes.RECTANGLE, { x, y, w: 0.06, h: 1.5, fill: { color: privacyItems[i][2] } }); s.addText(privacyItems[i][0], { x: x + 0.25, y: y + 0.15, w: 3.5, h: 0.35, fontSize: 15, fontFace: FONT_BODY, color: C.white, bold: true, margin: 0 }); s.addText(privacyItems[i][1], { x: x + 0.25, y: y + 0.55, w: 3.5, h: 0.8, fontSize: 12, fontFace: FONT_BODY, color: C.textLight, margin: 0 }); } addSlideNumber(s, 12, TOTAL); // ============================================= // SLIDE 13: KOSZTY I PRZYSZŁOŚĆ // ============================================= s = pres.addSlide(); s.background = { color: C.lightBg }; s.addText("Koszty, ograniczenia i przyszłość", { x: 0.8, y: 0.4, w: 8.4, h: 0.7, fontSize: 30, fontFace: FONT_TITLE, color: C.textDark, bold: true, margin: 0 }); s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.05, w: 1.2, h: 0.04, fill: { color: C.primary } }); // Left - what's included addCard(s, 0.8, 1.35, 4.2, 2.0, C.green); s.addText("Co jest w cenie członkostwa?", { x: 1.1, y: 1.5, w: 3.5, h: 0.35, fontSize: 14, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); const freeItems = [ "Profil firmy, katalog, wyszukiwarka", "Forum, ogłoszenia B2B, aktualności", "Kalendarz wydarzeń z zapisami", "Wiadomości prywatne i grupowe", "Powiadomienia email i push (PWA)", ]; s.addText(freeItems.map((item, i) => ({ text: item, options: { bullet: true, breakLine: i < freeItems.length - 1, fontSize: 12, color: C.textDark } })), { x: 1.1, y: 1.9, w: 3.6, h: 1.3, fontFace: FONT_BODY, paraSpaceAfter: 3, margin: 0 }); // Right - AI limits addCard(s, 5.4, 1.35, 4.2, 2.0, C.amber); s.addText("Ograniczenia AI (NordaGPT)", { x: 5.7, y: 1.5, w: 3.5, h: 0.35, fontSize: 14, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); const aiLimits = [ "Silnik AI: Google Gemini (darmowy tier)", "Limit dzienny na użytkownika", "Limit zbiorczy na cały portal", "Odpowiedzi w 2 trybach: szybki / zaawansowany", "W przyszłości: płatne plany = więcej AI", ]; s.addText(aiLimits.map((item, i) => ({ text: item, options: { bullet: true, breakLine: i < aiLimits.length - 1, fontSize: 12, color: C.textDark } })), { x: 5.7, y: 1.9, w: 3.6, h: 1.3, fontFace: FONT_BODY, paraSpaceAfter: 3, margin: 0 }); // Bottom - future plans addCard(s, 0.8, 3.6, 8.4, 1.7, C.indigo); s.addText("Co planujemy?", { x: 1.1, y: 3.75, w: 4, h: 0.35, fontSize: 15, fontFace: FONT_BODY, color: C.textDark, bold: true, margin: 0 }); const futureItems = [ [icons.robot, "NordaGPT 2.0", "Zaawansowany doradca — analiza rynku, rekomendacje, raporty"], [icons.handshake, "Marketplace usług", "Giełda kompetencji — członkowie oferują usługi sobie nawzajem"], [icons.chart, "Raporty branżowe", "Automatyczna analiza trendów i szans rynkowych przez AI"], [icons.globe, "Integracje", "WhatsApp Business, SMS — komunikacja bez granic"], ]; for (let i = 0; i < futureItems.length; i++) { const col = i % 2; const row = Math.floor(i / 2); const x = 1.1 + col * 4.1; const y = 4.2 + row * 0.5; s.addImage({ data: futureItems[i][0], x, y: y + 0.05, w: 0.25, h: 0.25 }); s.addText([ { text: futureItems[i][1] + " ", options: { bold: true, fontSize: 12 } }, { text: futureItems[i][2], options: { fontSize: 11, color: C.textMuted } } ], { x: x + 0.35, y, w: 3.5, h: 0.4, fontFace: FONT_BODY, color: C.textDark, margin: 0, valign: "middle" }); } addSlideNumber(s, 13, TOTAL); // ============================================= // SLIDE 14: PYTANIA // ============================================= s = pres.addSlide(); s.background = { color: C.darkBg }; s.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.06, fill: { color: C.primary } }); s.addText("Pytania?", { x: 0.8, y: 1.0, w: 8.4, h: 1.0, fontSize: 48, fontFace: FONT_TITLE, color: C.white, bold: true, align: "center", margin: 0 }); s.addShape(pres.shapes.RECTANGLE, { x: 4.2, y: 2.2, w: 1.6, h: 0.04, fill: { color: C.primary } }); s.addText("Zaloguj się i sprawdź", { x: 0.8, y: 2.6, w: 8.4, h: 0.6, fontSize: 22, fontFace: FONT_BODY, color: C.primaryLight, align: "center", margin: 0 }); // Norda compass next to URL s.addImage({ path: nordaLogoPath, x: 3.4, y: 3.05, w: 0.7, h: 0.7 }); s.addText("nordabiznes.pl", { x: 4.2, y: 3.1, w: 4, h: 0.6, fontSize: 28, fontFace: FONT_TITLE, color: C.white, bold: true, margin: 0, valign: "middle" }); // Bottom contact bar s.addShape(pres.shapes.RECTANGLE, { x: 1.5, y: 4.1, w: 7, h: 1.0, fill: { color: C.navyBg } }); s.addText([ { text: "Maciej Pienczyn", options: { bold: true, fontSize: 13, color: C.white } }, { text: " | maciej.pienczyn@inpi.pl | Izba Gospodarcza NORDA", options: { fontSize: 12, color: C.textLight } } ], { x: 1.5, y: 4.1, w: 7, h: 0.55, fontFace: FONT_BODY, align: "center", valign: "middle", margin: 0 }); // INPI logo + "Stworzone przez" — bottom of contact bar s.addImage({ path: inpiLogoPath, x: 3.65, y: 4.62, w: 0.55, h: 0.31 }); s.addText("Stworzone przez", { x: 2.2, y: 4.62, w: 1.4, h: 0.31, fontSize: 9, fontFace: FONT_BODY, color: C.textMuted, align: "right", valign: "middle", margin: 0 }); s.addText("sp. z o.o.", { x: 4.25, y: 4.62, w: 1, h: 0.31, fontSize: 9, fontFace: FONT_BODY, color: C.textMuted, valign: "middle", margin: 0 }); // Save const outputPath = "/Users/maciejpi/Desktop/NordaBiznes_Prezentacja_2026-04-09.pptx"; await pres.writeFile({ fileName: outputPath }); console.log("Presentation saved to:", outputPath); } createPresentation().catch(err => { console.error("Error:", err); process.exit(1); });