reelson 0.7.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/CHANGELOG.md +99 -0
- package/LICENSE +21 -0
- package/NOTICE.md +15 -0
- package/README.md +180 -0
- package/bin/reelson.js +649 -0
- package/bin/reelson.ts +676 -0
- package/bin/run.js +5 -0
- package/demo.config.example.json +51 -0
- package/docs/demo.webp +0 -0
- package/docs/prompting.md +72 -0
- package/docs/style-guide.md +117 -0
- package/examples/demo.config.json +18 -0
- package/examples/todo-add-item/scenario.ts +45 -0
- package/examples/todo-add-item/video.json +35 -0
- package/music/README.md +7 -0
- package/package.json +68 -0
- package/skills/reelson-compose/SKILL.md +226 -0
- package/skills/reelson-compose/schemas/video.schema.json +172 -0
- package/skills/reelson-compose/scripts/build.js +424 -0
- package/skills/reelson-compose/scripts/build.ts +532 -0
- package/skills/reelson-compose/scripts/captions.js +24 -0
- package/skills/reelson-compose/scripts/captions.ts +40 -0
- package/skills/reelson-compose/scripts/check.js +80 -0
- package/skills/reelson-compose/scripts/check.ts +93 -0
- package/skills/reelson-compose/scripts/composition.js +163 -0
- package/skills/reelson-compose/scripts/composition.ts +241 -0
- package/skills/reelson-compose/scripts/hyperframes.js +138 -0
- package/skills/reelson-compose/scripts/hyperframes.ts +157 -0
- package/skills/reelson-compose/scripts/portrait.js +150 -0
- package/skills/reelson-compose/scripts/portrait.ts +218 -0
- package/skills/reelson-compose/scripts/project.js +238 -0
- package/skills/reelson-compose/scripts/project.ts +324 -0
- package/skills/reelson-compose/scripts/studio.js +330 -0
- package/skills/reelson-compose/scripts/studio.ts +394 -0
- package/skills/reelson-compose/scripts/timeline.js +271 -0
- package/skills/reelson-compose/scripts/timeline.ts +514 -0
- package/skills/reelson-compose/scripts/tts.js +303 -0
- package/skills/reelson-compose/scripts/tts.ts +371 -0
- package/skills/reelson-compose/scripts/verify.js +190 -0
- package/skills/reelson-compose/scripts/verify.ts +209 -0
- package/skills/reelson-compose/scripts/voice.js +178 -0
- package/skills/reelson-compose/scripts/voice.ts +224 -0
- package/skills/reelson-compose/scripts/zooms.js +259 -0
- package/skills/reelson-compose/scripts/zooms.ts +333 -0
- package/skills/reelson-compose/sections/README.md +5 -0
- package/skills/reelson-compose/sections/intro/minimal/section.css +27 -0
- package/skills/reelson-compose/sections/intro/minimal/section.html +12 -0
- package/skills/reelson-compose/sections/intro/minimal/section.js +16 -0
- package/skills/reelson-compose/sections/intro/minimal/section.json +9 -0
- package/skills/reelson-compose/sections/intro/poster/section.css +27 -0
- package/skills/reelson-compose/sections/intro/poster/section.html +14 -0
- package/skills/reelson-compose/sections/intro/poster/section.js +28 -0
- package/skills/reelson-compose/sections/intro/poster/section.json +9 -0
- package/skills/reelson-compose/sections/intro/split/section.css +30 -0
- package/skills/reelson-compose/sections/intro/split/section.html +12 -0
- package/skills/reelson-compose/sections/intro/split/section.js +19 -0
- package/skills/reelson-compose/sections/intro/split/section.json +9 -0
- package/skills/reelson-compose/sections/outro/compact/section.css +23 -0
- package/skills/reelson-compose/sections/outro/compact/section.html +10 -0
- package/skills/reelson-compose/sections/outro/compact/section.js +11 -0
- package/skills/reelson-compose/sections/outro/compact/section.json +8 -0
- package/skills/reelson-compose/sections/outro/endcard/section.css +25 -0
- package/skills/reelson-compose/sections/outro/endcard/section.html +13 -0
- package/skills/reelson-compose/sections/outro/endcard/section.js +13 -0
- package/skills/reelson-compose/sections/outro/endcard/section.json +8 -0
- package/skills/reelson-compose/sections/outro/wordmark/section.css +11 -0
- package/skills/reelson-compose/sections/outro/wordmark/section.html +5 -0
- package/skills/reelson-compose/sections/outro/wordmark/section.js +12 -0
- package/skills/reelson-compose/sections/outro/wordmark/section.json +8 -0
- package/skills/reelson-compose/sections/recap/compact/section.css +26 -0
- package/skills/reelson-compose/sections/recap/compact/section.html +8 -0
- package/skills/reelson-compose/sections/recap/compact/section.js +23 -0
- package/skills/reelson-compose/sections/recap/compact/section.json +11 -0
- package/skills/reelson-compose/sections/recap/steps/section.css +36 -0
- package/skills/reelson-compose/sections/recap/steps/section.html +8 -0
- package/skills/reelson-compose/sections/recap/steps/section.js +27 -0
- package/skills/reelson-compose/sections/recap/steps/section.json +11 -0
- package/skills/reelson-compose/studio/index.html +81 -0
- package/skills/reelson-compose/studio/studio.css +142 -0
- package/skills/reelson-compose/studio/studio.js +1031 -0
- package/skills/reelson-compose/templates/README.md +193 -0
- package/skills/reelson-compose/templates/classic/assets/vendor/Inter-OFL.txt +93 -0
- package/skills/reelson-compose/templates/classic/assets/vendor/gsap.min.js +11 -0
- package/skills/reelson-compose/templates/classic/assets/vendor/inter-latin-ext-wght-normal.woff2 +0 -0
- package/skills/reelson-compose/templates/classic/assets/vendor/inter-latin-wght-normal.woff2 +0 -0
- package/skills/reelson-compose/templates/classic/stage.html +447 -0
- package/skills/reelson-compose/templates/classic/template.json +5 -0
- package/skills/reelson-record/SKILL.md +220 -0
- package/skills/reelson-record/schemas/demo.config.schema.json +269 -0
- package/skills/reelson-record/scripts/capture.js +142 -0
- package/skills/reelson-record/scripts/capture.ts +190 -0
- package/skills/reelson-record/scripts/config.js +148 -0
- package/skills/reelson-record/scripts/config.ts +264 -0
- package/skills/reelson-record/scripts/cursor-overlay.js +111 -0
- package/skills/reelson-record/scripts/cursor-overlay.ts +122 -0
- package/skills/reelson-record/scripts/doctor.js +128 -0
- package/skills/reelson-record/scripts/doctor.ts +147 -0
- package/skills/reelson-record/scripts/languages.js +29 -0
- package/skills/reelson-record/scripts/languages.ts +41 -0
- package/skills/reelson-record/scripts/record.js +365 -0
- package/skills/reelson-record/scripts/record.ts +451 -0
- package/skills/reelson-record/scripts/scenario.js +356 -0
- package/skills/reelson-record/scripts/scenario.ts +570 -0
- package/skills/reelson-record/scripts/validate.js +143 -0
- package/skills/reelson-record/scripts/validate.ts +179 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#outro { display: grid; place-items: center; }
|
|
2
|
+
#outro .card { display: flex; flex-direction: column; align-items: center; gap: 72px; text-align: center; will-change: transform; }
|
|
3
|
+
#outro .brand { display: flex; flex-direction: column; align-items: center; gap: 26px; }
|
|
4
|
+
#outro .wordmark { font-size: 120px; }
|
|
5
|
+
#outro .brand-line { width: 180px; }
|
|
6
|
+
#outro .what { display: flex; flex-direction: column; align-items: center; gap: 22px; }
|
|
7
|
+
#outro .title { font-size: 76px; font-weight: 800; letter-spacing: -0.04em; line-height: 1.05; max-width: 1400px; }
|
|
8
|
+
#outro .subtitle { font-size: 34px; font-weight: 400; color: var(--muted); max-width: 1300px; line-height: 1.35; }
|
|
9
|
+
#outro .subtitle:empty { display: none; }
|
|
10
|
+
|
|
11
|
+
/* Portrait: the same card, sized for a phone screen */
|
|
12
|
+
#root.portrait #outro .card { gap: 90px; padding: 0 80px; }
|
|
13
|
+
#root.portrait #outro .wordmark { font-size: 132px; }
|
|
14
|
+
#root.portrait #outro .brand-sub { font-size: 34px; }
|
|
15
|
+
#root.portrait #outro .title { font-size: 92px; max-width: 920px; }
|
|
16
|
+
#root.portrait #outro .subtitle { font-size: 42px; max-width: 880px; }
|
|
17
|
+
#root.portrait #outro .meta { font-size: 30px; }
|
|
18
|
+
|
|
19
|
+
/* Square: the same card, narrower */
|
|
20
|
+
#root.square #outro .card { gap: 64px; padding: 0 70px; }
|
|
21
|
+
#root.square #outro .wordmark { font-size: 112px; }
|
|
22
|
+
#root.square #outro .brand-sub { font-size: 30px; }
|
|
23
|
+
#root.square #outro .title { font-size: 76px; max-width: 940px; }
|
|
24
|
+
#root.square #outro .subtitle { font-size: 36px; max-width: 900px; }
|
|
25
|
+
#root.square #outro .meta { font-size: 26px; }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<section id="outro" class="clip" data-start="{{START}}" data-duration="{{DURATION}}" data-track-index="{{TRACK}}">
|
|
2
|
+
<div class="card">
|
|
3
|
+
<div class="brand">
|
|
4
|
+
<div class="wordmark" data-text="{{BRAND}}" data-logo="{{BRAND_LOGO}}"></div>
|
|
5
|
+
<div class="brand-line"></div>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="what">
|
|
8
|
+
<h1 class="title">{{TITLE}}</h1>
|
|
9
|
+
<p class="subtitle">{{SUBTITLE}}</p>
|
|
10
|
+
<div class="meta" data-chip></div>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</section>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Waits out the recap's cross-fade (0.3 s) so nothing lands on its text, then letters rise
|
|
2
|
+
// from the centre, the line wipes open, title / subtitle / chip follow. Then it holds with a barely-there drift and ends
|
|
3
|
+
// fully visible: the paused last frame is a readable end card.
|
|
4
|
+
const e0 = section.start + 0.3;
|
|
5
|
+
tl.fromTo('#outro .l', { opacity: 0, y: 36 }, { opacity: 1, y: 0, duration: 0.8, ease: 'power3.out', stagger: { each: 0.05, from: 'center' }, ...later }, e0 + 0.1);
|
|
6
|
+
tl.fromTo('#outro .brand-line', { scaleX: 0 }, { scaleX: 1, duration: 0.7, ease: 'power3.inOut', ...later }, e0 + 0.45);
|
|
7
|
+
tl.fromTo('#outro .title', { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: 0.7, ease: 'power3.out', ...later }, e0 + 0.6);
|
|
8
|
+
tl.fromTo('#outro .subtitle', { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.6, ease: 'power3.out', ...later }, e0 + 0.8);
|
|
9
|
+
tl.fromTo('#outro .meta', { opacity: 0, y: 16 }, { opacity: 1, y: 0, duration: 0.5, ease: 'power3.out', ...later }, e0 + 0.95);
|
|
10
|
+
tl.fromTo('#outro .card', { scale: 1 }, { scale: 1.02, duration: Math.max(0.5, section.duration - 1.9), ease: 'sine.inOut', ...later }, e0 + 1.6);
|
|
11
|
+
|
|
12
|
+
gsap.set(['#outro .l', '#outro .title', '#outro .subtitle', '#outro .meta'], { opacity: 0 });
|
|
13
|
+
gsap.set('#outro .brand-line', { scaleX: 0 });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "An end card that stays readable: wordmark (or logo) and accent line over the video title, subtitle and steps chip. Held longer, drifts very slightly, and ends fully visible, so the paused last frame says what the video was.",
|
|
3
|
+
"timing": {
|
|
4
|
+
"duration": 4.0
|
|
5
|
+
},
|
|
6
|
+
"portrait": true,
|
|
7
|
+
"square": true
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#outro { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 22px; text-align: center; }
|
|
2
|
+
|
|
3
|
+
/* Portrait: a wordmark that fits the width */
|
|
4
|
+
#root.portrait #outro { padding: 0 60px; }
|
|
5
|
+
#root.portrait #outro .wordmark { font-size: 150px; }
|
|
6
|
+
#root.portrait #outro .brand-sub { font-size: 38px; }
|
|
7
|
+
|
|
8
|
+
/* Square: a wordmark that fits the width */
|
|
9
|
+
#root.square #outro { padding: 0 60px; }
|
|
10
|
+
#root.square #outro .wordmark { font-size: 150px; }
|
|
11
|
+
#root.square #outro .brand-sub { font-size: 38px; }
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<section id="outro" class="clip" data-start="{{START}}" data-duration="{{DURATION}}" data-track-index="{{TRACK}}">
|
|
2
|
+
<div class="wordmark" data-text="{{BRAND}}" data-logo="{{BRAND_LOGO}}"></div>
|
|
3
|
+
<div class="brand-line"></div>
|
|
4
|
+
<div class="brand-sub">{{BRAND_SUB}}</div>
|
|
5
|
+
</section>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Letters drift in from farther out and scale up from 0.88, the line wipes from the left,
|
|
2
|
+
// then everything zooms out gently at the very end and settles at 15% opacity: the last
|
|
3
|
+
// frame still shows the wordmark (house rule), never empty.
|
|
4
|
+
const b0 = section.start;
|
|
5
|
+
tl.fromTo('#outro .l', { opacity: 0, x: (i, el, all) => (i - all.length / 2) * (DEMO.layout.format === 'landscape' ? 44 : 16), scale: 0.88, filter: 'blur(12px)' },
|
|
6
|
+
{ opacity: 1, x: 0, scale: 1, filter: 'blur(0px)', duration: 1.1, ease: 'power3.out', stagger: { each: 0.06, from: 'center' }, ...later }, b0 + 0.1);
|
|
7
|
+
tl.fromTo('#outro .brand-line', { scaleX: 0, transformOrigin: 'left center' }, { scaleX: 1, duration: 0.9, ease: 'power3.inOut', ...later }, b0 + 0.7);
|
|
8
|
+
tl.fromTo('#outro .brand-sub', { opacity: 0, y: 22 }, { opacity: 1, y: 0, duration: 0.8, ease: 'power3.out', ...later }, b0 + 0.95);
|
|
9
|
+
tl.fromTo('#outro', { opacity: 1, scale: 1 }, { opacity: 0.15, scale: 0.96, duration: 0.8, ease: 'power2.in', ...later }, b0 + section.duration - 0.8);
|
|
10
|
+
|
|
11
|
+
gsap.set(['#outro .l', '#outro .brand-sub'], { opacity: 0 });
|
|
12
|
+
gsap.set('#outro .brand-line', { scaleX: 0 });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Big centred wordmark (or logo): letters drift in from farther out, the line wipes from the left, then it zooms out gently and settles at 15% opacity — the last frame still shows the brand.",
|
|
3
|
+
"timing": {
|
|
4
|
+
"duration": 2.6
|
|
5
|
+
},
|
|
6
|
+
"portrait": true,
|
|
7
|
+
"square": true
|
|
8
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#recap { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 64px; padding: 0 140px; text-align: center; }
|
|
2
|
+
#recap .head { display: flex; flex-direction: column; align-items: center; gap: 22px; }
|
|
3
|
+
#recap .title { font-size: 96px; font-weight: 800; letter-spacing: -0.04em; line-height: 1.02; }
|
|
4
|
+
#recap .accent { transform-origin: center; }
|
|
5
|
+
#recap .chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px 18px; max-width: 1600px; }
|
|
6
|
+
#recap .chip { display: inline-flex; align-items: center; gap: 14px; padding: 10px 26px 10px 10px; border-radius: 999px;
|
|
7
|
+
border: 1px solid var(--line); background: var(--surface); font-size: 28px; font-weight: 500; white-space: nowrap; }
|
|
8
|
+
#recap .chip .n { width: 40px; height: 40px; font-size: 19px; }
|
|
9
|
+
#recap .chip .who { font-size: 17px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: var(--brand-soft);
|
|
10
|
+
padding-right: 14px; border-right: 1px solid var(--line); }
|
|
11
|
+
|
|
12
|
+
/* Portrait: the chips stack one per row */
|
|
13
|
+
#root.portrait #recap { gap: 80px; padding: 0 80px; }
|
|
14
|
+
#root.portrait #recap .title { font-size: 116px; }
|
|
15
|
+
#root.portrait #recap .chips { flex-direction: column; align-items: center; gap: 22px; max-width: 920px; }
|
|
16
|
+
#root.portrait #recap .chip { font-size: 38px; white-space: normal; text-align: left; padding: 14px 34px 14px 14px; }
|
|
17
|
+
#root.portrait #recap .chip .n { width: 54px; height: 54px; font-size: 26px; }
|
|
18
|
+
#root.portrait #recap .chip .who { font-size: 22px; }
|
|
19
|
+
|
|
20
|
+
/* Square: the chips stack one per row */
|
|
21
|
+
#root.square #recap { gap: 56px; padding: 0 70px; }
|
|
22
|
+
#root.square #recap .title { font-size: 96px; }
|
|
23
|
+
#root.square #recap .chips { flex-direction: column; align-items: center; gap: 16px; max-width: 940px; }
|
|
24
|
+
#root.square #recap .chip { font-size: 32px; white-space: normal; text-align: left; padding: 12px 30px 12px 12px; }
|
|
25
|
+
#root.square #recap .chip .n { width: 46px; height: 46px; font-size: 22px; }
|
|
26
|
+
#root.square #recap .chip .who { font-size: 19px; }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<section id="recap" class="clip" data-start="{{START}}" data-duration="{{DURATION}}" data-track-index="{{TRACK}}">
|
|
2
|
+
<div class="head">
|
|
3
|
+
<div class="eyebrow">{{EYEBROW}}</div>
|
|
4
|
+
<h1 class="title">{{OUTRO_TITLE}}</h1>
|
|
5
|
+
<div class="accent"></div>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="chips"></div>
|
|
8
|
+
</section>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Head rises, chips pop in one after another, then the card fades.
|
|
2
|
+
const chips = document.querySelector('#recap .chips');
|
|
3
|
+
const steps = DEMO.callouts.slice(0, section.maxSteps);
|
|
4
|
+
const rows = steps.map((c, i) => {
|
|
5
|
+
const el = node('div', 'chip');
|
|
6
|
+
el.append(node('span', 'n', String(i + 1)));
|
|
7
|
+
// Two-actor videos: the role tag opens each role's first chip.
|
|
8
|
+
if (c.group && c.group !== steps[i - 1]?.group) el.append(node('span', 'who', c.group));
|
|
9
|
+
el.append(node('span', '', c.text));
|
|
10
|
+
return chips.appendChild(el);
|
|
11
|
+
});
|
|
12
|
+
const r0 = section.start;
|
|
13
|
+
tl.fromTo('#recap', { opacity: 0 }, { opacity: 1, duration: FADE, ease: 'power2.inOut', ...later }, r0);
|
|
14
|
+
tl.fromTo('#recap .eyebrow', { opacity: 0, y: 16 }, { opacity: 1, y: 0, duration: 0.5, ease: 'power3.out', ...later }, r0 + 0.05);
|
|
15
|
+
tl.fromTo('#recap .title', { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: 0.6, ease: 'power3.out', ...later }, r0 + 0.12);
|
|
16
|
+
tl.fromTo('#recap .accent', { scaleX: 0 }, { scaleX: 1, duration: 0.5, ease: 'power3.inOut', ...later }, r0 + 0.35);
|
|
17
|
+
rows.forEach((row, i) => {
|
|
18
|
+
tl.fromTo(row, { opacity: 0, y: 18, scale: 0.94 }, { opacity: 1, y: 0, scale: 1, duration: 0.4, ease: 'back.out(1.6)', ...later }, r0 + 0.4 + i * 0.08);
|
|
19
|
+
});
|
|
20
|
+
tl.fromTo('#recap', { opacity: 1 }, { opacity: 0, duration: FADE, ease: 'power2.in', ...later }, r0 + section.duration - FADE);
|
|
21
|
+
|
|
22
|
+
gsap.set(['#recap', '#recap .eyebrow', '#recap .title', ...rows], { opacity: 0 });
|
|
23
|
+
gsap.set('#recap .accent', { scaleX: 0 });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Centred and short: eyebrow, title and accent line, then the steps as a wrapping row of numbered chips (role tag on the first chip of each role). Cross-fades in after the recording.",
|
|
3
|
+
"timing": {
|
|
4
|
+
"base": 1.8,
|
|
5
|
+
"perStep": 0.35,
|
|
6
|
+
"max": 5.5,
|
|
7
|
+
"maxSteps": 10
|
|
8
|
+
},
|
|
9
|
+
"portrait": true,
|
|
10
|
+
"square": true
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#recap { display: grid; grid-template-columns: 640px 1fr; align-items: center; column-gap: 120px; padding: 0 160px; }
|
|
2
|
+
#recap .left { display: flex; flex-direction: column; gap: 26px; }
|
|
3
|
+
#recap .title { font-size: 120px; font-weight: 800; letter-spacing: -0.04em; line-height: 1.02; }
|
|
4
|
+
#recap .steps { display: flex; flex-direction: column; gap: 14px; }
|
|
5
|
+
#recap .step { display: inline-flex; align-items: center; gap: 18px; font-size: 32px; font-weight: 500; color: var(--ink); white-space: nowrap; }
|
|
6
|
+
#recap .step .n { font-size: 21px; }
|
|
7
|
+
#recap .steps.dense { gap: 11px; }
|
|
8
|
+
#recap .steps.dense .step { font-size: 28px; gap: 16px; }
|
|
9
|
+
#recap .steps.dense .step .n { width: 38px; height: 38px; font-size: 18px; }
|
|
10
|
+
/* Split by actor: a titled list per role */
|
|
11
|
+
#recap .group { display: flex; align-items: center; gap: 14px; font-size: 22px; font-weight: 700; letter-spacing: 0.2em;
|
|
12
|
+
text-transform: uppercase; color: var(--brand-soft); margin: 18px 0 2px; }
|
|
13
|
+
#recap .group:first-child { margin-top: 0; }
|
|
14
|
+
#recap .group::after { content: ""; width: 64px; height: 2px; border-radius: 1px; background: var(--line); }
|
|
15
|
+
|
|
16
|
+
/* Portrait: the title above the steps, one column */
|
|
17
|
+
#root.portrait #recap { grid-template-columns: 1fr; align-content: center; row-gap: 90px; padding: 0 90px; }
|
|
18
|
+
#root.portrait #recap .title { font-size: 128px; }
|
|
19
|
+
#root.portrait #recap .steps { gap: 26px; }
|
|
20
|
+
#root.portrait #recap .step { font-size: 42px; gap: 24px; white-space: normal; align-items: flex-start; line-height: 1.25; }
|
|
21
|
+
#root.portrait #recap .step .n { width: 58px; height: 58px; font-size: 28px; }
|
|
22
|
+
#root.portrait #recap .steps.dense { gap: 18px; }
|
|
23
|
+
#root.portrait #recap .steps.dense .step { font-size: 36px; }
|
|
24
|
+
#root.portrait #recap .steps.dense .step .n { width: 50px; height: 50px; font-size: 24px; }
|
|
25
|
+
#root.portrait #recap .group { font-size: 28px; margin: 26px 0 4px; }
|
|
26
|
+
|
|
27
|
+
/* Square: the title above the steps, one column */
|
|
28
|
+
#root.square #recap { grid-template-columns: 1fr; align-content: center; row-gap: 56px; padding: 0 80px; }
|
|
29
|
+
#root.square #recap .title { font-size: 100px; }
|
|
30
|
+
#root.square #recap .steps { gap: 18px; }
|
|
31
|
+
#root.square #recap .step { font-size: 36px; gap: 20px; white-space: normal; align-items: flex-start; line-height: 1.25; }
|
|
32
|
+
#root.square #recap .step .n { width: 50px; height: 50px; font-size: 24px; }
|
|
33
|
+
#root.square #recap .steps.dense { gap: 12px; }
|
|
34
|
+
#root.square #recap .steps.dense .step { font-size: 30px; }
|
|
35
|
+
#root.square #recap .steps.dense .step .n { width: 42px; height: 42px; font-size: 20px; }
|
|
36
|
+
#root.square #recap .group { font-size: 24px; margin: 18px 0 2px; }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<section id="recap" class="clip" data-start="{{START}}" data-duration="{{DURATION}}" data-track-index="{{TRACK}}">
|
|
2
|
+
<div class="left">
|
|
3
|
+
<div class="eyebrow">{{EYEBROW}}</div>
|
|
4
|
+
<h1 class="title">{{OUTRO_TITLE}}</h1>
|
|
5
|
+
<div class="accent"></div>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="steps"></div>
|
|
8
|
+
</section>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Left block rises, steps cascade in on the right, then the card fades.
|
|
2
|
+
const list = document.querySelector('#recap .steps');
|
|
3
|
+
const steps = DEMO.callouts.slice(0, section.maxSteps); // the card holds this many
|
|
4
|
+
if (steps.length > 8) list.classList.add('dense');
|
|
5
|
+
// Two-actor videos: callouts carry a `group` (the role from the hand-off card); each
|
|
6
|
+
// role gets its own titled list, numbering continues across them.
|
|
7
|
+
const rows = [];
|
|
8
|
+
steps.forEach((c, i) => {
|
|
9
|
+
if (c.group && c.group !== steps[i - 1]?.group) {
|
|
10
|
+
rows.push(list.appendChild(node('div', 'group', c.group)));
|
|
11
|
+
}
|
|
12
|
+
const el = node('div', 'step');
|
|
13
|
+
el.append(node('span', 'n', String(i + 1)), node('span', '', c.text));
|
|
14
|
+
rows.push(list.appendChild(el));
|
|
15
|
+
});
|
|
16
|
+
const r0 = section.start;
|
|
17
|
+
tl.fromTo('#recap', { opacity: 0 }, { opacity: 1, duration: FADE, ease: 'power2.inOut', ...later }, r0);
|
|
18
|
+
tl.fromTo('#recap .eyebrow', { opacity: 0, y: 18 }, { opacity: 1, y: 0, duration: 0.5, ease: 'power3.out', ...later }, r0 + 0.1);
|
|
19
|
+
tl.fromTo('#recap .title', { opacity: 0, y: 40 }, { opacity: 1, y: 0, duration: 0.7, ease: 'power3.out', ...later }, r0 + 0.2);
|
|
20
|
+
tl.fromTo('#recap .accent', { scaleX: 0 }, { scaleX: 1, duration: 0.6, ease: 'power3.inOut', ...later }, r0 + 0.45);
|
|
21
|
+
rows.forEach((row, i) => {
|
|
22
|
+
tl.fromTo(row, { opacity: 0, x: 36 }, { opacity: 1, x: 0, duration: 0.45, ease: 'power3.out', ...later }, r0 + 0.5 + i * 0.1);
|
|
23
|
+
});
|
|
24
|
+
tl.fromTo('#recap', { opacity: 1 }, { opacity: 0, duration: FADE, ease: 'power2.in', ...later }, r0 + section.duration - FADE);
|
|
25
|
+
|
|
26
|
+
gsap.set(['#recap', '#recap .eyebrow', '#recap .title', ...rows], { opacity: 0 });
|
|
27
|
+
gsap.set('#recap .accent', { scaleX: 0 });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Eyebrow, big title and accent line on the left; ONE column of numbered steps on the right (split by role in two-actor videos). Cross-fades in after the recording.",
|
|
3
|
+
"timing": {
|
|
4
|
+
"base": 2.4,
|
|
5
|
+
"perStep": 0.45,
|
|
6
|
+
"max": 7.5,
|
|
7
|
+
"maxSteps": 10
|
|
8
|
+
},
|
|
9
|
+
"portrait": true,
|
|
10
|
+
"square": true
|
|
11
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>reelson studio</title>
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22%3E%3Crect width=%2216%22 height=%2216%22 rx=%223%22 fill=%22%235aa9ff%22/%3E%3Cpath d=%22M6 4.5v7l5.5-3.5z%22 fill=%22%230e1116%22/%3E%3C/svg%3E">
|
|
8
|
+
<script src="/hf/player.js"></script>
|
|
9
|
+
<link rel="stylesheet" href="/studio/studio.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<header>
|
|
13
|
+
<h1 id="title">reelson studio</h1>
|
|
14
|
+
<span class="slug" id="slug"></span>
|
|
15
|
+
<div class="chips" id="design"></div>
|
|
16
|
+
<span class="status" id="status">starting…</span>
|
|
17
|
+
</header>
|
|
18
|
+
<main>
|
|
19
|
+
<div class="stage">
|
|
20
|
+
<pre class="banner" id="banner"></pre>
|
|
21
|
+
<div class="viewer" id="viewer"><hyperframes-player id="player"></hyperframes-player></div>
|
|
22
|
+
<div class="transport">
|
|
23
|
+
<button id="start" title="Start (Home)">⏮</button>
|
|
24
|
+
<button id="back" title="Back one frame (←)">◀︎</button>
|
|
25
|
+
<button id="play" title="Play / pause (Space)">▶︎</button>
|
|
26
|
+
<button id="fwd" title="Forward one frame (→)">▶︎|</button>
|
|
27
|
+
<button id="end" title="Last frame (End)">⏭</button>
|
|
28
|
+
<span class="time" id="time">0.00 / 0.00<span class="frame"></span></span>
|
|
29
|
+
<span class="spacer"></span>
|
|
30
|
+
<button id="mute" title="Mute (M)">🔊</button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<aside>
|
|
34
|
+
<section class="inspector">
|
|
35
|
+
<h2>Selected</h2>
|
|
36
|
+
<div id="inspector"><p class="empty">Click an item on the timeline.</p></div>
|
|
37
|
+
</section>
|
|
38
|
+
<section>
|
|
39
|
+
<h2>Warnings</h2>
|
|
40
|
+
<ul class="warnings" id="warnings"></ul>
|
|
41
|
+
</section>
|
|
42
|
+
<section>
|
|
43
|
+
<h2>Keys</h2>
|
|
44
|
+
<div class="keys">
|
|
45
|
+
<kbd>Space</kbd><span>play / pause</span>
|
|
46
|
+
<kbd>← →</kbd><span>one frame (Shift: one second)</span>
|
|
47
|
+
<kbd>[ ]</kbd><span>previous / next item edge</span>
|
|
48
|
+
<kbd>Home End</kbd><span>first / last frame</span>
|
|
49
|
+
<kbd>+ − 0</kbd><span>zoom timeline / fit</span>
|
|
50
|
+
<kbd>M</kbd><span>mute</span>
|
|
51
|
+
<kbd>C</kbd><span>callout at the playhead</span>
|
|
52
|
+
<kbd>Delete</kbd><span>delete the selected callout / zoom</span>
|
|
53
|
+
<kbd>⌘Z ⇧⌘Z</kbd><span>undo / redo</span>
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
</aside>
|
|
57
|
+
</main>
|
|
58
|
+
<section class="timeline">
|
|
59
|
+
<div class="tl-bar">
|
|
60
|
+
<button id="add-callout" title="Add a callout at the playhead (C)">+ Callout</button>
|
|
61
|
+
<button id="undo" title="Undo (⌘Z)" disabled>↶ Undo</button>
|
|
62
|
+
<button id="redo" title="Redo (⇧⌘Z)" disabled>↷ Redo</button>
|
|
63
|
+
<span class="toast" id="toast"></span>
|
|
64
|
+
<span class="hint">Drag callouts to move them, their right edge to resize · drag the recording’s edges to trim · edits save to video.json</span>
|
|
65
|
+
<span class="spacer"></span>
|
|
66
|
+
<button id="zoom-out" title="Zoom out (−)">−</button>
|
|
67
|
+
<button id="zoom-fit" title="Fit (0)">Fit</button>
|
|
68
|
+
<button id="zoom-in" title="Zoom in (+)">+</button>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="tl-body" id="tl-body">
|
|
71
|
+
<div class="tl-inner" id="tl-inner">
|
|
72
|
+
<div class="labels" id="labels"></div>
|
|
73
|
+
<div class="tracks" id="tracks"></div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</section>
|
|
77
|
+
<div class="tooltip" id="tooltip"></div>
|
|
78
|
+
|
|
79
|
+
<script src="/studio/studio.js"></script>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #0e1116;
|
|
3
|
+
--panel: #151a21;
|
|
4
|
+
--panel-2: #1b212a;
|
|
5
|
+
--line: #262e39;
|
|
6
|
+
--text: #d7dde5;
|
|
7
|
+
--muted: #7d8896;
|
|
8
|
+
--accent: #5aa9ff;
|
|
9
|
+
--intro: #7c6cf0;
|
|
10
|
+
--recording: #3f7d5c;
|
|
11
|
+
--recap: #c7862f;
|
|
12
|
+
--outro: #b24f7a;
|
|
13
|
+
--callout: #e2b340;
|
|
14
|
+
--zoom: #38b2ac;
|
|
15
|
+
--handoff: #8f9bb3;
|
|
16
|
+
--click: #9ecbff;
|
|
17
|
+
--marker: #a0a8b5;
|
|
18
|
+
--audio: #4f6a8f;
|
|
19
|
+
--bad: #ef5b5b;
|
|
20
|
+
--playhead: #ff5a4f;
|
|
21
|
+
--label-w: 104px;
|
|
22
|
+
font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
23
|
+
color-scheme: dark;
|
|
24
|
+
}
|
|
25
|
+
* { box-sizing: border-box; }
|
|
26
|
+
body { margin: 0; background: var(--bg); color: var(--text); height: 100vh; display: grid; grid-template-rows: auto 1fr auto; overflow: hidden; }
|
|
27
|
+
header { display: flex; align-items: center; gap: 12px; padding: 8px 14px; border-bottom: 1px solid var(--line); background: var(--panel); min-width: 0; }
|
|
28
|
+
header h1 { font-size: 14px; font-weight: 600; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
29
|
+
header .slug { color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
30
|
+
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
31
|
+
.chip { font-size: 11px; padding: 2px 7px; border-radius: 10px; background: var(--panel-2); border: 1px solid var(--line); color: var(--muted); white-space: nowrap; }
|
|
32
|
+
.chip b { color: var(--text); font-weight: 500; }
|
|
33
|
+
.status { margin-left: auto; font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; white-space: nowrap; }
|
|
34
|
+
.status::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: #3fb96b; }
|
|
35
|
+
.status.error::before { background: var(--bad); }
|
|
36
|
+
.status.offline::before { background: var(--muted); }
|
|
37
|
+
|
|
38
|
+
main { display: grid; grid-template-columns: minmax(0, 1fr) 300px; min-height: 0; }
|
|
39
|
+
.stage { display: flex; flex-direction: column; min-height: 0; min-width: 0; padding: 12px; gap: 8px; }
|
|
40
|
+
.viewer { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
|
|
41
|
+
hyperframes-player { width: 100%; max-height: 100%; aspect-ratio: 16 / 9; border-radius: 6px; overflow: hidden; background: #000; }
|
|
42
|
+
.viewer.tall hyperframes-player { width: auto; height: 100%; }
|
|
43
|
+
.transport { display: flex; align-items: center; gap: 8px; }
|
|
44
|
+
button { font: inherit; color: var(--text); background: var(--panel-2); border: 1px solid var(--line); border-radius: 5px; padding: 4px 9px; cursor: pointer; }
|
|
45
|
+
button:hover { border-color: #3a4554; }
|
|
46
|
+
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
47
|
+
.time { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
48
|
+
.time .frame { color: var(--muted); margin-left: 8px; }
|
|
49
|
+
.spacer { flex: 1; }
|
|
50
|
+
.hint { color: var(--muted); font-size: 11px; }
|
|
51
|
+
|
|
52
|
+
aside { border-left: 1px solid var(--line); background: var(--panel); overflow: auto; padding: 12px 14px; display: flex; flex-direction: column; gap: 16px; }
|
|
53
|
+
aside h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); margin: 0 0 6px; font-weight: 600; }
|
|
54
|
+
.inspector dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; margin: 0; }
|
|
55
|
+
.inspector dt { color: var(--muted); }
|
|
56
|
+
.inspector dd { margin: 0; overflow-wrap: anywhere; }
|
|
57
|
+
.inspector .kind { display: inline-block; font-size: 11px; padding: 1px 6px; border-radius: 3px; margin-bottom: 6px; color: #0e1116; font-weight: 600; }
|
|
58
|
+
.inspector .problem { color: var(--bad); margin-top: 6px; font-size: 12px; }
|
|
59
|
+
.empty { color: var(--muted); font-size: 12px; }
|
|
60
|
+
.warnings { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
|
|
61
|
+
.warnings li { font-size: 12px; padding: 6px 8px; border-radius: 4px; background: #2a1f14; border-left: 3px solid var(--recap); }
|
|
62
|
+
.warnings li.bad { background: #2b1618; border-left-color: var(--bad); }
|
|
63
|
+
.warnings li.ok { background: #14261b; border-left-color: #3fb96b; }
|
|
64
|
+
.keys { font-size: 11px; color: var(--muted); display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; }
|
|
65
|
+
kbd { font-family: ui-monospace, Menlo, monospace; font-size: 10px; padding: 0 4px; border: 1px solid var(--line); border-radius: 3px; background: var(--panel-2); color: var(--text); }
|
|
66
|
+
|
|
67
|
+
.timeline { border-top: 1px solid var(--line); background: var(--panel); display: flex; flex-direction: column; height: 292px; }
|
|
68
|
+
.tl-bar { display: flex; align-items: center; gap: 6px; padding: 5px 10px; border-bottom: 1px solid var(--line); }
|
|
69
|
+
.tl-body { position: relative; flex: 1; overflow-x: auto; overflow-y: hidden; }
|
|
70
|
+
.tl-inner { position: relative; min-width: 100%; height: 100%; }
|
|
71
|
+
.labels { position: sticky; left: 0; z-index: 3; width: var(--label-w); float: left; height: 100%; background: var(--panel); border-right: 1px solid var(--line); }
|
|
72
|
+
.labels div { height: 28px; display: flex; align-items: center; padding: 0 10px; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; border-bottom: 1px solid var(--line); }
|
|
73
|
+
.labels .ruler-label { height: 22px; }
|
|
74
|
+
.tracks { position: absolute; left: var(--label-w); top: 0; bottom: 0; cursor: text; }
|
|
75
|
+
.ruler { position: relative; height: 22px; border-bottom: 1px solid var(--line); }
|
|
76
|
+
.tick { position: absolute; top: 0; bottom: 0; border-left: 1px solid var(--line); }
|
|
77
|
+
.tick span { position: absolute; left: 3px; top: 3px; font-size: 10px; color: var(--muted); font-family: ui-monospace, Menlo, monospace; white-space: nowrap; }
|
|
78
|
+
.tick.minor { top: 15px; }
|
|
79
|
+
.lane { position: relative; height: 28px; border-bottom: 1px solid var(--line); }
|
|
80
|
+
.item { position: absolute; top: 4px; height: 20px; border-radius: 4px; font-size: 11px; line-height: 20px; padding: 0 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #0e1116; cursor: pointer; min-width: 3px; border: 1px solid transparent; }
|
|
81
|
+
.item:hover { filter: brightness(1.15); }
|
|
82
|
+
.item.selected { border-color: #fff; box-shadow: 0 0 0 1px #fff; }
|
|
83
|
+
.item.bad { outline: 2px solid var(--bad); outline-offset: -1px; }
|
|
84
|
+
.item.intro { background: var(--intro); color: #fff; }
|
|
85
|
+
.item.recording { background: var(--recording); color: #fff; z-index: 1; } /* its trim handles sit over the cross-fades */
|
|
86
|
+
.item.recap { background: var(--recap); }
|
|
87
|
+
.item.outro { background: var(--outro); color: #fff; }
|
|
88
|
+
.item.callout { background: var(--callout); }
|
|
89
|
+
.item.handoff { background: var(--handoff); }
|
|
90
|
+
.item.audio { background: var(--audio); color: #fff; opacity: .85; }
|
|
91
|
+
.item.zoom { background: transparent; padding: 0; overflow: visible; }
|
|
92
|
+
.item.zoom svg { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
93
|
+
.item.zoom span { position: relative; padding: 0 6px; color: var(--text); font-weight: 600; }
|
|
94
|
+
.dot { position: absolute; top: 9px; width: 10px; height: 10px; margin-left: -5px; border-radius: 50%; background: var(--click); cursor: pointer; }
|
|
95
|
+
.dot.type { border-radius: 2px; }
|
|
96
|
+
.dot.selected { box-shadow: 0 0 0 2px #fff; }
|
|
97
|
+
.glide { position: absolute; top: 13px; height: 2px; background: color-mix(in srgb, var(--click) 45%, transparent); pointer-events: none; }
|
|
98
|
+
.typing { position: absolute; top: 11px; height: 6px; border-radius: 3px; background: color-mix(in srgb, var(--click) 55%, transparent); pointer-events: none; }
|
|
99
|
+
.pin { position: absolute; top: 3px; bottom: 3px; width: 2px; margin-left: -1px; background: var(--marker); cursor: pointer; }
|
|
100
|
+
.pin.unused { background: repeating-linear-gradient(var(--marker) 0 3px, transparent 3px 6px); }
|
|
101
|
+
.pin.selected { background: #fff; width: 3px; }
|
|
102
|
+
.belt { position: absolute; top: 4px; height: 20px; background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--handoff) 40%, transparent) 0 4px, transparent 4px 8px); border-radius: 4px 0 0 4px; pointer-events: none; }
|
|
103
|
+
.playhead { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--playhead); pointer-events: none; z-index: 2; }
|
|
104
|
+
.playhead::before { content: ""; position: absolute; top: 0; left: -5px; border: 5px solid transparent; border-top: 7px solid var(--playhead); }
|
|
105
|
+
.banner { display: none; margin: 0; padding: 8px 14px; background: #3a1619; color: #ffd5d5; font-size: 12px; white-space: pre-wrap; border-bottom: 1px solid #5a2328; font-family: ui-monospace, Menlo, monospace; }
|
|
106
|
+
.banner.show { display: block; }
|
|
107
|
+
.tooltip { position: fixed; z-index: 10; pointer-events: none; background: #000d; border: 1px solid var(--line); padding: 4px 7px; border-radius: 4px; font-size: 11px; max-width: 320px; display: none; }
|
|
108
|
+
|
|
109
|
+
@media (max-width: 860px) {
|
|
110
|
+
main { grid-template-columns: 1fr; }
|
|
111
|
+
aside { display: none; }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* editing */
|
|
115
|
+
button:disabled { opacity: .45; cursor: default; }
|
|
116
|
+
button.danger { color: #ffb4b4; border-color: #5a2328; }
|
|
117
|
+
button.danger:hover { background: #3a1619; }
|
|
118
|
+
input, select { font: inherit; color: var(--text); background: var(--bg); border: 1px solid var(--line); border-radius: 4px; padding: 3px 6px; width: 100%; min-width: 0; }
|
|
119
|
+
input:focus, select:focus { outline: none; border-color: var(--accent); }
|
|
120
|
+
input::placeholder { color: var(--muted); }
|
|
121
|
+
.inspector dl { align-items: center; }
|
|
122
|
+
.inspector .actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
|
123
|
+
.inspector .note { color: var(--muted); font-size: 12px; margin: 8px 0 0; }
|
|
124
|
+
.chip select { width: auto; background: transparent; border: none; padding: 0 2px; color: var(--text); font-weight: 500; font-size: 11px; cursor: pointer; }
|
|
125
|
+
.chip select:focus { outline: 1px solid var(--accent); }
|
|
126
|
+
.status.busy::before { background: var(--callout); }
|
|
127
|
+
.item.editable::after { content: ""; position: absolute; top: 3px; bottom: 3px; right: 1px; width: 2px; border-radius: 1px; background: #0006; }
|
|
128
|
+
.item.recording.editable::before { content: ""; position: absolute; top: 3px; bottom: 3px; left: 1px; width: 2px; border-radius: 1px; background: #fff8; }
|
|
129
|
+
.item.recording.editable::after { background: #fff8; }
|
|
130
|
+
.item.zoom.editable::after { display: none; }
|
|
131
|
+
.item.dragging { opacity: .8; box-shadow: 0 0 0 1px var(--accent); z-index: 1; }
|
|
132
|
+
.item.half { top: 4px; height: 9px; line-height: 9px; font-size: 9px; }
|
|
133
|
+
.lane-note { position: absolute; left: 8px; top: 7px; }
|
|
134
|
+
.toast { font-size: 12px; color: var(--text); background: var(--panel-2); border: 1px solid var(--line); border-radius: 4px; padding: 2px 8px; display: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 50%; }
|
|
135
|
+
.toast.show { display: inline-block; }
|
|
136
|
+
.toast.bad { color: #ffd5d5; background: #3a1619; border-color: #5a2328; white-space: normal; }
|
|
137
|
+
.toast.show ~ .hint { display: none; }
|
|
138
|
+
|
|
139
|
+
/* cursor lane */
|
|
140
|
+
.item.cursor { background: color-mix(in srgb, var(--click) 30%, transparent); color: var(--text); border: 1px dashed color-mix(in srgb, var(--click) 60%, transparent); }
|
|
141
|
+
.item.cursor.hidden, .item.cursor.filmed { background: transparent; color: var(--muted); }
|
|
142
|
+
.press { position: absolute; top: 8px; width: 2px; height: 12px; margin-left: -1px; background: var(--click); pointer-events: none; z-index: 1; }
|