Initial attempt

Ruben Beltran del Rio 2026-01-22 00:22:21 +01:00
commit faf0d1d5ec
No known key found for this signature in database
18 changed files with 2618 additions and 0 deletions

229
css/style.css 100644
View File

@ -0,0 +1,229 @@
* { margin: 0; padding: 0 }
body {
overflow: hidden;
}
iframe {
display: block !important;
background-color: #fff;
height: calc(100vh - 4rem);
top: var(--loadProgress);
position: absolute;
width: 100vw;
}
/* The bed of the Machine */
.machine {
background: url('/images/Metal Panel.svg');
background-repeat: repeat-x;
bottom: 0;
height: 4rem;
position: fixed;
width: 100vw;
z-index: 9000;
}
/* Labels */
label {
background-color: #202123;
text-shadow: 1px -1px 0px #42464dFF, -1px 1px 0px #ffffff1A;
color: #eeeeee;
display: block;
font: 0.625rem 'Arial Rounded MT Bold';
padding: 0.0625rem 0.3125rem;
text-transform: uppercase;
transform: rotate(-5deg);
width: max-content;
}
/* The URL grater and indicators */
.link-grater {
position: absolute;
left: 4rem;
top: 0;
.grater-bed {
background: url('/images/Grater Bed.svg');
background-repeat: no-repeat;
background-position: bottom;
position: absolute;
height: 4rem;
top: -4rem;
width: 8.5rem;
&::before {
background: url('/images/Sausage.svg');
background-repeat: no-repeat;
content: ' ';
display: block;
height: 3em;
opacity: 0;
position: absolute;
transition: all 0.5s;
width: 8.125em;
}
&.drag-over::before {
opacity: 0.5;
}
}
.grater-blade {
background: url('/images/Grater Blade.svg');
height: 0.625rem;
left: var(--graterTravel);
position: absolute;
bottom: 0.625rem;
width: 3.4375rem;
z-index: 10;
}
.lever {
bottom: -2.75rem;
height: 3.4375rem;
position: absolute;
left: 12rem;
width: 6.125rem;
.stem {
background: url('/images/Lever Handle.svg');
height: 3.4375rem;
position: absolute;
width: 3.0625rem;
transform: rotate(var(--leverRotation));
transform-origin: calc(100% - 3px) calc(100% - 2px);
.handle {
cursor: pointer;
height: 2.75rem;
position: absolute;
left: -0.75rem;
top: -1rem;
width: 2.75rem;
}
}
&::before {
background: url('/images/Lever Back.svg');
bottom: -0.5rem;
content: '';
height: 1.375rem;
left: 2.25rem;
position: absolute;
width: 1.375rem;
}
&::after {
background: url('/images/Lever Front.svg');
bottom: -0.5rem;
content: '';
height: 1.0625rem;
left: 2.40625rem;
position: absolute;
width: 1.0625rem;
}
}
.indicator-leds {
position: absolute;
width: max-content;
bottom: -3.3125rem;
left: 10rem;
.indicator {
display: inline-block;
height: 0.6875rem;
width: 0.6875rem;
}
}
label {
position: absolute;
bottom: -3rem;
left: 1rem;
}
}
.ready-to-grate {
background: url('/images/Red Unlit Light.svg');
}
.ready-to-load {
background: url('/images/Green Unlit Light.svg');
}
[data-current-state="readyToGrate"] .ready-to-grate {
background: url('/images/Red Lit Light.svg');
}
[data-current-state="readyToLoad"] .ready-to-load {
background: url('/images/Green Lit Light.svg');
}
/* The Link Loader Wheel and Chain Mechanism */
.chain {
background: url('/images/Chain Link.svg');
background-position-y: var(--chainOffset);
background-repeat: repeat-y;
height: 100vh;
position: fixed;
right: 1rem;
top: 0;
width: 1rem;
}
.url-loader {
position: absolute;
right: 1.5rem;
top: -3rem;
.wheel {
height: 6rem;
width: 6rem;
.body {
background: url('/images/Wheel.svg');
height: 6rem;
left: 0;
position: absolute;
top: 0;
transform: rotate(var(--wheelRotation));
width: 6rem;
}
.handle {
cursor: pointer;
height: 2.75rem;
position: absolute;
right: -1.125rem;
top: 1.625rem;
width: 2.75rem;
}
}
label {
position: absolute;
bottom: 0;
left: -4rem;
}
}
/* Make a Sausage */
.sausage {
background: url('/images/Sausage.svg');
background-position: top;
background-repeat: no-repeat;
color: #fff;
display: block;
font: 0.625rem 'Arial Rounded MT Bold';
height: calc(3rem - var(--grateLevel));
overflow: hidden;
position: absolute;
top: var(--grateLevel);
left: 0;
text-align: center;
text-transform: uppercase;
width: 8.125rem;
z-index: 0;
}

1969
example.html 100644

File diff suppressed because one or more lines are too long

8
humans.txt 100644
View File

@ -0,0 +1,8 @@
/* TEAM */
Maintainer: Ruben Beltran del Rio
Site: contact@r.bdr.sh
Mastodon: https://friendship.quest/@ruben
From: Berlin, Germany
/* SITE */
Language: English

View File

@ -0,0 +1 @@
<svg width="8" xmlns="http://www.w3.org/2000/svg" height="12" id="screenshot-d28dab85-57b8-80fc-8007-7280f8b3c887" viewBox="0 0 8 12" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-d28dab85-57b8-80fc-8007-7280f8b3c887"><defs><clipPath id="frame-clip-d28dab85-57b8-80fc-8007-7280f8b3c887-render-11" class="frame-clip frame-clip-def"><rect rx="0" ry="0" x="0" y="0" width="8" height="12" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"/></clipPath></defs><g class="frame-container-wrapper"><g class="frame-container-blur"><g class="frame-container-shadows"><g clip-path="url(#frame-clip-d28dab85-57b8-80fc-8007-7280f8b3c887-render-11)" fill="none"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7280f8b3c887"><rect rx="0" ry="0" x="0" y="0" width="8" height="12" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" class="frame-background"/></g><g class="frame-children"><g id="shape-d28dab85-57b8-80fc-8007-72810e62b280"><defs><filter id="filter-render-12" x="0" y="0" width="1" height="1" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset dx="0" dy="2"/><feGaussianBlur stdDeviation="0"/><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/><feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.2 0"/><feBlend mode="normal" in2="shape" result="filter_d28dab85-57b8-80fc-8007-728098e4ea0a"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset dx="0" dy="-2"/><feGaussianBlur stdDeviation="0"/><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/><feBlend mode="normal" in2="filter_d28dab85-57b8-80fc-8007-728098e4ea0a" result="filter_d28dab85-57b8-80fc-8007-7280c08c96a4"/></filter><filter id="filter-shadow-render-12" x="0" y="0" width="1" height="1" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset dx="0" dy="2"/><feGaussianBlur stdDeviation="0"/><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/><feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.2 0"/><feBlend mode="normal" in2="shape" result="filter_d28dab85-57b8-80fc-8007-728098e4ea0a"/><feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/><feOffset dx="0" dy="-2"/><feGaussianBlur stdDeviation="0"/><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/><feBlend mode="normal" in2="filter_d28dab85-57b8-80fc-8007-728098e4ea0a" result="filter_d28dab85-57b8-80fc-8007-7280c08c96a4"/></filter></defs><g class="fills" id="fills-d28dab85-57b8-80fc-8007-72810e62b280"><path d="M0,4C0,1.7923583984375,1.7923583984375,0,4,0L4,0C6.2076416015625,0,8,1.7923583984375,8,4L8,8C8,10.2076416015625,6.2076416015625,12,4,12L4,12C1.7923583984375,12,0,10.2076416015625,0,8L0,4M2,4L2,8C2,9.10382080078125,2.89617919921875,10,4,10L4,10C5.10382080078125,10,6,9.10382080078125,6,8L6,4C6,2.89617919921875,5.10382080078125,2,4,2L4,2C2.89617919921875,2,2,2.89617919921875,2,4" filter="url(#filter-render-12)" style="fill: rgb(202, 211, 237); fill-opacity: 1;"/></g></g></g></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1 @@
<svg width="136" xmlns="http://www.w3.org/2000/svg" height="17" id="screenshot-d28dab85-57b8-80fc-8007-7293859b0261" viewBox="0 0 136 17" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-d28dab85-57b8-80fc-8007-7293859b0261"><defs><clipPath id="frame-clip-d28dab85-57b8-80fc-8007-7293859b0261-render-1" class="frame-clip frame-clip-def"><rect rx="0" ry="0" x="0" y="0" width="136" height="17" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"/></clipPath></defs><g class="frame-container-wrapper"><g class="frame-container-blur"><g class="frame-container-shadows"><g clip-path="url(#frame-clip-d28dab85-57b8-80fc-8007-7293859b0261-render-1)" fill="none"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7293859b0261"><rect rx="0" ry="0" x="0" y="0" width="136" height="17" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" class="frame-background"/></g><g class="frame-children"><g id="shape-d28dab85-57b8-80fc-8007-7293859b6ed6"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7293859b6ed6"><path d="M2,0 h126 a2,2 0 0 1 2,2 v15 a0,0 0 0 1 0,0 h-128 a2,2 0 0 1 -2,-2 v-13 a2,2 0 0 1 2,-2 z" x="0" y="0" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="130" height="17" style="fill: rgb(160, 143, 134); fill-opacity: 1;"/></g></g><g id="shape-d28dab85-57b8-80fc-8007-7293859b6ed7"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7293859b6ed7"><path d="M130,2 h4 a2,2 0 0 1 2,2 v11 a2,2 0 0 1 -2,2 h-4 a0,0 0 0 1 0,0 v-15 a0,0 0 0 1 0,0 z" x="130" y="2" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="6" height="15" style="fill: rgb(148, 133, 123); fill-opacity: 1;"/></g></g><g id="shape-d28dab85-57b8-80fc-8007-7293859b6ed8" style="opacity: 0.1;"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7293859b6ed8"><path d="M2,0 h126 a2,2 0 0 1 2,2 v0 a0,0 0 0 1 0,0 h-130 a0,0 0 0 1 0,0 v0 a2,2 0 0 1 2,-2 z" x="0" y="0" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="130" height="2" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></g></g></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1 @@
<svg width="55" xmlns="http://www.w3.org/2000/svg" height="10" id="screenshot-d28dab85-57b8-80fc-8007-729346f2c37f" viewBox="0 0 55 10" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-d28dab85-57b8-80fc-8007-729346f2c37f"><defs><clipPath id="frame-clip-d28dab85-57b8-80fc-8007-729346f2c37f-render-17" class="frame-clip frame-clip-def"><rect rx="0" ry="0" x="0" y="0" width="55" height="10" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"/></clipPath></defs><g class="frame-container-wrapper"><g class="frame-container-blur"><g class="frame-container-shadows"><g clip-path="url(#frame-clip-d28dab85-57b8-80fc-8007-729346f2c37f-render-17)" fill="none"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-729346f2c37f"><rect rx="0" ry="0" x="0" y="0" width="55" height="10" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" class="frame-background"/></g><g class="frame-children"><g id="shape-d28dab85-57b8-80fc-8007-729346f2c389" style="opacity: 0.1;"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-729346f2c389"><path d="M2,3 h126 a2,2 0 0 1 2,2 v0 a0,0 0 0 1 0,0 h-130 a0,0 0 0 1 0,0 v0 a2,2 0 0 1 2,-2 z" x="0" y="3" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="130" height="2" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></g><g id="shape-d28dab85-57b8-80fc-8007-729346f2c38a"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-729346f2c38a"><rect rx="2" ry="2" x="0" y="0" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="55" height="10" style="fill: rgb(160, 143, 134); fill-opacity: 1;"/></g></g><g id="shape-d28dab85-57b8-80fc-8007-729346f2c38b" style="opacity: 0.1;"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-729346f2c38b"><path d="M2,0 h51 a2,2 0 0 1 2,2 v0 a0,0 0 0 1 0,0 h-55 a0,0 0 0 1 0,0 v0 a2,2 0 0 1 2,-2 z" x="0" y="0" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="55" height="2" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></g><g id="shape-d28dab85-57b8-80fc-8007-729346f2c38c" style="opacity: 0.05;"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-729346f2c38c"><path d="M0,8 h55 a0,0 0 0 1 0,0 v0 a2,2 0 0 1 -2,2 h-51 a2,2 0 0 1 -2,-2 v0 a0,0 0 0 1 0,0 z" x="0" y="8" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="55" height="2" style="fill: rgb(0, 0, 0); fill-opacity: 1;"/></g></g></g></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -0,0 +1 @@
<svg width="22" xmlns="http://www.w3.org/2000/svg" height="22" id="screenshot-d28dab85-57b8-80fc-8007-7296918de646" viewBox="0 0 22 22" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-d28dab85-57b8-80fc-8007-7296918de646"><defs><clipPath id="frame-clip-d28dab85-57b8-80fc-8007-7296918de646-render-34" class="frame-clip frame-clip-def"><rect rx="0" ry="0" x="0" y="0" width="22" height="22" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"/></clipPath></defs><g class="frame-container-wrapper"><g class="frame-container-blur"><g class="frame-container-shadows"><g clip-path="url(#frame-clip-d28dab85-57b8-80fc-8007-7296918de646-render-34)" fill="none"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296918de646"><rect rx="0" ry="0" x="0" y="0" width="22" height="22" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" class="frame-background"/></g><g class="frame-children"><g id="shape-d28dab85-57b8-80fc-8007-7296918e1ec4"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296918e1ec4"><ellipse cx="11" cy="11" rx="11" ry="11" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" style="fill: rgb(41, 35, 25); fill-opacity: 1;"/></g></g><g id="shape-d28dab85-57b8-80fc-8007-7296918e67ba"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296918e67ba"><path d="M8.342041015625,1.9999923706054688L8.939697265625,3.2817306518554688"/></g><g id="strokes-d5fe543c-f897-80e6-8007-72977611550c-d28dab85-57b8-80fc-8007-7296918e67ba" class="strokes"><g class="stroke-shape"><path d="M8.342041015625,1.9999923706054688L8.939697265625,3.2817306518554688" style="fill: none; stroke-width: 2; stroke: rgb(255, 255, 255); stroke-opacity: 0.05; stroke-linecap: round;"/></g></g></g></g></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
<svg width="17" xmlns="http://www.w3.org/2000/svg" height="17" id="screenshot-d28dab85-57b8-80fc-8007-7296fe542348" viewBox="0 0 17 17" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-d28dab85-57b8-80fc-8007-7296fe542348"><defs><clipPath id="frame-clip-d28dab85-57b8-80fc-8007-7296fe542348-render-22" class="frame-clip frame-clip-def"><rect rx="0" ry="0" x="0" y="0" width="17" height="17" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"/></clipPath></defs><g class="frame-container-wrapper"><g class="frame-container-blur"><g class="frame-container-shadows"><g clip-path="url(#frame-clip-d28dab85-57b8-80fc-8007-7296fe542348-render-22)" fill="none"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296fe542348"><rect rx="0" ry="0" x="0" y="0" width="17" height="17" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" class="frame-background"/></g><g class="frame-children"><g id="shape-d28dab85-57b8-80fc-8007-7296fe5448f2"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296fe5448f2"><ellipse cx="8.5" cy="8.5" rx="8.5" ry="8.5" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" style="fill: rgb(48, 38, 21); fill-opacity: 1;"/></g></g><g id="shape-d28dab85-57b8-80fc-8007-7296fe5448f3" style="opacity: 0.1;"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296fe5448f3"><path d="M6,1.4444503784179688C6,1.4444503784179688,8,0.44445037841796875,11,1.4444503784179688"/></g><g id="strokes-d5fe543c-f897-80e6-8007-729776079807-d28dab85-57b8-80fc-8007-7296fe5448f3" class="strokes"><g class="stroke-shape"><path d="M6,1.4444503784179688C6,1.4444503784179688,8,0.44445037841796875,11,1.4444503784179688" style="fill: none; stroke-width: 1; stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-linecap: round;"/></g></g></g></g></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
<svg width="49" xmlns="http://www.w3.org/2000/svg" height="55" id="screenshot-d28dab85-57b8-80fc-8007-7296675b78ed" viewBox="0 0 49 55" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-d28dab85-57b8-80fc-8007-7296675b78ed"><defs><clipPath id="frame-clip-d28dab85-57b8-80fc-8007-7296675b78ed-render-5" class="frame-clip frame-clip-def"><rect rx="0" ry="0" x="0" y="0" width="49" height="55" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)"/></clipPath></defs><g class="frame-container-wrapper"><g class="frame-container-blur"><g class="frame-container-shadows"><g clip-path="url(#frame-clip-d28dab85-57b8-80fc-8007-7296675b78ed-render-5)" fill="none"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296675b78ed"><rect rx="0" ry="0" x="0" y="0" width="49" height="55" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" class="frame-background"/></g><g class="frame-children"><g id="shape-d28dab85-57b8-80fc-8007-7296675ba9b6"><defs><radialGradient id="fill-color-gradient-render-6-0" cx="0.16799355935575022" cy="0.7100719012795145" r="0.4407795129732544" gradientTransform="matrix(-0.657762, 0.753226, -0.753226, -0.657762, 0.813338, 1.050593)"><stop offset="0.64" stop-color="#000000" stop-opacity="0.5"/><stop offset="1" stop-color="#000000" stop-opacity="0"/></radialGradient><pattern patternUnits="userSpaceOnUse" x="27.948235617059424" y="10.905810760497303" width="5.000000000000057" height="48" id="fill-0-render-6"><g><rect width="5.000000000000057" height="48" style="fill: rgb(30, 23, 13); fill-opacity: 1;"/><rect width="5.000000000000057" height="48" style="fill: url(&quot;#fill-color-gradient-render-6-0&quot;);"/></g></pattern></defs><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296675ba9b6"><rect rx="0" ry="0" x="27.948235617059424" y="10.905810760497303" transform="matrix(0.761946, -0.647641, 0.647641, 0.761946, -15.358098, 28.029000)" width="5.000000000000057" height="48" fill="url(#fill-0-render-6)"/></g></g><g id="shape-d28dab85-57b8-80fc-8007-7296675ba9b7" style="opacity: 0.1;"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296675ba9b7"><rect rx="0" ry="0" x="31.426007460126357" y="9.610494375291125" transform="matrix(0.761946, -0.647641, 0.647641, 0.761946, -14.167826, 28.676641)" width="0.996673460154625" height="48.00006918759399" style="fill: rgb(255, 255, 255); fill-opacity: 1;"/></g></g><g id="shape-d28dab85-57b8-80fc-8007-7296675ba9b9" style="opacity: 0.1;"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296675ba9b9"><path d="M47,52.00001525878906C47,52.00001525878906,49,51.00001525878906,52,52.00001525878906"/></g><g id="strokes-d5fe543c-f897-80e6-8007-729775fdf08f-d28dab85-57b8-80fc-8007-7296675ba9b9" class="strokes"><g class="stroke-shape"><path d="M47,52.00001525878906C47,52.00001525878906,49,51.00001525878906,52,52.00001525878906" style="fill: none; stroke-width: 1; stroke: rgb(255, 255, 255); stroke-opacity: 1; stroke-linecap: round;"/></g></g></g><g id="shape-d28dab85-57b8-80fc-8007-7296675ba9ba"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296675ba9ba"><path d="M47.201171875,47.85914611816406L47.798828125,49.14088439941406"/></g><g id="strokes-d5fe543c-f897-80e6-8007-729775fefb49-d28dab85-57b8-80fc-8007-7296675ba9ba" class="strokes"><g class="stroke-shape"><path d="M47.201171875,47.85914611816406L47.798828125,49.14088439941406" style="fill: none; stroke-width: 2; stroke: rgb(255, 255, 255); stroke-opacity: 0.05; stroke-linecap: round;"/></g></g></g><g id="shape-d28dab85-57b8-80fc-8007-7296675ba9bb"><g class="fills" id="fills-d28dab85-57b8-80fc-8007-7296675ba9bb"><path d="M1.295166015625,7.3522796630859375C0.580322265625,6.5111846923828125,0.682861328125,5.247966766357422,1.52398681640625,4.533123016357422L5.333984375,1.2951774597167969C6.175048828125,0.5803642272949219,7.43829345703125,0.6828727722167969,8.153076171875,1.5239677429199219L17.10491943359375,13.601425170898438C17.81976318359375,14.442550659179688,17.71722412109375,15.705764770507812,16.8760986328125,16.420578002929688L14.59014892578125,18.363357543945312C13.7490234375,19.078201293945312,12.48583984375,18.975662231445312,11.77099609375,18.134536743164062L1.295166015625,7.3522796630859375" style="fill: rgb(48, 38, 21); fill-opacity: 1;"/></g></g></g></g></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.1 KiB

1
images/Wheel.svg 100644

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

48
index.html 100644
View File

@ -0,0 +1,48 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Rubén Beltrán del Río">
<meta name="description" content="{{ description }}">
<meta name="fediverse:creator" content="@ruben@friendship.quest" />
<title>Internet Machine.</title>
<link rel="stylesheet" type="text/css" href="/css/style.css">
<link rel="author" href="humans.txt">
<script type="module" src="/js/machine.js"></script>
</head>
<body>
<main class="web-content">
</main>
<nav class="machine">
<section class="link-grater">
<article class="grater-bed">
<article class="grater-blade"></article>
</article>
<section class="indicator-leds">
<article class="indicator ready-to-grate"></article>
<article class="indicator ready-to-load"></article>
</section>
<article class="lever">
<article class="stem">
<article class="handle"></article>
</article>
</article>
<label>Link Grater</label>
</section>
<section class="url-loader">
<article class="chain"></article>
<article class="wheel">
<article class="body">
<article class="handle"></article>
</article>
</article>
<label>URL Loader</label>
</section
</nav>
</body>
</html>

351
js/machine.js 100644
View File

@ -0,0 +1,351 @@
// Constants ///////////////////////////////////////////////////////////////////
const kDimensions = {
wheelSize: 96,
leverWidth: 98,
sausageHeight: 48,
};
const kState = {
waiting: 'waiting',
readyToGrate: 'readyToGrate',
readyToLoad: 'readyToLoad'
};
const kAction = {
completeLoading: Symbol('completeLoading'),
startPullingLever: Symbol('startPullingLever'),
stopPullingLever: Symbol('stopPullingLever'),
pullLever: Symbol('pullLever'),
startTurningWheel: Symbol('startTurningWheel'),
stopTurningWheel: Symbol('stopTurningWheel'),
turnWheel: Symbol('turnWheel'),
setIsWaitingForFrame: Symbol('setIsWaitingForFrame'),
createSausage: Symbol('createSausage'),
completeGrating: Symbol('completeGrating'),
};
const kRotationDirection = {
clockwise: Symbol('clockwise'),
counterClockwise: Symbol('counterClockwise'),
};
const kPullDirection = {
right: Symbol('right'),
left: Symbol('left'),
};
// Configuration ///////////////////////////////////////////////////////////////
const configuration = {
wheelStrength: 1,
maxLoad: 100,
leverTravel: 80.72,
graterTravel: 69,
chainSpeed: 0.5,
grateStrength: 0.01,
defaultUrl: '/example.html'
};
// App State ///////////////////////////////////////////////////////////////////
/**
* The main app state
*/
let appState = null;
/**
* Functions to handle side effects
*/
const middleware = [
function completeLoading(state, action) {
if (action === kAction.turnWheel && state.currentState === kState.readyToLoad && parseFloat(state.load) >= configuration.maxLoad) {
send(kAction.completeLoading);
}
},
function updateIframe(state, action) {
if (action === kAction.turnWheel && state.currentState === kState.readyToLoad && parseFloat(state.load) <= configuration.maxLoad) {
state.currentIframe?.style.setProperty('--loadProgress', `${100 - state.load}vh`);
}
},
function completeGrating(state, action) {
if (action === kAction.pullLever && state.currentState === kState.readyToGrate && parseFloat(state.grateLevel) >= kDimensions.sausageHeight) {
removeSausages();
const $iframe = loadIframe(state.currentUrl, 0);
send(kAction.completeGrating, { iframe: $iframe });
}
},
function createSausageIfWaiting(state, action, data) {
if (action === kAction.createSausage && state.currentState === kState.waiting) {
createSausage(data.href, data.label);
}
},
function log(state, action, data) {
//console.log(state.load);
}
];
const $stateContainer = document.querySelector('.machine');
const updateState = function updateState(state) {
for (const [key, value] of Object.entries(state)) {
$stateContainer.style.setProperty(`--${key}`, value);
$stateContainer.dataset[key] = value;
}
appState = state;
};
const reduce = function reduce(stateReference, action, data) {
let state = Object.assign({}, stateReference);
switch (action) {
case kAction.turnWheel:
state.wheelRotation = `${data.angle}rad`;
if (state.currentState === kState.readyToLoad) {
const sign = data.direction === kRotationDirection.clockwise ? 1 : -1;
const load = Math.max(Math.min(state.load - sign * data.magnitude * configuration.wheelStrength, configuration.maxLoad), 0);
state.load = load;
if (load < configuration.maxLoad && load > 0) {
const chainOffset = parseFloat(state.chainOffset) + sign * data.magnitude * configuration.chainSpeed;
state.chainOffset = `${chainOffset}%`;
}
}
break;
case kAction.pullLever:
const diff = Math.abs(state.leverTravel - data.magnitude);
state.leverTravel = data.magnitude;
const rotation = data.magnitude * configuration.leverTravel / 100;
state.leverRotation = `${rotation}deg`
const gratingTravel = data.magnitude * configuration.graterTravel / 100;
state.graterTravel = `${gratingTravel}px`
if (state.currentState === kState.readyToGrate) {
const grateLevel = Math.min(parseFloat(state.grateLevel) + diff * configuration.grateStrength, kDimensions.sausageHeight);
state.grateLevel = `${grateLevel}px`;
}
break;
case kAction.createSausage:
state.currentUrl = data.href;
state.grateLevel = '0%';
state.currentState = kState.readyToGrate;
break;
case kAction.startTurningWheel:
state.isTurningWheel = true;
break;
case kAction.stopTurningWheel:
state.isTurningWheel = false;
break;
case kAction.startPullingLever:
state.isPullingLever = true;
break;
case kAction.stopPullingLever:
state.isPullingLever = false;
break;
case kAction.completeLoading:
state.currentState = kState.waiting;
state.load = 0;
break;
case kAction.completeGrating:
state.currentState = kState.readyToLoad;
state.currentIframe = data.iframe;
state.grateLevel = '0%';
state.load = 0;
break;
}
return state;
};
/**
* The main mechanism for view changes to trigger updates.
* It receives an action and its payload.
* It queues the middleware to run after this.
*/
const send = function send(action, data) {
const state = Object.assign({}, appState);
for (const m of middleware) {
setTimeout(() => m(state, action, data), 0);
}
const newState = reduce(state, action, data);
updateState(newState);
};
// Initializzation /////////////////////////////////////////////////////////////
const initialize = function initialize() {
const state = {
currentState: kState.waiting,
isPullingLever: false,
isTurningWheel: false,
leverTravel: 0,
grateLevel: 0,
leverRotation: '0deg',
graterTravel: '0rem',
wheelRotation: '0deg',
chainOffset: '0%',
load: 0,
};
updateState(state);
// Reset interaction with widgets if we lift a mouse button.
document.addEventListener('mouseup', () => {
send(kAction.stopTurningWheel)
send(kAction.stopPullingLever)
});
// The lever handle initiates interactions with the lever.
document.querySelector('.lever .handle')?.addEventListener('mousedown', () => {
send(kAction.startPullingLever)
});
// The wheel handle initiates interactions with the wheel.
document.querySelector('.wheel .handle')?.addEventListener('mousedown', () => {
send(kAction.startTurningWheel)
});
// Rotation is calculated relatively to the whole wheel.
// And travel based no the full arc of the lever
const $wheel = document.querySelector('.wheel');
const $lever = document.querySelector('.lever');
document.addEventListener('mousemove', (event) => {
if (appState.isTurningWheel && !appState.waitingForFrame) {
requestAnimationFrame(() => {
const elementPosition = $wheel.getBoundingClientRect();
const x = event.clientX - elementPosition.left;
const y = event.clientY - elementPosition.top;
const angle = angleFromCenter(x, y, kDimensions.wheelSize, kDimensions.wheelSize)
const oldAngle = parseFloat(appState.wheelRotation);
const { direction, magnitude } = rotationDirection(oldAngle, angle);
send(kAction.turnWheel, { angle, direction, magnitude });
send(kAction.setIsWaitingForFrame, { state: false })
});
send(kAction.setIsWaitingForFrame, { state: true })
}
if (appState.isPullingLever && !appState.waitingForFrame) {
requestAnimationFrame(() => {
const elementPosition = $lever.getBoundingClientRect();
const x = event.clientX - elementPosition.left;
const magnitude = Math.max(Math.min(x * 100 / kDimensions.leverWidth, 100), 0);
const direction = magnitude >= appState.leverTravel ? kPullDirection.right : kPullDirection.left;
send(kAction.pullLever, { magnitude, direction });
send(kAction.setIsWaitingForFrame, { state: false })
});
send(kAction.setIsWaitingForFrame, { state: true })
}
});
// The URL Grater is the drop area.
const $grater = document.querySelector('.grater-bed');
$grater.addEventListener('dragenter', (event) => {
event.preventDefault();
if (appState.currentState === kState.waiting) {
$grater.classList.add('drag-over');
}
});
$grater.addEventListener('dragover', (event) => event.preventDefault());
$grater.addEventListener('dragleave', (event) => {
if (!$grater.contains(event.relatedTarget)) {
$grater.classList.remove('drag-over');
}
});
$grater.addEventListener('drop', (event) => {
event.preventDefault();
if (appState.currentState !== kState.waiting) {
return;
}
const href = event.dataTransfer.getData('text/uri-list');
const label = event.dataTransfer.getData('text/plain');
$grater.classList.remove('drag-over');
send(kAction.createSausage, { href, label });
});
const params = new URLSearchParams(window.location.search);
const urlFromQuery = params.get("url");
const url = urlFromQuery ?? configuration.defaultUrl;
loadIframe(url, 100);
};
// Utility Functions ///////////////////////////////////////////////////////////
/**
* Calculates the angle of a point (x,y) relative to the center of a square
* with dimensions (w, h). The result is in radians.
*/
const angleFromCenter = function angleFromCenter(x, y, w, h) {
const centerX = w / 2;
const centerY = h / 2;
const dx = x - centerX;
const dy = y - centerY;
return Math.atan2(dy, dx);
};
/**
* Given two angles, calculates the direction of the rotation.
*/
const rotationDirection = function rotationDirection(angle1, angle2) {
let diff = angle2 - angle1;
if (diff > Math.PI) diff -= 2 * Math.PI;
if (diff < -Math.PI) diff += 2 * Math.PI;
const magnitude = Math.abs(diff);
const direction = diff > 0 ? kRotationDirection.counterClockwise : kRotationDirection.clockwise;
return {
direction,
magnitude
};
};
/**
* Loads an iframe and appends it into the body.
*/
const $iframeContainer = document.querySelector('.web-content');
const loadIframe = function loadIframe(url, loadProgress) {
const $iframe = document.createElement('iframe');
$iframe.src = url;
$iframe.style.setProperty('--loadProgress', `${100 - loadProgress}vh`);
$iframeContainer.appendChild($iframe);
$iframe.addEventListener('load', () => {
const iframeDocument = $iframe.contentDocument;
iframeDocument.querySelectorAll('a').forEach(($link) => {
$link.addEventListener('click', (event) => event.preventDefault());
$link.draggable = true;
$link.addEventListener('dragstart', (event) => {
event.dataTransfer.setData('text/uri-list', $link.href);
event.dataTransfer.setData('text/plain', $link.textContent);
const ghost = $link.cloneNode(true);
ghost.style.background = 'url(\'/images/Sausage.svg\')';
ghost.style.backgroundRepeat = 'no-repeat';
ghost.style.cursor = 'grabbing';
ghost.style.display = 'block';
ghost.style.height = '3rem';
ghost.style.width = '8.125rem';
ghost.style.color = '#fff';
ghost.style.textAlign = 'center';
ghost.style.fontSize = '0.625rem';
ghost.style.fontFamily = 'sans-serif';
ghost.style.textTransform = 'uppercase';
ghost.style.paddingTop = '1rem';
document.body.appendChild(ghost);
event.dataTransfer.setDragImage(ghost, 10, 10);
});
});
});
return $iframe;
};
const createSausage = function createSausage(href, label) {
const $sausage = document.createElement('article');
$sausage.classList.add('sausage');
$sausage.dataset.href = href;
$sausage.innerText = label;
const $graterBed = document.querySelector('.grater-bed');
$graterBed?.appendChild($sausage);
};
const removeSausages = function removeSausages() {
const $graterBed = document.querySelector('.grater-bed');
const $existingSausages = $graterBed.querySelectorAll('.sausage');
for (const $existingSausage of $existingSausages) {
$graterBed?.removeChild($existingSausage);
}
}
initialize();