@pathmx/player 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/navigation.ts ADDED
@@ -0,0 +1,97 @@
1
+ import type { PlayIntensity, PlayMode, PlayThemeChoice } from "./controls.ts"
2
+
3
+ type PlayLinkState = {
4
+ mode: PlayMode
5
+ intensity: PlayIntensity
6
+ theme: PlayThemeChoice
7
+ guide: boolean
8
+ notes: boolean
9
+ }
10
+
11
+ type BeatFragmentNavigation = (fragment: string) => boolean
12
+
13
+ function decodedFragment(url: URL) {
14
+ if (!url.hash) return
15
+ try {
16
+ return decodeURIComponent(url.hash.slice(1)) || undefined
17
+ } catch {
18
+ return
19
+ }
20
+ }
21
+
22
+ function eligibleLink(event: MouseEvent) {
23
+ if (
24
+ event.defaultPrevented ||
25
+ event.button !== 0 ||
26
+ event.metaKey ||
27
+ event.ctrlKey ||
28
+ event.shiftKey ||
29
+ event.altKey
30
+ ) {
31
+ return
32
+ }
33
+ const target = event.target
34
+ if (!(target instanceof Element)) return
35
+ const link = target.closest<HTMLAnchorElement>("a[href]")
36
+ if (
37
+ !link ||
38
+ link.download ||
39
+ (link.target && link.target !== "_self") ||
40
+ link.relList.contains("external")
41
+ ) {
42
+ return
43
+ }
44
+ return link
45
+ }
46
+
47
+ /** Carry an active Play session across ordinary same-origin Source links. */
48
+ export function installPlayLinkContinuation(
49
+ current: () => PlayLinkState | undefined,
50
+ navigateToBeat?: BeatFragmentNavigation,
51
+ ) {
52
+ document.addEventListener(
53
+ "click",
54
+ (event) => {
55
+ if (!(event instanceof MouseEvent)) return
56
+ const state = current()
57
+ const link = state && eligibleLink(event)
58
+ if (!link) return
59
+
60
+ const destination = new URL(link.href, location.href)
61
+ if (
62
+ destination.origin !== location.origin ||
63
+ destination.pathname.endsWith(".md") ||
64
+ destination.pathname.startsWith("/.pmx/") ||
65
+ destination.pathname.startsWith("/actions/")
66
+ ) {
67
+ return
68
+ }
69
+ if (
70
+ destination.pathname === location.pathname &&
71
+ destination.search === location.search
72
+ ) {
73
+ const fragment = decodedFragment(destination)
74
+ if (fragment && navigateToBeat?.(fragment)) event.preventDefault()
75
+ return
76
+ }
77
+
78
+ destination.searchParams.set("play", state.mode)
79
+ destination.searchParams.set("intensity", state.intensity)
80
+ if (state.theme === "source")
81
+ destination.searchParams.delete("play-theme")
82
+ else destination.searchParams.set("play-theme", state.theme)
83
+ if (state.guide) destination.searchParams.delete("play-guide")
84
+ else destination.searchParams.set("play-guide", "off")
85
+ if (state.notes) destination.searchParams.delete("play-notes")
86
+ else destination.searchParams.set("play-notes", "off")
87
+ const authoredHref = link.getAttribute("href")
88
+ link.href = destination.href
89
+ setTimeout(() => {
90
+ if (!link.isConnected) return
91
+ if (authoredHref === null) link.removeAttribute("href")
92
+ else link.setAttribute("href", authoredHref)
93
+ })
94
+ },
95
+ { capture: true },
96
+ )
97
+ }
package/notes.css ADDED
@@ -0,0 +1,65 @@
1
+ .pmx-play-note {
2
+ position: fixed;
3
+ z-index: 999;
4
+ right: max(1rem, env(safe-area-inset-right));
5
+ bottom: calc(max(1rem, env(safe-area-inset-bottom)) + 3.25rem);
6
+ display: grid;
7
+ grid-template-columns: auto minmax(0, 1fr);
8
+ align-items: start;
9
+ gap: 0.7rem;
10
+ box-sizing: border-box;
11
+ width: min(25rem, calc(100vw - 2rem));
12
+ max-height: min(34vh, 18rem);
13
+ padding: 0.85rem 1rem;
14
+ overflow-y: auto;
15
+ border: 1px solid
16
+ color-mix(in oklab, var(--pmx-color-border, currentColor) 78%, transparent);
17
+ border-radius: 0.85rem;
18
+ color: var(--pmx-color-fg, CanvasText);
19
+ background: color-mix(
20
+ in oklab,
21
+ var(--pmx-color-surface, Canvas) 94%,
22
+ transparent
23
+ );
24
+ box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 14%);
25
+ font: 520 0.875rem/1.45 var(--pmx-font-body, system-ui, sans-serif);
26
+ backdrop-filter: blur(14px);
27
+ animation: pmx-play-note-in 180ms ease-out;
28
+ }
29
+
30
+ .pmx-play-note > svg {
31
+ width: 1rem;
32
+ height: 1rem;
33
+ margin-block-start: 0.2rem;
34
+ color: var(--pmx-color-accent, #3897ff);
35
+ }
36
+
37
+ .pmx-play-note p {
38
+ margin: 0;
39
+ }
40
+
41
+ .pmx-play-note p + p {
42
+ margin-block-start: 0.65rem;
43
+ }
44
+
45
+ @keyframes pmx-play-note-in {
46
+ from {
47
+ opacity: 0;
48
+ transform: translateY(0.35rem);
49
+ }
50
+ }
51
+
52
+ @media (max-width: 42rem), (pointer: coarse) {
53
+ .pmx-play-note {
54
+ right: max(0.75rem, env(safe-area-inset-right));
55
+ bottom: calc(max(0.75rem, env(safe-area-inset-bottom)) + 3.5rem);
56
+ width: min(24rem, calc(100vw - 1.5rem));
57
+ max-height: min(30vh, 14rem);
58
+ }
59
+ }
60
+
61
+ @media (prefers-reduced-motion: reduce) {
62
+ .pmx-play-note {
63
+ animation: none;
64
+ }
65
+ }
package/notes.ts ADDED
@@ -0,0 +1,10 @@
1
+ const NOTE_PARAGRAPH = /<p>\s*\^\s+([\s\S]*?)<\/p>/gi
2
+
3
+ /** Compile Deckset-style caret paragraphs into hidden Player note metadata. */
4
+ export function rewritePlayNotes(html: string) {
5
+ if (!html.includes("^")) return html
6
+ return html.replace(
7
+ NOTE_PARAGRAPH,
8
+ "<aside data-pmx-play-note hidden>$1</aside>",
9
+ )
10
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@pathmx/player",
3
+ "description": "Focused reading and presentation experiences for PathMX.",
4
+ "version": "0.5.0",
5
+ "license": "SEE LICENSE IN LICENSE.md",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/pathmx/pathmx-beta.git",
9
+ "directory": "paths/player"
10
+ },
11
+ "homepage": "https://github.com/pathmx/pathmx-beta#readme",
12
+ "bugs": "https://github.com/pathmx/pathmx-beta/issues",
13
+ "publishConfig": {
14
+ "access": "public",
15
+ "registry": "https://registry.npmjs.org/",
16
+ "tag": "beta"
17
+ },
18
+ "type": "module",
19
+ "exports": "./index.ts",
20
+ "engines": {
21
+ "bun": ">=1.4.0"
22
+ },
23
+ "peerDependencies": {
24
+ "@pathmx/core": "^0.5.0",
25
+ "react": ">=19.0.0",
26
+ "react-dom": ">=19.0.0"
27
+ },
28
+ "dependencies": {
29
+ "@pathmx/react": "0.5.0",
30
+ "lucide-react": "^1.34.0"
31
+ }
32
+ }