unified styling

This commit is contained in:
David Allemang
2026-07-07 12:03:23 -04:00
parent 9cafb23dc2
commit 20b8ac6e3b
14 changed files with 271 additions and 108 deletions

View File

@@ -13,6 +13,15 @@
<main>
<h1>Visualizer</h1>
<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam nibh mi, aliquam ut urna sit amet, mollis sodales
augue. Duis faucibus nibh eu purus egestas lobortis. In ut vehicula eros. Orci varius natoque penatibus et
magnis dis parturient montes, nascetur ridiculus mus. In hac habitasse platea dictumst. Quisque ultricies mauris
sed tortor cursus, eu eleifend neque vulputate. Suspendisse vitae libero eu tellus porta vestibulum. Quisque
tortor turpis, tincidunt vel enim et, tempor facilisis sem. Sed condimentum elit odio, non finibus sapien
faucibus in. Duis a tempus ex. Mauris sodales nisi ut mi lacinia auctor. Nunc interdum cursus sem luctus
faucibus. Quisque mattis iaculis finibus. Aliquam commodo leo a risus porttitor, vel vulputate nisi
vehicula. </p>
<h2>Dynamic Pulses</h2>
<mc-diorama radius="4">
<mc-timeline loop autoplay controls="false"></mc-timeline>
@@ -40,10 +49,17 @@
<h2>Static multi-view scene</h2>
<p> Phasellus tortor turpis, commodo ut tortor scelerisque, fermentum tempus nisi. Pellentesque consectetur commodo
rhoncus. Sed ullamcorper turpis dapibus, luctus nibh eu, dapibus risus. Fusce finibus ante eleifend, venenatis
sem eu, tempor elit. Duis eget lacus justo. Ut efficitur lacus a massa sagittis fringilla. Proin non dictum
eros. Etiam porttitor libero a facilisis vulputate. Duis facilisis eget arcu quis ultricies. Etiam dui erat,
luctus ut tristique vitae, fermentum quis magna. </p>
<mc-frame id="nonsense">
<mc-camera orbit></mc-camera>
<mc-world>
p -1 0 0 redstone_wire east=side west=none north=none south=none power=0
p -1 -1 0 smooth_stone_slab type=top
p 0 0 0 repeater facing=east delay=1 locked=false powered=false
p 2 0 0 oak_trapdoor facing=east half=bottom open=true
p 0 -1 0 lodestone
@@ -54,12 +70,18 @@
<div class="grid-2col">
<mc-diorama frameid="nonsense" theta="180" phi="90"></mc-diorama>
<mc-diorama frameid="nonsense">
<mc-camera orbit></mc-camera>
<mc-camera pan="false" zoom="false"></mc-camera>
</mc-diorama>
<mc-diorama frameid="nonsense" theta="180" phi="0"></mc-diorama>
<mc-diorama frameid="nonsense" theta="270" phi="0"></mc-diorama>
</div>
<p> Sed urna ante, aliquet et ex nec, posuere laoreet sem. Aenean non sem a massa vehicula faucibus. Donec varius
mattis elit ut efficitur. Aenean quis mattis urna. Praesent quis lacus ac ligula sollicitudin mattis. Donec mi
enim, suscipit eget urna a, consectetur suscipit sapien. Etiam lacinia, arcu vel fermentum vulputate, erat nibh
efficitur eros, euismod varius ex eros in urna. Nulla magna ex, congue ac finibus sit amet, tristique nec
ante. </p>
<h2>Dynamic Timeline Control</h2>
<mc-diorama>
<mc-camera orbit></mc-camera>
@@ -124,6 +146,16 @@
</mc-world>
</mc-diorama>
<p> Suspendisse potenti. Nulla facilisi. Mauris non ullamcorper justo. Ut euismod lacus eget orci elementum, eu
cursus nisl pellentesque. Vestibulum ligula magna, venenatis eget ligula eu, gravida semper tellus. Praesent
quis enim eu metus hendrerit feugiat. Etiam lorem tortor, condimentum sed condimentum sit amet, placerat
vulputate ipsum. Duis varius lobortis elit, et pharetra velit feugiat sed. Class aptent taciti sociosqu ad
litora torquent per conubia nostra, per inceptos himenaeos. Cras venenatis ligula sed elit aliquam vestibulum.
Duis fringilla, mauris at lacinia posuere, mauris urna auctor lacus, vitae faucibus velit ante sit amet massa.
Ut id massa eget dui vestibulum pretium. Nunc tempus odio quis nisi ultrices, id volutpat dui tristique.
Suspendisse lacus metus, pulvinar ac urna vitae, pellentesque volutpat eros. In cursus ipsum in enim ornare, sed
tempus mi tincidunt. </p>
<div id="atlas-container"></div>
</main>
</body>

View File

@@ -18,19 +18,125 @@ mc-diorama {
.spacetime-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
inset: 0; /* Replaces top, left, width, height */
padding: 5px;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
box-sizing: border-box;
padding: 12px;
z-index: 10;
font-family: sans-serif;
/* Core icon engine rules */
& .icon {
display: inline-block;
width: 1.2em;
height: 1.2em;
background-color: currentColor; /* Inherits text color (white, or hover states) */
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: contain;
/* Map classes to your local SVG paths */
&.ico-play {
mask-image: url('ico-play.svg');
-webkit-mask-image: url('ico-play.svg');
}
&.ico-pause {
mask-image: url('ico-pause.svg');
-webkit-mask-image: url('ico-pause.svg');
}
&.ico-auto-events {
mask-image: url('ico-auto-events.svg');
-webkit-mask-image: url('ico-auto-events.svg');
}
&.ico-prev {
mask-image: url('ico-prev.svg');
-webkit-mask-image: url('ico-prev.svg');
}
&.ico-next {
mask-image: url('ico-next.svg');
-webkit-mask-image: url('ico-next.svg');
}
&.ico-prev-event {
mask-image: url('ico-prev-event.svg');
-webkit-mask-image: url('ico-prev-event.svg');
}
&.ico-next-event {
mask-image: url('ico-next-event.svg');
-webkit-mask-image: url('ico-next-event.svg');
}
&.ico-reset {
mask-image: url('ico-reset.svg');
-webkit-mask-image: url('ico-reset.svg');
}
/* Spatial indicators (mapped for future integration if desired) */
&.ico-rotate {
mask-image: url('ico-rotate.svg');
-webkit-mask-image: url('ico-rotate.svg');
}
&.ico-pan {
mask-image: url('ico-pan.svg');
-webkit-mask-image: url('ico-pan.svg');
}
&.ico-zoom {
mask-image: url('ico-zoom.svg');
-webkit-mask-image: url('ico-zoom.svg');
}
}
/* Define the safe zones for the top cluster */
& .top-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
pointer-events: none;
}
/* Spatial Tool Hints */
& .spacetime-indicator {
display: flex;
gap: 4px;
/* Mimic the .btn box model so baselines perfectly align with the Play button */
padding: 4px 8px;
border: 1px solid transparent;
color: rgba(255, 255, 255, 0.4);
font-family: monospace;
font-size: 14px;
opacity: 1;
transition: opacity 0.2s ease;
pointer-events: auto;
}
/* Unified Button Design Language */
& .btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px; /* Spacing between icon and text for the reset button */
}
& .btn {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
@@ -42,21 +148,19 @@ mc-diorama {
font-weight: bold;
font-family: inherit;
backdrop-filter: blur(4px);
transition: background 0.1s, border-color 0.1s;
pointer-events: auto; /* Always clickable when visible */
pointer-events: auto;
&:hover {
background: rgba(255, 255, 255, 0.2);
}
}
& .reset-wrapper {
display: flex;
justify-content: flex-end;
width: 100%;
& .top-row .btn {
background: rgba(0, 0, 0, 0.6);
visibility: hidden;
& .btn {
visibility: hidden;
&:hover {
background: rgba(0, 0, 0, 0.8);
}
}
@@ -65,8 +169,8 @@ mc-diorama {
& .visualizer-ui {
pointer-events: none;
background: transparent;
padding: 12px;
border-radius: 8px;
margin: -5px;
padding: 5px;
display: flex;
flex-direction: column;
gap: 12px;
@@ -88,7 +192,7 @@ mc-diorama {
& .track-container {
flex-grow: 1;
height: 24px;
padding: 0 10px;
padding: 0 8px; /* Safe zone so the rounded handle doesn't bleed out */
cursor: pointer;
display: flex;
align-items: center;
@@ -96,27 +200,16 @@ mc-diorama {
& .track-inner {
position: relative;
width: 100%;
height: 100%;
height: 8px; /* Defines the actual track geometry */
background: rgba(255, 255, 255, 0.2);
pointer-events: none;
& .track-bg {
position: absolute;
left: -10px;
right: -10px;
top: 8px;
height: 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
& .track-fill {
position: absolute;
left: -10px;
top: 8px;
height: 8px;
left: 0;
top: 0;
bottom: 0; /* Snaps tightly to inner geometry */
background: #f00;
width: 10px;
border-radius: 4px;
}
& .track-handle {
@@ -125,7 +218,7 @@ mc-diorama {
height: 16px;
background: #fff;
border-radius: 50%;
top: 12px;
top: 50%;
transform: translate(-50%, -50%);
z-index: 3;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
@@ -133,8 +226,8 @@ mc-diorama {
& .marker {
position: absolute;
top: 8px;
height: 8px;
top: 0;
bottom: 0; /* Matches track height */
transform: translateX(-50%);
pointer-events: none;
z-index: 1;
@@ -153,22 +246,6 @@ mc-diorama {
}
}
}
/* Spatial Tool Hints */
& .spacetime-indicator {
position: absolute;
left: 33px;
top: 29px;
display: flex;
gap: 12px;
color: rgba(255, 255, 255, 0.4);
font-family: monospace;
font-size: 14px;
pointer-events: none;
opacity: 1;
transition: opacity 0.2s ease;
}
}
/* --- Hover Visibility Logic --- */
@@ -184,10 +261,6 @@ mc-diorama {
pointer-events: auto;
}
& .spacetime-indicator {
opacity: 0;
}
& .visualizer-ui {
background: rgba(20, 20, 20, 0.85);
backdrop-filter: blur(4px);

View File

@@ -15,8 +15,7 @@ export class SpacetimeController {
subticks: true,
autoplay: false,
loop: false,
speed: 0.5,
...options
speed: 0.5, ...options
};
this.hasSpatial = this.opts.orbit || this.opts.pan || this.opts.zoom;
@@ -50,77 +49,79 @@ export class SpacetimeController {
this.overlay = document.createElement('div');
this.overlay.className = 'spacetime-overlay';
// --- Feature Indicator (Bottom Left) ---
const activeFeatures = [];
// 't' is removed; the permanent Play button is now the timeline indicator!
if (this.opts.pan) activeFeatures.push('p');
if (this.opts.zoom) activeFeatures.push('z');
if (this.opts.orbit) activeFeatures.push('o');
// --- Top Row Wrapper ---
const topRow = document.createElement('div');
topRow.className = 'top-row';
// --- Feature Indicator (Top Left) ---
this.indicator = document.createElement('div');
this.indicator.className = 'spacetime-indicator';
this.indicator.innerHTML = activeFeatures.map(f => `<span>${f}</span>`).join('');
this.overlay.appendChild(this.indicator);
// --- Top: Reset Button ---
// Inject individual SVG icons with structural tooltips
if (this.opts.orbit) {
this.indicator.innerHTML += `<span class="icon ico-rotate" title="Rotate view: left drag"></span>`;
}
if (this.opts.pan) {
this.indicator.innerHTML += `<span class="icon ico-pan" title="Move view: middle drag (or shift left drag)"></span>`;
}
if (this.opts.zoom) {
this.indicator.innerHTML += `<span class="icon ico-zoom" title="Zoom view: right drag (or ctrl left drag)"></span>`;
}
topRow.appendChild(this.indicator);
// --- Reset Button (Top Right) ---
if (this.hasSpatial) {
const topWrapper = document.createElement('div');
topWrapper.className = 'reset-wrapper';
this.resetBtn = document.createElement('div');
this.resetBtn.className = 'btn';
this.resetBtn.textContent = 'Reset View';
this.resetBtn.className = 'btn spacetime-hover-reveal';
this.resetBtn.innerHTML = `<span class="icon ico-reset" title="Reset camera"></span>`;
this.resetBtn.addEventListener('click', () => {
this.diorama.loadState();
this.resetBtn.style.visibility = 'hidden';
});
topWrapper.appendChild(this.resetBtn);
this.overlay.appendChild(topWrapper);
topRow.appendChild(this.resetBtn);
}
this.overlay.appendChild(topRow);
// --- Bottom: Timeline Controls ---
if (this.opts.timeline) {
this.uiContainer = document.createElement('div');
// Removed spacetime-hover-reveal from the wrapper
this.uiContainer.className = 'visualizer-ui';
if (this.opts.subticks) {
this.subRow = document.createElement('div');
// The entire subtick row hides on idle
this.subRow.className = 'scrubber-row spacetime-hover-reveal';
this.subRow.innerHTML = `
<div class="btn" id="btn-play-sub" title="Animate Events">S&gt;</div>
<div class="btn" id="btn-prev-sub" title="Previous Event">&lt;</div>
<div class="track-container" id="track-sub-container">
<div class="btn" id="btn-play-sub" title="Animate block updates"><span class="icon ico-auto-events"></span></div>
<div class="btn" id="btn-prev-sub" title="Previous block update"><span class="icon ico-next-event" style="transform: scaleX(-1);"></span></div>
<div class="track-container" id="track-sub-container" title="Select block update (in current tick)">
<div class="track-inner" id="track-sub-inner">
<div class="track-bg"></div>
<div class="track-fill" id="fill-sub"></div>
<div id="markers-sub"></div>
<div class="track-handle" id="handle-sub"></div>
</div>
</div>
<div class="btn" id="btn-next-sub" title="Next Event">&gt;</div>
<div class="btn" id="btn-next-sub" title="Next block update"><span class="icon ico-next-event"></span></div>
`;
this.uiContainer.appendChild(this.subRow);
}
this.tickRow = document.createElement('div');
this.tickRow.className = 'scrubber-row';
// Only the play button remains fully visible and clickable
this.tickRow.innerHTML = `
<div class="btn" id="btn-play" title="Play Realtime" style="pointer-events: auto;">R&gt;</div>
<div class="btn spacetime-hover-reveal" id="btn-loop" title="Toggle Loop">Loop</div>
<div class="btn spacetime-hover-reveal" id="btn-prev" title="Previous Tick">&lt;&lt;</div>
<div class="track-container spacetime-hover-reveal" id="track-tick-container">
<div class="btn" id="btn-play" title="Animate gameticks" style="pointer-events: auto;"><span class="icon ico-play"></span></div>
<div class="btn spacetime-hover-reveal" id="btn-loop" title="Repeat playback"><span class="icon ico-rotate"></span></div>
<div class="btn spacetime-hover-reveal" id="btn-prev" title="Previous gametick"><span class="icon ico-next" style="transform: scaleX(-1);"></span></div>
<div class="track-container spacetime-hover-reveal" id="track-tick-container" title="Select gametick">
<div class="track-inner" id="track-tick-inner">
<div class="track-bg"></div>
<div class="track-fill" id="fill-tick"></div>
<div id="markers-tick"></div>
<div class="track-handle" id="handle-tick"></div>
</div>
</div>
<div class="btn spacetime-hover-reveal" id="btn-next" title="Next Tick">&gt;&gt;</div>
<div class="btn spacetime-hover-reveal" id="btn-next" title="Next gametick"><span class="icon ico-next"></span></div>
`;
this.uiContainer.appendChild(this.tickRow);
this.overlay.appendChild(this.uiContainer);
@@ -195,13 +196,10 @@ export class SpacetimeController {
this.dragButton = e.button;
if (this.dragButton === 0) {
if (e.ctrlKey || e.metaKey) this.dragButton = 2;
else if (e.shiftKey) this.dragButton = 1;
if (e.ctrlKey || e.metaKey) this.dragButton = 2; else if (e.shiftKey) this.dragButton = 1;
}
if (this.dragButton === 1) this.canvas.style.cursor = 'move';
else if (this.dragButton === 2) this.canvas.style.cursor = 'ns-resize';
else this.canvas.style.cursor = 'grabbing';
if (this.dragButton === 1) this.canvas.style.cursor = 'move'; else if (this.dragButton === 2) this.canvas.style.cursor = 'ns-resize'; else this.canvas.style.cursor = 'grabbing';
});
window.addEventListener('mouseup', () => {
@@ -218,9 +216,7 @@ export class SpacetimeController {
if (Math.hypot(dx, dy) > 3) {
this.lastMouse = {x: e.clientX, y: e.clientY};
if (this.dragButton === 0) this.orbit(dx, dy);
else if (this.dragButton === 1) this.pan(dx, dy);
else if (this.dragButton === 2) this.zoomLinear(dy);
if (this.dragButton === 0) this.orbit(dx, dy); else if (this.dragButton === 1) this.pan(dx, dy); else if (this.dragButton === 2) this.zoomLinear(dy);
notifyCameraChange();
}
});
@@ -300,8 +296,7 @@ export class SpacetimeController {
};
getEl('btn-play').onclick = () => {
if (this.playMode === 'realtime') this.playMode = 'paused';
else {
if (this.playMode === 'realtime') this.playMode = 'paused'; else {
if (this.frame.currentTime >= this.getMaxTime() || this.frame.currentTime < 0) this.frame.seek(0);
this.playMode = 'realtime';
}
@@ -378,20 +373,37 @@ export class SpacetimeController {
if (!this.opts.timeline) return;
const getEl = id => this.uiContainer.querySelector('#' + id);
getEl('btn-play').textContent = this.playMode === 'realtime' ? '||' : 'R>';
const playButton = getEl('btn-play');
const playIcon = playButton.querySelector('.icon');
if (this.playMode === 'realtime') {
playIcon.className = 'icon ico-pause';
playButton.setAttribute('title', 'Pause gametick animation'); // Dynamic Tooltip
} else {
playIcon.className = 'icon ico-play';
playButton.setAttribute('title', 'Animate gameticks'); // Dynamic Tooltip
}
getEl('btn-loop').style.background = this.isLooping ? 'rgba(102, 204, 102, 0.4)' : 'rgba(255, 255, 255, 0.1)';
getEl('btn-loop').style.borderColor = this.isLooping ? '#6c6' : 'rgba(255, 255, 255, 0.2)';
if (this.opts.subticks) {
getEl('btn-play-sub').textContent = this.playMode === 'subtick' ? '||' : 'S>';
const subPlayButton = getEl('btn-play-sub');
const subPlayIcon = subPlayButton.querySelector('.icon');
if (this.playMode === 'subtick') {
subPlayIcon.className = 'icon ico-pause';
subPlayButton.setAttribute('title', 'Pause block update animation'); // Dynamic Tooltip
} else {
subPlayIcon.className = 'icon ico-auto-events';
subPlayButton.setAttribute('title', 'Animate block updates'); // Dynamic Tooltip
}
this.subRow.style.display = this.playMode === 'realtime' ? 'none' : 'flex';
}
const maxTime = this.getMaxTime();
const displayTime = Math.max(0, this.frame.currentTime);
const tickPct = maxTime > 0 ? (displayTime / maxTime) * 100 : 0;
getEl('fill-tick').style.width = `calc(${tickPct}% + 10px)`;
getEl('fill-tick').style.width = `${tickPct}%`;
getEl('handle-tick').style.left = `${tickPct}%`;
if (!this.opts.subticks) return;
@@ -399,8 +411,7 @@ export class SpacetimeController {
const events = this.frame.world.events;
let activeTickTime = -Infinity;
for (const ev of events) {
if (ev.time <= this.frame.currentTime) activeTickTime = ev.time;
else break;
if (ev.time <= this.frame.currentTime) activeTickTime = ev.time; else break;
}
let startIdx = 0, count = 0;
@@ -424,19 +435,18 @@ export class SpacetimeController {
const marker = document.createElement('div');
marker.className = 'marker';
marker.style.left = `${markerPct}%`;
if (labeledIndices.has(startIdx + i)) marker.classList.add('marker-label');
else marker.classList.add('marker-event');
if (labeledIndices.has(startIdx + i)) marker.classList.add('marker-label'); else marker.classList.add('marker-event');
markerContainer.appendChild(marker);
}
let localCursor = Math.max(0, Math.min(count, this.frame.currentIndex - startIdx));
const subPct = (localCursor / count) * 100;
getEl('fill-sub').style.width = `calc(${subPct}% + 10px)`;
getEl('fill-sub').style.width = `${subPct}%`;
getEl('handle-sub').style.left = `${subPct}%`;
getEl('handle-sub').style.display = 'block';
} else {
getEl('fill-sub').style.width = `0px`;
getEl('fill-sub').style.width = `0%`;
getEl('handle-sub').style.display = 'none';
}
}
@@ -468,8 +478,7 @@ export class SpacetimeController {
const nextIdx = this.frame.currentIndex + steps;
if (nextIdx > this.frame.world.events.length) {
if (this.isLooping) this.frame.seekIndex(this.frame.world.initialIndex);
else {
if (this.isLooping) this.frame.seekIndex(this.frame.world.initialIndex); else {
this.frame.seekIndex(this.frame.world.events.length);
this.playMode = 'paused';
}

8
src/ico-auto-events.svg Normal file
View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
<path
d="M500.7 138.7L512 149.4L512 96C512 78.3 526.3 64 544 64C561.7 64 576 78.3 576 96L576 224C576 241.7 561.7 256 544 256L416 256C398.3 256 384 241.7 384 224C384 206.3 398.3 192 416 192L463.9 192L456.3 184.8C456.1 184.6 455.9 184.4 455.7 184.2C380.7 109.2 259.2 109.2 184.2 184.2C109.2 259.2 109.2 380.7 184.2 455.7C259.2 530.7 380.7 530.7 455.7 455.7C463.9 447.5 471.2 438.8 477.6 429.6C487.7 415.1 507.7 411.6 522.2 421.7C536.7 431.8 540.2 451.8 530.1 466.3C521.6 478.5 511.9 490.1 501 501C401 601 238.9 601 139 501C39.1 401 39 239 139 139C238.9 39.1 400.7 39 500.7 138.7z"/>
<path
transform="scale(0.5) translate(320, 320)"
d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

12
src/ico-next-event.svg Normal file
View File

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
<rect x="0" y="0" width="640" height="640" fill="none" stroke="red"></rect>
<path transform="translate(120, 0)"
d="M441.3 299.8C451.5 312.4 450.8 330.9 439.1 342.6L311.1 470.6C301.9 479.8 288.2 482.5 276.2 477.5C264.2 472.5 256.5 460.9 256.5 448L256.5 192C256.5 179.1 264.3 167.4 276.3 162.4C288.3 157.4 302 160.2 311.2 169.3L439.2 297.3L441.4 299.7z"/>
<path
transform="scale(0.5) translate(120, 320)"
d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

1
src/ico-next.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M441.3 299.8C451.5 312.4 450.8 330.9 439.1 342.6L311.1 470.6C301.9 479.8 288.2 482.5 276.2 477.5C264.2 472.5 256.5 460.9 256.5 448L256.5 192C256.5 179.1 264.3 167.4 276.3 162.4C288.3 157.4 302 160.2 311.2 169.3L439.2 297.3L441.4 299.7z"/></svg>

After

Width:  |  Height:  |  Size: 467 B

1
src/ico-pan.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M342.6 73.4C330.1 60.9 309.8 60.9 297.3 73.4L233.3 137.4C220.8 149.9 220.8 170.2 233.3 182.7C245.8 195.2 266.1 195.2 278.6 182.7L288 173.3L288 288L173.3 288L182.7 278.6C195.2 266.1 195.2 245.8 182.7 233.3C170.2 220.8 149.9 220.8 137.4 233.3L73.4 297.3C60.9 309.8 60.9 330.1 73.4 342.6L137.4 406.6C149.9 419.1 170.2 419.1 182.7 406.6C195.2 394.1 195.2 373.8 182.7 361.3L173.3 351.9L288 351.9L288 466.6L278.6 457.2C266.1 444.7 245.8 444.7 233.3 457.2C220.8 469.7 220.8 490 233.3 502.5L297.3 566.5C309.8 579 330.1 579 342.6 566.5L406.6 502.5C419.1 490 419.1 469.7 406.6 457.2C394.1 444.7 373.8 444.7 361.3 457.2L351.9 466.6L351.9 351.9L466.6 351.9L457.2 361.3C444.7 373.8 444.7 394.1 457.2 406.6C469.7 419.1 490 419.1 502.5 406.6L566.5 342.6C579 330.1 579 309.8 566.5 297.3L502.5 233.3C490 220.8 469.7 220.8 457.2 233.3C444.7 245.8 444.7 266.1 457.2 278.6L466.6 288L351.9 288L351.9 173.3L361.3 182.7C373.8 195.2 394.1 195.2 406.6 182.7C419.1 170.2 419.1 149.9 406.6 137.4L342.6 73.4z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
src/ico-pause.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M176 96C149.5 96 128 117.5 128 144L128 496C128 522.5 149.5 544 176 544L240 544C266.5 544 288 522.5 288 496L288 144C288 117.5 266.5 96 240 96L176 96zM400 96C373.5 96 352 117.5 352 144L352 496C352 522.5 373.5 544 400 544L464 544C490.5 544 512 522.5 512 496L512 144C512 117.5 490.5 96 464 96L400 96z"/></svg>

After

Width:  |  Height:  |  Size: 528 B

5
src/ico-play.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
<path
d="M187.2 100.9C174.8 94.1 159.8 94.4 147.6 101.6C135.4 108.8 128 121.9 128 136L128 504C128 518.1 135.5 531.2 147.6 538.4C159.7 545.6 174.8 545.9 187.2 539.1L523.2 355.1C536 348.1 544 334.6 544 320C544 305.4 536 291.9 523.2 284.9L187.2 100.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 486 B

17
src/ico-prev-event.svg Normal file
View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
<rect x="0" y="0" width="640" height="640" fill="none" stroke="red"></rect>
<path
transform="translate(-120, 0)"
d="M199.7 299.8C189.4 312.4 190.2 330.9 201.9 342.6L329.9 470.6C339.1 479.8 352.8 482.5 364.8 477.5C376.8 472.5 384.6 460.9 384.6 447.9L384.6 191.9C384.6 179 376.8 167.3 364.8 162.3C352.8 157.3 339.1 160.1 329.9 169.2L201.9 297.2L199.7 299.6z"/>
<!-- <path transform="translate(120, 0)"-->
<!-- d="M441.3 299.8C451.5 312.4 450.8 330.9 439.1 342.6L311.1 470.6C301.9 479.8 288.2 482.5 276.2 477.5C264.2 472.5 256.5 460.9 256.5 448L256.5 192C256.5 179.1 264.3 167.4 276.3 162.4C288.3 157.4 302 160.2 311.2 169.3L439.2 297.3L441.4 299.7z"/>-->
<path
transform="scale(0.5) translate(520, 320)"
d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

1
src/ico-prev.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M199.7 299.8C189.4 312.4 190.2 330.9 201.9 342.6L329.9 470.6C339.1 479.8 352.8 482.5 364.8 477.5C376.8 472.5 384.6 460.9 384.6 447.9L384.6 191.9C384.6 179 376.8 167.3 364.8 162.3C352.8 157.3 339.1 160.1 329.9 169.2L201.9 297.2L199.7 299.6z"/></svg>

After

Width:  |  Height:  |  Size: 471 B

1
src/ico-reset.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M341.8 72.6C329.5 61.2 310.5 61.2 298.3 72.6L74.3 280.6C64.7 289.6 61.5 303.5 66.3 315.7C71.1 327.9 82.8 336 96 336L112 336L112 512C112 547.3 140.7 576 176 576L464 576C499.3 576 528 547.3 528 512L528 336L544 336C557.2 336 569 327.9 573.8 315.7C578.6 303.5 575.4 289.5 565.8 280.6L341.8 72.6zM304 384L336 384C362.5 384 384 405.5 384 432L384 528L256 528L256 432C256 405.5 277.5 384 304 384z"/></svg>

After

Width:  |  Height:  |  Size: 620 B

1
src/ico-rotate.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M129.9 292.5C143.2 199.5 223.3 128 320 128C373 128 421 149.5 455.8 184.2C456 184.4 456.2 184.6 456.4 184.8L464 192L416.1 192C398.4 192 384.1 206.3 384.1 224C384.1 241.7 398.4 256 416.1 256L544.1 256C561.8 256 576.1 241.7 576.1 224L576.1 96C576.1 78.3 561.8 64 544.1 64C526.4 64 512.1 78.3 512.1 96L512.1 149.4L500.8 138.7C454.5 92.6 390.5 64 320 64C191 64 84.3 159.4 66.6 283.5C64.1 301 76.2 317.2 93.7 319.7C111.2 322.2 127.4 310 129.9 292.6zM573.4 356.5C575.9 339 563.7 322.8 546.3 320.3C528.9 317.8 512.6 330 510.1 347.4C496.8 440.4 416.7 511.9 320 511.9C267 511.9 219 490.4 184.2 455.7C184 455.5 183.8 455.3 183.6 455.1L176 447.9L223.9 447.9C241.6 447.9 255.9 433.6 255.9 415.9C255.9 398.2 241.6 383.9 223.9 383.9L96 384C87.5 384 79.3 387.4 73.3 393.5C67.3 399.6 63.9 407.7 64 416.3L65 543.3C65.1 561 79.6 575.2 97.3 575C115 574.8 129.2 560.4 129 542.7L128.6 491.2L139.3 501.3C185.6 547.4 249.5 576 320 576C449 576 555.7 480.6 573.4 356.5z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
src/ico-zoom.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free v7.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M480 272C480 317.9 465.1 360.3 440 394.7L566.6 521.4C579.1 533.9 579.1 554.2 566.6 566.7C554.1 579.2 533.8 579.2 521.3 566.7L394.7 440C360.3 465.1 317.9 480 272 480C157.1 480 64 386.9 64 272C64 157.1 157.1 64 272 64C386.9 64 480 157.1 480 272zM272 416C351.5 416 416 351.5 416 272C416 192.5 351.5 128 272 128C192.5 128 128 192.5 128 272C128 351.5 192.5 416 272 416z"/></svg>

After

Width:  |  Height:  |  Size: 596 B