Compare commits

..

15 Commits

Author SHA1 Message Date
joe 28a2c31239 str 2025-04-25 13:04:02 +01:00
joe e5eacbba0d This is a working performance model 2025-04-25 09:03:54 +01:00
joe 0e31c0ea1d layout done 2025-04-24 13:19:06 +01:00
Joe e6ed178aac sorted mime type problem 2025-04-24 10:46:45 +01:00
Joe 10b8111517 should be good 2025-04-24 09:25:37 +01:00
Joe 9de15ba9f8 should be good 2025-04-24 09:24:48 +01:00
Joe f50bd236ca rendering correct dimentions 2025-04-23 21:02:34 +01:00
Joe 8993615953 nearly sorted with the rendering 2025-04-23 12:32:44 +01:00
Joe 75b66e245e this is garbage from chatty mc fuckin chat face 2025-04-18 14:35:30 +01:00
Joe f776fe377a a little progress 2025-04-18 03:57:30 +01:00
Joe be327165b8 ugh? 2025-04-16 03:25:55 +01:00
Joe c6c659535e sorted the title updates 2025-04-15 04:35:56 +01:00
Joe 17f1cbfa9b refactored - still some renedering issue 2025-04-14 05:06:59 +01:00
Joe ac58c0e8c3 startng to make classs - Socket class not okay 2025-04-13 14:26:24 +01:00
Joe 1393acdc42 distributing functions to files to go OO 2025-04-13 13:36:30 +01:00
23 changed files with 527 additions and 88874 deletions

View File

@ -1 +0,0 @@
# conductor

View File

@ -8,7 +8,13 @@
"bdrumnew-hit-v7-rr1-sum-(1).mp3" "bdrumnew-hit-v7-rr1-sum-(1).mp3"
], ],
"midi-channel-name": "conductorOrchestralBass", "midi-channel-name": "conductorOrchestralBass",
"image": { "filename": "bass.svg", "yPos": "13%" } "image": {
"filename": "bass.svg",
"yPos": "2.5%",
"width": "100",
"height": "100"
}
}, },
"Snare": { "Snare": {
"directory": "/assets/instruments/snare", "directory": "/assets/instruments/snare",
@ -17,19 +23,34 @@
"ropesnare-low-tsn-main-vl4-rr1.mp3" "ropesnare-low-tsn-main-vl4-rr1.mp3"
], ],
"midi-channel-name": "conductorSnare", "midi-channel-name": "conductorSnare",
"image": { "filename": "snare.svg", "yPos": "26%" } "image": {
"filename": "snare.svg",
"yPos": "7%",
"width": "100",
"height": "100"
}
}, },
"Surdo": { "Surdo": {
"directory": "/assets/instruments/surdo", "directory": "/assets/instruments/surdo",
"filenames": ["surdo-5.mp3", "surdo-6.mp3", "surdo-7.mp3"], "filenames": ["surdo-5.mp3", "surdo-6.mp3", "surdo-7.mp3"],
"midi-channel-name":"conductorSnare", "midi-channel-name": "conductorSurdo",
"image": { "filename": "surdo.svg", "yPos": "39%" } "image": {
"filename": "surdo.svg",
"yPos": "10.5%",
"width": "100",
"height": "100"
}
}, },
"Surdo Napa": { "Surdo Napa": {
"directory": "/assets/instruments/surdo-napa", "directory": "/assets/instruments/surdo-napa",
"filenames": ["surdo-1.mp3", "surdo-3.mp3", "surdo-4.mp3"], "filenames": ["surdo-1.mp3", "surdo-3.mp3", "surdo-4.mp3"],
"midi-channel-name": "conductorSurdoNapa", "midi-channel-name": "conductorSurdoNapa",
"image": { "filename": "surdo-napa.svg", "yPos": "42%" } "image": {
"filename": "surdo-napa.svg",
"yPos": "12%",
"width": "100",
"height": "100"
}
}, },
"Timpani Large": { "Timpani Large": {
"directory": "/assets/instruments/timpani-large", "directory": "/assets/instruments/timpani-large",
@ -39,17 +60,28 @@
"timpani7b-hit-v5-rr2-main.mp3", "timpani7b-hit-v5-rr2-main.mp3",
"timpani7d-hit-v2-rr1-main.mp3" "timpani7d-hit-v2-rr1-main.mp3"
], ],
"midi-channel-name":"conductorTimpaniLarge", "midi-channel-name": "conductorTimpaniLarge",
"image": { "filename": "timpani-large.svg", "yPos": "55%" } "image": {
"filename": "timpani-large.svg",
"yPos": "14%",
"width": "100",
"height": "100"
}
}, },
"Timpani Small": { "directory": "/assets/instruments/timpani-small", "Timpani Small": {
"directory": "/assets/instruments/timpani-small",
"filenames": [ "filenames": [
"timpani1a-hit-v5-rr1-main.mp3", "timpani1a-hit-v5-rr1-main.mp3",
"timpani2-hit-v3-rr1-sum.mp3", "timpani2-hit-v3-rr1-sum.mp3",
"timpani4-hit-v2-rr1-sum.mp3" "timpani4-hit-v2-rr1-sum.mp3"
], ],
"midi-channel-name": "conductorTimpaniSmall", "midi-channel-name": "conductorTimpaniSmall",
"image": { "filename": "timpani-small.svg", "yPos": "68%" } "image": {
"filename": "timpani-small.svg",
"yPos": "18%",
"width": "100",
"height": "100"
}
}, },
"Toms": { "Toms": {
"directory": "/assets/instruments/toms", "directory": "/assets/instruments/toms",
@ -59,6 +91,11 @@
"toml-rollm-v2-rr1-mid.mp3" "toml-rollm-v2-rr1-mid.mp3"
], ],
"midi-channel-name": "conductorToms", "midi-channel-name": "conductorToms",
"image": { "filename": "toms.svg", "yPos": "81%" } "image": {
"filename": "toms.svg",
"yPos": "42%",
"width": "100",
"height": "100"
}
} }
} }

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.33 18.79"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><path class="cls-1" d="M19.09,7.44c0,4.11-3.32,7.44-7.42,7.44s-7.42-3.33-7.42-7.44S7.56,0,11.66,0s7.42,3.33,7.42,7.44"/><path class="cls-2" d="M22.83,9.31c0,4.96-5,8.98-11.16,8.98S.5,14.27.5,9.31"/></g></svg> <?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.33 18.79" width="200" height="200"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><path class="cls-1" d="M19.09,7.44c0,4.11-3.32,7.44-7.42,7.44s-7.42-3.33-7.42-7.44S7.56,0,11.66,0s7.42,3.33,7.42,7.44"/><path class="cls-2" d="M22.83,9.31c0,4.96-5,8.98-11.16,8.98S.5,14.27.5,9.31"/></g></svg>

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 482 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.27 23.95"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="14.97 23.55 .78 12.61 10.65 3.86"/><path class="cls-1" d="M14.98,0c-1.35,2.55-2.72,6.05-3.05,8.78l-1.84-4.44-4.31-2.26c2.8-.05,6.49-1.02,9.2-2.08"/><line class="cls-2" x1=".76" y1="23.55" x2="7.02" y2="17.42"/></g></svg> <?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.27 23.95" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="14.97 23.55 .78 12.61 10.65 3.86"/><path class="cls-1" d="M14.98,0c-1.35,2.55-2.72,6.05-3.05,8.78l-1.84-4.44-4.31-2.26c2.8-.05,6.49-1.02,9.2-2.08"/><line class="cls-2" x1=".76" y1="23.55" x2="7.02" y2="17.42"/></g></svg>

Before

Width:  |  Height:  |  Size: 501 B

After

Width:  |  Height:  |  Size: 526 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.52"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-1" points="6.71 22.55 .71 10.73 12.72 10.73 6.71 22.55"/><path class="cls-1" d="M1.03,4.12h11.36M2.7.32l8.03,7.6M10.73.32L2.7,7.92"/></g></svg> <?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.52" width="100" height="100"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-1" points="6.71 22.55 .71 10.73 12.72 10.73 6.71 22.55"/><path class="cls-1" d="M1.03,4.12h11.36M2.7.32l8.03,7.6M10.73.32L2.7,7.92"/></g></svg>

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 422 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.67"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-2" points="6.71 22.71 .71 10.89 12.72 10.89 6.71 22.71"/><path class="cls-1" d="M11.33,4.54c0,2.51-2.07,4.54-4.62,4.54S2.09,7.05,2.09,4.54,4.16,0,6.71,0s4.62,2.03,4.62,4.54"/></g></svg> <?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.67" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-2" points="6.71 22.71 .71 10.89 12.72 10.89 6.71 22.71"/><path class="cls-1" d="M11.33,4.54c0,2.51-2.07,4.54-4.62,4.54S2.09,7.05,2.09,4.54,4.16,0,6.71,0s4.62,2.03,4.62,4.54"/></g></svg>

Before

Width:  |  Height:  |  Size: 473 B

After

Width:  |  Height:  |  Size: 498 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21.29 23.81"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="0 .5 19.76 .5 5.02 11.32 15.28 19.98"/><path class="cls-1" d="M19.84,23.81c-2.83-1.05-6.66-2.01-9.57-2.06l4.48-2.23,1.92-4.4c.35,2.7,1.76,6.17,3.18,8.69"/><line class="cls-2" x1="1.74" y1="23.81" x2="1.74" y2=".47"/></g></svg> <?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21.29 23.81" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="0 .5 19.76 .5 5.02 11.32 15.28 19.98"/><path class="cls-1" d="M19.84,23.81c-2.83-1.05-6.66-2.01-9.57-2.06l4.48-2.23,1.92-4.4c.35,2.7,1.76,6.17,3.18,8.69"/><line class="cls-2" x1="1.74" y1="23.81" x2="1.74" y2=".47"/></g></svg>

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 533 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.32 23.67"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="15.02 .4 .79 11.21 10.3 19.39"/><path class="cls-1" d="M15.22,23.67c-2.73-1.05-6.43-2.01-9.24-2.06l4.33-2.23,1.85-4.39c.34,2.7,1.7,6.16,3.07,8.68"/></g></svg> <?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.32 23.67" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style></defs><g id="Layer_1-2"><polyline class="cls-2" points="15.02 .4 .79 11.21 10.3 19.39"/><path class="cls-1" d="M15.22,23.67c-2.73-1.05-6.43-2.01-9.24-2.06l4.33-2.23,1.85-4.39c.34,2.7,1.7,6.16,3.07,8.68"/></g></svg>

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 465 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.96"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{stroke-miterlimit:10;}.cls-2,.cls-3{fill:none;stroke:#fff;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-2" points="6.71 22.99 .71 11.18 12.72 11.18 6.71 22.99"/><path class="cls-1" d="M9.11,7.86c0,1.3-1.07,2.36-2.4,2.36s-2.4-1.05-2.4-2.36,1.07-2.36,2.4-2.36,2.4,1.06,2.4,2.36"/><path class="cls-1" d="M9.11,2.36c0,1.3-1.07,2.36-2.4,2.36s-2.4-1.06-2.4-2.36S5.39,0,6.71,0s2.4,1.06,2.4,2.36"/><line class="cls-3" x1="3.46" y1="16.34" x2="9.97" y2="16.34"/></g></svg> <?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.43 23.96" width="100" height="100"><defs><style>.cls-1{fill:#fff;stroke-width:0px;}.cls-2{stroke-miterlimit:10;}.cls-2,.cls-3{fill:none;stroke:#fff;stroke-width:.87px;}</style></defs><g id="Layer_1-2"><polygon class="cls-2" points="6.71 22.99 .71 11.18 12.72 11.18 6.71 22.99"/><path class="cls-1" d="M9.11,7.86c0,1.3-1.07,2.36-2.4,2.36s-2.4-1.05-2.4-2.36,1.07-2.36,2.4-2.36,2.4,1.06,2.4,2.36"/><path class="cls-1" d="M9.11,2.36c0,1.3-1.07,2.36-2.4,2.36s-2.4-1.06-2.4-2.36S5.39,0,6.71,0s2.4,1.06,2.4,2.36"/><line class="cls-3" x1="3.46" y1="16.34" x2="9.97" y2="16.34"/></g></svg>

Before

Width:  |  Height:  |  Size: 662 B

After

Width:  |  Height:  |  Size: 688 B

View File

@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 330.51 33.72"><defs><style>.cls-1{stroke:#828383;stroke-width:.5px;}.cls-1,.cls-2{fill:none;}.cls-3{clip-path:url(#clippath-2);}.cls-2{stroke-width:0px;}.cls-4{clip-path:url(#clippath-1);}.cls-5{opacity:.75;}.cls-6{clip-path:url(#clippath);}</style><clipPath id="clippath"><rect class="cls-2" width="330.51" height="33.72"/></clipPath><clipPath id="clippath-1"><rect class="cls-2" y="0" width="330.51" height="33.72"/></clipPath><clipPath id="clippath-2"><rect class="cls-2" x="-25" y="-22" width="380.51" height="56.72"/></clipPath></defs><g id="Layer_1-2"><g class="cls-6"><g class="cls-4"><g class="cls-5"><g class="cls-3"><rect class="cls-1" x=".25" y=".25" width="33" height="33.22"/><rect class="cls-1" x="132.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="66.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="198.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="264.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="33.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="165.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="99.25" y=".25" width="33" height="33.22"/><rect class="cls-1" x="231.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="297.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x=".25" y=".25" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="13.58" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="6.91" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="20.24" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="26.91" width="330.01" height="6.56"/></g></g></g></g></g></svg> <?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 330.51 33.72" ><defs><style>.cls-1{stroke:#828383;stroke-width:.5px;}.cls-1,.cls-2{fill:none;}.cls-3{clip-path:url(#clippath-2);}.cls-2{stroke-width:0px;}.cls-4{clip-path:url(#clippath-1);}.cls-5{opacity:.75;}.cls-6{clip-path:url(#clippath);}</style><clipPath id="clippath"><rect class="cls-2" width="330.51" height="33.72"/></clipPath><clipPath id="clippath-1"><rect class="cls-2" y="0" width="330.51" height="33.72"/></clipPath><clipPath id="clippath-2"><rect class="cls-2" x="-25" y="-22" width="380.51" height="56.72"/></clipPath></defs><g id="Layer_1-2"><g class="cls-6"><g class="cls-4"><g class="cls-5"><g class="cls-3"><rect class="cls-1" x=".25" y=".25" width="33" height="33.22"/><rect class="cls-1" x="132.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="66.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="198.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="264.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="33.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x="165.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="99.25" y=".25" width="33" height="33.22"/><rect class="cls-1" x="231.27" y=".25" width="33" height="33.22"/><rect class="cls-1" x="297.26" y=".25" width="33" height="33.22"/><rect class="cls-1" x=".25" y=".25" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="13.58" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="6.91" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="20.24" width="330.01" height="6.56"/><rect class="cls-1" x=".25" y="26.91" width="330.01" height="6.56"/></g></g></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

View File

@ -15,11 +15,6 @@
<div class="loading"> <div class="loading">
</div> </div>
</div> </div>
<div id="buttons">
<img src="/assets/svg/mute.svg" class="audio" id="mute" />
<img src="/assets/svg/menu.svg" class="menu" id="menu-button" />
</div>
<div id="main-content"> <div id="main-content">
@ -35,57 +30,7 @@
<div class="sheet-music-window" id="music-window"> <div class="sheet-music-window" id="music-window">
<object type="image/svg+xml" data="/assets/svg/time-pointer.svg" class="time-indicator" <object type="image/svg+xml" data="/assets/svg/time-pointer.svg" class="time-indicator"
id="time-indicator"></object> id="time-indicator"></object>
</div>
</div>
<div id="about-content">
<div class="about-title">
<img src="/assets/svg/the-conductor-title.svg"></img>
</div>
<div class="about-content">
<div class="safari">
<div class="instruments-key" id="instrument-key-div"></div>
</div>
<p>
The Conductor translates live lightning strikes from around the world into a graphic score for seven percussion
instruments.
</p>
<p>
The project was conceived and developed by the artist Mishka Henner between 2023 and 2024 with a team of
musicians, sound artists and acoustics engineers during an artist's residency at Energy House 2.0 at the
University of Salford.
</p>
<p>
The Conductor was first performed at the University of Salfords Acoustic Laboratories during the Sounds From
the Other City Festival on 5 May 2024.
</p>
<p>
Click <a href="https://mishkahenner.com/The-Conductor" style="color: #fff; text-decoration:underline">here</a>
to read more about the project. Download a copy of the artist's graphic score <a
href="https://files.cargocollective.com/c20096/CONDUCTOR-SHEET-MUSIC-Black-BG.pdf"
style="color: #fff; text-decoration:underline">here</a>.
</p>
<br />
<div class="production-credits">
<br />
<div>
Project Conception and Design by Mishka Henner.
</div>
Data Capture, Web Design and Coding by Joe Gibson.
<div>
Lightning data from Blitzortung.org
</div>
<p>
With support from the University of Salford Art Collection in partnership with Open Eye Gallery, Liverpool as
part of the LOOK Photo Biennial, and Castlefield Gallery, Manchester. Generously supported by Friends of
Energy House Labs.
</p>
</div>
</div> </div>
</div> </div>
<script type="module" src="/src/script.js"></script> <script type="module" src="/src/script.js"></script>

View File

@ -1,18 +0,0 @@
stop zooming issue - resize rules
mishka gonna send an x icon for the back behaviour
look at memory leak??
make the sound and menu icons part of an 'app bar' along with the conductor logo
make the mid size the default size but double check the size on the phone isnt too small
refactor when it's all done
format the key as per the pdf
sort out the title size
sort out the blitzortung connection issue
make the text max size smaller
about page make title smaller

45
src/audio.js Normal file
View File

@ -0,0 +1,45 @@
let audioContext;
let gainNode;
function startAudio() {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
gainNode = audioContext.createGain();
gainNode.gain.value = 0;
gainNode.connect(audioContext.destination);
}
function muter() {
fadeOutVolume();
}
function unMute() {
startAudio();
fadeInVolume();
}
function fadeInVolume() {
gainNode.gain.setValueAtTime(0, audioContext.currentTime); // Start at 0
gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2); // Fade to full volume
}
function fadeOutVolume() {
gainNode.gain.setValueAtTime(1, audioContext.currentTime); // Start at 1
gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 2); // Fade to 0
}
export function playSound(instrument) {
if (interacted) {
// const source = audioContext.createBufferSource();
const samples = instrument.samples;
const sampleKeys = Object.keys(samples);
const numSamples = sampleKeys.length;
const randomSampleNumber = Math.floor(Math.random() * (numSamples - 0));
// const randomKey =
// sampleKeys[randomSampleNumber];
sendMidiMessage(instrument.midiChannelName, randomSampleNumber);
// source.buffer = samples[`${randomKey}`];
// source.connect(gainNode);
// source.start();
}
}

62
src/conductor.js Normal file
View File

@ -0,0 +1,62 @@
import { loadInstruments } from "./instruments.js";
export class Conductor {
instruments;
constructor() {}
showingWhichContent;
interacted = true;
get instruments() {
return this.instruments;
}
static setTitle() {
const now = new Date();
const future = new Date(now.getTime() + 59 * 1000); // 59 seconds later
const formattedDate = `${Conductor.padZero(
now.getDate()
)}.${Conductor.padZero(now.getMonth() + 1)}.${now.getFullYear()}`;
const formattedTime = `${Conductor.padZero(
now.getHours()
)}:${Conductor.padZero(now.getMinutes())}:${this.padZero(
now.getSeconds()
)}`;
const formattedFutureTime = `${Conductor.padZero(
future.getHours()
)}:${Conductor.padZero(future.getMinutes())}:${Conductor.padZero(
future.getSeconds()
)}`;
title.innerHTML = `${formattedDate}<span class="tab-space"></span>${formattedTime} - ${formattedFutureTime} UTC`;
}
showMainContent() {
Conductor.setTitle();
this.fadeAndReplaceADiv("main-content", "loading-screen");
}
fadeAndReplaceADiv(innyDivId, outtyDivId) {
const outty = document.getElementById(outtyDivId);
const inny = document.getElementById(innyDivId);
outty.classList.remove("fade-in");
outty.classList.add("fade-out");
outty.style.display = "none";
inny.style.display = "flex";
inny.style.opacity = "0";
void inny.offsetWidth;
inny.classList.add("fade-in");
inny.style.opacity = "1";
}
static padZero(num) {
return num.toString().padStart(2, "0");
}
async init() {
this.instruments = await loadInstruments();
}
}

View File

@ -3,62 +3,67 @@ async function loadInstrumentsConfig() {
return await response.json(); return await response.json();
} }
async function fetchAudioData(url) { async function fetchSvgText(url) {
const response = await fetch(url); const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer(); return await response.text();
return arrayBuffer;
} }
async function decodeAudioData(context, arrayBuffer) { function svgTextToImage(svgText, defaultWidth = 100, defaultHeight = 100) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
context.decodeAudioData(arrayBuffer, resolve, reject); // Create a blob from the SVG text
}); const blob = new Blob([svgText], { type: "image/svg+xml" });
} const url = URL.createObjectURL(blob);
const img = new Image();
async function loadAudioSamples(context, directory, filenames) { // Set the onload callback
const audioFiles = filenames; img.onload = () => {
// Check if naturalWidth and naturalHeight are invalid
if (img.naturalWidth === 0 || img.naturalHeight === 0) {
// Set default dimensions if image is invalid (missing width/height in SVG)
img.width = defaultWidth;
img.height = defaultHeight;
}
const audioBuffers = {}; // Clean up the object URL to free memory
for (const file of audioFiles) { URL.revokeObjectURL(url);
const arrayBuffer = await fetchAudioData(`${directory}/${file}`);
const audioBuffer = await decodeAudioData(context, arrayBuffer);
const fileName = file.split("/").pop(); // Extract filename without path for reference
audioBuffers[fileName] = audioBuffer;
}
return audioBuffers; resolve(img);
} };
async function fetchImageData(url) { // Handle image loading errors
const response = await fetch(url); img.onerror = (err) => {
const blob = await response.blob(); URL.revokeObjectURL(url); // Clean up URL
return new Promise((resolve, reject) => { reject(err);
const reader = new FileReader(); };
reader.onloadend = () => resolve(reader.result); img.src = url;
reader.onerror = reject;
reader.readAsDataURL(blob); // Convert to base64
}); });
} }
export async function loadInstruments() { export async function loadInstruments() {
const instrumentsConfig = await loadInstrumentsConfig(); const instrumentsConfig = await loadInstrumentsConfig();
const context = new (window.AudioContext || window.webkitAudioContext)();
const instruments = {}; const instruments = {};
for (const [instrument, config] of Object.entries(instrumentsConfig)) { for (const [instrument, config] of Object.entries(instrumentsConfig)) {
const samples = await loadAudioSamples( const numSamples = config.filenames.length;
context,
config.directory, const svgText = await fetchSvgText(
config.filenames
);
const imageData = await fetchImageData(
`${config.directory}/${config.image.filename}` `${config.directory}/${config.image.filename}`
); );
// Pass the width and height to the svgTextToImage function
const img = await svgTextToImage(
svgText,
config.image.width || 100,
config.image.height || 100
);
instruments[instrument] = { instruments[instrument] = {
samples: samples, numSamples: numSamples,
image: imageData, image: img,
yPos: config.image.yPos, // Store base64-encoded image data yPos: config.image.yPos,
midiChannelName: config["midi-channel-name"], midiChannelName: config["midi-channel-name"],
width: config.image.width,
height: config.image.height,
}; };
} }

View File

@ -1,30 +1,29 @@
export async function sendMidiMessage(midiChannelName, sampleNumber ) { export async function sendMidiMessage(midiChannelName, sampleNumber, midiAccess) {
try { try {
const midiAccess = await navigator.requestMIDIAccess(); console.log(midiChannelName, sampleNumber);
const availableMidiOutputs = midiAccess.outputs.values(); const availableMidiOutputs = midiAccess.outputs.values();
let midiOut; let midiOut;
if (availableMidiOutputs.length === 0) { if (availableMidiOutputs.length === 0) {
console.error("No MIDI output devices found."); console.error("No MIDI output devices found.");
return; return;
}
for ( const availableMidiOutput of availableMidiOutputs){
if (availableMidiOutput.name === midiChannelName){
midiOut = availableMidiOutput;
break;
}
}
// MIDI Note On (Play Note)
midiOut.send([0x90, 36 + sampleNumber, 127]);
// MIDI Note Off after 500ms
setTimeout(() => {
midiOut.send([0x80, 36 + sampleNumber, 0]); // Stop Note
}, 500);
} catch (error) {
console.error("Failed to get MIDI access:", error);
} }
for (const availableMidiOutput of availableMidiOutputs) {
if (availableMidiOutput.name === midiChannelName) {
midiOut = availableMidiOutput;
break;
}
}
// MIDI Note On (Play Note)
midiOut.send([0x90, 35 + sampleNumber, 127]);
// MIDI Note Off after 500ms
setTimeout(() => {
midiOut.send([0x80, 35 + sampleNumber, 0]); // Stop Note
}, 500);
} catch (error) {
console.error("Failed to get MIDI access:", error);
}
} }

135
src/render.js Normal file
View File

@ -0,0 +1,135 @@
import { Conductor } from "./conductor.js";
export class Renderer {
timeIndicator;
currStaveNumber;
numStaves;
sheetWindow;
canvases = [];
iconCache = [];
iconScale = 0.20;
constructor(dataStream, strikeHandler) {
this.dataStream = dataStream;
this.strikeHandler = strikeHandler;
this.numStaves = 6;
this.currStaveNumber = 1;
this.timeIndicator = document.getElementById("time-indicator");
this.sheetWindow = document.getElementById("music-window");
window.addEventListener("resize", () => {
this.redrawIcons();
});
this.timeIndicator.addEventListener("animationiteration", () => {
this.currStaveNumber++;
if (this.currStaveNumber > this.numStaves) {
this.dataStream.removeEventListener("strike", this.strikeHandler);
setTimeout( () =>{this.dataStream.addEventListener("strike", this.strikeHandler);
this.cleanUpAndRestart();}, 60000);
}
});
for (let i = 1; i <= this.numStaves; i++) {
const staveWrapper = document.createElement("div");
staveWrapper.classList.add("stave-wrapper");
staveWrapper.setAttribute("id", `stave-wrapper-${i}`);
staveWrapper.style.position = "relative";
const staveObject = document.createElement("object");
staveObject.type = "image/svg+xml";
staveObject.data = "assets/svg/stave.svg";
staveObject.className = "stave-svg"; // Fixed className
const canvas = document.createElement("canvas");
canvas.id = `canvas-${i}`;
canvas.className = "event-canvas";
const ctx = canvas.getContext("2d");
this.canvases.push({ canvas, ctx });
staveWrapper.appendChild(staveObject);
staveWrapper.appendChild(canvas);
this.sheetWindow.appendChild(staveWrapper);
// Your exact sizing method + minimal DPR adjustment
requestAnimationFrame(() => {
const dpr = window.devicePixelRatio || 1;
// Keep your offset measurements
const displayWidth = canvas.offsetWidth;
const displayHeight = canvas.offsetHeight;
// Apply to actual canvas buffer
canvas.width = Math.round(displayWidth * dpr);
canvas.height = Math.round(displayHeight * dpr);
// Maintain your display size
canvas.style.width = `${displayWidth}px`;
canvas.style.height = `${displayHeight}px`;
// Scale context to compensate
ctx.scale(dpr , dpr);
});
}
}
async placeIcon(instrument) {
if (this.currStaveNumber > this.numStaves) {
this.currStaveNumber = 1;
this.iconCache.length = 0;
}
const rect = this.timeIndicator.getBoundingClientRect();
const sheetLeft = this.sheetWindow.getBoundingClientRect().left;
const xPosition = rect.left + window.scrollX - sheetLeft;
const canvasEntry = this.canvases[this.currStaveNumber - 1];
const canvas = canvasEntry.canvas;
const ctx = canvasEntry.ctx;
const yPercent = parseFloat(instrument.yPos);
const y = (yPercent / 100) * canvas.height;
const width = instrument.width * this.iconScale;
const height = instrument.height * this.iconScale;
ctx.drawImage(instrument.image, xPosition, y, width, height);
this.iconCache.push({
image: instrument.image,
x: xPosition,
y,
width,
height,
stave: this.currStaveNumber,
});
}
redrawIcons() {
this.canvases.forEach(({ ctx, canvas }) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
this.iconCache.forEach(({ image, x, y, width, height, stave }) => {
const ctx = this.canvases[stave - 1].ctx;
ctx.drawImage(image, x, y, width, height);
});
}
cleanUpAndRestart(reload = false) {
this.iconCache = [];
this.currStaveNumber = 1;
this.canvases.forEach(({ ctx, canvas }) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
Conductor.setTitle();
if (reload) location.reload();
}
}

View File

@ -1,294 +1,44 @@
import { Client, BrowserSocketFactory } from "./blitz.js";
import { loadInstruments } from "./instruments.js";
import { sendMidiMessage } from "./midi.js"; import { sendMidiMessage } from "./midi.js";
import { Conductor } from "./conductor.js";
import { DataStream } from "./socket.js";
import { Renderer } from "./render.js";
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
let showing;
let audioContext;
let interacted = true;
let gainNode;
sendMidiMessage(); const midiAccess = await navigator.requestMIDIAccess();
const dataStream = new DataStream();
const conductor = new Conductor();
await conductor.init();
const instruments = await conductor.instruments;
let strikes = 0;
const strikeEvery = 2;
function startAudio() { const strikeHandler = () => {
audioContext = new (window.AudioContext || window.webkitAudioContext)(); console.log(strikes);
gainNode = audioContext.createGain(); //** select instrument */
gainNode.gain.value = 0; if (strikes === strikeEvery){
gainNode.connect(audioContext.destination);
}
function muter() { const keys = Object.keys(instruments);
fadeOutVolume(); const randomIndex = Math.floor(Math.random() * keys.length);
} const selectedInstrument = instruments[keys[randomIndex]];
function unMute() { //** select sample */
startAudio(); const numSamples = selectedInstrument.numSamples;
fadeInVolume(); const randomSampleNumber = Math.floor(Math.random() * numSamples);
}
renderer.placeIcon(selectedInstrument);
function padZero(num) { sendMidiMessage(selectedInstrument.midiChannelName, randomSampleNumber + 1, midiAccess);
return num.toString().padStart(2, "0"); strikes = 0;
}
function setTitle() {
const now = new Date();
const future = new Date(now.getTime() + 59 * 1000); // 59 seconds later
const formattedDate = `${padZero(now.getDate())}.${padZero(
now.getMonth() + 1
)}.${now.getFullYear()}`;
const formattedTime = `${padZero(now.getHours())}:${padZero(
now.getMinutes()
)}:${padZero(now.getSeconds())}`;
const formattedFutureTime = `${padZero(future.getHours())}:${padZero(
future.getMinutes()
)}:${padZero(future.getSeconds())}`;
title.innerHTML = `${formattedDate}<span class="tab-space"></span>${formattedTime} - ${formattedFutureTime} UTC`;
}
function fadeInVolume() {
gainNode.gain.setValueAtTime(0, audioContext.currentTime); // Start at 0
gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2); // Fade to full volume
}
function fadeOutVolume() {
gainNode.gain.setValueAtTime(1, audioContext.currentTime); // Start at 1
gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 2); // Fade to 0
}
function showMainContent() {
setTitle();
const mute = document.getElementById("mute");
mute.addEventListener("click", () => {
const mutedSrc = "/assets/svg/sound-on.svg";
const unMutedSrc = "/assets/svg/mute.svg";
interacted = !interacted;
interacted ? unMute() : muter();
mute.src = interacted ? mutedSrc : unMutedSrc;
startAudio();
fadeInVolume();
});
fadeAndReplaceADiv("main-content", "loading-screen");
fadeAndReplaceADiv("buttons", "buttons");
showing = "main-content";
document.getElementById("menu-button").onclick = function () {
toggleInfo();
};
}
function toggleInfo() {
if (showing === "main-content") {
fadeAndReplaceADiv("about-content", "main-content");
cleanUpAndRestart();
showing = "about-content";
} else {
fadeAndReplaceADiv("main-content", "about-content");
showing = "main-content";
} }
} strikes++;
function fadeAndReplaceADiv(innyDivId, outtyDivId) {
const outty = document.getElementById(outtyDivId);
const inny = document.getElementById(innyDivId);
outty.classList.remove("fade-in");
outty.classList.add("fade-out");
outty.style.display = "none";
inny.style.display = "flex";
inny.style.opacity = "0";
void inny.offsetWidth;
inny.classList.add("fade-in");
inny.style.opacity = "1";
}
const socketFactory = new BrowserSocketFactory();
let client = new Client(socketFactory);
client.connect();
let connectionTimeout; // Timeout for detecting inactivity
let lastReceived = Date.now(); // Tracks the last time data was received
function resetTimeout() {
clearTimeout(connectionTimeout);
connectionTimeout = setTimeout(() => {
// 15 seconds of inactivity
console.log("No data received in 15 seconds, reconnecting...");
reconnectWebSocket();
}, 15000);
}
function reconnectWebSocket() {
console.log("Reconnecting WebSocket...");
// Clean up the existing WebSocket connection
if (client) {
client.close(); // Ensure the current connection is closed
}
// Create a new client instance and re-establish the connection
client = new Client(new BrowserSocketFactory());
setupWebSocketListeners(); // Set up listeners for the new WebSocket
client.connect(); // Reconnect
// Reset timeout after reconnecting
resetTimeout(); // Ensure timeout is re-established after reconnection
}
function setupWebSocketListeners() {
client.removeAllListeners("data");
client.removeAllListeners("error");
client.removeAllListeners("close");
client.on("data", (strike) => {
lastReceived = Date.now(); // Update the last received timestamp
resetTimeout(); // Reset the timeout on any data received
// Process the strike (existing logic from your original code)
strikeNumber++;
if (strikeNumber == 1) {
const randomInstrumentNumber = Math.floor(
Math.random() * (Object.keys(instruments).length - 1)
);
const selectedInstrumentName =
Object.keys(instruments)[randomInstrumentNumber];
placeIcon(instruments[selectedInstrumentName]);
playSound(instruments[selectedInstrumentName]);
strikeNumber = 0;
}
});
client.on("error", (message) => {
console.log("WebSocket error:", message);
reconnectWebSocket();
});
client.on("close", () => {
console.log("WebSocket closed, attempting to reconnect...");
reconnectWebSocket();
});
}
let currStaveNumber = 1;
const instruments = await loadInstruments();
const instNames = Object.keys(instruments);
showMainContent();
/***
* Add the instrument images and names into the about section
*/
const instrumentsKey = document.getElementById("instrument-key-div");
for (const instrument of instNames) {
const keyDiv = document.createElement("div");
const keySvg = document.createElement("object");
const keyName = document.createElement("div");
keyName.innerText = instrument.toUpperCase();
keySvg.type = "image/svg+xml";
keySvg.data = instruments[instrument].image;
keyDiv.appendChild(keySvg);
keyDiv.appendChild(keyName);
keyDiv.classList.add("instrument-key");
instrumentsKey.appendChild(keyDiv);
}
const numStaves = 6;
const sheetWindow = document.getElementById("music-window");
for (let i = 1; i <= numStaves; i++) {
const staveWrapper = document.createElement("div");
staveWrapper.classList.add(`stave-wrapper`);
staveWrapper.setAttribute("id", `stave-wrapper-${i}`);
staveWrapper.style.position = "relative";
const staveObject = document.createElement("object");
staveObject.type = "image/svg+xml";
staveObject.data = "assets/svg/stave.svg";
staveObject.classList.add("stave-svg");
staveWrapper.appendChild(staveObject);
sheetWindow.appendChild(staveWrapper);
}
const timeIndicator = document.querySelector("#time-indicator");
timeIndicator.addEventListener("animationiteration", () => {
currStaveNumber++;
if (currStaveNumber > numStaves) {
cleanUpAndRestart();
}
});
const cleanUpAndRestart = (reload = false) => {
const icons = document.querySelectorAll(".event-icon");
const timeIndicator = document.getElementById("time-indicator");
icons.forEach((icon) => {
icon.classList.add("fade-out");
icon.addEventListener("transitionend", () => {
icon.remove();
});
});
currStaveNumber = 1;
setTitle();
if (reload) location.reload();
}; };
const playSound = (instrument) => { const renderer = new Renderer(dataStream, strikeHandler);
if (interacted) {
// const source = audioContext.createBufferSource();
const samples = instrument.samples;
const sampleKeys = Object.keys(samples);
const numSamples = sampleKeys.length;
const randomSampleNumber = Math.floor(Math.random() * (numSamples - 1));
// const randomKey =
// sampleKeys[randomSampleNumber];
sendMidiMessage(instrument.midiChannelName, randomSampleNumber);
// source.buffer = samples[`${randomKey}`]; dataStream.addEventListener("strike", strikeHandler);
// source.connect(gainNode);
// source.start();
}
};
const placeIcon = (instrument) => { dataStream.init();
if (currStaveNumber > numStaves) currStaveNumber = 1;
// Select the staveWrapper in which to place the div
const staveWrapper = document.getElementById(
`stave-wrapper-${currStaveNumber}`
);
// Determine the position of the time indicator
const rect = timeIndicator.getBoundingClientRect();
const sheetLeft = sheetWindow.getBoundingClientRect().left;
const xPosition = rect.left + window.scrollX - sheetLeft;
// Create the icon div and give it its location data conductor.showMainContent();
const newObject = document.createElement("div");
newObject.classList.add("event-icon");
newObject.style.position = "absolute";
newObject.style.left = `${
xPosition - document.documentElement.clientWidth / 200
}px`;
newObject.style.top = `${instrument.yPos}`;
// Create the icon
const eventIcon = document.createElement("object");
eventIcon.type = "image/svg+xml";
eventIcon.data = instrument.image;
// Append icon to div
newObject.appendChild(eventIcon);
// Append the div to the staveWrapper
staveWrapper.appendChild(newObject);
};
let strikeNumber = 0;
setupWebSocketListeners(); // Setup the WebSocket listeners
resetTimeout(); // Start the timeout timer
}); });

71
src/socket.js Normal file
View File

@ -0,0 +1,71 @@
import { BrowserSocketFactory, Client } from "./blitz.js";
export class DataStream extends EventTarget {
constructor() {
super();
this.socketFactory = null;
this.client = null;
this.lastReceived = null;
this.connectionTimeout = null;
this.test = false;
this.testInterval = null;
}
async init() {
this.socketFactory = new BrowserSocketFactory();
this.client = new Client(this.socketFactory);
this.lastReceived = Date.now();
this.setupWebSocketListeners();
this.client.connect();
this.resetTimeout();
}
resetTimeout() {
clearTimeout(this.connectionTimeout);
this.connectionTimeout = setTimeout(() => {
console.log("No data received in 14 seconds, reconnecting...");
this.reconnectWebSocket();
}, 14999);
}
reconnectWebSocket() {
if (this.client) {
this.client.close();
}
this.client = new Client(new BrowserSocketFactory());
this.setupWebSocketListeners();
this.client.connect();
this.resetTimeout();
}
setupWebSocketListeners() {
if (this.test === false) {
this.client.removeAllListeners?.("data");
this.client.removeAllListeners?.("error");
this.client.removeAllListeners?.("close");
this.client.on("data", (data) => {
this.lastReceived = Date.now();
this.resetTimeout();
this.dispatchEvent(new CustomEvent("strike", { detail: data }));
});
this.client.on("error", (err) => {
console.error("WebSocket error:", err);
this.reconnectWebSocket();
});
this.client.on("close", () => {
console.log("WebSocket closed, attempting to reconnect...");
this.reconnectWebSocket();
});
} else {
clearInterval(this.testInterval);
this.testInterval = setInterval(() => {
this.dispatchEvent(new CustomEvent("strike", { detail: "fakeData" }));
}, 1000);
}
}
}

View File

@ -1,20 +1,20 @@
body { body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
background-color: rgb(24, 24, 24); background-color: rgb(0,0,0);
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
} }
object { object {
margin-bottom: 10px; margin-bottom: 10px;
} }
html { html {
overflow-x: hidden; overflow-x: visible;
} }
* { * {
@ -32,7 +32,7 @@ html {
align-items: center; align-items: center;
opacity: 0; opacity: 0;
transition: opacity 2s ease; transition: opacity 2s ease;
overflow: hidden; overflow: visible;
} }
@ -74,6 +74,29 @@ html {
transition: opacity 1s ease; transition: opacity 1s ease;
} }
.event-canvas {
position: absolute;
top: 0;
left: -10px;
height: 12vh;
width: 102%; /* Matches JS padding */
display: block;
pointer-events: none;
overflow: visible;
z-index: 9999;
}
.stave-wrapper {
position: relative;
display: block;
width: 100%;
height: 12vh;
background-color: rgb(0,0,0);
padding-top: 30px;
padding-bottom: 20px;
overflow: visible;
z-index: 9995;
}
.logo { .logo {
color: #fff; color: #fff;
@ -88,10 +111,6 @@ html {
margin: 20px; margin: 20px;
} }
.event-icon {
width: 1vw;
min-width: 10px;
}
.instruments-key { .instruments-key {
position: relative; position: relative;
@ -121,8 +140,9 @@ html {
max-width: 700px; max-width: 700px;
height: 70%; height: 70%;
margin: 0; margin: 0;
background-color: rgb(24,24,24); background-color: rgb(0,0,0);
margin-bottom: 100px; margin-bottom: 100px;
overflow: visible;
} }
.conductor-title{ .conductor-title{
@ -149,27 +169,30 @@ html {
z-index: 10; z-index: 10;
} }
.title{
top: 30px;
width: 100%;
margin: 30px;
text-align: center;
color: white;
background-color: rgb(24,24,24);
/* height: 20vh; */
font-family: Helvetica;
font-size: clamp(8px,1.1vw, 18px);
}
.about-title { .about-title {
top: 30px; top: 30px;
width: 50%; width: 50%;
} }
.title{
top: 30px;
width: 100%;
margin-top: 30px;
margin-bottom: 80px;
text-align: center;
color: white;
background-color: rgb(0,0,0);
/* height: 20vh; */
font-family: Helvetica;
font-size: clamp(14px,1.1vw, 18px);
}
.title-title { .title-title {
font-family: Helvetica; font-family: Helvetica;
color: #fff; color: #fff;
font-size: clamp(12px,1.5vw, 24px); margin-top: 40px;
font-size: clamp(18px,1.5vw, 24px);
} }
.production-credits { .production-credits {
@ -180,25 +203,25 @@ html {
content: '\00a0\00a0\00a0\00a0\00a0\00a0'; /* 6 non-breaking spaces */ content: '\00a0\00a0\00a0\00a0\00a0\00a0'; /* 6 non-breaking spaces */
} }
.stave-wrapper {
.stave-svg {
position: relative; position: relative;
background-color: rgb(24,24,24); z-index: 1;
border: 1px solid rgb(24,24,24);
padding-top: 30px;
padding-bottom: 20px;
} }
.time-indicator {
top: 0; #time-indicator {
left: 0;
position: absolute; position: absolute;
width: 2px; top: 0;
/* height: 100%; */ bottom: 0;
width: 2px;
height: 92%;
background-color: rgb(0, 0, 0);
z-index: 0;
animation: moveRight 10s linear infinite; animation: moveRight 10s linear infinite;
z-index: 2; will-change: transform;
} }
.fade-in { .fade-in {
opacity: 1; opacity: 1;
} }
@ -215,7 +238,8 @@ html {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
@keyframes moveRight { @keyframes moveRight {
0% { 0% {
left: 0; left: 0;