diff --git a/CLAUDE.md b/CLAUDE.md
index 8da21a25..1b1b2248 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -79,7 +79,9 @@ Completed (Phase 3B — continued, Session 11; HOOK SUPERSEDED by the Session-12
Current implementation target:
-- Recorded 5-minute demo (spec §15), then the full README + demo video write-up
+- Coverage + gas snapshot, then the full README write-up
+ (presentation deck ✅ + demo recorded & uploaded ✅ — Session 15;
+ demo video https://www.youtube.com/watch?v=82_9mEh_POM)
Upcoming implementation order:
@@ -87,7 +89,9 @@ Upcoming implementation order:
2. ReactVM (reactive) deployment ✅ (Session 12 — Reactive Lasna 0xC0e6…B70b, live + wired + verified)
3. Demo script (RangeGuardDemo.s.sol) ✅ (Session 13)
4. Frontend dashboard ✅ (Session 14 — frontend/, live coverage report; https://range-guard.vercel.app)
-5. Recorded 5-minute demo + full README ← current
+5. Presentation deck ✅ (Session 15 — docs/RangeGuard-Demo-Deck.pptx, 6-slide Google-Slides .pptx + logo)
+6. Recorded 5-minute demo ✅ (Session 15 — uploaded https://www.youtube.com/watch?v=82_9mEh_POM)
+7. Coverage + gas snapshot + full README ← current
---
@@ -361,7 +365,34 @@ At the start of every session, Claude must:
# Current Session State
-Last completed (Session 14): Frontend dashboard — the LP coverage report (spec §4 Pillar 4).
+Last completed (Session 15): Presentation deck + logo, and the recorded demo (uploaded to YouTube).
+The slides are COMPLETE and the 5-minute demo is RECORDED & UPLOADED:
+https://www.youtube.com/watch?v=82_9mEh_POM (~3m 53s).
+
+DECK — `docs/RangeGuard-Demo-Deck.pptx`: a 6-slide `.pptx` (python-pptx 1.0.2) that imports directly
+into Google Slides. 16:9 (13.33"×7.5"), Calibri, dark-navy design system (bg #0f1117, white #ffffff,
+accent #00d395, slate #94a3b8, amber #f59e0b, danger #ef4444, card #1e2433). Slides: 1 Title ·
+2 The Solution · 3 Economic Flywheel · 4 Five Pillars · 5 Code Walkthrough · 6 Closing. FULL speaker
+notes on every slide. REBUILT from the prior 9-slide version: removed the two IL-explanation slides
+(judges know IL) + the two transition slides (demo / coverage-report); added a Title slide.
+
+LOGO — `docs/assets/`: shield (gradient stroke #FF007A→#9B59B6, transparent fill) wrapping three
+green #00d395 bar-chart bars. Variants: logo-icon.svg (64²) · favicon.svg (32², 2px) ·
+logo-standalone.svg (+ -light) · logo-full.svg (+ -light). Partner logos from official sources:
+uniswap-logo.svg (pink unicorn) · reactive-logo.svg (+ -dark wordmark). Usage on slides: shield icon
+top-left on content slides; Title + Closing wordmark lockups are NATIVE Calibri text + the shield
+icon (crisper than rasterizing, and dodges a faint downscaled-text raster seam); partner logos on
+Title + Closing under "Built on" / "Powered by".
+
+RASTERIZE PIPELINE — python-pptx can't embed SVG; `docs/build_assets.py` renders each needed SVG→PNG
+on a navy bg (qlmanage composites on white, so we inject a #0f1117 rect, key it to transparent, crop)
+→ `docs/build_deck.py` embeds the PNGs. Build order: build_assets.py then build_deck.py. Verified by
+rendering the .pptx to slide images via LibreOffice headless (LibreOffice + poppler installed this
+session for visual QA). 292 tests cited; demo figures use the REAL fork run (entry 228.38, total
+coverage 12.51, payout 2.23 USDC / IL_CAP), matching the frontend ?demo view.
+-> docs/session-15-slides.md
+
+Previously completed (Session 14): Frontend dashboard — the LP coverage report (spec §4 Pillar 4).
React 18 + Vite + Tailwind + viem SPA in `frontend/`, NO backend — reads public Sepolia RPC only.
- TWO MODES (Option C): LIVE (default, or `?positionKey=0x…`) renders the real on-chain coverage
report for any position; `?demo=true` renders a hardcoded fork narrative from
@@ -431,7 +462,8 @@ docs/reactive-lib-omni-audit.md. Session record: docs/session-12-reactive-deploy
NOT done: Phase-7 end-to-end (LP deposit → swap → PositionTracked → Checkpointed) — needs the demo
script (RangeGuardDemo.s.sol). No live LP-deposit/swap tooling exists yet for the Sepolia pool.
-Current target: Recorded 5-minute demo (spec §15), then the full README + demo video write-up.
+Current target: Coverage + gas snapshot, then the full README write-up. (Slides ✅ + demo recorded &
+uploaded ✅ in Session 15 — https://www.youtube.com/watch?v=82_9mEh_POM.)
Carry-ins: payout recipient = v4 sender (owner=sender MVP). The Callback Proxy is PER NETWORK under
Omni — for any future host chain confirm it at dev.reactive.network/origins-and-destinations before
deploying the hook (it is NOT the legacy 0x…fffFfF).
diff --git a/README.md b/README.md
index 530efbaf..1f127e91 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,6 @@ A Uniswap v4 hook providing native on-chain impermanent loss coverage for liquid
**Live dashboard:** https://range-guard.vercel.app
-**Demo video:** coming soon
+**Demo video:** https://www.youtube.com/watch?v=82_9mEh_POM
**Documentation and full README coming soon.**
diff --git a/context.md b/context.md
index 7c4ef3a2..b8bdaf8c 100644
--- a/context.md
+++ b/context.md
@@ -60,7 +60,15 @@ via PoolManager extsload. Deployed on Vercel (auto from main): https://range-gua
Next implementation target:
-- Recorded 5-minute demo (spec §15), then the full README + demo video write-up
+- Coverage + gas snapshot (forge coverage / forge snapshot), then the full README write-up
+
+Completed (Session 15): presentation deck + RangeGuard logo, and the recorded 5-minute demo (uploaded
+to YouTube). Deck docs/RangeGuard-Demo-Deck.pptx — 6-slide Google-Slides .pptx (Title / The Solution /
+Economic Flywheel / Five Pillars / Code Walkthrough / Closing; 16:9, dark-navy design system, Calibri,
+full speaker notes). Logo in docs/assets/ (shield + bar-chart: logo-icon/favicon/standalone/full +
+light variants) plus Uniswap + Reactive partner logos; built via docs/build_assets.py +
+docs/build_deck.py. Demo video: https://www.youtube.com/watch?v=82_9mEh_POM (~3m 53s; linked in
+README.md). -> docs/session-15-slides.md
Completed (Session 13): demo tooling — RangeGuardDemo.s.sol (Option A, fork+vm.warp, spec §14),
LiveEndToEnd.s.sol / LiveWithdraw.s.sol (Option B live broadcast), DemoLPRouter.sol (live LP whose
@@ -74,9 +82,9 @@ not contracts). -> docs/session-13-demo-script.md, docs/reactive-evidence.md
Planned next steps:
-- Record the 5-minute demo (spec §15): terminal segment (RangeGuardDemo.s.sol) + coverage-report
- segment (the ?demo=true dashboard view) + reactive evidence. Then write the full README and link
- the demo video.
+- Coverage + gas snapshot: run forge coverage and forge snapshot, record the numbers. Then write the
+ full README (the demo is recorded + linked — https://www.youtube.com/watch?v=82_9mEh_POM; slides in
+ docs/RangeGuard-Demo-Deck.pptx).
Recent architecture update:
diff --git a/docs/RangeGuard-Demo-Deck.pptx b/docs/RangeGuard-Demo-Deck.pptx
new file mode 100644
index 00000000..0fd099a5
Binary files /dev/null and b/docs/RangeGuard-Demo-Deck.pptx differ
diff --git a/docs/assets/favicon.svg b/docs/assets/favicon.svg
new file mode 100644
index 00000000..a2e48531
--- /dev/null
+++ b/docs/assets/favicon.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/logo-full-light.svg b/docs/assets/logo-full-light.svg
new file mode 100644
index 00000000..366a73ef
--- /dev/null
+++ b/docs/assets/logo-full-light.svg
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RangeGuard
+
+ Protect your liquidity. Guard your range.
+
diff --git a/docs/assets/logo-full.svg b/docs/assets/logo-full.svg
new file mode 100644
index 00000000..8c28d038
--- /dev/null
+++ b/docs/assets/logo-full.svg
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RangeGuard
+
+ Protect your liquidity. Guard your range.
+
diff --git a/docs/assets/logo-icon.svg b/docs/assets/logo-icon.svg
new file mode 100644
index 00000000..649f3171
--- /dev/null
+++ b/docs/assets/logo-icon.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/logo-standalone-light.svg b/docs/assets/logo-standalone-light.svg
new file mode 100644
index 00000000..3716e031
--- /dev/null
+++ b/docs/assets/logo-standalone-light.svg
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RangeGuard
+
diff --git a/docs/assets/logo-standalone.svg b/docs/assets/logo-standalone.svg
new file mode 100644
index 00000000..e1a9b7d9
--- /dev/null
+++ b/docs/assets/logo-standalone.svg
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RangeGuard
+
diff --git a/docs/assets/png/logo-icon.png b/docs/assets/png/logo-icon.png
new file mode 100644
index 00000000..72a5b9fd
Binary files /dev/null and b/docs/assets/png/logo-icon.png differ
diff --git a/docs/assets/png/reactive-logo.png b/docs/assets/png/reactive-logo.png
new file mode 100644
index 00000000..ec9a6d05
Binary files /dev/null and b/docs/assets/png/reactive-logo.png differ
diff --git a/docs/assets/png/uniswap-logo.png b/docs/assets/png/uniswap-logo.png
new file mode 100644
index 00000000..c4bc2e00
Binary files /dev/null and b/docs/assets/png/uniswap-logo.png differ
diff --git a/docs/assets/reactive-logo-dark.svg b/docs/assets/reactive-logo-dark.svg
new file mode 100644
index 00000000..34ad8586
--- /dev/null
+++ b/docs/assets/reactive-logo-dark.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/reactive-logo.svg b/docs/assets/reactive-logo.svg
new file mode 100644
index 00000000..dd86814f
--- /dev/null
+++ b/docs/assets/reactive-logo.svg
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/assets/uniswap-logo.svg b/docs/assets/uniswap-logo.svg
new file mode 100644
index 00000000..daacfc15
--- /dev/null
+++ b/docs/assets/uniswap-logo.svg
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/build_assets.py b/docs/build_assets.py
new file mode 100644
index 00000000..6257c9f6
--- /dev/null
+++ b/docs/build_assets.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+"""Rasterize the deck's SVG logos to PNGs for python-pptx embedding.
+
+python-pptx cannot embed SVG, and the only local renderer (macOS qlmanage)
+composites SVGs on an opaque WHITE background. Every logo in the deck is placed
+on the flat #0f1117 slide background, so we render each SVG with a matching navy
+background rect (seamless on-slide) and crop away qlmanage's white padding.
+
+Output: docs/assets/png/.png (run from anywhere; paths are absolute)
+"""
+import os
+import re
+import subprocess
+import tempfile
+from PIL import Image
+
+ASSETS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets")
+PNG_DIR = os.path.join(ASSETS, "png")
+SLIDE_BG = "#0f1117"
+RENDER_PX = 2000 # longest side; generous for crisp downscaling in the deck
+
+# name -> (source svg, background color, keep_left fraction)
+# keep_left: after cropping to content, keep only the leftmost fraction of width.
+# The Reactive docs asset is a "reactive | Dev" lockup; we keep just the "reactive"
+# icon+wordmark (drop the "| Dev" suffix) for a clean "Powered by" partner badge.
+#
+# Only the shield ICON + partner logos are rasterized for embedding. The "RangeGuard"
+# wordmark and tagline are drawn as NATIVE pptx Calibri text on the slides (crisper, and
+# avoids a faint full-width raster-edge artifact under downscaled tagline text). The full
+# logo-full.svg / logo-standalone.svg remain as standalone brand assets in docs/assets/.
+ASSETS_TO_RENDER = {
+ "logo-icon": ("logo-icon.svg", SLIDE_BG, 1.0),
+ "uniswap-logo": ("uniswap-logo.svg", SLIDE_BG, 1.0),
+ "reactive-logo": ("reactive-logo.svg", SLIDE_BG, 0.685),
+}
+
+
+def inject_bg(svg_text, color):
+ """Insert an opaque background rect right after the opening tag."""
+ m = re.search(r"]*>", svg_text)
+ if not m:
+ raise ValueError("no tag")
+ rect = f' '
+ i = m.end()
+ return svg_text[:i] + rect + svg_text[i:]
+
+
+def render(name, src, bg, keep_left=1.0):
+ src_path = os.path.join(ASSETS, src)
+ with open(src_path) as f:
+ svg = f.read()
+ svg = inject_bg(svg, bg)
+ with tempfile.TemporaryDirectory() as td:
+ tmp_svg = os.path.join(td, f"{name}.svg")
+ with open(tmp_svg, "w") as f:
+ f.write(svg)
+ subprocess.run(["qlmanage", "-t", "-s", str(RENDER_PX), "-o", td, tmp_svg],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
+ out_png = os.path.join(td, f"{name}.svg.png")
+ im = Image.open(out_png).convert("RGB")
+ # crop away qlmanage's pure-white padding -> tight content rect (all navy/logo)
+ px = im.load()
+ W, H = im.size
+ white = (255, 255, 255)
+ # scan for non-white bounds
+ from PIL import ImageChops
+ bg_img = Image.new("RGB", im.size, white)
+ diff = ImageChops.difference(im, bg_img)
+ bbox = diff.getbbox()
+ if bbox:
+ im = im.crop(bbox)
+ if keep_left < 1.0:
+ w, h = im.size
+ im = im.crop((0, 0, int(w * keep_left), h))
+ # re-trim any navy gap left at the new right edge
+ from PIL import ImageChops as _IC
+ bg2 = Image.new("RGB", im.size, (255, 255, 255))
+ b2 = _IC.difference(im, bg2).getbbox()
+ if b2:
+ im = im.crop(b2)
+ # Key the navy fill to transparent so the PNG has NO opaque rectangle/edge seam
+ # on the slide. Background pixels are exactly SLIDE_BG; logo colors are bright and
+ # never collide with it, so an exact-match key is clean (anti-aliased silhouette
+ # pixels stay and blend correctly against the same navy slide).
+ nb = tuple(int(bg.lstrip("#")[k:k+2], 16) for k in (0, 2, 4))
+ rgba = im.convert("RGBA")
+ datas = rgba.getdata()
+ keyed = [(r, g, b, 0) if (r, g, b) == nb else (r, g, b, a)
+ for (r, g, b, a) in datas]
+ rgba.putdata(keyed)
+ im = rgba
+ # Add a small transparent margin so the image boundary never sits exactly on a
+ # glyph edge — otherwise a full-width text baseline at the crop edge resamples to a
+ # faint 1px line on the slide. Symmetric, so centered placement is unaffected.
+ from PIL import ImageOps
+ m = max(8, round(0.012 * max(im.size)))
+ im = ImageOps.expand(im, border=m, fill=(0, 0, 0, 0))
+ os.makedirs(PNG_DIR, exist_ok=True)
+ im.save(os.path.join(PNG_DIR, f"{name}.png"))
+ w, h = im.size
+ print(f"{name:18s} {w}x{h} aspect={w/h:.3f}")
+ return w / h
+
+
+if __name__ == "__main__":
+ aspects = {}
+ for name, (src, bg, keep_left) in ASSETS_TO_RENDER.items():
+ aspects[name] = render(name, src, bg, keep_left)
+ print("\naspects =", {k: round(v, 4) for k, v in aspects.items()})
diff --git a/docs/build_deck.py b/docs/build_deck.py
new file mode 100644
index 00000000..7446880d
--- /dev/null
+++ b/docs/build_deck.py
@@ -0,0 +1,439 @@
+#!/usr/bin/env python3
+"""Build RangeGuard-Demo-Deck.pptx — 6-slide Google-Slides-ready deck (rebuild).
+
+Structure (Session-15 rebuild):
+ 1 Title (logo-full + presenter + partner logos)
+ 2 The Solution (headline + 5 bullets + tagline)
+ 3 Economic Flywheel (two-row flow loop)
+ 4 Five Pillars
+ 5 Code Walkthrough (lifecycle + 2 amber callouts)
+ 6 Closing (standalone logo + bullets + beneficiary table + partner logos + links)
+
+Logos are SVG but python-pptx embeds raster only, so docs/build_assets.py
+pre-renders each to a navy-background PNG (seamless on the #0f1117 slide).
+Run `python3 docs/build_assets.py` first, then this script.
+
+Design system: bg #0f1117 · white #ffffff · accent #00d395 · slate #94a3b8 ·
+amber #f59e0b · danger #ef4444 · card #1e2433 · Calibri · 16:9 widescreen.
+"""
+import os
+from PIL import Image
+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
+
+# ---------------------------------------------------------------- palette
+BG = RGBColor(0x0F, 0x11, 0x17)
+WHITE = RGBColor(0xFF, 0xFF, 0xFF)
+ACCENT = RGBColor(0x00, 0xD3, 0x95)
+SLATE = RGBColor(0x94, 0xA3, 0xB8)
+AMBER = RGBColor(0xF5, 0x9E, 0x0B)
+DANGER = RGBColor(0xEF, 0x44, 0x44)
+CARD = RGBColor(0x1E, 0x24, 0x33)
+
+HEAD_FONT = "Calibri"
+BODY_FONT = "Calibri"
+CODE_FONT = "Consolas"
+
+EMU_IN = 914400
+HERE = os.path.dirname(os.path.abspath(__file__))
+PNG = os.path.join(HERE, "assets", "png")
+
+prs = Presentation()
+prs.slide_width = Emu(int(13.333 * EMU_IN))
+prs.slide_height = Emu(int(7.5 * EMU_IN))
+BLANK = prs.slide_layouts[6]
+
+_ASPECT = {}
+def aspect(name):
+ if name not in _ASPECT:
+ w, h = Image.open(os.path.join(PNG, f"{name}.png")).size
+ _ASPECT[name] = w / h
+ return _ASPECT[name]
+
+
+# ---------------------------------------------------------------- helpers
+def add_slide():
+ s = prs.slides.add_slide(BLANK)
+ r = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height)
+ r.fill.solid(); r.fill.fore_color.rgb = BG
+ r.line.fill.background(); r.shadow.inherit = False
+ sp = r._element; sp.getparent().remove(sp); s.shapes._spTree.insert(2, sp)
+ return s
+
+
+def _set_run(run, rd):
+ run.text = rd["t"]
+ f = run.font
+ f.size = Pt(rd.get("size", 18)); f.bold = rd.get("bold", False)
+ f.italic = rd.get("italic", False); f.name = rd.get("font", BODY_FONT)
+ f.color.rgb = rd.get("color", WHITE)
+
+
+def txt(slide, x, y, w, h, lines, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, wrap=True):
+ tb = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
+ tf = tb.text_frame; tf.word_wrap = wrap; tf.vertical_anchor = anchor
+ tf.margin_left = 0; tf.margin_right = 0; tf.margin_top = 0; tf.margin_bottom = 0
+ for i, para in enumerate(lines):
+ p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
+ p.alignment = align
+ if isinstance(para, dict):
+ para = [para]
+ meta = para[0] if para else {}
+ if meta.get("space_before") is not None: p.space_before = Pt(meta["space_before"])
+ if meta.get("space_after") is not None: p.space_after = Pt(meta["space_after"])
+ if meta.get("line_spacing") is not None: p.line_spacing = meta["line_spacing"]
+ for rd in para:
+ _set_run(p.add_run(), rd)
+ return tb
+
+
+def center(slide, x, y, w, h, paras, anchor=MSO_ANCHOR.MIDDLE):
+ return txt(slide, x, y, w, h, paras, align=PP_ALIGN.CENTER, anchor=anchor)
+
+
+def card(slide, x, y, w, h, fill=CARD, border=None, border_w=1.5, radius=0.08):
+ sh = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE,
+ Inches(x), Inches(y), Inches(w), Inches(h))
+ sh.fill.solid(); sh.fill.fore_color.rgb = fill
+ if border is None: sh.line.fill.background()
+ else: sh.line.color.rgb = border; sh.line.width = Pt(border_w)
+ sh.shadow.inherit = False
+ try: sh.adjustments[0] = radius
+ except Exception: pass
+ return sh
+
+
+def line(slide, x, y, w, color=ACCENT, weight=2.5):
+ sh = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE,
+ Inches(x), Inches(y), Inches(w), Inches(weight / 72.0))
+ sh.fill.solid(); sh.fill.fore_color.rgb = color
+ sh.line.fill.background(); sh.shadow.inherit = False
+ return sh
+
+
+def arrow_shape(slide, x, y, w, h, color=ACCENT, shape=MSO_SHAPE.RIGHT_ARROW):
+ sh = slide.shapes.add_shape(shape, Inches(x), Inches(y), Inches(w), Inches(h))
+ sh.fill.solid(); sh.fill.fore_color.rgb = color
+ sh.line.fill.background(); sh.shadow.inherit = False
+ return sh
+
+
+def img(slide, name, x, y, h):
+ """Place PNG by height (inches), preserving aspect. Returns (x, y, w, h)."""
+ w = h * aspect(name)
+ pic = slide.shapes.add_picture(os.path.join(PNG, f"{name}.png"),
+ Inches(x), Inches(y), height=Inches(h))
+ pic.line.fill.background() # ensure no faint picture border at the raster edge
+ pic.shadow.inherit = False
+ return x, y, w, h
+
+
+def img_center(slide, name, cx, y, h):
+ w = h * aspect(name)
+ return img(slide, name, cx - w / 2, y, h)
+
+
+def label(slide, text):
+ txt(slide, 0.92, 0.46, 6.0, 0.4,
+ [[{"t": text, "size": 13, "color": ACCENT, "bold": True, "font": HEAD_FONT}]])
+ img(slide, "logo-icon", 0.5, 0.4, 0.3) # subtle 24px-ish icon top-left
+
+
+def notes(slide, text):
+ slide.notes_slide.notes_text_frame.text = text
+
+
+def _text_w(s, size):
+ return len(s) * 0.46 * size / 72.0
+
+
+def hrow(slide, cy, items, gap=0.14, cx=6.6665):
+ """Lay mixed text/image items in one horizontal row, centered at cx, middle at cy."""
+ widths = []
+ for it in items:
+ if it["type"] == "text":
+ widths.append(_text_w(it["t"], it.get("size", 13)) + 0.04)
+ else:
+ widths.append(it["h"] * aspect(it["name"]))
+ total = sum(widths) + gap * (len(items) - 1)
+ x = cx - total / 2
+ for it, w in zip(items, widths):
+ if it["type"] == "text":
+ th = it.get("size", 13) / 72.0 * 1.8
+ txt(slide, x, cy - th / 2, w + 0.1, th,
+ [[{"t": it["t"], "size": it.get("size", 13),
+ "color": it.get("color", SLATE), "bold": it.get("bold", False),
+ "font": it.get("font", BODY_FONT)}]],
+ align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.MIDDLE, wrap=False)
+ else:
+ img(slide, it["name"], x, cy - it["h"] / 2, it["h"])
+ x += w + gap
+
+
+# ================================================================ SLIDE 1 — Title
+s = add_slide()
+# Full lockup composed natively: shield icon + crisp Calibri name + tagline
+img_center(s, "logo-icon", 6.6665, 1.0, 1.5)
+center(s, 1.0, 2.72, 11.33, 0.95,
+ [[{"t": "RangeGuard", "size": 48, "color": WHITE, "bold": True, "font": HEAD_FONT}]],
+ anchor=MSO_ANCHOR.TOP)
+center(s, 1.0, 3.78, 11.33, 0.5,
+ [[{"t": "Protect your liquidity. Guard your range.", "size": 20, "color": SLATE}]],
+ anchor=MSO_ANCHOR.TOP)
+center(s, 1.0, 4.62, 11.33, 0.95, [
+ [{"t": "Gary Kocsis", "size": 22, "color": WHITE, "bold": True, "space_after": 3}],
+ [{"t": "Uniswap Hook Incubator", "size": 16, "color": SLATE}],
+], anchor=MSO_ANCHOR.TOP)
+hrow(s, 6.35, [
+ {"type": "text", "t": "Built on", "size": 14, "color": SLATE},
+ {"type": "img", "name": "uniswap-logo", "h": 0.34},
+ {"type": "text", "t": "·", "size": 18, "color": SLATE},
+ {"type": "text", "t": "Powered by", "size": 14, "color": SLATE},
+ {"type": "img", "name": "reactive-logo", "h": 0.30},
+], gap=0.22)
+notes(s,
+"Hello everyone, my name is Gary Kocsis, and I'm excited to present RangeGuard — Protect your "
+"liquidity. Guard your range. As many of you know, impermanent loss is the single biggest reason "
+"liquidity providers leave AMMs. Even with fee rewards, most LPs lose money when prices move — "
+"and there's no native, on-chain way to protect them. Until now.")
+
+# ================================================================ SLIDE 2 — The Solution
+s = add_slide()
+label(s, "THE SOLUTION")
+center(s, 1.0, 1.05, 11.33, 1.2, [
+ [{"t": "RangeGuard turns impermanent loss", "size": 30, "color": WHITE, "bold": True, "font": HEAD_FONT}],
+ [{"t": "into an earned, capped, on-chain claim.", "size": 30, "color": WHITE, "bold": True, "font": HEAD_FONT}],
+], anchor=MSO_ANCHOR.TOP)
+sol_bullets = [
+ ("Coverage accrues only while in range —", "exposure-weighted, not time-weighted"),
+ ("Day-count convention (Actual/365 Fixed) —", "predictable, auditable, comparable across pools"),
+ ("Buffer funded by dynamic fee skimming —", "sustainable, no external subsidies"),
+ ("Automatic payout on withdrawal —", "capped by earned coverage, buffer health, and protocol params"),
+ ("Reactive Network integration —", "range transitions detected cross-chain, no keepers"),
+]
+y = 2.55
+for top, sub in sol_bullets:
+ txt(s, 1.5, y, 10.5, 0.78, [
+ [{"t": "▸ ", "size": 17, "color": ACCENT, "bold": True},
+ {"t": top, "size": 17, "color": WHITE, "bold": True}],
+ [{"t": " " + sub, "size": 15, "color": SLATE, "space_before": 1}],
+ ])
+ y += 0.82
+center(s, 1.0, 6.85, 11.33, 0.45,
+ [[{"t": "“Protect your liquidity. Guard your range.”",
+ "size": 19, "color": ACCENT, "bold": True, "italic": True}]])
+notes(s,
+"RangeGuard brings native, transparent IL coverage directly into a Uniswap v4 pool. When an LP "
+"provides liquidity, they start earning coverage — but only while their position is in range and "
+"exposed to IL risk. Coverage accrues using a day-count convention — just like traditional "
+"finance — so LPs can see exactly how much protection they're earning and compare it across "
+"pools. The coverage buffer is funded by a small configurable portion of trading fees via "
+"Uniswap v4's dynamic fee system — sustainable, no external subsidies. When an LP withdraws, the "
+"hook automatically calculates their impermanent loss and pays out a capped reimbursement — "
+"bounded by what they've earned, the buffer's health, and protocol parameters. And the system "
+"runs autonomously. RangeGuard integrates with the Reactive Network — a cross-chain automation "
+"layer running on Lasna — so range transitions are detected and recorded automatically. No "
+"keeper bots. No off-chain infrastructure.")
+
+# ================================================================ SLIDE 3 — Economic Flywheel
+s = add_slide()
+label(s, "THE SOLUTION")
+center(s, 1.0, 0.95, 11.33, 0.6,
+ [[{"t": "Self-Funding by Design", "size": 30, "color": WHITE, "bold": True, "font": HEAD_FONT}]],
+ anchor=MSO_ANCHOR.TOP)
+# two-row flow: 3 columns
+bw, bh = 3.35, 1.05
+c = [1.05, 4.99, 8.93] # column x's
+r1y, r2y = 2.25, 4.35
+row1 = ["LPs provide\nliquidity", "Traders use\nthe pool", "Swaps generate\nfees"]
+row2 = ["LPs are\nprotected", "Buffer pays\ncapped IL", "Buffer slice\nskimmed"]
+row2_hl = [False, True, True]
+def fbox(x, y, text, hl):
+ card(s, x, y, bw, bh, fill=CARD, border=(ACCENT if hl else None), border_w=2)
+ center(s, x, y, bw, bh,
+ [[{"t": ln, "size": 15, "color": WHITE, "bold": hl}] for ln in text.split("\n")])
+for i in range(3):
+ fbox(c[i], r1y, row1[i], False)
+ fbox(c[i], r2y, row2[i], row2_hl[i])
+# row1 right arrows (between cols)
+for i in range(2):
+ ax = c[i] + bw; aw = c[i+1] - (c[i] + bw)
+ arrow_shape(s, ax + 0.05, r1y + bh/2 - 0.18, aw - 0.1, 0.36, color=ACCENT, shape=MSO_SHAPE.RIGHT_ARROW)
+# row2 left arrows (between cols, pointing left)
+for i in range(2):
+ ax = c[i] + bw; aw = c[i+1] - (c[i] + bw)
+ arrow_shape(s, ax + 0.05, r2y + bh/2 - 0.18, aw - 0.1, 0.36, color=ACCENT, shape=MSO_SHAPE.LEFT_ARROW)
+# down arrow on right column (row1 -> row2)
+arrow_shape(s, c[2] + bw/2 - 0.18, r1y + bh + 0.06, 0.36, r2y - (r1y + bh) - 0.12,
+ color=ACCENT, shape=MSO_SHAPE.DOWN_ARROW)
+# up arrow on left column (row2 -> row1)
+arrow_shape(s, c[0] + bw/2 - 0.18, r1y + bh + 0.06, 0.36, r2y - (r1y + bh) - 0.12,
+ color=ACCENT, shape=MSO_SHAPE.UP_ARROW)
+center(s, 1.0, 6.7, 11.33, 0.5,
+ [[{"t": "Swap activity funds the buffer that protects LPs.",
+ "size": 19, "color": ACCENT, "bold": True}]])
+notes(s,
+"The economics are self-sustaining. LPs provide liquidity, traders use the pool, swaps generate "
+"fees, a buffer slice is skimmed that funds the coverage buffer, and the buffer pays capped IL "
+"claims. The buffer grows from the same swap activity that LP liquidity enables. No external "
+"capital required.")
+
+# ================================================================ SLIDE 4 — Five Pillars
+s = add_slide()
+label(s, "THE SOLUTION")
+center(s, 1.0, 0.95, 11.33, 0.6,
+ [[{"t": "How RangeGuard Works: Five Pillars", "size": 29, "color": WHITE, "bold": True, "font": HEAD_FONT}]],
+ anchor=MSO_ANCHOR.TOP)
+pillars = [
+ ("1", "Accrual Gating", ["Coverage accrues only while the position is in range"], False),
+ ("2", "Buffer Funding", ["A portion of every swap fee funds the coverage buffer"], False),
+ ("3", "Claim Settlement", ["IL computed and paid automatically on withdrawal",
+ "Payout = min(covered IL, earned coverage, buffer cap)"], False),
+ ("4", "LP Transparency ★", ["A verifiable, day-by-day coverage report —",
+ "built entirely from on-chain events"], True),
+ ("5", "Pool Configuration", ["Immutable parameters set once at pool initialization"], False),
+]
+y = 1.8
+for num, title, body, hl in pillars:
+ rowh = 0.74 + 0.26 * (len(body) - 1)
+ if hl:
+ card(s, 0.85, y - 0.08, 11.63, rowh + 0.12, fill=CARD, border=ACCENT, border_w=2)
+ txt(s, 1.1, y, 0.7, rowh, [[{"t": num, "size": 25, "color": ACCENT, "bold": True}]])
+ paras = [[{"t": title, "size": 19, "color": WHITE, "bold": True, "space_after": 2}]]
+ for b in body:
+ paras.append([{"t": b, "size": 14.5, "color": SLATE, "space_after": 1}])
+ txt(s, 1.8, y, 10.5, rowh, paras)
+ y += rowh + 0.16
+center(s, 1.0, 6.85, 11.33, 0.45,
+ [[{"t": "Every pillar is enforced on-chain. No off-chain assumptions.",
+ "size": 17, "color": ACCENT, "bold": True}]])
+notes(s,
+"RangeGuard is built on five pillars. Accrual gating — coverage only earns while in range. Buffer "
+"funding — every swap contributes automatically. Claim settlement — IL is computed and paid in "
+"the same withdrawal transaction, capped by three limits: covered IL, earned coverage, and "
+"buffer capacity. LP transparency — the key differentiator — a verifiable day-by-day coverage "
+"statement from pure on-chain events. And pool configuration — immutable parameters, so LPs "
+"always know what they signed up for.\n\n"
+"Verbal transition: Let me show you the core functions that make this work.")
+
+# ================================================================ SLIDE 5 — Code Walkthrough
+s = add_slide()
+label(s, "UNDER THE HOOD")
+center(s, 1.0, 0.95, 11.33, 0.6,
+ [[{"t": "How Coverage Becomes a Claim", "size": 29, "color": WHITE, "bold": True, "font": HEAD_FONT}]],
+ anchor=MSO_ANCHOR.TOP)
+steps = ["LP Deposits", "_accrue()", "afterSwap()", "_computeIL()", "_computePayout()", "ClaimSettled"]
+n = len(steps); fx = 0.55; fw = 1.78; fgap = (12.78 - 0.55 - n * fw) / (n - 1)
+fy = 2.2; fh = 0.95
+for i, lab in enumerate(steps):
+ x = fx + i * (fw + fgap)
+ card(s, x, fy, fw, fh, fill=CARD, border=ACCENT, border_w=2) # all same color
+ center(s, x, fy, fw, fh,
+ [[{"t": lab, "size": 11.5, "color": WHITE, "bold": True, "font": CODE_FONT}]])
+ if i < n - 1:
+ ax = x + fw + (fgap - 0.4) / 2
+ txt(s, ax, fy + fh/2 - 0.25, 0.5, 0.5,
+ [[{"t": "→", "size": 24, "color": ACCENT, "bold": True}]],
+ align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
+card(s, 0.85, 3.95, 5.6, 1.95, fill=CARD, border=AMBER, border_w=2)
+txt(s, 1.15, 4.2, 5.05, 1.5, [
+ [{"t": "⚡ Swaps never iterate positions", "size": 16, "color": AMBER, "bold": True, "space_after": 6}],
+ [{"t": "Accrual is lazy, position-specific", "size": 15, "color": WHITE, "space_after": 4}],
+ [{"t": "O(1) gas on every swap", "size": 15, "color": WHITE, "bold": True}],
+])
+card(s, 6.75, 3.95, 5.7, 1.95, fill=CARD, border=AMBER, border_w=2)
+txt(s, 7.05, 4.2, 5.2, 1.55, [
+ [{"t": "⚡ Actual/365 Fixed day-count", "size": 16, "color": AMBER, "bold": True, "space_after": 6}],
+ [{"t": "Coverage = Notional × APR × (days ÷ 365)", "size": 14, "color": WHITE, "font": CODE_FONT, "space_after": 4}],
+ [{"t": "Same convention as fixed-income finance", "size": 15, "color": WHITE}],
+])
+center(s, 1.0, 6.4, 11.33, 0.5,
+ [[{"t": "One lifecycle. Fully on-chain. No off-chain assumptions.",
+ "size": 18, "color": ACCENT, "bold": True}]])
+notes(s,
+"Before the demo, here's the core code path. _accrue advances coverage lazily — only while in "
+"range, using Actual/365 Fixed day-count math. _computePayout applies three caps in order. The "
+"day-count convention turns coverage from a black-box reward into an auditable financial "
+"accrual. Let me show you.\n\n"
+"IDE narration — _accrue: range gate first — if the tick is outside the LP's bounds, delta is "
+"zero. If in range — year fraction from day-count basis, multiplied by entry notional and APR. "
+"Fifteen days in range earns exactly 15/365 of the annual coverage amount. _computePayout: three "
+"caps in order — IL cap, earned coverage cap, buffer cap. Minimum of all three. Limiting factor "
+"is recorded so the LP knows exactly why they received what they received.")
+
+# ================================================================ SLIDE 6 — Closing
+s = add_slide()
+# Standalone lockup composed natively: shield icon + crisp Calibri name, centered
+_name_w = 2.05 # generous, avoids wrap of bold 27pt "RangeGuard"
+_icon_h = 0.52
+_grp = _icon_h + 0.12 + _name_w
+_gx = 6.6665 - _grp / 2
+img(s, "logo-icon", _gx, 0.29, _icon_h)
+txt(s, _gx + _icon_h + 0.12, 0.3, _name_w, 0.5,
+ [[{"t": "RangeGuard", "size": 27, "color": WHITE, "bold": True, "font": HEAD_FONT}]],
+ align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.MIDDLE, wrap=False)
+center(s, 1.0, 0.95, 11.33, 0.55,
+ [[{"t": "“Protect your liquidity. Guard your range.”",
+ "size": 22, "color": ACCENT, "bold": True, "italic": True}]])
+fbul = [
+ "Native IL coverage — built into the pool, not bolted on",
+ "Self-funding buffer — swap fees cover the claims",
+ "Capped payouts — actuarially sound, buffer protected",
+ "Fully auditable — every accrual verifiable on-chain",
+]
+txt(s, 2.3, 1.65, 9.0, 1.25,
+ [[{"t": "✓ ", "size": 16, "color": ACCENT, "bold": True},
+ {"t": b, "size": 16, "color": WHITE}] for b in
+ [f for f in fbul]],
+ )
+# beneficiary table
+ty = 3.0
+card(s, 1.7, ty, 9.93, 1.6, fill=CARD)
+bens = [
+ ("Who Benefits", "Why It Matters", True),
+ ("Passive LPs", "Downside protection without complex hedging", False),
+ ("Protocols / DAOs", "Deeper liquidity without reliance on emissions", False),
+ ("Uniswap Ecosystem", "Durable liquidity, tighter spreads, better execution", False),
+]
+by0 = ty + 0.12; brh = 0.36
+for j, (a, b, hdr) in enumerate(bens):
+ yy = by0 + j * brh
+ txt(s, 2.05, yy, 3.3, brh,
+ [[{"t": a, "size": 13.5, "color": (ACCENT if hdr else WHITE), "bold": True}]],
+ anchor=MSO_ANCHOR.MIDDLE)
+ txt(s, 5.4, yy, 6.0, brh,
+ [[{"t": b, "size": 13, "color": SLATE}]], anchor=MSO_ANCHOR.MIDDLE)
+ if hdr:
+ line(s, 2.05, yy + brh - 0.02, 9.2, color=SLATE, weight=1)
+center(s, 1.0, 4.78, 11.33, 0.45,
+ [[{"t": "Earned over time. Funded by swaps. Settled on-chain.",
+ "size": 19, "color": ACCENT, "bold": True}]])
+line(s, 5.165, 5.3, 3.0, color=SLATE, weight=1)
+center(s, 1.0, 5.42, 11.33, 0.4,
+ [[{"t": "🔗 range-guard.vercel.app · github.com/garykocsis/RangeGuard",
+ "size": 14, "color": WHITE, "bold": True}]])
+hrow(s, 6.15, [
+ {"type": "text", "t": "Built on", "size": 13, "color": SLATE},
+ {"type": "img", "name": "uniswap-logo", "h": 0.32},
+ {"type": "text", "t": "·", "size": 16, "color": SLATE},
+ {"type": "text", "t": "Powered by", "size": 13, "color": SLATE},
+ {"type": "img", "name": "reactive-logo", "h": 0.28},
+], gap=0.2)
+center(s, 1.0, 6.95, 11.33, 0.35,
+ [[{"t": "292 tests passing · Fuzz tested · Invariant tested",
+ "size": 12, "color": SLATE}]])
+notes(s,
+"RangeGuard makes providing liquidity safer, more transparent, and more attractive — helping "
+"Uniswap pools retain and grow their LP base. The primary beneficiaries are passive LPs who get "
+"downside protection without complex hedging. But the impact extends to protocols that need "
+"sticky liquidity without emissions, and to the broader ecosystem through deeper pools and "
+"better execution. Earned over time. Funded by swaps. Settled on-chain. Live dashboard at "
+"range-guard.vercel.app. Full source and 292 passing tests on GitHub. Thank you.")
+
+# ---------------------------------------------------------------- save
+out = os.path.join(HERE, "RangeGuard-Demo-Deck.pptx")
+prs.save(out)
+print("saved", out, "slides:", len(prs.slides._sldIdLst))
diff --git a/docs/session-15-slides.md b/docs/session-15-slides.md
new file mode 100644
index 00000000..cbc40da0
--- /dev/null
+++ b/docs/session-15-slides.md
@@ -0,0 +1,403 @@
+# Session 15 — Logo + Presentation Deck + Recorded Demo
+
+**Date:** 2026-06-08 → 2026-06-09
+**Branch:** `feat/slides`
+**Deliverables:**
+- `docs/RangeGuard-Demo-Deck.pptx` — 6-slide Google-Slides-ready deck
+- `docs/assets/` — RangeGuard logo (8 SVGs) + partner logos
+- `docs/build_assets.py`, `docs/build_deck.py` — reproducible builders
+- **Demo video:** https://www.youtube.com/watch?v=82_9mEh_POM (~3m 53s)
+
+> This session ran in two parts: an initial 9-slide deck build, then a **Logo + Slides Rebuild**
+> (the opening prompt of which is reproduced verbatim below) that produced the final 6-slide deck and
+> the logo. The recorded demo was then published to YouTube. This document is the close-out record.
+
+---
+
+## 1. Opening prompt (verbatim) — "RangeGuard — Logo + Slides Rebuild"
+
+```
+RangeGuard — Logo + Slides Rebuild
+Branch: feat/slides (already exists — continue on this branch)
+Mandatory first steps:
+
+Read docs/demo-narrative.md
+Read docs/demo-run-output.md
+Read docs/session-15-slides.md (existing deck context)
+Confirm understanding before writing any code
+
+
+PHASE 1 — Generate Logo (stop and wait for approval before Phase 2)
+Generate the RangeGuard logo as SVG files. Save to docs/assets/:
+Design spec:
+Shield shape: Classic shield, slightly rounded bottom point
+ Proportions: ~1:1.2 width:height ratio
+
+Inside the shield: 3 vertical bars of different heights
+ - Left bar: 60% of shield height, width = 12% of shield width
+ - Center bar: 85% of shield height, width = 12% of shield width
+ - Right bar: 45% of shield height, width = 12% of shield width
+ - Bars positioned centered horizontally, bottom-aligned
+ - Gap between bars: 8% of shield width
+ - Bar color: #00d395 (accent green)
+
+Shield fill: transparent (see-through center shows bars)
+Sstroke: gradient left-to-right #FF007A → #9B59B6
+Shield stroke width: 3px
+Generate four variants:
+docs/assets/logo-icon.svg — icon only, 64×64 viewBox:
+Just the shield + bars, no text
+docs/assets/logo-standalone.svg — icon + name, horizontal layout:
+[Shield 48px] [RangeGuard — Calibri Bold, 32px, #ffffff]
+Total width ~300px
+docs/assets/logo-full.svg — icon + name + tagline, stacked:
+[Shield 64px centered]
+[RangeGuard — Calibri Bold, 36px, #ffffff, centered]
+[Protect your liquidity. Guard your range. — Calibri Regular, 16px, #94a3b8, centered]
+Total height ~140px
+docs/assets/favicon.svg — 32×32, icon only, simplified:
+Same as logo-icon but optimized for 32px display
+Stroke width: 2px at this size
+After generating all four variants:
+
+Present them for review
+STOP — do not proceed to Phase 2 until user explicitly approves the logo
+If changes are requested, regenerate and present again
+Only proceed to Phase 2 after explicit approval
+
+
+PHASE 2 — Rebuild Slide Deck (only after logo approval)
+d-Demo-Deck.pptx with the following changes from the previous version:
+Structural changes:
+
+Remove old Slides 1-2 (IL explanation — judges know IL)
+Remove old Slide 7 (demo transition)
+Remove old Slide 8 (coverage report transition)
+Add new Title slide as Slide 1
+Result: 6 slides total
+
+Logo placement:
+
+Title slide: logo-full.svg centered, prominent
+All other slides: logo-icon.svg top-left corner, 24px height, subtle
+Closing slide: logo-standalone.svg top center
+
+Partner logos:
+
+Download or reference Uniswap logo SVG from official source
+Download or reference Reactive Network logo from official source
+Save to docs/assets/uniswap-logo.svg and docs/assets/reactive-logo.svg
+Title slide: both logos very small at bottom, with "Built on" and "Powered by" labels, slate gray
+Closing slide: both logos small but visible, same labels
+
+
+The 6 slides — complete spec:
+Slide 1 — Title
+[Full logo centered — logo-full.svg]
+
+[Presenter line — bottom center, slate gray]
+Gary Kocsis
+Uniswap Hook Incubator
+
+[Partner ly bottom, small]
+Built on [Uniswap Logo] · Powered by [Reactive Network Logo]
+Speaker notes:
+
+"Hello everyone, my name is Gary Kocsis, and I'm excited to present RangeGuard — Protect your liquidity. Guard your range. As many of you know, impermanent loss is the single biggest reason liquidity providers leave AMMs. Even with fee rewards, most LPs lose money when prices move — and there's no native, on-chain way to protect them. Until now."
+
+
+Slide 2 — The Solution
+[Label] THE SOLUTION
+[Icon top-left — 24px]
+
+[Headline]
+RangeGuard turns impermanent loss
+into an earned, capped, on-chain claim.
+
+[Five bullets]
+- Coverage accrues only while in range —
+ exposure-weighted, not time-weighted
+- Day-count convention (Actual/365 Fixed) —
+ predictable, auditable, comparable across pools
+- Buffer funded by dynamic fee skimming —
+ sustainable, no external subsidies
+- Automatic payout on withdrawal —
+ capped by earned coverage, buffer health, and protocol params
+- Reactive Network integration —
+ etected cross-chain, no keepers
+
+[Tagline — accent color, bottom]
+"Protect your liquidity. Guard your range."
+Speaker notes:
+
+"RangeGuard brings native, transparent IL coverage directly into a Uniswap v4 pool. When an LP provides liquidity, they start earning coverage — but only while their position is in range and exposed to IL risk. Coverage accrues using a day-count convention — just like traditional finance — so LPs can see exactly how much protection they're earning and compare it across pools. The coverage buffer is funded by a small configurable portion of trading fees via Uniswap v4's dynamic fee system — sustainable, no external subsidies. When an LP withdraws, the hook automatically calculates their impermanent loss and pays out a capped reimbursement — bounded by what they've earned, the buffer's health, and protocol parameters. And the system runs autonomously. RangeGuard integrates with the Reactive Network — a cross-chain automation layer running on Lasna — so range transitionsd recorded automatically. No keeper bots. No off-chain infrastructure."
+
+
+Slide 3 — Economic Flywheel
+[Label] THE SOLUTION
+[Icon top-left — 24px]
+
+[Headline]
+Self-Funding by Design
+
+[Two-row flow diagram — NOT circular, two rows with wrap]
+
+Row 1 (left to right, with right arrows):
+[LPs provide liquidity] → [Traders use the pool] → [Swaps generate fees]
+ ↓ (down arrow)
+Row 2 (right to left, with left arrows):
+[LPs are protected] ← [Buffer pays capped IL] ← [Buffer slice skimmed]
+ ↑ (up arrow on far left connecting back to Row 1 start)
+
+Box styling: card bg #1e2433, white text, rounded corners
+Arrow color: accent green #00d395
+Highlight boxes 4 and 5 (Buffer slice skimmed, Buffer pays capped IL)
+with accent color border
+
+[Single line — accent color, centered, bottom]
+Swap activity funds the buffer that protects LPs.
+Speaker notes:
+
+"The economics are self-sustaining. LPs provide liquidity, traders use the pool, swaps generation funds the coverage buffer, and the buffer pays capped IL claims. The buffer grows from the same swap activity that LP liquidity enables. No external capital required."
+
+
+Slide 4 — Five Pillars
+[Label] THE SOLUTION
+[Icon top-left — 24px]
+
+[Headline]
+How RangeGuard Works: Five Pillars
+
+[Five items — numbered, left aligned, evenly spaced]
+
+1. Accrual Gating
+ Coverage accrues only while the position is in range
+
+2. Buffer Funding
+ A portion of every swap fee funds the coverage buffer
+
+3. Claim Settlement
+ IL computed and paid automatically on withdrawal
+ Payout = min(covered IL, earned coverage, buffer cap)
+
+4. LP Transparency ★
+ A verifiable, day-by-day coverage report —
+ built entirely from on-chain events
+ [Highlighted — accent color left border or background]
+
+5. Pool Configuration
+ Immutable parameters set once at pool initialization
+
+[Bottom — accent color]
+Every pillar is enforced on-chain. No off-chain assumptions.
+Speaker notes:
+
+"RangeGuard is built on five pillars. — coverage only earns while in range. Buffer funding — every swap contributes automatically. Claim settlement — IL is computed and paid in the same withdrawal transaction, capped by three limits: covered IL, earned coverage, and buffer capacity. LP transparency — the key differentiator — a verifiable day-by-day coverage statement from pure on-chain events. And pool configuration — immutable parameters, so LPs always know what they signed up for."
+
+Verbal transition:
+
+"Let me show you the core functions that make this work."
+
+
+Slide 5 — Code Walkthrough
+[Label] UNDER THE HOOD
+[Icon top-left — 24px]
+
+[Headline]
+How Coverage Becomes a Claim
+
+[Horizontal lifecycle flow — 6 boxes, ALL SAME COLOR]
+All boxes: card bg #1e2433, white text, accent color border
+Arrow color: accent green
+
+LP Deposits → _accrue() → afterSwap() → _computeIL() → _computePayout() → ClaimSettled
+
+Note: "ClaimSettled" — ensure correct spelling, no spell-check underline
+
+[Two amber callout boxes — side by side]
+
+Left:
+⚡ Swaps never iterate positions
+ Accrual is lazy, position-specific
+ O(1) gas on every swap
+
+Right:
+⚡ Actual/365 Fixed day-count
+ Coverage = Notional × APR × (days ÷ 365)
+ Same convention as fixed-income finance
+
+[Bottom — accent color]
+One lifecycle. Fully on-chain. No off-chain assumptions.
+Speaker notes:
+
+"Before the demo, here's the core code path. _accrue advances coverage lazily — only while in range, using Actual/365 Fixed day-count math. _computePayout applies three caps in order. The day-count convention turns coverage from a black-box reward into an auditable financial accrual. Let me show you."
+
+IDE narration:
+
+"_accrue: range gate first — if the tick is outside the LP's bounds, delta is zero. If in range — year fraction from day-count basis, multiplied by entry notional and APR. Fifteen days in range earns exactly 15/365 of the annual coverage amount. _computePayout: three caps in order — IL cap, earned coverage cap, buffer cap. Minimum of all three. Limiting faceived what they received."
+
+
+Slide 6 — Closing
+[Logo — logo-standalone.svg, top center]
+
+[Tagline — accent color, centered]
+"Protect your liquidity. Guard your range."
+
+[Four bullets — white]
+✓ Native IL coverage — built into the pool, not bolted on
+✓ Self-funding buffer — swap fees cover the claims
+✓ Capped payouts — actuarially sound, buffer protected
+✓ Fully auditable — every accrual verifiable on-chain
+
+[Beneficiary table — card bg]
+Who Benefits Why It Matters
+───────────────── ──────────────────────────────────
+Passive LPs Downside protection without complex hedging
+Protocols / DAOs Deeper liquidity without reliance on emissions
+Uniswap Ecosystem Durable liquidity, tighter spreads, better execution
+
+[Closing line — large, accent color, centered]
+Earned over time. Funded by swaps. Settled on-chain.
+
+[Divider]
+
+[Two columns — links]
+🔗 rangowered by [Reactive Network Logo]
+
+[Very bottom — slate gray]
+292 tests passing · Fuzz tested · Invariant tested
+Speaker notes:
+
+"RangeGuard makes providing liquidity safer, more transparent, and more attractive — helping Uniswap pools retain and grow their LP base. The primary beneficiaries are passive LPs who get downside protection without complex hedging. But the impact extends to protocols that need sticky liquidity without emissions, and to the broader ecosystem through deeper pools and better execution. Earned over time. Funded by swaps. Settled on-chain. Live dashboard at range-guard.vercel.app. Full source and 292 passing tests on GitHub. Thank you."
+
+
+After generating the deck:
+
+Present docs/RangeGuard-Demo-Deck.pptx for download
+Update docs/session-15-slides.md with changes made
+Update project-status.md
+Do NOT write additional closing docs — session is ongoing - keep this prompt and add it to the session-15 closing document when given the instruction to update the closing documents
+```
+
+---
+
+## 2. Logo variants generated and their usage
+
+All logos live in `docs/assets/`. The mark: a **classic shield** (gradient stroke `#FF007A → #9B59B6`
+left→right, transparent fill) wrapping **three green (`#00d395`) vertical bars** — a "range" bar-chart
+guarded by a shield. Bars: left 60% / center 85% / right 45% height, 12%-width, 8% gaps, centered +
+bottom-aligned (center bar is rendered at ~80% rather than a literal 85% so it doesn't poke through
+the shield's rounded top).
+
+| File | Spec | Used in the deck |
+|---|---|---|
+| `logo-icon.svg` | 64×64, shield + bars, 3px stroke | Top-left corner of every content slide (2–5), ~24px, beside the section label; also the shield glyph in the Title & Closing lockups |
+| `favicon.svg` | 32×32, simplified, 2px stroke | Not embedded in the deck — favicon asset for the frontend/site |
+| `logo-standalone.svg` | 300×64, icon + "RangeGuard" (Calibri Bold 32, white) | Closing-slide lockup (composed natively — see §4) |
+| `logo-standalone-light.svg` | as above, name `#0f1117` (dark) | Light-background placements (added on request) |
+| `logo-full.svg` | 360×140, icon + name (36, white) + tagline (16, `#94a3b8`) | Title-slide lockup (composed natively — see §4) |
+| `logo-full-light.svg` | as above, name `#0f1117`, tagline `#4a5568` | Light-background placements (added on request) |
+
+**Partner logos** (fetched from official sources):
+
+| File | Source | Used in the deck |
+|---|---|---|
+| `uniswap-logo.svg` | cryptologos.cc — the pink unicorn mark (`#F50DB4`) | Title + Closing, under "Built on" |
+| `reactive-logo.svg` | `dev.reactive.network/img/rn-docs-logo-white.svg` | Title + Closing, under "Powered by" |
+| `reactive-logo-dark.svg` | `dev.reactive.network/img/rn-docs-logo-black.svg` | Dark-text variant kept for light backgrounds |
+
+The Reactive asset is a `reactive | Dev` lockup; for the embedded partner badge the `| Dev` suffix is
+cropped off (`keep_left=0.685` in `build_assets.py`) to leave a clean `reactive` icon + wordmark.
+
+---
+
+## 3. Slide-structure decisions (9 → 6 slides)
+
+Removed from the prior 9-slide deck and why:
+- **Old Slides 1–2 (IL explanation: statement/mechanism + the math)** — removed; the audience
+ (Uniswap Hook Incubator judges) already understands impermanent loss, so the deck opens on the
+ solution instead of teaching the problem.
+- **Old Slide 7 (Demo Script transition)** and **old Slide 8 (Coverage Report transition)** —
+ removed; these were lead-ins to the live demo/dashboard, which the recorded video now covers
+ directly, so the static deck doesn't need transition slides.
+- **Added a Title slide** (logo, presenter, partner attributions) as the new Slide 1.
+
+Final 6 slides:
+1. **Title** — full lockup (shield + "RangeGuard" + tagline) centered; "Gary Kocsis / Uniswap Hook
+ Incubator"; "Built on [Uniswap] · Powered by [Reactive]".
+2. **The Solution** — headline + 5 two-line bullets + tagline.
+3. **Economic Flywheel** — two-row flow **loop** (not circular): row 1 L→R, down arrow on the right,
+ row 2 R→L, up arrow on the left back to the start; the two buffer boxes accent-bordered.
+4. **Five Pillars** — numbered; Pillar 4 (LP Transparency ★) highlighted with an accent border.
+5. **Code Walkthrough** — 6-box lifecycle (`LP Deposits → _accrue() → afterSwap() → _computeIL() →
+ _computePayout() → ClaimSettled`), all boxes the same accent-bordered style; two amber callouts.
+6. **Closing** — standalone lockup top center; ✓ bullets; beneficiary table; "Earned over time.
+ Funded by swaps. Settled on-chain."; dashboard + GitHub links; partner logos; 292-tests footer.
+
+Full speaker notes (including verbal transitions and IDE narration) are carried in every slide's
+notes panel, verbatim from the prompt.
+
+Design system (all slides): bg `#0f1117` · white `#ffffff` · accent `#00d395` · slate `#94a3b8` ·
+amber `#f59e0b` · danger `#ef4444` · card `#1e2433`; Calibri; 16:9 (13.333"×7.5").
+
+---
+
+## 4. Build pipeline & verification
+
+python-pptx **cannot embed SVG**. The only local renderer (macOS `qlmanage`) composites SVGs on an
+opaque **white** background, so `docs/build_assets.py` renders each needed SVG with a matching navy
+(`#0f1117`) background rect, **keys that navy to transparent**, crops to a tight content box, and
+writes PNGs to `docs/assets/png/`. `docs/build_deck.py` embeds those PNGs (sized by height, aspect
+from the PNG) and lays out all native text.
+
+**Build order:** `python3 docs/build_assets.py` → `python3 docs/build_deck.py`.
+
+Verified by rendering the `.pptx` to slide images via **LibreOffice headless** (LibreOffice + poppler
+installed this session for visual QA — macOS has no Quick Look generator for `.pptx`). All six slides
+were eyeballed; the only fix surfaced by rendering was a faint raster seam under downscaled tagline
+text, resolved by switching the Title/Closing wordmarks to native text (see Deviations).
+
+Numbers: 292 tests cited (per the prompt); where slides reference demo figures they use the **real
+fork run** from `docs/demo-run-output.md` (entry notional 228.38, total coverage 12.51, payout 2.23
+USDC bound by IL_CAP) — matching the frontend `?demo=true` view.
+
+---
+
+## 5. Recorded demo
+
+- **URL:** https://www.youtube.com/watch?v=82_9mEh_POM
+- **Runtime:** ~**3m 53s** (under the 5-minute target in spec §15).
+- **Linked in:** `README.md` ("Demo video:") and the live dashboard context.
+- Content follows `docs/demo-narrative.md`: the terminal segment (`RangeGuardDemo.s.sol`, fork +
+ `vm.warp` 45-day lifecycle) and the coverage-report segment (the `?demo=true` dashboard view),
+ with the Reactive-Network cross-chain automation as the differentiator.
+
+---
+
+## 6. Deviations from plan
+
+1. **Title/Closing logos are native Calibri text, not embedded `logo-full.svg` / `logo-standalone.svg`
+ images.** Rasterizing the full SVGs (text included) and downscaling them in the renderer produced a
+ faint full-width seam under the tagline/wordmark. Composing the lockups from the shield **icon**
+ (raster) + **native pptx text** is crisper, on-brand (Calibri), and artifact-free. The full SVG
+ lockups remain as standalone brand assets in `docs/assets/`.
+2. **Light logo variants** (`logo-full-light.svg`, `logo-standalone-light.svg`) were added on request
+ mid-Phase-1 — not in the original four-variant spec.
+3. **Reactive partner logo** came as a `reactive | Dev` docs lockup; the `| Dev` suffix is cropped for
+ a clean "Powered by" badge. Uniswap's official mark is `#F50DB4` (current brand pink), distinct
+ from the deck's gradient-stroke `#FF007A`.
+4. **Tooling installed for QA:** LibreOffice + poppler (for `.pptx`→image rendering). Earlier in the
+ session, the pptx `SKILL.md` referenced by the original 9-slide prompt (`/mnt/skills/public/pptx/`)
+ did not exist on this machine, so the deck was built directly with `python-pptx` (pip-installed).
+5. **Center bar height** rendered at ~80% of shield height rather than a literal 85% (a literal 85%
+ poked through the shield's rounded top); reads as intended.
+6. **Demo runtime** came in at ~3m 53s, comfortably under the 5-minute target.
+
+---
+
+## 7. Carry-ins / remaining
+
+- **Next:** coverage + gas snapshot (`forge coverage` / `forge snapshot`), then the full README
+ write-up. The demo is recorded + linked and the slides are done.
+- Deck and logos are reproducible from source via the two builders; PNGs in `docs/assets/png/` are
+ committed so the deck rebuilds without needing `qlmanage`/macOS.
diff --git a/project-status.md b/project-status.md
index 2c5a1a84..24cac62f 100644
--- a/project-status.md
+++ b/project-status.md
@@ -1,5 +1,5 @@
RangeGuard Project Status
-Last Updated: 2026-06-07 (Session 14 — frontend dashboard / coverage report)
+Last Updated: 2026-06-08 (Session 15 — Google Slides demo deck)
How to use this file
The Roadmap is the single source of truth for progress — one checkbox per item.
@@ -14,9 +14,28 @@ invariant; correctness before gas.
Now
-Active target: Recorded 5-minute demo (spec §15). All protocol code, deployment, demo tooling, and
-the frontend dashboard are complete. Remaining work is the recording itself + the full README/demo
-video write-up afterward.
+Active target: Coverage + gas snapshot (forge coverage / forge snapshot), then the full README
+write-up. All protocol code, deployment, demo tooling, the frontend dashboard, the presentation
+deck, AND the recorded demo are complete.
+
+Just completed (Session 15): Presentation deck + RangeGuard logo, and the recorded 5-minute demo
+(uploaded to YouTube). The deck was REBUILT from the original 9-slide version to a tighter 6-slide
+narrative (Title / The Solution / Economic Flywheel / Five Pillars / Code Walkthrough / Closing),
+a custom logo was designed, and the demo was recorded and published.
+- DEMO VIDEO: https://www.youtube.com/watch?v=82_9mEh_POM (~3m 53s; linked in README.md).
+- DECK (docs/RangeGuard-Demo-Deck.pptx): 16:9, dark-navy design system, Calibri, full speaker notes
+ on every slide (incl. verbal transitions + IDE narration). Title + Closing wordmark lockups are
+ native Calibri text + the shield icon (crisper); shield icon top-left on content slides; Uniswap +
+ Reactive partner logos on Title + Closing. 292 tests cited.
+- LOGO (docs/assets/): shield (gradient stroke #FF007A→#9B59B6) wrapping three green #00d395
+ bar-chart bars. SVGs: logo-icon.svg, favicon.svg, logo-standalone.svg(+ -light),
+ logo-full.svg(+ -light). Partner logos from official sources: uniswap-logo.svg (pink unicorn),
+ reactive-logo.svg(+ -dark wordmark).
+- RASTERIZE PIPELINE: python-pptx can't embed SVG; docs/build_assets.py renders each needed SVG→PNG
+ on a navy bg (qlmanage composites on white, so we inject a #0f1117 rect, key to transparent, crop)
+ → build_deck.py embeds the PNGs. Build order: build_assets.py then build_deck.py. Verified by
+ rendering to slide images via LibreOffice headless.
+-> docs/session-15-slides.md
Just completed (Session 14): Frontend dashboard — coverage report (frontend/, React 18 + Vite +
Tailwind + viem, no backend). Renders the LP coverage report from LIVE Sepolia on-chain events;
@@ -246,7 +265,16 @@ Reactive contract ✅ (complete — see Completed section / session-10 doc)
- [x] Frontend dashboard (Session 14: coverage report from LIVE Sepolia events; React+Vite+viem at
frontend/; live mode + ?demo=true simulated mode; Vercel https://range-guard.vercel.app —
see docs/session-14-frontend.md)
-- [ ] Recorded 5-minute demo (spec §15) + full README / demo video write-up ← NOW
+- [x] Presentation deck + logo (Session 15): docs/RangeGuard-Demo-Deck.pptx — REBUILT to a 6-slide
+ Google-Slides-ready .pptx (Title / Solution / Flywheel / Five Pillars / Code Walkthrough /
+ Closing; 16:9, dark-navy design system, Calibri, full speaker notes, embedded logos). Custom
+ RangeGuard logo (shield + bar-chart) in docs/assets/ — logo-icon.svg, favicon.svg,
+ logo-standalone.svg (+ -light), logo-full.svg (+ -light), plus Uniswap (uniswap-logo.svg) +
+ Reactive (reactive-logo.svg, + -dark) partner logos. Built via docs/build_assets.py (SVG→PNG) +
+ docs/build_deck.py — see docs/session-15-slides.md
+- [x] Recorded 5-minute demo (spec §15) (Session 15): recorded + uploaded to YouTube
+ https://www.youtube.com/watch?v=82_9mEh_POM (~3m 53s); linked in README.md
+- [ ] Coverage + gas snapshot (forge coverage / forge snapshot) + full README write-up ← NOW
Phase 4: Protocol Invariants (cross-cutting)