Commit graph

764 commits

Author SHA1 Message Date
f780f0dab6 Redesign ring header into structured 2-row grid, show full now-playing title and thumbnail
Ring header restructured from flat 1fr/auto grid into two semantic rows:
- Info row: title + badges left, progress counter right
- Controls row: Reverse and Autoplay toggles right-aligned

Now-playing row shows full title (no line-clamp truncation) and product
thumbnail. JS reads og:image meta tag for current thumbnail during SPA
navigation. SPA updates for edit button, download button, file info,
comments, and canonical link. Footer and docs updates.
2026-02-09 11:46:54 -05:00
3ade78d512 Fix ring double-advance defect in DJ crossfade, use defect not bug in CLAUDE.md
completeDjCrossfade was advancing ringPosition after syncRingPosition
already set it correctly at the halfway mark, causing autoplay to skip
one song ahead of what the sidebar displayed. Removed the redundant
advance — syncRingPosition handles positioning via updatePageContent.
2026-02-09 11:31:46 -05:00
7f8a5e631a Show 3 previous + current (highlighted) + 21 next items in ring sidebar
Ring sidebar now displays a window around the current position: 3 items
behind (dimmed), the current item highlighted in blue with a play marker,
and 21 items ahead. Each item shows its signed ring offset (-3 to +21).

get_ring_related_products returns dicts with product and offset. Views
pass offset through to JS. Server-rendered template handles both formats.
2026-02-09 11:02:16 -05:00
98efbfe566 Track ring loop completions with +N badge and show mod badge for shop owners
When a viewer watches every item in the ring, the loop counter increments,
history resets (un-dims all items), and a +1/+2/+3 badge appears next to
the progress display. Shop owners/editors get a green "mod" badge.

Ring state: ringLoops persisted in localStorage alongside existing ring state.
Server: watch_json now returns is_mod flag. Templates pass data-watch-is-mod.
2026-02-09 10:57:56 -05:00
740001f09b Fix radio highlight not updating on subscribe page, default to None
Replace display:none on radio inputs with visually-hidden technique
(position:absolute, opacity:0) so :checked pseudo-class updates
reliably when clicking labels. Default frequency changed to None.
2026-02-09 10:52:45 -05:00
50bfb13309 Stack all watch-video-container children in same grid cell for DJ crossfade
grid-area: 1/1 was only on video elements, so non-video children (countdown
overlay, unmute button) could push videos into separate grid rows. Apply
grid-area: 1/1 to all children so both active and standby videos always
occupy the same x/y position with z-index layering.
2026-02-09 10:30:14 -05:00
df9270b3aa Add Immediate frequency option, reorder to None/Immediate/Daily/Weekly, default Daily
Subscription frequency was missing Immediate and defaulted to Weekly.
Now offers four options in logical order with Daily as the default digest.
2026-02-09 10:22:16 -05:00
7f3b3e2fee Add comment moderation dashboard at /s/{shop_id}/comments 2026-02-09 09:37:32 -05:00
5b39ab273a Anonymous commenting with email verification via OTP
Show comment form to all visitors instead of requiring sign-in first.
Anonymous users enter email + comment, verify via 6-digit OTP, then
their comment is created. Uses existing OTP infrastructure and honeypot
spam protection.
2026-02-09 09:11:13 -05:00
7257a588a0 Fix DJ crossfade event listener accumulation and non-idempotent preload
Named event handlers (onMediaEnded, onMediaLoadedMetadata, onMediaTimeUpdate)
replace anonymous functions so setupMediaEvents() can removeEventListener
on both active and standby elements before adding fresh listeners. This
prevents duplicate handlers from accumulating after each crossfade swap.

preloadNext() now tracks preloadingId to skip redundant fetches — prevents
presigned URL regeneration from invalidating already-buffered standby media.
2026-02-09 09:10:39 -05:00
08aceed3b8 Fix mobile watch mode: content scrolls under sticky video, add horizontal padding
- Give product-images, description, comments, and product-right opaque
  backgrounds so they slide beneath the sticky video instead of through it
- Add 4px horizontal padding to mobile two-column layout matching nav inset
2026-02-09 07:47:42 -05:00
836d78d310 Move ring.json route before shop_slug catch-all so it matches 2026-02-09 07:44:07 -05:00
b933daffd9 Forge ring on first access, add /s/{shop_id}/ring.json endpoint
Existing shops had no ring until a product edit triggered it.
Now watch_json and server-rendered views forge the ring on first
access if empty. Added ring.json diagnostic endpoint to inspect
a shop's discovery ring state.
2026-02-09 07:19:11 -05:00
bc41c3481b Precompute discovery ring per shop for deterministic content traversal
Replace per-request Jaccard similarity with a precomputed circular
ordering of all public products. Greedy nearest-neighbor walk starts
from the newest product, accumulating stems to cluster similar content.
Ring reforges on product create/edit/upload when watch mode is enabled.

Frontend stores ring position, direction, and watch history in
localStorage. Queue items splice into playback without moving ring
position. Watched items dim in sidebar with progress indicator.
2026-02-08 16:07:25 -05:00
4c1cd11050 Lazy cart creation: don't persist session carts until product added
Bots and crawlers were creating empty cart rows on every page visit,
bloating the database (531k empty rows, 80% of all carts). Session
carts are now kept in-memory until a product is actually added, at
which point the view persists them via dbsession.add + flush.

- add_session_cart returns in-memory Cart, reuses UUID across requests
- authentication.py guards dbsession.delete for transient carts
- 17 new tests: unit, integration, and functional coverage
2026-02-08 15:26:35 -05:00
5ee2175a3f Serve feeds as application/xml so browsers render instead of download
Also add target=_blank to subscribe page feed links.
2026-02-08 14:24:45 -05:00
95e90f0ed8 Open RSS and Atom feed links in new window 2026-02-08 14:22:13 -05:00
528d99398d Set ribbon to fixed 42px height, match sticky video offset 2026-02-08 14:19:03 -05:00
e3d1fc8ad3 Offset sticky video below ribbon on desktop (top: 38px) 2026-02-08 14:18:10 -05:00
c00c33754c Track recently played items, filter from Up Next for 4 hours
Stores played product IDs + timestamps in localStorage. Items played
within the last 4 hours are hidden from Up Next sidebar and skipped
by getNextItem(). Queue items always play (user-explicit). Cleans
stale entries on page load.
2026-02-08 14:17:07 -05:00
da465868e3 Push footer to bottom of viewport on short pages 2026-02-08 14:05:58 -05:00
0e5b04b947 Desktop sticky nav; watch mode sticks ribbon+logo only; mobile never sticky
- Desktop non-watch: full nav bar sticks at top on scroll
- Desktop watch mode: only ribbon and logo stick, nav scrolls away
- Mobile/tablet: nothing sticky except video in watch mode
2026-02-08 13:58:00 -05:00
0fa40d921a Link unturf.com to /software/ page in footer 2026-02-08 13:48:36 -05:00
4d500cb42b Revert streamlined footer, restore CTAs and original sizing
Bring back CTA buttons and original font sizes/spacing.
Make footer full width to match the nav bar.
2026-02-08 13:45:56 -05:00
2712f34a1e Improve shop footer: add description, nav links, feeds, account
Shop footer now has 4-column grid matching SaaS layout with shop
description, cart/subscribe links, RSS/Atom feeds, and account link.
2026-02-08 13:43:45 -05:00
bd3757bb9a Streamline SaaS footer: remove redundant CTAs, tighten spacing
Remove CTA buttons section (app already has + New in navbar).
Use grid instead of flex, reduce font sizes, tighten padding.
2026-02-08 13:35:54 -05:00
94d3953d6d Two-phase preload: fast JSON fetch, buffer media 30s before end
Phase 1: preloadNext() fetches JSON metadata immediately (fast API call,
no media download). Standby src set with preload=none.
Phase 2: bufferNext() triggered by timeupdate when ~30s from end of
current track. Switches standby to preload=auto and calls load() to
start real buffering for seamless DJ crossfade.
2026-02-08 13:18:20 -05:00
63f62cf234 Use preload=metadata instead of auto to avoid downloading entire next video 2026-02-08 13:15:08 -05:00
7574318ba8 Fix static content not switching: hard navigate when no active media 2026-02-08 13:13:44 -05:00
309c291a92 Reset static content 42s timer on scroll — user is still reading 2026-02-08 13:13:01 -05:00
9841013306 Only ribbon sticky on desktop, nav and logo flow normally 2026-02-08 13:12:07 -05:00
8d6c892835 Use Jaccard similarity for related content ranking
Raw stem overlap count favored products with large descriptions
(like books) that matched common words with everything. Jaccard
index (intersection/union) normalizes by total unique stems so
content size doesn't dominate the score.
2026-02-08 13:10:02 -05:00
c60c6bd84b Pin setuptools<81 — version 82 removed pkg_resources
Pyramid 2.0.2 imports pkg_resources which was dropped from setuptools 81+.
Pin to <81 until Pyramid removes its pkg_resources dependency.
2026-02-08 12:18:29 -05:00
2bf586595f Install setuptools before editable install in Makefile
Cached CI venvs skip dependency resolution on editable installs.
Explicitly install setuptools first in both dev and prod targets.
2026-02-08 12:11:58 -05:00
db2d23aabd Add setuptools to requirements-dev.txt
The --editable install skips dependency resolution on cached CI venvs.
requirements-dev.txt is installed with --upgrade so setuptools will
actually get picked up.
2026-02-08 12:11:26 -05:00
ef0df8d777 Restore js-only on queue btn, make + always visible
Reverts removal of js-only class. Changes queue button color from
text-muted to text-primary so it's always clearly visible, not
just on hover.
2026-02-08 12:09:41 -05:00
9063e7d485 Bump version to 1.1.6
Force pip to re-resolve dependencies so setuptools gets installed
in cached CI venvs.
2026-02-08 12:03:26 -05:00
4e91dcb99a Always show queue + button in related content 2026-02-08 11:59:36 -05:00
117227a901 Add setuptools to requirements for Python 3.12+ compatibility
Python 3.12 no longer bundles setuptools in virtual environments.
Pyramid imports pkg_resources from setuptools, causing CI to fail with
ModuleNotFoundError: No module named 'pkg_resources'.
2026-02-08 11:47:52 -05:00
c5f3b2915f Sticky ribbon + logo/nav on desktop scroll
Wraps ribbon and nav-grid in .sticky-header div inside section.main.
Desktop only (min-width: 800px) — sticks to top on scroll, eliminates
whitespace gap above sticky video. No change on mobile/tablet.
2026-02-08 11:47:15 -05:00
7becbb94a1 Always show queue add button, add mobile usability rule to CLAUDE.md
Hover-only controls are unreachable on touch devices. Removed opacity:0
hover-reveal pattern from queue add button. Added MOBILE USABILITY
guideline to CLAUDE.md: all interactive elements must be always visible.
2026-02-08 11:41:50 -05:00
d1fbcf53cf Add autoplay toggle switch inline with Up Next heading
iOS-style slider toggle, on by default, persisted in localStorage.
Moved from standalone checkbox into related-content header row.
Rebuilt on SPA navigation with rebindAutoplayToggle().
2026-02-08 11:41:01 -05:00
39b186d651 Prevent download/up-next from overlapping sticky video on mobile 2026-02-08 11:36:06 -05:00
8ad849cc72 Bump related content thumbnails from 56px to 58px 2026-02-08 11:35:08 -05:00
93152ab26d Left-align Up Next text, honor thumbnail aspect ratio 2026-02-08 11:34:13 -05:00
1ab8f6ff20 Reduce countdown from 14s to 7s, left-align countdown text 2026-02-08 11:32:26 -05:00
0a08b9b14e Add context-aware footer to base template
SaaS domain shows full MPS footer with navigation links, resources, and CTAs.
Shop domains show minimal footer with shop name and powered-by link.
2026-02-08 11:25:22 -05:00
52e2ffc06c Redesign countdown overlay as compact frosted-glass bottom bar
Countdown preview now anchors to bottom of media container instead of
covering the entire video. Uses backdrop-filter blur with theme-aware
semi-transparent background (light/dark). Compact single-row grid layout
with 60px thumb, title+number, and action buttons inline.

Also completes the stalled watch mode v2 changes: static content timer,
autoplay toggle persistence, audio container wrapping in hardSwap, and
watch_json endpoint accepting non-playable media types.
2026-02-08 10:36:03 -05:00
91fb6f7916 Bump product-main max-height from 33vh to 42vh 2026-02-08 10:17:30 -05:00
d03178b5b5 Redesign Up Next sidebar: compact rows, numbered index, hover-reveal queue button 2026-02-08 08:52:47 -05:00