Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6853a6c
fix: hide bottom nav and suppress pull-to-refresh when any sheet is open
GraysonCAdams Mar 2, 2026
7b12c93
fix: scroll inputs into view when iOS keyboard appears on sign-in scr…
GraysonCAdams Mar 2, 2026
525201d
fix: theme-aware comment sheet bg and GIF pill inside input
GraysonCAdams Mar 2, 2026
1f28dd6
fix: send icon inside input, compact input height, smaller sheet
GraysonCAdams Mar 2, 2026
9de575e
fix: vertically center input actions on single-line, bottom-anchor on…
GraysonCAdams Mar 2, 2026
8e4ffd0
fix: show accurate hint when push unsupported on installed iOS PWA
GraysonCAdams Mar 2, 2026
94d09bc
fix: pin textarea to 1-line height so placeholder aligns with icons
GraysonCAdams Mar 2, 2026
d8fffb7
feat: tiktok-style layout with progress bar above comment bar
GraysonCAdams Mar 2, 2026
01ddb9c
fix: strip textarea native appearance, snap to 1-line height on clear
GraysonCAdams Mar 2, 2026
b4a56e2
fix: tighten bottom ui stack and center progress bar between comment …
GraysonCAdams Mar 2, 2026
0c14756
fix: lower clip overlay ui to safe-area edge, vertically center view …
GraysonCAdams Mar 2, 2026
5f2f607
refactor: replace phosphor reaction icons with native emoji in picker
GraysonCAdams Mar 2, 2026
d30d799
feat: add reel-bg tokens and feed-context class for iOS status bar
GraysonCAdams Mar 2, 2026
d03af8e
feat: add Faves screen with grid and reel view
GraysonCAdams Mar 2, 2026
e81d90f
feat: remove favorites filter from feed, add push nudge on end-slide
GraysonCAdams Mar 2, 2026
2f4bac9
fix: various server and service worker improvements
GraysonCAdams Mar 2, 2026
1dd86fe
fix: remove faded opacity on comment reaction pills
GraysonCAdams Mar 2, 2026
a1b8481
fix: align input actions with first text line using fixed top offset
GraysonCAdams Mar 2, 2026
b4bc2e3
fix: equalize input bar top/bottom padding to 12px
GraysonCAdams Mar 2, 2026
a97bba9
fix: remove user-scalable=no from viewport meta
GraysonCAdams Mar 2, 2026
a30d119
fix: migrate ProgressBar to unified pointer events with setPointerCap…
GraysonCAdams Mar 2, 2026
56e1a4a
fix: migrate CommentRow long-press to pointer events
GraysonCAdams Mar 2, 2026
54e72c5
fix: migrate feed gestures to pointer events, add touch-action
GraysonCAdams Mar 2, 2026
6121756
fix: migrate ClipOverlay swipe-to-dismiss to pointer events
GraysonCAdams Mar 2, 2026
3576776
feat: add drag-to-dismiss gesture to BaseSheet
GraysonCAdams Mar 2, 2026
62f3345
fix: center comment input action buttons vertically on single line
GraysonCAdams Mar 2, 2026
54819b3
feat: expand sheet drag zone to cover full header, not just grip bar
GraysonCAdams Mar 2, 2026
8e426fb
feat: add slide-in entry animation to ClipOverlay, guard swipe during…
GraysonCAdams Mar 2, 2026
4091f23
fix: adjust CommentInput vertical padding for optical centering
GraysonCAdams Mar 2, 2026
39ca0fb
refactor: move AddVideoModal to app layout, accessible from all pages
GraysonCAdams Mar 2, 2026
b09d965
fix: add comment prompt skeleton bone, adjust reel overlay bone posit…
GraysonCAdams Mar 2, 2026
95e2cfa
feat: add compact GIF carousel, expand comments sheet when GIF picker…
GraysonCAdams Mar 2, 2026
6debaca
feat: redesign reel bottom row, move MusicDisc to sidebar, improve pl…
GraysonCAdams Mar 2, 2026
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
13 changes: 12 additions & 1 deletion src/app.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, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
<meta name="description" content="Private video sharing for your friend group" />
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)" />
Expand Down Expand Up @@ -38,6 +38,17 @@
}
} catch(e) { /* ignore malformed cookie */ }
}
// Feed route: set dark background immediately (before JS hydrates) so the iOS
// black-translucent status bar sees the correct reel-bg color on cold start.
if (window.location.pathname === '/') {
document.documentElement.classList.add('feed-context');
var pref = m && m[1] !== 'system' ? m[1] : 'system';
var isDark = pref === 'dark' || (pref !== 'light' && window.matchMedia('(prefers-color-scheme: dark)').matches);
var reelBg = isDark ? '#0d0d0d' : '#1c1c1c';
document.querySelectorAll('meta[name="theme-color"]').forEach(function(el) {
el.setAttribute('content', reelBg);
});
}
})();
</script>
</head>
Expand Down
56 changes: 39 additions & 17 deletions src/lib/components/ActionSidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import HeartIcon from 'phosphor-svelte/lib/HeartIcon';
import ChatIcon from 'phosphor-svelte/lib/ChatIcon';
import ArrowSquareOutIcon from 'phosphor-svelte/lib/ArrowSquareOutIcon';
import MusicDisc from './MusicDisc.svelte';

const {
favorited,
Expand All @@ -15,6 +16,12 @@
muted = true,
uiHidden = false,
isOwn = false,
albumArt = null,
spotifyUrl = null,
appleMusicUrl = null,
youtubeMusicUrl = null,
active = false,
paused = false,
onsave,
oncomment,
onreactionhold,
Expand All @@ -28,6 +35,12 @@
muted?: boolean;
uiHidden?: boolean;
isOwn?: boolean;
albumArt?: string | null;
spotifyUrl?: string | null;
appleMusicUrl?: string | null;
youtubeMusicUrl?: string | null;
active?: boolean;
paused?: boolean;
onsave: () => void;
oncomment: () => void;
onreactionhold?: (x: number, y: number) => void;
Expand Down Expand Up @@ -121,9 +134,7 @@
>
<span class="icon-circle" class:pop={justSaved}>
{#if reactedEmoji && reactedEmoji !== '❤️' && REACTION_MAP.has(reactedEmoji)}
{@const def = REACTION_MAP.get(reactedEmoji)!}
{@const ReactionIcon = def.component}
<ReactionIcon size={24} weight={def.weight} />
<span class="reaction-emoji">{reactedEmoji}</span>
{:else}
<HeartIcon size={24} weight={favorited ? 'fill' : 'regular'} />
{/if}
Expand All @@ -148,26 +159,30 @@
{/if}
</button>

<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -- external URL, not app navigation -->
<a
href={originalUrl}
target="_blank"
rel="noopener noreferrer"
class="sidebar-btn"
onclick={stop}
aria-label="Open original"
>
<span class="icon-circle">
<ArrowSquareOutIcon size={24} />
</span>
</a>
{#if albumArt}
<MusicDisc {albumArt} {spotifyUrl} {appleMusicUrl} {youtubeMusicUrl} {active} {paused} />
{:else}
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -- external URL, not app navigation -->
<a
href={originalUrl}
target="_blank"
rel="noopener noreferrer"
class="sidebar-btn"
onclick={stop}
aria-label="Open original"
>
<span class="icon-circle">
<ArrowSquareOutIcon size={24} />
</span>
</a>
{/if}
</div>

<style>
.action-sidebar {
position: absolute;
right: var(--space-lg);
bottom: calc(var(--bottom-nav-height, 64px) + 88px);
bottom: calc(var(--bottom-nav-height, 64px) + 84px);
display: flex;
flex-direction: column;
align-items: center;
Expand Down Expand Up @@ -221,6 +236,13 @@
filter: drop-shadow(0 1px 2px var(--reel-icon-shadow));
}

.reaction-emoji {
font-size: 22px;
line-height: 1;
user-select: none;
pointer-events: none;
}

.sidebar-btn.active .icon-circle {
background: color-mix(in srgb, var(--accent-magenta) 20%, transparent);
}
Expand Down
117 changes: 95 additions & 22 deletions src/lib/components/BaseSheet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { Snippet } from 'svelte';
import { pushState, beforeNavigate } from '$app/navigation';
import { onDestroy } from 'svelte';
import { openSheet, closeSheet } from '$lib/stores/sheetOpen';

let {
title = '',
Expand All @@ -23,6 +24,54 @@
let closedViaBack = false;
let timers: ReturnType<typeof setTimeout>[] = [];

let dragZoneEl: HTMLElement | null = $state(null);
let dragY = $state(0);
let dragging = $state(false);
let dragTracking = false;
let dragStartY = 0;
let dragStartX = 0;
const DRAG_COMMIT_THRESHOLD = 6; // px of downward motion before locking to a drag
const DRAG_DISMISS_THRESHOLD = 120;

function startDrag(e: PointerEvent) {
dragTracking = true;
dragStartY = e.clientY;
dragStartX = e.clientX;
dragY = 0;
}

function moveDrag(e: PointerEvent) {
if (!dragTracking) return;
const dy = e.clientY - dragStartY;
const dx = e.clientX - dragStartX;

if (!dragging) {
// Commit to drag only when moving clearly downward
if (dy > DRAG_COMMIT_THRESHOLD && dy > Math.abs(dx)) {
dragging = true;
dragZoneEl?.setPointerCapture(e.pointerId);
} else if (Math.abs(dx) > DRAG_COMMIT_THRESHOLD || dy < -DRAG_COMMIT_THRESHOLD) {
dragTracking = false;
}
return;
}

dragY = Math.max(0, dy);
}

function endDrag() {
if (!dragTracking) return;
dragTracking = false;
if (!dragging) return;
dragging = false;
if (dragY > DRAG_DISMISS_THRESHOLD) {
dragY = 0;
dismiss();
} else {
dragY = 0;
}
}

// Prevent history.back() in cleanup when a real navigation occurs (e.g. clicking a link inside the sheet)
beforeNavigate(() => {
closedViaBack = true;
Expand All @@ -36,6 +85,7 @@

// Animate in, lock scroll, manage history
$effect(() => {
openSheet();
requestAnimationFrame(() => {
visible = true;
});
Expand All @@ -49,6 +99,7 @@
window.addEventListener('popstate', handlePopState);

return () => {
closeSheet();
document.body.style.overflow = '';
window.removeEventListener('popstate', handlePopState);
if (!closedViaBack) history.back();
Expand All @@ -65,28 +116,43 @@

<div class="base-overlay" class:visible onclick={dismiss} role="presentation"></div>

<div class="base-sheet" class:visible>
{#if showHandle}
<div
class="base-handle-bar"
onclick={dismiss}
onkeydown={(e) => {
if (e.key === 'Enter' || e.key === ' ') dismiss();
}}
role="button"
tabindex="-1"
>
<div class="base-handle"></div>
</div>
{/if}

{#if header}
{@render header()}
{:else if title}
<div class="base-header">
<span class="base-title">{title}</span>
</div>
{/if}
<div
class="base-sheet"
class:visible
class:dragging
style:transform={dragY > 0 ? `translateY(${dragY}px)` : undefined}
>
<div
class="base-drag-zone"
bind:this={dragZoneEl}
onpointerdown={startDrag}
onpointermove={moveDrag}
onpointerup={endDrag}
onpointercancel={endDrag}
role="presentation"
>
{#if showHandle}
<div
class="base-handle-bar"
onclick={dismiss}
onkeydown={(e) => {
if (e.key === 'Enter' || e.key === ' ') dismiss();
}}
role="button"
tabindex="-1"
>
<div class="base-handle"></div>
</div>
{/if}

{#if header}
{@render header()}
{:else if title}
<div class="base-header">
<span class="base-title">{title}</span>
</div>
{/if}
</div>

{@render children()}
</div>
Expand Down Expand Up @@ -120,6 +186,13 @@
.base-sheet.visible {
transform: translateY(0);
}
.base-sheet.dragging {
transition: none;
}

.base-drag-zone {
touch-action: none;
}

.base-handle-bar {
display: flex;
Expand Down
Loading
Loading