Week 5 – reading

  • What’s something (not mentioned in the reading) that drives you crazy and how could it be improved?

I wis Norman’s analysis went deeper into economic incentives behind poor design. While he touches on cost-cutting measures, like companies using “the least expensive sound device” that can only beep, or single lights with confusing flash patterns instead of clear displays, he doesn’t fully address whether some companies intentionally create poor user experiences.

I suspect many businesses actually profit from user confusion. Think about subscription services with deliberately maze-like cancellation processes, or software that makes basic functions require premium upgrades (Dark UI/UX patterns). Norman notes that when people struggle with technology, “the people are blamed for not understanding the machine” – but this blame-shifting can be profitable through tech support fees, extended warranties, or forcing users toward expensive premium versions. Apple is notoriously known for doing this.

Norman hints at this when discussing cost reduction forcing designers to use inadequate feedback systems, but I wish he’d been more direct about the perverse economic incentives. Sometimes bad design isn’t just oversight – it’s strategy. Companies know users will adapt and memorise workarounds rather than switch products, especially when switching costs are high.

  • How can you apply some of the author’s principles of design to interactive media?

In my interactive media and web design work, I constantly rely on Norman’s concept of discoverability. Having built apps and websites, I’ve learnt there are established conventions most users already understand – the hamburger menu, colour changes indicating clickable text, or standard navigation patterns.

These conventions work as what Norman calls “signifiers”, they provide clues about what actions are possible and where to perform them. When I use a familiar icon or follow expected layout patterns, I’m leveraging users’ existing mental models rather than forcing them to learn arbitrary new systems in the hopes of a simpler smoother user experience.

Norman’s principle of natural mapping is also significant. Just as he advocates arranging light switches to match the pattern of the lights they control, I arrange interface elements to match users’ spatial expectations. Navigation goes where people expect it, buttons look like buttons with proper visual hierarchy, and interactive elements behave as they should.

The key insight I take from Norman is that good interactive design should feel invisible – users shouldn’t have to think about how to use it. I should be able to show my work to someone who hardly interacts with websites and be confident that they will able to navigate around my work without additional instructions. If I have to add text for something simple, it is a good indicator that I didn’t design my solution well.

Week 4 – Persian generative text

Inspiration

For this week, I was really struggling on what to do I was prompting with Claude AI and decided to go with the generative text option. I knew I wanted to do something with a different language and then decided to go with my mother-tongue, Farsi. Persian poetry is famous and so I wanted to choose a quote that had a theme, replicate that theme in p5.js with shapes and colours.

My name in Farsi ‘خاطره’ means memory, remembrance, longing – something along those lines. It was difficult to find old poetry with that word as it is more modern but I liked the theme of memories and love so I went with the following line from Rumi – Masnavi (مثنوی معنوی)

Rumi is a 13th century poet and was known for his work regarding mystical themes in Islam (Sufism). Majority of his work is in Persian and very famous.

Idea

This was the quote I went with.

let poem = {
persian: “بشنو از نی چون حکایت می‌کند، از جدایی‌ها شکایت می‌کند”,
transliteration: “Beshno az ney chon hekayat mikonad, Az jodayiha shekayat mikonad”,
english: “Listen to the reed flute, how it tells a tale, complaining of separations”,
};
I wanted the words to be floating around, referring to ‘separation’, and. I want the colours / shapes to be pink, hearts just to refer to love.
I also wanted the user to be able to see the quote altogether so that they can see the final image.

Heart

There is no heart shape in p5.js so I was looking at different examples with math function and came across this link. The artist uses sin and cos functions to connect the hearts.  https://editor.p5js.org/marlontenorio/sketches/M_BGUpfKL

I edited it to suit the image in my mind. I made it a function with the size parameter because I wanted the heart to have some sort of pounding effect.

drawHeart(size) {
        beginShape();
        let s = size * 0.5;
        for (let t = 0; t < TWO_PI; t += 0.1) {
            let x = s * (16 * pow(sin(t), 3)) * 0.03;
            let y = -s * (13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t)) * 0.03;
            vertex(x, y);
        }
        endShape(CLOSE);
    }

Interactivity

Click Mouse

I wanted the user to be able to add words or hearts when the mouse is pressed.

function mousePressed() {
    hearts.push(new Heart());
    texts.push(new Text(mouseX, mouseY));
    
    // Limit elements
    if (hearts.length > 15) hearts.shift();
    if (texts.length > 15) texts.shift();
}

I added a limit so the program won’t be too complex. The words added would be from either the Persian, transliteration, or English – and at the point of the mouse. The hearts would simply be added randomly on the canvas.

SpaceBar – pressed

I wanted a way for users to add more words to screen, but a bunch of words. So I added the keyPressed function for the spacebar so that they user can see more words at once.

function keyPressed() {
    if (key === ' ') {
        for (let i = 0; i < 3; i++) texts.push(new Text());
    }

 

A / a – pressed

I wanted a way for the user to see the 3 versions of the poem at once, so I just used the A button as that trigger.

I have a Text class that will choose random words from the 3 lines verses and then have the floating simulation similar to the hearts. I also wanted these words to have some lifespan and disappear into the background slowly to reference the theme of the separation and memory.

class Text {
    constructor(x = random(100, width-100), y = random(100, height-100)) {
        this.x = x;
        this.y = y;
        this.text = this.getRandomText();
        this.size = random(14, 20);
        this.life = 0;
        this.maxLife = random(400, 800);
        this.dx = random(-0.3, 0.3);
        this.dy = random(-0.2, 0.2);
    }

Here is the display function and how it uses alpha to dictate the opacity. Depending on the life value, the opacity of the text changes.

display() {
    let alpha = this.getAlpha();
    if (alpha <= 0) return;
    
    push();
    textAlign(CENTER, CENTER);
    textSize(this.size);
    fill(340, 50, 90, alpha);
    noStroke();
    text(this.text, this.x, this.y);
    pop();
}

// calculate the opacity value for fading 
getAlpha() {
    let ratio = this.life / this.maxLife; // value between 0 and 1
    // fade in at the first 20% of life
    if (ratio < 0.2) return map(ratio, 0, 0.2, 0, 0.8);
    // fade out at the last 20% of life
    if (ratio > 0.8) return map(ratio, 0.8, 1, 0.8, 0);
    // stay mostly visible in the middle
    return 0.8;
}

Complete poem texts

Whenever the ‘a’ button is pressed, I would add the whole lines of poetry to the texts variable so that it would float around exactly like the singular words. I also added the life parameter as the same so that they disappear at the same time.

function showCompletePoem() {
    texts = [];
    let centerX = width / 2;
    let startY = height / 2 - 60;
    
    // add complete poem texts
    let persian = new Text(centerX, startY);
    persian.text = poem.persian;
    persian.size = 24;
    persian.maxLife = 1200;
    texts.push(persian);
    
    let transliteration = new Text(centerX, startY + 50);
    transliteration.text = poem.transliteration;
    transliteration.size = 18;
    transliteration.maxLife = 1200;
    texts.push(transliteration);
    
    let english = new Text(centerX, startY + 100);
    english.text = poem.english;
    english.size = 20;
    english.maxLife = 1200;
    texts.push(english);
}

It is also important to remove ‘old’ texts from the array once their life is ‘complete’. This gets checked in the draw function.

// remove old texts
texts = texts.filter(text => text.life < text.maxLife);

Next time

Next time, I would definitely want to add some flute noises to really tie the piece together. Next time I should probably add some message to indicate which key / mouse action equates to each action.

Outcome

Week 4: Passing Moments (Data Visualization)

(higher amplitude = longer sleep; larger frequency = longer lifespan)

Conception

In this project I wanted to demonstrate the idea of passing moments and how your total lifespan affects how fast you may perceive each moment. Since I love animals, the project started with me just wanting to play around with some data regarding animals; however, I was struggling to find something interesting.

At first I had some data with brain mass and wanted to compare each animal’s brain mass to body mass ratio using circles but it ended up looking very bland so I completely scrapped that sketch.

Then I looked around online for some more databases until I found one that had both lifespan and sleep durations. I thought I could perhaps make something to visualize the impermanence of life and demonstrate the idea of “passing moments.” You could say it’s a bit of a memento mori in a way for both you and the animal mentioned on screen.

Development

I was thinking of how I could represent lifespan and I thought about the heart rate monitors beside hospital beds. I thought perhaps I could use sin waves to represent life span and have the amplitude and frequency be scaled with different things, so that’s what I went with.

I matched the frequency with their life span 

let frequency = map(chosenMammal.lifeSpan, 2, 80, 0.02, 0.08);
...
sin(x*frequency - frameCount * 0.1)

Then I matched the amplitude with hours of sleep per day

let sleepHours = chosenMammal.totalSleep;
let ampMult = 8;
...
let y = height/2 + sin(x*frequency - frameCount * 0.1) * sleepHours * ampMult;

Together, I was able to use the beginShape() and endShape() functions to create a continuous animated sine wave for the mammal. However, that felt like it lacked a frame of reference so I added another wave behind it in gray to represent humans. This way, it could really put into perspective just how fast each moment for us vs the chosen mammal was. I was quite proud of how this part turned out.

function drawSleepSpiral(chosenMammal, color){
  let sleepHours = chosenMammal.totalSleep;
  let ampMult = 8;
  let frequency = map(chosenMammal.lifeSpan, 2, 80, 0.02, 0.08);
  
  push();
  noFill();
  
  if (chosenMammal == allMammals[21]){ //21 is human
    stroke(200);
  } else{
    stroke(color);
  }
  
  strokeWeight(6);
  beginShape();
    for (let x = 0; x < width; x+=1){
      let y = height/2 + sin(x*frequency - frameCount * 0.1) * sleepHours * ampMult;
      vertex(x,y);
    }
  endShape();
  pop();
}

I wasn’t happy with how the default font looked so I loaded in a very beloved font, Helvetica, into the project with what we learned last week.

textFont("Helvetica");
textAlign(CENTER, CENTER);

I was also thinking of adding a text input for the user to put in how many hours they sleep and compare that to the mammal onscreen but I felt like that took away from the idea of lifespan and sounded more like I wanted the user to acknowledge how healthy their sleep schedule was, which I thought would greatly detract from the memento mori effect I was aiming for.

Lastly I added a mousePressed function to cycle through the database without having to hit the start button each time.

function mousePressed(){
  chosenMammal = random(allMammals); // switch to a new mammal on click
  randomColor = random(colorArray); //changes to a random color with the same click
}
Conclusion / Difficulties Endured

I didn’t even realize I could upload my own files and create folders in the sketch files until this past week so this production piece provided me with a lot more insight into what p5js is capable of.

I initially had some trouble figuring out how to get the data out from the CSV file in a natural and efficient way but then I remembered I could extract each column data as a separate characteristic of my object like this:

for (let i = 0; i < table.getRowCount(); i++) {
  let mammalObject = {
    species: table.getString(i, "Species of animal"),
    bodyMass: table.getNum(i, "Body Weight (kg)"),
    brainMass: table.getNum(i, "Brain Weight (g)"),
    totalSleep: table.getNum(i, "Total sleep (hrs/day)"),
    lifeSpan: table.getNum(i, "Maximum life span (years)")
  };
  allMammals.push(mammalObject) //now i can obtain mammalObject.bodyMass for instance
}//closes for() after appending all mammals

This made the process of finding specific attributes of the chosen mammal so much more convenient and efficient.

I also made sure credit the source of my database at the very end of my code: https://gist.github.com/yppmark/d907dc265a84cac76ba7

Week 4 – Reading Response

One thing that drives me crazy is the overcomplicated interface of most modern microwaves. Even as someone who’s pretty tech-savvy, I groan every time I use a new one. 10+ buttons are there for “defrost poultry,” “bake potato,” “reheat pizza,” and more, when all I usually need is to heat up leftovers. Half the time when I use a new microwave, I end up pressing random buttons wasting an equal amount of time to set a basic 2-minute timer. It feels like designers prioritize “showing off” features over usability, exactly as Norman warns. They cram functions to make the microwave seem advanced, but they forget the core user need. Simplicity.

This frustration ties directly to Norman’s principles. The microwaves lack good signifiers, as there’s no clear visual cue (like a large, labeled “Quick Heat” button) to guide basic use. The mapping is muddled, too, as why is “Popcorn” next to “Sensor Cook” when most users reach for quick heating first? They are inherently all part of audience “affordances”—I don’t like this word, as we could simply call it design “friendliness”—which the author argues, sometimes contradicts with the designers’ desire to “show off”.

I agree to a certain extent. On the other hand, however, a designer IS capable of prioritizing human-centered design as their end goal—think of Steves Jobs or a random Apple designer—it is also a process to perfection to excel in design friendliness. How about this simple, intuitive microwave you might have seen: a large digital dial for time (natural mapping) and one “Start” button, plus a small “More Functions” menu for niche uses. This keeps discoverability high, because even a first-time user would know to twist the dial, and reserves extra features for those who need them, without cluttering the experience.

Design doesn’t have to choose between “impressive” and “usable.” Apple proves this by making complex tech feel intuitive, and microwaves could do the same, once designers focused on what users actually do instead of what they think looks good.

Week 4 – Torrent of Transience

For this week’s assignment, I wanted to take the NYC Leading Causes of Death data and turn it into generative text art on p5.js. Initially, I was torn between a “Data Rain” concept where statistics fall like a cascade, and an “Unraveling Text” idea where words literally dissolve, mirroring entropy. 

After some back-and-forth, I settled on combining the two into what I’m calling “Torrent of Transience”. The core idea is a continuous stream of disease names falling from the top of the screen. But it’s not just falling. Each word is actively dissolving, blurring, and fading as it descends, vanishing before it even reaches the bottom. It’s meant to evoke a waterfall of ink words, where the ink itself is dissolving as it flows.

The challenge was mapping the data in a way that felt intuitive and impactful. I decided that the Deaths count for each cause would determine the textSize – larger words for more fatalities, making their presence felt more strongly. The Age Adjusted Death Rate also became useful, as it controls both how fast the word falls and how quickly it dissolves. So, a cause with a high death rate will rush down the screen and disappear rapidly, a stark visual metaphor for its devastating impact.

I also made sure to clean up the data. Those ICD-10 codes in the Leading Cause column were a mouthful, so I’m stripping them out, leaving just the disease name for clarity. And I’m filtering for valid Deaths and Death Rate entries, because a null isn’t going to map to anything meaningful.

For the “unraveling” effect, I knew textToPoints() on every single particle would crash the sketch. My solution was a bit of a cheat, but effective: I draw each word a few times, with slight random offsets, and increase that offset as the word fades. This creates a ghosting, blurring effect that visually implies dissolution. Coupled with a semi-transparent background, it gives the impression of words literally melting into the ether.

Right now, the dataSample is a curated (i.e., selected) list of causes to get the demo running smoothly. If this were a full-blown project, I’d implement a way to dynamically load and parse the entire CSV, allowing the user to select a year and see a completely different torrent. That’s a future enhancement, but for now, the sample gives a good impression of the dynamic effect.

Week 4 – Shahram Chaudhry – Reading Response

As a senior actively applying to jobs, one thing that really drives me crazy is Workday applications. When I spend time filling out a long online application, only for the site to suddenly prompt me (after I click “Next”) to log in or create an account. Sometimes, it even realizes I already have an account and redirects me to a different version where I am signed in. The logical solution would be to sign up or log in first, but the problem is that not all applications require it upfront. Some let you start without logging in, others ask midway, and many don’t make it clear at all. This inconsistency forces me to guess the “correct” order of actions every time, which becomes especially frustrating when data gets lost in the process. A better approach would be a more consistent and user-friendly UX design: clearly prompting users at the start to sign in, continue an existing application, or proceed as a guest with visible consequences for each. Even more importantly, any entered information should be temporarily saved in local storage or cache, so if a redirect occurs, my progress isn’t wiped out. Small design choices like these can make a huge difference in preserving user effort and trust.

As for the reading, it was a fun and informative read. I used to think interaction design and experience design were basically interchangeable. But now I understand that interaction design is more about usability, discoverability, and understanding, while experience design is about the emotional impact and satisfaction we feel when using something. That distinction really clicked for me. I also loved the conversation about how engineers think logically and they think everyone does too or everyone should at least, but systems need to be intuitive not logical. Just because the engineers wrote a manual to use it, and the logical step for users is to read the manual before using the system, doesn’t mean they will. So it’s the desginers/engineers job to understand their audience/people and design accordingly. Human’s shouldn’t have to try to understand all the different rules different machines/systems have. I mean I can see the microwave with at least 10 different buttons, but I always use the same settings. It’s not that I don’t want more options, it’s that the way they’re presented makes them hard to understand. This is where Norman’s principles of discoverability and understanding come in. If something is hard to discover or hard to understand, we just stop using it.

In interactive media, principles like affordances, signifiers, and feedback can greatly improve how users interact with a piece. Affordances aren’t just properties of an object, but also depend on what the user is capable of doing. A heavy chair might afford sitting, and maybe lifting, depending on who is interacting with it. That highlights how important it is to design with the user in mind, not just the object. For example, a scrollable layout that hints at more content affords downward movement, a pulsing microphone icon acts as a signifier that it’s listening for input, and a search bar that updates results in real-time offers immediate feedback. These small design choices guide, inform, and respond to user actions in intuitive ways. I applied these ideas in my own p5.js generative art sketch. I included a placeholder that says “Enter a word” to clearly signal that users need to type something in the input box. Once the user presses Enter, the word appears instantly on screen providing immediate feedback.



Week 4 – Shahram Chaudhry – Antakshari

This project started with a simple yet poetic idea , visualizing lines from old Urdu poetry on screen, placing the main verse at the center and surrounding it with words from the next line, like floating thoughts. But then I began replacing those with some of my favorite Urdu song lyrics , the kind I really connect with , trying to merge different verses across songs to create something emotionally layered. That’s when I was reminded of Antakshari, a fun game many of us grew up playing. The rules are simple: one person sings a song, and the next person has to sing another song that starts with the last letter of the previous one. I loved the idea of using that structure as the basis for generative text. I initially wanted to build this fully in Urdu, but I realized it would be tricky to detect and match the last letter accurately in the script. So, I switched to English to get the logic working smoothly. Instead of full lyrics, I used individual words and for each word, the next one is chosen randomly from a list of possible options starting with the last letter. That means each run of the program generates a unique chain of words, but with a meaningful constraint just like Antakshari. I also added visual flair: a short delay between words, small random rotations and scaling for different text sizes so it wouldn’t look like a grid structure and feel a little unpredictable.

The two functions placeWord() and pickNextWord() were the heart of my sketch. I was particularly proud of how I used my prior knowledge of dictionaries to implement the words dictionary, which acts as a lookup system. It takes the last letter of the previous word and finds a list of possible next words starting with that letter. Then I randomly select one to create branching possibilities making the output  different every time.

In placeWord(), I figured out how to make each word feel organic and unique by adding randomness to its angle and size using random(-PI/16, PI/16) and a scaling factor. I also calculated the text width dynamically so I could position the next word without overlap between words. These choices made the flow of words appear natural.

function placeWord(word) {
  let fontOriginalSize = 24;
  let wordMargin = 8; 

  let angle = random(-PI / 16, PI / 16);
  let scaleFactor = random(0.9, 1.3);
  //find color from colormap
  let firstLetter = word.charAt(0).toLowerCase();
  let wordColor = colorMap[firstLetter];
  
  
  textSize(fontOriginalSize * scaleFactor);
  let wordWidth = textWidth(word);

  placedWords.push({
    word: word,
    x: gridX,
    y: gridY + random(-3, 3),
    size: fontOriginalSize * scaleFactor,
    angle: angle,
    color: wordColor
  });
  
  //move the horizontal position to right for next word
  gridX += wordWidth + wordMargin;
  
  //starts a new line/row
  if (gridX > width - 100) {
    gridX = 50;
    gridY += gridSpacing;
  }
  //canvas not filled checked by seeing if we reach bottom or not
  if (autoRun && gridY < height - 50) {
    pickNextWord();
    lastPlacedWord = nextWord;
    setTimeout(() => placeWord(nextWord), 150);
  }
}
function pickNextWord() {
  let lastLetter = lastPlacedWord.charAt(lastPlacedWord.length - 1).toLowerCase();
  //find the candidate words using lastletter and choose a random word
  let candidates = words[lastLetter];
  nextWord = random(candidates);
}

Here’s the sketch:

Eventually, I’d love to go back to my original vision and bring Urdu fully into the experience. That would mean figuring out last-letter detection in Urdu script and possibly integrating a calligraphic font to preserve the beauty of the language. If I can pull that off, the result would be a truly generative, Urdu Antakshari as a perfect blend of nostalgia, music, and generative text. 

 



Week 4 – Word Rain

Concept

For this exercise, I wanted to merge two things: playful interactivity and meaningful (well almost) outcome. I came up with this Tetris-like game where instead of geometric blocks, words fall from the sky. Players can move these word blocks left or right with arrow keys to stack them into bizarre sentences or whatever they like.

The sentences hardly make sense; in fact, the randomness of the generated words is something that makes the results funny. This can be perceived as a silly project but it’s fun. Stack words – make nonsense – laugh.

Inspiration

The inspiration came from two souces:

  1. Tetris game
  2. Text Rain (by Camille Utterback, where letters fall and interact with the human body)

I wanted to combine the two ideas intro something playful but also generative, where the player becomes either a poet or a chaotic builder (or both) depending on the words that randomly fall and where they place them.

Code Structure

Here is the list of things I implemented this with:

  • Array of words (tried to include nouns, verbs, adjectives, adverbs, connectors; however, this can be modified for the code meaning user can use any array of any words they like)
  • fallingWord object that moves down the grid
  • blocks array to store all placed word, and collision detection so blocks stack on top of each other or store at the floor.
  • Keyboard controls to move words left and right within the canvas
  • A small function to resize text dynamically. This was done keeping in mind that is user wants longer words in the array and they will be able to do so as the font size will be adjusted as needed.

Every new word is randomly chosen from the array, dropped from the top, and the loop continues.

Reflection

I liked how this project was fairly simple yet becomes engaging once the player is allowed to have some control. This is a result of both randomness (of the word array) and control (the stacking mechanism).

Challenges:

  • Making sure words didn’t move out of canvas. I tried to put this condition in the same one as if keyPressed but that wasn’t to work. It turned out I have to create a nested condition to implement this.
  • Handling long words that wouldn’t fit in the blocks. I thought whether to allocate more than 1 blocks for long words, but i realized that adjusting the font size is much convenient.

Future Improvements

  • Add scoring system: If a player completes a sentence, they get a point. This requires the rules of grammar and is too difficult for me at present.
  • Make the termination case: for now, I didn’t instruct the code what to do when the canvas is full. I think this is necessary for a game, but mine isn’t yet.

Week 4 Reading Response

One everyday object that always drives me crazy, but not quite do with interactive media, is my water bottles. Every bottle I’ve owned is either too tight to open or too loose so it leaks everywhere. Especially for the current one I’m using, I always ended up struggling like it’s a test of grip strength. The most baffling example is the viral Stanley cup, people on social media complain nonstop about leaks, and yet it still become a must-have item. I’ve even seen TikToks where the “fixing the leak” process itself becomes a kind of trend. From Norman’s perspective, that feels like a failure of design: the affordance (a secure, portable cup) clashes with the signifiers (the lid/the cap) that don’t clearly tell you how to tighten it enough. The fact that millions of users are improvising and complaining is proof that the “system image” isn’t communicating what the designer intended.

Norman’s principles also made me think about my own experiments in p5.js. When I build interactive sketches, I realize I need to add clear signifiers, not just rely on “hidden” affordances. For example, I once made a generative text piece where users could drag characters around, but unless I showed some visual cue, like a subtle highlight or a cursor change, no one discovered the interaction. It’s exactly what Norman warns about: affordances exist, but if they’re invisible, they fail.

Another part of the article I found really interesting was Norman’s discussion of mapping. He uses the example of car seat adjustment buttons shaped like the actual seat, you just push the part of the button that matches the part of the seat you want to move. That struck me because it feels so obvious and natural, but you realize how many objects miss this. I think about the confusing array of stove knobs in my apartment where I’m constantly turning on the wrong burner. If those knobs had a layout that mapped directly to the burners, I’d never make that mistake. It made me realize that whether it’s an app, a sketch, or just a stove, people shouldn’t need labels and trial-and-error to figure out something so basic.

Week 4: Generative Text Art

Concept

I started the brainstorming of generative text art by the inspiration of Tibetan font we’ve seen in class. I felt so amazing how this niche and complicated language can be incorporated into unicode and redesigned into various new format. Then I came up the idea of generating rearrangement of typography from Chinese Wubi input method, which decomposes characters into strokes that can then be recombined into full forms. I wanted to apply this same principle of decomposition and recomposition to my favorite language of all times: Nüshu (The direct shortcut key for ü is v, referring as Nvshu in the following).

Nvshu was historically used in Jiangyong County, Hunan, China, as a secretive women’s writing system. It is the only known script in the world designed and used exclusively by women, and was transmitted through sisterhood groups, generational teaching, and playful exchanges. Today, Nvshu often appears on carvings, embroidered fans, handbags, or even tattoos, celebrated as both a form of sisterhood heritage and an aesthetic design language.

My goal was to design a digital generative “Nvshu board” where users can appreciate the beauty of these glyphs and play with them (dragging, rearranging, and composing their own patterns). The resulting forms resemble ornaments, motifs, or even tattoo-like compositions, highlighting how a script can carry both linguistic and visual meaning.

Sketch (Written Horizontally, same as historical Chinese Characters)

Highlight Code

The code I am most proud of is the section that generates random Nvshu glyphs from the Unicode range and places them into a 10×10 board. To make sure the characters display properly, I had to research Unicode code points and how to convert them into visible strings in p5.js. The line below is the most rewarding part of my code:

//       randomized and round down to integer to match the string
      let nscode = floor(random(0x1B170, 0x1B2FB + 1));
      //take the code create the string from the unicode sequence of code points
      // saw this way of locating unicode in chinese-fonts p5.js works by tytyshi
      let nsglyph = String.fromCodePoint(nscode);
      // store it into the array with parameters

Here, random() generates a floating-point number within the Unicode range for Nvshu (0x1B170 to 0x1B2FB inclusive). floor() ensures the result is an integer, since Unicode code points must be whole numbers. Then, String.fromCodePoint() converts that integer into the actual Nvshu glyph. This was a breakthrough moment for me, because it connected abstract Unicode numbers to visible characters and allowed me to fill the canvas with living Nvshu script.

On top of this, I added drag-and-drop interactivity: when the user presses a glyph, the program tracks it and updates its coordinates as the mouse moves. This simple interaction lets users create their own custom compositions from the script.

Future Improvements and Problems

While researching fonts, I first discovered a brush-style Nvshu font package on GitHub. However, its size (18.3MB) exceeded the 5MB upload limit on the p5.js online editor. This led me to the only option of Noto Sans Nvshu on Google fonts. This size of 0.2MB really surprised me. Learning this powerfulness of unifying typeface made me think about how technology preserves cultural memory. I also consulted Lisa Huang’s design reflections on the challenges of Noto Sans Nüshu (https://www.lisahuang.work/noto-sans-nueshu-type-design-with-multiple-unknowns) to better understand the typographic logic of the font.

I would also like to add a save feature in future, so that once users have composed a design, they could export it as an image (to use as a print, tattoo design, or ornament).

This project combined cultural research, typographic exploration, and interactive coding. By bringing Nvshu into a playful digital space, I wanted to highlight both its fragility as an endangered script and its resilience through Unicode and modern typography. Users can engage with scripts not only as carriers of language but also as visual and cultural materials.