
This article is currently under development and will include discussion on the following topics:
- Manipulation of generative art
- Photoshop masking and layers
- HTML/CSS/Javascript programming (matrix ‘cascade’ function)
- Premiere Pro, advanced blending and compositing layers
You might be here because you saw a post on my Facebook page about living in the matrix. It was an idea that came about originally as meme text but then grew into an afternoon-long project that included some cool stuff like programming a Matrix-like letter cascade routine.
Stay tuned. There’s a lot to unpack in this simple graphic-turned-video-animation-project. It will take a day or two (or 3, depending on my other responsibilities) to finish writing things up.
What’s does the graphic mean?
Pretty much exactly what it says.
If you want deeper meaning, just look at the technicals of the image.
A nondescript female form walking along through a city courtyard of some sort, taking a photo of herself (INSERTED) into the immediate landscape. Her solid form is full color while her duplicate form is desaturated and transparent allowing the original background of the courtyard to bleed through. Sandwiched in between her actual form and the inserted form is a grid of binary numbers.
I could try using AI to create this but I’m not sure I have enough patience or generative AI credits for that. But let’s give it a try using Firefly and a similar prompt:

First brush, I didn’t try to refine further. I also followed up by using the same prompt and a more advanced Firefly model, but, not even close to what I had in mind. That isn’t to say I didn’t use AI!
Step 1: generate an image suitable for the vision of the graphic
For the base image, I absolutely did use generative AI and this is the prompt I used: somebody turned away from the camera looking at their phone screening and scrolling, full length body shot. Among a few other less-suitable images, Firefly generated this:

5 fingers! Yay! But as you can see I still had some work to do! What’s up with the ghost? That had to go. The background behind it? The background behind her? What background? This is a flat image! Background has to be created.
Step 2: Masking and layering: I won’t go into the gory details but in order to make her full color image stand off a desaturated version of the same image, I had to create several layers, each layer having a slightly different mask:

Masking was easy because the image lent itself to being easily masked (automasking is one of of Photoshop’s many built-in AI-assisted tools).
Drawing in the background? AI using Photoshop’s content aware fill helped with that. Turned an otherwise meaningless hour-long long into mere seconds of effort. Cool. Next!
What’s next?
As you can see, there is lots of generative AI and AI assists. Did I have to use AI? No, I suppose I could have spent an additional hour or two masking and drawing in the background myself …
Or, I could instead spend time …
To be continued. <bookmark and read more as this blog entry develops!>
Cascading Letters effect
Fun and easy, mainly because it’s code complete. This code is good to run locally in your own computer browser, but if you plan to put this code online, you should first add an input sanitization routine!
To use:
- Open Notepad
- Copy and paste the html code below into the document
- Save document using HTML as the filename extension
- Drag and drop your new HTML file into a blank browser tab.
This code should work fine under all of the major browsers but no guarantees! If you are using an ultra-secure browser, and/or have Javascript disabled it simply won’t run. (maybe I’ll add a noscript fallback message at some point — no wait, there I go again, turning it into a programming project, LOL nnnnnope!)
What you will see/experience:
Assuming you don’t have some oddball browser and everything works, once you drag the HTML file into a new browser tab it should open up and play the cascading screen effect. By default it displays 1’s and 0’s at random speeds, but you can use your own characters if you want!
The input screen lets you enter in your own characters / phrase if you want something other than 0’s and 1’s. If you plan on using a phrase, plan on it too being randomized (there is a small statistical chance that all letters will appear in the correct order, but otherwise, it’s gonna look fairly garbled!)
Each column drops at a different speed. You can influence the minimum and maximum drop speed by setting the Minimum and Maximum speed values. 1 is slowest, 10 is fastest. You can get some interesting visual results by varying the length of your custom text and the min/max speeds. If you like it the way it looks like when it first starts, just enter the characters 0 1 as custom text, leave the min/max at 1 and 3 and click on Apply Settings to Matrix.
Clicking on Apply Settings to Matrix makes the UX portion disappear.
To change your custom text or min/max speeds, refresh your browser (usually Ctrl + R).
CLEAN/FULLSCREEN MODE: This effect looks great when your browser is operating at max size in full screen mode. (You can switch to full screen mode in most major browsers by pressing F11 on PCs, or Control + Command + F on a Mac). Pressing F11 again will return your browser to normal.)
Here is the code. Enjoy!
Cascading letters code <click to expand>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Matrix Cascade</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
body {
font-family: 'Inter', sans-serif;
overflow: hidden; /* Prevent scrollbars */
background-color: #000; /* Black background for the matrix effect */
margin: 0; /* Remove default body margin */
padding: 0; /* Remove default body padding */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh; /* Ensure it takes full viewport height */
color: white; /* Default text color */
}
canvas {
display: block;
background-color: #000; /* Ensure canvas background is black */
position: absolute; /* Position canvas to cover the whole screen */
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1; /* Place canvas behind other content */
}
.controls-container {
position: relative; /* Bring controls above the canvas */
z-index: 1;
background-color: rgba(0, 0, 0, 0.7); /* Semi-transparent background for readability */
padding: 20px;
border-radius: 10px;
display: flex;
flex-direction: column;
gap: 15px;
align-items: center;
box-shadow: 0 0 15px rgba(0, 255, 0, 0.5); /* Green glow effect */
margin-bottom: 20px; /* Space from the bottom if content is centered */
transition: opacity 0.5s ease-in-out, visibility 0.5s ease-in-out; /* Smooth fade transition */
opacity: 1;
visibility: visible;
}
.controls-container.fade-out {
opacity: 0;
visibility: hidden;
pointer-events: none; /* Disable interactions when faded out */
}
.controls-container input[type="text"],
.controls-container input[type="number"] { /* Apply styles to number inputs too */
padding: 10px 15px;
border-radius: 5px;
border: 1px solid #0F0; /* Green border */
background-color: #1a1a1a; /* Darker input background */
color: #0F0; /* Green text */
font-size: 1rem;
width: 250px;
max-width: 80vw; /* Responsive width */
}
.controls-container input[type="text"]::placeholder,
.controls-container input[type="number"]::placeholder { /* Apply styles to number inputs too */
color: #080; /* Lighter green placeholder */
}
.controls-container button {
background-color: #0F0; /* Green button */
color: #000; /* Black text */
padding: 10px 20px;
border-radius: 5px;
border: none;
cursor: pointer;
font-weight: bold;
transition: background-color 0.3s ease, transform 0.1s ease;
box-shadow: 0 0 8px rgba(0, 255, 0, 0.7); /* Button glow */
}
.controls-container button:hover {
background-color: #0C0; /* Darker green on hover */
transform: translateY(-2px); /* Slight lift effect */
}
.controls-container button:active {
transform: translateY(0); /* Press effect */
}
.speed-inputs {
display: flex;
gap: 10px; /* Space between min/max speed inputs */
width: 100%;
justify-content: center;
}
.speed-inputs div {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
</style>
</head>
<body>
<canvas id="matrixCanvas"></canvas>
<div class="controls-container" id="controlsContainer">
<label for="textInput" class="text-lg text-green-300">Enter your custom text:</label>
<input type="text" id="textInput" placeholder="e.g., Hello World! 123 @#$" autocomplete="off">
<div class="speed-inputs">
<div>
<label for="minSpeedInput" class="text-sm text-green-300">Min Speed (1-10):</label>
<input type="number" id="minSpeedInput" value="1" min="1" max="10">
</div>
<div>
<label for="maxSpeedInput" class="text-sm text-green-300">Max Speed (1-10):</label>
<input type="number" id="maxSpeedInput" value="3" min="1" max="10">
</div>
</div>
<button id="applyTextButton">Apply Settings to Matrix</button>
</div>
<script>
const canvas = document.getElementById('matrixCanvas');
const ctx = canvas.getContext('2d');
const textInput = document.getElementById('textInput');
const minSpeedInput = document.getElementById('minSpeedInput');
const maxSpeedInput = document.getElementById('maxSpeedInput');
const applyTextButton = document.getElementById('applyTextButton');
const controlsContainer = document.getElementById('controlsContainer'); // Get the controls container
// Set canvas dimensions to fill the window
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Default characters (binary numbers)
let currentCharacters = '01';
const fontSize = 16;
let columns = canvas.width / fontSize; // Number of columns for the cascade
// Speed variables, initialized with default values
let minFallSpeed = parseFloat(minSpeedInput.value);
let maxFallSpeed = parseFloat(maxSpeedInput.value);
// An array to store the y-position and speed of each character in each column
const drops = [];
// Function to initialize or re-initialize drops with random speeds
function initializeDrops() {
drops.length = 0; // Clear existing drops
// Ensure min and max speeds are valid and min <= max
const validatedMinSpeed = Math.max(1, Math.min(10, minFallSpeed));
const validatedMaxSpeed = Math.max(validatedMinSpeed, Math.min(10, maxFallSpeed));
for (let i = 0; i < columns; i++) {
// Each drop now has a y position and a random speed within the user-defined range
drops[i] = {
y: 1, // Start at the top of the canvas
// Generate a random speed between validatedMinSpeed and validatedMaxSpeed
speed: Math.random() * (validatedMaxSpeed - validatedMinSpeed) + validatedMinSpeed
};
}
}
initializeDrops(); // Initialize drops on load
// Function to update characters and speed based on user input and hide/show controls
function updateSettings() {
const inputText = textInput.value.trim();
if (inputText.length > 0) {
currentCharacters = inputText;
} else {
currentCharacters = '01'; // Revert to binary if input is empty
}
// Update speed variables from input fields
minFallSpeed = parseFloat(minSpeedInput.value);
maxFallSpeed = parseFloat(maxSpeedInput.value);
// Re-initialize drops with new speed settings
initializeDrops();
// Handle fade-out/in of controls
if (inputText.length > 0) { // Only fade out if custom text is entered
controlsContainer.classList.add('fade-out');
} else {
controlsContainer.classList.remove('fade-out');
}
}
// Event listener for the button
applyTextButton.addEventListener('click', updateSettings);
// Event listener for 'Enter' key in the input fields
textInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
updateSettings();
}
});
minSpeedInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
updateSettings();
}
});
maxSpeedInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
updateSettings();
}
});
// Event listener for input changes to show controls if text is cleared
textInput.addEventListener('input', () => {
if (textInput.value.trim() === '') {
controlsContainer.classList.remove('fade-out');
}
});
// The main drawing loop
function draw() {
// Semi-transparent black rectangle to create the fading trail effect
// This makes previous characters fade out over time
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Set the color for the binary numbers (Matrix green)
ctx.fillStyle = '#0F0'; // Green color
ctx.font = `${fontSize}px Inter`; // Set font size and family
// Loop through each column
for (let i = 0; i < drops.length; i++) {
// Get a character from the currentCharacters string
const text = currentCharacters.charAt(Math.floor(Math.random() * currentCharacters.length));
// Draw the character at the current position in the column
// Use drops[i].y for the y-coordinate
ctx.fillText(text, i * fontSize, drops[i].y * fontSize);
// If the character has reached the bottom of the screen,
// or a random condition is met, reset its position to the top
// Use drops[i].y for the y-coordinate check
if (drops[i].y * fontSize > canvas.height && Math.random() > 0.975) {
drops[i].y = 0; // Reset to top
// Randomize speed again when resetting to top, using current min/max speeds
const validatedMinSpeed = Math.max(1, Math.min(10, minFallSpeed));
const validatedMaxSpeed = Math.max(validatedMinSpeed, Math.min(10, maxFallSpeed));
drops[i].speed = Math.random() * (validatedMaxSpeed - validatedMinSpeed) + validatedMinSpeed;
}
// Move the character down for the next frame using its individual speed
drops[i].y += drops[i].speed;
}
}
// Handle window resizing to keep the canvas full screen
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Recalculate columns based on new width
columns = canvas.width / fontSize; // Update columns variable
// Re-initialize drops to adjust for new column count and assign new speeds
initializeDrops();
});
// Start the animation loop
setInterval(draw, 33); // Approximately 30 frames per second
</script>
</body>
</html>