Picture by creator
explores how browser-based computational notebooks — notably the WLJS Pocket book — can rework static slides into dynamic, real-time experiences. This strategy isn’t restricted to shows; you’ll be able to put together interactive lecture notes for college kids or colleagues and publish it on internet. For information scientists, physicists, it highlights new methods to speak fashions, simulations, and visualizations, making advanced concepts extra intuitive and interesting.
Is a PDF Sufficient?
Animations, bells and whistles, particularly the type that have been fashionable in PowerPoint 15–20 years in the past, have largely taken a backseat. Add to this the compatibility points between LibreOffice and MS Workplace (even between variations for Home windows and Mac), the presence or absence of crucial fonts — and the need to do one thing uncommon on the “stage” fades away rapidly.
Take a look at fashionable technical shows: very often, it’s only a PDF doc consisting of pages with vector and raster graphics, and generally GIF animations that eat up megabytes (like this put up), with no mercy.
Unused Potential
It’s value separating ornamental bells and whistles from those who carry extra data in some media format. For instance, check out ECMA-363 [1] specification.
A 3D mannequin inside a PDF doc merely enhances the person/viewer expertise. You observe the article from totally different angles/cross-sections.
It’s disappointing that such a function is sort of nowhere supported aside from Adobe Acrobat and sure is not going to be. It looks like we made a leap previously, however now we have now returned to static slides.
Massive Scientific Convention DPG
DPG-Frühjahrstagung is a big European physics convention organized by the German Bodily Society (DPG) [2]. Yearly, they collect greater than 10^4
scientists and happen in German cities, masking an unlimited array of physics fields.


There are such a lot of shows, and it lasts virtually every week, so by the top, it turns into too overwhelming. Nonetheless, this doesn’t diminish its worth as a platform for networking, working towards shows, and a dependable method to study what’s at present in the marketplace, which trains have gone already, and that are simply departing.
The contributors in plenary classes are largely Grasp’s college students and PhD college students, with postdocs being rarer.
Such a big and accessible platform is a wonderful motivation to strive one thing new 💡 even when one thing could go unsuitable.
What’s a JoyCon?
Absolutely, the reader has seen units like this:

This machine acts as a slide switcher and generally as a laser pointer, connecting through Bluetooth or via a dongle. In any case, it’s a sort of controller with buttons. Controllers will be extra fascinating — just like the one from the 2017 Nintendo Change handheld console

It’s not a lot larger, but it surely has some extra cool options:
- Analog stick 🕹️
- 11 buttons ☎️
- IR digital camera 📸 (tough to make use of, no good API documentation)
- Full IMU 🌐 (Inertial Measurement Unit) aka gyroscope with an accelerometer
- Bluetooth connectivity; acknowledged as an everyday HID
The buttons can certainly be mapped to PowerPoint, or the stick can be utilized to regulate slides, emulating mouse or keyboard clicks, because it was applied in these initiatives:
I believed it will be cool to one way or the other use the IMU and analog stick. However for that, one would want to transcend PowerPoint and PDF 🧙🏼♂️
Shifting Slides to Browser Atmosphere
The concept behind this isn’t new, but it surely’s essential to keep in mind that this strategy could not work for everybody. Nonetheless, by shifting the presentation show and creation to the browser (notably Javascript and HTML lands), we robotically achieve entry to all the chances of recent internet expertise: peripheral machine assist, JavaScript, CSS animation magic, and rather more, together with video. It’s essential to notice that each one of that is cross-platform by default and can work virtually in every single place.
For instance, slides will be created in Markdown (or/and HTML) with the assistance of a easy framework (fairly, a small library) RevealJS [5]
There’s additionally MDX-based presentation engines, and issues like Manim [6], Movement Canvas [7], however these guys require much more abilities to grasp.
The RevealJS API is kind of easy, so controlling the slides through JavaScript instructions is straightforward to implement:
setTimeout(() =>{
Reveal.navigateNext(1);
}, 1000)
Nonetheless, this direct strategy has important drawbacks. It requires an web connection, and when you’d desire to keep away from it, you’ll want to make use of bundlers (similar to Rollup) and embed all JavaScript libraries right into a single HTML file, as an example. Alternatively, you possibly can run an area internet server.
Possibility with Jupyter Pocket book
In the event you like Python and IPYNB, then use nbconvert — it should convert your pocket book immediately right into a RevealJS presentation, and also you received’t even discover it! Or use the extension for Jupyter—RISE [8]

In any case, the concept stays easy — we have to one way or the other enter the online browser setting to benefit from all the chances of JoyCon.
Attempt it on Binder!
Possibility with WLJS Pocket book
My opinion on WLJS [9] may be considerably biased as I’m one in all its builders (and energetic customers). This open-source IDE with a pocket book interface is extra tightly built-in with the online setting, as slides aren’t exported there however are as a substitute executed and are simply one other sort of output cell, alongside the acquainted Markdown.

Underneath the hood, it additionally makes use of RevealJS however with a number of additional options:
- It really works offline
- It permits embedding interactive components and parts, just like LaTeX Beamer
- It’s built-in with Wolfram Language (freeware distribution)
See extra about it in this story [10]. An final information on make presentation there we revealed in our official weblog: Dynamic Presentation, or How to Code a Slide with Markdown and WL [11].
Let’s Dive into JoyCon
So, the simplest choice is to make use of the already ready-made library joy-con-webhid [12]. Why spend time reinventing the wheel when folks have already performed a terrific job for us?
npm set up joy-con-webhid --prefix .
All subsequent examples will likely be taken from the WLJS Pocket book. Nonetheless, you are able to do just about the identical factor utilizing Python + FAST API to interface with JavaScript or one thing comparable, and even simply use JS alone. The net model of the pocket book is on the market here [13].
First, let’s take heed to what’s coming from the controller port.
Code
.esm
import { connectJoyCon, connectedJoyCons } from 'joy-con-webhid';
// Create join button
const connectButton = doc.createElement('button');
connectButton.className = 'relative cursor-pointer rounded-md h-6 pl-3 pr-2 text-left text-gray-500 focus:outline-none ring-1 sm:text-xs sm:leading-6 bg-gray-100';
connectButton.innerText = "Join";
let connectionState = "Join";
let isJoyConConnected = false;
let lastUpdateTime = efficiency.now();
let isAllowedToConnect = false;
// essential handler operate (warning! known as at 60FPS)
operate handleJoyConInput(element) {
const currentTime = efficiency.now();
if (currentTime - lastUpdateTime > 50) { // decelerate
lastUpdateTime = currentTime;
console.log(element);
}
}
// JoyCon periodically goes to sleep, we have to wake it up
const connectionCheckInterval = setInterval(async () => {
if (!isAllowedToConnect) return;
const connectedDevices = connectedJoyCons.values();
isJoyConConnected = false;
for (const joyCon of connectedDevices) {
isJoyConConnected = true;
if (joyCon.eventListenerAttached) proceed;
await joyCon.open();
await joyCon.enableStandardFullMode();
await joyCon.enableIMUMode();
await joyCon.enableVibration();
await joyCon.rumble(600, 600, 0.5);
joyCon.addEventListener('hidinput', ({ element }) => handleJoyConInput(element));
joyCon.eventListenerAttached = true;
}
updateConnectionState();
}, 2000);
// Replace button state
operate updateConnectionState() {
if (isJoyConConnected && connectionState !== "Related") {
connectionState = "Related";
connectButton.innerText = connectionState;
connectButton.fashion.background = '#d8ffd8';
} else if (!isJoyConConnected && connectionState !== "Join") {
connectionState = "Join";
connectButton.innerText = connectionState;
connectButton.fashion.background = '';
}
}
// Deal with click on occasion
connectButton.addEventListener('click on', async () => {
isAllowedToConnect = true;
if (!isJoyConConnected) {
await connectJoyCon();
}
});
// Simply decorations
const container = doc.createElement('div');
container.innerHTML = `Presenter controller`;
container.appendChild(connectButton);
container.className = 'flex flex-col gap-y-2 bg-white rounded-md shadow-md';
// Return DOM aspect to the web page
this.return(container);
// When a cell acquired eliminated
this.ondestroy(() => {
cancelInterval(connectionCheckInterval);
});
Crucial operate right here is:
operate handleJoyConInput(element) {
const currentTime = efficiency.now();
if (currentTime - lastUpdateTime > 50) { // decelerate
lastUpdateTime = currentTime;
console.log(element); //output to the console
}
}
It appears to be like like there are a lot of steps to do. In actuality, most of this code offers with connecting the controller and drawing a big “Join” button. Don’t pay an excessive amount of consideration to the particular strategies — they’ll simply get replaced with these obtainable in your particular setting:
this.return(dom)
passes aDOMElement
for embedding on the web pagethis.ondestroy(operate)
callsoperate
when the cell is deleted, to scrub up timers, and so on.- The primary line
.esm
is a method to specify the JavaScript cell subtype in WLJS Pocket book, which requires pre-bundling.
Once we run this code cell, we are going to see the next:

Then observe these steps:
- Disconnect the controller from the Nintendo Change (System → Controllers → Disconnect).
- Pair the JoyCon (R) with the PC by holding the small button on the facet.
- Press “Join” on our presenter controller.
Opening the browser console, we reveal the next messages:
{
"buttonStatus": {
"y": false,
"x": false,
"b": false,
"a": false,
"r": false,
"zr": false,
"sr": false,
"sl": false,
"plus": false,
"rightStick": false,
"dwelling": false,
},
"analogStickRight": {
"horizontal": "0.1",
"vertical": "0.3"
},
"actualAccelerometer": {
"x": 0,
"y": 0,
"z": 0
},
"actualGyroscope": {
"dps": {
"x": 0,
"y": 0,
"z": 0
},
"rps": {
"x": 0,
"y": 0,
"z": 0
}
}
}
Various information! Let’s strive utilizing this for the advantage of our presentation 💡
Buttons ☎️
To start, we will use two buttons to modify slides

Within the WLJS pocket book, slides will also be managed programmatically via a Wolfram wrapper operate that calls the RevealJS API.
FrontSlidesSelected["navigateNext", 1] // FrontSubmit
All that’s left is to set off this operate on the proper second when the button (or change) is clicked. To do that, occasions have to be despatched from the Javascript world to the Wolfram machine, the place we will then do no matter we wish with them. This leads to the next diagram:

You don’t have to consider this, since it’s seamlessly applied through APIs
Let’s return to the code cell and modify the handler.
Code
//....
//.......
const buttonStates = { //all buttons states on JoyCon (R)
a: false, b: false, dwelling: false, plus: false, r: false, sl: false, sr: false,
x: false, y: false, zr: false
};
const joystickPosition = [0.0, 0.0];
let restingJoystick = [0.0, 0.0];
let isCalibrated = false;
operate handleJoyConInput(element) {
if (!isCalibrated) { //calibration
restingJoystick = [Number(detail.analogStickRight.horizontal), Number(detail.analogStickRight.vertical)];
isCalibrated = true;
return;
}
const currentTime = efficiency.now();
if (currentTime - lastUpdateTime > 50) {
lastUpdateTime = currentTime;
let buttonPressed = false;
let joystickMoved = false;
for (const key of Object.keys(buttonStates)) {
if (!buttonStates[key] && element.buttonStatus[key]) buttonPressed = true;
buttonStates[key] = element.buttonStatus[key];
}
const verticalOffset = Quantity(element.analogStickRight.vertical) - restingJoystick[1];
const horizontalOffset = Quantity(element.analogStickRight.horizontal) - restingJoystick[0];
if (Math.abs(verticalOffset) > 0.1 || Math.abs(horizontalOffset) > 0.1) {
joystickMoved = true;
}
joystickPosition[0] = horizontalOffset;
joystickPosition[1] = -verticalOffset;
if (buttonPressed) {
for (const key of Object.keys(buttonStates)) {
if (buttonStates[key]) {
server.kernel.io.fireplace('JoyCon', true, key);
break;
}
}
}
if (joystickMoved) {
server.kernel.io.fireplace('JoyCon', joystickPosition, 'Stick');
}
}
}
//.......
//..
As you’ll be able to see, we have now added a number of objects right here:
- Joystick calibration — analog sticks drift, so their digital place is rarely good
0.,0.
. - State of all buttons — why hammer the door each time when you solely want to softly knock when the state adjustments? This reduces system stress.
- Sending states to the occasion pool — that is particular to WLJS, the place we ship information to the Wolfram machine (or Python when you’re in Jupyter).
The final level appears to be like like this (change with the equal in your setting):
server.kernel.io.fireplace(String title, Object state, String sample);
Then, on the Wolfram facet, we will simply subscribe to those occasions like this
EventHandler["name", {
"pattern" -> Function[state,
Print[state];
]
}]
That is very handy, as Javascript sends the names of the pressed buttons because the sample. On this case, you’ll be able to instantly subscribe to slip switching, for instance, like this:
- ZR — subsequent slide
- Y — again
Thus, programmatically controlling slides turns into intuitive:
EventHandler["JoyCon", {
"zr" -> (FrontSubmit[FrontSlidesSelected["navigateNext", 1]]&),
"y" -> (FrontSubmit[FrontSlidesSelected["navigatePrev", 1]]&)
}];
Let’s Take a look at in Observe
Let’s create a easy presentation. Begin typing with
.slide
# Slide 1
__Hey Medium!__
---

Now, let’s join the JoyCon to the PC and hyperlink it to our Javascript script by urgent the Join button. Then, subscribe to the occasions as soon as within the energetic session.
Now, simply run the cell with the slides:

Analog Stick 🕹️
The stick theoretically permits controlling two sliders concurrently. For the DPG Spring Conferences, I had the concept of a reside demonstration of a really peculiar impact m𝒶𝑔𝒾c𝑎𝓁 𝓌𝑜𝓇𝒹𝓈 𝒻𝓇𝑜𝓂 𝓅𝒽𝓎𝓈𝒾𝒸𝓈. I imagine that some ideas are rather more impactful and understandable when demonstrated reside on stage.
Here’s a condensed code snippet for the interactive widget:
FaradayWidget := ManipulatePlot[
Abs[(E^(I w (-1 + Sqrt[1 + (f/((-I g - w) w + (d - w0)^2))])) + E^(I w (-1 + Sqrt[1 + (f/((-I g - w) w + (d + w0)^2))]))) /. {g -> 0.694, w0 -> 50.0}]
, {w, 20, 80}, {{f,2},0,100,1}, {{d,0},0,10,1}
, FrameLabel->{"wavenumber", "transmission"}
, Body->True
];
FaradayWidget
The interactive on-line model of this widget is available here [14]

To embed it in a slide, insert its image as a tag (just like JSX):
.slide
# Faraday Widget
Right here it's in motion
Now, let’s hyperlink it to our stick

To start with, let’s carry out a easy check and bind its place to a disk on the display:
pos = {0.,0.};
EventHandler["JoyCon", {"Stick" -> ((pos = #)&)}];
Graphics[{
Circle[{0,0}, 2.],
Disk[pos // Offload, 0.1]
}]

Clearly, the actions are too abrupt. Furthermore, making small changes is kinda painful utilizing JoyCon. The answer? Integration!
EventHandler["JoyCon", {"Stick" -> ((pos += 0.1 #)&)}];

Now, let’s hyperlink the pos
variable to the sliders of our widget:
FaradayWidget := ManipulatePlot[
Abs[(E^(I w (-1 + Sqrt[1 + (f/((-I g - w) w + (d - w0)^2))])) + E^(I w (-1 + Sqrt[1 + (f/((-I g - w) w + (d + w0)^2))]))) /. {g -> 0.694, w0 -> 50.0}]
, {w, 20, 80}, {{f,2},0,100,1}, {{d,0},0,10,1}
, FrameLabel->{"wavenumber", "transmission"}
, Body->True
, "TrackedExpression" -> Offload[5 pos] (*
Right here’s the way it appears to be like reside on a slide:

And within the precise DPG presentation:

A Second of Relaxation
Final 12 months, DPG befell in Berlin, and this 12 months — in Regensburg, which has about 23 occasions fewer inhabitants and is 10 occasions smaller in space. Nonetheless, the comfortable lands of Bavaria have at all times been nearer to my ❤️

And that is the college. A strong 60s-style constructing. Wha 💪🏻

A brand new invention — a cup “Drink and Eat Me”

As a bonus, each drink will get a touch of waffle taste! However, be careful — don’t chew into it whereas it’s full of sizzling tea.
I couldn’t take extra photographs since I acquired sick on the primary day and went again dwelling to Augsburg. Typically, spending six days at a convention is kind of difficult.

Again to enterprise 🐏
IMU or Gyroscope-Accelerometer Mixture 🌐
To make use of them, we have to learn the corresponding fields from the particulars
object, specifically:
- actualAccelerometer: x, y, z
- actualGyroscope: rps (radians per second)
Code
//..
//....
const buttonStates = {
a: false, b: false, dwelling: false, plus: false, r: false, sl: false, sr: false,
x: false, y: false, zr: false
};
const joystickPosition = [0.0, 0.0];
let restingJoystick = [0.0, 0.0];
let isCalibrated = false;
let imuEnabled = false;
// Allow IMU mode if allowed
core.JoyConIMU = async (args, env) => {
imuEnabled = await interpretate(args[0], env);
};
// Perform to deal with Pleasure-Con enter
operate handleJoyConInput(element) {
if (!isCalibrated) {
restingJoystick = [Number(detail.analogStickRight.horizontal), Number(detail.analogStickRight.vertical)];
isCalibrated = true;
return;
}
const currentTime = efficiency.now();
if (currentTime - lastUpdateTime > 50) { // Replace each 50ms
lastUpdateTime = currentTime;
let buttonPressed = false;
let joystickMoved = false;
for (const key of Object.keys(buttonStates)) {
if (!buttonStates[key] && element.buttonStatus[key]) buttonPressed = true;
buttonStates[key] = element.buttonStatus[key];
}
const verticalOffset = Quantity(element.analogStickRight.vertical) - restingJoystick[1];
const horizontalOffset = Quantity(element.analogStickRight.horizontal) - restingJoystick[0];
if (Math.abs(verticalOffset) > 0.1 || Math.abs(horizontalOffset) > 0.1) {
joystickMoved = true;
}
joystickPosition[0] = horizontalOffset;
joystickPosition[1] = -verticalOffset;
if (imuEnabled) {
server.kernel.io.fireplace('JoyCon', {
'Accelerometer': Object.values(element.actualAccelerometer),
'Gyroscope': Object.values(element.actualGyroscope.dps)
}, 'IMU');
}
if (buttonPressed) {
for (const key of Object.keys(buttonStates)) {
if (buttonStates[key]) {
server.kernel.io.fireplace('JoyCon', true, key);
break;
}
}
}
if (joystickMoved) {
server.kernel.io.fireplace('JoyCon', joystickPosition, 'Stick');
}
}
}
//....
//..
Since IMU will not be at all times wanted, the script features a boolean variable and a management operate JoyConIMU[True | False]
, permitting IMU measurements to be enabled or disabled.
The JoyCon, like most different units with IMU (some smartphones, watches, however undoubtedly not VR headsets or quadcopters), consists of:
- 3-axis gyroscope — returns angular velocity in rad/sec round all three axes
- 3-axis accelerometer — returns a single acceleration vector
Query: Why can’t we use solely a gyroscope or an accelerometer?
Let’s strive outputting each. First, allow IMU utilization:
JoyConIMU[True] // FrontSubmit;
Now, outline auxiliary capabilities and variables:
prevTime = AbsoluteTime[];
angles = {0,0,0};
acceleration = {0,0,-1};
course of[imu_] := With[{time = AbsoluteTime[]},
With[{dt = time - prevTime},
angles = (angles + {-1,1,1} imu["Gyroscope"][[{3,1,2}]] dt);
acceleration = imu["Accelerometer"];
prevTime = time;
]
]
What occurs right here:
- The accelerometer vector is just saved in
acceleration
. - Gyroscope information is processed by:
- Reordering angular velocity values (JoyCon {hardware} orientation) and adjusting instructions.
- Integrating over time to acquire orientation angles
Consequently, we receive:
- Three angles defining JoyCon orientation
angles
. - One acceleration vector (at relaxation — the gravity path)
acceleration
.
These three angles are conveniently expressed as a matrix (tensor):
RollPitchYawMatrix[{[Alpha], [Beta], [Gamma]}] // MatrixForm
Making use of this matrix to any 3D object permits it to be oriented based on these angles. Bodily, on the JoyCon, it appears to be like like this:

You will need to observe that since we measure solely the primary by-product (utilizing Gyro), then the preliminary IMU orientation stays unknown. Subsequently, we manually set the preliminary state, i.e.
angles = {0., 0., 0}
EventHandler["JoyCon", {
"IMU" -> Function[val,
process[val];
]
}];
angles = {0,0,0}; (* calibration *)
Refresh[acceleration, 0.25] (* dynamically replace *)
Refresh[angles, 0.25] (* dynamically replace *)
Actual-time Information Output:

Nicely… Not fairly apparent what these values imply. Let’s strive to attract then as vectors in 3D area:
axis = Desk[{{0.,0.,0.}, Table[1.0 KroneckerDelta[i, j], {i,3}]}, {j,3}];
EventHandler["JoyCon", {
"IMU" -> Function[val,
process[val];
axis[[1]] = {{0.,0.,0.}, RollPitchYawMatrix[angles].{0,1.0,0.0}};
axis[[2]] = {{0.,0.,0.}, RollPitchYawMatrix[angles].{-1.0,0.0,0}};
axis[[3]] = {{0.,0.,0.}, -Normalize[acceleration][[{2,1,3}]]};
axis = axis;
]
}];
After which render them as coloured cones, the place:
- Blue and purple — defines angles derived from the gyroscope information
- Inexperienced — accelerometer information (inverted and normalized)
{
{Opacity[0.2], Sphere[]},
Purple, Tube[axis[[1]]//Offload, {0.2, 0.01}],
Blue, Tube[axis[[2]]//Offload, {0.2, 0.01}],
Inexperienced, Tube[axis[[3]]//Offload, {0.2, 0.01}]
} // Graphics3D
EventHandler[InputButton["Reset"], Perform[Null, angles *= .0]]

The inexperienced vector is at all times aligned “accurately,” whereas the blue and purple vectors, representing gyroscope angles, accumulate errors over time, particularly with speedy actions, inflicting drift.
There are various methods to unravel this problem. The overall thought is to regulate the angles utilizing accelerometer information (inexperienced vector), because the accelerometer exactly determines the downward path ( till an exterior drive disturbs it).
For a extra detailed rationalization, take a look at a terrific video by James Lambert [15], which explores these issues and their options, together with a detailed example with Oculus DK1.
Why the heck do we’d like this within the presentation?
I requested myself this query once I found how deep the rabbit gap goes. In my discuss on the magnetism session, there was precisely one slide the place the concept of an IMU made any sense:

Do you see the crystalline construction? Discovering a “good” digital camera angle for it’s certainly tough, so why not rotate it immediately, energetic? We don’t want all three angles and acceleration-just one will suffice.
Thus, we discard the accelerometer and maintain solely the gyroscope:
FrontSubmit[JoyConIMU[True]];
timestamp = AbsoluteTime[];
angle = 0.;
rotation = RotationMatrix[angle, {0,0,1.0}];
EventHandler["JoyCon", {
"IMU" -> Function[val,
With[{angularSpeed = val["Gyroscope"][[1]], time = AbsoluteTime[], oldAngle = angle},
angle += (time - timestamp) angularSpeed;
timestamp = time;
];
rotation = RotationMatrix[angle, {0,0,1.0}];
]
}];
Now, we simply want to use the rotation
transformation tensor to our 3D construction. Since this crystal incorporates many ions, that are additionally coloured, I compressed them into base64
base64 code
CrystalRawStructure = "1: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" // Uncompress ;
CrystalStructure = Graphics3D[
GeometricTransformation[CrystalRawStructure, rotation // Offload]
, ViewPoint->3.5{1.0,0.5,0.5}
, ImageSize->{550,600}
]
Let’s embed it into our slide:
.slide
# Slide
Right here is my crystal construction!

To make it much more handy, it will be helpful to subscribe to IMU solely when the slide is energetic and unsubscribe when leaving it. That is simple to do as a result of RevealJS indicators the core on slide state change occasions. Let’s implement this as a part utilizing .wlx
cell sort:
.wlx
InteractiveCrystalStructure := Module[{rotation = RotationMatrix[1Degree, {0,0,1.0}], id = CreateUUID[], timestamp = AbsoluteTime[], angle = 0., CrystalStructure},
CrystalStructure = Graphics3D[
GeometricTransformation[CrystalRawStructure, rotation // Offload]
, ViewPoint->3.5{1.0,0.5,0.5}
, ImageSize->{550,600}
];
EventHandler[id, {
"Slide" -> Function[Null,
FrontSubmit[JoyConIMU[True]];
EventHandler["JoyCon", {
"IMU" -> Function[val,
With[{angularSpeed = val["Gyroscope"][[1]], time = AbsoluteTime[], oldAngle = angle},
angle += (time - timestamp) angularSpeed;
timestamp = time;
];
rotation = RotationMatrix[angle, {0,0,1.0}];
]
}];
],
("Destroy" | "Left") -> Perform[Null,
FrontSubmit[JoyConIMU[False]];
]
}];
]
By putting this code on any slide, we obtain the specified end result with out polluting the worldwide area or interfering with different occasion handlers. Thus, IMU subscription administration is localized to the precise slide, and when switching slides, we accurately allow and disable information dealing with with out affecting different slides and their processing
.slide
# Earlier than
---
# Slide
Right here is my crystal construction!
---
# After
These are precise slides from DPG2025:

Quick video with my DPG2025 slides
Ultimate Code and Pocket book
The compiled presenter controller cell code is offered below the spoiler. If inserted into an empty cell, it should produce a useful widget for connecting a JoyCon.
Compressed cells
jsfc4uri1percent3AeJztfWtz28aWoGtmdqdq99NU7Q9oa3ZiUiJpAqRkmYqccWTnxlNx4vXjfvGqHJBoSkhAgAJAiaSjpercent2FRf7afpercent2FsnnO6Gpercent2BhuNEjKj3vvVN1UTAHd531Ovxpercent2B4P05fTpercent2Fpercent2Fh3r17percent2BTpercent2FBz09RXkzpercent2FEdpercent2Fpercent2BOpercent2Fw8zfN0EgVFlCYVyOtFzNpercent2Fgw7OgCN78v3percent2B5d6percent2FH89npercent2F7ntpercent2BNJunWcEpercent2BskmaJHxSpercent2FEe6OkuTjnrloUjI2S2bZumMPfgtXXUhs3vDx5dRpercent2BOAEieCpercent2Fhwpercent2FZWcaDgrMgCVlerGLOikuuCIE4bLwoijRBYEjMC5X1PSWzUxamk8WMJ0VvQoSexxzfWg8E3oP2iUStkHqTOMjzn4MZBpercent2FQHGY9B72vgucjyNOvO0ygpeMaydJGEPOzOQnbZPWLzuDtg86zrs4Ivi27Mp4V4usiCVfew32dTECQfpYsijhLeTdKEsyxKLroey2cjAl3mpercent2BBjzIMT0Izapercent2BENhevpercent2Fpercent2FAIWYEL9lbQAQx985E1l5pupgXmpneFGhEGw5holw440z5BqCmQZzzEgKsUbybh0DgbUQ2mfNsmmazIJnwXpLetNolUpercent2BECYVpimQP4R8xhLBgJuh02Lp8u0xkvXpercent2BYx2Ee9ZOVTHlePZaoguSxzVuXTWsEgyK0lGcRZXkST31percent2BleUTRc8re93v9DoOf81LhjANQcvEfEtgNFOVnQRyNs8Blsmi2eJ4E49iVlTpercent2BN4percent2FSGh29TaXIDREa9wGYvXr5jszTkLJqyQKAJZTLeE25DiFMW5KtkwlpBdpF3GEpercent2Bu2percent2Bz0ibK7IUtwE0QgA0bwPOPoIUJ63z8XeCem1UCSHxaJKGhFyi6hFIJUwLkLrIHMfFEg2FTBCAApGea2QmASxe1SmClr3ddNVpercent2BYwl9lpercent2FXszGPJNEekESxOnFG8x8HV1cFr3LNIvWaVIEcbvDtsBe8wxeAFK4EBlaLiyyBTpercent2BphCkWWSJfb5U9GJOVzCLLoBbZWB6Esjpk1y5KT9hhHwzAwMwilXEQcwWps1wJUit9GsWTSi4A5KrkvQJL5lboKQhVAl6m1xaEggJVQGxS83epercent2BYumUpercent2FTLpercent2BDcK0B295Sypercent2Fbbc150rV69ntAOGfffMOkS6q8hchr1percent2BQ1XMBYndppI7ES7VZXRuihfPpercent2FLdJpzLGpercent2B7xgq4zIrK917FSRCvgnBX8lrYOhjIOqYMUjDry6C47AXjvGUq0oYA6vc89scfrISwpZEwhqfsGDCsbpjPri9BOIC3eZw0gnsI3jWlrmlXVVCGlDnPABGiLkt43IvS3jTKeOuBqFwedHRQxh48nUx4zKEzwaFqezBSMXsdxAuel56YFIsgNkDbHYPMn1ZZmkpercent2FSOd9MogTrhfO8XVG47bAHUCMpercent2FaLvNidoaIW8opercent2FKkFT6erlRULZrtBMQw6yLp9YiKOoef0u5F2q6ncqKkRZndyrR1HYFUqPrZd5Zpercent2BSM9Sjr3gWpSFGW7xik0sO7Qjatex5qsYrrpercent2FUYgc8Zwrpercent2FAxhGcDpEL0areWrKJNVpW0ZjZ7XlbbzyMVkMJ8YxfRxPqG9k9YhVtZROysXcmYLS4percent2BY2AMXRsXpr9XTSNKkA6Dpercent2FJ70B4lBY4FOPQ1nxZFACYFXwJ1qLMWZsMhpercent2BhYSEcpG0qrcZeRxKu0QqkkYZOEPizhpercent2BCX2czdBQqrYDpercent2FZkac3BkE1gGFXPMW0d96M3RT7932DaUkIBBGD7XVW89gHEJ9XcgPFsfZSvEbikcGns97ROLrNOcpvm1YrSgZvpercent2FM7MRL1aCm8fv9fiV81YcQNQF794KNA2xW4VkbLpercent2BVIxOi0NbDRo7weMtCs26OLpercent2B6fVpercent2BIKHe1rENY9DAO7EgnKMbiz8BgwaHvbGweT3Cxqc4ejtX8Pj6TQ8fqCMyziUHVVw76jTbhp9XX0e6FGi13spercent2Fiq659P0kxg40RVt93FiPbQKHwHbVca7hihaublNqlhLlz5gIKEun0MIePmLlEsAYOdswegpercent2Bj62roLqCFhX98percent2Bpercent2FInwPv123wGTcATbGc5jdcRLktjaPipercent2FfSjyfrXwgznUWeHZZRSHLcNiNiNjfmAa8yXDnpercent2B4kjdlFMOpercent2Buuj4O3W8uIwgMbZogvwzC9AaeqvmN4jLKe6K1aJX022Z2Cvg5iL5qGY6Z4HAjLpunhkZMVhbtE8e0zdvVnLpercent2F5Lpercent2FBAdZYF8M84rxPl8zhYvflv8DyByncWZVma3WlW6Hpercent2F833v3IDoTwP4AA1nIyxpercent2Bakz0PQwjDh7MgvLiBIOz9lht100uZ3sqD2RxaAalWh6VzzKpercent2BaNfkOHlFP0Cvpercent2BeHuit7percent2BCxg8ZvwIwjwpercent2FYQ2aSldA0koIavCLWo1eg2O8NNaAoiaATHImxiwIN0xeQHGF6IOYYTkWJYdpercent2BJppuN9AKEdK76KFCHXXnwF1qlK1percent2Fpercent2BHeBfDTDjk2jpercent2BxlCjVAITJGhpPmXWpzpercent2Bpercent2BfiNaB2hHWxfLDrtYwb81FHl4DuA5WGtltmT1MwxrjeFjDlLlIGnuw7percent2BBkXX1LC1QBpercent2Fjjiz8D8WdogF37V0ADfj369ekXIKpercent2BHlD6k9CGlH9PzMT5f9TH3ysOUK8K6GlxVpercent2FIk32qp3yPZZqwuG3GcXSxhdgSnhaYVPA3xatw0kv0ICJxDOgcRZlzgrE2dg4xB1Tpercent2BAcSJyliTO0cQjSK7F9C4eq1VawpODpY8sUrLTntXg2xwGlx4AXou6Dc4ELIO7jzwGi7ZOj9percent2FdZF6TRuvQIfurwOSP0hpx1Uw76GIRApa76Zrqn0j0z3Vfpvpkpercent2BUOkDPX1I9Ic1percent2BkOiP6zRHxL9YY3percent2BMcEf1percent2BCPCf7YhscohHTyoMEYAxMzPJvSldDsqqYahi9mDGzdcuRACu4L7ANhHulNlYMMu8Kg6F2dgCcIkCzIpSvsKAkIo0kiIq8vAqQrsA6EWSSP6k3KIggHa51jaVxJVwrd14T269JIjr7g4RscfYOjXpercent2BM4KN0s9dSMUWrpS64iz7cNZZSXHIWFnwO03z7percent2BHKBepercent2Bpercent2FhzgPzgaeAqOYjoLgVIqCHHb8wZNOWIKq57KlomlNXK9PVMz8oc6Jmpercent2BlTnUM6tYLEf24MiDUynAvt0GVdWcp6D8TVCpercent2BghpsghooqOEGKMOBslCqapUCSYaRqpSvHA68anDfVYPzrhpcdpercent2BV03K3VHkpercent2ByNM8percent2FzDPoCU2KVtX2dtgYnsfwPF4bMpercent2BLYM2QfcaGDqvExNkpUjSPsaiSfsaGjCn8MlNYjpercent2BYzlS2At2W2DQBx6ZNnT5AL6ZT9k6ezFbPE6CA3BZvA8gpercent2BfZ2hzpercent2BQH9qHhWTS5zgE7OPMJ73CZVe86sMNKy1PVUTJ2gUsznWV82GgQYfxpercent2BkOPLpercent2BGJ2E7RLW3lH9X8upercent2FapoHDAaChSY9UJbxfh59kCnqS5i3EtiHyuYLIo6RF9qmBZDqIi8gKbTpDywHDLhgei2VmAS0JCP1cCiTYga0JFpercent2FjsozwHgsBEvNpz12Kt0fLh6rID9NsntTCU4B3hpercent2BY4wYFNoFenpercent2FWkCHM0vgxW9pgVaLo8lKt6sG2VPy7VcTNZolV7old0abzJu4iXh285o38dqAZMaLjkSGczPKmhi5cErP3IyEh0G5fbTnQelpercent2Fpercent2BIGAhgok1percent2FO7KiBkpercent2FmqkJVT4E5Wpercent2F1unniJpercent2Fr4E0xgAOkXWsTjuOtO9VHlukiHA9ZURfUyrzeZkRXvRtQgP4c4Jpercent2BleFuKt5V4W4m3tXhzdJpercent2BpUyhpOVuSU0Xb2Z6cKl7OVuVU8a7nGsNPewpx4yiwYQioW7fDQh4XAS4zvuETo1Gyx6A6IA6Sm9prMVdUCm2uHm6IlHqP5GsOUCpercent2BB8eXKSMKpercent2BLMpDf1fy71qMYkX6WPxi2pCeh2OR7x7rQrp8868sIcVAt08D3b4cpercent2Bg7Koa9HKR6liGEwETQGw2jmmRg5XqdRCENG8MlsZSes7QRtsDnTBpszNdjU3XWniQWjz60tqpercent2BtdzL8P4Ppercent2BWBvCGJNTLmC1Vx2K2Un2KmVOSWaMks0ZJZhunEjAy5WQCSWJnr4zslZ29NrLXZrZXEfdcxLpercent2FqNAZVA7p0NpqGWMNunozoi8mIvi2f5FbnhFVMmWHwaJzX8AQpr0bKE6S8GqnGmRChpEPBpimSy6Xqpercent2B5INuioKaKxpercent2FoJwuR34CTk4siBkCAeprCWs1TyCgUVT1PDBZY6zJmJTsBDVNkLUafsrwIl5dCScEkRJcabM7mgyrOl9sYcqOIY6sLpHuJRbKSwSpercent2FNBbtsSWijReloGpGRgqhSjCJrQs6UAOOymJpercent2BabEqU0guRbam5saqQKHMZs66yjFmcpercent2FpSWMxryYkgOWvTJ9sFy3blLQHRLpercent2BUj0GDVJoTxWhWWFtkMpercent2FmK13VVORXEFLYJsSU7Ip00percent2Bx8xWVpercent2BLKeBJEvbZOlUoAIZr0percent2BnLmAZPRlzJzWROLVDsgVXUCUloxv4dCExmjERUzfGIWbbO5percent2BpvNNVTVXguZlTNw8lEYDJubygaDzzRsZYmKZvpercent2BT7dqq3NQV8VV306db2VdFaKuZBzuY2dpercent2FFzBB4w3FZY7gN9BlW93RPfr7Vpercent2B5rVpercent2FS9l9YGIbWpercent2Br0f1NRtdN2VyGP6dm6GtUpercent2Fcpercent2BuGbwvY72percent2FT2mzv09ppercent2F0WmtMuZJ7F7aOQYEXZoUApercent2BjQV5UczIt1percent2FT2zYjhqHcJfzyai8KR7XqEEpQzSpercent2FJJ3percent2FOydVfBPMhyLrcUXAcZw1MZakcBGDjhN9UGA09OMSNchtt0dwIMQjpercent2FkOHpercent2FqHffZQ9Epercent2Fepercent2FWC8svpF9yF9WYxawVZhgPiCCwB1uClIeSRg0WsNtJqGpercent2FpoOyDu5yPkpercent2Bsyp2AuOk0SSaoswzHGogrpfjvetYSfyZqflQzUt8F3JYaRyD1SSXajMwNAUujWsMeNB0jK1kShop5dlbodJVXDPi6bUbc22Ok7NuMJOMU8uCpwxJzDQkCWLOAb1percent2BqAYpfUEyIkhkUT7rtmFDxXMiP0cpercent2FOwQcBLEk0UMwN8HBWCtfuLXPFaaaQEQY7rkntpercent2FQnLKAetpercent2FXdpercent2FJOQBB2PKqcR3i4percent2FWwKCu1ptaC5aZfwhi68GQpercent2BjxWwLpupercent2FCjNObLWieC22SRbQbfAtu34XLZpercent2FNi1YwY8mkAUefkehlkF1Fyseesxirr36qifVbuF8N9UkDiI6jD9n7Cg11yDpercent2FFeBwzD9mhTv5Y2gLRXWapR2JOVF9IdR0FOc0mPDsu0Ncpercent2FS71U6pFU5ORiKq4HfqxcQb75ZuRTp82rKvIWHRf5MO4hxIk7uUepercent2BwQNpercent2Fx3mFyuexFaJWSJL0BVs8gWI1jLCIzxBqiot8rohnPC2jBoHi0ELPrzMUiglutRtUpercent2BJheNU2Rpercent2BYgpDjaC2lqgrgbPppercent2BwybQTN55U5eU3K7LkEQzy9x3YF65soN1JurAR1UFqX5percent2BLDQzpercent2FmQuPeteVKNhtxPRt43qVOOTnwpiGNvE6UaK4lqBpDR8ZBsVVX5Gu2LYDZrYCuydL6rnfgiVNfF2O4YkNVGVbBRu3LsPT7GgOl6ThvvYzvarhpSSBgOpercent2Buzf2ONpercent2Bu1ekP0RLHraO2hBNDbgO1MGRgduhbuHIIYLsLpoSmKhktJ11EibeRNFSRENogL91NDFGFaD1tK6s4n2DpVusUsGvUdiWS8qioUhvaWStVpRFE0C9lZG1XlMWDYF6akLH9n9L9ZH29YV2GrwphjihrMjcgJWXOOpercent2Bpercent2Fwmn1Nc6y39y0nQ6sCMvFa1wxNQjTCoAUHYg4fblRvpUkc1AaBpercent2BXrKgEPnAI2OEnzDJY5qpVjV2WM1Uk9woztmVU3treQC3pAsrcUtT7uYKCpercent2Fa8EE0percent2BnvSv5d11aQDZJWh90q33rfpercent2BPO4q66iQdHF3DalPMEljnqYfSnZbY74jey34zbkP8OrgOuNF9Op2mJdSkDjhBpercent2FiNChaLX845N0j8DXSQGleJIV31OqL81GGo93CPeMXGef5K5694SBNpercent2BKUFhB7CkdcffIaErpercent2Fl1Gi9otpercent2FJXk9I7egx2fPTJUhItcXbpRTJNoZTeoAgdBvEWWGKOVpercent2BKguYTp5TFgtbzDDvMOoXh6ntmTmUbZ7CbIpercent2BMvgtzT7M89y4Pc6QEWJkEQHQb22OoxZQ42SDagwhvVrqAX0IuugMM4dmNLNgsnTMMTjgXXoIcjUbwIH2PeKpUGkBpercent2BPJ58HkstVK6FyqfopABpercent2B3NFpercent2FmlhAHvvCnwegJwZLln69Zkncpercent2BjszROsxfJu9yhmgdm8HwTpRxqfmQfwFujmsV9qF4percent2FXPKlO0eZX1ppercent2BBGRm6MRRk087bIaeGjU67rZDjhlZ3f330lvgxPOOZqKRbq7fUmhv9kZ7IJhhilHNMnRs9hQ3699aVVpercent2FTKJnCn85lvOZ4l8aLZxtLQKRDauY1SwSFtLJvaKXbohkkmyTETRpercent2FZRsmwi581SkQlxSERpdsSEakmSYzh9cbKQgNslIuKpUMumU7DxVHD2N4Grpercent2BmhS9CkTnUab2vtNzFAP00lW0STZqPNtTP4m22uAW6KBaeAnktAnWKzDXF6tfjCYg467Mglpkxfjdj3aRrjlJTHvlG47wfnbZrfVHmpercent2BnTeu8oZ2XlDlHdt5WZV3VENca7meX8MN05vELe4hZipercent2FmbnkpkzptbpkpHzuUbrFFdoPYlLmOm8Sm7FzX6shUCjcyOfMEpkZ44DdjDmrqQjOCl9O4bDXEfHF3jctaw9JadITdbbKhMpkFc2zDiAtznBpSpercent2FiSYQpercent2FngDWoKEDkT9qcsmjfZGQApercent2FpcQ9rW662FjQtBsxNpWzYUM5G9aE0wjuIBvO4TXIRxsAypercent2Bstqj7lpercent2B6Nz9gdTSO8fnYOxvMM2percent2Bpercent2FZbdixFMdBa2kUcOAP2percent2BDGu3dHqo191fcuOKW0ylLdkILbG58kTNgTWKuX4HHkOkVBXrYjpmOUVIThV6Pu4eNTIteYMmtxscshRhz12OUSmVpercent2Fp2KoGa3YSsdnAVza3e1VePDV95percent2Fbpercent2BQs5CR5S3Ppercent2B5ruEhPPTf56bHSnw3rGHT1G3Jpc9kMUF1t6glMCaRTXWdjrJV1QaRJDXACRbhbkWgI190SgTpercent2BS5percent2ByIixxZKUWwM6G314YZ60IMKz3MaRpercent2BbUXNZcB75ZjCfpbBYk4ZbxRK4BNkuGw9FDp2Qix5ZMp7pdRBh7xCtkt6OkJXyzwG5hNwlaEm12rTY5vrl7acyj03CdDnZtcLtbXpUD9Eb2ZJiTSBuHuatGTjhP8shtGZmzAycBSpzWjZweAZCzDSlzduAkQNu4I6DDNljQB5F8p15lznZuEnSzBX0QyXfqVebswEmAbragD3X3wDmGL3N24CRAt1twACINnHqVOdu5SdDNFhyASEOnXmXODpwE6GYLDmE4OXTW7WXOdk4SlCx4btepercent2BeiFvqjXKpercent2B8M21xjl2hzVFu8bShWo7TttVpercent2BaE83zkmpl2EsLhioB3zhO7cUQkOUMW53LcMw8qZzfxJPidxFM4G8SDKtJ31rNlzo7iCfC7iSdxIIo6rMG9A5zpercent2FcNqvzNlNQAlpercent2BJwEVTrP9BqDCwGmpercent2FMmdH8QT43cSTOBvEgpercent2Fp74GwEypwdxRPgdxNP4mxy7xDHs077lTm7CSjB7ySgwmm23xBUGDrtVpercent2BbsKJ4Av5t4EmeDeNC4DJ0tVJmzo3gCpercent2FG7iSRxwr9UIVPX2ph1hTpercent2Bu3UZp7LXKrOeBxMM95KOpercent2BBPeRdukbDwDC3salpercent2BZ40PdZHBYPqKndiYZ1KbBfNW64IWjC5wIxoOTTUpjEU8at7vRNDbRnB9R4Lpercent2BZoL1kVLNMNv9VTbdrcrLupercent2Fipgm720Zpercent2BM9p7U1dBqzuiwjSDedhA02LlIEndG6percent2Bapercent2B3mBKapercent2BVX3FBh6UCXSq8c6d65uKXCTvfPnVsnSKbXUXJxVnXEnN0lXKpercent2Bc0KXeDY3BccNyk8rIiyyIkpG54K1oiCVv7MwicH1Fu7a2J6SpFMJpercent2Fupercent2BxalosvUmNtpercent2B7KZwcprv0NaJjcuhxNb9vg0SvgbTsPYNCueInh5npercent2Fcp3mNZjXQ7jCpercent2FndHmfAO7Qelu6KJ7jpW8veZ4HF3TPoUzu7cnbLyV7n17Qcqpercent2BgLEU5VzemZjxP42u80p3percent2FRrej6qvP1SIhkMTdLnjn6lvxat53Jpercent2FZ3EqdexmfpNTcvMdyjBcuMZNpercent2FriKtftQVU85QECtJCWUm5lkNT7aISaH0Opercent2BaB6l8tuFgO0J124aAuN23kooyeEAy1xnyB0q8wLeq2j0cYlu9KfYnID5X4XJcXxU9yNLGmHVYjqqmpbyfFS4I7YSX2uXS4MyFkE4xDTpercent2B64bhpEFEsG50XOxmdzYM9KoiPupercent2B4Cpercent2FmzknMg0zFTBlKxqGiKqbKaDzUrpS5LZpercent2BUSLU7MneWR9x3qejkZeHDdUzLcpercent2Bpercent2F76r6apercent2Bvpercent2B9Xq8qmpercent2BeNWzCwEiqFwAty36b95aBPlzJsKPxGyRpercent2Bx99gPPD63qwAcQ3vDERvgONdkTPftQj308uwdVo6percent2B792ZJc4RulkORszzjzuC93A7c8percent2F3vLvzx7HDgO4X8prd8GXpercent2F94eDZiM7rAxtzPNlgToePr6rfsePG1kd4wrAEbD0af9LvfIbsT36NAk2pklaQC2ySEKq8F3i8esXyQpercent2F4percent2FYMCWqVpdHF4dldZHpercent2FsddgTuwOESPRlGw8bZx3E85B3Cwpercent2BFQypercent2FXoHpercent2F7QUB8fjnBpercent2Bq4QZNvjCG2IAHhHxQ0W40WSPpercent2Fbp38iLI0AA8S4L4VRpjSB4percent2BvbPuQkYRhn6zAH1dAFHJ1Auh6gpsBKPi4oTU420TQN3jTmi3gQxQ41MjW3tGkDaxe0SToMAdWyFVqTndnNFh43pnaCJbzqBH79ligutEgeyMswM2lopercent2Blk6HyLpBY30wZd1hggapdM5pKKJ247Rj40oXB4FhoSsKcUcPyNsjAmIaIUiTVmytb1XwxhwaoamHoVmABRcGGD2ZmtaWcpercent2BsLlMYsRHXvqqK3ZfbWVulpercent2Fuhu7bVzmJTpy4zV27Qh5vN9HkoOverRt9RAzYQJoetw6Ftja8BPtB3rMuikhvHMHYHDOqZlIXHuz8mlpercent2BBMQptP2ntKB1UgeYeuuq8qb3ShIMz3percent2F4MiOyc7d6mWpercent2Fjl1ki9C626K3afUoqcVAqJ7ry4d34kg4LSb21caW9np0vgRYAHptbJm5percent2BECDnuq9K49HC4tQ0ERl0GiNStFTq53GrPJHA9MnaQ9tbumOmxpnXOTOpercent2FXemo02KxtmjcObdohZpercent2BwJpercent2FKRoO6ui7bjpercent2FyeF29gXD7Xtz76QWcMauyjuFnMSkLZXkRp2JMpercent2Bx0Xo2BZwI1hd64gdcOwbej3Hpercent2BlAJSf2IhwBpercent2BSPL56JT2d8ZpWHOxwHX67apercent2B2LmcClepercent2B7bI5percent2BsOA6Tpercent2FFLqXX0r5TJWPaID2N64ydAupercent2FtM6fXtXpercent2BRd2sfevmM5Vpercent2B5G91NAxN0DRHQpercent2Fvtrpercent2BHsL6v5Fnfpercent2FbWguXK7mwJupercent2BrqLyNe4bqL178z20DOqzc1LCmkUXpercent2BRiMoeszwVMyNJTFGf6erlPem2JXvCWOGGXVhCeeffGP2percent2Bypercent2BuHihtBaOI4DDlnGDPomGswH2NJp33tYb77sT8O9CoI4percent2Bpercent2BFzpercent2BQ53ArcNj8rtVcXqDt8UseDJZddhldHGpveJlMlGxCHnNhzD8pCP24uwebWbHQ01L8hIdeoUU8ZFBSNWg2gLsZFshO3IXIUvNxxUhmkqmbwlWVpercent2F7QLRhTMVqezS1dhpercent2F3e8aPD4percent2BNDKHzpercent2BUc8percent2FPvIG2pQ8bQWucC3LHHu9R4feI9w45hpercent2F6vcNHvn90VCETolCfvtaDepercent2BblYeA4vfBbkL9P31DEpercent2B5YeHpercent2BEKo3Zj7FRdUuDEjXXco6FlJpercent2BEWIXPpPlHR2Lo9nc2NUkOo9q2sBGYYVfvgFWJ8i7J4j5xIrUpqBN3HaxtQ14Ev1X7IWrgvmsDm6Q1C4UoYauZtYugPnPw2s8Mf36b6KVLjdRBd5gpercent2BP7AKmIk6TRriQSLfJb4eWwpercent2BZBFhV4IF6gpercent2FRvzDafI7CeWW7rd2PBfJYBkLv4percent2BeaLZUSQdnOpBs4GFAPpercent2FjFPRpercent2BdHRc50QFjr7GCdH8DfMPD82y6Iss4skw4ppercent2BANMcC0iq2g3OSGOGU3E2gw3PUQECZTGldij6hROEKf76FQsWigwNDLaKCZ3gjpercent2BvQrKUFvdQ2percent2FTsOb8percent2BKn58percent2FEFpercent2FGSWsX68y8fXr97percent2Bf1Pz5u7h3bvwuyADLEx2LHz4Gh4oA9SirBx1aiukqGNmDTkIX0bEKPIw1MLiUsu3SAGmpMXfn1tC7dvTtnpercent2FaQlpercent2B7Apercent2FBeAguTpzj5rtxx1nv33fmbk2w1sxBUn2OUOaspVosLgXD0vCRYhyXykXHqSMn0KACPBWf8zMqgpercent2FvGJgj8rpercent2Blpercent2Bbdki1percent2BbLW3ZIKc54UsxRStQSs90nuORLufWeoGhHCZ7gpi4GPjgPlAul58Hkdy7OsRiHjkeuU9DCPpJdW5uoUZdmKQUMs9CNUkeDkTkppTGGgiPeOsZBt5HjoKspQUcpercent2FIjOqH4OzocX5lZFxaqZRJpercent2FzPpercent2FvjuraXVYDCyUobHOpercent2BlJx6lHpercent2BjluW1Z9dmzkOGZtw5unhkfOk8w1HnVrO8percent2FtbrD6T3TU1HnIbwPWa3GA1X3gzMZTB3xG1jGjjZ7DEqqNgBxbPppcE1Sh5Aghpercent2FRjNyHGwpxmpercent2BPM1SQ6sO29jY1ez8qHYpx0b1bzfaYnhcs0XDKRnXMRuT8YmLjrF33t50vwNpercent2BNqfvM2percent2Ffpercent2FGfsqNP30l33gEa77SYffgHyYTN5sGKdpercent2FKatpoJyQJTfB71lD7JxoXOlHtb4cN7A7qq8sYeZn2BpgQnoRIUKpF5139HJbkVBl1K71Y7I1rQaCV7W1kJcRA9REHTqrcrpercent2BJYugQaWZpJF1j561ikqITWo4yFX6W4T127nKR6BQvXRo3percent2BAZYhr7Hncvao0Nxq3edRD27VWlpercent2B7se3nliFEqx1prxCYpercent2Bu9WJfw3Ws6Wo89Gbkux4tmDSzMRoYB4Gm5WOFT70FWqqR2HYnrK6OpofVTQyjfB5Ar0LQwpercent2B7Q2SIv0pl4N9cfGxZfG9gbahr63U0EaxWqcUHOEMPeoSCPfZu7FOTmBSnNDhsUnBOOzW6p0OWKnXK21ur3qlW7DUCrXYDGuwAFuwDhVQpercent2B7wGW7AK13gsJbH3ZiWV4zcXKXIFKftjcCSFbHW0JHHUHpercent2FG4ydxXwXkpercent2BEdKLvAxeqpercent2Bgl1csAsg3SiyEpercent2Bed4mgnKHk7yK76percent2FoUi6Upercent2B4oB7EZpercent2BUNXHpercent2FFePoqSuKpercent2FXba3RUnIlpercent2FX9percent2FjwUpercent2BmOH8eEpercent2Bpercent2Bpercent2FcPH169epercent2F38wwe2percent2F1DeTD5vVVeSC9W12percent2BAw8YKrvU84X994YCAp95PlPRLml6llSrrqlj0xJ57lboTENLciRLfKmVQkgoKQWpercent2FDUNHIVGUEYPlP73LacdYhwPk1T0percent2BKIQGnMcaK49euPYIXStiP2Pz9G4S382j07Y8qiDvBzMOO3v2oMDFpercent2FRVsEo7JgrdEI6JVvbUlfsjpercent2FlaGkOIfXWlRYUCeuuqJcF1dIHD5R5EumMnjaQCxUlqpercent2FLGc9dK1LoPB3EDa3olHpTywaWCgm9percent2FFI0wnixmetaiTfpercent2FbLS6ypercent2BMC0NQh5WurhO5VDU49dlRGyYspcezcutkgqjvMixMTLUvKTbVuVupNuqupBWKbeqNootJq7ktRWgfZhmLyCOvGHpercent2FqDoEX2Qre478vZDgvEHdTNpercent2BZCY5RbLR9U2Km067ompercent2Bc6t5gARsU0Uchxw7s1pY8FhtJFbipercent2BBcO8w8TyTB4PcjZhRwDcVXpzxpercent2FE1ZW1sMpxM2VmF0Xf5b4ULVXpercent2FXfa1qWy3BNRB9bpercent2B3nrBZ0g9percent2BTF9qz1ur1n2N8hiZw9M0SxV7TJnQK3Sa1aj8DdoAtHC0xzc7GRs2m7QzN8fQtZM2y5percent2Fchs3wSMVhPKjsC3eQE9iCdW6IjpTmyBVT0zgZF8wZpercent2FHfCbGegSwJ5gYSD3qLgmXsb2Mx0EBpercent2FRz8UEeeZt15GiV45pdWN3nYnYXssnvE5nF3wOZZ12cF9LO62OETTxdZsOoe9vtsCoLko3RRxFHCu0macJqc6Hosn40IdJnjYywpercent2Btww0xxcC2percent2Bv39xxiRvCSvQVEFFNODOpercent2FZRSgSfVDuhIlyEWhnquUBqGkAoV5C4KyNpercent2BP6MPH8Lpercent2FcEpnlVIJvpnDehTDGWHV2yUlpercent2F4dCYod8To2X8UFfHqKuPpPT8nMV7x10Hi38pfm68p8XStos775TQbyqzSP5HHO99V6opercent2BhNAEByoSKpercent2BDhDlZ0FMM9kOM0azxXOKbUdWpercent2FjSO0xsevk2lGwyQSZrxnvASlIuqKgyyixzq0uRarw0NNqJkUbDOoQjhehoi0TlixNNKUnk4percent2BBJKacwlOpercent2ByVt0SP3LhNpercent2Fb6uqtFxrZnoZ7rpercent2BWBLp2esFverOtHaHbYFVd6q1qwVeypercent2BTaN5ut5uxWb3rlB3c2xrPxpRpercent2BC7NpF4QmeeTQpercent2FTmEUFA25FApbK1FMXuGdx1Y0KAgVjCpercent2Bhpercent2F1SH0M6g4unR6ugpvOUtvRCap07Jc3o2Hj49x8percent2FnSovrA1bKa9dkNSzMWJ3aaSOxesdC6KDcpercent2Bst0mtN89a5hAB6xAu59bdtlFVpercent2B7ktci0sFA23percent2BA5hRfOBjnLVOJNu4n6Xl4BWoJYUsiYewegOF3w9ql2eyqSmz8sumfNILTtpWuKbGhVVWJGNLlPAMkiLIs4XEvSnvTKOOtPVFX4OC9AmVsz1hG2BupGKXNcHlppercent2FfqKQ7tjkCnXHDaTKMFoEaeicNthe1Br7jXMqBvhbSj7qYVMp6uVi9ri3DZjous7yNpe1rGXI9yHwQ0tjbC6k0vt2AFrUlGp21N03percent2BtdjzNxsBsce003dNIJapercent2FHWcoyNZOtiN4htszaf6Bd1czkuycVpercent2BDHMALWOlrNM3dncMr8sONDje5qP38x30jEIrzU9dW32Ipercent2B7QoYOCJ3gDqUMNoKM298Lv1q3fqWTuBtA3hbjC5ffYIzwnTT7percent2BnbVWTQPVRvTbRVx51klMGjd2OGlmnGU2zy5AU31I5M3vB6gsoHearOyjKvk8Dgh6bdYdD82l3tPEqh70SYs8cEzd0yQHuxIJydPItpercent2FAaMvFjBuHscTH6percent2FoDEKsvnX8Hg6DYpercent2FLz7VV49n7d9ZpN42percent2Brj57J3a9Ypercent2BI4psVirLicE0nOHrgWUG4zNR0bEGDmd37MGSKI7ISm5ZuGpmF0XY1LBbSw249vXpercent2F4EeL9percent2Bm8percent2BCOH6CLRenweikHNtpercent2Fpercent2B1Dkpercent2FWrhB3OoS8KzyygOW4atbEbG4Hca8yXDnpercent2B4kjdlFMOpercent2BuYIQL49KbywjcrY2B88sgTGpercent2FgSbjmoipercent2FPKGK93Sqptpercent2FXMFHBzEHtlXk0zwb54XDYTDY2JMfU2percent2FYd79percent2B7lpercent2FwQpercent2FrxdQJpercent2BID7sp781percent2Fh4RfarGpBpercent2FDM8PIvyeRys3vwjPPN89v8BSeiR3Qpercent3Dpercent3D
No analysis is required (don’t run it ;)). Merely conceal the enter cell, leaving solely the output seen via the properties (click on on the top-right nook of the group).
Here’s a notebook with all examples [13] (a few of which work in a browser as effectively and not using a kernel).
What if utilizing my very own laptop computer will not be an choice?
The WLJS Pocket book will be exported to HTML, preserving even some dynamic behavior [16]. That is achieved via a fairly subtle algorithm that tracks all occasion chains occurring on the JS and WL sides, making an attempt to approximate them utilizing a easy state machine. The result’s a normal HTML file containing all cells, slides, and the information of those state machines.
Nonetheless, because of the number of values acquired from the IMU, the exporter can’t robotically seize this in a JS state machine. Consequently, rotations and the joystick enter is not going to be preserved. Nonetheless, every little thing else will operate as anticipated. 🙂
… like a fish wants a bicycle
Please confer with the ultimate sentence of that beautiful comics by
Zach Weinersmith.
If not for the pressing have to showcase a rotating crystalline construction, this put up wouldn’t exist.
Thanks in your consideration, and to those that learn this far 🧙🏼♂️ 🤍
References
[1] — ECMA-363 Specification, Wikipedia: https://en.wikipedia.org/wiki/Universal_3D
[2] — DPG2025, Homepage: https://www.dpg-physik.de/
[3] — Leo. Proper Pleasure-con Controller as a Distant, Hackster: https://www.hackster.io/leo49/right-joy-con-controller-as-a-presentation-remote-5810e4 (2024)
[4] — Jen Tong, Nintendo Change Pleasure-Con Presentation Distant, Medium: https://medium.com/@mimming/nintendo-switch-joy-con-presentation-remote-5a7e08e7ad11 (2018)
[5] — RevealJS, Homepage: https://revealjs.com/
[6] — Manim, Homepage: https://www.manim.community/
[7] — Movement Canvas, Homepage: https://motioncanvas.io/
[8] — RISE, Github Web page: https://github.com/damianavila/RISE
[9] — WLJS Pocket book, Homepage: https://wljs.io/
[10] — Vasin Ok. Reinventing dynamic and transportable notebooks with Javascript and Wolfram Language, Medium: https://medium.com/@krikus.ms/reinventing-dynamic-and-portable-notebooks-with-javascript-and-wolfram-language-22701d38d651 (2024)
[11] — Vasin Ok. Dynamic Presentation, or How one can Code a Slide with Markdown and WL, Weblog put up: https://wljs.io/blog/2025/03/02/ultimate-ppt (2025)
[12] — Pleasure-Con WebHID, Github Web page: https://github.com/tomayac/joy-con-webhid
[13] — JoyCon Presenter Instrument, On-line pocket book: https://jerryi.github.io/wljs-demo/PresenterJoyCon.html
[14] — Faraday Impact, On-line pocket book: https://jerryi.github.io/wljs-demo/THzFaraday.html
[15] — James Lambert VR powered by N64, Youtube video: https://www.youtube.com/watch?v=ha3fDU-1wHk
[16] — Dynamic HTML, WLJS Documentation web page: https://wljs.io/frontend/Exporting/Dynamic%20HTML/
All hyperlinks offered have been visited on March 2025.