Build 'HOME ON THE ROAD — The Tuareg of the Sahara' slide deck (.pptx) from the source article text. Run: python3 deck/build_deck.py """ import os from PIL import Image, ImageOps from pptx import Presentation from pptx.util import Inches, Pt from pptx.dml.color import RGBColor from pptx.enum.text import PP_ALIGN, MSO_ANCHOR from pptx.enum.shapes import MSO_SHAPE from pptx.oxml.ns import qn

HERE = os.path.dirname(os.path.abspath(file)) SRC, IMG = os.path.join(HERE, "..", "assets"), os.path.join(HERE, "img") os.makedirs(IMG, exist_ok=True)

---------------------------------------------------------------- image prep

SLOTS = {"cover": (2000, 1125, "cover_sahara.png", 0.45), "veil": (1000, 1250, "tuareg_veil.png", 0.40), "tea": (1000, 780, "tea.png", 0.5), "caravan": (1400, 500, "caravan.png", 0.5), "drought_crop": (1400, 500, "drought.png", 0.55), "music": (1000, 700, "music_poetry.png", 0.5), "drought": (1600, 900, "drought.png", 0.5)} for name, (w, h, src, cy) in SLOTS.items(): p = os.path.join(IMG, name + ".jpg") im = ImageOps.fit(Image.open(os.path.join(SRC, src)).convert("RGB"), (w, h), Image.LANCZOS, centering=(0.5, cy)) im.save(p, "JPEG", quality=88, progressive=True)

---------------------------------------------------------------- design system

SW, SH = 13.3333, 7.5 NIGHT = RGBColor(0x10, 0x1A, 0x2C); NIGHT2 = RGBColor(0x16, 0x24, 0x3C) CARD = RGBColor(0x1B, 0x2C, 0x48); CARDLN = RGBColor(0x2B, 0x42, 0x66) INDIGO = RGBColor(0x2E, 0x45, 0x8E); INDIGO_L = RGBColor(0x5B, 0x7B, 0xD6) SAND = RGBColor(0xF3, 0xE9, 0xD8); SAND_M = RGBColor(0xBE, 0xB2, 0x9D) CREAM = RGBColor(0xFA, 0xF6, 0xEE); INK = RGBColor(0x1A, 0x22, 0x33) INK_M = RGBColor(0x5C, 0x67, 0x7C); OCHRE = RGBColor(0xC1, 0x86, 0x3A) WHITE = RGBColor(0xFF, 0xFF, 0xFF); RULE = RGBColor(0xE2, 0xD9, 0xC8) HEAD, BODY = "Georgia", "Calibri"

prs = Presentation() prs.slide_width, prs.slide_height = Inches(SW), Inches(SH) BLANK = prs.slide_layouts[6]

def slide(bg=NIGHT): s = prs.slides.add_slide(BLANK) s.background.fill.solid(); s.background.fill.fore_color.rgb = bg return s

def rect(s, x, y, w, h, fill=None, line=None, lw=1.0, shape=MSO_SHAPE.RECTANGLE, radius=None): sp = s.shapes.add_shape(shape, Inches(x), Inches(y), Inches(w), Inches(h)) sp.shadow.inherit = False if fill is None: sp.fill.background() else: sp.fill.solid(); sp.fill.fore_color.rgb = fill if line is None: sp.line.fill.background() else: sp.line.color.rgb = line; sp.line.width = Pt(lw) if radius is not None and shape == MSO_SHAPE.ROUNDED_RECTANGLE: sp.adjustments[0] = radius return sp

def set_alpha(shape, pct): srgb = shape.fill._xPr.find(qn('a:solidFill')).find(qn('a:srgbClr')) srgb.append(srgb.makeelement(qn('a:alpha'), {'val': str(int(pct * 1000))}))

def scrim(shape, direction='bt', lo=2, hi=80): shape.fill.gradient() st = shape.fill.gradient_stops for i, (pos, al) in enumerate([(0.0, lo), (1.0, hi)]): st[i].position = pos; st[i].color.rgb = RGBColor(0, 0, 0) srgb = list(st[i]._gs)[0] srgb.append(srgb.makeelement(qn('a:alpha'), {'val': str(al * 1000)})) shape.fill.gradient_angle = {'bt': 90.0, 'tb': 270.0, 'rl': 180.0, 'lr': 0.0}[direction]

def tb(s, x, y, w, h, paras, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP): box = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) tf = box.text_frame tf.word_wrap = True; tf.vertical_anchor = anchor tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = 0 for i, spec in enumerate(paras): p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() if spec.get('bullet'): pPr = p._p.get_or_add_pPr() pPr.set('indent', '-190500'); pPr.set('marL', '190500') bu = pPr.makeelement(qn('a:buClr'), {}) bu.append(pPr.makeelement(qn('a:srgbClr'), {'val': str(spec['bullet'])})) pPr.append(bu) for tag, attrs in (('a:buFont', {'typeface': 'Arial'}), ('a:buChar', {'char': '\u25AA'})): pPr.append(pPr.makeelement(qn(tag), attrs)) p.alignment = spec.get('align', align) p.line_spacing = spec.get('line_spacing', 1.0) if spec.get('space_before'): p.space_before = Pt(spec['space_before']) p.space_after = Pt(spec.get('space_after', 0)) runs = spec.get('runs') or [dict(spec)] for r in runs: run = p.add_run(); run.text = r.get('text', '') f = run.font f.size = Pt(r.get('size') or spec.get('size') or 14) f.bold = r.get('bold') if r.get('bold') is not None else spec.get('bold', False) f.italic = r.get('italic') if r.get('italic') is not None else spec.get('italic', False) f.name = r.get('font') or spec.get('font') or BODY f.color.rgb = r.get('color') or spec.get('color') or SAND return box

def spc(box, val=240): box.text_frame.paragraphs[0].runs[0].font._rPr.set('spc', str(val)) return box

def pic(s, name, x, y, w, h, frame=None): p = s.shapes.add_picture(os.path.join(IMG, name + ".jpg"), Inches(x), Inches(y), Inches(w), Inches(h)) if frame: p.line.color.rgb = frame; p.line.width = Pt(1.25) return p

def kicker(s, text, x=0.62, y=0.42, color=OCHRE, w=9.5): return spc(tb(s, x, y, w, 0.3, [{'text': text.upper(), 'size': 11.5, 'bold': True, 'color': color, 'font': BODY}]), 260)

def heading(s, text, x=0.62, y=0.72, w=8.2, size=31, color=SAND, sub=None, sub_color=SAND_M): tb(s, x, y, w, 0.85, [{'text': text, 'size': size, 'bold': True, 'color': color, 'font': HEAD, 'line_spacing': 0.95}]) ybar = y + (0.80 if size >= 30 else 0.68) rect(s, x, ybar, 0.62, 0.055, fill=OCHRE) if sub: tb(s, x, ybar + 0.16, w, 0.4, [{'text': sub, 'size': 13.5, 'color': sub_color, 'italic': True, 'font': BODY}])

def footer(s, n, label="Home on the Road · Section A · The Tuareg of the Sahara", dark=True): col = SAND_M if dark else INK_M tb(s, 0.62, 7.06, 9.0, 0.26, [{'text': label, 'size': 9.5, 'color': col, 'font': BODY}]) tb(s, 11.3, 7.03, 1.42, 0.28, [{'text': f"{n:02d} / 12", 'size': 10, 'bold': True, 'color': col, 'font': BODY}], align=PP_ALIGN.RIGHT)

def notes(s, text): s.notes_slide.notes_text_frame.text = text

def imgcard(s, name, x, y, w, img_h, cap, cap_h=0.62, cap_size=10.5): pic(s, name, x, y, w, img_h, frame=CARDLN) tb(s, x, y + img_h + 0.14, w, cap_h, [{'text': cap, 'size': cap_size, 'italic': True, 'color': SAND_M, 'font': BODY, 'line_spacing': 1.15}])

================================================================ 1 · TITLE

s = slide(NIGHT) pic(s, "cover", 0, 0, SW, SH) b = rect(s, 0, 3.3, SW, 4.2); scrim(b, 'bt', 0, 86) t = rect(s, 0, 0, SW, 2.0); scrim(t, 'tb', 0, 62) spc(tb(s, 0.85, 0.6, 9.4, 0.34, [{'text': "READING · SECTION A · THE TUAREG OF THE SAHARA", 'size': 11, 'bold': True, 'color': WHITE, 'font': BODY}]), 240) rect(s, 0.85, 4.12, 0.9, 0.06, fill=OCHRE) tb(s, 0.85, 4.28, 11.0, 1.3, [{'text': "HOME ON THE ROAD", 'size': 60, 'bold': True, 'color': WHITE, 'font': HEAD, 'line_spacing': 0.9}]) tb(s, 0.87, 5.46, 10.6, 0.5, [{'text': "Life on the move with the Tuareg of the Sahara", 'size': 19, 'italic': True, 'color': SAND, 'font': HEAD}]) tb(s, 0.87, 6.18, 11.4, 0.3, [{'runs': [ {'text': "Based on the article by ", 'size': 12, 'color': SAND_M}, {'text': "Steven Jones, travel journalist", 'size': 12, 'bold': True, 'color': SAND}, {'text': " · 12 slides · speaker notes included", 'size': 12, 'color': SAND_M}], 'font': BODY}]) notes(s, "Frame the topic: people with no key and no fixed address. Every quotation that follows comes " "from 'Home on the Road', Section A (The Tuareg of the Sahara) by Steven Jones. The images in " "this deck are AI illustrations of the writer's descriptions, not photos from his trip.")

================================================================ 2 · THE HOOK

s = slide(CREAM) kicker(s, "The big idea", color=OCHRE) heading(s, "Who are the world's nomads?", size=33, color=INK, w=7.6, sub=None) tb(s, 0.62, 1.82, 7.35, 1.5, [ {'text': "Most of us take it for granted that a home has a key and a fixed address.", 'size': 17, 'color': INK, 'font': BODY, 'line_spacing': 1.22}, {'text': "Nomads live the opposite life: their home travels with them.", 'size': 17, 'color': INK_M, 'font': BODY, 'line_spacing': 1.22, 'space_before': 9}]) for i, (num, lab, note) in enumerate([ ("10–40", "million", "estimated nomads around the world"), ("6–7", "cups", "of mint tea a day, the Tuareg rhythm of hospitality"), ("1", "warning", "“the beginning of the end of the Tuareg way of life”")]): y = 3.5 + i * 0.98 rect(s, 0.62, y, 7.35, 0.86, fill=WHITE, line=RULE) rect(s, 0.62, y, 0.09, 0.86, fill=INDIGO) tb(s, 0.95, y + 0.17, 1.95, 0.55, [{'runs': [ {'text': num, 'size': 25, 'bold': True, 'color': INDIGO, 'font': HEAD}, {'text': " " + lab, 'size': 12.5, 'color': INK_M, 'font': BODY}]}]) tb(s, 2.95, y + 0.3, 4.85, 0.4, [{'text': note, 'size': 13, 'color': INK, 'font': BODY}]) rect(s, 8.35, 1.35, 4.38, 5.35, fill=NIGHT2, line=CARDLN, shape=MSO_SHAPE.ROUNDED_RECTANGLE, radius=0.04) tb(s, 8.68, 1.62, 3.8, 0.3, [{'text': "WORDS TO CARRY", 'size': 10.5, 'bold': True, 'color': OCHRE, 'font': BODY}]) for i, (w_, d) in enumerate([("nomad", "a person with no fixed home who moves from place to place"), ("fixed address", "a permanent, unchanging place of residence"), ("way of life", "the ordinary customs and habits of a group"), ("migrate", "to go and live in another place"), ("drought", "a long period with far too little rain"), ("herder", "a person who looks after livestock")]): y = 2.06 + i * 0.76 tb(s, 8.68, y, 3.8, 0.3, [{'text': w_, 'size': 14, 'bold': True, 'color': SAND, 'font': HEAD}]) tb(s, 8.68, y + 0.29, 3.8, 0.44, [{'text': d, 'size': 10.5, 'color': SAND_M, 'font': BODY, 'line_spacing': 1.1}]) footer(s, 2, dark=False) notes(s, "The 10–40 million range is deliberately vague: nomads are hard to count because they are moving " "and because they cross national borders. Warm-up question for the class: what would you have to " "give up to live without a fixed address?")

================================================================ 3 · MEET YOUSSEFF

s = slide(NIGHT) pic(s, "veil", 7.75, 0, 5.5833, 7.5) sc = rect(s, 7.75, 0, 2.6, 7.5); scrim(sc, 'rl', 4, 78) kicker(s, "The writer's surprise") heading(s, "“Imagine my surprise…”", size=30, w=6.7, sub="One figure on a camel undoes an assumption") tb(s, 0.62, 2.32, 6.75, 0.8, [{'text': "Last year the writer travelled to Mali to see the ancient Tuareg " "way of life in the Sahara, a desert of north and west Africa.", 'size': 13, 'color': SAND_M, 'font': BODY, 'line_spacing': 1.2}]) for i, (n, h, t) in enumerate([ ("01", "He saw bright indigo veils", "and took the wearers, naturally, for the women of the tribe."), ("02", "One figure on a camel spoke", "introducing ‘himself’ as Yousseff al-Hamada — a man."), ("03", "The assumption collapsed", "it is the men, not the women, who wear this protection.")]): y = 3.28 + i * 1.0 rect(s, 0.62, y, 6.75, 0.88, fill=CARD, line=CARDLN, lw=0.75) tb(s, 0.85, y + 0.24, 0.7, 0.4, [{'text': n, 'size': 18, 'bold': True, 'color': OCHRE, 'font': HEAD}]) tb(s, 1.6, y + 0.14, 5.6, 0.62, [{'text': h, 'size': 14.5, 'bold': True, 'color': SAND, 'font': BODY}, {'text': t, 'size': 12, 'color': SAND_M, 'space_before': 2}]) tb(s, 8.05, 6.6, 4.9, 0.55, [{'text': "Illustration, after the writer's description — not a " "photograph from Mali.", 'size': 9.5, 'italic': True, 'color': SAND_M, 'font': BODY, 'line_spacing': 1.1}]) footer(s, 3) notes(s, "The turning point of the article. Note the journalist's honesty: he marks his own guess in " "brackets, ‘(I assumed)’, and then lets his host correct him. Good reporting names its bias.")

================================================================ 4 · THE VEIL

s = slide(NIGHT) kicker(s, "The Blue Men of the Sahara") heading(s, "Why the men wear the veil", size=31, w=9.0, sub="Practical first, cultural second") for i, (k, h, t) in enumerate([ ("PROTECTION", "against the harsh sand", "The cloth is a shield from sun, wind and blowing sand " "in one of the driest regions on earth."), ("INDIGO", "the colour that named them", "The bright indigo veils give the Tuareg their nickname: " "the ‘Blue Men of the Sahara’."), ("WHO WEARS IT", "men, not women", "The article's central surprise — an expectation the writer had " "to correct face to face, on camel-back.")]): x = 0.62 + i * 4.16 rect(s, x, 2.25, 3.9, 2.72, fill=CARD, line=CARDLN, shape=MSO_SHAPE.ROUNDED_RECTANGLE, radius=0.05) rect(s, x + 0.3, 2.52, 0.42, 0.05, fill=OCHRE) tb(s, x + 0.3, 2.68, 3.32, 0.3, [{'text': k, 'size': 10.5, 'bold': True, 'color': INDIGO_L, 'font': BODY}]) tb(s, x + 0.3, 3.0, 3.32, 0.66, [{'text': h, 'size': 16.5, 'bold': True, 'color': SAND, 'font': HEAD, 'line_spacing': 1.0}]) tb(s, x + 0.3, 3.74, 3.32, 1.1, [{'text': t, 'size': 11.5, 'color': SAND_M, 'font': BODY, 'line_spacing': 1.2}]) rect(s, 0.62, 5.22, 12.11, 1.5, fill=NIGHT2, line=CARDLN, lw=0.75) tb(s, 0.95, 5.48, 11.45, 1.05, [ {'text': "“…one of these figures on a camel introduced ‘himself’ as Yousseff al-Hamada and explained " "that it's the Tuareg men, not the women, who wear this protection against the harsh sand.”", 'size': 15, 'italic': True, 'color': SAND, 'font': HEAD, 'line_spacing': 1.2}, {'text': "— Home on the Road, Section A", 'size': 10.5, 'color': OCHRE, 'space_before': 7}]) footer(s, 4) notes(s, "Language point: ‘to take someone for’ = to mistakenly believe someone is of a certain kind. " "Discussion: how does a single visible custom get misread by an outsider?")

================================================================ 5 · TEA

s = slide(NIGHT) imgcard(s, "tea", 0.62, 1.9, 4.85, 3.79, "Mint tea: the rhythm the whole conversation runs on. The article notes the Tuareg drink tea " "six or seven times a day.", cap_h=0.8) kicker(s, "Hospitality", x=5.9) heading(s, "First of many cups of mint tea", x=5.9, w=6.8, size=28, sub="Everything the writer learns, he learns sitting down") tb(s, 5.9, 2.36, 6.82, 0.34, [{'text': "What the tea scene tells the reader:", 'size': 12.5, 'color': SAND_M, 'font': BODY}]) for i, (h, t) in enumerate([ ("An unhurried interview", "The meeting is framed as ‘the first of many cups of mint tea’ — " "access is given slowly, over time."), ("Six or seven cups a day", "Tea structures the day and the welcome; it is hospitality as routine."), ("Knowledge passed in person", "Script, poems and songs are demonstrated on the spot, not " "described from a book.")]): y = 2.82 + i * 1.06 rect(s, 5.9, y + 0.06, 0.1, 0.7, fill=OCHRE) tb(s, 6.18, y, 6.5, 0.34, [{'text': h, 'size': 15, 'bold': True, 'color': SAND, 'font': HEAD}]) tb(s, 6.18, y + 0.36, 6.5, 0.6, [{'text': t, 'size': 12, 'color': SAND_M, 'font': BODY, 'line_spacing': 1.18}]) footer(s, 5) notes(s, "Use this slide for register: personal, first-person, slow. Compare with a wire-service report, " "which would open with figures and dates rather than with tea.")

================================================================ 6 · SCRIPT

s = slide(CREAM) rect(s, 0, 0, 0.16, SH, fill=INDIGO) kicker(s, "An ancient script", color=INDIGO) heading(s, "Writing that goes any which way", size=30, color=INK, w=9.2, sub="Yousseff showed the writer the ancient script of the Tuareg's language", sub_color=INK_M) rect(s, 0.62, 2.32, 7.4, 1.5, fill=WHITE, line=RULE) tb(s, 0.95, 2.6, 6.9, 0.4, [{'text': "RIGHT → LEFT · LEFT → RIGHT · TOP → BOTTOM", 'size': 17, 'bold': True, 'color': INDIGO, 'font': HEAD}]) tb(s, 0.95, 3.12, 6.9, 0.6, [{'text': "One writing system, three directions: the script “can be written " "from right to left, left to right or top to bottom.”", 'size': 12, 'color': INK_M, 'font': BODY, 'line_spacing': 1.15}]) tb(s, 0.62, 4.15, 7.4, 2.3, [ {'text': "WHY IT MATTERS IN THE ARTICLE", 'size': 11.5, 'bold': True, 'color': OCHRE, 'space_after': 8}, {'text': "It gives the Tuareg a documented history and literacy — against any idea that an unsettled " "life means no culture of record.", 'size': 13, 'color': INK, 'bullet': INDIGO, 'space_after': 7, 'line_spacing': 1.18}, {'text': "It is shown face to face: with nomads, knowledge travels with people rather than with " "buildings and archives.", 'size': 13, 'color': INK, 'bullet': INDIGO, 'space_after': 7, 'line_spacing': 1.18}, {'text': "“Ancient” is the writer's choice of adjective, and it does real work in persuading us that " "this life has depth.", 'size': 13, 'color': INK, 'bullet': INDIGO, 'line_spacing': 1.18}]) rect(s, 8.35, 2.32, 4.38, 3.95, fill=NIGHT, shape=MSO_SHAPE.ROUNDED_RECTANGLE, radius=0.05) tb(s, 8.68, 2.6, 3.8, 0.3, [{'text': "BEYOND THE TEXT", 'size': 10.5, 'bold': True, 'color': OCHRE, 'font': BODY}]) tb(s, 8.68, 3.0, 3.8, 3.1, [ {'text': "The script the article describes is Tifinagh, used for the Tuareg languages (Tamajaq / " "Tamahaq) and descended from ancient Libyco-Berber letterforms.", 'size': 12, 'color': SAND, 'font': HEAD, 'line_spacing': 1.2, 'space_after': 10}, {'text': "Marked as outside material: the article itself says only “the ancient script of the " "Tuareg's language”. Use it only if your course allows sources beyond the text.", 'size': 10.5, 'color': SAND_M, 'line_spacing': 1.15}]) footer(s, 6) notes(s, "If you use the extra Tifinagh detail, say so out loud and cite it separately. Keeping source and " "commentary apart is part of what the class marks.")

================================================================ 7 · MUSIC & POETRY

s = slide(NIGHT) imgcard(s, "music", 7.6, 1.9, 5.13, 3.59, "“…the men singing traditional poems and the women playing a violin-like instrument…”", cap_h=0.7) kicker(s, "The scene") heading(s, "Poems, songs — and one loaded word", size=28, w=6.7) tb(s, 0.62, 2.4, 6.6, 1.5, [ {'text': "the men", 'size': 13, 'bold': True, 'color': OCHRE, 'runs': [{'text': "the men", 'size': 13, 'bold': True, 'color': OCHRE, 'font': BODY}, {'text': " sing traditional poems", 'size': 14.5, 'color': SAND, 'font': BODY}]}, {'text': "the women", 'size': 13, 'bold': True, 'color': OCHRE, 'space_before': 12, 'runs': [ {'text': "the women", 'size': 13, 'bold': True, 'color': OCHRE, 'font': BODY}, {'text': " play a violin-like instrument", 'size': 14.5, 'color': SAND, 'font': BODY}]}, {'text': "the writer", 'size': 13, 'bold': True, 'color': OCHRE, 'space_before': 12, 'runs': [ {'text': "the writer", 'size': 13, 'bold': True, 'color': OCHRE, 'font': BODY}, {'text': " sits, fascinated, and listens", 'size': 14.5, 'color': SAND, 'font': BODY}]}]) rect(s, 0.62, 4.1, 6.6, 0.05, fill=CARDLN) tb(s, 0.62, 4.36, 6.6, 2.2, [ {'text': "THE PIVOT WORD", 'size': 11, 'bold': True, 'color': INDIGO_L, 'space_after': 6}, {'text': "“idyllic”", 'size': 23, 'bold': True, 'color': SAND, 'font': HEAD, 'space_after': 7}, {'text': "Charmingly simple and peaceful — and the writer undercuts it in the very next breath with " "“Unfortunately”. That single adjective is the hinge of the article: everything before it " "is wonder, everything after it is loss.", 'size': 12.5, 'color': SAND_M, 'font': BODY, 'line_spacing': 1.2}]) footer(s, 7) notes(s, "Language focus: the pivot adjective and the adverb ‘Unfortunately’. Also note the division of " "musical roles the article describes between men and women.")

================================================================ 8 · THEN / NOW

s = slide(CREAM) kicker(s, "How the work changed", color=OCHRE) heading(s, "From caravans to cattle", size=31, color=INK, w=9.0, sub="One sentence in the article carries a whole economic shift", sub_color=INK_M) for i, (k, h, im, q, note) in enumerate([ ("IN THE PAST", "guardians of the trade routes", "caravan", "“…these ‘Blue Men of the Sahara’ protected the camel trading caravans crossing the Sahara " "Desert.”", "Worth came from movement: escorting goods across the desert."), ("NOW", "livestock herders", "drought_crop", "“Now they mostly work as livestock herders.”", "Worth depends on animals — and on rainfall that no longer arrives.")]): x = 0.62 + i * 6.29 rect(s, x, 2.3, 5.85, 4.25, fill=WHITE, line=RULE) rect(s, x, 2.3, 5.85, 0.52, fill=INDIGO if i == 0 else OCHRE) tb(s, x + 0.26, 2.42, 5.3, 0.3, [{'text': k + " · " + h.upper(), 'size': 11.5, 'bold': True, 'color': WHITE, 'font': BODY}]) pic(s, im, x + 0.26, 2.98, 5.33, 1.9) tb(s, x + 0.26, 5.02, 5.33, 0.85, [{'text': q, 'size': 12.5, 'italic': True, 'color': INK, 'font': HEAD, 'line_spacing': 1.18}]) tb(s, x + 0.26, 5.98, 5.33, 0.45, [{'text': note, 'size': 10.5, 'color': INK_M, 'font': BODY, 'line_spacing': 1.12}]) ar = s.shapes.add_shape(MSO_SHAPE.RIGHT_ARROW, Inches(6.28), Inches(4.1), Inches(0.78), Inches(0.5)) ar.shadow.inherit = False; ar.fill.solid(); ar.fill.fore_color.rgb = INDIGO; ar.line.fill.background() footer(s, 8, dark=False) notes(s, "Historical context for the shift: motor transport, closed borders and state customs posts took " "away the caravan-escort trade. Herding was what remained — and herding is the first victim of " "drought.")

================================================================ 9 · THE PRESSURES

s = slide(NIGHT) kicker(s, "The turn of the article") heading(s, "“Unfortunately…”", size=32, w=8.0, sub="Three pressures, one consequence") for i, (h, t) in enumerate([ ("WATER HOLES DRYING UP", "The desert's fixed points — the places that made a route possible at " "all — stop giving water."), ("ANIMALS DYING", "Recent droughts kill the livestock that is now the Tuareg's main work and main " "wealth."), ("MIGRATION TO CITIES", "With the herds gone, families move to towns — trading movement for the " "fixed address the article opened with.")]): y = 2.42 + i * 1.2 rect(s, 0.62, y, 7.5, 1.05, fill=CARD, line=CARDLN, lw=0.75) rect(s, 0.62, y, 0.1, 1.05, fill=OCHRE) tb(s, 0.95, y + 0.14, 6.95, 0.3, [{'text': h, 'size': 12.5, 'bold': True, 'color': INDIGO_L, 'font': BODY}]) tb(s, 0.95, y + 0.48, 6.95, 0.55, [{'text': t, 'size': 12, 'color': SAND, 'font': BODY, 'line_spacing': 1.18}]) imgcard(s, "drought", 8.35, 2.42, 4.38, 2.46, "Drought in the desert edge — the pressure described in " "the article.", cap_h=0.5) rect(s, 8.35, 5.5, 4.38, 1.2, fill=NIGHT2, line=OCHRE, lw=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE, radius=0.07) tb(s, 8.65, 5.7, 3.8, 0.9, [{'text': "CAUSE → EFFECT", 'size': 10, 'bold': True, 'color': OCHRE, 'font': BODY, 'space_after': 4}, {'text': "no water → no animals → no reason to stay → no desert life.", 'size': 12, 'color': SAND, 'font': HEAD, 'line_spacing': 1.15}]) footer(s, 9) notes(s, "Read the sentence as a chain rather than a list: ‘with water holes drying up and animals dying " "because of recent droughts, many Tuareg are now migrating to cities’. Grammar: ‘because of’ + " "noun phrase; ‘migrate to’ + place; present continuous for a change in progress.")

================================================================ 10 · QUOTE

s = slide(NIGHT) pic(s, "drought", 0, 0, SW, SH) ov = rect(s, 0, 0, SW, SH); ov.fill.solid(); ov.fill.fore_color.rgb = RGBColor(0x09, 0x11, 0x1E) set_alpha(ov, 80) rect(s, 0.98, 2.32, 0.09, 2.5, fill=OCHRE) tb(s, 0.62, 1.62, 8.0, 0.35, [{'text': "THE LAST LINE", 'size': 11, 'bold': True, 'color': OCHRE, 'font': BODY}]) tb(s, 1.35, 2.34, 10.6, 2.4, [{'text': "“We are seeing the beginning of the end of the Tuareg " "way of life,”", 'size': 37, 'bold': True, 'color': WHITE, 'font': HEAD, 'line_spacing': 1.05}]) tb(s, 1.35, 4.85, 10.6, 0.45, [{'runs': [ {'text': "Yousseff al-Hamada", 'size': 15, 'bold': True, 'color': SAND, 'font': BODY}, {'text': " — said, in the article, “sadly”", 'size': 15, 'color': SAND_M, 'font': BODY}]}]) tb(s, 1.35, 5.55, 10.5, 0.9, [{'text': "The piece ends on a warning rather than a conclusion. Nothing is " "measured and nothing is proved: the last word belongs to the man who lives it.", 'size': 13, 'italic': True, 'color': SAND_M, 'font': HEAD, 'line_spacing': 1.2}]) footer(s, 10) notes(s, "Rhetorical move to name: closing with a local voice rather than the journalist's summary. Ask " "whether ‘the beginning of the end’ is a prediction, an obituary, or a request for attention.")

================================================================ 11 · RECAP + TALK

s = slide(CREAM) kicker(s, "Recap & response", color=OCHRE) heading(s, "Tell it back in six moves", size=29, color=INK, w=8.4, sub="Cover the article and use the six prompts as a speaking frame", sub_color=INK_M) for i, t in enumerate([ "No key, no fixed address — 10 to 40 million nomads around the world", "A trip to Mali to see the ancient Tuareg way of life in the Sahara", "The surprise: men, not women, wear the indigo veil against the sand", "Mint tea six or seven times a day; a script with three writing directions", "Men sing poems, women play a violin-like instrument — “idyllic”", "Past: guarding caravans → now: herders → cities, because of drought"]): y = 2.5 + i * 0.7 rect(s, 0.62, y, 0.42, 0.42, fill=INDIGO, shape=MSO_SHAPE.ROUNDED_RECTANGLE, radius=0.28) tb(s, 0.62, y + 0.07, 0.42, 0.3, [{'text': str(i + 1), 'size': 13, 'bold': True, 'color': WHITE, 'font': BODY}], align=PP_ALIGN.CENTER) tb(s, 1.22, y + 0.06, 6.35, 0.5, [{'text': t, 'size': 12.5, 'color': INK, 'font': BODY, 'line_spacing': 1.12}]) rect(s, 7.85, 2.42, 4.88, 4.25, fill=NIGHT, shape=MSO_SHAPE.ROUNDED_RECTANGLE, radius=0.045) tb(s, 8.15, 2.68, 4.3, 0.3, [{'text': "TALK ABOUT IT", 'size': 10.5, 'bold': True, 'color': OCHRE, 'font': BODY}]) for i, q in enumerate([ "The writer took the veiled figures for women. What does that say about him rather than about the " "Tuareg?", "Is “idyllic” the right word for a hard desert life? Who has the right to choose it?", "“The beginning of the end” — is a way of life ever lost, or does it only change shape?", "Would you trade a fixed address for that freedom? What exactly would you be giving up?"]): tb(s, 8.15, 3.12 + i * 0.9, 4.32, 0.85, [{'runs': [ {'text': f"Q{i+1} ", 'size': 11, 'bold': True, 'color': OCHRE, 'font': BODY}, {'text': q, 'size': 11.5, 'color': SAND, 'font': BODY}], 'line_spacing': 1.14}]) footer(s, 11, dark=False) notes(s, "Production stage: 90 seconds per question in pairs, then open class. The six-step list also works " "as a written summary task — one sentence per line, no notes allowed.")

================================================================ 12 · SOURCE

s = slide(NIGHT) kicker(s, "Back matter") heading(s, "Source, and what this deck adds", size=27) rect(s, 0.62, 1.85, 7.5, 2.25, fill=CARD, line=CARDLN, lw=0.75) tb(s, 0.95, 2.08, 6.85, 1.9, [ {'text': "SOURCE TEXT", 'size': 10.5, 'bold': True, 'color': OCHRE, 'space_after': 7}, {'text': "“Home on the Road”, Section A: The Tuareg of the Sahara — Steven Jones, travel journalist.", 'size': 13, 'color': SAND, 'font': HEAD, 'line_spacing': 1.2, 'space_after': 8}, {'text': "Every quotation on these slides is taken from that passage. The figures (10–40 million " "nomads, 6–7 cups of tea a day) are the article's own estimates, not independent statistics.", 'size': 11.5, 'color': SAND_M, 'line_spacing': 1.18}]) rect(s, 0.62, 4.3, 7.5, 2.25, fill=NIGHT2, line=CARDLN, lw=0.75) tb(s, 0.95, 4.53, 6.85, 1.9, [ {'text': "WHAT IS NOT IN THE ARTICLE", 'size': 10.5, 'bold': True, 'color': INDIGO_L, 'space_after': 7}, {'text': "The name of the script (Tifinagh), the name of the instrument (imzad), the modern geography " "of Mali, Niger and Algeria, and the causes of the droughts all come from outside the text " "and are marked as such on slide 6.", 'size': 11.5, 'color': SAND, 'line_spacing': 1.18, 'space_after': 7}, {'text': "All images are AI-generated illustrations of the writer's descriptions — not documentary " "photographs of Yousseff al-Hamada or of the trip to Mali.", 'size': 11.5, 'color': SAND_M, 'line_spacing': 1.18}]) rect(s, 8.4, 1.85, 4.33, 4.7, fill=CARD, line=CARDLN, lw=0.75) tb(s, 8.72, 2.1, 3.8, 0.3, [{'text': "PRONUNCIATION", 'size': 10.5, 'bold': True, 'color': OCHRE, 'font': BODY}]) for i, (w_, p_) in enumerate([("Tuareg", "TWAH-reg"), ("Sahara", "sah-HAH-rah"), ("indigo", "IN-di-go"), ("Yousseff al-Hamada", "YOOS-eff al ha-MAH-da"), ("idyllic", "ih-DIL-ik"), ("livestock", "LIVE-stok"), ("drought", "DROWT, rhymes with ‘out’")]): y = 2.5 + i * 0.65 tb(s, 8.72, y, 3.85, 0.28, [{'text': w_, 'size': 12.5, 'bold': True, 'color': SAND, 'font': HEAD}]) tb(s, 8.72, y + 0.27, 3.85, 0.26, [{'text': p_, 'size': 10.5, 'italic': True, 'color': SAND_M, 'font': BODY}]) footer(s, 12) notes(s, "Keep this slide if the deck is submitted: it separates what the article says from what the " "presentation adds, and it declares every image as an illustration.")

out = os.path.join(HERE, "..", "Home_on_the_Road_Tuareg.pptx") prs.save(out) print("saved", os.path.abspath(out), "| slides:", len(prs.slides._sldIdLst))