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
4 changes: 4 additions & 0 deletions .bolt/skills/slides/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ to the screen, so nothing scales-and-clips:
with **`.appmock`** (sidebar + content; sidebar column collapses on phones) and
put **`.hide-narrow`** on chrome that should vanish on small screens.
- **One idea per slide**, sized to fill ~one screen with deliberate negative space.
- **Phones (≤640px)**: a slide taller than the screen scrolls vertically instead of
clipping, and swiping left/right pages the deck (`StatGrid` also compacts to
2-up cards there — built in). Still aim to fit one screen; scroll is the escape
hatch, not the layout plan.
- **Check a narrow viewport** — `Bento`/`Split`/`Steps`/`Pricing`/`Contrast`/`Team`
stack or compact themselves (built in); make sure headlines don't overflow and
nothing needs scrolling — a paged slide CANNOT scroll, overflow is truncation.
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<!-- Always replace the title + favicon emoji to match the deck topic -->
<title>Replace — your deck title</title>
<link
Expand Down
43 changes: 43 additions & 0 deletions src/deck/Deck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
← / ↑ previous S sidebar G grid view
Home / End first / last A annotate P presenter (new tab)
F fullscreen H hide/show the UI
swipe ← / → next / previous on touch (vertical drags scroll the slide)
Copy verbatim; theme only via the :root tokens. ───────────────────────── */

const fmt = (s: number) =>
Expand Down Expand Up @@ -263,6 +264,48 @@ export default function Deck({ children }: { children: ReactNode }) {
return () => window.removeEventListener('keydown', onKey);
}, [next, prev, go, total, toggleRail, toggleGrid, toggleFs, openPresenter]);

// touch: a quick horizontal swipe pages; vertical drags stay free so a
// slide taller than a phone screen can scroll (see base.css ≤640px)
useEffect(() => {
let x0 = 0,
y0 = 0,
t0 = 0,
live = false;
const onStart = (e: TouchEvent) => {
const t = e.target as HTMLElement | null;
if (
e.touches.length !== 1 ||
t?.closest(
'.noir-dock,.noir-rail,.noir-grid,.noir-presenter,.ann-bar,.ann-canvas,button,a,textarea,input'
)
) {
live = false;
return;
}
live = true;
x0 = e.touches[0].clientX;
y0 = e.touches[0].clientY;
t0 = performance.now();
};
const onEnd = (e: TouchEvent) => {
if (!live) return;
live = false;
const dx = e.changedTouches[0].clientX - x0;
const dy = e.changedTouches[0].clientY - y0;
// slow drags and diagonal/vertical moves are scroll intent, not paging
if (performance.now() - t0 > 600) return;
if (Math.abs(dx) < 48 || Math.abs(dx) < 1.5 * Math.abs(dy)) return;
if (dx < 0) next();
else prev();
};
window.addEventListener('touchstart', onStart, { passive: true });
window.addEventListener('touchend', onEnd, { passive: true });
return () => {
window.removeEventListener('touchstart', onStart);
window.removeEventListener('touchend', onEnd);
};
}, [next, prev]);

// safety net: if the authored deck kept the placeholder tab title, derive
// one from the current slide's heading so shared links look right.
useEffect(() => {
Expand Down
78 changes: 71 additions & 7 deletions src/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,61 @@ strong {
grid-template-columns: 180px 1fr;
}
@media (max-width: 640px) {
/* a slide taller than the screen scrolls instead of clipping both ends
(flex centering can't overflow-scroll upward) — `safe center` keeps short
slides centered and degrades to top-aligned where unsupported; the extra
bottom padding only bites on slides that scroll, clearing the dock */
.slide {
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
justify-content: flex-start;
justify-content: safe center;
padding-bottom: calc(var(--gutter-y) + 92px);
}
/* the -15% atmosphere bleed (and drift's scale) would give every slide
~140px of phantom scroll once slides are scroll containers — pin the
gradient static; the 26s drift isn't perceptible at phone size */
.slide::before {
inset: 0;
animation: none;
transform: none;
}
/* stacked split: media keeps its natural height instead of being crushed
into the leftover row, and the slide scrolls when the stack runs long */
.slide.full {
padding-bottom: 0;
}
.split {
height: auto;
min-height: 100%;
grid-template-rows: auto minmax(min-content, 1fr);
padding-bottom: 92px;
}
/* stat grid: 2-up compact cards so six stats fit one phone screen —
full-width stacking runs ~1.5 screens tall */
.stat-grid {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.stat-card {
padding: 14px 12px;
gap: 4px;
}
.stat-card .tick {
margin-bottom: 2px;
}
.stat-value {
font-size: clamp(24px, 7vw, 34px);
}
.stat-label {
font-size: 13.5px;
margin-top: 4px;
}
.stat-caption {
font-size: 12px;
line-height: 1.35;
}
.appmock {
grid-template-columns: 1fr;
}
Expand All @@ -1841,17 +1896,26 @@ strong {
.ch-donut {
--ch-size: 120px !important;
}
/* data tables keep their column integrity and pan horizontally instead */
/* data tables FIT the phone width — sideways panning buries the far
columns. Type/pads shrink, headers and label cells wrap; number strings
carry no break points so they stay whole. overflow-x remains only as the
escape hatch for genuinely too-wide tables. */
.dtable {
overflow-x: auto;
}
.dtable table {
min-width: 540px;
min-width: 0;
font-size: 11px;
}
.dtable th {
font-size: 9px;
letter-spacing: 0.05em;
padding: 7px 6px;
white-space: normal;
}
.dtable th,
.dtable td {
white-space: nowrap;
padding: 9px 12px;
padding: 8px 6px;
white-space: normal;
}
}

Expand Down Expand Up @@ -2716,7 +2780,7 @@ strong {
@media (max-width: 640px) {
.noir-dock {
opacity: 1;
bottom: 14px;
bottom: calc(14px + env(safe-area-inset-bottom, 0px));
display: flex;
flex-direction: column;
align-items: center;
Expand Down Expand Up @@ -2768,7 +2832,7 @@ strong {
/* annotation toolbar: one swipeable row on phones (wrapping made the
999px-radius pill collapse into a pinched oval); clears the taller dock */
.ann-bar {
bottom: 104px;
bottom: calc(104px + env(safe-area-inset-bottom, 0px));
flex-wrap: nowrap;
justify-content: flex-start;
overflow-x: auto;
Expand Down