Freedom & Proscription
Palestine
Generative Art
June 26, 2025
---
title: "Freedom & Proscription"
date: "2025-06-26"
categories: [Palestine, Generative Art]
editor: visual
#self-contained: true
toc: false
code-tools: true
execute:
echo: false
warning: false
message: false
---
::: {#sketch-container style="max-width: 920px;"}
```{=html}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
<script src="OPTIBuffer-Bold.otf"></script>
<script>
let font; // Font variable
let particles = []; // Array to store particles
let particleText = "WE ARE ALL PALESTINE ACTION!"; // Text to be displayed
let fontSize; // Font size
let maxWidth; // Maximum width for text
let textHeight; // Height of the text
let particlePool = []; // Particle pool for reusing particles - method from game dev
let lastInteractionTime = 0;
let isInteracting = false;
const SETTLE_DELAY = 500; // ms before starting to settle
const SETTLE_DURATION = 2000; // ms to fully settle
// Preload function to load font
function preload() {
font = loadFont('OPTIBuffer-Bold.otf')// 'Atkinson-Hyperlegible-Regular-102.otf'); // Replace with your font
}
// Setup function to initialize canvas and text setup
function setup() {
let sketchContainer = document.getElementById('sketch-container');
let divWidth = sketchContainer.offsetWidth;
textFont(font);
fontSize = Math.sqrt(divWidth * 0.9) * 2.8; // Set font size to square root of div width (yeah this is stupid but I was tinkering)
textSize(fontSize);
maxWidth = divWidth * 0.9; // Set maximum text width to 90% of div width
setupText(); // This will calculate textHeight
let canvas = createCanvas(divWidth, textHeight); // Set canvas height to textHeight
canvas.parent('sketch-container');
canvas.style('touch-action', 'none');
//pixelDensity(1);
// Attach touch handlers to *just* this canvas
canvas.touchStarted(() => { isInteracting = true; lastInteractionTime = millis(); return false; });
canvas.touchMoved(() => { isInteracting = true; lastInteractionTime = millis(); return false; });
canvas.touchEnded(() => { isInteracting = false; lastInteractionTime = millis(); return false; });
}
// Function to handle window resize event
function windowResized() {
let sketchContainer = document.getElementById('sketch-container');
let divWidth = sketchContainer.offsetWidth;
fontSize = Math.sqrt(divWidth * 0.9) * 2.8; // Recalculate font size
textSize(fontSize);
maxWidth = divWidth * 0.9; // Recalculate maximum text width
setupText(); // Recalculate textHeight
resizeCanvas(divWidth, textHeight); // Adjust canvas size
}
// Function to setup text particles
function setupText() {
particles = [];
particlePool = [];
let x = 10;
let y = fontSize;
textHeight = 0; // Reset text height
let words = particleText.split(' ');
for (let i = 0; i < words.length; i++) {
let wordWidth = textWidth(words[i] + ' ');
if (x + wordWidth > maxWidth) {
x = 10;
y += fontSize;
}
let points = font.textToPoints(words[i], x, y, fontSize, {
sampleFactor: 0.3
});
for (let j = 0; j < points.length; j++) {
let particle;
if (particlePool.length > 0) {
particle = particlePool.pop(); // Reuse particle from the pool
particle.reset(points[j].x, points[j].y);
} else {
particle = new Particle(points[j].x, points[j].y); // Create new particle
}
particles.push(particle);
}
x += wordWidth;
textHeight = Math.max(textHeight, y); // Update text height
}
textHeight += 5; // Add 5px padding below the last word
}
let colors = [
'#e4312b' //, left out additional colours - just red
//'#000000',
//'#149954'
]; // Color array
// Particle class
class Particle {
constructor(x, y) {
this.pos = createVector(random(width), random(height)); // Initialize position randomly
this.target = createVector(x, y); // Target vector
this.vel = p5.Vector.random2D(); // Velocity vector
this.acc = createVector(); // Acceleration vector
this.maxspeed = 6; //10; // Maximum speed
this.maxforce = 1; // Maximum steering force
let c = color(random(colors)); // Choose a random color
this.color = [red(c), green(c), blue(c)]; // Set particle color
}
// Reset particle position and target
reset(x, y) {
this.pos.set(random(width), random(height));
this.target.set(x, y);
}
// slightly improved for touchscreen and timeout so particles settle
behaviors() {
let arrive = this.arrive(this.target);
let pointer;
if (touches.length > 0) {
pointer = createVector(touches[0].x, touches[0].y);
} else {
pointer = createVector(mouseX, mouseY);
}
// Check if we should be interacting RIGHT NOW
let shouldInteract = false;
// Desktop: mouse is over canvas
if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
shouldInteract = true;
}
// Mobile: touching
if (touches.length > 0) {
shouldInteract = true;
}
// Update last interaction time if currently interacting
if (shouldInteract) {
lastInteractionTime = millis();
}
// Calculate settle factor
let timeSinceInteraction = millis() - lastInteractionTime;
let settleFactor = 1;
if (timeSinceInteraction < SETTLE_DELAY) {
settleFactor = 0;
} else if (timeSinceInteraction < SETTLE_DELAY + SETTLE_DURATION) {
let settleProgress = (timeSinceInteraction - SETTLE_DELAY) / SETTLE_DURATION;
settleFactor = settleProgress;
}
let flee = this.flee(pointer);
arrive.mult(1);
flee.mult(5 * (1 - settleFactor));
this.applyForce(arrive);
this.applyForce(flee);
}
// Apply force to particle
applyForce(f) {
this.acc.add(f); // Add force to acceleration
}
// Update particle position and velocity
update() {
this.pos.add(this.vel); // Update position
this.vel.add(this.acc); // Update velocity
this.acc.mult(0); // Reset acceleration
}
// Display particle
show() {
fill(this.color[0], this.color[1], this.color[2]);
noStroke();
ellipse(this.pos.x, this.pos.y, 4, 4); // Draw particle as ellipse - will render faster apparently
}
// Arrive behavior
arrive(target) {
let desired = p5.Vector.sub(target, this.pos); // Calculate desired vector
let d = desired.mag(); // Calculate distance to target
let speed = this.maxspeed; // Set speed to maximum speed
if (d < 100) { // If distance is less than 100, adjust speed
speed = map(d, 0, 100, 0, this.maxspeed);
}
desired.setMag(speed); // Set desired vector magnitude
let steer = p5.Vector.sub(desired, this.vel); // Calculate steering force
steer.limit(this.maxforce); // Limit steering force
return steer; // Return steering force
}
// Flee behavior
flee(target) {
let desired = p5.Vector.sub(target, this.pos); // Calculate desired vector
let d = desired.mag(); // Calculate distance to target
if (d < 50) { // If distance is less than 50, flee from target
desired.setMag(this.maxspeed); // Set desired vector magnitude
desired.mult(-1); // Reverse desired vector
let steer = p5.Vector.sub(desired, this.vel); // Calculate steering force
steer.limit(this.maxforce); // Limit steering force
return steer; // Return steering force
} else {
return createVector(0, 0); // Return zero vector if not fleeing
}
}
}
// Draw function to update and display particles
function draw() {
background(255); // Set background color
particles.forEach(particle => {
particle.behaviors(); // Apply behaviors to particle
particle.update(); // Update particle position and velocity
particle.show(); // Display particle
});
}
// Function to return particles to the pool
function returnParticlesToPool() {
particles.forEach(particle => {
particlePool.push(particle);
});
particles = [];
}
function resetAnimation() {
clear(); // Clears the canvas
returnParticlesToPool(); // Returns current particles to the pool
setupText(); // Recreates particles for the text
draw(); // Draws the new state of the particles
}
function mouseClicked() {
// Check if the click is inside the canvas or a specific div
if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) {
resetAnimation();
}
}
</script>
```
:::