A lightweight, modular slide system to build beautiful presentations fast using React, Vite, Tailwind v4, and Framer Motion — branded as “Gabe Presentations Styles”. Each presentation is a component; each slide is a tiny TSX file for maximum reuse and composability.
- React 18 + Vite 5
- Tailwind CSS v4 (via
@tailwindcss/vite) - TypeScript
- Framer Motion (animations)
- Bun (package manager & scripts)
- Install deps:
bun install - Dev server:
bun devthen open http://localhost:5173 - Build:
bun run build(outputs todist/) - Preview build:
bun run preview - Typecheck:
bun run typecheck
src/
App.tsx # routes
routes/
PresentationList.tsx # list of decks
DeckRunner.tsx # renders a selected deck
shared/
deck/ # Deck, Slide, controls, navigation
motion/ # animation presets
presentations/
index.ts # registry of all presentations
sample/ # example deck
storypoints/ # Story Points deck (slides as TSX files)
slides/
Title.tsx
HookJeffries.tsx
ComplexityTrap.tsx
Weaponization.tsx
AccuracyVsCounting.tsx
Goodhart.tsx
ValueOverEffort.tsx
FocusPrioritizationImpact.tsx
WhatToUseInstead.tsx
MiniExample.tsx
CloseSlide.tsx
Citations.tsx
- Create a deck component under
src/presentations/<your-deck>/slides/with one TSX per slide. - Export a router component that switches on
slide.id. - Register it in
src/presentations/index.tswith anid,title, andslidesarray. - Navigate to
/p/<id>or click from the homepage.
- Keep slides small and focused. Compose visuals using Tailwind and Framer Motion.
- Use the identity gradient with
bg-gradient-to-r from-accent to-accent2 bg-clip-text text-transparent. - Built-in transitions:
'fade' | 'slide' | 'up' | 'scale'. Seeshared/motion/presets.ts. - Keyboard:
←→andSpace. Mouse wheel supported.
- Config is plugin-based; no
tailwind.config.jsrequired. Tokens are defined insrc/styles/index.cssunder@theme. - Add global styles/tokens in
src/styles/index.css.
.github/workflows/ci.ymlruns install, typecheck, and build on pushes and PRs tomainusing Bun.
# First time
git init
git add .
git commit -m "chore: bootstrap presentations app"
# Create the main branch (if needed)
git branch -M main
# Set the remote and push
git remote add origin git@github.com:Nepomuceno/presentations.git
git push -u origin mainIf you don’t want to commit or publish some presentations:
- Create
src/presentations/private.tslocally (this file is gitignored). - Export an array of
PresentationEntrywith your private decks. - Public builds (including GitHub Pages) automatically use the empty stub
src/presentations/private.example.ts.
Example:
// src/presentations/private.ts (local only)
import type { PresentationEntry } from '../types'
import MyPrivateDeck from './my-private/MyPrivateDeck'
const privatePresentations: PresentationEntry[] = [
{
id: 'my-private',
title: 'Confidential Deck',
subtitle: 'Internal only',
slides: [{ id: 'title', transition: 'fade' }],
component: MyPrivateDeck,
},
]
export default privatePresentationsTo include private decks when developing or building locally, set:
VITE_INCLUDE_PRIVATE=true bun run dev
VITE_INCLUDE_PRIVATE=true bun run buildCI (GitHub Pages) runs without this flag, so only public decks are deployed.
This is a light, client‑side gate. The password/hash ships in the bundle, so use only as a deterrent (not true security).
- Generate a SHA‑256 hash of your password:
node -e "console.log(require('crypto').createHash('sha256').update('mySecret').digest('hex'))"- Add the hash to your deck entry (either public or private):
// e.g., in src/presentations/public.ts or your private.ts
{
id: 'vantage',
title: 'Vantage – Opportunity Brief',
subtitle: 'From Demonstrator to Greenfield Product',
passwordHash: 'e0b1...<your sha256 hex>...',
slides: [ /* ... */ ],
component: VantageDeck,
}- Run the app and open
/p/vantage. You’ll see a password prompt. Successful unlock persists inlocalStorageper deck.
Notes:
- Hash comparison happens client‑side using WebCrypto.
- Anyone can still extract the hash from the JS and brute force it offline. For real protection, front the site with Cloudflare Access or host behind auth.
- Create your deck under
src/presentations/<your-private-id>/with slides just like public decks. - Create
src/presentations/private.ts(gitignored) and export your entries. - Run locally with
VITE_INCLUDE_PRIVATE=true bun run dev. - Open the deck at
/p/<your-private-id>. - Optional: build a local bundle that includes private decks with
VITE_INCLUDE_PRIVATE=true bun run build(do not upload this to public hosting).
Troubleshooting:
- If you see “Failed to resolve import 'presentations-private'”, restart the dev server after creating
src/presentations/private.tsso Vite picks up the alias.
GitHub Pages does not support authentication. For simple gating beyond obscurity:
- Cloudflare Access in front of your Pages domain (SSO / one-time PIN; free tier available).
- Host on Netlify/Vercel and enable site-wide password (requires paid plan).
- Serve from your own server behind HTTP Basic Auth.
Client-side password prompts are possible but weak: the password and content ship in the JS bundle. Use only as a light deterrent.
If you want private decks in a separate repo:
- Add the private repo as a Git submodule:
git submodule add git@github.com:<you>/<private-decks>.git src/presentations/private-repo- In your local
src/presentations/private.ts(gitignored), import from that repo and re-export entries:
import type { PresentationEntry } from '../types'
import MyPrivateDeck from './private-repo/my-deck/MyPrivateDeck'
const privatePresentations: PresentationEntry[] = [
{ id: 'my-private', title: 'Private', slides: [ { id: 'title' } ], component: MyPrivateDeck },
]
export default privatePresentations- Work locally with
VITE_INCLUDE_PRIVATE=true bun run dev. CI will skip them and deploy only public decks.
MIT — see LICENSE.