@markgrafhq/starlight-markgraf 0.0.35 → 0.0.37
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/README.md +17 -0
- package/dist/MarkgrafPlayer.d.ts +16 -0
- package/dist/MarkgrafPlayer.js +61 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/markgraf-react.css +107 -245
- package/package.json +6 -3
- package/src/Markgraf.astro +6 -3
- package/src/MarkgrafPlayer.tsx +217 -0
- package/src/player.css +154 -0
package/README.md
CHANGED
|
@@ -53,3 +53,20 @@ import Markgraf from "@markgrafhq/starlight-markgraf/Markgraf.astro";
|
|
|
53
53
|
|
|
54
54
|
<Markgraf src={`seed 1\nscene v1 { + a: A }`} />
|
|
55
55
|
```
|
|
56
|
+
|
|
57
|
+
Players include the floating play, scrub, time, and speed controls by default.
|
|
58
|
+
The Astro component accepts `controls={false}` when a page should hide them.
|
|
59
|
+
|
|
60
|
+
Custom controls belong in a React island so the control function stays in the
|
|
61
|
+
browser rather than crossing Astro's serialization boundary:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { StarlightMarkgrafPlayer } from "@markgrafhq/starlight-markgraf";
|
|
65
|
+
|
|
66
|
+
export const Diagram = ({ src }: { src: string }) => (
|
|
67
|
+
<StarlightMarkgrafPlayer
|
|
68
|
+
src={src}
|
|
69
|
+
controls={(api) => <MyDiagramControls api={api} />}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type MarkgrafApi } from "@markgrafhq/markgraf-react";
|
|
3
|
+
type Renderer = "canvas" | "svg";
|
|
4
|
+
export type MarkgrafControls = boolean | ((api: MarkgrafApi<Element>) => ReactNode);
|
|
5
|
+
export interface MarkgrafPlayerProps {
|
|
6
|
+
src: string;
|
|
7
|
+
renderer?: Renderer;
|
|
8
|
+
className?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Shows the transport bar by default. Pass `false` to hide it, or a render
|
|
11
|
+
* function to replace it with controls driven by the same player API.
|
|
12
|
+
*/
|
|
13
|
+
controls?: MarkgrafControls;
|
|
14
|
+
}
|
|
15
|
+
export declare const StarlightMarkgrafPlayer: ({ src, renderer, className, controls, }: MarkgrafPlayerProps) => import("react").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { useMarkgraf } from "@markgrafhq/markgraf-react";
|
|
4
|
+
const playIcon = (_jsx("svg", { viewBox: "0 0 15 15", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M3.24 2.05a.5.5 0 0 1 .52.02l8.5 5a.5.5 0 0 1 0 .86l-8.5 5A.5.5 0 0 1 3 12.5v-10a.5.5 0 0 1 .24-.45Z" }) }));
|
|
5
|
+
const pauseIcon = (_jsx("svg", { viewBox: "0 0 15 15", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M3.5 2.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v10a.5.5 0 0 1-.5.5H4a.5.5 0 0 1-.5-.5v-10Zm5 0A.5.5 0 0 1 9 2h2a.5.5 0 0 1 .5.5v10a.5.5 0 0 1-.5.5H9a.5.5 0 0 1-.5-.5v-10Z" }) }));
|
|
6
|
+
const speedIcon = (_jsx("svg", { className: "markgraf-doc-speed-icon", viewBox: "0 0 15 15", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M3.14 5.64a.5.5 0 0 1 .71 0L7.5 9.29l3.65-3.65a.5.5 0 0 1 .7.71l-4 4a.5.5 0 0 1-.7 0l-4-4a.5.5 0 0 1 0-.71Z" }) }));
|
|
7
|
+
const useDocumentTheme = () => {
|
|
8
|
+
const [theme, setTheme] = useState("light");
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const root = document.documentElement;
|
|
11
|
+
const update = () => setTheme(root.dataset.theme === "dark" ? "dark" : "light");
|
|
12
|
+
update();
|
|
13
|
+
const observer = new MutationObserver(update);
|
|
14
|
+
observer.observe(root, {
|
|
15
|
+
attributes: true,
|
|
16
|
+
attributeFilter: ["data-theme"],
|
|
17
|
+
});
|
|
18
|
+
return () => observer.disconnect();
|
|
19
|
+
}, []);
|
|
20
|
+
return theme;
|
|
21
|
+
};
|
|
22
|
+
const formatTime = (seconds) => {
|
|
23
|
+
const safe = Number.isFinite(seconds) ? Math.max(0, seconds) : 0;
|
|
24
|
+
const minutes = Math.floor(safe / 60);
|
|
25
|
+
const remainder = safe - minutes * 60;
|
|
26
|
+
return `${minutes}:${remainder.toFixed(1).padStart(4, "0")}`;
|
|
27
|
+
};
|
|
28
|
+
const DefaultControls = ({ api, speed, setSpeed, }) => {
|
|
29
|
+
const duration = Number.isFinite(api.duration)
|
|
30
|
+
? Math.max(0, api.duration)
|
|
31
|
+
: 0;
|
|
32
|
+
const time = Number.isFinite(api.time)
|
|
33
|
+
? Math.max(0, Math.min(api.time, duration))
|
|
34
|
+
: 0;
|
|
35
|
+
return (_jsxs("div", { className: "markgraf-doc-controls", "aria-label": "Diagram playback controls", children: [_jsx("button", { className: "markgraf-doc-play", type: "button", "aria-label": api.playing ? "Pause diagram" : "Play diagram", "aria-pressed": api.playing, disabled: !api.ready, onClick: api.toggle, children: api.playing ? pauseIcon : playIcon }), _jsx("input", { className: "markgraf-doc-scrub", type: "range", "aria-label": "Diagram time", min: "0", max: Math.max(duration, 0.001), step: "0.01", value: time, disabled: !api.ready, onChange: (event) => api.seek(event.currentTarget.valueAsNumber) }), _jsxs("output", { className: "markgraf-doc-time", "aria-live": "off", children: [formatTime(time), " / ", formatTime(duration)] }), _jsxs("div", { className: "markgraf-doc-speed-control", children: [_jsxs("select", { className: "markgraf-doc-speed", "aria-label": "Playback speed", value: speed, disabled: !api.ready, onChange: (event) => setSpeed(Number(event.currentTarget.value)), children: [_jsx("option", { value: "0.5", children: "0.5\u00D7" }), _jsx("option", { value: "1", children: "1\u00D7" }), _jsx("option", { value: "1.5", children: "1.5\u00D7" }), _jsx("option", { value: "2", children: "2\u00D7" })] }), speedIcon] })] }));
|
|
36
|
+
};
|
|
37
|
+
const Controls = ({ api, controls, }) => {
|
|
38
|
+
const [speed, setSpeed] = useState(1);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (controls === true && api.ready)
|
|
41
|
+
api.setSpeed(speed);
|
|
42
|
+
}, [api.ready, controls, speed]);
|
|
43
|
+
if (typeof controls === "function")
|
|
44
|
+
return controls(api);
|
|
45
|
+
if (!controls)
|
|
46
|
+
return null;
|
|
47
|
+
return _jsx(DefaultControls, { api: api, speed: speed, setSpeed: setSpeed });
|
|
48
|
+
};
|
|
49
|
+
const CanvasPlayer = ({ src, theme, className, controls, }) => {
|
|
50
|
+
const api = useMarkgraf(src, { renderer: "canvas", theme });
|
|
51
|
+
return (_jsxs("div", { className: `markgraf-doc-player ${className ?? ""}`, "data-markgraf-theme": theme, children: [_jsx("canvas", { className: "markgraf-player markgraf-doc-stage", ref: api.elementRef, onClick: api.toggle, "aria-label": "Animated Markgraf diagram" }), _jsx(Controls, { api: api, controls: controls })] }));
|
|
52
|
+
};
|
|
53
|
+
const SvgPlayer = ({ src, theme, className, controls, }) => {
|
|
54
|
+
const api = useMarkgraf(src, { renderer: "svg", theme });
|
|
55
|
+
return (_jsxs("div", { className: `markgraf-doc-player ${className ?? ""}`, "data-markgraf-theme": theme, children: [_jsx("svg", { className: "markgraf-player markgraf-doc-stage", ref: api.elementRef, onClick: api.toggle, "aria-label": "Animated Markgraf diagram" }), _jsx(Controls, { api: api, controls: controls })] }));
|
|
56
|
+
};
|
|
57
|
+
export const StarlightMarkgrafPlayer = ({ src, renderer = "canvas", className, controls = true, }) => {
|
|
58
|
+
const theme = useDocumentTheme();
|
|
59
|
+
const props = { src, renderer, className, controls, theme };
|
|
60
|
+
return renderer === "svg" ? (_jsx(SvgPlayer, { ...props })) : (_jsx(CanvasPlayer, { ...props }));
|
|
61
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AstroIntegration } from "astro";
|
|
2
2
|
import remarkMarkgraf from "./remark-markgraf.mjs";
|
|
3
3
|
export { remarkMarkgraf };
|
|
4
|
+
export { StarlightMarkgrafPlayer, type MarkgrafControls, type MarkgrafPlayerProps, } from "./MarkgrafPlayer.js";
|
|
4
5
|
export default function markgrafIntegration(): AstroIntegration;
|
package/dist/index.js
CHANGED
package/dist/markgraf-react.css
CHANGED
|
@@ -1,292 +1,154 @@
|
|
|
1
|
-
@
|
|
2
|
-
font-family: 'CommitMono';
|
|
3
|
-
src: url('./CommitMono-Regular.woff2') format('woff2');
|
|
4
|
-
font-weight: 400;
|
|
5
|
-
font-display: swap;
|
|
6
|
-
}
|
|
1
|
+
@import "@markgrafhq/markgraf-react/css";
|
|
7
2
|
|
|
8
|
-
.markgraf-
|
|
9
|
-
--mg-bg: #ffffff;
|
|
10
|
-
--mg-fg: #111111;
|
|
11
|
-
--mg-bar-bg: #111111;
|
|
12
|
-
--mg-bar-fg: #ffffff;
|
|
13
|
-
--mg-muted: #9a9a9a;
|
|
14
|
-
--mg-accent: #ffffff;
|
|
15
|
-
--mg-track: #111111;
|
|
16
|
-
--mg-track-hover: #111111;
|
|
3
|
+
.markgraf-doc-player {
|
|
17
4
|
position: relative;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
color: var(--mg-fg);
|
|
21
|
-
font: 13px/1.4 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
22
|
-
overflow: hidden;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.markgraf-embed canvas[data-mg="stage"],
|
|
26
|
-
.markgraf-embed svg[data-mg="stage"] {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
27
7
|
width: 100%;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
outline: none;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* Big centered play hint shown while paused. Hidden as soon as a play
|
|
40
|
-
button anywhere in the embed flips to data-mg-playing="1". */
|
|
41
|
-
.markgraf-embed [data-mg="play-overlay"] {
|
|
42
|
-
position: absolute;
|
|
43
|
-
inset: 0;
|
|
44
|
-
margin: auto;
|
|
45
|
-
width: 72px;
|
|
46
|
-
height: 72px;
|
|
47
|
-
border-radius: 50%;
|
|
48
|
-
background: rgba(0, 0, 0, 0.55);
|
|
49
|
-
pointer-events: none;
|
|
50
|
-
opacity: 0;
|
|
51
|
-
transform: scale(0.85);
|
|
52
|
-
transition: opacity 0.12s ease, transform 0.12s ease;
|
|
8
|
+
min-width: 0;
|
|
9
|
+
min-height: 11.25rem;
|
|
10
|
+
aspect-ratio: 16 / 9;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
border: 1px solid color-mix(in srgb, currentColor 14%, transparent);
|
|
13
|
+
border-radius: 0.875rem;
|
|
14
|
+
background: #ffffff;
|
|
15
|
+
box-shadow: 0 1rem 3rem rgb(0 0 0 / 10%);
|
|
53
16
|
}
|
|
54
17
|
|
|
55
|
-
.markgraf-
|
|
56
|
-
|
|
57
|
-
position: absolute;
|
|
58
|
-
inset: 0;
|
|
59
|
-
margin: auto;
|
|
60
|
-
width: 32px;
|
|
61
|
-
height: 32px;
|
|
62
|
-
background: #fff;
|
|
63
|
-
-webkit-mask: var(--mg-icon-play) center / contain no-repeat;
|
|
64
|
-
mask: var(--mg-icon-play) center / contain no-repeat;
|
|
18
|
+
.markgraf-doc-player[data-markgraf-theme="dark"] {
|
|
19
|
+
background: #101827;
|
|
65
20
|
}
|
|
66
21
|
|
|
67
|
-
.markgraf-
|
|
68
|
-
|
|
69
|
-
|
|
22
|
+
.markgraf-doc-stage {
|
|
23
|
+
display: block;
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 100%;
|
|
26
|
+
min-height: 11.25rem;
|
|
27
|
+
cursor: pointer;
|
|
70
28
|
}
|
|
71
29
|
|
|
72
|
-
.markgraf-
|
|
30
|
+
.markgraf-doc-controls {
|
|
73
31
|
position: absolute;
|
|
74
|
-
|
|
75
|
-
right:
|
|
76
|
-
bottom: 0;
|
|
77
|
-
display:
|
|
32
|
+
z-index: 2;
|
|
33
|
+
right: 50%;
|
|
34
|
+
bottom: 0.75rem;
|
|
35
|
+
display: grid;
|
|
36
|
+
grid-template-columns: 1.75rem minmax(7rem, 1fr) auto auto;
|
|
78
37
|
align-items: center;
|
|
79
|
-
gap:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
opacity: 0;
|
|
84
|
-
transform: translateY(2px);
|
|
85
|
-
transition: opacity 0.08s ease, transform 0.08s ease;
|
|
86
|
-
pointer-events: none;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.markgraf-embed:hover [data-mg="bar"],
|
|
90
|
-
.markgraf-embed:focus-within [data-mg="bar"] {
|
|
91
|
-
opacity: 1;
|
|
92
|
-
transform: translateY(0);
|
|
93
|
-
pointer-events: auto;
|
|
94
|
-
transition: opacity 0.16s ease, transform 0.16s ease;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.markgraf-embed [data-mg="play"] {
|
|
98
|
-
background: transparent;
|
|
99
|
-
color: var(--mg-fg);
|
|
100
|
-
border: 0;
|
|
38
|
+
gap: 0.5rem;
|
|
39
|
+
width: min(35rem, calc(100% - 2rem));
|
|
40
|
+
padding: 0.375rem 0.5rem;
|
|
41
|
+
border: 1px solid rgb(255 255 255 / 14%);
|
|
101
42
|
border-radius: 999px;
|
|
102
|
-
|
|
103
|
-
|
|
43
|
+
color: #ffffff;
|
|
44
|
+
background: rgb(8 13 22 / 58%);
|
|
45
|
+
box-shadow: 0 0.875rem 2.75rem rgb(0 0 0 / 28%);
|
|
46
|
+
opacity: 0.78;
|
|
47
|
+
transform: translateX(50%);
|
|
48
|
+
transition:
|
|
49
|
+
opacity 140ms ease,
|
|
50
|
+
background 140ms ease;
|
|
51
|
+
backdrop-filter: blur(18px);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.markgraf-doc-player:hover .markgraf-doc-controls,
|
|
55
|
+
.markgraf-doc-controls:focus-within {
|
|
56
|
+
background: rgb(8 13 22 / 78%);
|
|
57
|
+
opacity: 0.98;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.markgraf-doc-play {
|
|
61
|
+
display: grid;
|
|
62
|
+
width: 1.75rem;
|
|
63
|
+
height: 1.75rem;
|
|
104
64
|
padding: 0;
|
|
65
|
+
border: 0;
|
|
66
|
+
border-radius: 999px;
|
|
67
|
+
place-items: center;
|
|
68
|
+
color: inherit;
|
|
69
|
+
background: rgb(255 255 255 / 10%);
|
|
105
70
|
cursor: pointer;
|
|
106
|
-
display: inline-flex;
|
|
107
|
-
align-items: center;
|
|
108
|
-
justify-content: center;
|
|
109
|
-
transition: background 0.1s ease;
|
|
110
71
|
}
|
|
111
72
|
|
|
112
|
-
.markgraf-
|
|
113
|
-
|
|
73
|
+
.markgraf-doc-play:hover,
|
|
74
|
+
.markgraf-doc-play:focus-visible {
|
|
75
|
+
background: rgb(255 255 255 / 20%);
|
|
114
76
|
}
|
|
115
77
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.
|
|
119
|
-
content: "";
|
|
120
|
-
display: block;
|
|
121
|
-
width: 14px;
|
|
122
|
-
height: 14px;
|
|
123
|
-
background: currentColor;
|
|
124
|
-
-webkit-mask: var(--mg-icon-play) center / contain no-repeat;
|
|
125
|
-
mask: var(--mg-icon-play) center / contain no-repeat;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.markgraf-embed [data-mg="play"][data-mg-playing="1"]::before {
|
|
129
|
-
-webkit-mask: var(--mg-icon-pause) center / contain no-repeat;
|
|
130
|
-
mask: var(--mg-icon-pause) center / contain no-repeat;
|
|
78
|
+
.markgraf-doc-play svg {
|
|
79
|
+
width: 0.875rem;
|
|
80
|
+
height: 0.875rem;
|
|
131
81
|
}
|
|
132
82
|
|
|
133
|
-
.markgraf-
|
|
134
|
-
|
|
135
|
-
|
|
83
|
+
.markgraf-doc-scrub {
|
|
84
|
+
min-width: 0;
|
|
85
|
+
height: 0.875rem;
|
|
86
|
+
margin: 0;
|
|
87
|
+
accent-color: #ffffff;
|
|
88
|
+
cursor: pointer;
|
|
136
89
|
}
|
|
137
90
|
|
|
138
|
-
.markgraf-
|
|
91
|
+
.markgraf-doc-time {
|
|
92
|
+
color: rgb(255 255 255 / 76%);
|
|
93
|
+
font-family: var(
|
|
94
|
+
--sl-font-mono,
|
|
95
|
+
var(--ifm-font-family-monospace, ui-monospace, monospace)
|
|
96
|
+
);
|
|
97
|
+
font-size: 0.625rem;
|
|
98
|
+
line-height: 1;
|
|
139
99
|
font-variant-numeric: tabular-nums;
|
|
140
|
-
font-size: 11px;
|
|
141
|
-
color: var(--mg-fg);
|
|
142
100
|
white-space: nowrap;
|
|
143
101
|
}
|
|
144
102
|
|
|
145
|
-
.markgraf-
|
|
146
|
-
background: transparent;
|
|
147
|
-
border: 1px solid currentColor;
|
|
148
|
-
border-radius: 4px;
|
|
149
|
-
padding: 1px 4px;
|
|
150
|
-
color: var(--mg-fg);
|
|
151
|
-
font: inherit;
|
|
152
|
-
font-size: 11px;
|
|
153
|
-
line-height: 1;
|
|
154
|
-
cursor: pointer;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.markgraf-embed [data-mg="speed"] option {
|
|
158
|
-
color: var(--mg-fg);
|
|
159
|
-
background: var(--mg-bg);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.markgraf-embed [data-mg="scrub-wrap"] {
|
|
103
|
+
.markgraf-doc-speed-control {
|
|
163
104
|
position: relative;
|
|
164
|
-
flex: 1;
|
|
165
105
|
display: flex;
|
|
166
106
|
align-items: center;
|
|
167
107
|
}
|
|
168
108
|
|
|
169
|
-
.markgraf-
|
|
170
|
-
position: absolute;
|
|
171
|
-
left: 7px;
|
|
172
|
-
right: 7px;
|
|
173
|
-
top: 50%;
|
|
174
|
-
transform: translateY(-50%);
|
|
175
|
-
height: 18px;
|
|
176
|
-
pointer-events: none;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.markgraf-embed .mg-tick {
|
|
180
|
-
position: absolute;
|
|
181
|
-
top: 50%;
|
|
182
|
-
width: 4px;
|
|
183
|
-
height: 4px;
|
|
184
|
-
margin-left: -2px;
|
|
185
|
-
margin-top: -2px;
|
|
186
|
-
border-radius: 50%;
|
|
187
|
-
background: #fff;
|
|
188
|
-
opacity: 0.7;
|
|
189
|
-
pointer-events: auto;
|
|
190
|
-
cursor: pointer;
|
|
191
|
-
transition: transform 0.1s ease, opacity 0.1s ease;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.markgraf-embed .mg-tick:hover {
|
|
195
|
-
opacity: 1;
|
|
196
|
-
transform: scale(1.6);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
.markgraf-embed .mg-tick::after {
|
|
200
|
-
content: attr(data-label);
|
|
201
|
-
position: absolute;
|
|
202
|
-
bottom: 100%;
|
|
203
|
-
left: 50%;
|
|
204
|
-
transform: translate(-50%, -4px);
|
|
205
|
-
background: rgba(15, 23, 42, 0.95);
|
|
206
|
-
color: #fff;
|
|
207
|
-
font-size: 11px;
|
|
208
|
-
padding: 2px 6px;
|
|
209
|
-
border-radius: 4px;
|
|
210
|
-
white-space: nowrap;
|
|
211
|
-
opacity: 0;
|
|
212
|
-
pointer-events: none;
|
|
213
|
-
transition: opacity 0.1s ease;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.markgraf-embed .mg-tick:hover::after {
|
|
217
|
-
opacity: 1;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/* Custom-styled scrubber. Cross-browser range inputs are notoriously ugly,
|
|
221
|
-
so we strip the native chrome and rebuild from CSS primitives. */
|
|
222
|
-
.markgraf-embed [data-mg="scrub"] {
|
|
223
|
-
-webkit-appearance: none;
|
|
109
|
+
.markgraf-doc-speed {
|
|
224
110
|
appearance: none;
|
|
225
111
|
width: 100%;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
cursor: pointer;
|
|
229
|
-
margin: 0;
|
|
230
|
-
position: relative;
|
|
231
|
-
z-index: 1;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.markgraf-embed [data-mg="scrub"]::-webkit-slider-runnable-track {
|
|
235
|
-
height: 4px;
|
|
236
|
-
background: var(--mg-track);
|
|
112
|
+
padding: 0.1875rem 1.375rem 0.1875rem 0.5rem;
|
|
113
|
+
border: 0;
|
|
237
114
|
border-radius: 999px;
|
|
238
|
-
|
|
115
|
+
color: #ffffff;
|
|
116
|
+
background: rgb(255 255 255 / 10%);
|
|
117
|
+
font-size: 0.625rem;
|
|
118
|
+
cursor: pointer;
|
|
239
119
|
}
|
|
240
120
|
|
|
241
|
-
.markgraf-
|
|
242
|
-
|
|
121
|
+
.markgraf-doc-speed-icon {
|
|
122
|
+
position: absolute;
|
|
123
|
+
right: 0.4375rem;
|
|
124
|
+
width: 0.75rem;
|
|
125
|
+
height: 0.75rem;
|
|
126
|
+
color: rgb(255 255 255 / 72%);
|
|
127
|
+
pointer-events: none;
|
|
243
128
|
}
|
|
244
129
|
|
|
245
|
-
.markgraf-
|
|
246
|
-
|
|
247
|
-
background:
|
|
248
|
-
border-radius: 999px;
|
|
130
|
+
.markgraf-doc-speed option {
|
|
131
|
+
color: #111827;
|
|
132
|
+
background: #ffffff;
|
|
249
133
|
}
|
|
250
134
|
|
|
251
|
-
.markgraf-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
width: 14px;
|
|
255
|
-
height: 14px;
|
|
256
|
-
border-radius: 50%;
|
|
257
|
-
background: #111;
|
|
258
|
-
border: 2px solid #fff;
|
|
259
|
-
margin-top: -5px;
|
|
260
|
-
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
|
|
261
|
-
transition: transform 0.08s ease;
|
|
135
|
+
.markgraf-doc-controls :disabled {
|
|
136
|
+
cursor: wait;
|
|
137
|
+
opacity: 0.5;
|
|
262
138
|
}
|
|
263
139
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
140
|
+
@media (max-width: 32rem) {
|
|
141
|
+
.markgraf-doc-controls {
|
|
142
|
+
grid-template-columns: 1.75rem minmax(5rem, 1fr) auto;
|
|
143
|
+
}
|
|
267
144
|
|
|
268
|
-
.markgraf-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
border-radius: 50%;
|
|
272
|
-
background: #111;
|
|
273
|
-
border: 2px solid #fff;
|
|
274
|
-
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
|
|
145
|
+
.markgraf-doc-time {
|
|
146
|
+
display: none;
|
|
147
|
+
}
|
|
275
148
|
}
|
|
276
149
|
|
|
277
|
-
@media (prefers-
|
|
278
|
-
.markgraf-
|
|
279
|
-
|
|
280
|
-
--mg-fg: #ffffff;
|
|
281
|
-
--mg-track: #ffffff;
|
|
282
|
-
--mg-track-hover: #ffffff;
|
|
283
|
-
}
|
|
284
|
-
.markgraf-embed [data-mg="bar"] {
|
|
285
|
-
background: rgba(17, 17, 17, 0.92);
|
|
286
|
-
}
|
|
287
|
-
.markgraf-embed [data-mg="scrub"]::-webkit-slider-thumb,
|
|
288
|
-
.markgraf-embed [data-mg="scrub"]::-moz-range-thumb {
|
|
289
|
-
background: #fff;
|
|
290
|
-
border-color: #111;
|
|
150
|
+
@media (prefers-reduced-motion: reduce) {
|
|
151
|
+
.markgraf-doc-controls {
|
|
152
|
+
transition: none;
|
|
291
153
|
}
|
|
292
154
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markgrafhq/starlight-markgraf",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37",
|
|
4
4
|
"description": "Starlight/Astro integration for embedding markgraf animations. Transforms ```markgraf fenced code blocks into a live player and exposes a <Markgraf src=\"…\" /> component.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
22
|
"src/Markgraf.astro",
|
|
23
|
+
"src/MarkgrafPlayer.tsx",
|
|
24
|
+
"src/player.css",
|
|
23
25
|
"README.md",
|
|
24
26
|
"LICENSE"
|
|
25
27
|
],
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
"./dist/markgraf-react.css"
|
|
28
30
|
],
|
|
29
31
|
"scripts": {
|
|
30
|
-
"build": "tsc -p tsconfig.json && cp src/remark-markgraf.mjs dist/remark-markgraf.mjs && cp src/remark-markgraf.mjs dist/remark-markgraf.js && cp
|
|
32
|
+
"build": "tsc -p tsconfig.json && cp src/remark-markgraf.mjs dist/remark-markgraf.mjs && cp src/remark-markgraf.mjs dist/remark-markgraf.js && cp src/player.css dist/markgraf-react.css"
|
|
31
33
|
},
|
|
32
34
|
"peerDependencies": {
|
|
33
35
|
"astro": ">=4",
|
|
@@ -36,10 +38,11 @@
|
|
|
36
38
|
"react-dom": ">=18"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@markgrafhq/markgraf-react": "^0.1.
|
|
41
|
+
"@markgrafhq/markgraf-react": "^0.1.27",
|
|
40
42
|
"unist-util-visit": "^5"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
45
|
+
"@types/react": "^19.0.0",
|
|
43
46
|
"typescript": "^5"
|
|
44
47
|
},
|
|
45
48
|
"keywords": [
|
package/src/Markgraf.astro
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { StarlightMarkgrafPlayer } from "./MarkgrafPlayer";
|
|
3
|
+
import "./player.css";
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
src: string;
|
|
6
7
|
renderer?: "canvas" | "svg";
|
|
7
8
|
class?: string;
|
|
9
|
+
controls?: boolean;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
const { src, renderer = "canvas", class: className } = Astro.props;
|
|
12
|
+
const { src, renderer = "canvas", class: className, controls = true } = Astro.props;
|
|
11
13
|
---
|
|
12
14
|
|
|
13
|
-
<
|
|
15
|
+
<StarlightMarkgrafPlayer
|
|
14
16
|
client:visible
|
|
15
17
|
src={src}
|
|
16
18
|
renderer={renderer}
|
|
17
19
|
className={className}
|
|
20
|
+
controls={controls}
|
|
18
21
|
/>
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { type ReactNode, useEffect, useState } from "react";
|
|
2
|
+
import { useMarkgraf, type MarkgrafApi } from "@markgrafhq/markgraf-react";
|
|
3
|
+
|
|
4
|
+
type Renderer = "canvas" | "svg";
|
|
5
|
+
type Theme = "light" | "dark";
|
|
6
|
+
|
|
7
|
+
export type MarkgrafControls =
|
|
8
|
+
| boolean
|
|
9
|
+
| ((api: MarkgrafApi<Element>) => ReactNode);
|
|
10
|
+
const playIcon = (
|
|
11
|
+
<svg viewBox="0 0 15 15" aria-hidden="true" focusable="false">
|
|
12
|
+
<path d="M3.24 2.05a.5.5 0 0 1 .52.02l8.5 5a.5.5 0 0 1 0 .86l-8.5 5A.5.5 0 0 1 3 12.5v-10a.5.5 0 0 1 .24-.45Z" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const pauseIcon = (
|
|
17
|
+
<svg viewBox="0 0 15 15" aria-hidden="true" focusable="false">
|
|
18
|
+
<path d="M3.5 2.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5v10a.5.5 0 0 1-.5.5H4a.5.5 0 0 1-.5-.5v-10Zm5 0A.5.5 0 0 1 9 2h2a.5.5 0 0 1 .5.5v10a.5.5 0 0 1-.5.5H9a.5.5 0 0 1-.5-.5v-10Z" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const speedIcon = (
|
|
23
|
+
<svg
|
|
24
|
+
className="markgraf-doc-speed-icon"
|
|
25
|
+
viewBox="0 0 15 15"
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
focusable="false"
|
|
28
|
+
>
|
|
29
|
+
<path d="M3.14 5.64a.5.5 0 0 1 .71 0L7.5 9.29l3.65-3.65a.5.5 0 0 1 .7.71l-4 4a.5.5 0 0 1-.7 0l-4-4a.5.5 0 0 1 0-.71Z" />
|
|
30
|
+
</svg>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface MarkgrafPlayerProps {
|
|
34
|
+
src: string;
|
|
35
|
+
renderer?: Renderer;
|
|
36
|
+
className?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Shows the transport bar by default. Pass `false` to hide it, or a render
|
|
39
|
+
* function to replace it with controls driven by the same player API.
|
|
40
|
+
*/
|
|
41
|
+
controls?: MarkgrafControls;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const useDocumentTheme = (): Theme => {
|
|
45
|
+
const [theme, setTheme] = useState<Theme>("light");
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
const root = document.documentElement;
|
|
49
|
+
const update = () =>
|
|
50
|
+
setTheme(root.dataset.theme === "dark" ? "dark" : "light");
|
|
51
|
+
update();
|
|
52
|
+
|
|
53
|
+
const observer = new MutationObserver(update);
|
|
54
|
+
observer.observe(root, {
|
|
55
|
+
attributes: true,
|
|
56
|
+
attributeFilter: ["data-theme"],
|
|
57
|
+
});
|
|
58
|
+
return () => observer.disconnect();
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
return theme;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const formatTime = (seconds: number): string => {
|
|
65
|
+
const safe = Number.isFinite(seconds) ? Math.max(0, seconds) : 0;
|
|
66
|
+
const minutes = Math.floor(safe / 60);
|
|
67
|
+
const remainder = safe - minutes * 60;
|
|
68
|
+
return `${minutes}:${remainder.toFixed(1).padStart(4, "0")}`;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const DefaultControls = ({
|
|
72
|
+
api,
|
|
73
|
+
speed,
|
|
74
|
+
setSpeed,
|
|
75
|
+
}: {
|
|
76
|
+
api: MarkgrafApi<Element>;
|
|
77
|
+
speed: number;
|
|
78
|
+
setSpeed: (speed: number) => void;
|
|
79
|
+
}) => {
|
|
80
|
+
const duration = Number.isFinite(api.duration)
|
|
81
|
+
? Math.max(0, api.duration)
|
|
82
|
+
: 0;
|
|
83
|
+
const time = Number.isFinite(api.time)
|
|
84
|
+
? Math.max(0, Math.min(api.time, duration))
|
|
85
|
+
: 0;
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
className="markgraf-doc-controls"
|
|
90
|
+
aria-label="Diagram playback controls"
|
|
91
|
+
>
|
|
92
|
+
<button
|
|
93
|
+
className="markgraf-doc-play"
|
|
94
|
+
type="button"
|
|
95
|
+
aria-label={api.playing ? "Pause diagram" : "Play diagram"}
|
|
96
|
+
aria-pressed={api.playing}
|
|
97
|
+
disabled={!api.ready}
|
|
98
|
+
onClick={api.toggle}
|
|
99
|
+
>
|
|
100
|
+
{api.playing ? pauseIcon : playIcon}
|
|
101
|
+
</button>
|
|
102
|
+
|
|
103
|
+
<input
|
|
104
|
+
className="markgraf-doc-scrub"
|
|
105
|
+
type="range"
|
|
106
|
+
aria-label="Diagram time"
|
|
107
|
+
min="0"
|
|
108
|
+
max={Math.max(duration, 0.001)}
|
|
109
|
+
step="0.01"
|
|
110
|
+
value={time}
|
|
111
|
+
disabled={!api.ready}
|
|
112
|
+
onChange={(event) => api.seek(event.currentTarget.valueAsNumber)}
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
<output className="markgraf-doc-time" aria-live="off">
|
|
116
|
+
{formatTime(time)} / {formatTime(duration)}
|
|
117
|
+
</output>
|
|
118
|
+
|
|
119
|
+
<div className="markgraf-doc-speed-control">
|
|
120
|
+
<select
|
|
121
|
+
className="markgraf-doc-speed"
|
|
122
|
+
aria-label="Playback speed"
|
|
123
|
+
value={speed}
|
|
124
|
+
disabled={!api.ready}
|
|
125
|
+
onChange={(event) => setSpeed(Number(event.currentTarget.value))}
|
|
126
|
+
>
|
|
127
|
+
<option value="0.5">0.5×</option>
|
|
128
|
+
<option value="1">1×</option>
|
|
129
|
+
<option value="1.5">1.5×</option>
|
|
130
|
+
<option value="2">2×</option>
|
|
131
|
+
</select>
|
|
132
|
+
{speedIcon}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const Controls = ({
|
|
139
|
+
api,
|
|
140
|
+
controls,
|
|
141
|
+
}: {
|
|
142
|
+
api: MarkgrafApi<Element>;
|
|
143
|
+
controls: MarkgrafControls;
|
|
144
|
+
}) => {
|
|
145
|
+
const [speed, setSpeed] = useState(1);
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (controls === true && api.ready) api.setSpeed(speed);
|
|
149
|
+
}, [api.ready, controls, speed]);
|
|
150
|
+
|
|
151
|
+
if (typeof controls === "function") return controls(api);
|
|
152
|
+
if (!controls) return null;
|
|
153
|
+
return <DefaultControls api={api} speed={speed} setSpeed={setSpeed} />;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const CanvasPlayer = ({
|
|
157
|
+
src,
|
|
158
|
+
theme,
|
|
159
|
+
className,
|
|
160
|
+
controls,
|
|
161
|
+
}: MarkgrafPlayerProps & { theme: Theme; controls: MarkgrafControls }) => {
|
|
162
|
+
const api = useMarkgraf(src, { renderer: "canvas", theme });
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div
|
|
166
|
+
className={`markgraf-doc-player ${className ?? ""}`}
|
|
167
|
+
data-markgraf-theme={theme}
|
|
168
|
+
>
|
|
169
|
+
<canvas
|
|
170
|
+
className="markgraf-player markgraf-doc-stage"
|
|
171
|
+
ref={api.elementRef}
|
|
172
|
+
onClick={api.toggle}
|
|
173
|
+
aria-label="Animated Markgraf diagram"
|
|
174
|
+
/>
|
|
175
|
+
<Controls api={api} controls={controls} />
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const SvgPlayer = ({
|
|
181
|
+
src,
|
|
182
|
+
theme,
|
|
183
|
+
className,
|
|
184
|
+
controls,
|
|
185
|
+
}: MarkgrafPlayerProps & { theme: Theme; controls: MarkgrafControls }) => {
|
|
186
|
+
const api = useMarkgraf(src, { renderer: "svg", theme });
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<div
|
|
190
|
+
className={`markgraf-doc-player ${className ?? ""}`}
|
|
191
|
+
data-markgraf-theme={theme}
|
|
192
|
+
>
|
|
193
|
+
<svg
|
|
194
|
+
className="markgraf-player markgraf-doc-stage"
|
|
195
|
+
ref={api.elementRef}
|
|
196
|
+
onClick={api.toggle}
|
|
197
|
+
aria-label="Animated Markgraf diagram"
|
|
198
|
+
/>
|
|
199
|
+
<Controls api={api} controls={controls} />
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export const StarlightMarkgrafPlayer = ({
|
|
205
|
+
src,
|
|
206
|
+
renderer = "canvas",
|
|
207
|
+
className,
|
|
208
|
+
controls = true,
|
|
209
|
+
}: MarkgrafPlayerProps) => {
|
|
210
|
+
const theme = useDocumentTheme();
|
|
211
|
+
const props = { src, renderer, className, controls, theme };
|
|
212
|
+
return renderer === "svg" ? (
|
|
213
|
+
<SvgPlayer {...props} />
|
|
214
|
+
) : (
|
|
215
|
+
<CanvasPlayer {...props} />
|
|
216
|
+
);
|
|
217
|
+
};
|
package/src/player.css
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
@import "@markgrafhq/markgraf-react/css";
|
|
2
|
+
|
|
3
|
+
.markgraf-doc-player {
|
|
4
|
+
position: relative;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
width: 100%;
|
|
8
|
+
min-width: 0;
|
|
9
|
+
min-height: 11.25rem;
|
|
10
|
+
aspect-ratio: 16 / 9;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
border: 1px solid color-mix(in srgb, currentColor 14%, transparent);
|
|
13
|
+
border-radius: 0.875rem;
|
|
14
|
+
background: #ffffff;
|
|
15
|
+
box-shadow: 0 1rem 3rem rgb(0 0 0 / 10%);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.markgraf-doc-player[data-markgraf-theme="dark"] {
|
|
19
|
+
background: #101827;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.markgraf-doc-stage {
|
|
23
|
+
display: block;
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 100%;
|
|
26
|
+
min-height: 11.25rem;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.markgraf-doc-controls {
|
|
31
|
+
position: absolute;
|
|
32
|
+
z-index: 2;
|
|
33
|
+
right: 50%;
|
|
34
|
+
bottom: 0.75rem;
|
|
35
|
+
display: grid;
|
|
36
|
+
grid-template-columns: 1.75rem minmax(7rem, 1fr) auto auto;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 0.5rem;
|
|
39
|
+
width: min(35rem, calc(100% - 2rem));
|
|
40
|
+
padding: 0.375rem 0.5rem;
|
|
41
|
+
border: 1px solid rgb(255 255 255 / 14%);
|
|
42
|
+
border-radius: 999px;
|
|
43
|
+
color: #ffffff;
|
|
44
|
+
background: rgb(8 13 22 / 58%);
|
|
45
|
+
box-shadow: 0 0.875rem 2.75rem rgb(0 0 0 / 28%);
|
|
46
|
+
opacity: 0.78;
|
|
47
|
+
transform: translateX(50%);
|
|
48
|
+
transition:
|
|
49
|
+
opacity 140ms ease,
|
|
50
|
+
background 140ms ease;
|
|
51
|
+
backdrop-filter: blur(18px);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.markgraf-doc-player:hover .markgraf-doc-controls,
|
|
55
|
+
.markgraf-doc-controls:focus-within {
|
|
56
|
+
background: rgb(8 13 22 / 78%);
|
|
57
|
+
opacity: 0.98;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.markgraf-doc-play {
|
|
61
|
+
display: grid;
|
|
62
|
+
width: 1.75rem;
|
|
63
|
+
height: 1.75rem;
|
|
64
|
+
padding: 0;
|
|
65
|
+
border: 0;
|
|
66
|
+
border-radius: 999px;
|
|
67
|
+
place-items: center;
|
|
68
|
+
color: inherit;
|
|
69
|
+
background: rgb(255 255 255 / 10%);
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.markgraf-doc-play:hover,
|
|
74
|
+
.markgraf-doc-play:focus-visible {
|
|
75
|
+
background: rgb(255 255 255 / 20%);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.markgraf-doc-play svg {
|
|
79
|
+
width: 0.875rem;
|
|
80
|
+
height: 0.875rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.markgraf-doc-scrub {
|
|
84
|
+
min-width: 0;
|
|
85
|
+
height: 0.875rem;
|
|
86
|
+
margin: 0;
|
|
87
|
+
accent-color: #ffffff;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.markgraf-doc-time {
|
|
92
|
+
color: rgb(255 255 255 / 76%);
|
|
93
|
+
font-family: var(
|
|
94
|
+
--sl-font-mono,
|
|
95
|
+
var(--ifm-font-family-monospace, ui-monospace, monospace)
|
|
96
|
+
);
|
|
97
|
+
font-size: 0.625rem;
|
|
98
|
+
line-height: 1;
|
|
99
|
+
font-variant-numeric: tabular-nums;
|
|
100
|
+
white-space: nowrap;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.markgraf-doc-speed-control {
|
|
104
|
+
position: relative;
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.markgraf-doc-speed {
|
|
110
|
+
appearance: none;
|
|
111
|
+
width: 100%;
|
|
112
|
+
padding: 0.1875rem 1.375rem 0.1875rem 0.5rem;
|
|
113
|
+
border: 0;
|
|
114
|
+
border-radius: 999px;
|
|
115
|
+
color: #ffffff;
|
|
116
|
+
background: rgb(255 255 255 / 10%);
|
|
117
|
+
font-size: 0.625rem;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.markgraf-doc-speed-icon {
|
|
122
|
+
position: absolute;
|
|
123
|
+
right: 0.4375rem;
|
|
124
|
+
width: 0.75rem;
|
|
125
|
+
height: 0.75rem;
|
|
126
|
+
color: rgb(255 255 255 / 72%);
|
|
127
|
+
pointer-events: none;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.markgraf-doc-speed option {
|
|
131
|
+
color: #111827;
|
|
132
|
+
background: #ffffff;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.markgraf-doc-controls :disabled {
|
|
136
|
+
cursor: wait;
|
|
137
|
+
opacity: 0.5;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@media (max-width: 32rem) {
|
|
141
|
+
.markgraf-doc-controls {
|
|
142
|
+
grid-template-columns: 1.75rem minmax(5rem, 1fr) auto;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.markgraf-doc-time {
|
|
146
|
+
display: none;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@media (prefers-reduced-motion: reduce) {
|
|
151
|
+
.markgraf-doc-controls {
|
|
152
|
+
transition: none;
|
|
153
|
+
}
|
|
154
|
+
}
|