@fretfall/player 0.2.2 → 0.3.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/README.md +46 -1
- package/index.html +1 -1
- package/mount.d.ts +81 -0
- package/mount.js +970 -0
- package/package.json +15 -2
- package/player.js +1 -1
package/README.md
CHANGED
|
@@ -45,14 +45,56 @@ fretfall.part = 1;
|
|
|
45
45
|
| Member | |
|
|
46
46
|
|:--|:--|
|
|
47
47
|
| `open(files, options)` / `pick()` | Open songs, or show the file dialog |
|
|
48
|
-
| `playing`, `time`, `length` | Where playback is, in seconds. `playing` is settable: it waits for the synth's instruments |
|
|
48
|
+
| `playing`, `time`, `length` | Where playback is, in seconds. `playing` is settable: it waits for the synth's instruments. So is `time`: a jump to there, kept within the song |
|
|
49
49
|
| `parts`, `part` | The song's parts, and the one shown (settable) |
|
|
50
|
+
| `notes` | What the part shown asks for: the open strings, and each note's time, string, fret and midi note. A copy each read |
|
|
51
|
+
| `speed` | How fast it plays, 1 being the song's own tempo. Settable, in the player's steps of a tenth from 0.1 to 1.5 |
|
|
52
|
+
| `style` | The look: note shape, colour set, fonts, string colours, headstock. Settable whole or in part |
|
|
50
53
|
| `addFormat({ name, extensions, open })` | Read another file type: `open(file)` resolves to `{ song, audio }` |
|
|
51
54
|
| `closeSheets()` | Close Settings and the notation sheet |
|
|
55
|
+
| `dock(element, more?)` | Mounted in a page: the header's controls in an element of the page's own, see below |
|
|
52
56
|
|
|
53
57
|
Events on `window`: `fretfall:ready`, `fretfall:song`, `fretfall:playing`, `fretfall:sheet`. The modules import on their own
|
|
54
58
|
too, for example `music.js` for tunings and note names.
|
|
55
59
|
|
|
60
|
+
### In a page of your own
|
|
61
|
+
|
|
62
|
+
A page that isn't the player's, a React app's say, mounts it in an element instead. `mount.js` is written from the player's
|
|
63
|
+
page by the build, so it carries the same markup and styles, kept to that element:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
import { mount } from '@fretfall/player/mount.js';
|
|
67
|
+
|
|
68
|
+
const fretfall = await mount(element); // window.fretfall, once fretfall:ready has fired
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- **Once a page.** The player's modules run once and there is no unmount: a second call returns the first one's promise. Keep
|
|
72
|
+
the element and hide it (`hidden`, `display: none`) rather than removing it; hidden, the player draws nothing and leaves
|
|
73
|
+
the keys alone. It plays on, so pause it first: `fretfall.playing = false`.
|
|
74
|
+
- **The element needs a size**, a height above all: the player fills it, whether that is the whole screen or a hero. Inside,
|
|
75
|
+
it is a box of its own (`contain: layout`): its bar and sheets stay in it, and full screen is that box's.
|
|
76
|
+
- **The ids in the player are its own** — `play`, `open`, `menu`, `settings`, `file`, `fonts` and a hundred more — so the page
|
|
77
|
+
around it must not use them; `mount()` rejects if it finds one taken. Its small-screen layouts still follow the window's
|
|
78
|
+
width, not the element's.
|
|
79
|
+
- The page around it stays the page's: its styles, its title (follow `fretfall:song` to set one), keys pressed in its inputs
|
|
80
|
+
and on its buttons, and files dropped outside the player. With the focus nowhere, the shortcuts are the player's. Band
|
|
81
|
+
windows open the page's own URL with `?band=`, so that URL has to mount the player again.
|
|
82
|
+
|
|
83
|
+
#### One bar: the player's controls in the page's own
|
|
84
|
+
|
|
85
|
+
A page with a bar of its own has two once the player is in it. `fretfall.dock(element)` moves the header's controls — the
|
|
86
|
+
part picker, time, speed, volume, loop, play, open, help, settings and full screen — into `element`, and hides the player's
|
|
87
|
+
header while they are there, so the highway starts at the top of its element. They are the player's own buttons, styled
|
|
88
|
+
by its stylesheet: `element` is marked `data-fretfall` (and `data-fretfall-dock`) so that reaches it, in its own colours
|
|
89
|
+
and fonts rather than the theme's — set the variables it draws with (`--chip`, `--chip-border`, `--ink`, `--text`,
|
|
90
|
+
`--muted`, `--accent`, `--ui`, `--num`) on the element for your bar's, and hide or restyle what your bar does differently
|
|
91
|
+
under that attribute. `fretfall.dock(element, more)` puts help, settings and full screen into `more` instead, for a bar
|
|
92
|
+
with a right side of its own. The tooltips and the volume slider open under the buttons, where they stand now.
|
|
93
|
+
`fretfall.dock(null)` brings everything home, and a page removing its bar has to call it first, or the controls go with
|
|
94
|
+
it (`demo/mount.html` shows a dock).
|
|
95
|
+
|
|
96
|
+
Types come with it (`mount.d.ts`): `mount()`, the `fretfall` hook and its events.
|
|
97
|
+
|
|
56
98
|
Keys: <kbd>Space</kbd> play · <kbd>L</kbd> loop · <kbd>O</kbd> open · <kbd>D</kbd> 2D tab · <kbd>K</kbd> metronome ·
|
|
57
99
|
<kbd>F</kbd> full screen · <kbd>S</kbd> settings · <kbd>?</kbd> everything else. Add `?perf` to the URL for a frame profiler.
|
|
58
100
|
|
|
@@ -71,6 +113,9 @@ npm run bench # frame time, draw calls, sync and size of dist/
|
|
|
71
113
|
into `dist/`, which is what npm publishes and what the demo serves — so the page next to the modules, as a page built on the
|
|
72
114
|
player has it. The front door, between `<!-- demo -->` and `<!-- /demo -->` in the page, stays out of the package.
|
|
73
115
|
|
|
116
|
+
`demo/mount.html` tries the mounted player, in a page with a look and an input of its own. `mount.js` only exists once built,
|
|
117
|
+
so it is served from the built demo: `DEMO=1 npm run build && npx serve dist`, then `/mount.html`.
|
|
118
|
+
|
|
74
119
|
No pull request may make the player slower or bigger. CI builds it and the base branch, benchmarks both in turns on the same
|
|
75
120
|
runner, and fails on more than 10% frame or sync time, 5% draw calls or 1% gzipped size. A regression taken on purpose
|
|
76
121
|
passes with the `perf-ok` label.
|
package/index.html
CHANGED
|
@@ -11,7 +11,7 @@ rel="preload"
|
|
|
11
11
|
href="https://cdn.jsdelivr.net/npm/@coderline/alphatab@1.8.4/dist/alphaTab.min.js"
|
|
12
12
|
as="script"
|
|
13
13
|
/>
|
|
14
|
-
<style>:root{--bg: #0e1014;--ink: #0e1014;--text: #e8eaef;--muted: #7b8190;--accent: #c8f04a;--chip: #171a20;--chip-border: #2a2f38;--done: #e8eaef;--todo: #23272f;--ui: system-ui, sans-serif;--num: system-ui, sans-serif;--lyric: system-ui, sans-serif;--lyric-style: normal}*{box-sizing:border-box}[hidden]{display:none!important}html,body{height:100%}body{margin:0;overflow:hidden;background:var(--bg);color:var(--text);font:14px/1.4 var(--ui);accent-color:var(--accent)}canvas{position:fixed;inset:0;width:100%;height:100%;display:block}button{font:inherit;color:inherit}.bar{position:fixed;z-index:3;left:32px;right:32px;top:18px;height:40px;display:flex;align-items:center;justify-content:space-between;gap:24px;transition:transform .25s ease,opacity .25s ease,visibility .25s}.group{display:flex;align-items:center;gap:10px;min-width:0}.group:last-child{flex:none}.wordmark{display:flex;align-items:center;gap:7px;font:700 15px var(--num);letter-spacing:.22em}:is(.wordmark,.welcome-mark) .fall{transform-box:fill-box;transform-origin:center;transform:rotate(14deg);animation:fall .75s .8s both}@keyframes fall{0%{transform:translate(-3.5px,-1.42px) rotate(0);animation-timing-function:cubic-bezier(.55,0,.85,.35)}40%{transform:translate(0) rotate(18deg);animation-timing-function:cubic-bezier(.2,.7,.4,1)}56%{transform:translateY(-.35px) rotate(11.5deg);animation-timing-function:ease-in-out}74%{transform:translateY(.08px) rotate(15.2deg);animation-timing-function:ease-in-out}88%{transform:translate(0) rotate(13.6deg);animation-timing-function:ease-in-out}to{transform:translate(0) rotate(14deg)}}@media(prefers-reduced-motion:reduce){.wordmark .fall{animation:none}}.seg{display:flex;gap:2px;padding:3px;border-radius:9px;background:var(--chip);border:1px solid var(--chip-border);overflow-x:auto;scrollbar-width:none}.seg button{border:0;background:none;cursor:pointer;padding:5px 12px;border-radius:6px;font:500 13px var(--ui);color:var(--muted);white-space:nowrap}.seg button[aria-pressed=true]{background:var(--text);color:var(--ink);font-weight:600}.seg select{max-width:22vw;border:0;padding:4px 6px;background:none;color:var(--text);font:600 13px var(--ui)}.seg option{background:var(--ink);color:var(--text)}.icon{flex:none;cursor:pointer;width:36px;height:36px;display:grid;place-items:center;padding:0;border-radius:8px;background:var(--chip);border:1px solid var(--chip-border);color:var(--text)}.icon[aria-pressed=true],.icon[aria-expanded=true]{background:var(--accent);border-color:var(--accent);color:var(--ink)}button:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.speed{height:36px;display:flex;align-items:center;padding:0 2px;border-radius:8px;background:var(--chip);border:1px solid var(--chip-border);color:var(--muted)}.speed button{cursor:pointer;width:28px;height:32px;display:grid;place-items:center;padding:0;border:0;background:none;color:inherit}.speed #speed{width:auto;min-width:46px;font:600 14px var(--num);color:var(--text)}.time{margin-right:6px;font:14px var(--num);color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}.divider{width:1px;height:24px;margin:0 4px;background:var(--chip-border)}.menu{display:contents}#menuButton{display:none}#phrases{position:fixed;left:32px;right:32px;top:74px;height:54px;cursor:pointer;touch-action:none;-webkit-user-select:none;user-select:none}#phrases div{position:absolute;bottom:0;min-width:2px;border-radius:2px;background:var(--todo)}#phrases div.done{background:var(--done)}#phraseTime{position:absolute;top:calc(100% + 16px);transform:translate(-50%);padding:2px 7px;border-radius:6px;background:var(--ink);border:1px solid var(--chip-border);font:600 11px var(--num);font-variant-numeric:tabular-nums;color:var(--text);pointer-events:none}#loopBox{position:absolute;top:-6px;bottom:-17px;border:1.5px solid var(--accent);background:color-mix(in srgb,var(--accent) 10%,transparent);box-shadow:0 0 18px color-mix(in srgb,var(--accent) 22%,transparent)}#loopBox:before,#loopBox:after{content:"";position:absolute;top:-11px;bottom:-11px;width:4px;border:4px solid transparent;border-radius:6px;background:var(--accent) padding-box;cursor:ew-resize}#loopBox:before{left:-6.75px}#loopBox:after{right:-6.75px}#progress{position:fixed;left:32px;right:32px;top:134px;height:5px;border-radius:3px;overflow:hidden;background:var(--todo)}#progress div{width:0;height:100%;background:var(--accent);opacity:.9}#lyrics{position:fixed;left:40px;top:164px;max-width:min(560px,46vw);display:flex;flex-direction:column;gap:8px;pointer-events:none;font-family:var(--lyric);font-style:var(--lyric-style);text-shadow:0 2px 14px var(--ink),0 0 4px var(--ink)}#section{min-height:16px;font:600 12px var(--num);letter-spacing:.18em;color:var(--accent)}#lines{position:relative;height:2.45em;font-size:clamp(20px,2.3vw,32px);line-height:1.15}#lines div{position:absolute;top:0;left:0;white-space:nowrap;color:var(--muted);transition:transform .45s cubic-bezier(.22,1,.36,1),opacity .35s ease}#lines .low{transform:translateY(1.3em);opacity:.5}#lines .gone{opacity:0;transition-duration:.45s,.25s}#lines .gone.up{transform:translateY(-.8em)}#lines span{transition:color .15s}#lines .sung{color:var(--text)}@media(prefers-reduced-motion:reduce){#lines div{transition-property:opacity}}#info{position:fixed;left:40px;right:226px;bottom:14px;display:flex;align-items:center;font-size:13px;white-space:nowrap;color:var(--muted);pointer-events:none}#info b{color:var(--text);font-weight:600}#info>span+span:before{content:"";display:inline-block;width:1px;height:12px;margin:0 14px;vertical-align:-1px;background:var(--chip-border)}#info .tuning{position:relative;pointer-events:auto;cursor:help}#info .tuning>b{text-decoration:underline dotted var(--muted);text-underline-offset:3px}#info .tip{left:0;right:auto;white-space:normal}#info .tuning:is(:hover,:focus)>.tip{opacity:1;visibility:visible;transform:none}.pegs{display:flex;align-items:flex-start;gap:4px;margin:10px 0 8px}.peg{display:flex;flex-direction:column;align-items:center;min-width:26px;gap:2px;font:600 13px var(--num)}.peg i{font-style:normal;font-size:11px;color:var(--muted)}.peg .move:before{content:"";display:block;width:3px;height:calc(6px + var(--d) * 3px);margin:2px auto 3px;border-radius:2px;background:var(--c)}.peg strong{color:var(--c)}.peg.same{opacity:.45}.tip small{display:block;font-size:11px}:root:fullscreen .bar{transform:translateY(calc(-100% - 18px));opacity:0;visibility:hidden;transition-delay:.4s}:root:fullscreen:has(#phrases:hover,#progress:hover,.bar:hover) .bar{transform:none;opacity:1;visibility:visible;transition-delay:0s}#tools{position:fixed;z-index:3;right:32px;bottom:6px}#tools .icon{position:relative}.icon:disabled{cursor:default;color:var(--muted)}.icon:disabled svg{opacity:.5}#loopTools{position:fixed;z-index:4;display:flex;transform:translate(-50%,calc(-50% + 3px))}#loopTools .icon{width:32px;height:26px;background:var(--ink);border-color:var(--accent)}#loopTools svg{width:15px;height:15px}#trainer{border-radius:13px 0 0 13px}#clearLoop{border-left:0;border-radius:0 13px 13px 0;color:var(--muted)}#clearLoop:hover{color:var(--text)}#loopTools .icon[aria-pressed=true]{background:var(--accent);color:var(--ink)}.tip{position:absolute;right:0;bottom:calc(100% + 10px);width:max-content;max-width:250px;padding:8px 10px;border-radius:8px;background:var(--ink);border:1px solid var(--chip-border);box-shadow:0 12px 30px #00000073;color:var(--muted);font:12px/1.45 var(--ui);text-align:left;pointer-events:none;opacity:0;visibility:hidden;transform:translateY(4px);transition:opacity .12s ease,transform .12s ease,visibility .12s}.tip b{display:flex;justify-content:space-between;gap:12px;margin-bottom:2px;color:var(--text);font-weight:600}.tip kbd{padding:0 5px;border:1px solid var(--chip-border);border-radius:4px;color:var(--muted);font:inherit}:is(.icon,.speed):hover>.tip,.icon:focus-visible>.tip,.speed:focus-within>.tip{opacity:1;visibility:visible;transform:none}.bar :is(.icon,.speed){position:relative}.bar .tip{top:calc(100% + 10px);bottom:auto;transform:translateY(-4px)}.bar [aria-expanded=true]>.tip{display:none}#loopTools .tip{top:calc(100% + 10px);bottom:auto;left:var(--tip, 50%);right:auto;transform:translate(calc(-1 * var(--tip, 50%)),-4px)}#loopTools .icon:hover>.tip,#loopTools .icon:focus-visible>.tip{transform:translate(calc(-1 * var(--tip, 50%)))}:root:fullscreen :is(#info,#tools>:not(#minimal)){display:none}:root.minimal :is(.bar,#phrases,#progress,#loopTools,#info,#tools>:not(#minimal)){display:none}:root.tab2d .only-3d,:root:not(.tab2d) .only-2d{display:none}:root.tab2d .only-3d+.only-3d+h3{padding-top:0;border-top:0}:root.minimal #lyrics{top:24px}#status{position:fixed;left:50%;top:42%;transform:translate(-50%,-50%);display:flex;align-items:center;gap:12px;max-width:min(560px,90vw);padding:10px 16px;border-radius:10px;background:var(--chip);border:1px solid var(--chip-border);backdrop-filter:blur(10px);text-align:center}:root.banding :is(#highway,#phrases,#progress,#loopTools,#lyrics,#info,#intro,#time){display:none}#bandNote{position:fixed;left:50%;top:56%;transform:translate(-50%,-50%);display:grid;justify-items:center;gap:8px;max-width:min(440px,90vw);padding:14px 20px;border-radius:10px;background:var(--chip);border:1px solid var(--chip-border);text-align:center}#bandNote .text-button{justify-self:center}#status:not(.loading) .bars{display:none}.bars{flex:none;display:inline-flex;align-items:flex-end;gap:3px;height:16px}.bars i{width:3px;height:100%;border-radius:2px;background:var(--accent);transform:scaleY(.3);transform-origin:bottom}.bars.moving i{animation:bars .8s ease-in-out infinite alternate}.bars.moving i:nth-child(2){animation-duration:1.1s;animation-delay:-.5s}.bars.moving i:nth-child(3){animation-duration:.7s;animation-delay:-.2s}.bars.moving i:nth-child(4){animation-duration:1s;animation-delay:-.7s}@keyframes bars{0%{transform:scaleY(.2)}to{transform:scaleY(1)}}@media(prefers-reduced-motion:reduce){.bars.moving i{animation:none;transform:scaleY(.6)}}#intro{position:fixed;left:50%;top:36%;transform:translate(-50%,-50%);display:grid;justify-items:center;gap:6px;max-width:min(640px,90vw);padding:22px 30px;border-radius:16px;background:color-mix(in srgb,var(--ink) 72%,transparent);border:1px solid var(--chip-border);text-align:center;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .5s ease,visibility .5s}#intro.show{opacity:1;visibility:visible}#introTitle{font:700 clamp(22px,3vw,38px) / 1.15 var(--num);color:var(--text)}#introFacts{font-size:14px;color:var(--muted)}#introState{display:flex;align-items:center;gap:10px;margin-top:10px;font:600 12px var(--num);letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}.sheet{position:fixed;right:32px;top:66px;z-index:2;width:min(400px,calc(100vw - 32px));max-height:calc(100vh - 118px);display:flex;flex-direction:column;border-radius:14px;overflow:hidden;background:color-mix(in srgb,var(--ink) 94%,transparent);backdrop-filter:blur(18px);border:1px solid var(--chip-border);box-shadow:0 24px 60px #0000008c}.sheet-head{display:flex;align-items:center;justify-content:space-between;padding:12px 12px 10px 18px}.sheet-head h2{margin:0;font:600 15px var(--ui)}.close{width:30px;height:30px;display:grid;place-items:center;padding:0;border:0;border-radius:8px;background:none;color:var(--muted);cursor:pointer}.close:hover{background:var(--chip);color:var(--text)}.tabs{display:grid;grid-template-columns:repeat(5,1fr);gap:2px;margin:0 14px;padding:3px;border-radius:9px;background:var(--chip);border:1px solid var(--chip-border)}.tabs button{padding:6px 0;border:0;border-radius:6px;background:none;font:500 13px var(--ui);color:var(--muted);cursor:pointer}.tabs button:hover{color:var(--text)}.tabs button[aria-selected=true]{background:var(--text);color:var(--ink);font-weight:600}.pane{display:grid;align-content:start;gap:18px;padding:18px 18px 20px;overflow:hidden auto}.pane h3{margin:0 0 -6px;font:600 11px var(--num);letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}.pane h3~h3{padding-top:16px;border-top:1px solid var(--chip-border)}.setting{display:grid;gap:8px}.pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:18px 20px}.setting-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;font-size:13px;font-weight:500}.setting-head output{font:600 12px var(--num);color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}.setting .seg{overflow:visible}.setting .seg button{flex:1}.scale{display:flex;justify-content:space-between;margin-top:-4px;font-size:11px;color:var(--muted)}.hint{margin:-2px 0 0;font-size:12px;line-height:1.4;color:var(--muted)}.text-button{justify-self:start;padding:4px 0;border:0;background:none;font:500 12px var(--ui);color:var(--muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}.text-button:hover{color:var(--text)}:is(#settings,.volume) input[type=range]{-webkit-appearance:none;appearance:none;height:18px;margin:0;background:none;cursor:pointer}#settings input[type=range]{width:100%}:is(#settings,.volume) input[type=range]::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:linear-gradient(90deg,var(--accent) var(--fill, 50%),var(--chip-border) var(--fill, 50%))}:is(#settings,.volume) input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;margin-top:-6px;border-radius:50%;background:var(--text);border:3px solid var(--accent);box-shadow:0 1px 4px #0006}:is(#settings,.volume) input[type=range]::-moz-range-track{height:4px;border-radius:2px;background:var(--chip-border)}:is(#settings,.volume) input[type=range]::-moz-range-progress{height:4px;border-radius:2px;background:var(--accent)}:is(#settings,.volume) input[type=range]::-moz-range-thumb{width:10px;height:10px;border-radius:50%;background:var(--text);border:3px solid var(--accent)}.toggle{display:flex;align-items:center;justify-content:space-between;gap:16px;font-size:13px;font-weight:500;cursor:pointer}.toggle small{display:block;margin-top:2px;font-size:12px;font-weight:400;line-height:1.4;color:var(--muted)}.toggle input{-webkit-appearance:none;appearance:none;flex:none;position:relative;width:36px;height:20px;margin:0;border-radius:10px;background:var(--chip-border);cursor:pointer;transition:background .15s}.toggle input:after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--text);transition:transform .15s}.toggle input:checked{background:var(--accent)}.toggle input:checked:after{transform:translate(16px);background:var(--ink)}.cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.cards.three{grid-template-columns:repeat(3,minmax(0,1fr))}.cards button{display:grid;gap:8px;justify-items:start;padding:10px;border-radius:9px;border:1px solid var(--chip-border);background:var(--chip);font:500 12px var(--ui);color:var(--muted);text-align:left;cursor:pointer}.cards button:hover{color:var(--text)}.cards button[aria-pressed=true]{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);color:var(--text)}.dots{display:flex;gap:4px}.dots i{width:10px;height:10px;border-radius:50%;box-shadow:inset 0 0 0 1px #ffffff2e}.headstock{width:100%;height:52px}.order{font:600 12px var(--num);letter-spacing:.12em;color:var(--text)}#legend{width:min(430px,calc(100vw - 32px))}.legend-find{padding:0 14px 6px}.legend-find input{width:100%;height:34px;padding:0 12px;border-radius:9px;border:1px solid var(--chip-border);background:var(--chip);color:var(--text);font:13px var(--ui)}.legend-find input::placeholder{color:var(--muted)}.legend{gap:4px;padding-top:10px}.legend h3{margin:14px 0 4px}.legend h3:first-child{margin-top:0}.legend-item{display:grid;grid-template-columns:64px 1fr;gap:12px;align-items:center;padding:4px 0}.legend-item b{display:block;font-size:13px;font-weight:500}.legend-item span{display:block;font-size:12px;line-height:1.4;color:var(--muted)}.legend-item .keys{display:flex;justify-content:center;gap:4px}.legend-item kbd{min-width:22px;padding:2px 6px;border:1px solid var(--chip-border);border-bottom-width:2px;border-radius:5px;background:var(--chip);font:600 12px var(--num);text-align:center;white-space:nowrap}.legend-item svg{width:64px;height:36px;border-radius:8px;background:var(--chip);border:1px solid var(--chip-border);fill:none;stroke-linecap:round;stroke-linejoin:round}.legend-item text{font-family:var(--num);font-weight:700;text-anchor:middle;dominant-baseline:central}.legend-empty{margin:8px 0;font-size:13px;color:var(--muted)}.f0{fill:var(--s0)}.f1{fill:var(--s1)}.f2{fill:var(--s2)}.f3{fill:var(--s3)}.f4{fill:var(--s4)}.l0{stroke:var(--s0)}.l1{stroke:var(--s1)}.l2{stroke:var(--s2)}.l3{stroke:var(--s3)}.l4{stroke:var(--s4)}.lm{stroke:var(--muted)}.li{stroke:var(--ink)}.ft{fill:var(--text)}.lt{stroke:var(--text)}.fi{fill:var(--ink)}.fa{fill:var(--accent)}.fl{fill:var(--lane)}.ll{stroke:var(--lane)}.lw{stroke:#fff}.fm{fill:var(--muted)}.la{stroke:var(--accent)}.fw{fill:#fff}.volume{position:relative}.volume .icon.muted{color:var(--muted)}.volume-panel{position:absolute;top:calc(100% + 8px);left:50%;transform:translate(-50%);display:flex;align-items:center;gap:8px;padding:6px 14px 6px 6px;border-radius:10px;background:var(--ink);border:1px solid var(--chip-border);box-shadow:0 12px 30px #00000073}.volume-panel input{width:150px}.volume-panel output{min-width:36px;font:600 12px var(--num);color:var(--muted);text-align:right;font-variant-numeric:tabular-nums}.volume-mute{flex:none;width:30px;height:30px;display:grid;place-items:center;padding:0;border:0;border-radius:7px;background:none;color:var(--text);cursor:pointer}.volume-mute:hover{background:var(--chip)}.volume-mute[aria-pressed=true]{color:var(--muted)}#drop{position:fixed;inset:12px;z-index:5;display:grid;place-items:center;border:2px dashed var(--accent);border-radius:16px;background:#00000073;font:600 20px var(--num)}#tab{position:fixed;left:-10000px;top:0;width:1000px}@media(max-width:900px){.time{display:none}#menuButton{display:grid}.volume{position:static}.volume-panel{left:auto;right:0;transform:none}.menu{display:none;position:absolute;top:calc(100% + 8px);right:0;width:230px;flex-direction:column;gap:2px;padding:6px;border-radius:12px;background:var(--ink);border:1px solid var(--chip-border);box-shadow:0 12px 30px #00000073}#menuButton[aria-expanded=true]+.menu{display:flex}.menu .icon{width:100%;height:38px;display:flex;justify-content:flex-start;gap:12px;padding:0 10px;border:0;border-radius:7px;background:none;font:500 13px var(--ui)}.menu .icon:after{content:attr(data-label)}.menu .icon:hover{background:var(--chip)}.menu .icon[aria-pressed=true],.menu .icon[aria-expanded=true]{background:none;color:var(--accent)}.menu .tip{display:none}.menu .divider{width:auto;height:1px;margin:4px}}@media(max-width:560px){.bar{flex-wrap:wrap;height:auto;row-gap:8px}.bar>.group:first-child,.bar>.group:last-child{flex:1 1 100%;height:40px}.speed{margin-left:auto}#menuButton{position:absolute;top:2px;right:0}.menu{top:48px}#phrases{top:122px}#info{flex-direction:column;align-items:flex-start;gap:2px;font-size:12px}#info>span+span:before{display:none}#info>span:first-child{max-width:100%;overflow:hidden;text-overflow:ellipsis}#progress{top:182px}#lyrics{top:212px}.sheet{top:114px;max-height:calc(100vh - 166px)}}@media(max-width:520px){.sheet{right:16px}}</style>
|
|
14
|
+
<style>:root{--bg: #0e1014;--ink: #0e1014;--text: #e8eaef;--muted: #7b8190;--accent: #c8f04a;--chip: #171a20;--chip-border: #2a2f38;--done: #e8eaef;--todo: #23272f;--ui: system-ui, sans-serif;--num: system-ui, sans-serif;--lyric: system-ui, sans-serif;--lyric-style: normal}*{box-sizing:border-box}[hidden]{display:none!important}html,body{height:100%}body{margin:0;overflow:hidden;background:var(--bg);color:var(--text);font:14px/1.4 var(--ui);accent-color:var(--accent)}canvas{position:fixed;inset:0;width:100%;height:100%;display:block}button{font:inherit;color:inherit}.bar{position:fixed;z-index:3;left:32px;right:32px;top:18px;height:40px;display:flex;align-items:center;justify-content:space-between;gap:24px;transition:transform .25s ease,opacity .25s ease,visibility .25s}.group{display:flex;align-items:center;gap:10px;min-width:0}.group:last-child{flex:none}.wordmark{display:flex;align-items:center;gap:7px;font:700 15px var(--num);letter-spacing:.22em}:is(.wordmark,.welcome-mark) .fall{transform-box:fill-box;transform-origin:center;transform:rotate(14deg);animation:fall .75s .8s both}@keyframes fall{0%{transform:translate(-3.5px,-1.42px) rotate(0);animation-timing-function:cubic-bezier(.55,0,.85,.35)}40%{transform:translate(0) rotate(18deg);animation-timing-function:cubic-bezier(.2,.7,.4,1)}56%{transform:translateY(-.35px) rotate(11.5deg);animation-timing-function:ease-in-out}74%{transform:translateY(.08px) rotate(15.2deg);animation-timing-function:ease-in-out}88%{transform:translate(0) rotate(13.6deg);animation-timing-function:ease-in-out}to{transform:translate(0) rotate(14deg)}}@media(prefers-reduced-motion:reduce){.wordmark .fall{animation:none}}.seg{display:flex;gap:2px;padding:3px;border-radius:9px;background:var(--chip);border:1px solid var(--chip-border);overflow-x:auto;scrollbar-width:none}.seg button{border:0;background:none;cursor:pointer;padding:5px 12px;border-radius:6px;font:500 13px var(--ui);color:var(--muted);white-space:nowrap}.seg button[aria-pressed=true]{background:var(--text);color:var(--ink);font-weight:600}.seg select{max-width:22vw;border:0;padding:4px 6px;background:none;color:var(--text);font:600 13px var(--ui)}.seg option{background:var(--ink);color:var(--text)}.icon{flex:none;cursor:pointer;width:36px;height:36px;display:grid;place-items:center;padding:0;border-radius:8px;background:var(--chip);border:1px solid var(--chip-border);color:var(--text)}.icon[aria-pressed=true],.icon[aria-expanded=true]{background:var(--accent);border-color:var(--accent);color:var(--ink)}button:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.speed{height:36px;display:flex;align-items:center;padding:0 2px;border-radius:8px;background:var(--chip);border:1px solid var(--chip-border);color:var(--muted)}.speed button{cursor:pointer;width:28px;height:32px;display:grid;place-items:center;padding:0;border:0;background:none;color:inherit}.speed #speed{width:auto;min-width:46px;font:600 14px var(--num);color:var(--text)}.time{margin-right:6px;font:14px var(--num);color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}.divider{width:1px;height:24px;margin:0 4px;background:var(--chip-border)}.menu{display:contents}#menuButton{display:none}#phrases{position:fixed;left:32px;right:32px;top:74px;height:54px;cursor:pointer;touch-action:none;-webkit-user-select:none;user-select:none}#phrases div{position:absolute;bottom:0;min-width:2px;border-radius:2px;background:var(--todo)}#phrases div.done{background:var(--done)}#phraseTime{position:absolute;top:calc(100% + 16px);transform:translate(-50%);padding:2px 7px;border-radius:6px;background:var(--ink);border:1px solid var(--chip-border);font:600 11px var(--num);font-variant-numeric:tabular-nums;color:var(--text);pointer-events:none}#loopBox{position:absolute;top:-6px;bottom:-17px;border:1.5px solid var(--accent);background:color-mix(in srgb,var(--accent) 10%,transparent);box-shadow:0 0 18px color-mix(in srgb,var(--accent) 22%,transparent)}#loopBox:before,#loopBox:after{content:"";position:absolute;top:-11px;bottom:-11px;width:4px;border:4px solid transparent;border-radius:6px;background:var(--accent) padding-box;cursor:ew-resize}#loopBox:before{left:-6.75px}#loopBox:after{right:-6.75px}#progress{position:fixed;left:32px;right:32px;top:134px;height:5px;border-radius:3px;overflow:hidden;background:var(--todo)}#progress div{width:0;height:100%;background:var(--accent);opacity:.9}:root[data-docked] #phrases{top:18px}:root[data-docked] #progress{top:78px}:root[data-docked] #lyrics{top:108px}:root[data-docked] .sheet{top:18px}#lyrics{position:fixed;left:40px;top:164px;max-width:min(560px,46vw);display:flex;flex-direction:column;gap:8px;pointer-events:none;font-family:var(--lyric);font-style:var(--lyric-style);text-shadow:0 2px 14px var(--ink),0 0 4px var(--ink)}#section{min-height:16px;font:600 12px var(--num);letter-spacing:.18em;color:var(--accent)}#lines{position:relative;height:2.45em;font-size:clamp(20px,2.3vw,32px);line-height:1.15}#lines div{position:absolute;top:0;left:0;white-space:nowrap;color:var(--muted);transition:transform .45s cubic-bezier(.22,1,.36,1),opacity .35s ease}#lines .low{transform:translateY(1.3em);opacity:.5}#lines .gone{opacity:0;transition-duration:.45s,.25s}#lines .gone.up{transform:translateY(-.8em)}#lines span{transition:color .15s}#lines .sung{color:var(--text)}@media(prefers-reduced-motion:reduce){#lines div{transition-property:opacity}}#info{position:fixed;left:40px;right:226px;bottom:14px;display:flex;align-items:center;font-size:13px;white-space:nowrap;color:var(--muted);pointer-events:none}#info b{color:var(--text);font-weight:600}#info>span+span:before{content:"";display:inline-block;width:1px;height:12px;margin:0 14px;vertical-align:-1px;background:var(--chip-border)}#info .tuning{position:relative;pointer-events:auto;cursor:help}#info .tuning>b{text-decoration:underline dotted var(--muted);text-underline-offset:3px}#info .tip{left:0;right:auto;white-space:normal}#info .tuning:is(:hover,:focus)>.tip{opacity:1;visibility:visible;transform:none}.pegs{display:flex;align-items:flex-start;gap:4px;margin:10px 0 8px}.peg{display:flex;flex-direction:column;align-items:center;min-width:26px;gap:2px;font:600 13px var(--num)}.peg i{font-style:normal;font-size:11px;color:var(--muted)}.peg .move:before{content:"";display:block;width:3px;height:calc(6px + var(--d) * 3px);margin:2px auto 3px;border-radius:2px;background:var(--c)}.peg strong{color:var(--c)}.peg.same{opacity:.45}.tip small{display:block;font-size:11px}:root:fullscreen .bar{transform:translateY(calc(-100% - 18px));opacity:0;visibility:hidden;transition-delay:.4s}:root:fullscreen:has(#phrases:hover,#progress:hover,.bar:hover) .bar{transform:none;opacity:1;visibility:visible;transition-delay:0s}#tools{position:fixed;z-index:3;right:32px;bottom:6px}#tools .icon{position:relative}.icon:disabled{cursor:default;color:var(--muted)}.icon:disabled svg{opacity:.5}#loopTools{position:fixed;z-index:4;display:flex;transform:translate(-50%,calc(-50% + 3px))}#loopTools .icon{width:32px;height:26px;background:var(--ink);border-color:var(--accent)}#loopTools svg{width:15px;height:15px}#trainer{border-radius:13px 0 0 13px}#clearLoop{border-left:0;border-radius:0 13px 13px 0;color:var(--muted)}#clearLoop:hover{color:var(--text)}#loopTools .icon[aria-pressed=true]{background:var(--accent);color:var(--ink)}.tip{position:absolute;right:0;bottom:calc(100% + 10px);width:max-content;max-width:250px;padding:8px 10px;border-radius:8px;background:var(--ink);border:1px solid var(--chip-border);box-shadow:0 12px 30px #00000073;color:var(--muted);font:12px/1.45 var(--ui);text-align:left;pointer-events:none;opacity:0;visibility:hidden;transform:translateY(4px);transition:opacity .12s ease,transform .12s ease,visibility .12s}.tip b{display:flex;justify-content:space-between;gap:12px;margin-bottom:2px;color:var(--text);font-weight:600}.tip kbd{padding:0 5px;border:1px solid var(--chip-border);border-radius:4px;color:var(--muted);font:inherit}:is(.icon,.speed):hover>.tip,.icon:focus-visible>.tip,.speed:focus-within>.tip{opacity:1;visibility:visible;transform:none}:root:where([data-fretfall-dock]){display:flex;align-items:center;gap:10px}.bar :is(.icon,.speed),:root:where([data-fretfall-dock]) :is(.icon,.speed){position:relative}.bar .tip,:root:where([data-fretfall-dock]) .tip{top:calc(100% + 10px);bottom:auto;transform:translateY(-4px)}.bar [aria-expanded=true]>.tip,:root:where([data-fretfall-dock]) [aria-expanded=true]>.tip{display:none}#loopTools .tip{top:calc(100% + 10px);bottom:auto;left:var(--tip, 50%);right:auto;transform:translate(calc(-1 * var(--tip, 50%)),-4px)}#loopTools .icon:hover>.tip,#loopTools .icon:focus-visible>.tip{transform:translate(calc(-1 * var(--tip, 50%)))}:root:fullscreen :is(#info,#tools>:not(#minimal)){display:none}:root.minimal :is(.bar,#phrases,#progress,#loopTools,#info,#tools>:not(#minimal)){display:none}:root.tab2d .only-3d,:root:not(.tab2d) .only-2d{display:none}:root.tab2d .only-3d+.only-3d+h3{padding-top:0;border-top:0}:root.minimal #lyrics{top:24px}#status{position:fixed;left:50%;top:42%;transform:translate(-50%,-50%);display:flex;align-items:center;gap:12px;max-width:min(560px,90vw);padding:10px 16px;border-radius:10px;background:var(--chip);border:1px solid var(--chip-border);backdrop-filter:blur(10px);text-align:center}:root.banding :is(#highway,#phrases,#progress,#loopTools,#lyrics,#info,#intro,#time){display:none}#bandNote{position:fixed;left:50%;top:56%;transform:translate(-50%,-50%);display:grid;justify-items:center;gap:8px;max-width:min(440px,90vw);padding:14px 20px;border-radius:10px;background:var(--chip);border:1px solid var(--chip-border);text-align:center}#bandNote .text-button{justify-self:center}#status:not(.loading) .bars{display:none}.bars{flex:none;display:inline-flex;align-items:flex-end;gap:3px;height:16px}.bars i{width:3px;height:100%;border-radius:2px;background:var(--accent);transform:scaleY(.3);transform-origin:bottom}.bars.moving i{animation:bars .8s ease-in-out infinite alternate}.bars.moving i:nth-child(2){animation-duration:1.1s;animation-delay:-.5s}.bars.moving i:nth-child(3){animation-duration:.7s;animation-delay:-.2s}.bars.moving i:nth-child(4){animation-duration:1s;animation-delay:-.7s}@keyframes bars{0%{transform:scaleY(.2)}to{transform:scaleY(1)}}@media(prefers-reduced-motion:reduce){.bars.moving i{animation:none;transform:scaleY(.6)}}#intro{position:fixed;left:50%;top:36%;transform:translate(-50%,-50%);display:grid;justify-items:center;gap:6px;max-width:min(640px,90vw);padding:22px 30px;border-radius:16px;background:color-mix(in srgb,var(--ink) 72%,transparent);border:1px solid var(--chip-border);text-align:center;pointer-events:none;opacity:0;visibility:hidden;transition:opacity .5s ease,visibility .5s}#intro.show{opacity:1;visibility:visible}#introTitle{font:700 clamp(22px,3vw,38px) / 1.15 var(--num);color:var(--text)}#introFacts{font-size:14px;color:var(--muted)}#introState{display:flex;align-items:center;gap:10px;margin-top:10px;font:600 12px var(--num);letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}.sheet{position:fixed;right:32px;top:66px;z-index:2;width:min(400px,calc(100vw - 32px));max-height:calc(100vh - 118px);display:flex;flex-direction:column;border-radius:14px;overflow:hidden;background:color-mix(in srgb,var(--ink) 94%,transparent);backdrop-filter:blur(18px);border:1px solid var(--chip-border);box-shadow:0 24px 60px #0000008c}.sheet-head{display:flex;align-items:center;justify-content:space-between;padding:12px 12px 10px 18px}.sheet-head h2{margin:0;font:600 15px var(--ui)}.close{width:30px;height:30px;display:grid;place-items:center;padding:0;border:0;border-radius:8px;background:none;color:var(--muted);cursor:pointer}.close:hover{background:var(--chip);color:var(--text)}.tabs{display:grid;grid-template-columns:repeat(5,1fr);gap:2px;margin:0 14px;padding:3px;border-radius:9px;background:var(--chip);border:1px solid var(--chip-border)}.tabs button{padding:6px 0;border:0;border-radius:6px;background:none;font:500 13px var(--ui);color:var(--muted);cursor:pointer}.tabs button:hover{color:var(--text)}.tabs button[aria-selected=true]{background:var(--text);color:var(--ink);font-weight:600}.pane{display:grid;align-content:start;gap:18px;padding:18px 18px 20px;overflow:hidden auto}.pane h3{margin:0 0 -6px;font:600 11px var(--num);letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}.pane h3~h3{padding-top:16px;border-top:1px solid var(--chip-border)}.setting{display:grid;gap:8px}.pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:18px 20px}.setting-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;font-size:13px;font-weight:500}.setting-head output{font:600 12px var(--num);color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}.setting .seg{overflow:visible}.setting .seg button{flex:1}.scale{display:flex;justify-content:space-between;margin-top:-4px;font-size:11px;color:var(--muted)}.hint{margin:-2px 0 0;font-size:12px;line-height:1.4;color:var(--muted)}.text-button{justify-self:start;padding:4px 0;border:0;background:none;font:500 12px var(--ui);color:var(--muted);text-decoration:underline;text-underline-offset:3px;cursor:pointer}.text-button:hover{color:var(--text)}:is(#settings,.volume) input[type=range]{-webkit-appearance:none;appearance:none;height:18px;margin:0;background:none;cursor:pointer}#settings input[type=range]{width:100%}:is(#settings,.volume) input[type=range]::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:linear-gradient(90deg,var(--accent) var(--fill, 50%),var(--chip-border) var(--fill, 50%))}:is(#settings,.volume) input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;margin-top:-6px;border-radius:50%;background:var(--text);border:3px solid var(--accent);box-shadow:0 1px 4px #0006}:is(#settings,.volume) input[type=range]::-moz-range-track{height:4px;border-radius:2px;background:var(--chip-border)}:is(#settings,.volume) input[type=range]::-moz-range-progress{height:4px;border-radius:2px;background:var(--accent)}:is(#settings,.volume) input[type=range]::-moz-range-thumb{width:10px;height:10px;border-radius:50%;background:var(--text);border:3px solid var(--accent)}.toggle{display:flex;align-items:center;justify-content:space-between;gap:16px;font-size:13px;font-weight:500;cursor:pointer}.toggle small{display:block;margin-top:2px;font-size:12px;font-weight:400;line-height:1.4;color:var(--muted)}.toggle input{-webkit-appearance:none;appearance:none;flex:none;position:relative;width:36px;height:20px;margin:0;border-radius:10px;background:var(--chip-border);cursor:pointer;transition:background .15s}.toggle input:after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--text);transition:transform .15s}.toggle input:checked{background:var(--accent)}.toggle input:checked:after{transform:translate(16px);background:var(--ink)}.cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.cards.three{grid-template-columns:repeat(3,minmax(0,1fr))}.cards button{display:grid;gap:8px;justify-items:start;padding:10px;border-radius:9px;border:1px solid var(--chip-border);background:var(--chip);font:500 12px var(--ui);color:var(--muted);text-align:left;cursor:pointer}.cards button:hover{color:var(--text)}.cards button[aria-pressed=true]{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);color:var(--text)}.dots{display:flex;gap:4px}.dots i{width:10px;height:10px;border-radius:50%;box-shadow:inset 0 0 0 1px #ffffff2e}.headstock{width:100%;height:52px}.order{font:600 12px var(--num);letter-spacing:.12em;color:var(--text)}#legend{width:min(430px,calc(100vw - 32px))}.legend-find{padding:0 14px 6px}.legend-find input{width:100%;height:34px;padding:0 12px;border-radius:9px;border:1px solid var(--chip-border);background:var(--chip);color:var(--text);font:13px var(--ui)}.legend-find input::placeholder{color:var(--muted)}.legend{gap:4px;padding-top:10px}.legend h3{margin:14px 0 4px}.legend h3:first-child{margin-top:0}.legend-item{display:grid;grid-template-columns:64px 1fr;gap:12px;align-items:center;padding:4px 0}.legend-item b{display:block;font-size:13px;font-weight:500}.legend-item span{display:block;font-size:12px;line-height:1.4;color:var(--muted)}.legend-item .keys{display:flex;justify-content:center;gap:4px}.legend-item kbd{min-width:22px;padding:2px 6px;border:1px solid var(--chip-border);border-bottom-width:2px;border-radius:5px;background:var(--chip);font:600 12px var(--num);text-align:center;white-space:nowrap}.legend-item svg{width:64px;height:36px;border-radius:8px;background:var(--chip);border:1px solid var(--chip-border);fill:none;stroke-linecap:round;stroke-linejoin:round}.legend-item text{font-family:var(--num);font-weight:700;text-anchor:middle;dominant-baseline:central}.legend-empty{margin:8px 0;font-size:13px;color:var(--muted)}.f0{fill:var(--s0)}.f1{fill:var(--s1)}.f2{fill:var(--s2)}.f3{fill:var(--s3)}.f4{fill:var(--s4)}.l0{stroke:var(--s0)}.l1{stroke:var(--s1)}.l2{stroke:var(--s2)}.l3{stroke:var(--s3)}.l4{stroke:var(--s4)}.lm{stroke:var(--muted)}.li{stroke:var(--ink)}.ft{fill:var(--text)}.lt{stroke:var(--text)}.fi{fill:var(--ink)}.fa{fill:var(--accent)}.fl{fill:var(--lane)}.ll{stroke:var(--lane)}.lw{stroke:#fff}.fm{fill:var(--muted)}.la{stroke:var(--accent)}.fw{fill:#fff}.volume{position:relative}.volume .icon.muted{color:var(--muted)}.volume-panel{position:absolute;top:calc(100% + 8px);left:50%;transform:translate(-50%);display:flex;align-items:center;gap:8px;padding:6px 14px 6px 6px;border-radius:10px;background:var(--ink);border:1px solid var(--chip-border);box-shadow:0 12px 30px #00000073}.volume-panel input{width:150px}.volume-panel output{min-width:36px;font:600 12px var(--num);color:var(--muted);text-align:right;font-variant-numeric:tabular-nums}.volume-mute{flex:none;width:30px;height:30px;display:grid;place-items:center;padding:0;border:0;border-radius:7px;background:none;color:var(--text);cursor:pointer}.volume-mute:hover{background:var(--chip)}.volume-mute[aria-pressed=true]{color:var(--muted)}#drop{position:fixed;inset:12px;z-index:5;display:grid;place-items:center;border:2px dashed var(--accent);border-radius:16px;background:#00000073;font:600 20px var(--num)}#tab{position:fixed;left:-10000px;top:0;width:1000px}@media(max-width:900px){.time{display:none}:root:where([data-fretfall-dock]) .time{display:block}#menuButton{display:grid}.volume{position:static}.volume-panel{left:auto;right:0;transform:none}.menu{display:none;position:absolute;top:calc(100% + 8px);right:0;width:230px;flex-direction:column;gap:2px;padding:6px;border-radius:12px;background:var(--ink);border:1px solid var(--chip-border);box-shadow:0 12px 30px #00000073}#menuButton[aria-expanded=true]+.menu{display:flex}.menu .icon{width:100%;height:38px;display:flex;justify-content:flex-start;gap:12px;padding:0 10px;border:0;border-radius:7px;background:none;font:500 13px var(--ui)}.menu .icon:after{content:attr(data-label)}.menu .icon:hover{background:var(--chip)}.menu .icon[aria-pressed=true],.menu .icon[aria-expanded=true]{background:none;color:var(--accent)}.menu .tip{display:none}.menu .divider{width:auto;height:1px;margin:4px}}@media(max-width:560px){.bar{flex-wrap:wrap;height:auto;row-gap:8px}.bar>.group:first-child,.bar>.group:last-child{flex:1 1 100%;height:40px}.speed{margin-left:auto}#menuButton{position:absolute;top:2px;right:0}.menu{top:48px}#phrases{top:122px}#info{flex-direction:column;align-items:flex-start;gap:2px;font-size:12px}#info>span+span:before{display:none}#info>span:first-child{max-width:100%;overflow:hidden;text-overflow:ellipsis}#progress{top:182px}#lyrics{top:212px}.sheet{top:114px;max-height:calc(100vh - 166px)}}@media(max-width:520px){.sheet{right:16px}}</style>
|
|
15
15
|
<link rel="modulepreload" href="player.js">
|
|
16
16
|
<link rel="modulepreload" href="fingering.js">
|
|
17
17
|
<link rel="modulepreload" href="highway.js">
|
package/mount.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// The player mounted in an element of a page that isn't its own, and the hook that page talks to it through:
|
|
2
|
+
// window.fretfall, and the fretfall:* events on window (see the README)
|
|
3
|
+
|
|
4
|
+
/** A kind of song file the player doesn't read itself, added with `fretfall.addFormat`. */
|
|
5
|
+
export interface FretfallFormat<Options = Record<string, unknown>> {
|
|
6
|
+
/** As the Open dialog says it: "a .pak". */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Lower case, with the dot: `[".pak"]`. */
|
|
9
|
+
extensions: string[];
|
|
10
|
+
/** `options`: what `fretfall.open` was given, and `status(text)` to say what is taking the time. */
|
|
11
|
+
open(file: File, options: Options & { status(text: string): void }): Promise<{ song: unknown; audio?: Blob | null }>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Fretfall {
|
|
15
|
+
/** Open songs: a tab, or a tab with its recording. `options` are handed on to the format that opens a file. */
|
|
16
|
+
open(files: Iterable<File>, options?: Record<string, unknown>): Promise<void>;
|
|
17
|
+
/** Show the file dialog. */
|
|
18
|
+
pick(): void;
|
|
19
|
+
/** Settable: `true` waits for the synth's instruments if the song has just opened. */
|
|
20
|
+
playing: boolean;
|
|
21
|
+
/** Seconds into the song. Settable: a jump to there, kept within the song. */
|
|
22
|
+
time: number;
|
|
23
|
+
/** The song's length in seconds, 0 without a song. */
|
|
24
|
+
readonly length: number;
|
|
25
|
+
/** How fast it plays: 1 is the song's own tempo. Settable, in the player's steps of a tenth from 0.1 to 1.5. */
|
|
26
|
+
speed: number;
|
|
27
|
+
/**
|
|
28
|
+
* What the instrument looks like: the note shape (`look`), the colour set (`colors`), the fonts, the string colours
|
|
29
|
+
* (`strings`) and the headstock. Settable whole or in part; a name the player does not know is ignored, so add a
|
|
30
|
+
* colour set of your own to `COLORS` in `themes.js` before naming it here.
|
|
31
|
+
*/
|
|
32
|
+
style: { look: string; colors: string; fonts: string; strings: string; headstock: string };
|
|
33
|
+
/**
|
|
34
|
+
* What the part shown is asking for, or null without a song: `open`, the open strings' midi notes lowest string
|
|
35
|
+
* first, and every note as `time` and `sustain` in seconds, `string` (0 is the lowest), `fret`, and the `midi` note
|
|
36
|
+
* it sounds. A copy, made on each read: read it once a song, not each frame.
|
|
37
|
+
*/
|
|
38
|
+
readonly notes: { open: number[]; notes: { time: number; sustain: number; string: number; fret: number; midi: number }[] } | null;
|
|
39
|
+
/** The names of the song's parts. */
|
|
40
|
+
readonly parts: string[];
|
|
41
|
+
/** The part shown, -1 without one. Settable. */
|
|
42
|
+
part: number;
|
|
43
|
+
addFormat(format: FretfallFormat<any>): void;
|
|
44
|
+
/** Close Settings and the notation sheet. */
|
|
45
|
+
closeSheets(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Move the header's controls (the part picker, time, speed, volume, loop, play, open, help, settings, full screen)
|
|
48
|
+
* into `element`, an element of the page's own bar say, and hide the header meanwhile: the page has one bar and
|
|
49
|
+
* the highway the row. Help, settings and full screen go into `more` instead when given. Both are marked
|
|
50
|
+
* `data-fretfall` and `data-fretfall-dock`, so the player's styles reach the controls. `null` brings them home.
|
|
51
|
+
*/
|
|
52
|
+
dock(element: HTMLElement | null, more?: HTMLElement): void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface FretfallSong {
|
|
56
|
+
/** The file's name; null for a band window's song. */
|
|
57
|
+
file: string | null;
|
|
58
|
+
title?: string;
|
|
59
|
+
artist?: string;
|
|
60
|
+
parts: string[];
|
|
61
|
+
part: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Mounts the player in `element`, which has to be in the page and have a size. Once a page: there is no unmount, and a
|
|
66
|
+
* second call returns the first one's promise, so keep the element and hide it rather than removing it.
|
|
67
|
+
* Resolves to `window.fretfall` once `fretfall:ready` has fired.
|
|
68
|
+
*/
|
|
69
|
+
export function mount(element: HTMLElement): Promise<Fretfall>;
|
|
70
|
+
|
|
71
|
+
declare global {
|
|
72
|
+
var fretfall: Fretfall | undefined; // window.fretfall too
|
|
73
|
+
interface WindowEventMap {
|
|
74
|
+
'fretfall:ready': CustomEvent<null>;
|
|
75
|
+
'fretfall:song': CustomEvent<FretfallSong>;
|
|
76
|
+
'fretfall:playing': CustomEvent<{ playing: boolean }>;
|
|
77
|
+
/** Played out to its end, rather than paused. */
|
|
78
|
+
'fretfall:ended': CustomEvent<null>;
|
|
79
|
+
'fretfall:sheet': CustomEvent<{ name: 'settings' | 'legend'; open: boolean }>;
|
|
80
|
+
}
|
|
81
|
+
}
|