The idea for this project actually came from a quiet weekend when I watched the Chinese movie Letters to Grandma(《给阿嬷的情书》). The story really hit home for me. It’s about a time before smartphones and social media, when people had to write real letters across oceans, waiting endlessly just to share their feelings with family and loved ones. That deep sense of yearning and patience resonated with me so much that I was like, “I really, really want to build something that brings that letter-writing feeling back.”
In the film, flowers and ocean waves are huge symbols of distance and memory, and I was so set on bringing those exact visuals into my project, along with some traditional Chinese aesthetic touches. I spent a ton of time tweaking the visuals—making the falling petals and moving waves look just right—and honestly, seeing it all come together gave me such a huge sense of achievement!
At first, I just had on-screen typing, but then I realized just typing wasn’t quite fun or meaningful enough. I really wanted it to feel real. After getting some great advice from my professor, I added a feature where you can actually export what you write into a real Word document (.docx). That way, it’s not just a digital demo—you get to save your words as a real letter, just like people used to do back in the day.
How this was made
The Home Page, Layered Aesthetics & Swaying Physics
For the home page, Instead of relying on a flat, static layout, I spent a lot of time crafting a rich 3D depth by splicing three separate flower images (flower.png, flower2.png, etc.) together so that the hanging stationery looks like it’s literally growing out from deep inside the blossoms. However, the most challenging technical hurdle here was simulating realistic pendulum motion for the hanging ropes and letter cards. I solved this by using trigonometric sine waves (sin()) tied to frameCount,(learn from YouTube video)generating independent speeds, phase offsets, and curved paths for each individual rope:
function createRopes() {
let lengths = [250, 340, 270, 395, 305, 370, 260, 420, 285, 355];
let positions = [-115, -86, -57, -29, -10, 10, 29, 57, 86, 115];
ropes = [];
for (let i = 0; i < 10; i++) {
ropes.push({
offsetX: positions[i], length: lengths[i],
swingSpeed: random(0.008, 0.02), phase: random(TWO_PI),
swingAmount: random(8, 14), rotationAmount: random(0.04, 0.08)
});
}
}
function drawRopes() {
let ropeStartY = getRopeStartY();
for (let rope of ropes) {
let swing = sin(frameCount * rope.swingSpeed + rope.phase);
let sway = swing * rope.swingAmount;
beginShape();
for (let j = 0; j <= 24; j++) {
let t = j / 24;
let y = ropeStartY + rope.length * t;
let curve = (t + sin(t * PI) * 0.6) * sway;
vertex(baseX + curve, y);
}
endShape();
}
}
Interactive Writing, Text Scrolling
When users click a letter to transition into the writing view, I built a virtual keyboard to increase interactivity .Every time a key is pressed.it triggers a audio feedback using singleKey.wav and lights up a temporary press highlight (flashKey) to mimic a real keyboard:
function drawKeyboard() {
let rows = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"];
let keyW = 32, keyH = 34, gap = 4, startY = 475;
for (let r = 0; r < rows.length; r++) {
let letters = rows[r];
let totalWidth = letters.length * keyW + (letters.length - 1) * gap;
let startX = (width - totalWidth) / 2;
for (let i = 0; i < letters.length; i++) {
let letterKey = letters[i];
let x = startX + i * (keyW + gap), y = startY + r * (keyH + gap);
let isPressed = flashKey === letterKey && frameCount < flashUntilFrame;
if (isPressed) { fill(0); stroke(0); rect(x, y, keyW, keyH, 4); fill(255); }
else { fill(255, 160); stroke(0, 120); rect(x, y, keyW, keyH, 4); fill(0, 200); }
noStroke(); text(letterKey, x + keyW / 2, y + keyH / 2);
}
}
}
function drawTypedText() {
push();
textFont("cursive"); textSize(20); fill(50, 50, 50);
let lines = wrapText(typedText, maxCharsPerLine).slice(-9);
for (let i = 0; i < lines.length; i++) {
text(lines[i], textStartX, textStartY + i * textLineHeight);
}
if (typingActive && frameCount % 60 < 30 && lines.length > 0) {
let lastLine = lines[lines.length - 1];
let cursorX = textStartX + textWidth(lastLine);
let cursorY = textStartY + (lines.length - 1) * textLineHeight;
line(cursorX + 2, cursorY + 2, cursorX + 2, cursorY + 20);
}
pop();
}
Ocean Waves & Practical Word Export
To transition out of the writing screen, I made a design use multi-layered ocean waves (drawWave()) . By stacking multiple wave layers with varying frequencies, semi-transparent water stream lines, and glowing foam highlights, the ocean rises to cover the canvas:
function drawWave() {
push(); noStroke(); wavePhase += waveSpeed;
let baseY = height - calmWaveHeight, riseHeight = 0;
if (waveSending) {
let elapsed = frameCount - waveSendStartFrame;
let t = constrain(elapsed / waveSendDuration, 0, 1);
let maxRise = height * 0.52;
riseHeight = t < 0.46 ? maxRise * easeWave(t / 0.46) : t < 0.64 ? maxRise : maxRise * (1 - easeWave((t - 0.64) / 0.36));
}
let backY = baseY - riseHeight;
drawWaveLayer(backY + 32, 27, 0.012, 0, waveColors[0], 0.7);
drawWaveLayer(backY, 8, 0.040, 6.0, waveColors[4], 1.0);
drawInternalStreams(backY);
pop();
}
Once the wave completely clears the screen, it triggers the document saving process. To be honest, I didn’t write the Word export logic entirely from scratch—I did some research online to figure out how web apps export files, discovered the docxlibrary, and then pieced together and adapted the code to fit my project’s data flow
One of the biggest hurdles I ran into during development was handling mouse interactions on the home screen because the hanging letter cards are constantly swinging, rotating, I had to overcome this challenge by researching and implementing coordinate transformations to accurately detect mouse hovers on those moving cards.
However, I still have other unresolved issues, such as text layout : setting a fixed character limit per line continues to cause formatting bugs where wide letters like ‘W’ overflow the page while narrow characters wrap way too early, and I am still looking for a robust way to fix this line-wrapping problem.
I really wanted to add background music , but I struggled with it for an entire day—I kept running into errors every time I tried to upload my MP3 file, likely because the file size was too large, so I ultimately had to give up on the background music .
AI used for debugging errors, providing ideas for the text-wrapping logic, and helping implement the transformation math needed for mouse hit-testing on the letter.
My project is inspired by the Chinese film A Letter to Grandma. The film deeply touched me because of the way it portrays love, memories, and connection through letters.
Before the age of phones and instant messaging, letters were one of the most important ways for people to stay connected. A letter was not only a way to communicate information, but also a way to express emotions that might be difficult to say in person. This made me think about how we communicate with people today. Although we can send messages instantly, we often do not take the time to carefully write down how we feel. I therefore want to create an interactive experience where people can write, save, and keep their own letters, especially letters expressing their thoughts and feelings towards family and friends.
I also wanted to incorporate some important visual elements from the film. Flowers and birds are meaningful symbols throughout the movie, so I decided to bring these elements into my own work. The flower represents memory, love, and something that continues to grow, while the bird represents the movement of a message from one person to another.
Current progress
At the moment, I have completed the first major part of the project.
The papers are currently hanging and gently moving, creating the feeling that they are suspended in the air.
I am happy with this stage because the basic visual language of the project is already beginning to communicate the idea of letters, memories, and connection.
Next step
My next major goal is to make each piece of paper interactive.When the user clicks on a letter, I want the screen to transition into a writing interface.
The idea for this part actually came from something very simple. Over the weekend, I was bored and found a website where I could just sit down and type. This made me think about how writing itself could become part of the artwork.
The Writing Interface
I also want the writing interface itself to feel artistic rather than looking like a normal text editor.
My idea is to design a visual keyboard on the screen.
When the user presses a key:the corresponding key on the visual keyboard will react,the typed letter will appear on the screen,the text will gradually form into a letter
I also want to introduce a small bird into this interaction.
As the user types, a little bird could fly from the keyboard toward the words on the screen.
The bird would act almost like a messenger, connecting the act of typing with the idea of sending a letter.
This connects back to the original inspiration from the film: a letter is not just text; it is something that carries emotion from one person to another.
Computer vision differs from human vision because computers do not naturally understand images and videos in the same flexible way that humans do. Instead, computers rely on specific algorithms and assumptions to identify useful information from pixels. For example, an algorithm may need movement, differences in brightness, or differences between an object and its background in order to detect something. Because of this, computer vision can be sensitive to changes in lighting, background, or whether an object is moving.
There are several techniques we can use to help computers see and track what we are interested in. Frame differencing can be used to detect movement by comparing two video frames, while background subtraction can separate people or objects from a known background. Brightness thresholding can also identify objects based on how light or dark they are, and brightness tracking can be used to follow a bright object such as a flashlight. We can also improve computer vision by designing the physical environment, such as using strong lighting, high contrast.
I think computer vision’s ability to track and surveil people can make interactive art more interesting and responsive, but it also raises concerns about privacy, consent, and how people’s information is used. For example, people may feel uncomfortable if a camera is tracking their movements or recording their images without them knowing. I think artists can address these ethical issues by clearly informing participants about what is being collected, how the information will be used, and whether it will be stored or shared. They should also give people the choice to participate or not whenever possible. At the same time, artists can use surveillance itself as a theme to make people think about how technology is used to monitor and understand people. Therefore, I think computer vision can be very powerful in interactive art, but it should be used in a way that respects people’s privacy and gives them a clear understanding of what is happening.
The idea for this project came from a hilarious clip I watched of The Tonight Show Starring Jimmy Fallon, where Jimmy played a word-association game with Benedict Cumberbatch (who plays Doctor Strange). In the game, Jimmy would ask random, nonsensical questions, and Benedict had to shout out random words—verbs, nouns, adjectives—without thinking. In the end, all those completely unrelated words were stuffed into a pre-written script, creating a surprisingly funny and absurd little story.
I loved how something as simple as pulling random words out of context could instantly create unexpected comedy. That made me wonder: Can I recreate that same feeling of hilarious randomness using code?
By setting up three separate word banks—subjects, verbs, and objects—and picking one from each using p5.js’s random()function, this project acts as a mini story generator. Every time you click, it stitches together a completely unpredictable sentence, turning basic code into a fun, surreal storytelling game.
How this was made?
The overall logic of this program is very clean and straightforward, without any unnecessary complexity. First, I stored 20 words into three separate arrays for subjects, verbs, and objects. Next, I set up mousePressed() so that every time you click, the code uses random() to pull one random item from each of those three arrays.
To make it look more polished, I played around with typography, positioning, and color. I arranged the three parts vertically using specific Y-coordinates, picked a nice clean font, and made the middle verb bright red so it stands out as the action of the sentence.
Reflection
To be honest, this project was relatively simple, and I intentionally kept my approach straightforward without overcomplicating things. I focused on building a clean, functional logic that works reliably rather than wrestling with complex algorithms.
However, looking at the finished piece, I definitely see room to make the user experience much richer in the future:Adding subtle, soft typing sound effects whenever a new sentence is generated to give the clicks better tactile feedback.Or experimenting with fade-in effects or gentle motion trails so the words slide in smoothly instead of just abruptly popping onto the screen.
1.What’s something (not mentioned in the reading) that drives you crazy and how could it be improved?
A great example of bad design in daily life is the automatic faucet in public restrooms. Designers like to hide the sensor inside the shiny metal tap to make it look clean and nice. However, this removes all visual signifiers. Users cannot see where the sensor is. They have to wave their hands around blindly to guess the right spot. This causes a lot of frustration.
To make things worse, the water often comes out with a short delay. In good design, feedback must be fast. Because of the delay, users think the faucet did not see them. They pull their hands away. At that exact moment, the water shoots out. This breaks the user’s simple conceptual model. They expect water to flow as soon as their hands are under the tap.
We can fix this problem very easily. First, designers can print a simple dot or hand icon on the tap. This gives a clear signifier for the sensor area. Second, a small light can turn on the instant the sensor detects a hand. This gives immediate feedback. Users will know the water is coming, so they will not pull their hands away.
2.How can you apply some of the author’s principles of design to interactive media?
To apply Norman’s principles to interactive media, we must change how we design user actions, system feedback, and layouts.
First, we apply signifiers by making active elements stand out from background elements. Since digital surfaces are flat, we must use visual contrast, lighting, or motion to guide the user’s eyes. This clearly shows users what can be changed and where to click.
Second, we apply natural mapping by matching system movements with real-world physics. For example, dragging an object up should increase power, and moving it right should move to the next step. When controls follow natural space, users do not need to memorize rules.
Third, we apply feedback by designing immediate visual or audio responses for every action. The system should never leave the user waiting in silence. Even a tiny color change or a brief sound confirms that the system received the input.
Finally, we build a clear system image by keeping the rules and layout consistent across the entire platform. When the design logic stays the same, users can quickly form a correct conceptual model and master the system without training.
I often find myself wondering: beneath all the sleek design and high-definition media, what is the internet really made of? At its core, it’s nothing more than basic lines of text, numbers, and code.
This project is my exploration of that idea. By using basic alphanumeric characters (a-z, 0-9,@#%?&) as the fundamental “atoms” of my digital canvas, I let them generate randomly while using code to keep them organized and separated. It’s my way of turning raw, invisible data into a visual piece of art.
How this was made?
One problem I met at first is how to prevent the letters from overlap.instead of writing super long and confusing if statements to prevent letters from crashing into each other, I used a return;. If a new letter gets too close to an old one, the code just stops right away and tries again next time. It’s super simple and keeps the code clean.
for (let i = 0; i < charCount; i = i + 1) {
let c = chars[i];
let d = dist(x, y, c.x, c.y);
if (d < (size + c.size) * 0.45) {
return;
}
}
I didn’t just store the letter itself in the array. I packed its X/Y positions, size, letter type, and RGB colors together into one object. This makes it super easy later—my drawCharacter() function knows exactly how to paint it.
When I was setting up the object properties, my instinct was to write something simple like “color:rgb” or directly grab an entry from color palette I thought, “Why can’t I just store the whole color array together and hand it to fill()?” But when I tried that, p5.js didn’t handle the color array the way I expected inside fill(). I was so confused until I realized I had to unpack the array and store the RGB values as three separate properties (r, g, b) inside each object so fill(c.r, c.g, c.b) could read them smoothly.
Reflection
To be completely honest, my code went wrong a million times before it finally worked. Going into this project, I knew using an array was a strict requirement, but I didn’t actually have a deep understanding of how arrays function under the hood.
I spent a huge amount of time just trying to wrap my head around one fundamental question: Why couldn’t I just pick a random letter and a position inside AddCharacter() and paint it right onto the screen? Why did I have to save everything into an array first, and then write a completely separate for loop inside draw() just to repaint it every single frame? Wrestling with this concept and breaking down p5.js’s frame-by-frame rendering loop was easily the biggest learning curve for me.
Looking back, there are definitely things I wish I could have added: I wanted to experiment with cooler typography effects, like adding soft ghosting or motion shadows behind the characters, but it felt a bit too complex for my current skill level, so I decided to keep the visuals simple for now.And I’d love to explore adding interactivity in future versions—such as letting users click anywhere on the canvas to spawn specific words, using mouse clicks to control the generation order, or adding a click-to-clear button to reset the canvas and start over.
Based on Chris Crawford’s reading, string interaction acts like a two way conversation that needs solid listening, thinking and responding, and one weak part can break the whole experience.Beyond this idea,I also believe a good interactive system should feel approachable for regular users and give them room to experiment without breaking everything.human comfort also shaped the interaction.Even if a system completes the input-process-output loop, it stills fails as interactive work if users feel worrying they will crash the sketch etc.It should also meet users halfway:it does not demand expert knowledge to participate.New users can find satisfying results with simple actions, while people who dig deeper can still find more complex effects.there should not only be one “correct” way to interact.Multiple different user choice can all lead to interesting outcomes, rather than forcing users to follow one intended path.
For my own p5 sketch, I can make general improvements across Crawford’s three interaction stages. For better listening, I can try to out some music ,or built some interactions that allow users to record their own voice.For better thinking, I can let the system remember past user activity so responses build over time, instead of always triggering the same fixed outcome. For better speaking, I can add clear feedback so users can tell how their actions affect what they see.
After watching the video, I wanted to experiment with randomness in my artwork and discover what visual possibilities it could create.
I realised that the effect of chance would not be very clear within just one static image. For this reason, I used ‘mousePressed’ as an interactive feature: every click reruns all the random values and generates a brand‑new composition. This allows the viewer to see the wide variety of outcomes produced by one single system.
I chose two simple forms: bezier curves and circles of varying sizes. I defined their general shape, boundaries and direction, while letting random() decide their exact position, size and colour. Rather than placing each curve and circle by hand, I let the code produce unexpected arrangements. My aim is to observe the balances and relationships between curves and circles.
How this was made?
for (let i = 0; i < 10; i++) {
let circleX = random(30, width - 30);
let circleY = random(30, height - 30);
let diameter = random(15, 90);
noStroke();
fill(random(255), random(255), random(255));
circle(circleX, circleY, diameter);
}
For the random circles I used a for‑loop. I wanted ten circles in total. Their coordinates are random, and so are their diameters. However, I set rough limits for these values: a range for their size and another for their position. Their colours are also random.
for (let m = 0; m < 8; m++) {
drawCurve();
}
function drawCurve() {
let x1 = random(-20, width + 20);
let y1 = random(-20, 0);
for(let i = 0; i < 8; i++){
let cx1 = x1 + random(-75,75);
let cy1 = y1 + random(-65,65);
let cx2 = x1 + random(-75,75);
let cy2 = y1 + random(35,95);
let x2 = x1 + random(-55,55);
let y2 = y1 + random(65,115);
bezier(x1, y1, cx1, cy1, cx2, cy2, x2, y2);
x1 = x2;
y1 = y2;
}
}
For the lines, I was not sure at first which shape to use. After checking the p5.js reference page, I found that the bezier curve worked best for my idea. I then briefly learned how its coordinates function and how to control them, and started to build my curves.
I discovered that without startX = endX and startY = endY, all the segments would cluster near the top of the canvas and would not connect together. This created a problem. Once I noticed this issue, I adjusted my code and asked AI to help me understand why it was happening.
Reflection
I have realised that creating artwork with random() saves a lot of time. Unlike my self‑portrait project, where I had to carefully refine every single detail, randomness can unexpectedly produce really appealing patterns. I feel I could use randomness more often in my future artworks. I really enjoyed this creative process and kept experimenting throughout. Right now I have only used curves and circles, which are still quite basic and simple. I hope in the future I can use randomness to generate more complex forms.
For me, I feel that every artwork needs to represent something that the artist wants to express. An artwork should communicate an idea or feeling.Therefore, I do not want randomness to decide what my work is trying to express. Instead, I want randomness to help me decide what my work looks like.
For example, when I am creating a project, I can first decide what I want to express and what kind of overall atmosphere I want the work to have. Then, I can use randomness to control certain elements, such as shapes, lines, or color. In this way, randomness is not replacing my creative decisions. Instead, it helps me discover forms that I might not have thought of on my own.
As for the optimal balance between randomness and control, I think it is difficult to define. I do not think a 50/50 balance is necessarily the best one. It depends on the final form of the artwork and on how much randomness the artist wants to allow. In other words, I think the important question is not how much randomness we use, but how much of the final result we are willing to give up control over.
I also think randomness can help us discover things we did not know we liked. randomness can produce a result that we did not choose at first. When we see that unexpected result, we might actually like it. In this sense, randomness expands the possibilities we can choose from.
My goal was to represent not only my appearance, but also some aspects of my personality that are important to me.
Before this assignment, I had no experience with programming or p5.js. Since I wanted to do a good job on my first assignment, I spent a lot of time learning the basics, experimenting with the code, and using different resources, including AI, to help me solve problems and develop my ideas.
I realized that simply drawing myself could not fully represent my personality. Therefore, I added an interactive element using words that are related to me. Through these interactions, I wanted to give the audience more information about my personality and make the representation of myself more complete.
My goal was to make some words describing my personality appear when I press the mouse over the circles. Since I did not know how to create this interaction, I used AI to help me figure it out. Through this process, I learned how to use an if statement with mouseIsPressed and the distance between the mouse and the circle to control when the text appears. I also understood the logic behind this type of interaction, rather than just using the code without knowing how it works.
Color
For the colors, I used a website called Color Picker. I could adjust the color using its color wheel and get the corresponding RGB values. I then used these RGB values in my p5.js code to choose the colors for my project.
Shapes
For the shapes, I also used AI to help me understand how to use coordinates to control the position of different shapes. One of the most useful things I learned was how to create an arc. I learned that, besides defining its position and size, I also need to specify its start and end angles to control the direction of the arc. I found this part quite interesting.
Reflection and ideas
Reflection
I enjoyed working on this project, but it also took me a lot of time. One part that took especially long was designing the hair. Originally, I wanted to make it look more like my actual hair, with bangs and a round shape on top. I tried to use curves to create this shape, but I realized that I did not fully understand how curves work. I did not want to use code that I did not understand, so I decided to use two quarter circles instead. However, this made the top of the head look more square, and I am not completely satisfied with the result. In the future, I would like to learn how to use curves better so that I can create more complex shapes.
Another thing I noticed was that I spent a lot of time adjusting the coordinates of different elements. I often had to move them little by little and guess where they should be. For example, if an element was slightly too high or too low, I would have to keep changing its coordinates until it was in the position I wanted. I would like to learn a more efficient way to locate and adjust positions in the future.
Overall, I think this project gave me a good starting point for learning programming, and I hope that I can use these skills to create more interesting and interactive projects in the future.