Skip to main content

Crypto text

Here we will create a component to make the following effect possible on a slide

It consists of two parts

  • WLX template
  • small Javascript function to efficiently update the text

Handling text updates

One could use TextView or something similar, that automatically binds to Wolfram Language. However, if we have a lot of text or big paragraph it might break the line breaks and in general is a bit heavy then a single span or div in HTML tree.

cell 1
.js
//just to reduce getElementById requests
const hashTable = {};

core.UpdateTextField = async (args, env) => {
const uid = await interpretate(args[1], env);
const data = await interpretate(args[0], env);
if (uid in hashTable) {
hashTable[uid].innerText = data;
} else {
hashTable[uid] = document.getElementById(uid);
hashTable[uid].innerText = data;
}
}

here we defined a function, that changes the text-content of a given HTML element found by a given ID

UpdateTextField[text_String, Id_String]

One can test it using HTML or WLX cells

cell 1
.html
<span id="someId">Text</span>
cell 2
UpdateTextField[RandomWord[], "someId"] // FrontSubmit

Crypto effect

The idea is to use Boltzmann weighted sampling function and pick up a letters from English alphabet. The original letter presented in the string will be "energetically" more favourable than the rest letters from the alphabet. The energy difference can be tuned by the statistical temperature T

cell 2
CT`prop[T_] := Join[{Exp[(*FB[*)((1)(*,*)/(*,*)(2 T))(*]FB*)]},  (Exp[(*FB[*)((-1)(*,*)/(*,*)(2 T))(*]FB*)])&/@Range[26]]

and we need to match the uppercases and lowercases

cell 3
CT`variants[c_] := If[LowerCaseQ[c], Join[{c}, Alphabet[]], Join[{c}, ToUpperCase/@Alphabet[]]]

now let us test

RandomChoice[CT`prop[0.2] -> CT`variants[#]] &/@ StringSplit["Hello World", ""] // StringRiffle
"H w l l o   F o r l d"

Component approach

Now we can construct a sort of a wrapper function, that we can use at any text string

cell 4
.wlx

CryptoText[TextX_String, OptionsPattern[]] := With[{
UId = CreateUUID[], cell = ResultCell[], win = CurrentWindow[],
slide = EventClone[OptionValue["Slide"]], onStartEvent = OptionValue["OnStart"]
},

Module[{
task,
crypted,
original,
temperature = 1.0,
mainTask, easeTask,
finished = False
},

original = StringSplit[TextX, ""];
crypted = original;

EventHandler[slide, {
"Slide" -> Function[Null,
(* show first time -> start *)

mainTask = SetInterval[
(crypted[[#]] = If[original[[#]] =!= " ", RandomChoice[CT`prop[temperature] -> CT`variants[original[[#]]]], original[[#]]]) &/@ RandomSample[Range[Length[original]]];

FrontSubmit[UpdateTextField[StringRiffle[crypted, ""], UId], "Window"->win];
, 100];

],

onStartEvent -> Function[Null,
(* slowly morph into the original text *)

With[{easeTask = SetInterval[temperature = temperature + 0.1(0.05 - temperature), 200]},
SetTimeout[
TaskRemove[easeTask];
TaskRemove[mainTask];
, 6000];
];

EventRemove[slide];
finished = True;
]
}];


EventHandler[cell, {"Destroy"->Function[Null,
(* if a cell was removed *)
Print["Removed"];

If[finished, Return[]];

TaskRemove[mainTask];
EventRemove[slide];

]}];

<span style="font-family:monospace" id="{UId}"><TextX/></span>
]
]

Options[CryptoText] = {"OnStart"->"", "Slide"->""};

The final function looks like this

CryptoText[text_String, "Slide"->slideEvent_String, "OnStart"->onStart_String] _String
  1. It returns a DOM element span with a unique Id.
  2. It assigns an event listener to slideEvent, which is typically generated by SlideEventListener and check if a slide was revealed.
  3. If the last thing is true it starts to regenerate text-string and update the DOM element using an Id generated previously.
  4. Once onStart pattern was captured (can be a fragment event - see animations), it reduces the statistical temperature to the minimum (easeTask) and then stops the animation after some time

We can see it in action on the slide here

cell 5
.slide

Just an empty slide to trigger an event on the next one

---

# <CryptoText OnStart={"fragment-1"} Slide={"slide-1"}>Heading</CryptoText>

This is a body of the slide

<CryptoText OnStart={"fragment-1"} Slide={"slide-1"}>Housework could be everyone’s work, not just “women’s work”. Why do women enable men to act oblivious to cleaning, grocery shopping, pet feeding, etc? Somehow when men live alone they figure out how to do all of those things all on their own. My friend’s husband claimed he didn’t know that sheets should be washed more than once a season. He said he didn’t know one had to clean toilets. He assumed that since you flush toilets they clean themselves. She tried to get him to help but he did an awful job so she let him off the hook.</CryptoText>

<br/>

<CryptoText OnStart={"fragment-1"} Slide={"slide-1"}>Wouldn’t it be better if she spent the time and energy to get him to do it right instead of letting him claim he is “just bad at it”. My sons were raised to clean toilets and change their own sheets. Hopefully, in their future homes, the housework will be equally divided.</CryptoText>



Stop <!-- .element: class="fragment" data-fragment-index="1" -->

<SlideEventListener Id={"slide-1"}/>

warning

This effect will not work if a cell output is projected to another window, since Javascript cells are local and belongs to the original Notebook's window.

Press f on a slide to make it fullscreen