Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions braden-partnership-deck/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Braden operating-partnership deck

Private 18-slide founder-to-founder presentation for Alex's next Biologix conversation.

## Files

- `index.html`: live presentation with embedded speaker notes
- `deck.css`: OVO visual system, product-preview UI, responsive and print layouts
- `deck.js`: keyboard, touch, fullscreen, notes, overview, progress and image-fallback behavior
- `talking-points.md`: full call script, system explanation, objections and negotiation boundaries
- `braden-operating-partnership.pdf`: static 18-slide leave-behind export

## Run locally

From this directory:

```sh
python3 -m http.server 4179 --bind 127.0.0.1
```

Open `http://127.0.0.1:4179/`.

## Controls

- Right arrow, down arrow, space, or Page Down: next slide
- Left arrow, up arrow, or Page Up: previous slide
- `F`: fullscreen
- `N`: speaker notes
- `O`: slide overview
- `B`: black screen
- Home / End: first or last slide

The URL hash opens a specific slide. For example, `#7`, `#8`, and `#9` open the executive cockpit, affiliate command center, and creator portal previews.

## Presentation discipline

- Slides 7 through 9 contain illustrative interface data only. They are concept previews, not Biologix actuals.
- Do not send the deck cold. Present it live and use the questions in `talking-points.md`.
- Do not lead with equity numbers. Establish the operating mandate and paid verification gate first.
- The pictured OVO creators demonstrate ecosystem capability. They are not represented as committed Biologix affiliates.
- No product-use demonstrations, dosing, medical advice, or undisclosed promotion belong in the retreat concept.

## Visual review

Claude Fable 5 authored the route-specific visual direction and reviewed rendered desktop and 430px mobile evidence. The final review returned `SHIP` after the mobile dashboard, photo contrast, palette, typography and collision issues were corrected.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
315 changes: 315 additions & 0 deletions braden-partnership-deck/deck.css

Large diffs are not rendered by default.

187 changes: 187 additions & 0 deletions braden-partnership-deck/deck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
(() => {
"use strict";

const slides = [...document.querySelectorAll(".slide")];
const stage = document.querySelector(".stage");
const progress = document.querySelector(".progress-bar");
const currentLabel = document.querySelector("[data-current-slide]");
const totalLabel = document.querySelector("[data-total-slides]");
const notesPanel = document.querySelector(".speaker-notes");
const notesBody = document.querySelector("[data-notes-body]");
const overview = document.querySelector(".overview-panel");
const overviewGrid = document.querySelector(".overview-grid");
const blank = document.querySelector(".blank-screen");
const curtain = document.querySelector(".loading-curtain");

let current = 0;
let previous = -1;
let transitionTimer;
let touchStartX = 0;
let touchStartY = 0;

const pad = (value) => String(value).padStart(2, "0");
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));

function fromHash() {
const requested = Number.parseInt(window.location.hash.slice(1), 10);
return Number.isFinite(requested) ? clamp(requested - 1, 0, slides.length - 1) : 0;
}

function updateNotes() {
const source = slides[current]?.querySelector(".notes");
if (notesBody) notesBody.innerHTML = source?.innerHTML || "<p>No notes for this slide.</p>";
}

function updateOverview() {
overviewGrid?.querySelectorAll(".overview-card").forEach((card, index) => {
card.setAttribute("aria-current", index === current ? "true" : "false");
});
}

function updateChrome() {
const active = slides[current];
document.body.classList.toggle("is-light-slide", active?.classList.contains("light"));
if (currentLabel) currentLabel.textContent = pad(current + 1);
if (progress) progress.style.width = `${((current + 1) / slides.length) * 100}%`;
document.title = `${active?.dataset.title || "Presentation"} · OVO Labs`;
}

function goTo(index, { replaceHash = false } = {}) {
const next = clamp(index, 0, slides.length - 1);
window.clearTimeout(transitionTimer);
previous = current;
current = next;

slides.forEach((slide, slideIndex) => {
slide.classList.remove("is-active", "was-active");
slide.setAttribute("aria-hidden", slideIndex === current ? "false" : "true");
if (slideIndex === current) slide.scrollTop = 0;
});

if (previous !== current && slides[previous]) slides[previous].classList.add("was-active");
slides[current]?.classList.add("is-active");
transitionTimer = window.setTimeout(() => {
slides.forEach((slide, indexToClear) => {
if (indexToClear !== current) slide.classList.remove("was-active");
});
}, 740);

const nextHash = `#${current + 1}`;
if (window.location.hash !== nextHash) {
window.history[replaceHash ? "replaceState" : "pushState"](null, "", nextHash);
}

updateChrome();
updateNotes();
updateOverview();
}

function togglePanel(panel) {
if (!panel) return;
const shouldOpen = !panel.classList.contains("is-open");
[notesPanel, overview].forEach((item) => item?.classList.remove("is-open"));
panel.classList.toggle("is-open", shouldOpen);
}

async function toggleFullscreen() {
try {
if (document.fullscreenElement) await document.exitFullscreen();
else await document.documentElement.requestFullscreen();
} catch (_) {
// Fullscreen can be blocked in embedded browsers; the deck remains usable.
}
}

function action(name) {
const actions = {
previous: () => goTo(current - 1),
next: () => goTo(current + 1),
notes: () => togglePanel(notesPanel),
overview: () => togglePanel(overview),
fullscreen: toggleFullscreen,
};
actions[name]?.();
}

function buildOverview() {
if (!overviewGrid) return;
const fragment = document.createDocumentFragment();
slides.forEach((slide, index) => {
const button = document.createElement("button");
button.type = "button";
button.className = "overview-card";
button.innerHTML = `<span>${pad(index + 1)} · ${slide.dataset.section || "Deck"}</span><strong>${slide.dataset.title || `Slide ${index + 1}`}</strong>`;
button.addEventListener("click", () => {
goTo(index);
overview.classList.remove("is-open");
});
fragment.appendChild(button);
});
overviewGrid.replaceChildren(fragment);
}

function handleKey(event) {
const key = event.key.toLowerCase();
if (blank?.classList.contains("is-open")) {
if (key === "b" || key === "escape") blank.classList.remove("is-open");
return;
}
if (key === "escape") {
notesPanel?.classList.remove("is-open");
overview?.classList.remove("is-open");
} else if (["arrowright", "arrowdown", "pagedown", " "].includes(key)) {
event.preventDefault();
goTo(current + 1);
} else if (["arrowleft", "arrowup", "pageup"].includes(key)) {
event.preventDefault();
goTo(current - 1);
} else if (key === "home") {
goTo(0);
} else if (key === "end") {
goTo(slides.length - 1);
} else if (key === "n") {
togglePanel(notesPanel);
} else if (key === "o") {
togglePanel(overview);
} else if (key === "f") {
toggleFullscreen();
} else if (key === "b") {
blank?.classList.add("is-open");
}
}

document.querySelectorAll("[data-action]").forEach((button) => {
button.addEventListener("click", () => action(button.dataset.action));
});
document.addEventListener("keydown", handleKey);
window.addEventListener("hashchange", () => goTo(fromHash(), { replaceHash: true }));

stage?.addEventListener("pointermove", (event) => {
const rect = stage.getBoundingClientRect();
stage.style.setProperty("--pointer-x", `${((event.clientX - rect.left) / rect.width) * 100}%`);
stage.style.setProperty("--pointer-y", `${((event.clientY - rect.top) / rect.height) * 100}%`);
});
stage?.addEventListener("touchstart", (event) => {
touchStartX = event.changedTouches[0]?.clientX || 0;
touchStartY = event.changedTouches[0]?.clientY || 0;
}, { passive: true });
stage?.addEventListener("touchend", (event) => {
const deltaX = (event.changedTouches[0]?.clientX || 0) - touchStartX;
const deltaY = (event.changedTouches[0]?.clientY || 0) - touchStartY;
if (Math.abs(deltaX) > 54 && Math.abs(deltaX) > Math.abs(deltaY)) goTo(current + (deltaX < 0 ? 1 : -1));
}, { passive: true });

document.querySelectorAll("img").forEach((image) => {
image.addEventListener("error", () => image.closest(".slide, figure")?.classList.add("image-unavailable"));
});

if (totalLabel) totalLabel.textContent = pad(slides.length);
buildOverview();
current = fromHash();
slides.forEach((slide) => slide.classList.remove("is-active", "was-active"));
goTo(current, { replaceHash: true });

Promise.resolve(document.fonts?.ready).finally(() => {
window.setTimeout(() => curtain?.classList.add("is-ready"), 220);
});
})();
Loading