Reading Reflection 5

The utilitarian Design vs Aesthetics:

Norman,“Emotion & Design: Attractive things work better”
Her Code Got Humans on the Moon

In reflecting on Don Norman’s “Emotion & Design: Attractive Things Work Better” and the article “Her Code Got Humans on the Moon”, I’ve gained a deeper appreciation for the role of user-centered design, particularly in high-stakes environments. Norman’s insights on emotional engagement in design highlight how well-designed, intuitive products improve user experience, functionality, and even safety. This principle aligns with Margaret Hamilton’s story in the article, where her recommendation to include a warning note in the Apollo software was initially dismissed but could have prevented a critical error that later occurred.

Both Norman and Hamilton emphasize that design must go beyond the technical requirements and account for human unpredictability. In high-stress situations—such as a lunar landing or, more broadly, any critical application—users may act differently than designers anticipate. Hamilton’s experience reflects Norman’s point about designing not only for ideal circumstances but also for scenarios where things go wrong. This reinforces the importance of creating safeguards in design to prevent errors, support users under pressure, and mitigate risks, demonstrating that effective design is as much about empathy and foresight as it is about functionality.

In reflecting on how design impacts safety and usability, an example that comes to mind is the design of fire extinguishers. While essential in emergencies, many fire extinguishers are not immediately intuitive to use, especially in high-stress situations. The sequence—pull the pin, aim the nozzle, squeeze the handle, and sweep—may seem simple, but in a crisis, it can be easy to forget steps or become disoriented, particularly for those who haven’t received training.

Midterm Project: Labyrinth Race

Concept:

Sketch Link: p5.js Web Editor | Labyrinth Final (p5js.org)

Inspired by the Greek myth of Theseus, who navigated the labyrinth to defeat the Minotaur, I envisioned a game that captures the thrill of navigating a maze to reach a goal. In this two-player game, only one can survive the labyrinth. Players must race to the safe zone at the center of the maze while avoiding touching the walls of the maze. The first player to reach the center wins, while the other faces death. Alternatively, if a player touches the walls at any point, they are immediately eliminated, granting victory to their opponent by default. This intense competition fosters a sense of urgency and strategy as players navigate the ever-changing labyrinth.

Design features:

  • Dynamic Maze Generation: Each time a new game begins, the maze layout changes, ensuring that no two playthroughs are the same. This feature keeps the gameplay fresh, challenging players to adapt to new environments every time they enter the labyrinth.
  • Boundary Enforcement: Players are confined to the game canvas, ensuring they stay within the limits of the maze. Exiting the bounds is not an option.
  • Customizable Player Names: Players can personalize their experience by entering their own names before starting a match. For those who prefer to jump right into the action, the game also offers default character names, maintaining a smooth and accessible start.
  • Animated Player Sprites and Movement Freedom: Each player is represented by a sprite that shows movement animations. The game allows Movement in 8 directions.
    EightDirMovement21
  • Fullscreen Mode: For a more immersive experience, players can opt to play the game in Fullscreen mode.
  • Collision Detection: The game includes collision detection, where touching the maze walls results in instant disqualification. Players must carefully navigate the labyrinth, avoiding any contact with the walls or face elimination.

Process:

The most challenging and time-consuming aspect of this game was designing the maze. The logic used to ensure that a new maze is generated with every iteration involves several key steps:

Maze Initialization: The process begins by drawing six concentric white circles at the center of a black canvas, with each subsequent circle having a radius that decreases by 60pt. This creates the foundational layout of the maze.

let numCircles = 6; // Number of white circles in the maze
let spacing = 50; // Spacing between circles

// Loop to draw white circles
for (let i = 0; i < numCircles; i++) {
    let radius = (i + 1) * spacing; // Calculate radius for each circle
    noFill(); 
    stroke(255); 
    strokeWeight(2); 
    ellipse(width / 2, height / 2, radius * 2, radius * 2); // Draw each circle
}

Black Circle Placement: Black circles are then placed randomly on top of the white circles. The innermost black circle is erased to create a clear entrance. The number of black circles on each white circle increases as we move outward, adding complexity to the maze design.

// Function to generate positions for the black circles
function generateBlackCircles() {
    // Loop through each circle in the maze
    for (let i = 0; i < numCircles; i++) {
        let radius = (numCircles - i) * spacing; // Start with the outermost circle first
        let maxBlackCircles = numCircles - i; // Outermost gets 6 circles, innermost gets 1
        
        // Generate random positions for the black circles
        for (let j = 0; j < maxBlackCircles; j++) {
            let validPosition = false;
            let angle;

            // Keep generating a random angle until it is far enough from other circles
            while (!validPosition) {
                angle = random(TWO_PI); // Generate a random angle
                validPosition = true; // Assume it's valid

                // Check if the angle is far enough from previously placed angles
                for (let k = 0; k < placedAngles.length; k++) {
                    let angleDifference = abs(angle - placedAngles[k]);
                    angleDifference = min(angleDifference, TWO_PI - angleDifference);
                    if (angleDifference < minAngleDifference) {
                        validPosition = false; // Too close, generate again
                        break;
                    }
                }
            }
            // Calculate the coordinates and store them
            let x = width / 2 + radius * cos(angle); 
            let y = height / 2 + radius * sin(angle);
            blackCircles.push({ x: x, y: y, angle: angle, radius: radius });
            placedAngles.push(angle); // Save the angle for future spacing
        }
    }
}

Collision Detection for Black Circles: An overlap function checks to ensure that no two black circles spawn on top of one another. This is crucial for maintaining the integrity of the maze entrances.

// Function to check if the midpoint of a line is overlapping with a black circle
function isOverlapping(circle, x1, y1, x2, y2) {
    let { midX, midY } = calculateMidpoint(x1, y1, x2, y2);
    let distance = dist(midX, midY, circle.x, circle.y);
    return distance < circleRadius; // Return true if overlapping
}

Maze Line Generation: For each black circle, a white maze line is generated that is perpendicular to the white circles. Another overlap function checks whether the maze lines overlap with the black circles, adjusting their positions as necessary.

// Function to draw the lines from the edge of the black circle
function drawMazeLines() {
    stroke(255); // Set the stroke color to white
    for (let i = 1; i < blackCircles.length - 2; i++) {
        let circle = blackCircles[i];
        // Calculate starting and ending points for the line
        let startX = circle.x - (50 * cos(circle.angle)); 
        let startY = circle.y - (50 * sin(circle.angle));
        let endX = startX + (50 * cos(circle.angle));
        let endY = startY + (50 * sin(circle.angle));
        // Check for overlap and adjust rotation if necessary
        while (isAnyCircleOverlapping(startX, startY, endX, endY)) {
            // Rotate the line around the origin
            let startVector = createVector(startX - width / 2, startY - height / 2);
            let endVector = createVector(endX - width / 2, endY - height / 2);
            startVector.rotate(rotationStep);
            endVector.rotate(rotationStep);
            // Update the coordinates
            startX = startVector.x + width / 2;
            startY = startVector.y + height / 2;
            endX = endVector.x + width / 2;
            endY = endVector.y + height / 2;
        }
        line(startX, startY, endX, endY); // Draw the line
    }
}

Test Sketch:

 Final Additions:

  • Player Integration: I introduced two player characters to the canvas. Each player was assigned different movement keys and given 8 degrees of movement as players needed to utilize their movement skills effectively while avoiding collisions with the maze walls.
  • Collision Detection: With the players in place, I implemented collision detection between the players and the maze structure.
  • Game States: I established various game states to manage different phases of the gameplay, such as the start screen, gameplay, and game over conditions. This structure allowed for a more organized flow and made it easier to implement additional features like restarting the game.
  • Player Name Input: To personalize the gaming experience further, I incorporated a mechanism for players to input their names before starting the game. Additionally, I created default names for the characters, ensuring that players could quickly jump into the action without needing to set their names.
  • Sprite Movement: I dedicated time to separately test the sprite sheet movement in a different sketch to ensure smooth animations. Once the movement was working perfectly, I replaced the placeholder player shapes with the finalized sprites, enhancing the visual appeal and immersion of the game.
  • Audio and Visual Enhancements: Finally, I added background music to create an engaging atmosphere, along with a game-ending video to show player death. Additionally, I included background image to the game screen.

Final Sketch:

Future Improvements:

One significant challenge I encountered with the game was implementing the fullscreen option. Initially, the maze generation logic relied on the canvas being a perfect square, which restricted its adaptability. To address this, I had to modify the maze generation logic to allow for resizing the canvas.

However, this issue remains partially unresolved. While the canvas no longer needs to be a perfect square, it still cannot be dynamically adjusted for a truly responsive fullscreen mode. To accommodate this limitation, I hardcoded a larger overall canvas size of 1500 by 800 pixels. As a result, the game can only be played in fullscreen, which may detract from the user experience on smaller screens or varying aspect ratios.

Moving forward, I aim to refine the canvas resizing capabilities to enable a fully responsive fullscreen mode.

 

Midterm Progress: Defeat the Minotaur

Concept:

I’ve been reading books on Greek mythology lately, and I’ve noticed how certain tropes keep resurfacing in these tales. Anyone familiar with Greek myths knows that labyrinths are a huge aspect of these stories. Like, take Orpheus, who went into the underworld to bring back his dead wife. His story brought people to tears, and it really showed how much of a maze the underworld was and how insane it was for him to brave that journey.

Since I’m a literature major too, I thought I had the thought of working on a project that would be a fusion of both of my majors for once. That’s how I got the idea to create my own labyrinth game, which if the name of the project does not already give away— is an adaptation of the Greek myth of Theseus, who went into the Minotaur’s labyrinth to defeat him.

Knossos Palace in Assassin's Creed Odyssey: A Historian-Scientist ...

 

But, honestly, if I were in Theseus’s shoes, my strategy wouldn’t be to kill the Minotaur. With my very human physique, I’m not cut out for that. I’d be focused on getting to safety instead. So, the idea for this game is simple: you start with a circular maze that has multiple entrances around the edge. There are two players — one using the arrow keys to move and the other using WASD. At the start of the game, each player must choose a separate entrance to the maze, and the goal is to find their way to the safe zone in the center before the Minotaur catches them. Once a player reaches the safe zone, both players can’t move anymore, and the one who didn’t make it gets eaten by the Minotaur.

MYTHS OF THE LABYRINTH | Ashmolean Museum

Players can choose to play as Theseus or Orpheus, and the game also ends if anyone touches the boundary walls of the maze.

Design:

The maze itself will be created using vectors. Each segment of the labyrinth will be drawn programmatically, ensuring that the structure remains consistent while allowing for flexibility in the maze’s layout. For the characters, I’ll use a sprite sheet to handle movement animations, enabling smooth transitions between different frames as the characters navigate through the maze.

In terms of the pseudo-algorithm for the game:

1. Maze Construction: The maze will be created using vector functions. I’ll define an array of vectors to represent the maze walls and entrances. This will allow for flexible maze design. The setup() function will initialize the canvas and call a function to draw the maze based on pre-defined parameters.

2. Player Movement: Two player objects will be created, each with properties for position and speed. The players will respond to keyboard inputs, with Player 1 controlled by the arrow keys and Player 2 using WASD. The draw() function will include a movement handler that updates each player’s position based on key presses.

3. Safe Zone Mechanism: A function will check if one of the players reaches the safe zone. When this occurs, the game will stop updating the players’ positions. This can be handled with a simple boolean flag that controls movement.

function checkSafeZone() {
    if (dist(player1.x, player1.y, safeZone.x, safeZone.y) < safeZone.radius) {
        gameActive = false; // Stops player movement
    }
}

4. Minotaur Appearance: The Minotaur will be an object with properties for position and movement speed. When the safe zone is reached, a function will activate the Minotaur, setting its initial position off-screen. The Minotaur will then move towards the player outside the safe zone using a straightforward pathfinding algorithm or direct movement logic.

5. Game Over Condition: A function will determine if the Minotaur reaches the player outside the safe zone. If they collide, the game will trigger a game over state, displaying who won and include restart button.

The Challange:

 

Making the Minotaur move toward the player outside the safe zone feels like a challenging task for a few reasons. First, I need to allow the Minotaur to override the boundary wall constraints, which means it would glide over any obstacles on the screen to reach the player. Since the Minotaur won’t be restricted by the usual collision detection that applies to the maze walls, it should make it a little simpler to implement the tracking mechanism.

To implement this, I envision starting with a function that checks which player is outside the safe zone. From there, I’ll need to determine how to return the coordinates of that player so the Minotaur can track them effectively. The tricky part is figuring out how to make the Minotaur move towards those coordinates. I’ll need to increment or decrement its x and y positions based on the player’s location.

The real challenge lies in determining when to increment or decrement. I need to devise a way for the program to recognize the Minotaur’s position relative to the player’s coordinates. This means creating conditions that account for whether the Minotaur is to the left, right, above, or below the player. Getting that logic right will be crucial for smooth movement, and I can already tell it’s going to take some trial and error to nail down the precise mechanics.

Risk Management:

To reduce the risk of encountering major issues while programming the Minotaur’s movement, I’ve taken several proactive measures. First, I’ve broken down the implementation into manageable tasks, focusing on one aspect at a time, such as determining the player’s position outside the safe zone before programming the Minotaur’s movement. This incremental approach allows me to test each function independently and ensure that everything works as expected before integrating it into the game.

Additionally, I’ve researched pathfinding algorithms and movement logic used in similar games, which has given me insights into best practices for implementing smooth character movement. I’m also making use of clear comments in my code to keep track of my thought process and logic, which should help me debug any issues that arise more easily.

Finally, I’ve set up a simple testing framework where I can run the game at various stages of development. This way, I can identify potential problems early on and address them before they become larger obstacles.

 

 

Reading Reflection 4

Computer Vision for Artists and Designers:

In reflecting on this paper on computer vision, I find its potential utility for artists and designers both compelling and distinct from human vision. The difference between computer vision and human vision mostly comes down to senses—humans use their five senses to process information, while computers need fixed algorithms to handle physical or visual data. But once that data is processed, we can program the computer to trigger a specific action based on what it sees.

A lot of the techniques in the paper revolved around pixel tracking, which is basically comparing one pixel to a predefined one until a match is found. This could be useful in something like a salad-sifting machine, where I could train the model to recognize red pixels as tomatoes and have it remove all the red objects, essentially removing all the tomatoes from my salad.

As for how computer vision’s ability to track and surveil affects its use in interactive art, I think it’s a double-edged sword. On one hand, it’s amazing for creating immersive, responsive art that can change depending on how people interact with it—like tracking movement or emotions to alter the artwork in real-time. But at the same time, the idea of constant surveillance can be slightly problematic, especially in art spaces where people want to feel free and unobserved. So, there’s this tension between using computer vision to enhance interactive experiences and making sure it doesn’t cross any lines when it comes to privacy.

Reading Reflection 3

The Design of everyday things:

In “The Design of Everyday Things,” Don Norman critiques the reliance on logic in engineering design, arguing that effective design should anticipate and account for human error. While I understand his perspective, I find myself disagreeing with the notion that design flaws are solely to blame for user errors. Given the vast variability in human behavior, it’s nearly impossible to design for every possible error. For example, consider a standard hinged door: while it might pose no issue for an average person, a shorter individual may struggle with a handle positioned too high. Adjusting the handle height to accommodate one group could inadvertently create challenges for another.

That said, I agree that designers should strive to make their products as intuitive as possible for the average user. This brings me to my frustration with mixer grinders, which I find notoriously difficult to manage. Each new brand presents a unique setup process, often leading to confusion and errors. I believe the design of these devices could be greatly improved by using magnetized parts for easier assembly and reducing the number of buttons to just a power switch and perhaps a safety mechanism, as well as one additional button for varying power levels.

Additionally, one of Norman’s design principles that could enhance interactive media projects is the use of intuitive icons on buttons. These icons should visually convey the action triggered by the button, making it easier for users to understand and interact with the interface.

 

Assignment 4: Too much Espresso

Concept:

In this project, I visualized the frequency of Google searches for the word “espresso” since the beginning of 2024. My inspiration stemmed from the popularity of Sabrina Carpenter’s song “Espresso,” which has captured attention and sparked interest since its release early this year. This trend led me to hypothesize that the search volume for “espresso” would similarly experience a notable increase.

Sabrina Carpenter sweetens up Coachella 2024 with new retro pop single ...

To explore this hypothesis, I aimed to create a visual representation that illustrates the correlation between the song’s popularity and the search frequency of the term “espresso.” I envisioned an effect that mimics espresso pouring out of a teacup, with the volume of the pour symbolizing the number of searches. This is accomplished using circles: the larger the circle, the greater the volume of searches.

Highlight:

A key highlight of this project was my attempt to ensure that the color of the circles corresponded to the volume of searches for “espresso.” I aimed to create a visual gradient where the shades of brown varied in darkness or lightness based on the search frequency. To achieve this, I mapped the espresso values to a color variable, allowing me to adjust the fill color of the circles by assigning this color variable as an argument to the fill().

  // Color based on espressoValue with brown tones
let colorVal = map(dataPoint.espressoValue, 0, maxValue(), 10, 120); // Adjust the color range for darker tones
fill(colorVal, 40, 20); // More muted brown tones
noStroke();

Finding the right numbers for the brown tones was also a matter of trial and error.
Reflections:

The final sketch of this visualization organizes time in an ascending manner, with the top of the y-axis representing the beginning of 2024 and the lower end depicting the months leading up to the present. The size of the circles indicates the volume of searches, while the shades of brown inversely correlate with search frequency—darker shades represent lower search volumes, and lighter shades signify higher volumes. This relationship may appear counterintuitive to viewers, highlighting one of the significant flaws in this project.

In future iterations, I would aim to reverse this color representation for clearer communication of the data. Additionally, I would like to enhance the aesthetic of the espresso pouring from the cup to create a more natural and visually pleasing effect.

Reading Reflection 2

The art of interactive design:

Chapter One of “The Art of Interactive Design,” Chris Crawford uses the example of conversation to explain the importance of feedback. He points out that just like in a conversation between people, where you expect immediate and relevant responses to keep the dialogue going, interactive systems also need to provide clear and timely feedback to keep users engaged.

Applying this idea to my artwork or interactive sketch, I should think of the system as if it were another person in a conversation with the user. If the system doesn’t respond quickly or appropriately, it’s like talking to someone who doesn’t reply or doesn’t give useful responses. This would make the interaction feel disconnected and less interesting.

In my p5.js sketch with bubbles, if the outer circles don’t react well to user interactions, it’s like having a conversation partner who ignores what you say. For example, if the outer bubbles don’t clearly expand or contract in response to user actions, or if clicking on a bubble doesn’t produce a visible effect, it would be frustrating for users. They wouldn’t get the feedback they need to understand what’s happening or adjust their actions.

To improve this, I should make sure that the artwork responds clearly and promptly to user actions, just like a good conversation partner would. This means making the outer bubbles change size in a noticeable way when interacted with, and adding visual or sound effects when bubbles are clicked. This approach makes the system feel more alive and engaging, similar to how a lively conversation keeps people interested and involved.

Assignment 3: Wallmart OSU

Concept:

In this project, I utilized objects and classes to recreate one of my favorite rhythm games called OSU. I took inspiration from OSU’s circles mode gameplay where the player needs to click on the ‘beat’ circle once an outer contracting circle coincides with the inner ‘beat’ circle, in time with the rhythm as referenced in the image below.

osu! Skins | Circle People

Highlight:

During the creation of this sketch, I began by simplifying the interface and establishing the core functionality. I decided to start with a canvas containing a set number of circles. When a viewer hovers their mouse over the canvas, outer circles corresponding to the initial circles would appear. These outer circles would contract until their diameter reached zero, then expand again up to a predefined maximum diameter. Additionally, if the viewer clicks on a circle while the outer circle is either within or touching the inner circle, both the inner circle and its respective outer circle would disappear.

To achieve this, I first developed the Bubble class to handle the drawing of the initial circles on the canvas. Next, I created the outerBubble class, which managed the appearance and resizing of the outer circles. I designed functions to make these outer circles appear and update their diameter accordingly.

// Create an outer expanding circle for the bubble
bubblepopper() {
  let outerCircle = new outerBubble(this.x, this.y, 150, this.colorValue);
  this.outerBubble = outerCircle;
  expandingBubbles.push(outerCircle);
}

A key challenge was ensuring that the outer circles correctly enveloped their respective inner circles. I solved this by calling the outerBubble class functions from within the Bubble class, which allowed the outer circles to align precisely with the inner circles.

function mousePressed() {
  // Remove bubble and expanding circle if clicked within the bubble and near the circle
  for (let i = 0; i < bubbleList.length; i++) {
    if (bubbleList[i].click() && expandingBubbles[i].diameter - bubbleList[i].radius < 5) {
      bubbleList.splice(i, 1);
      expandingBubbles.splice(i, 1);
    }
  }

The most difficult part of the project was making the circles disappear when clicked at the right moment. I tackled this by using the splice() method to remove elements from the arrays containing both the inner and outer bubbles. By looping through these arrays, I was able to erase the clicked bubbles efficiently.


Reflections:

In completing this sketch, I aimed to enhance the user experience by introducing complexity through a more interactive and skill-based challenge. The current version allows users to pop bubbles when the outer circle is touching or overlapping with the inner circle. However, I realized that this mechanic doesn’t fully capture the level of precision I initially envisioned for the project.

For future improvements, I would like to introduce a condition where the user needs to be very precise with their timing. Instead of allowing the bubble to be clicked as long as the outer circle touches the inner circle in any way, I want to restrict the interaction so that the bubble can only be popped when the outer circle perfectly aligns with the outline of the inner circle. This would require more skill and quick reflexes from the user, making the game more engaging and challenging. The added difficulty would create a more rewarding experience for players as they master the timing needed to pop multiple bubbles in one session.

Reading Reflection 1

Casey Reas’ Eyeo talk on chance operations:

After watching Casey Reas’s assessment of randomness in art, I’m convinced of its valuable role, especially in the STEM field, where it allows researchers to predict and analyze randomness in their subjects. His explanation reminded me of a study where Japanese researchers used algae to optimize their rail transport system. The algae grew in random, unpredictable ways, yet always found the most efficient pathway to form a network based on the pattern or map of the space in which it was cultured. (In 2010, a team of researchers from Japan and the U.K. fed a slime mold with nutrients arranged to imitate the nodes of the Tokyo subway system. The resulting network closely resembled the actual subway network, leading to the development of biologically inspired adaptive network design.) This study highlights the potential of introducing randomness to the template designs of objects, something Casey Reas often emphasizes in his work.

When it comes to incorporating randomness in my own projects, I find it most effective in creating animations. For example, using a random number generator and initializing it to a variable, then applying that variable as an argument for certain shapes in my self-portrait sketch, allowed me to simulate the movement of the ‘mouth’ shape, giving the illusion of the sketch talking. To me, randomness is most enjoyable and useful when applied in animations.

In the balance between total randomness and complete control, I prefer maintaining more control over an object while leaving some variables to function randomly. This approach not only makes the model more reliable but also allows me to observe and understand the specific randomization patterns more clearly. Much like in research, where we use a “control” scenario to keep experiments fair, having a balance between control and randomness helps detect how certain variables influence the behavior of others.

Assignment 2: Fuzzy Brain

Concept:

In this project, I draw inspiration from the geometric artworks discussed in: COMPUTER_GRAPHICS_AND_ART_Aug1977.  My goal with this project was to explore the potential of ‘for’ loops to generate grids of symmetrical, curved lines, creating a structured, rhythmic design. However, I hoped to disrupt this symmetry by introducing heavy distortion, with the intention of simulating the visual effect of mind fog. The resulting artwork presents a uniform arrangement of curves that distort and displace when the cursor hovers over it, evoking a sense of disorientation and randomness—mirroring the feeling of brain fog.

Highlight:

I’m particularly proud of the distortion animation I added to this sketch. By utilizing the dist() function, I created interactive conditions that are activated when the mouse hovers over the Bézier curves. Using an if statement, I introduced random increments within a range of negative to positive values to the variables used as arguments for the original Bézier curves. This approach helped change the positions of the curve lines at random, adding distortion and creating the brain fog effect that I intended. Additionally, reducing the frame rate helped give the animation a 90’s cartoon effect aesthetic.

// mouse hover animation
    if (dist(mouseX, mouseY, x, y) < 300) {
      // displace lines randomly
      x1 += random(-20, 10);
      y1 += random(-30, 10);
      x2 += random(-40, 10);
      y2 += random(-50, 10);
      x3 += random(-60, 10);
      y3 += random(-70, 10);
      x4 += random(-80, 10);
      y4 += random(-90, 10);
    }
    
    bezier(x1, y1, x2, y2, x3, y3, x4, y4);

Reflections:

While working on this project, I experimented with creating symmetrical grids and distorting the curves to simulate mind fog. Initially, I focused on generating the grids using for loops, but as I introduced interaction through mouse hover effects, I realized how much potential this had to enhance the dynamic nature of the piece. The use of dist() and if() functions to trigger random distortions worked well in creating a more immersive experience.

Looking back, I think there’s room for improvement in making the distortions more fluid and gradual. Currently, the randomness of the distortions can feel abrupt, so in future iterations, I would explore using easing functions to smooth the transitions.