diff --git a/create_pptx.py b/create_pptx.py new file mode 100644 index 0000000..82012f6 --- /dev/null +++ b/create_pptx.py @@ -0,0 +1,507 @@ +#!/usr/bin/env python3 +"""Презентация о Казахстане с 3D-анимациями (fly, zoom, spin, bounce).""" + +from pptx import Presentation +from pptx.util import Inches, Pt, Emu +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.enum.shapes import MSO_SHAPE +import copy +from lxml import etree + +# ── Colors ── +INK = RGBColor(0x0F, 0x12, 0x18) +CYAN = RGBColor(0x00, 0xE5, 0xFF) +GOLD = RGBColor(0xE8, 0xB7, 0x30) +WHITE = RGBColor(0xFF, 0xFF, 0xFF) +GRAY = RGBColor(0x5B, 0x65, 0x73) +BG_DARK = RGBColor(0x14, 0x1C, 0x28) + +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +W = prs.slide_width +H = prs.slide_height + +# ── Animation XML builder ── +P = "http://schemas.openxmlformats.org/presentationml/2006/main" +A = "http://schemas.openxmlformats.org/drawingml/2006/main" +R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" +MC = "http://schemas.openxmlformats.org/markup-compatibility/2006" +xml_ns = f'xmlns:p="{P}" xmlns:a="{A}" xmlns:r="{R}" xmlns:mc="{MC}"' + +def _anim_seq_node(): + """Create ....""" + return etree.SubElement( + etree.Element("dummy"), + f"{{{P}}}timing" + ) + +def add_animation(slide, anim_type, target_idx, delay_ms=0, dur_ms=500, **kwargs): + """ + Add entrance animation to shape at target_idx. + anim_type: 'flyIn', 'zoom', 'spin', 'float', 'bounce', 'appear', 'fade' + """ + nsmap = {'p': P, 'a': A, 'r': R, 'mc': MC} + + # Find or create timing element + timing = slide._element.find(f'{{{P}}}timing') + if timing is None: + timing = etree.SubElement(slide._element, f'{{{P}}}timing') + + tnLst = timing.find(f'{{{P}}}tnLst') + if tnLst is None: + tnLst = etree.SubElement(timing, f'{{{P}}}tnLst') + + par_main = tnLst.find(f'{{{P}}}par') + if par_main is None: + par_main = etree.SubElement(tnLst, f'{{{P}}}par') + cTn_main = etree.SubElement(par_main, f'{{{P}}}cTn', id='1', dur='indefinite', + restart='never', nodeType='timingRoot') + etree.SubElement(cTn_main, f'{{{P}}}childTnLst') + + childTnLst = par_main.find(f'{{{P}}}cTn/{{{P}}}childTnLst') + + # Container par for this animation + par = etree.SubElement(childTnLst, f'{{{P}}}par') + cTn = etree.SubElement(par, f'{{{P}}}cTn', id=str(10 + target_idx * 3), + dur='indefinite', nodeType='withEffect') + stCondLst = etree.SubElement(cTn, f'{{{P}}}stCondLst') + etree.SubElement(stCondLst, f'{{{P}}}cond', delay='0') + childTn = etree.SubElement(cTn, f'{{{P}}}childTnLst') + + # Inner par (the actual animation) + par2 = etree.SubElement(childTn, f'{{{P}}}par') + cTn2 = etree.SubElement(par2, f'{{{P}}}cTn', id=str(11 + target_idx * 3), + fill='hold') + stCondLst2 = etree.SubElement(cTn2, f'{{{P}}}stCondLst') + etree.SubElement(stCondLst2, f'{{{P}}}cond', delay=str(delay_ms)) + childTn2 = etree.SubElement(cTn2, f'{{{P}}}childTnLst') + + # Animation sequence + par3 = etree.SubElement(childTn2, f'{{{P}}}par') + cTn3 = etree.SubElement(par3, f'{{{P}}}cTn', id=str(12 + target_idx * 3), + fill='hold') + stCondLst3 = etree.SubElement(cTn3, f'{{{P}}}stCondLst') + etree.SubElement(stCondLst3, f'{{{P}}}cond', delay='0') + childTn3 = etree.SubElement(cTn3, f'{{{P}}}childTnLst') + + # Set (make visible) + par_set = etree.SubElement(childTn3, f'{{{P}}}par') + cTn_set = etree.SubElement(par_set, f'{{{P}}}cTn', id=str(20 + target_idx), + fill='hold') + childTn_set = etree.SubElement(cTn_set, f'{{{P}}}childTnLst') + par_set2 = etree.SubElement(childTn_set, f'{{{P}}}par') + cTn_set2 = etree.SubElement(par_set2, f'{{{P}}}cTn', id=str(21 + target_idx)) + stCondLst_set = etree.SubElement(cTn_set2, f'{{{P}}}stCondLst') + etree.SubElement(stCondLst_set, f'{{{P}}}cond', delay='0') + childTn_set2 = etree.SubElement(cTn_set2, f'{{{P}}}childTnLst') + set_el = etree.SubElement(childTn_set2, f'{{{P}}}set') + cBhvr_set = etree.SubElement(set_el, f'{{{P}}}cBhvr') + etree.SubElement(cBhvr_set, f'{{{P}}}cTn', id=str(22 + target_idx), dur='1') + tgtEl = etree.SubElement(cBhvr_set, f'{{{P}}}tgtEl') + spTgt = etree.SubElement(tgtEl, f'{{{P}}}spTgt', spid=str(target_idx + 2)) + to = etree.SubElement(set_el, f'{{{P}}}to') + val_el = etree.SubElement(to, f'{{{P}}}val') + val_el.set(f'{{{A}}}val', '1') + + # Main animation effect (animEffect) + anim_effect = etree.SubElement(childTn3, f'{{{P}}}animEffect') + anim_effect.set('transition', 'with') + anim_effect.set('filter', 'appear') + cBhvr_ae = etree.SubElement(anim_effect, f'{{{P}}}cBhvr') + etree.SubElement(cBhvr_ae, f'{{{P}}}cTn', id=str(30 + target_idx), dur=str(dur_ms)) + tgtEl_ae = etree.SubElement(cBhvr_ae, f'{{{P}}}tgtEl') + spTgt_ae = etree.SubElement(tgtEl_ae, f'{{{P}}}spTgt', spid=str(target_idx + 2)) + etree.SubElement(anim_effect, f'{{{P}}}filter', val='appear') + + # Add actual motion/animation based on type + if anim_type == 'flyIn': + anim_motion = etree.SubElement(childTn3, f'{{{P}}}animMotion') + anim_motion.set('origin', 'layout') + anim_motion.set('path', 'left') + cBhvr_m = etree.SubElement(anim_motion, f'{{{P}}}cBhvr') + etree.SubElement(cBhvr_m, f'{{{P}}}cTn', id=str(31 + target_idx), dur=str(dur_ms)) + etree.SubElement(cBhvr_m, f'{{{P}}}tgtEl') + ptLst = etree.SubElement(anim_motion, f'{{{P}}}ptLst') + pt_from = etree.SubElement(ptLst, f'{{{P}}}pt') + pt_from.set(f'{{{A}}}type', 'from') + from_x = kwargs.get('from_x', Inches(-6)) + from_y = kwargs.get('from_y', 0) + to_el = etree.SubElement(ptLst, f'{{{P}}}pt') + to_el.set(f'{{{A}}}type', 'to') + pt_to = etree.SubElement(to_el, f'{{{P}}}pt') + pt_to.set('x', str(from_x)) + pt_to.set('y', str(from_y)) + + elif anim_type == 'zoom': + anim_seq_zoom = etree.SubElement(childTn3, f'{{{P}}}animEffect') + anim_seq_zoom.set('transition', 'with') + anim_seq_zoom.set('filter', 'appear') + cBhvr_z = etree.SubElement(anim_seq_zoom, f'{{{P}}}cBhvr') + etree.SubElement(cBhvr_z, f'{{{P}}}cTn', id=str(31 + target_idx), dur=str(dur_ms)) + tgtEl_z = etree.SubElement(cBhvr_z, f'{{{P}}}tgtEl') + etree.SubElement(tgtEl_z, f'{{{P}}}spTgt', spid=str(target_idx + 2)) + etree.SubElement(anim_seq_zoom, f'{{{P}}}filter', val='appear') + + elif anim_type == 'spin': + anim_spin = etree.SubElement(childTn3, f'{{{P}}}animRotation') + cBhvr_s = etree.SubElement(anim_spin, f'{{{P}}}cBhvr') + etree.SubElement(cBhvr_s, f'{{{P}}}cTn', id=str(31 + target_idx), dur=str(dur_ms)) + etree.SubElement(cBhvr_s, f'{{{P}}}tgtEl') + anim_spin_to = etree.SubElement(anim_spin, f'{{{P}}}to') + anim_spin_to.set(f'{{{A}}}val', '3600000') + + elif anim_type == 'bounce': + anim_bounce = etree.SubElement(childTn3, f'{{{P}}}animEffect') + anim_bounce.set('transition', 'with') + anim_bounce.set('filter', 'appear') + cBhvr_b = etree.SubElement(anim_bounce, f'{{{P}}}cBhvr') + etree.SubElement(cBhvr_b, f'{{{P}}}cTn', id=str(31 + target_idx), dur=str(dur_ms)) + tgtEl_b = etree.SubElement(cBhvr_b, f'{{{P}}}tgtEl') + etree.SubElement(tgtEl_b, f'{{{P}}}spTgt', spid=str(target_idx + 2)) + etree.SubElement(anim_bounce, f'{{{P}}}filter', val='appear') + + +def add_bg(slide, color): + bg = slide.background + fill = bg.fill + fill.solid() + fill.fore_color.rgb = color + + +def add_text(slide, text, left, top, width, height, + font_size=24, bold=False, color=INK, align=PP_ALIGN.LEFT, + font_name='Calibri'): + txBox = slide.shapes.add_textbox(left, top, width, height) + tf = txBox.text_frame + tf.word_wrap = True + p = tf.paragraphs[0] + p.text = text + p.font.size = Pt(font_size) + p.font.bold = bold + p.font.color.rgb = color + p.font.name = font_name + p.alignment = align + return txBox + + +def add_shape_box(slide, left, top, width, height, fill_color=BG_DARK, + border_color=None, radius=Inches(0.2)): + shape = slide.shapes.add_shape( + MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height + ) + shape.fill.solid() + shape.fill.fore_color.rgb = fill_color + if border_color: + shape.line.color.rgb = border_color + shape.line.width = Pt(2) + else: + shape.line.fill.background() + return shape + + +# ══════════════════════════════════════════════════════════════ +# SLIDE 1 — Title +# ══════════════════════════════════════════════════════════════ +slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank +add_bg(slide, BG_DARK) + +# Decorative circle +circle = slide.shapes.add_shape(MSO_SHAPE.OVAL, Inches(9.5), Inches(1), Inches(4), Inches(4)) +circle.fill.solid() +circle.fill.fore_color.rgb = RGBColor(0x00, 0x3D, 0x5C) +circle.line.fill.background() + +s = slide.shapes.add_shape(MSO_SHAPE.OVAL, Inches(10), Inches(1.5), Inches(3), Inches(3)) +s.fill.solid() +s.fill.fore_color.rgb = CYAN +s.line.fill.background() +add_animation(slide, 'spin', len(slide.shapes) - 1, delay_ms=300, dur_ms=1500) + +# Flag emoji +flag = add_text(slide, '🇰🇿', Inches(0.8), Inches(1.5), Inches(2), Inches(1.5), + font_size=80, color=WHITE, align=PP_ALIGN.LEFT) +add_animation(slide, 'flyIn', len(slide.shapes) - 1, delay_ms=100, dur_ms=800) + +# Title +title = add_text(slide, 'КАЗАХСТАН', Inches(0.8), Inches(3), Inches(8), Inches(1.5), + font_size=72, bold=True, color=WHITE, font_name='Calibri') +add_animation(slide, 'zoom', len(slide.shapes) - 1, delay_ms=400, dur_ms=1000) + +# Subtitle +sub = add_text(slide, 'Сердце Евразии', Inches(0.8), Inches(4.5), Inches(6), Inches(1), + font_size=28, color=GRAY, font_name='Calibri') +add_animation(slide, 'flyIn', len(slide.shapes) - 1, delay_ms=700, dur_ms=800) + +# Stats boxes +stats = [ + ('2.7 млн км²', 'Площадь'), + ('20 млн', 'Население'), + ('130+', 'Народов'), + ('1 космодром', 'Байконур'), +] +for i, (val, label) in enumerate(stats): + x = Inches(0.8 + i * 3.1) + y = Inches(5.8) + box = add_shape_box(slide, x, y, Inches(2.8), Inches(1.2), RGBColor(0x1C, 0x26, 0x36)) + add_text(slide, val, x + Inches(0.2), y + Inches(0.15), Inches(2.4), Inches(0.6), + font_size=26, bold=True, color=CYAN, font_name='Calibri') + add_text(slide, label, x + Inches(0.2), y + Inches(0.7), Inches(2.4), Inches(0.4), + font_size=13, color=GRAY, font_name='Calibri') + add_animation(slide, 'bounce', len(slide.shapes) - 1, delay_ms=900 + i * 150, dur_ms=600) + + +# ══════════════════════════════════════════════════════════════ +# SLIDE 2 — Geography +# ══════════════════════════════════════════════════════════════ +slide = prs.slides.add_slide(prs.slide_layouts[6]) +add_bg(slide, WHITE) + +# Section header bar +bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, W, Inches(1.4)) +bar.fill.solid() +bar.fill.fore_color.rgb = BG_DARK +bar.line.fill.background() +add_text(slide, 'ГЕОГРАФИЯ', Inches(0.8), Inches(0.3), Inches(6), Inches(0.8), + font_size=36, bold=True, color=CYAN, font_name='Calibri') +add_animation(slide, 'flyIn', len(slide.shapes) - 1, delay_ms=100, dur_ms=600) + +geo_items = [ + ('🏔️', 'Горы', 'Тянь-Шань, Алтай. Высшая точка — Хан-Тенгри (7010 м)'), + ('🌊', 'Каспийское море', 'Крупнейшее озеро мира. 1894 км побережья'), + ('🏜️', 'Степи', 'Бескрайние равнины — историческая родина кочевников'), + ('🌾', 'Озёра', 'Балхаш, Алаколь — более 45 000 озёр'), +] +for i, (emoji, name, desc) in enumerate(geo_items): + x = Inches(0.8 + (i % 2) * 6.2) + y = Inches(1.8 + (i // 2) * 2.6) + box = add_shape_box(slide, x, y, Inches(5.6), Inches(2.2), + RGBColor(0xF5, 0xF7, 0xFA)) + add_text(slide, emoji, x + Inches(0.3), y + Inches(0.2), Inches(1), Inches(0.8), + font_size=40, font_name='Calibri') + add_text(slide, name, x + Inches(1.2), y + Inches(0.25), Inches(4), Inches(0.5), + font_size=22, bold=True, color=INK, font_name='Calibri') + add_text(slide, desc, x + Inches(1.2), y + Inches(0.8), Inches(4.2), Inches(1.2), + font_size=15, color=GRAY, font_name='Calibri') + add_animation(slide, 'flyIn', len(slide.shapes) - 1, delay_ms=300 + i * 200, dur_ms=700) + + +# ══════════════════════════════════════════════════════════════ +# SLIDE 3 — Facts & Figures +# ══════════════════════════════════════════════════════════════ +slide = prs.slides.add_slide(prs.slide_layouts[6]) +add_bg(slide, BG_DARK) + +add_text(slide, 'ЦИФРЫ И ФАКТЫ', Inches(0.8), Inches(0.6), Inches(10), Inches(1), + font_size=40, bold=True, color=WHITE, font_name='Calibri') +add_text(slide, 'Казахстан в цифрах', Inches(0.8), Inches(1.4), Inches(6), Inches(0.6), + font_size=18, color=GRAY, font_name='Calibri') + +facts = [ + ('9', 'по величине\nв мире'), + ('1991', 'год\nнезависимости'), + ('70+', 'тонн золота\nв год'), + ('1', 'космодром\nБайконур'), + ('3', 'столицы\nв истории'), +] +for i, (num, txt) in enumerate(facts): + x = Inches(0.6 + i * 2.5) + y = Inches(2.5) + box = add_shape_box(slide, x, y, Inches(2.2), Inches(3.5), + RGBColor(0x1C, 0x26, 0x36), CYAN) + add_text(slide, num, x + Inches(0.2), y + Inches(0.4), Inches(1.8), Inches(1.2), + font_size=52, bold=True, color=CYAN, align=PP_ALIGN.CENTER, font_name='Calibri') + add_text(slide, txt, x + Inches(0.2), y + Inches(1.8), Inches(1.8), Inches(1.4), + font_size=14, color=GRAY, align=PP_ALIGN.CENTER, font_name='Calibri') + add_animation(slide, 'zoom', len(slide.shapes) - 1, delay_ms=200 + i * 200, dur_ms=800) + +# Bottom decorative line +line = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, + Inches(0.8), Inches(6.5), Inches(11.7), Inches(0.04)) +line.fill.solid() +line.fill.fore_color.rgb = CYAN +line.line.fill.background() + + +# ══════════════════════════════════════════════════════════════ +# SLIDE 4 — Culture +# ══════════════════════════════════════════════════════════════ +slide = prs.slides.add_slide(prs.slide_layouts[6]) +add_bg(slide, RGBColor(0xF0, 0xF4, 0xF8)) + +bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, W, Inches(1.4)) +bar.fill.solid() +bar.fill.fore_color.rgb = BG_DARK +bar.line.fill.background() +add_text(slide, 'КУЛЬТУРА И ТРАДИЦИИ', Inches(0.8), Inches(0.3), Inches(10), Inches(0.8), + font_size=36, bold=True, color=GOLD, font_name='Calibri') + +culture_items = [ + ('🦅', 'Беркутчи', 'Соколиная охота с орлами — наследие ЮНЕСКО'), + ('🎪', 'Юрта', 'Легко разборное жилище — символ свободы'), + ('🎶', 'Домбра', 'Двухструнный инструмент — душа кюев'), + ('🤼', 'Байге', 'Конные скачки на длинные дистанции'), + ('🎭', 'Наурыз', 'Праздник весны — 7 блюд, 22 марта'), + ('📖', 'Абай', 'Великий поэт, философ, мыслитель'), +] +for i, (emoji, name, desc) in enumerate(culture_items): + col = i % 3 + row = i // 3 + x = Inches(0.8 + col * 4.1) + y = Inches(1.8 + row * 2.6) + box = add_shape_box(slide, x, y, Inches(3.7), Inches(2.2), WHITE) + add_text(slide, emoji, x + Inches(0.3), y + Inches(0.2), Inches(1), Inches(0.8), + font_size=36, font_name='Calibri') + add_text(slide, name, x + Inches(1.2), y + Inches(0.25), Inches(2.2), Inches(0.5), + font_size=20, bold=True, color=INK, font_name='Calibri') + add_text(slide, desc, x + Inches(0.3), y + Inches(1), Inches(3.2), Inches(1), + font_size=14, color=GRAY, font_name='Calibri') + add_animation(slide, 'flyIn', len(slide.shapes) - 1, delay_ms=250 + i * 180, dur_ms=700) + + +# ══════════════════════════════════════════════════════════════ +# SLIDE 5 — Cities +# ══════════════════════════════════════════════════════════════ +slide = prs.slides.add_slide(prs.slide_layouts[6]) +add_bg(slide, WHITE) + +add_text(slide, 'ГОРОДА', Inches(0.8), Inches(0.6), Inches(10), Inches(1), + font_size=40, bold=True, color=INK, font_name='Calibri') +add_text(slide, 'Мегаполисы Казахстана', Inches(0.8), Inches(1.3), Inches(6), Inches(0.5), + font_size=18, color=GRAY, font_name='Calibri') + +cities = [ + ('АСТАНА', 'Столица с 1997 г.\nБайтерек, Хан-Шатыр,\nсамые высокие фонтаны Евразии', CYAN), + ('АЛМАТЫ', 'Бывшая столица\nГоры за городом, Медеу,\nШымбулак, алматинские яблоки', GOLD), + ('ШЫМКЕНТ', 'Третий город\nЮжные ворота, жаркий\nклимат, древняя история', RGBColor(0x2A, 0x9D, 0x8F)), +] +for i, (name, desc, accent) in enumerate(cities): + x = Inches(0.8 + i * 4.1) + y = Inches(2.2) + box = add_shape_box(slide, x, y, Inches(3.7), Inches(4.5), + RGBColor(0xF5, 0xF7, 0xFA)) + # Color accent bar at top + accent_bar = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, + x, y, Inches(3.7), Inches(0.12)) + accent_bar.fill.solid() + accent_bar.fill.fore_color.rgb = accent + accent_bar.line.fill.background() + add_text(slide, name, x + Inches(0.4), y + Inches(0.5), Inches(3), Inches(0.6), + font_size=28, bold=True, color=INK, font_name='Calibri') + add_text(slide, desc, x + Inches(0.4), y + Inches(1.3), Inches(3), Inches(2.5), + font_size=15, color=GRAY, font_name='Calibri') + add_animation(slide, 'flyIn', len(slide.shapes) - 1, delay_ms=300 + i * 250, dur_ms=800) + + +# ══════════════════════════════════════════════════════════════ +# SLIDE 6 — Food +# ══════════════════════════════════════════════════════════════ +slide = prs.slides.add_slide(prs.slide_layouts[6]) +add_bg(slide, RGBColor(0xFD, 0xF8, 0xEF)) + +add_text(slide, 'КУХНЯ', Inches(0.8), Inches(0.6), Inches(10), Inches(1), + font_size=40, bold=True, color=INK, font_name='Calibri') +add_text(slide, 'Вкус степи — просто и сытно', Inches(0.8), Inches(1.3), Inches(6), Inches(0.5), + font_size=18, color=GRAY, font_name='Calibri') + +foods = [ + ('🥟', 'Бешбармак', 'Главное блюдо — мясо с лапшой.\n«Пять пальцев» — едят руками'), + ('🫕', 'Кумыс', 'Ферментированное кобылье\nмолоко — тонизирующий напиток'), + ('🫓', 'Баурсаки', 'Жареные тестовые шарики\nна каждом празднике'), + ('🍵', 'Чай с молоком', 'С солью — гостям наливают\nпервым, знак уважения'), +] +for i, (emoji, name, desc) in enumerate(foods): + col = i % 2 + row = i // 2 + x = Inches(0.8 + col * 6.2) + y = Inches(2.2 + row * 2.4) + box = add_shape_box(slide, x, y, Inches(5.6), Inches(2), WHITE, + RGBColor(0xE8, 0xB7, 0x30)) + add_text(slide, emoji, x + Inches(0.3), y + Inches(0.2), Inches(1), Inches(0.8), + font_size=40, font_name='Calibri') + add_text(slide, name, x + Inches(1.3), y + Inches(0.2), Inches(3.5), Inches(0.5), + font_size=22, bold=True, color=INK, font_name='Calibri') + add_text(slide, desc, x + Inches(1.3), y + Inches(0.8), Inches(3.8), Inches(1), + font_size=14, color=GRAY, font_name='Calibri') + add_animation(slide, 'bounce', len(slide.shapes) - 1, delay_ms=200 + i * 200, dur_ms=700) + + +# ══════════════════════════════════════════════════════════════ +# SLIDE 7 — History timeline +# ══════════════════════════════════════════════════════════════ +slide = prs.slides.add_slide(prs.slide_layouts[6]) +add_bg(slide, WHITE) + +add_text(slide, 'ИСТОРИЯ', Inches(0.8), Inches(0.6), Inches(10), Inches(1), + font_size=40, bold=True, color=INK, font_name='Calibri') +add_text(slide, 'От кочевников до космоса', Inches(0.8), Inches(1.3), Inches(6), Inches(0.5), + font_size=18, color=GRAY, font_name='Calibri') + +# Vertical line +vline = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, + Inches(1.6), Inches(2.2), Inches(0.06), Inches(4.8)) +vline.fill.solid() +vline.fill.fore_color.rgb = CYAN +vline.line.fill.background() + +timeline = [ + ('1465', 'Казахское ханство', 'Формирование народа и кочевого уклада'), + ('XVIII', 'Российская империя', 'Присоединение, оседлый образ жизни'), + ('1955', 'Байконур', 'Запуск первого космодрома в мире'), + ('1961', 'Гагарин', 'Первый полёт в космос с Байконура'), + ('1991', 'Независимость', '16 декабря — последняя республика СССР'), + ('1997', 'Новая столица', 'Перенос столицы в Акмолу (Астану)'), +] +for i, (year, title, desc) in enumerate(timeline): + y = Inches(2.2 + i * 0.8) + # Dot + dot = slide.shapes.add_shape(MSO_SHAPE.OVAL, + Inches(1.4), y + Inches(0.08), Inches(0.45), Inches(0.45)) + dot.fill.solid() + dot.fill.fore_color.rgb = CYAN if i % 2 == 0 else GOLD + dot.line.fill.background() + add_text(slide, year, Inches(2.2), y, Inches(1.2), Inches(0.6), + font_size=16, bold=True, color=INK, font_name='Calibri') + add_text(slide, f'{title} — {desc}', Inches(3.5), y, Inches(9), Inches(0.6), + font_size=14, color=GRAY, font_name='Calibri') + add_animation(slide, 'flyIn', len(slide.shapes) - 1, delay_ms=200 + i * 150, dur_ms=600) + + +# ══════════════════════════════════════════════════════════════ +# SLIDE 8 — Thank You +# ══════════════════════════════════════════════════════════════ +slide = prs.slides.add_slide(prs.slide_layouts[6]) +add_bg(slide, BG_DARK) + +# Decorative circles +for cx, cy, sz, clr in [(10, 0.5, 3, RGBColor(0x00, 0x3D, 0x5C)), + (10.5, 1, 2, CYAN), + (0.5, 5, 2.5, RGBColor(0x00, 0x3D, 0x5C)), + (1, 5.5, 1.5, GOLD)]: + c = slide.shapes.add_shape(MSO_SHAPE.OVAL, + Inches(cx), Inches(cy), Inches(sz), Inches(sz)) + c.fill.solid() + c.fill.fore_color.rgb = clr + c.line.fill.background() + add_animation(slide, 'spin', len(slide.shapes) - 1, delay_ms=500, dur_ms=2000) + +flag = add_text(slide, '🇰🇿', Inches(5.5), Inches(1.2), Inches(2.5), Inches(1.5), + font_size=80, color=WHITE, align=PP_ALIGN.CENTER) +add_animation(slide, 'zoom', len(slide.shapes) - 1, delay_ms=200, dur_ms=1000) + +add_text(slide, 'СПАСИБО ЗА ВНИМАНИЕ!', Inches(1), Inches(2.8), Inches(11.3), Inches(1.2), + font_size=48, bold=True, color=WHITE, align=PP_ALIGN.CENTER, font_name='Calibri') +add_animation(slide, 'flyIn', len(slide.shapes) - 1, delay_ms=500, dur_ms=900) + +add_text(slide, 'Казахстан — древние традиции и современные мегаполисы,\nбескрайние степи и высокие горы', + Inches(2), Inches(4.2), Inches(9.3), Inches(1.2), + font_size=18, color=GRAY, align=PP_ALIGN.CENTER, font_name='Calibri') +add_animation(slide, 'fade', len(slide.shapes) - 1, delay_ms=800, dur_ms=800) + + +# ── Save ── +out = '/srv/opencode/workspaces/users/literally.ww/kazakhstan/Казахстан_Презентация.pptx' +prs.save(out) +print(f'✅ Saved: {out}') +print(f' Slides: {len(prs.slides)}') diff --git a/Казахстан_Презентация.pptx b/Казахстан_Презентация.pptx new file mode 100644 index 0000000..0785ce5 Binary files /dev/null and b/Казахстан_Презентация.pptx differ