РУБАНОВА ЕКАТЕРИНА

Python from reportlab.pdfgen import canvas from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib.colors import HexColor import textwrap # Register fonts pdfmetrics.registerFont(TTFont('Poppins-Bold', '/usr/share/fonts/truetype/google-fonts/Poppins-Bold.ttf')) pdfmetrics.registerFont(TTFont('Poppins-Light', '/usr/share/fonts/truetype/google-fonts/Poppins-Light.ttf')) pdfmetrics.registerFont(TTFont('Poppins-Regular', '/usr/share/fonts/truetype/google-fonts/Poppins-Regular.ttf')) # Brand colors from brief GRAPHITE = HexColor('#211d1c') # dark background GOLD = HexColor('#FFDAA9') # primary accent CREAM = HexColor('#FDECD4') # secondary warm WHITE = HexColor('#FFFFFF') WARM_MID = HexColor('#C4956A') # mid-tone for accents/lines # Slide dimensions: 1080x1920 (9:16) # BUT readable content area is 1080x1350 (4:5) centered vertically # We'll create 1080x1920 px → convert pt: 1pt = 1px at 72dpi, but we work at 72dpi # So: 1080 pt wide x 1920 pt tall W = 1080 H = 1920 SAFE_TOP = (H - 1350) // 2 # 285 pt from top — start of readable area SAFE_BOT = SAFE_TOP + 1350 # 1635 pt PAD_X = 80 def draw_background(c, slide_idx): """Draw dark graphite background with subtle texture.""" c.setFillColor(GRAPHITE) c.rect(0, 0, W, H, fill=1, stroke=0) # Subtle grain dots for texture import random random.seed(slide_idx * 7) c.setFillColor(HexColor('#2a2623')) for _ in range(300): x = random.randint(0, W) y = random.randint(0, H) r = random.uniform(0.5, 2) c.circle(x, y, r, fill=1, stroke=0) def draw_brand_frame(c): """Draw the safe-zone frame lines — gold horizontal bars.""" # Top gold line c.setStrokeColor(GOLD) c.setLineWidth(1.5) c.line(PAD_X, H - SAFE_TOP, W - PAD_X, H - SAFE_TOP) # Bottom gold line c.line(PAD_X, H - SAFE_BOT, W - PAD_X, H - SAFE_BOT) def draw_slide_number(c, num, total=8): txt = f"{num:02d} / {total:02d}" c.setFont('Poppins-Light', 22) c.setFillColor(WARM_MID) c.drawString(PAD_X, H - SAFE_TOP + 20, txt) def draw_logo(c): """Small logo text bottom right inside safe zone.""" c.setFont('Poppins-Light', 20) c.setFillColor(WARM_MID) c.drawRightString(W - PAD_X, H - SAFE_BOT + 20, '@ekaterina_rubanova_design') def wrap_text(c, text, font, size, max_width): """Returns list of lines.""" c.setFont(font, size) words = text.split() lines = [] current = '' for w in words: test = (current + ' ' + w).strip() if c.stringWidth(test, font, size) <= max_width: current = test else: if current: lines.append(current) current = w if current: lines.append(current) return lines def draw_multiline(c, text, font, size, color, x, y, max_width, leading=None): """Draw wrapped text, return y after last line.""" if leading is None: leading = size * 1.35 lines = wrap_text(c, text, font, size, max_width) c.setFont(font, size) c.setFillColor(color) for line in lines: c.drawString(x, y, line) y -= leading return y def draw_bullet_list(c, items, x, y, font, size, color, dot_color, max_width, leading=None): """Draw bullet list, return y after last item.""" if leading is None: leading = size * 1.5 c.setFont(font, size) for item in items: # bullet dot c.setFillColor(dot_color) c.circle(x + 10, y + size * 0.3, 4, fill=1, stroke=0) c.setFillColor(color) # text, indented y = draw_multiline(c, item, font, size, color, x + 28, y, max_width - 28, leading) y -= 6 return y def draw_error_number(c, num_str, x, y): """Large decorative error number background.""" c.setFont('Poppins-Bold', 180) c.setFillColor(HexColor('#2c2826')) c.drawString(x, y, num_str) def gold_tag(c, text, x, y): """Draw a small gold tag/badge.""" tw = c.stringWidth(text, 'Poppins-Bold', 22) pad = 14 rh = 36 c.setFillColor(GOLD) c.roundRect(x, y, tw + pad*2, rh, 6, fill=1, stroke=0) c.setFont('Poppins-Bold', 22) c.setFillColor(GRAPHITE) c.drawString(x + pad, y + 9, text) return tw + pad*2 # ─── SLIDE DEFINITIONS ──────────────────────────────────────────────────────── def slide1(c): """HOOK slide.""" draw_background(c, 1) draw_brand_frame(c) draw_slide_number(c, 1) draw_logo(c) # Large decorative number draw_error_number(c, '5', W - 200, H - SAFE_TOP - 460) # Gold accent line cy = H - SAFE_TOP - 80 c.setStrokeColor(GOLD) c.setLineWidth(3) c.line(PAD_X, cy, PAD_X + 80, cy) # Pre-title tag gold_tag(c, 'ДО РЕМОНТА', PAD_X, H - SAFE_TOP - 130) cy = H - SAFE_TOP - 230 # Main headline draw_multiline(c, '5 вещей, которые забывают', 'Poppins-Bold', 72, WHITE, PAD_X, cy, W - PAD_X*2 - 60, leading=82) cy -= 82*2 draw_multiline(c, 'ПЕРЕД ремонтом', 'Poppins-Bold', 72, GOLD, PAD_X, cy, W - PAD_X*2, leading=82) cy -= 110 # Subline draw_multiline(c, 'А потом живут с этим годами.', 'Poppins-Light', 38, CREAM, PAD_X, cy, W - PAD_X*2, leading=52) def slide2(c): """Ошибка №1 — Свет.""" draw_background(c, 2) draw_brand_frame(c) draw_slide_number(c, 2) draw_logo(c) draw_error_number(c, '1', W - 220, H - SAFE_TOP - 440) cy = H - SAFE_TOP - 100 gold_tag(c, 'ОШИБКА №1', PAD_X, cy) cy -= 90 draw_multiline(c, 'Свет продумали', 'Poppins-Bold', 68, WHITE, PAD_X, cy, W - PAD_X*2, leading=80) cy -= 80 draw_multiline(c, 'после ремонта', 'Poppins-Bold', 68, GOLD, PAD_X, cy, W - PAD_X*2, leading=80) cy -= 100 # Gold divider c.setStrokeColor(GOLD) c.setLineWidth(1) c.line(PAD_X, cy, W - PAD_X, cy) cy -= 50 c.setFont('Poppins-Light', 32) c.setFillColor(CREAM) c.drawString(PAD_X, cy, 'В итоге:') cy -= 52 bullets = [ 'Тёмные углы в комнате', 'Выключатели в неудобных местах', 'Мало розеток', 'Одна люстра на всё помещение', ] cy = draw_bullet_list(c, bullets, PAD_X, cy, 'Poppins-Light', 34, CREAM, GOLD, W - PAD_X*2, leading=52) cy -= 40 # Tip box c.setFillColor(HexColor('#2e2926')) c.roundRect(PAD_X, cy - 80, W - PAD_X*2, 80, 10, fill=1, stroke=0) c.setStrokeColor(GOLD) c.setLineWidth(1.5) c.roundRect(PAD_X, cy - 80, W - PAD_X*2, 80, 10, fill=0, stroke=1) c.setFont('Poppins-Regular', 30) c.setFillColor(GOLD) c.drawString(PAD_X + 24, cy - 50, 'Освещение проектируют до начала электрики') def slide3(c): """Ошибка №2 — Сантехника.""" draw_background(c, 3) draw_brand_frame(c) draw_slide_number(c, 3) draw_logo(c) draw_error_number(c, '2', W - 220, H - SAFE_TOP - 440) cy = H - SAFE_TOP - 100 gold_tag(c, 'ОШИБКА №2', PAD_X, cy) cy -= 90 draw_multiline(c, 'Сначала плитка —', 'Poppins-Bold', 66, WHITE, PAD_X, cy, W - PAD_X*2, leading=78) cy -= 78 draw_multiline(c, 'потом сантехника', 'Poppins-Bold', 66, GOLD, PAD_X, cy, W - PAD_X*2, leading=78) cy -= 98 c.setStrokeColor(GOLD) c.setLineWidth(1) c.line(PAD_X, cy, W - PAD_X, cy) cy -= 50 c.setFont('Poppins-Light', 32) c.setFillColor(CREAM) c.drawString(PAD_X, cy, 'В результате:') cy -= 52 bullets = [ 'Неудобный душ', 'Проблемы с инсталляцией унитаза', 'Ошибки с высотой смесителей', 'Плохая вентиляция', ] cy = draw_bullet_list(c, bullets, PAD_X, cy, 'Poppins-Light', 34, CREAM, GOLD, W - PAD_X*2, leading=52) cy -= 40 c.setFillColor(HexColor('#2e2926')) c.roundRect(PAD_X, cy - 80, W - PAD_X*2, 80, 10, fill=1, stroke=0) c.setStrokeColor(GOLD) c.setLineWidth(1.5) c.roundRect(PAD_X, cy - 80, W - PAD_X*2, 80, 10, fill=0, stroke=1) c.setFont('Poppins-Regular', 30) c.setFillColor(GOLD) c.drawString(PAD_X + 24, cy - 50, 'Мокрые зоны продумываются заранее') def slide4(c): """Ошибка №3 — Материалы.""" draw_background(c, 4) draw_brand_frame(c) draw_slide_number(c, 4) draw_logo(c) draw_error_number(c, '3', W - 220, H - SAFE_TOP - 440) cy = H - SAFE_TOP - 100 gold_tag(c, 'ОШИБКА №3', PAD_X, cy) cy -= 90 draw_multiline(c, 'Выбирали материалы', 'Poppins-Bold', 64, WHITE, PAD_X, cy, W - PAD_X*2, leading=76) cy -= 76 draw_multiline(c, 'только по картинке', 'Poppins-Bold', 64, GOLD, PAD_X, cy, W - PAD_X*2, leading=76) cy -= 96 c.setStrokeColor(GOLD) c.setLineWidth(1) c.line(PAD_X, cy, W - PAD_X, cy) cy -= 50 c.setFont('Poppins-Light', 32) c.setFillColor(CREAM) c.drawString(PAD_X, cy, 'Без проверки:') cy -= 52 bullets = [ 'Как ведёт себя при вашем свете', 'Практичность в уходе', 'Текстура в реальности', 'Ощущение в пространстве', ] cy = draw_bullet_list(c, bullets, PAD_X, cy, 'Poppins-Light', 34, CREAM, GOLD, W - PAD_X*2, leading=52) cy -= 40 c.setFillColor(HexColor('#2e2926')) c.roundRect(PAD_X, cy - 100, W - PAD_X*2, 100, 10, fill=1, stroke=0) c.setStrokeColor(GOLD) c.setLineWidth(1.5) c.roundRect(PAD_X, cy - 100, W - PAD_X*2, 100, 10, fill=0, stroke=1) draw_multiline(c, 'Красивое в магазине может раздражать дома каждый день', 'Poppins-Regular', 30, GOLD, PAD_X + 24, cy - 32, W - PAD_X*2 - 50, leading=40) def slide5(c): """Ошибка №4 — Хранение.""" draw_background(c, 5) draw_brand_frame(c) draw_slide_number(c, 5) draw_logo(c) draw_error_number(c, '4', W - 220, H - SAFE_TOP - 440) cy = H - SAFE_TOP - 100 gold_tag(c, 'ОШИБКА №4', PAD_X, cy) cy -= 90 draw_multiline(c, 'Хранение не', 'Poppins-Bold', 68, WHITE, PAD_X, cy, W - PAD_X*2, leading=80) cy -= 80 draw_multiline(c, 'продумали заранее', 'Poppins-Bold', 68, GOLD, PAD_X, cy, W - PAD_X*2, leading=80) cy -= 100 c.setStrokeColor(GOLD) c.setLineWidth(1) c.line(PAD_X, cy, W - PAD_X, cy) cy -= 50 c.setFont('Poppins-Light', 32) c.setFillColor(CREAM) c.drawString(PAD_X, cy, 'Потом появляются:') cy -= 52 bullets = [ 'Визуальный шум', 'Коробки и вещи на виду', 'Ощущение вечного беспорядка', ] cy = draw_bullet_list(c, bullets, PAD_X, cy, 'Poppins-Light', 34, CREAM, GOLD, W - PAD_X*2, leading=52) cy -= 55 c.setFillColor(HexColor('#2e2926')) c.roundRect(PAD_X, cy - 100, W - PAD_X*2, 100, 10, fill=1, stroke=0) c.setStrokeColor(GOLD) c.setLineWidth(1.5) c.roundRect(PAD_X, cy - 100, W - PAD_X*2, 100, 10, fill=0, stroke=1) draw_multiline(c, 'Красивый интерьер — это не только стиль. Это сценарий жизни', 'Poppins-Regular', 30, GOLD, PAD_X + 24, cy - 32, W - PAD_X*2 - 50, leading=40) def slide6(c): """Ошибка №5 — Подготовка.""" draw_background(c, 6) draw_brand_frame(c) draw_slide_number(c, 6) draw_logo(c) draw_error_number(c, '5', W - 220, H - SAFE_TOP - 440) cy = H - SAFE_TOP - 100 gold_tag(c, 'ОШИБКА №5', PAD_X, cy) cy -= 90 draw_multiline(c, 'Начали ремонт без', 'Poppins-Bold', 65, WHITE, PAD_X, cy, W - PAD_X*2, leading=78) cy -= 78 draw_multiline(c, 'полной подготовки', 'Poppins-Bold', 65, GOLD, PAD_X, cy, W - PAD_X*2, leading=78) cy -= 98 c.setStrokeColor(GOLD) c.setLineWidth(1) c.line(PAD_X, cy, W - PAD_X, cy) cy -= 50 c.setFont('Poppins-Light', 32) c.setFillColor(CREAM) c.drawString(PAD_X, cy, 'Без:') cy -= 52 bullets = [ 'Чертежей и плана', 'Сметы', 'Договора с подрядчиком', 'Финальной закупки материалов', ] cy = draw_bullet_list(c, bullets, PAD_X, cy, 'Poppins-Light', 34, CREAM, GOLD, W - PAD_X*2, leading=52) cy -= 40 c.setFillColor(HexColor('#2e2926')) c.roundRect(PAD_X, cy - 80, W - PAD_X*2, 80, 10, fill=1, stroke=0) c.setStrokeColor(GOLD) c.setLineWidth(1.5) c.roundRect(PAD_X, cy - 80, W - PAD_X*2, 80, 10, fill=0, stroke=1) c.setFont('Poppins-Regular', 30) c.setFillColor(GOLD) c.drawString(PAD_X + 24, cy - 50, 'Здесь начинаются самые дорогие ошибки') def slide7(c): """Emotional / manifesto slide.""" draw_background(c, 7) draw_brand_frame(c) draw_slide_number(c, 7) draw_logo(c) # Big decorative quote marks c.setFont('Poppins-Bold', 200) c.setFillColor(HexColor('#2c2826')) c.drawString(PAD_X - 10, H - SAFE_TOP - 220, '\u201c') cy = H - SAFE_TOP - 140 draw_multiline(c, 'Хороший интерьер —', 'Poppins-Bold', 66, WHITE, PAD_X, cy, W - PAD_X*2, leading=80) cy -= 80 draw_multiline(c, 'это не про «красиво»', 'Poppins-Bold', 66, GOLD, PAD_X, cy, W - PAD_X*2, leading=80) cy -= 110 c.setStrokeColor(GOLD) c.setLineWidth(1) c.line(PAD_X, cy, PAD_X + 100, cy) cy -= 50 items = [ 'Удобство каждый день', 'Свет, который не раздражает', 'Пространство, где можно дышать', 'Ощущения, которые остаются', ] c.setFont('Poppins-Light', 36) for item in items: c.setFillColor(GOLD) c.drawString(PAD_X, cy, '—') c.setFillColor(CREAM) c.drawString(PAD_X + 35, cy, item) cy -= 56 cy -= 20 draw_multiline(c, 'Это жизнь, которая будет\nпроисходить в этом пространстве.', 'Poppins-Regular', 34, HexColor('#FDECD4'), PAD_X, cy, W - PAD_X*2, leading=50) def slide8(c): """CTA slide.""" draw_background(c, 8) draw_brand_frame(c) draw_slide_number(c, 8) draw_logo(c) # Gold large accent block c.setFillColor(GOLD) c.rect(0, H - SAFE_TOP - 420, W, 4, fill=1, stroke=0) cy = H - SAFE_TOP - 100 c.setFont('Poppins-Light', 34) c.setFillColor(CREAM) c.drawString(PAD_X, cy, 'Я подготовила') cy -= 55 # Big highlight draw_multiline(c, 'ЧЕК-ЛИСТ', 'Poppins-Bold', 110, GOLD, PAD_X, cy, W - PAD_X*2, leading=120) cy -= 130 draw_multiline(c, '42 пункта', 'Poppins-Bold', 72, WHITE, PAD_X, cy, W - PAD_X*2, leading=82) cy -= 90 c.setFont('Poppins-Light', 34) c.setFillColor(CREAM) draw_multiline(c, 'которые помогают понять, готовы ли вы к ремонту', 'Poppins-Light', 34, CREAM, PAD_X, cy, W - PAD_X*2, leading=48) cy -= 110 c.setStrokeColor(GOLD) c.setLineWidth(1) c.line(PAD_X, cy, W - PAD_X, cy) cy -= 60 # CTA box c.setFillColor(GOLD) c.roundRect(PAD_X, cy - 100, W - PAD_X*2, 100, 14, fill=1, stroke=0) c.setFont('Poppins-Bold', 46) c.setFillColor(GRAPHITE) txt = 'Напишите: «ЧЕК-ЛИСТ»' tw = c.stringWidth(txt, 'Poppins-Bold', 46) c.drawString((W - tw) / 2, cy - 60, txt) cy -= 130 c.setFont('Poppins-Light', 30) c.setFillColor(CREAM) draw_multiline(c, 'и я отправлю материалы в директ', 'Poppins-Light', 30, CREAM, PAD_X, cy, W - PAD_X*2, leading=44) # ─── BUILD PDF ──────────────────────────────────────────────────────────────── output_path = '/mnt/user-data/outputs/rubanova_carousel_remont.pdf' c = canvas.Canvas(output_path, pagesize=(W, H)) c.setTitle('Рубанова — 5 ошибок до ремонта') slides = [slide1, slide2, slide3, slide4, slide5, slide6, slide7, slide8] for fn in slides: fn(c) c.showPage() c.save() print(f'Done → {output_path}')
Made on
Tilda