Ruben Beltran del Rio 2026-01-22 12:59:10 +01:00
parent c4bf944f93
commit 87d65c599e
No known key found for this signature in database
4 changed files with 16528 additions and 2130 deletions

View File

@ -1,4 +1,7 @@
* { margin: 0; padding: 0 }
* {
margin: 0;
padding: 0;
}
body {
overflow: hidden;
@ -16,7 +19,7 @@ iframe {
/* The bed of the Machine */
.machine {
background: url('/images/Metal Panel.svg');
background: url("/images/Metal Panel.svg");
background-repeat: repeat-x;
bottom: 0;
height: 4rem;
@ -29,10 +32,12 @@ iframe {
label {
background-color: #202123;
text-shadow: 1px -1px 0px #42464dFF, -1px 1px 0px #ffffff1A;
text-shadow:
1px -1px 0px #42464dff,
-1px 1px 0px #ffffff1a;
color: #eeeeee;
display: block;
font: 0.625rem 'Arial Rounded MT Bold';
font: 0.625rem "Arial Rounded MT Bold";
padding: 0.0625rem 0.3125rem;
text-transform: uppercase;
transform: rotate(-5deg);
@ -47,7 +52,7 @@ label {
top: 0;
.grater-bed {
background: url('/images/Grater Bed.svg');
background: url("/images/Grater Bed.svg");
background-repeat: no-repeat;
background-position: bottom;
position: absolute;
@ -56,9 +61,9 @@ label {
width: 8.5rem;
&::before {
background: url('/images/Sausage.svg');
background: url("/images/Sausage.svg");
background-repeat: no-repeat;
content: ' ';
content: " ";
display: block;
height: 3em;
opacity: 0;
@ -72,7 +77,7 @@ label {
}
.grater-blade {
background: url('/images/Grater Blade.svg');
background: url("/images/Grater Blade.svg");
height: 0.625rem;
left: var(--graterTravel);
position: absolute;
@ -88,7 +93,7 @@ label {
width: 6.125rem;
.stem {
background: url('/images/Lever Handle.svg');
background: url("/images/Lever Handle.svg");
height: 3.4375rem;
position: absolute;
width: 3.0625rem;
@ -107,9 +112,9 @@ label {
}
&::before {
background: url('/images/Lever Back.svg');
background: url("/images/Lever Back.svg");
bottom: -0.5rem;
content: '';
content: "";
height: 1.375rem;
left: 2.25rem;
position: absolute;
@ -117,9 +122,9 @@ label {
}
&::after {
background: url('/images/Lever Front.svg');
background: url("/images/Lever Front.svg");
bottom: -0.5rem;
content: '';
content: "";
height: 1.0625rem;
left: 2.40625rem;
position: absolute;
@ -149,22 +154,22 @@ label {
}
.ready-to-grate {
background: url('/images/Red Unlit Light.svg');
background: url("/images/Red Unlit Light.svg");
}
.ready-to-load {
background: url('/images/Green Unlit Light.svg');
background: url("/images/Green Unlit Light.svg");
}
[data-current-state="readyToGrate"] .ready-to-grate {
background: url('/images/Red Lit Light.svg');
background: url("/images/Red Lit Light.svg");
}
[data-current-state="readyToLoad"] .ready-to-load {
background: url('/images/Green Lit Light.svg');
background: url("/images/Green Lit Light.svg");
}
/* The Link Loader Wheel and Chain Mechanism */
.chain {
background: url('/images/Chain Link.svg');
background: url("/images/Chain Link.svg");
background-position-y: var(--chainOffset);
background-repeat: repeat-y;
height: 100vh;
@ -184,7 +189,7 @@ label {
width: 6rem;
.body {
background: url('/images/Wheel.svg');
background: url("/images/Wheel.svg");
height: 6rem;
left: 0;
position: absolute;
@ -212,27 +217,28 @@ label {
/* Make a Sausage */
.sausage {
background: url('/images/Sausage.svg');
align-items: center;
background: url("/images/Sausage.svg");
background-position: top;
background-repeat: no-repeat;
color: #fff;
display: block;
font: 0.625rem 'Arial Rounded MT Bold';
display: flex;
font: 0.625rem "Arial Rounded MT Bold";
height: calc(3rem - var(--grateLevel));
justify-content: center;
overflow: hidden;
position: absolute;
top: var(--grateLevel);
left: 0;
text-align: center;
text-overflow: ellipsis;
text-transform: uppercase;
width: 8.125rem;
white-space: nowrap;
z-index: 0;
display: flex;
align-items: center;
justify-content: center;
}
/* Util */
.no-select {
user-select: none
user-select: none;
}

18027
example.html

File diff suppressed because one or more lines are too long

View File

@ -1,23 +1,22 @@
<!DOCTYPE HTML>
<!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 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">
<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>
<main class="web-content"></main>
<nav class="machine">
<section class="link-grater">
<article class="grater-bed">
@ -42,7 +41,7 @@
</article>
</article>
<label>URL Loader</label>
</section
</section>
</nav>
</body>
</html>

View File

@ -5,90 +5,147 @@ const kDimensions = {
sausageHeight: 48,
};
const kState = {
waiting: 'waiting',
readyToGrate: 'readyToGrate',
readyToLoad: 'readyToLoad'
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'),
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'),
clockwise: Symbol("clockwise"),
counterClockwise: Symbol("counterClockwise"),
};
const kPullDirection = {
right: Symbol('right'),
left: Symbol('left'),
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'
};
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;
/**
* The main app state
*/
let appState = null;
/**
* Functions to handle side effects
*/
const middleware = [
function disableSelect(_, action) {
if (action === kAction.startTurningWheel || action === kAction.startPullingLever) {
document.querySelector('.machine')?.classList.add('no-select');
}
},
function enableSelect(_, action) {
if (action === kAction.stopTurningWheel || action === kAction.stopPullingLever) {
document.querySelector('.machine')?.classList.remove('no-select');
}
},
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);
* Functions to handle side effects: Creation or removal of HTML elements,
* actions that occur as a result of a change in state.
*/
const middleware = [
/**
* Makes text unselectable while we move the lever or wheel.
*/
function disableSelect(_, action) {
if (
action === kAction.startTurningWheel ||
action === kAction.startPullingLever
) {
document.querySelector(".machine")?.classList.add("no-select");
}
];
},
const $stateContainer = document.querySelector('.machine');
/**
* Makes text selectable when we're done moving the lever or wheel.
*/
function enableSelect(_, action) {
if (
action === kAction.stopTurningWheel ||
action === kAction.stopPullingLever
) {
document.querySelector(".machine")?.classList.remove("no-select");
}
},
/**
* Cleans up the old iFrames when we load the new one, and triggers a complete.
*/
function completeLoading(state, action) {
if (
action === kAction.turnWheel &&
state.currentState === kState.readyToLoad &&
parseFloat(state.load) >= configuration.maxLoad
) {
const iframes = $iframeContainer.querySelectorAll("iframe");
for (const iframe of iframes) {
if (iframe !== appState.currentIframe) {
$iframeContainer.removeChild(iframe);
}
}
send(kAction.completeLoading);
}
},
/**
* Sets the individual load state to the current iframe.
*/
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`,
);
}
},
/**
* Cleans up the sausage and loads the iframe once the grating is complete.
*/
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 });
}
},
/**
* Creates the sausage element if we are at the right state.
*/
function createSausageIfWaiting(state, action, data) {
if (
action === kAction.createSausage &&
state.currentState === kState.waiting
) {
createSausage(data.href, data.label);
}
},
];
/**
* We store the state as CSS variables and data attributes in an element
* declared as the $stateContainer. UI updates as a result of this. We
* try to avoid manipulating the elements with JS, except for creating and
* removing elements.
*/
const $stateContainer = document.querySelector(".machine");
const updateState = function updateState(state) {
for (const [key, value] of Object.entries(state)) {
$stateContainer.style.setProperty(`--${key}`, value);
@ -97,6 +154,9 @@ const updateState = function updateState(state) {
appState = state;
};
/**
* We modify a copy of the state and return it.
*/
const reduce = function reduce(stateReference, action, data) {
let state = Object.assign({}, stateReference);
@ -105,10 +165,18 @@ const reduce = function reduce(stateReference, action, data) {
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);
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;
const chainOffset =
parseFloat(state.chainOffset) +
sign * data.magnitude * configuration.chainSpeed;
state.chainOffset = `${chainOffset}%`;
}
}
@ -116,18 +184,21 @@ const reduce = function reduce(stateReference, action, data) {
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`
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);
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.grateLevel = "0%";
state.currentState = kState.readyToGrate;
break;
case kAction.startTurningWheel:
@ -144,12 +215,13 @@ const reduce = function reduce(stateReference, action, data) {
break;
case kAction.completeLoading:
state.currentState = kState.waiting;
state.currentIframe = null;
state.load = 0;
break;
case kAction.completeGrating:
state.currentState = kState.readyToLoad;
state.currentIframe = data.iframe;
state.grateLevel = '0%';
state.grateLevel = "0%";
state.load = 0;
break;
}
@ -158,132 +230,149 @@ const reduce = function reduce(stateReference, action, data) {
};
/**
* 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);
};
* 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);
/**
* Initializes state and events.
*/
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);
};
/**
* 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.
*/
/**
* Given two angles, calculates the direction of the rotation.
*/
const rotationDirection = function rotationDirection(angle1, angle2) {
let diff = angle2 - angle1;
@ -291,46 +380,49 @@ const rotationDirection = function rotationDirection(angle1, angle2) {
if (diff < -Math.PI) diff += 2 * Math.PI;
const magnitude = Math.abs(diff);
const direction = diff > 0 ? kRotationDirection.counterClockwise : kRotationDirection.clockwise;
const direction =
diff > 0
? kRotationDirection.counterClockwise
: kRotationDirection.clockwise;
return {
direction,
magnitude
magnitude,
};
};
/**
* Loads an iframe and appends it into the body.
*/
const $iframeContainer = document.querySelector('.web-content');
/**
* 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');
const $iframe = document.createElement("iframe");
$iframe.src = url;
$iframe.style.setProperty('--loadProgress', `${100 - loadProgress}vh`);
$iframe.style.setProperty("--loadProgress", `${100 - loadProgress}vh`);
$iframeContainer.appendChild($iframe);
$iframe.addEventListener('load', () => {
$iframe.addEventListener("load", () => {
const iframeDocument = $iframe.contentDocument;
iframeDocument.querySelectorAll('a').forEach(($link) => {
$link.addEventListener('click', (event) => event.preventDefault());
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);
$link.addEventListener("dragstart", (event) => {
event.dataTransfer.setData("text/uri-list", $link.href);
event.dataTransfer.setData("text/plain", $link.textContent.trim());
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';
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);
@ -341,21 +433,27 @@ const loadIframe = function loadIframe(url, loadProgress) {
return $iframe;
};
/**
* Creates a sausage link for grating.
*/
const createSausage = function createSausage(href, label) {
const $sausage = document.createElement('article');
$sausage.classList.add('sausage');
const $sausage = document.createElement("article");
$sausage.classList.add("sausage");
$sausage.dataset.href = href;
$sausage.innerText = label;
const $graterBed = document.querySelector('.grater-bed');
const $graterBed = document.querySelector(".grater-bed");
$graterBed?.appendChild($sausage);
};
/**
* Removes a completely grated sausage link.
*/
const removeSausages = function removeSausages() {
const $graterBed = document.querySelector('.grater-bed');
const $existingSausages = $graterBed.querySelectorAll('.sausage');
const $graterBed = document.querySelector(".grater-bed");
const $existingSausages = $graterBed.querySelectorAll(".sausage");
for (const $existingSausage of $existingSausages) {
$graterBed?.removeChild($existingSausage);
}
}
};
initialize();