Spaces:
Sleeping
Sleeping
| """Legawa Explainer Video β Manim CE script. | |
| Scenes: | |
| 1. The Problem β stack of RUU documents | |
| 2. What Legawa Does β 4 agent cards | |
| 3. How It Works β flow diagram | |
| 4. Free & Open β tokens + links | |
| Rendered with: manim -qm script.py Scene1_Problem Scene2_Agents Scene3_Flow Scene4_Closing | |
| """ | |
| from manim import * | |
| # ββ Palette (Warm Academic) ββββββββββββββββββββββββββββββββββββββββββββββ | |
| BG = "#1a1a2e" | |
| TITLE = "#eaeaea" | |
| BODY = "#cccccc" | |
| PRIMARY = "#ff6b6b" | |
| SECONDARY = "#ffd93d" | |
| ACCENT = "#6bcb77" | |
| MUTED = "#666666" | |
| MONO = "monospace" | |
| # ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def T(text, size=36, color=TITLE, weight=NORMAL): | |
| """Shorthand for monospace Text.""" | |
| return Text(text, font_size=size, font=MONO, color=color, weight=weight) | |
| def card_box(width=2.2, height=1.2, color=PRIMARY): | |
| """Rounded rect for agent cards.""" | |
| return RoundedRectangle( | |
| width=width, height=height, | |
| corner_radius=0.15, | |
| stroke_color=color, stroke_width=2.5, | |
| fill_color=color, fill_opacity=0.12, | |
| ) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Scene 1 β The Problem | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class Scene1_Problem(Scene): | |
| def construct(self): | |
| self.camera.background_color = BG | |
| # Papers piling up | |
| papers = VGroup() | |
| labels = ["RUU", "RUU", "RUU", "RUU", "RUU", "RUU"] | |
| colors = [PRIMARY, SECONDARY, ACCENT, PRIMARY, SECONDARY, ACCENT] | |
| for i, (label, c) in enumerate(zip(labels[:6], colors[:6])): | |
| w, h = 2.6 - i * 0.15, 3.2 - i * 0.15 | |
| x_shift = i * 0.06 | |
| y_shift = i * 0.08 - 1.0 | |
| paper = Rectangle( | |
| width=w, height=h, | |
| stroke_color=c, stroke_width=2, | |
| fill_color=c, fill_opacity=0.08, | |
| ).shift(RIGHT * x_shift + UP * y_shift) | |
| text = T(label, size=18, color=c).move_to(paper) | |
| papers.add(paper, text) | |
| self.play( | |
| LaggedStart(*[FadeIn(p, shift=UP * 0.3, scale=0.9) for p in papers], | |
| lag_ratio=0.12), | |
| run_time=2.5, | |
| ) | |
| self.wait(0.5) | |
| # Stressed legislator text | |
| stress = T("?", size=72, color=PRIMARY, weight=BOLD).shift(RIGHT * 3.2 + UP * 1.0) | |
| self.play(Write(stress), run_time=0.5) | |
| self.wait(0.3) | |
| # Problem text above the stack | |
| problem_text = T( | |
| "Ratusan RUU setiap tahun...\nberhari-hari riset per dokumen", | |
| size=26, color=BODY, | |
| ).to_edge(UP, buff=0.5) | |
| self.play(Write(problem_text), run_time=1.2) | |
| self.wait(1.5) | |
| # Fade out to Legawa logo | |
| logo = T("ποΈ Legawa", size=56, color=SECONDARY, weight=BOLD) | |
| subtitle = T("Multi-Agent AI untuk Legislator", size=24, color=MUTED).next_to(logo, DOWN) | |
| self.play( | |
| FadeOut(VGroup(*papers, stress, problem_text)), | |
| run_time=0.8, | |
| ) | |
| self.play(Write(logo), run_time=1.0) | |
| self.play(Write(subtitle), run_time=0.8) | |
| self.wait(1.5) | |
| self.play(FadeOut(VGroup(logo, subtitle)), run_time=0.5) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Scene 2 β What Legawa Does | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class Scene2_Agents(Scene): | |
| def construct(self): | |
| self.camera.background_color = BG | |
| title = T("Empat Agen dalam Satu Asisten", size=40, color=TITLE, weight=BOLD) | |
| title.to_edge(UP, buff=0.5) | |
| self.play(Write(title), run_time=0.8) | |
| self.wait(0.3) | |
| # 4 agent cards in a 2x2 grid | |
| agents = [ | |
| ("π Analisis\nRUU", PRIMARY, "Analisis pasal-per-pasal\nlengkap sitasi"), | |
| ("π Riset\nHukum", ACCENT, "Cari peraturan di\npasal.id"), | |
| ("βοΈ Draf\nDokumen", SECONDARY, "Pidato, naskah akademik,\nmemo kebijakan"), | |
| ("π¬ Surat\nKonstituen", PRIMARY, "Triase & draft\nbalasan"), | |
| ] | |
| positions = [ | |
| LEFT * 3 + UP * 0.5, | |
| RIGHT * 3 + UP * 0.5, | |
| LEFT * 3 + DOWN * 1.8, | |
| RIGHT * 3 + DOWN * 1.8, | |
| ] | |
| cards = VGroup() | |
| for (emoji_name, color, desc), pos in zip(agents, positions): | |
| box = RoundedRectangle( | |
| width=4.5, height=2.2, | |
| corner_radius=0.15, | |
| stroke_color=color, stroke_width=2.5, | |
| fill_color=color, fill_opacity=0.1, | |
| ).move_to(pos) | |
| name_text = T(emoji_name, size=30, color=color, weight=BOLD) | |
| name_text.move_to(box).shift(UP * 0.3) | |
| desc_text = T(desc, size=18, color=BODY) | |
| desc_text.next_to(name_text, DOWN, buff=0.3) | |
| cards.add(VGroup(box, name_text, desc_text)) | |
| # Animate in one by one | |
| for card in cards: | |
| self.play(FadeIn(card, shift=UP * 0.3, scale=0.95), run_time=0.6) | |
| self.wait(0.15) | |
| self.wait(1.0) | |
| # Tagline | |
| tag = T("Small models, big adventure ποΈ", size=24, color=SECONDARY) | |
| tag.to_edge(DOWN, buff=0.4) | |
| self.play(Write(tag), run_time=0.8) | |
| self.wait(1.5) | |
| self.play(FadeOut(VGroup(title, cards, tag)), run_time=0.5) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Scene 3 β How It Works (Flow Diagram) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class Scene3_Flow(Scene): | |
| def construct(self): | |
| self.camera.background_color = BG | |
| title = T("Bagaimana Cara Kerjanya?", size=36, color=TITLE, weight=BOLD) | |
| title.to_edge(UP, buff=0.4) | |
| self.play(Write(title), run_time=0.6) | |
| self.wait(0.2) | |
| def make_box(text_lines, color, w=2.2, h=1.0): | |
| """Helper: box with centered text.""" | |
| box = RoundedRectangle( | |
| width=w, height=h, | |
| corner_radius=0.12, | |
| stroke_color=color, stroke_width=2, | |
| fill_color=color, fill_opacity=0.1, | |
| ) | |
| txt = T(text_lines, size=18, color=TITLE) | |
| txt.move_to(box) | |
| return VGroup(box, txt) | |
| def arrow_between(left_obj, right_obj, color=MUTED): | |
| """Arrow from right edge of left_obj to left edge of right_obj.""" | |
| start = left_obj.get_right() + RIGHT * 0.15 | |
| end = right_obj.get_left() + LEFT * 0.15 | |
| arr = Arrow( | |
| start, end, | |
| stroke_width=2.5, | |
| stroke_color=color, | |
| max_tip_length_to_length_ratio=0.08, | |
| ) | |
| return arr | |
| # Row 1: Input | |
| input_box = make_box("π\nInput RUU", ACCENT, w=2.0, h=1.2) | |
| input_box.shift(LEFT * 5.5 + UP * 0.5) | |
| # Row 1: BIG LLM | |
| big_box = make_box("π§ BIG\nQwen3.5-27B", PRIMARY, w=2.2, h=1.2) | |
| big_box.shift(LEFT * 2.0 + UP * 0.5) | |
| # Row 1: pasal.id | |
| pasal_box = make_box("π\npasal.id", SECONDARY, w=2.0, h=1.2) | |
| pasal_box.shift(RIGHT * 1.2 + UP * 0.5) | |
| # Row 2: SMALL LLM | |
| small_box = make_box("π§ SMALL\nQwen3.5-9B", PRIMARY, w=2.2, h=1.2) | |
| small_box.shift(LEFT * 1.0 + DOWN * 1.3) | |
| # Row 2: Output | |
| output_box = make_box("β \nHasil Analisis", ACCENT, w=2.0, h=1.2) | |
| output_box.shift(RIGHT * 3.5 + DOWN * 1.3) | |
| # Animate in | |
| self.play(FadeIn(input_box, shift=RIGHT * 0.5), run_time=0.6) | |
| self.wait(0.15) | |
| self.play(FadeIn(big_box, shift=RIGHT * 0.5), run_time=0.6) | |
| self.wait(0.15) | |
| # Arrow: input β BIG | |
| arr1 = arrow_between(input_box, big_box) | |
| self.play(Create(arr1), run_time=0.3) | |
| self.play(FadeIn(pasal_box, shift=RIGHT * 0.5), run_time=0.6) | |
| arr2 = arrow_between(big_box, pasal_box) | |
| self.play(Create(arr2), run_time=0.3) | |
| self.wait(0.3) | |
| self.play(FadeIn(small_box, shift=UP * 0.5), run_time=0.6) | |
| # Arrow: BIG β SMALL (down-left) | |
| arr3 = Arrow( | |
| big_box.get_bottom() + DOWN * 0.15, | |
| small_box.get_top() + UP * 0.15, | |
| stroke_width=2.5, stroke_color=MUTED, | |
| max_tip_length_to_length_ratio=0.08, | |
| ) | |
| self.play(Create(arr3), run_time=0.3) | |
| # Arrow: SMALL β Output (right) | |
| arr4 = Arrow( | |
| small_box.get_right() + RIGHT * 0.15, | |
| output_box.get_left() + LEFT * 0.15, | |
| stroke_width=2.5, stroke_color=MUTED, | |
| max_tip_length_to_length_ratio=0.08, | |
| ) | |
| self.play(FadeIn(output_box, shift=RIGHT * 0.5), run_time=0.6) | |
| self.play(Create(arr4), run_time=0.3) | |
| self.wait(0.5) | |
| # Arrow: pasal.id β SMALL (feed back) | |
| arr5 = Arrow( | |
| pasal_box.get_bottom() + DOWN * 0.15, | |
| small_box.get_right() + RIGHT * 0.5, | |
| stroke_width=2, stroke_color=SECONDARY, | |
| max_tip_length_to_length_ratio=0.08, | |
| ) | |
| # Curve it by adding a path | |
| self.play(Create(arr5), run_time=0.4) | |
| # Value text | |
| value = T("Hasil lengkap dalam hitungan menit β‘", size=28, color=SECONDARY, weight=BOLD) | |
| value.to_edge(DOWN, buff=0.5) | |
| self.play(Write(value), run_time=0.8) | |
| self.wait(2.0) | |
| self.play(FadeOut(VGroup( | |
| title, input_box, big_box, pasal_box, small_box, output_box, | |
| arr1, arr2, arr3, arr4, arr5, value, | |
| )), run_time=0.5) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Scene 4 β Free & Open (Closing) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class Scene4_Closing(Scene): | |
| def construct(self): | |
| self.camera.background_color = BG | |
| # Token icons β spread wider | |
| hf_token = T("π HF Token", size=32, color=PRIMARY, weight=BOLD) | |
| pasal_token = T("π pasal.id Token", size=32, color=SECONDARY, weight=BOLD) | |
| hf_token.shift(LEFT * 3.5 + UP * 0.8) | |
| pasal_token.shift(RIGHT * 3.5 + UP * 0.8) | |
| self.play(FadeIn(hf_token, shift=UP * 0.5), run_time=0.6) | |
| self.wait(0.2) | |
| self.play(FadeIn(pasal_token, shift=UP * 0.5), run_time=0.6) | |
| self.wait(0.3) | |
| # Tagline β centered below both tokens | |
| byo = T("Bawa token sendiri. Buka di GitHub.", size=26, color=BODY) | |
| byo.shift(DOWN * 0.5) | |
| self.play(Write(byo), run_time=0.8) | |
| self.wait(0.3) | |
| # Links | |
| gh = T("github.com/pebaryan/Legawa", size=20, color=ACCENT) | |
| hf = T("build-small-hackathon/legawa", size=20, color=ACCENT) | |
| gh.to_edge(DOWN, buff=0.8) | |
| hf.next_to(gh, DOWN, buff=0.2) | |
| self.play( | |
| Write(gh), Write(hf), | |
| run_time=0.6, | |
| ) | |
| self.wait(0.5) | |
| # Final brand | |
| final = T("ποΈ Legawa β small models, big adventure ποΈ", size=34, color=SECONDARY, weight=BOLD) | |
| final.to_edge(UP, buff=1.5) | |
| self.play(Write(final), run_time=1.0) | |
| self.wait(2.0) | |
| # Clean exit | |
| self.play(*[FadeOut(mob) for mob in self.mobjects], run_time=0.5) | |