from pathlib import Path import html, textwrap, os, re, json, subprocess, sys, time from docx import Document docx_path = Path("/mnt/data/Новый документ.docx") html_path = Path("/mnt/data/landing_snova_vmeste_final.html") png_path = Path("/mnt/data/landing_snova_vmeste_final_preview.png") # Extract source text for traceability doc = Document(str(docx_path)) paras = [p.text.strip() for p in doc.paragraphs if p.text.strip()] # Final content structure based on the edited doc. # Light technical cleanup: double dot in one sentence, normalized dash in 1,5–2. content = { "title": "Снова вместе", "subtitle": "Как говорить так, чтобы сближаться, а не ругаться", "descriptor": "Практический курс для женщин, которые хотят вернуть диалог и близость в отношения", "cta": "Записаться на курс", "intro": { "kicker": "Знакомо?", "title": "Вы живёте под одной крышей, но как будто на разных планетах.", "p1": "Говоришь — не слышит. Просишь — забывает. Объясняешь — не понимает. И в какой-то момент проще замолчать, чем снова чувствовать эту стену.", "p2": "Вы не ссоритесь. Вы просто... перестали разговаривать. По-настоящему.", "quote": "«Живём как соседи» — если эта фраза про вас, вы не одна." }, "problem": { "kicker": "Что происходит на самом деле", "title": "Любовь никуда не делась. Поломался диалог.", "p1": "Вы копите недовольство, потому что не знаете, как сказать без претензий. Терпите, чтобы «не пилить». А потом — или взрыв, или молчанка. И он смотрит на вас как на истеричку, хотя вы просто устали терпеть.", "p2": "Он говорит: «У меня всё нормально». А вы думаете: «Как у тебя может быть всё нормально, когда наши отношения рушатся?!»", "p3": "И кажется, что выхода два: терпеть дальше или разводиться.", "accent": "Но есть третий путь." }, "audience": [ "Вы в браке, и вам есть что сохранять — но вы не знаете, как вернуть близость", "Вы устали от ощущения, что бьетесь в стену — говорите, а он не слышит", "Вы хотите научиться доносить свои мысли без ссор, претензий и обид", "Вы чувствуете, что потеряли себя в ролях «мама», «хозяйка», «жена» — и хотите вернуться к себе настоящей", "Вы после развода и хотите построить новые отношения без старых ошибок", ], "not_for": "Этот курс НЕ подойдёт, если в отношениях есть физическое насилие или зависимости. В этих случаях нужны другие специалисты — и мы это честно говорим.", "program": { "title": "Что вы получите за 8 занятий", "part1": { "title": "Часть 1. Вернуть себе силы", "meta": "занятия 1–4", "lead": "Прежде чем разговаривать с ним — нужно разобраться с собой.", "text": "Когда вы истощены, любая мелочь становится поводом для ссоры. Вы не можете говорить спокойно, потому что внутри всё кипит.", "sub": "На первой части курса вы:", "items": [ ("Восстановите внутренний ресурс", "чтобы были силы на отношения, а не только на выживание"), ("Поймёте свои потребности и ценности", "что вам действительно нужно от отношений"), ("Научитесь не терять себя", "даже когда вокруг хаос"), ("Освоите практики саморегуляции", "что делать, когда эмоции накрывают"), ] }, "part2": { "title": "Часть 2. Говорить так, чтобы Вас слышали", "meta": "занятия 5–8", "lead": "Теперь, когда вы в ресурсе и понимаете себя — можно начать говорить о своих потребностях.", "items": [ ("Поймёте мужскую психологию", "чего он на самом деле хочет (спойлер: не то, что вы думаете)"), ("Освоите ненасильственную коммуникацию", "как говорить о своих чувствах без эмоций и скандалов"), ("Научитесь слышать и быть услышанной", "техники, которые работают"), ("Попрактикуете «диалоги в любви»", "разговоры, которые сближают, а не отдаляют"), ] } }, "results": [ ("Понимаете себя", "свои чувства, потребности, границы."), ("Не боитесь говорить", "о важном, о сложном, о том, что накопилось. Без страха «опять будет ссора»"), ("Умеете доносить мысли", "так, чтобы он слышал, а не защищался. Без претензий и обвинений"), ("Чувствуете опору внутри", "не зависите от его настроения и реакций"), ("Принимаете решения из спокойствия", "а не из страха, обиды или отчаяния"), ], "difference": { "title": "Чем этот курс отличается", "p1": "Мы не учим «служить мужчине» и «надеть длинную юбку».", "p2": "Здесь нет советов «будь мягче», «терпи» и «он же добытчик».", "p3": "Мы за равные партнёрские отношения. Где оба — взрослые люди. Где женщина может работать, развиваться, иметь свои интересы. И при этом — быть в близких, тёплых отношениях с мужем.", "p4": "Вы учитесь общаться не РАДИ него — а ДЛЯ себя. Потому что это ваши отношения. И вам в них жить." }, "experts": [ ("Олег Гадецкий", "Основатель Института ЦОП, психолог с 25-летним стажем, автор метода ценностно-ориентированной психологии. Провёл тысячи консультаций по семейным отношениям. На курсе ведёт занятие по восстановлению внутреннего ресурса и духовной опоре в отношениях."), ("Девран Садык", "Практикующий психолог, специалист по коммуникации в отношениях. Ведёт занятия о потребностях и ценностях, мужской психологии и ненасильственном общении. Научит, как экологично доносить свои чувства и быть услышанной."), ("Зоя Краславская", "Психолог-практик, специалист по семейным отношениям. Ведёт занятия о том, что мешает и помогает строить отношения, а также практики саморегуляции в конфликтах. Поможет увидеть в партнёре личность и справляться с эмоциями в сложные моменты."), ("Евгений Зиборов", "Практикующий психолог. Преподаватель Института ЦОП. Ведёт занятие по навыкам слушания — научит техникам активного слушания и переформулирования, чтобы вы по-настоящему слышали друг друга."), ("Александр Борисов", "Психолог, специалист по глубинной коммуникации. Ведёт финальное занятие курса — практики «диалогов в любви», разговоры, которые делают отношения по-настоящему близкими."), ], "experts_result": "5 экспертов — 8 занятий — один результат: вы снова слышите друг друга.", "experts_note": "Мы провели тысячи консультаций и знаем: проблема не в том, что «он такой». Проблема в том, что вас не научили разговаривать так, чтобы вас слышали.", "format": [ ("8 онлайн-занятий", "теория + практика на каждом занятии", "calendar"), ("Длительность", "1,5–2 часа каждое занятие", "clock"), ("Домашние задания", "чтобы применить новые навыки в своих отношениях", "note"), ("Чат поддержки", "вы не одна на этом пути", "chat"), ("Записи занятий", "если не успели онлайн", "video"), ], "price": { "amount": "7 000 ₽", "meta": "За весь курс из 8 занятий", "text": "Это меньше, чем одна сессия у семейного психолога. А здесь — системная работа над собой и своими навыками общения." }, "faq": [ ("А если муж не хочет ничего менять?", "Курс для вас, не для него. Вы научитесь говорить по-другому — и это изменит динамику ваших отношений. Один человек может изменить танец двоих."), ("У меня нет времени на курс", "1,5–2 часа в неделю. Есть записи. И подумайте: сколько времени вы тратите на переживания, ссоры и молчанки?"), ("А если нам уже ничего не поможет?", "Если любовь осталась — значит, есть что возвращать. Этот курс для тех, кто готов попробовать по-другому, прежде чем принимать окончательные решения."), ("Мне нужна индивидуальная работа", "Курс даёт базу и практические навыки. Если нужна глубокая проработка — мы подскажем, как продолжить после курса."), ], "final": { "title": "Любовь никуда не делась. Делся диалог.", "p1": "Вы можете вернуть его.", "p2": "Не «терпеть ради детей». Не «закрывать глаза». Не ждать, что он сам догадается.", "p3": "А научиться говорить так, чтобы вас слышали. И слышать в ответ.", "p4": "Снова вместе — это возможно.", "start": "Старт ближайшего потока — уточняйте у менеджера", "questions": "Остались вопросы?", "questions_text": "Напишите нам — ответим и поможем понять, подходит ли вам этот курс." } } def esc(s): return html.escape(s, quote=True) # SVG illustration components. All are inline so the HTML is self-contained. def svg_couple_talk(): return """ """ def svg_distance(): return """ """ def svg_woman_journal(): return """ """ def svg_partnership_walk(): return """ """ def svg_laptop(): return """ """ def svg_candle(): return """ """ # Small icon generator for cards def icon_svg(kind): icons = { "heart": '', "wall": '', "speech": '', "person": '', "restart": '', "warning": '', "calendar": '', "clock": '', "note": '', "chat": '', "video": '', "target": '' } return f'' aud_icons = ["heart","wall","speech","person","restart","warning"] result_icons = ["target","speech","chat","heart","clock"] audience_html = "" for i, item in enumerate(content["audience"]): audience_html += f"""
{icon_svg(aud_icons[i])}

{esc(item)}

""" audience_html += f"""
{icon_svg('warning')}

Курс НЕ подойдёт
{esc(content['not_for'].replace('Этот курс НЕ подойдёт, ', ''))}

""" part1_items = "\n".join([f"
  • {esc(a)} — {esc(b)}
  • " for a,b in content["program"]["part1"]["items"]]) part2_items = "\n".join([f"
  • {esc(a)} — {esc(b)}
  • " for a,b in content["program"]["part2"]["items"]]) results_html = "\n".join([ f"""
    {icon_svg(result_icons[i])}

    {esc(t)}

    {esc(d)}

    """ for i,(t,d) in enumerate(content["results"]) ]) experts_html = "\n".join([ f"""
    {''.join([w[0] for w in name.split()[:2]])}

    {esc(name)}

    {esc(desc)}

    """ for name, desc in content["experts"] ]) format_html = "\n".join([ f"""
    {icon_svg(kind)}

    {esc(title)}

    {esc(desc)}

    """ for title, desc, kind in content["format"] ]) faq_html = "\n".join([ f"""
    {esc(q)}+

    {esc(a)}

    """ for i,(q,a) in enumerate(content["faq"]) ]) html_code = f""" {esc(content['title'])} — курс Института ЦОП
    Практический курс для женщин

    {esc(content['title'])}

    {esc(content['subtitle'])}

    {esc(content['descriptor'])}

    {esc(content['cta'])} →
    7 000 ₽ за весь курс
    {svg_couple_talk()}
    Вернуть диалог Не через давление и претензии, а через спокойный разговор, в котором Вас слышат.
    {svg_distance()}
    {esc(content['intro']['kicker'])}

    {esc(content['intro']['title'])}

    {esc(content['intro']['p1'])}

    {esc(content['intro']['p2'])}

    «Живём как соседи»

    {esc(content['intro']['quote'].replace('«Живём как соседи» — ', ''))}

    {esc(content['problem']['kicker'])}

    {esc(content['problem']['title'])}

    {esc(content['problem']['p1'])}

    {esc(content['problem']['p2'])}

    {esc(content['problem']['p3'])}

    {esc(content['problem']['accent'])}
    {svg_candle()}
    Для кого этот курс

    Для женщин, которым есть что сохранять — и которые хотят попробовать по-другому

    {audience_html}
    8 занятий

    {esc(content['program']['title'])}

    {esc(content['program']['part1']['meta'])}

    {esc(content['program']['part1']['title'])}

    {esc(content['program']['part1']['lead'])}

    {esc(content['program']['part1']['text'])}

    {svg_woman_journal()}

    {esc(content['program']['part1']['sub'])}

      {part1_items}
    {esc(content['program']['part2']['meta'])}

    {esc(content['program']['part2']['title'])}

    {esc(content['program']['part2']['lead'])}

    {svg_couple_talk()}
      {part2_items}
    Результат курса

    После 8 занятий вы:

    {results_html}
    {svg_laptop()}
    {esc(content['difference']['title'])}

    Не про терпение любой ценой. Про взрослый диалог.

    {esc(content['difference']['p1'])}

    {esc(content['difference']['p2'])}

    {esc(content['difference']['p3'])}

    {esc(content['difference']['p4'])}

    {svg_partnership_walk()}
    Кто ведёт курс

    Курс создан экспертами Института ценностно-ориентированной психологии

    {experts_html}

    {esc(content['experts_result'])}

    {esc(content['experts_note'])}

    Формат курса

    Онлайн-занятия с теорией, практикой и поддержкой

    {format_html}
    {svg_laptop()}
    Стоимость
    {esc(content['price']['amount'])}

    {esc(content['price']['meta'])}

    {esc(content['price']['text'])}

    {esc(content['cta'])} →
    Частые вопросы

    FAQ

    {svg_candle()}
    {faq_html}

    {esc(content['final']['title'])}

    {esc(content['final']['p1'])}

    {esc(content['final']['p2'])}

    {esc(content['final']['p3'])}

    {esc(content['final']['p4'])}

    {esc(content['cta'])} →

    {esc(content['final']['start'])}

    {svg_couple_talk()}

    {esc(content['final']['questions'])}
    {esc(content['final']['questions_text'])}

    """ html_path.write_text(html_code, encoding="utf-8") # Take full page screenshot using Playwright try: from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page(viewport={"width":1440, "height":1100}, device_scale_factor=1) page.goto(html_path.as_uri(), wait_until="networkidle") page.screenshot(path=str(png_path), full_page=True) browser.close() screenshot_status = f"PNG-превью создано: {png_path}" except Exception as e: screenshot_status = f"HTML создан, но PNG-превью не удалось снять: {e}" print(f"Готов HTML: {html_path}") print(screenshot_status) print(f"Исходных непустых абзацев в docx: {len(paras)}") print(f"Размер HTML: {html_path.stat().st_size/1024:.1f} KB") if png_path.exists(): print(f"Размер PNG: {png_path.stat().st_size/1024:.1f} KB")