#!/usr/bin/env python3
"""
Fibo – Logistics Deck v2
Hebrew RTL, matching investor deck design system (Heebo font, #1565C0 blue)
Changes from v1:
  - All text RIGHT aligned (proper Hebrew RTL)
  - Removed thick left bar → thin frame (left + right + bottom)
  - Removed kicker boxes and OFF_WHITE fills
  - Blue: #1565C0 (RGB 21,101,192)
  - Font: Heebo
  - Title: 48pt bold, right-aligned
  - Blue rule below title: right-aligned
  - Step circles: keep LTR flow (01→05), text below RIGHT aligned
  - Comparison table: RTL column order
"""

import json
import time
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

# ── Auth ──────────────────────────────────────────────────────────────────────
with open('/root/.config/gws/credentials.json') as f:
    creds_data = json.load(f)
if 'installed' in creds_data:
    creds_data = creds_data['installed']

creds = Credentials(
    token=None,
    refresh_token=creds_data['refresh_token'],
    token_uri='https://oauth2.googleapis.com/token',
    client_id=creds_data['client_id'],
    client_secret=creds_data['client_secret'],
    scopes=['https://www.googleapis.com/auth/presentations', 'https://www.googleapis.com/auth/drive']
)
service = build('slides', 'v1', credentials=creds)

# ── Palette (matching investor deck) ─────────────────────────────────────────
#  #1565C0 = RGB(21, 101, 192) → normalized: r=0.082, g=0.396, b=0.753
BLUE       = {'red': 0.082, 'green': 0.396, 'blue': 0.753}
WHITE      = {'red': 1.0,   'green': 1.0,   'blue': 1.0}
BLACK      = {'red': 0.102, 'green': 0.102, 'blue': 0.102}  # #1A1A1A
DARK_GRAY  = {'red': 0.259, 'green': 0.259, 'blue': 0.259}  # #424242
MID_GRAY   = {'red': 0.600, 'green': 0.600, 'blue': 0.600}  # #999999
LIGHT_GRAY = {'red': 0.878, 'green': 0.878, 'blue': 0.878}  # #E0E0E0

# ── EMU helpers ───────────────────────────────────────────────────────────────
W = 9144000   # Slide width EMU (16:9 standard)
H = 5143500   # Slide height EMU
MARGIN = 457200  # 0.5 inch left/right margin

def cm(c):    return int(c * 360000)
def pt(p):    return int(p * 12700)

def sz(w, h): return {'width': {'magnitude': w, 'unit': 'EMU'}, 'height': {'magnitude': h, 'unit': 'EMU'}}
def tr(x, y): return {'scaleX': 1, 'scaleY': 1, 'translateX': x, 'translateY': y, 'unit': 'EMU'}

# Color helpers
def rgb(c):    return {'rgbColor': c}                          # for solidFill.color
def rgb_fg(c): return {'opaqueColor': {'rgbColor': c}}         # for text foregroundColor

# ── Core drawing primitives ───────────────────────────────────────────────────

def text_box(reqs, sid, oid, text, x, y, w, h,
             font_size=16, bold=False, italic=False,
             color=None, align='END', font='Heebo',
             rtl=True):
    """
    Create a text box with Hebrew RTL settings.
    align='END' = right-aligned (correct for Hebrew).
    """
    if color is None: color = BLACK
    reqs.append({'createShape': {
        'objectId': oid, 'shapeType': 'TEXT_BOX',
        'elementProperties': {'pageObjectId': sid, 'size': sz(w, h), 'transform': tr(x, y)}
    }})
    reqs.append({'insertText': {'objectId': oid, 'text': text, 'insertionIndex': 0}})
    reqs.append({'updateTextStyle': {
        'objectId': oid,
        'style': {
            'bold': bold, 'italic': italic,
            'fontSize': {'magnitude': font_size, 'unit': 'PT'},
            'foregroundColor': rgb_fg(color),
            'fontFamily': font,
        },
        'fields': 'bold,italic,fontSize,foregroundColor,fontFamily',
        'textRange': {'type': 'ALL'}
    }})
    dir_val = 'RIGHT_TO_LEFT' if rtl else 'LEFT_TO_RIGHT'
    reqs.append({'updateParagraphStyle': {
        'objectId': oid,
        'style': {'alignment': align, 'direction': dir_val},
        'fields': 'alignment,direction',
        'textRange': {'type': 'ALL'}
    }})


def rect(reqs, sid, oid, x, y, w, h, fill, border=None):
    """Filled rectangle. border=color or None for matching fill."""
    reqs.append({'createShape': {
        'objectId': oid, 'shapeType': 'RECTANGLE',
        'elementProperties': {'pageObjectId': sid, 'size': sz(w, h), 'transform': tr(x, y)}
    }})
    b_color = border if border else fill
    reqs.append({'updateShapeProperties': {
        'objectId': oid,
        'shapeProperties': {
            'shapeBackgroundFill': {'solidFill': {'color': rgb(fill)}},
            'outline': {
                'outlineFill': {'solidFill': {'color': rgb(b_color)}},
                'weight': {'magnitude': 1, 'unit': 'PT'}
            }
        },
        'fields': 'shapeBackgroundFill,outline'
    }})


def ellipse(reqs, sid, oid, x, y, w, h, fill):
    """Filled ellipse/circle."""
    reqs.append({'createShape': {
        'objectId': oid, 'shapeType': 'ELLIPSE',
        'elementProperties': {'pageObjectId': sid, 'size': sz(w, h), 'transform': tr(x, y)}
    }})
    reqs.append({'updateShapeProperties': {
        'objectId': oid,
        'shapeProperties': {
            'shapeBackgroundFill': {'solidFill': {'color': rgb(fill)}},
            'outline': {
                'outlineFill': {'solidFill': {'color': rgb(fill)}},
                'weight': {'magnitude': 1, 'unit': 'PT'}
            }
        },
        'fields': 'shapeBackgroundFill,outline'
    }})


def slide_bg(reqs, sid, color=None):
    """Set slide background to white (default)."""
    if color is None: color = WHITE
    reqs.append({'updatePageProperties': {
        'objectId': sid,
        'pageProperties': {'pageBackgroundFill': {'solidFill': {'color': rgb(color)}}},
        'fields': 'pageBackgroundFill'
    }})


def add_frame(reqs, sid, idx):
    """
    Add the three-sided frame: left, right, bottom (no top).
    Matches investor deck style.
    """
    thickness = pt(5)
    # Left
    rect(reqs, sid, f'{sid}_fl', 0, 0, thickness, H, BLUE)
    # Right
    rect(reqs, sid, f'{sid}_fr', W - thickness, 0, thickness, H, BLUE)
    # Bottom
    rect(reqs, sid, f'{sid}_fb', 0, H - thickness, W, thickness, BLUE)


def add_slide_number(reqs, sid, num):
    """Slide number bottom-right, small gray."""
    text_box(reqs, sid, f'{sid}_snum', f'{num:02d}',
             W - cm(3), H - cm(1.6), cm(2.5), cm(1.2),
             font_size=13, color=MID_GRAY, align='END', rtl=False)


def add_title(reqs, sid, title, y_top=cm(0.8)):
    """
    Title: 48pt bold, RIGHT-aligned in full-width box.
    Blue underline rule on the RIGHT side (RTL convention).
    """
    # Title text — full width, right-aligned
    text_box(reqs, sid, f'{sid}_title', title,
             MARGIN, y_top, W - 2 * MARGIN, pt(65),
             font_size=48, bold=True, color=BLACK, align='END')
    # Blue rule — right-aligned, ~22% of slide width, under title
    rule_w = int(W * 0.22)
    rule_y = y_top + pt(60) + cm(0.15)
    rect(reqs, sid, f'{sid}_rule',
         W - MARGIN - rule_w, rule_y,
         rule_w, pt(4), BLUE)


# ── Slide content definitions ─────────────────────────────────────────────────
SLIDES = [
    {'id': 'slide_01', 'num': 1,  'type': 'cover',      'title': 'החבילה יודעת איפה היא.',
     'subtitle': 'תמיד.', 'tagline': 'FiBo — מעקב לוגיסטי פסיבי בזמן אמת', 'date': 'מרץ 2026'},

    {'id': 'slide_02', 'num': 2,  'type': 'bullets',    'title': 'הלוגיסטיקה פועלת על סריקות ידניות',
     'points': [
         'כל חבילה שלא נסרקת — היא כתם עיוור במערכת',
         '1%–3% מהחבילות אובדות או מתעכבות מדי שנה',
         'המייל האחרון הוא היקר ביותר — וגם הכי פחות נראה',
         'תורות, טעויות אנוש, ואחריות שנעלמת בין ידיים',
     ],
     'kicker': 'כל סריקה שלא בוצעה — פרצה במידע, עיכוב בלקוח, ועלות שמישהו משלם.'},

    {'id': 'slide_03', 'num': 3,  'type': 'stats',      'title': 'שוק שמחכה לפתרון',
     'stats': [('$500B+', 'שוק הלוגיסטיקה העולמי'), ('1–3%', 'חבילות אבודות / מאוחרות'),
               ('+$700M', 'הפסד שנתי — ניו יורק'), ('€700M', 'הפסד שנתי — פריז')],
     'body': 'מרבית מערכות המעקב הקיימות מבוססות על ברקוד או NFC קצר טווח. כל פעולה שלא בוצעה — נתון שאבד.'},

    {'id': 'slide_04', 'num': 4,  'type': 'bullets',    'title': 'מדבקת FiBo על כל חבילה.',
     'points': [
         'פרוטוקול MFC (Medium Field Communication) — טווח 50 ס״מ',
         'פסיבי לחלוטין — אין צורך בסריקה, בלחיצה, בפעולה כלשהי',
         'מוצפן ברמת מערכת — פועל גם ללא אינטרנט',
         'API פתוח — משתלב ישירות עם WMS / ERP קיים',
     ],
     'kicker': 'החבילה נכנסת לטווח האנטנה → המערכת מזהה, מתעדת, מעדכנת. אפס חיכוך.'},

    {'id': 'slide_05', 'num': 5,  'type': 'steps',      'title': 'ככה זה עובד',
     'steps': [
         ('01', 'מדבקת FiBo',     'מודבקת על החבילה — פסיבית, קטנה, ללא סוללה'),
         ('02', 'אנטנת זיהוי',    'מותקנת בנקודות ביקורת — מחסן, רציף, רכב, מייל אחרון'),
         ('03', 'זיהוי אוטומטי',  'החבילה עוברת בטווח 50 ס״מ → אירוע נרשם אוטומטית'),
         ('04', 'שרת מקומי',      'מעבד נתונים locally, ללא תלות ברשת חיצונית'),
         ('05', 'דשבורד + API',   'נתוני מיקום בזמן אמת → WMS, ERP, וכל מערכת קיימת'),
     ]},

    {'id': 'slide_06', 'num': 6,  'type': 'bullets',    'title': 'מה אתם רואים',
     'points': [
         'כל חבילה, כל מיקום, היסטוריית מסלול מלאה',
         'התראות חריגה — עיכוב, סטייה ממסלול, עצירה',
         'ניתוח דפוסי תנועה לשיפור תהליכים',
         'אינטגרציה מלאה עם מערכות קיימות דרך API פתוח',
     ],
     'kicker': 'ממשק אחד. מידע שלם. בזמן אמת.'},

    {'id': 'slide_07', 'num': 7,  'type': 'comparison', 'title': 'FiBo מול החלופות',
     # RTL column order: criterion on RIGHT → FiBo on LEFT (most favorable)
     'header': ('FiBo', 'שער RFID', 'GPS', 'ברקוד', 'קריטריון'),
     'rows': [
         ('✓', '✗', '✗', '✗', 'פסיבי — ללא פעולה'),
         ('✓', '✓', '✗', '✓', 'ללא סוללה בתג'),
         ('✓', '✗', '✓', '✓', 'ללא שערים יקרים'),
         ('✓', '✓', '✗', '✗', 'טווח 50 ס״מ בתנועה'),
         ('✓', '✗', '✗', '✓', 'עובד offline'),
         ('✓', 'חלקי', 'חלקי', 'חלקי', 'API פתוח'),
     ]},

    {'id': 'slide_08', 'num': 8,  'type': 'cards',      'title': 'איפה FiBo עובד',
     'cards': [
         ('🏭', 'מחסן',          'כניסה / יציאה אוטומטית, ספירת מלאי בזמן אמת'),
         ('🚚', 'הובלה',         'עדכוני טעינה / פריקה ללא נהג, ניטור רכב'),
         ('📦', 'מייל אחרון',    'אישור מסירה אוטומטי, ללא חתימה דיגיטלית'),
         ('❄️', 'שרשרת קרה',    'תיעוד כל נקודת מעבר לצרכי תאימות'),
         ('🔁', 'החזרות',        'קבלה ומיון אוטומטיים — בלי צוואר בקבוק'),
         ('💎', 'סחורה יקרת ערך', 'ניטור צמוד, התראות חריגה בזמן אמת'),
     ]},

    {'id': 'slide_09', 'num': 9,  'type': 'bullets',    'title': 'הטכנולוגיה כבר בשטח',
     'points': [
         'פיילוט פעיל עם חברת דן — תיקוף אוטומטי של נסיעות בתחבורה ציבורית',
         'פריסה מבצעית עם צבא ההגנה לישראל וחיל הרפואה',
         'בפיתוח מתקדם עם שותפים בתשתיות חכמות ומתקני תעשייה',
         'הפרוטוקול פותח מתוך דרישה מבצעית אמיתית — לא POC מעבדתי',
     ],
     'kicker': 'הטכנולוגיה שעובדת בתוך אוטובוס צפוף — תעבוד גם במחסן שלכם.'},

    {'id': 'slide_10', 'num': 10, 'type': 'table',      'title': 'מודל עסקי',
     'header': ('הערות', 'תמחור', 'רכיב'),
     'rows': [
         ('כולל תמיכה ורישיון', '₪300K הטמעה + ₪500K/שנה', 'מערכת FiBo'),
         ('תחזוקה כלולה',       '₪2,500/שנה + ₪1,000 התקנה', 'אנטנת FiBo'),
         ('עצמאית, ללא אינטרנט', '₪10,000/שנה', 'מערכת הדפסת תגים'),
     ],
     'kicker': 'מחיר קבוע לפי נפח — ללא עמלה לסריקה, ללא הפתעות.'},

    {'id': 'slide_11', 'num': 11, 'type': 'team',       'title': 'הצוות',
     'members': [
         ('אור מרכוס',  'מנכ״ל ומייסד משותף',
          'קצין פעיל לשעבר, מנהל מוצר ביחידות 8200 וממר״ם.\nהוביל פרויקטים טכנולוגיים בקנה מידה ארצי.'),
         ('ניב בייביץ', 'סמנכ״ל טכנולוגיות ומייסד משותף',
          'מומחה ארכיטקטורת סייבר, יחידה 108.\nהקים בית תוכנה וניהל מאות עובדים.'),
     ],
     'note': 'שיתוף פעולה עם תוכנית המצוינות "מפתחים" — כוח אדם איכותי וגמיש.'},

    {'id': 'slide_12', 'num': 12, 'type': 'cta',        'title': 'בואו נתחיל בפיילוט',
     'offer': [
         'הגדרת נקודות ביקורת — מחסן, רציף, רכב',
         'התקנת אנטנות ב-X מיקומים',
         '90 יום — נמדוד ביחד: כיסוי, דיוק, זמן תגובה',
         'לאחר הפיילוט: מסלול פריסה מלא',
     ],
     'kicker': 'הטכנולוגיה מוכנה. השוק מוכן. השאלה היחידה: כמה חבילות אבדו לכם החודש?'},
]

# ── Slide builders ────────────────────────────────────────────────────────────

def build_cover(reqs, data):
    sid = data['id']
    slide_bg(reqs, sid)
    add_frame(reqs, sid, data['num'])
    add_slide_number(reqs, sid, data['num'])

    # Main title — large, right-aligned
    text_box(reqs, sid, f'{sid}_t1', data['title'],
             MARGIN, cm(3.2), W - 2*MARGIN, pt(75),
             font_size=54, bold=True, color=BLACK, align='END')
    # Subtitle in accent blue
    text_box(reqs, sid, f'{sid}_t2', data['subtitle'],
             MARGIN, cm(6.5), W - 2*MARGIN, pt(75),
             font_size=54, bold=True, color=BLUE, align='END')
    # Blue rule below subtitle
    rule_w = int(W * 0.22)
    rect(reqs, sid, f'{sid}_rule',
         W - MARGIN - rule_w, cm(9.2), rule_w, pt(4), BLUE)
    # Tagline
    text_box(reqs, sid, f'{sid}_tag', data['tagline'],
             MARGIN, cm(9.6), W - 2*MARGIN, pt(30),
             font_size=20, color=DARK_GRAY, align='END')
    # Date
    text_box(reqs, sid, f'{sid}_date', data['date'],
             MARGIN, cm(11.2), W - 2*MARGIN, pt(20),
             font_size=14, color=MID_GRAY, align='END')


def build_bullets(reqs, data):
    sid = data['id']
    slide_bg(reqs, sid)
    add_frame(reqs, sid, data['num'])
    add_slide_number(reqs, sid, data['num'])
    add_title(reqs, sid, data['title'])

    content_y = cm(0.8) + pt(65) + cm(0.4) + pt(4) + cm(0.5)

    for j, point in enumerate(data.get('points', [])):
        text_box(reqs, sid, f'{sid}_p{j}', f'● {point}',
                 MARGIN, content_y + j * pt(30), W - 2*MARGIN, pt(28),
                 font_size=18, color=BLACK, align='END')

    if 'kicker' in data:
        kicker_y = H - cm(2.5) - pt(5)
        # Thin blue rule above kicker
        rect(reqs, sid, f'{sid}_kr', MARGIN, kicker_y - cm(0.2),
             W - 2*MARGIN, pt(2), LIGHT_GRAY)
        text_box(reqs, sid, f'{sid}_kicker', data['kicker'],
                 MARGIN, kicker_y, W - 2*MARGIN, pt(28),
                 font_size=16, italic=True, color=BLUE, align='END')


def build_stats(reqs, data):
    sid = data['id']
    slide_bg(reqs, sid)
    add_frame(reqs, sid, data['num'])
    add_slide_number(reqs, sid, data['num'])
    add_title(reqs, sid, data['title'])

    stats = data['stats']
    stat_w = (W - 2*MARGIN) // 4
    stat_y = cm(0.8) + pt(65) + cm(0.7)

    for j, (num, label) in enumerate(stats):
        x = MARGIN + j * stat_w
        # Vertical divider (not first)
        if j > 0:
            rect(reqs, sid, f'{sid}_div{j}', x, stat_y, pt(1), pt(60) + cm(0.5), LIGHT_GRAY)
        text_box(reqs, sid, f'{sid}_sn{j}', num,
                 x, stat_y, stat_w - cm(0.3), pt(55),
                 font_size=40, bold=True, color=BLUE, align='END')
        text_box(reqs, sid, f'{sid}_sl{j}', label,
                 x, stat_y + pt(58), stat_w - cm(0.3), pt(25),
                 font_size=15, color=DARK_GRAY, align='END')

    if 'body' in data:
        body_y = stat_y + pt(60) + cm(0.5) + cm(0.4)
        # Light rule
        rect(reqs, sid, f'{sid}_br', MARGIN, body_y - cm(0.2),
             W - 2*MARGIN, pt(1), LIGHT_GRAY)
        text_box(reqs, sid, f'{sid}_body', data['body'],
                 MARGIN, body_y, W - 2*MARGIN, pt(45),
                 font_size=15, color=DARK_GRAY, align='END')


def build_steps(reqs, data):
    """Slide 05 — kkha ze oved. Circles flow LTR (sequence), text RIGHT aligned."""
    sid = data['id']
    slide_bg(reqs, sid)
    add_frame(reqs, sid, data['num'])
    add_slide_number(reqs, sid, data['num'])
    add_title(reqs, sid, data['title'])

    steps = data['steps']
    n = len(steps)
    # Distribute 5 circles evenly across slide width
    # circle center x positions: 12%, 31%, 50%, 69%, 88% of W (matching investor deck)
    cx_pcts = [0.12, 0.31, 0.50, 0.69, 0.88]
    circle_d = cm(1.7)      # ~75px on 1920 slide
    circle_y = cm(4.5)      # Y of circle top
    name_y   = circle_y + circle_d + cm(0.4)
    desc_y   = name_y + pt(28)

    # Horizontal connecting line behind circles
    line_y = circle_y + circle_d // 2 - pt(1)
    rect(reqs, sid, f'{sid}_hline',
         int(W * cx_pcts[0]), line_y,
         int(W * (cx_pcts[-1] - cx_pcts[0])), pt(2),
         LIGHT_GRAY)

    col_half = cm(2.5)  # half-width of each text column

    for j, (num, name, desc) in enumerate(steps):
        cx = int(W * cx_pcts[j])
        ox = cx - circle_d // 2

        # Circle
        ellipse(reqs, sid, f'{sid}_c{j}', ox, circle_y, circle_d, circle_d, BLUE)
        # Number inside circle (LTR, centered)
        text_box(reqs, sid, f'{sid}_cn{j}', num,
                 ox, circle_y + pt(8), circle_d, circle_d - pt(12),
                 font_size=22, bold=True, color=WHITE, align='CENTER', rtl=False)

        # Arrow between circles
        if j < n - 1:
            ax = int(W * (cx_pcts[j] + cx_pcts[j+1]) / 2) - pt(8)
            text_box(reqs, sid, f'{sid}_arr{j}', '→',
                     ax, circle_y + pt(14), pt(24), pt(24),
                     font_size=18, color=MID_GRAY, align='CENTER', rtl=False)

        # Step name — RIGHT aligned
        text_box(reqs, sid, f'{sid}_nm{j}', name,
                 cx - col_half, name_y, col_half * 2, pt(28),
                 font_size=18, bold=True, color=BLACK, align='END')
        # Step description — RIGHT aligned
        text_box(reqs, sid, f'{sid}_ds{j}', desc,
                 cx - col_half, desc_y, col_half * 2, pt(55),
                 font_size=13, color=DARK_GRAY, align='END')


def build_comparison(reqs, data):
    """RTL comparison table: criteria on RIGHT, FiBo (best) on LEFT."""
    sid = data['id']
    slide_bg(reqs, sid)
    add_frame(reqs, sid, data['num'])
    add_slide_number(reqs, sid, data['num'])
    add_title(reqs, sid, data['title'])

    header = data['header']  # (FiBo, RFID, GPS, Barcode, Criterion) RTL order
    rows = data['rows']
    n_cols = len(header)

    # Column widths: criterion (rightmost) wider, others equal
    crit_w   = cm(4.5)
    data_w   = (W - 2*MARGIN - crit_w) // (n_cols - 1)
    row_h    = pt(36)
    table_y  = cm(0.8) + pt(65) + cm(0.5)

    # Column x positions (left to right): FiBo, RFID, GPS, Barcode, Criterion
    col_x = [MARGIN + i * data_w for i in range(n_cols - 1)] + [W - MARGIN - crit_w]

    # Header row
    rect(reqs, sid, f'{sid}_hbg', MARGIN, table_y, W - 2*MARGIN, row_h, BLUE)
    for ci, (label, x) in enumerate(zip(header, col_x)):
        cw = crit_w if ci == n_cols - 1 else data_w
        # FiBo column header = bold white, others = white
        b = (ci == 0)
        text_box(reqs, sid, f'{sid}_h{ci}', label,
                 x, table_y + pt(3), cw, row_h - pt(4),
                 font_size=14, bold=True, color=WHITE, align='END')

    # Data rows
    for ri, row in enumerate(rows):
        y = table_y + (ri + 1) * row_h
        if ri % 2 == 0:
            rect(reqs, sid, f'{sid}_rbg{ri}', MARGIN, y, W - 2*MARGIN, row_h,
                 {'red': 0.94, 'green': 0.95, 'blue': 0.98})  # very light blue tint
        for ci, (cell, x) in enumerate(zip(row, col_x)):
            cw = crit_w if ci == n_cols - 1 else data_w
            # FiBo ✓ in blue, ✗ in gray, text in black
            if ci == 0:
                c = BLUE if cell == '✓' else (MID_GRAY if cell == '✗' else BLACK)
                b = (cell == '✓')
            elif ci == n_cols - 1:  # criterion column
                c = BLACK
                b = False
            else:
                c = DARK_GRAY if cell == '✓' else MID_GRAY
                b = False
            text_box(reqs, sid, f'{sid}_r{ri}c{ci}', cell,
                     x, y + pt(3), cw, row_h - pt(4),
                     font_size=14, bold=b, color=c, align='END')


def build_cards(reqs, data):
    """6 use-case cards in 3×2 grid."""
    sid = data['id']
    slide_bg(reqs, sid)
    add_frame(reqs, sid, data['num'])
    add_slide_number(reqs, sid, data['num'])
    add_title(reqs, sid, data['title'])

    cards = data['cards']
    card_w = (W - 2*MARGIN - cm(0.6)) // 3
    card_h = cm(3.0)
    gap    = cm(0.3)
    start_y = cm(0.8) + pt(65) + cm(0.5)

    for j, (icon, name, desc) in enumerate(cards):
        col = j % 3
        row = j // 3
        x = MARGIN + col * (card_w + gap)
        y = start_y + row * (card_h + gap)

        # Card border only (no fill — clean)
        reqs.append({'createShape': {
            'objectId': f'{sid}_cbg{j}', 'shapeType': 'RECTANGLE',
            'elementProperties': {'pageObjectId': sid,
                                  'size': sz(card_w, card_h),
                                  'transform': tr(x, y)}
        }})
        reqs.append({'updateShapeProperties': {
            'objectId': f'{sid}_cbg{j}',
            'shapeProperties': {
                'shapeBackgroundFill': {'solidFill': {'color': rgb(WHITE)}},
                'outline': {
                    'outlineFill': {'solidFill': {'color': rgb(LIGHT_GRAY)}},
                    'weight': {'magnitude': 1, 'unit': 'PT'}
                }
            },
            'fields': 'shapeBackgroundFill,outline'
        }})

        # Icon
        text_box(reqs, sid, f'{sid}_ci{j}', icon,
                 x + cm(0.3), y + cm(0.25), cm(1.2), cm(1.0),
                 font_size=22, align='CENTER', rtl=False)
        # Card name
        text_box(reqs, sid, f'{sid}_cn{j}', name,
                 x + cm(0.3), y + cm(0.25), card_w - cm(0.6), cm(0.9),
                 font_size=16, bold=True, color=BLACK, align='END')
        # Description
        text_box(reqs, sid, f'{sid}_cd{j}', desc,
                 x + cm(0.3), y + cm(1.3), card_w - cm(0.6), cm(1.5),
                 font_size=13, color=DARK_GRAY, align='END')


def build_table(reqs, data):
    """Pricing / model table."""
    sid = data['id']
    slide_bg(reqs, sid)
    add_frame(reqs, sid, data['num'])
    add_slide_number(reqs, sid, data['num'])
    add_title(reqs, sid, data['title'])

    header = data['header']  # RTL: notes | price | component
    rows   = data['rows']
    n_cols = len(header)
    col_w  = (W - 2*MARGIN) // n_cols
    row_h  = pt(46)
    table_y = cm(0.8) + pt(65) + cm(0.5)

    col_x = [MARGIN + i * col_w for i in range(n_cols)]

    # Header
    rect(reqs, sid, f'{sid}_hbg', MARGIN, table_y, W - 2*MARGIN, row_h, BLUE)
    for ci, (label, x) in enumerate(zip(header, col_x)):
        text_box(reqs, sid, f'{sid}_h{ci}', label,
                 x + cm(0.2), table_y + pt(5), col_w - cm(0.4), row_h - pt(8),
                 font_size=15, bold=True, color=WHITE, align='END')

    for ri, row in enumerate(rows):
        y = table_y + (ri + 1) * row_h
        if ri % 2 == 0:
            rect(reqs, sid, f'{sid}_rbg{ri}', MARGIN, y, W - 2*MARGIN, row_h,
                 {'red': 0.94, 'green': 0.95, 'blue': 0.98})
        for ci, (cell, x) in enumerate(zip(row, col_x)):
            c = BLUE if ci == 1 else BLACK  # price column in blue
            text_box(reqs, sid, f'{sid}_r{ri}c{ci}', cell,
                     x + cm(0.2), y + pt(5), col_w - cm(0.4), row_h - pt(8),
                     font_size=14, color=c, align='END')

    if 'kicker' in data:
        kicker_y = table_y + (len(rows) + 1) * row_h + cm(0.4)
        text_box(reqs, sid, f'{sid}_kicker', data['kicker'],
                 MARGIN, kicker_y, W - 2*MARGIN, pt(25),
                 font_size=15, italic=True, color=BLUE, align='END')


def build_team(reqs, data):
    sid = data['id']
    slide_bg(reqs, sid)
    add_frame(reqs, sid, data['num'])
    add_slide_number(reqs, sid, data['num'])
    add_title(reqs, sid, data['title'])

    members = data['members']
    card_w = (W - 2*MARGIN - cm(1)) // 2
    start_y = cm(0.8) + pt(65) + cm(0.6)

    for j, (name, role, bio) in enumerate(members):
        x = MARGIN + j * (card_w + cm(1))
        # Avatar circle
        ellipse(reqs, sid, f'{sid}_av{j}',
                x + card_w - cm(1.8), start_y, cm(1.6), cm(1.6), BLUE)
        text_box(reqs, sid, f'{sid}_avi{j}', name[0],
                 x + card_w - cm(1.8), start_y + pt(4), cm(1.6), cm(1.6) - pt(8),
                 font_size=22, bold=True, color=WHITE, align='CENTER', rtl=False)
        # Name
        text_box(reqs, sid, f'{sid}_nm{j}', name,
                 x, start_y, card_w - cm(2), cm(0.9),
                 font_size=22, bold=True, color=BLACK, align='END')
        # Role
        text_box(reqs, sid, f'{sid}_rl{j}', role,
                 x, start_y + cm(1.0), card_w, cm(0.8),
                 font_size=15, color=BLUE, align='END')
        # Thin divider
        rect(reqs, sid, f'{sid}_div{j}', x, start_y + cm(2.0), card_w, pt(1), LIGHT_GRAY)
        # Bio
        text_box(reqs, sid, f'{sid}_bio{j}', bio,
                 x, start_y + cm(2.2), card_w, cm(2.5),
                 font_size=14, color=DARK_GRAY, align='END')

    if 'note' in data:
        note_y = start_y + cm(4.8)
        rect(reqs, sid, f'{sid}_nr', MARGIN, note_y - cm(0.15), W - 2*MARGIN, pt(1), LIGHT_GRAY)
        text_box(reqs, sid, f'{sid}_note', data['note'],
                 MARGIN, note_y, W - 2*MARGIN, pt(25),
                 font_size=14, color=MID_GRAY, align='END')


def build_cta(reqs, data):
    sid = data['id']
    slide_bg(reqs, sid)
    add_frame(reqs, sid, data['num'])
    add_slide_number(reqs, sid, data['num'])
    add_title(reqs, sid, data['title'])

    offer_y = cm(0.8) + pt(65) + cm(0.5)
    for j, point in enumerate(data['offer']):
        # Blue bullet circle
        ellipse(reqs, sid, f'{sid}_bc{j}',
                W - MARGIN - cm(0.5), offer_y + j * pt(36) + pt(6),
                cm(0.5), cm(0.5), BLUE)
        text_box(reqs, sid, f'{sid}_o{j}', point,
                 MARGIN, offer_y + j * pt(36), W - MARGIN - cm(0.8), pt(30),
                 font_size=18, color=BLACK, align='END')

    # Kicker — full-width blue box
    kicker_y = offer_y + len(data['offer']) * pt(36) + cm(0.5)
    rect(reqs, sid, f'{sid}_kbg', MARGIN, kicker_y, W - 2*MARGIN, pt(55), BLUE)
    text_box(reqs, sid, f'{sid}_kicker', data['kicker'],
             MARGIN + cm(0.4), kicker_y + pt(8), W - 2*MARGIN - cm(0.8), pt(40),
             font_size=16, bold=True, color=WHITE, align='END')


# ── Slide router ──────────────────────────────────────────────────────────────
def build_slide_content(reqs, data):
    t = data['type']
    if   t == 'cover':      build_cover(reqs, data)
    elif t == 'bullets':    build_bullets(reqs, data)
    elif t == 'stats':      build_stats(reqs, data)
    elif t == 'steps':      build_steps(reqs, data)
    elif t == 'comparison': build_comparison(reqs, data)
    elif t == 'cards':      build_cards(reqs, data)
    elif t == 'table':      build_table(reqs, data)
    elif t == 'team':       build_team(reqs, data)
    elif t == 'cta':        build_cta(reqs, data)


# ── Main builder ──────────────────────────────────────────────────────────────
def build():
    # Create presentation
    pres = service.presentations().create(body={
        'title': 'Fibo – Logistics | מעקב חבילות v2'
    }).execute()
    pid = pres['presentationId']
    default_sid = pres['slides'][0]['objectId']
    print(f"Created: https://docs.google.com/presentation/d/{pid}/edit")

    # Delete default slide
    service.presentations().batchUpdate(presentationId=pid,
        body={'requests': [{'deleteObject': {'objectId': default_sid}}]}
    ).execute()
    time.sleep(0.3)

    for i, data in enumerate(SLIDES):
        sid = data['id']
        # Create the slide
        service.presentations().batchUpdate(presentationId=pid, body={'requests': [
            {'createSlide': {
                'objectId': sid,
                'insertionIndex': i,
                'slideLayoutReference': {'predefinedLayout': 'BLANK'}
            }}
        ]}).execute()
        time.sleep(0.25)

        # Build content
        reqs = []
        build_slide_content(reqs, data)
        if reqs:
            service.presentations().batchUpdate(presentationId=pid, body={'requests': reqs}).execute()
            time.sleep(0.3)

        print(f"  ✓ {i+1:02d}. {data.get('title', sid)}")

    return pid


if __name__ == '__main__':
    pid = build()
    url = f'https://docs.google.com/presentation/d/{pid}/edit'
    print(f'\n✅ Done! → {url}')
    with open('/root/.openclaw/workspace/work/pitches/fibo/logistics-deck/deck_v2_url.txt', 'w') as f:
        f.write(url + '\n')
