te amo

This sketch creates a romantic love counter displaying how long a couple has been together, overlaid on an animated field of 50 procedurally-generated sunflowers. Each sunflower features realistic petals, stems, leaves, and a golden-angle phyllotaxis center, while a counter box at the top shows elapsed time in days, hours, minutes, and seconds since a target date 128 days and 10 hours ago.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the love message — Replace the Spanish text with your own message—the counter will adapt the box size automatically.
  2. Make the counter box pink — The counter background will change from cream to a warm pink, creating a more romantic aesthetic.
  3. Pack more sunflowers — Increase the number of flowers in the field from 50 to 100—the garden becomes much denser and more vibrant.
  4. Make petals longer — Increase the petal length range to 50-80% of flower size instead of 40-60%, creating more dramatic, sweeping petals.
  5. Create a sunset background — Replace the field green with a warm orange-pink sunset color that creates a completely different romantic mood.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch combines romantic visuals with real-time calculations to create a heartfelt time counter. The foreground displays a counter box showing exactly how many days, hours, minutes, and seconds a couple has been together, calculated from a hardcoded target date. The background features 50 procedurally-generated sunflowers that fill the canvas with organic, realistic detail—each flower includes a curved stem drawn with bezier curves, leaves with veins, 60 individually-rendered petals with color variation, and a center made of 800 florets arranged in the golden angle spiral found in nature.

The code is organized into a setup() function that initializes the canvas, color palette, and target timestamp, a draw() function that updates the time counter and renders all sunflowers every frame, a generateSunflowers() helper that creates the sunflower array, and a Sunflower class that encapsulates all the geometry for drawing a single flower. By studying it, you will learn how to combine mathematical patterns (phyllotaxis), real-world math (Date.now()), responsive canvas sizing (pixelDensity, windowResized), and object-oriented design (classes) into a single polished sketch.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, defines a realistic color palette for petals, centers, stems, and leaves, calculates the target timestamp by subtracting 128 days and 10 hours from the current time using Date.now(), and generates 50 Sunflower objects placed randomly in the bottom half of the screen.
  2. Every frame, draw() clears the canvas with a light-green background, loops through all sunflowers and calls their display() method to render each flower at its stored position.
  3. The draw loop calculates the elapsed time in milliseconds by subtracting the target timestamp from the current timestamp, then converts that millisecond difference into days, hours, minutes, and seconds using modulo arithmetic.
  4. A counter box is drawn at the top of the screen using rect() with rounded corners, filled with a semi-transparent cream color, and stroked with a subtle border.
  5. The elapsed time string is rendered inside the counter box in black text, formatted across two lines: 'Te amo desde hace' (I have loved you for) on line one, and the numeric breakdown on line two.
  6. When the window is resized, windowResized() updates the canvas size and text size proportionally, and regenerates all sunflower positions to fill the new screen dimensions.

🎓 Concepts You'll Learn

Phyllotaxis and golden angleObject-oriented design (ES6 classes)Bezier curves for organic shapesReal-time date and time calculationsResponsive canvas sizingTransform stack (push/pop, translate, rotate)Color palettes and color theoryArray iteration and object instantiation

📝 Code Breakdown

setup()

setup() runs once when the sketch loads. It is where you initialize all your variables, create the canvas, and prepare data structures. In this sketch, setup() calculates the target date 128 days in the past and generates all the sunflowers that will be drawn later.

function setup() {
  pixelDensity(2);
  createCanvas(windowWidth, windowHeight);
  
  petalColor1 = color(255, 200, 0);
  petalColor2 = color(255, 170, 0);
  centerColor1 = color(120, 60, 10);
  centerColor2 = color(180, 100, 30);
  stemColor = color(50, 160, 50);
  leafColor = color(70, 180, 70);
  backgroundColor = color(220, 255, 220);

  targetTimestamp = Date.now() - (128 * 24 * 60 * 60 * 1000) - (10 * 60 * 60 * 1000);

  generateSunflowers();

  textAlign(CENTER, CENTER);
  textSize(width * 0.035);
  fill(0);
  textFont('Arial');
}
Line-by-line explanation (15 lines)

🔧 Subcomponents:

calculation Color Palette Definition petalColor1 = color(255, 200, 0);

Establishes the realistic color scheme for all flower parts—yellows for petals, browns for centers, greens for stems and leaves

calculation Target Timestamp Calculation targetTimestamp = Date.now() - (128 * 24 * 60 * 60 * 1000) - (10 * 60 * 60 * 1000);

Calculates the past moment in time (128 days and 10 hours ago) by subtracting milliseconds from the current time

calculation Text Styling Setup textSize(width * 0.035);

Makes text size responsive to window width so the counter scales properly on different screen sizes

pixelDensity(2);
Sets the rendering resolution to 2x, making graphics crisp and detailed on high-DPI displays like retina screens
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the sketch fullscreen and responsive
petalColor1 = color(255, 200, 0);
Defines a bright yellow (RGB: 255, 200, 0) used for alternating petals to create color variation
petalColor2 = color(255, 170, 0);
Defines a deeper orange-yellow (RGB: 255, 170, 0) for the other set of alternating petals
centerColor1 = color(120, 60, 10);
Defines a dark brown (RGB: 120, 60, 10) for the phyllotaxis florets to create texture in the center
centerColor2 = color(180, 100, 30);
Defines a lighter brown-orange (RGB: 180, 100, 30) that alternates with centerColor1 in the spiral
stemColor = color(50, 160, 50);
Defines a natural green (RGB: 50, 160, 50) for the curved stem and leaf veins
leafColor = color(70, 180, 70);
Defines a slightly brighter green (RGB: 70, 180, 70) for the leaf shapes
backgroundColor = color(220, 255, 220);
Defines a light pastel green (RGB: 220, 255, 220) for the background, simulating a sunny field
targetTimestamp = Date.now() - (128 * 24 * 60 * 60 * 1000) - (10 * 60 * 60 * 1000);
Calculates the target moment: current time minus 128 days (converted to milliseconds) minus 10 hours (converted to milliseconds)
generateSunflowers();
Calls the helper function to populate the sunflowers array with 50 randomly-positioned Sunflower objects
textAlign(CENTER, CENTER);
Centers text both horizontally and vertically around its anchor point, making positioning easier for the counter box
textSize(width * 0.035);
Sets text size proportionally to window width (3.5% of canvas width), so text scales when the window resizes
fill(0);
Sets the text fill color to black (RGB: 0, 0, 0)
textFont('Arial');
Sets the font to Arial, a common web-safe font that renders consistently across browsers

draw()

draw() runs 60 times per second, creating the animation and live updates. Every frame, it clears the background, renders all sunflowers, calculates the elapsed time, and redraws the counter box with updated numbers. This is where the heartbeat of the sketch lives—without draw(), nothing moves or updates.

🔬 This loop renders every sunflower in the array. What happens if you change it to only render every other sunflower by checking if (i % 2 === 0)? You'll need to convert the for-of loop to a traditional for loop first. Try it and count how many sunflowers appear.

  for (let sunflower of sunflowers) {
    sunflower.display();
  }
function draw() {
  background(backgroundColor);

  for (let sunflower of sunflowers) {
    sunflower.display();
  }

  const currentTimestamp = Date.now();
  const diff = currentTimestamp - targetTimestamp;

  let seconds = floor(diff / 1000);
  let minutes = floor(seconds / 60);
  let hours = floor(minutes / 60);
  let days = floor(hours / 24);

  seconds %= 60;
  minutes %= 60;
  hours %= 24;

  const line1 = "Te amo desde hace";
  const line2 = `${days} días, ${hours} horas, ${minutes} minutos y ${seconds} segundos`;
  const timeString = line1 + "\n" + line2;

  const boxPadding = width * 0.02;
  const textHeightForOneLine = textSize();
  const lineHeightFactor = 1.2;
  const textHeightCalculated = textHeightForOneLine + textHeightForOneLine * lineHeightFactor;

  const textWidthLine1 = textWidth(line1);
  const textWidthLine2 = textWidth(line2);
  const textWidthCalculated = max(textWidthLine1, textWidthLine2);

  const boxWidth = textWidthCalculated + boxPadding * 2;
  const boxHeight = textHeightCalculated + boxPadding * 2;
  const boxX = width / 2;
  const boxY = height * 0.15;

  noStroke();
  fill(255, 255, 220, 220);
  rectMode(CENTER);
  rect(boxX, boxY, boxWidth, boxHeight, 15);

  stroke(100);
  strokeWeight(2);
  noFill();
  rect(boxX, boxY, boxWidth, boxHeight, 15);

  noStroke();
  fill(0);
  text(timeString, boxX, boxY);
}
Line-by-line explanation (37 lines)

🔧 Subcomponents:

for-loop Sunflower Rendering Loop for (let sunflower of sunflowers) {

Iterates through the sunflowers array and calls display() on each flower, drawing all 50 sunflowers to the canvas

calculation Millisecond to Time Unit Conversion let seconds = floor(diff / 1000);

Converts the elapsed milliseconds into seconds, minutes, hours, and days through successive division and floor operations

calculation Remainder Extraction for Display seconds %= 60;

Uses modulo (%) to extract only the remainder for each time unit, so minutes shows 0-59 instead of total minutes

calculation Counter Box Size Calculation const boxWidth = textWidthCalculated + boxPadding * 2;

Calculates the box dimensions based on the actual width and height of the text, plus padding, so the box always fits the content perfectly

calculation Counter Box Rendering rect(boxX, boxY, boxWidth, boxHeight, 15);

Draws the background rectangle for the counter box with rounded corners (radius 15), centered on the screen

background(backgroundColor);
Clears the entire canvas with the light-green background color, erasing any previous frame and preventing visual artifacts
for (let sunflower of sunflowers) {
Starts a loop that iterates through each Sunflower object in the sunflowers array, one per iteration
sunflower.display();
Calls the display() method on the current sunflower, which renders all its petals, stem, leaves, and center to the canvas
const currentTimestamp = Date.now();
Gets the current time in milliseconds since January 1, 1970, updated every frame to keep the counter live
const diff = currentTimestamp - targetTimestamp;
Calculates how many milliseconds have elapsed between the target date and now—the core of the time counter
let seconds = floor(diff / 1000);
Divides total milliseconds by 1000 to convert to seconds, using floor() to discard the fractional part
let minutes = floor(seconds / 60);
Divides total seconds by 60 to get total minutes elapsed
let hours = floor(minutes / 60);
Divides total minutes by 60 to get total hours elapsed
let days = floor(hours / 24);
Divides total hours by 24 to get the number of complete days elapsed
seconds %= 60;
Uses modulo to keep only the remainder of seconds—values from 0 to 59—discarding the minutes already counted
minutes %= 60;
Uses modulo to keep only the remainder of minutes—values from 0 to 59—discarding the hours already counted
hours %= 24;
Uses modulo to keep only the remainder of hours—values from 0 to 23—discarding the days already counted
const line1 = "Te amo desde hace";
Stores the Spanish phrase 'I have loved you for' as the first line of the counter display
const line2 = `${days} días, ${hours} horas, ${minutes} minutos y ${seconds} segundos`;
Uses template literals to insert the calculated days, hours, minutes, and seconds into a formatted string with Spanish labels
const timeString = line1 + "\n" + line2;
Combines both lines with a newline character (\n) so the text appears on two separate lines when drawn
const boxPadding = width * 0.02;
Calculates responsive padding (2% of canvas width) around the text inside the counter box
const textHeightForOneLine = textSize();
Retrieves the current text size in pixels, which approximately equals the height of one line of text
const lineHeightFactor = 1.2;
Defines a multiplier (1.2) to account for p5.js's default line spacing between text lines
const textHeightCalculated = textHeightForOneLine + textHeightForOneLine * lineHeightFactor;
Calculates the total height needed for two lines of text: one line height plus spacing for a second line
const textWidthLine1 = textWidth(line1);
Measures the pixel width of the first line of text to ensure the box is wide enough
const textWidthLine2 = textWidth(line2);
Measures the pixel width of the second line (usually longer due to the numbers)
const textWidthCalculated = max(textWidthLine1, textWidthLine2);
Takes the maximum of both widths so the box accommodates the longer line
const boxWidth = textWidthCalculated + boxPadding * 2;
Adds padding on both left and right sides to the text width, creating the final box width
const boxHeight = textHeightCalculated + boxPadding * 2;
Adds padding on top and bottom to the text height, creating the final box height
const boxX = width / 2;
Positions the box's center horizontally at the middle of the canvas
const boxY = height * 0.15;
Positions the box's center vertically at 15% down from the top, in the upper portion of the screen
noStroke();
Disables the outline stroke so the background rectangle will not have a border
fill(255, 255, 220, 220);
Sets the box fill color to a light cream (RGB: 255, 255, 220) with alpha 220 for subtle transparency
rectMode(CENTER);
Changes rect() to measure from the center point instead of the top-left corner, making positioning easier
rect(boxX, boxY, boxWidth, boxHeight, 15);
Draws the background rectangle for the counter box centered at (boxX, boxY) with rounded corners (radius 15)
stroke(100);
Sets the stroke color to a medium gray (RGB: 100, 100, 100) for the box border
strokeWeight(2);
Sets the border thickness to 2 pixels—thin enough to be subtle but visible
noFill();
Disables fill so the second rect() draws only the outline border, not a solid rectangle
rect(boxX, boxY, boxWidth, boxHeight, 15);
Draws the border rectangle at the same position and size, creating a subtle outline around the cream background
noStroke();
Disables the stroke before drawing text so the text itself has no outline
fill(0);
Sets the text fill color to black (RGB: 0, 0, 0) for high contrast and readability
text(timeString, boxX, boxY);
Draws the two-line time string centered at (boxX, boxY), displaying days, hours, minutes, and seconds in real-time

windowResized()

windowResized() is a special p5.js function that p5 calls automatically whenever the browser window is resized. Without it, the canvas would not adapt to the new window size, leaving blank space or cutting off content. By regenerating sunflowers here, you ensure the field always fills the screen beautifully, no matter the window size.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  textSize(width * 0.035);
  generateSunflowers();
}
Line-by-line explanation (3 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new browser window dimensions whenever the user resizes their window
textSize(width * 0.035);
Recalculates the text size proportionally (3.5% of the new canvas width) so text stays at the right scale on the resized canvas
generateSunflowers();
Regenerates all 50 sunflowers with new random positions and sizes appropriate for the new canvas dimensions

generateSunflowers()

generateSunflowers() is a helper function that populates the sunflowers array with new Sunflower objects. It is called once in setup() and again whenever the window is resized. By putting all the generation logic in one place, the code stays organized and easy to modify.

function generateSunflowers() {
  sunflowers = [];
  for (let i = 0; i < numSunflowers; i++) {
    let x = random(width);
    let y = random(height * 0.5, height);
    let size = random(width * 0.07, width * 0.18);
    sunflowers.push(new Sunflower(x, y, size));
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

calculation Array Initialization sunflowers = [];

Clears the sunflowers array, discarding any old sunflowers before creating new ones

for-loop Sunflower Creation Loop for (let i = 0; i < numSunflowers; i++) {

Repeats 50 times to create 50 new Sunflower objects with random positions and sizes

sunflowers = [];
Empties the sunflowers array completely, preparing it to hold new sunflowers
for (let i = 0; i < numSunflowers; i++) {
Starts a loop that runs 50 times (numSunflowers = 50), creating one new sunflower per iteration
let x = random(width);
Generates a random x position anywhere from the left edge (0) to the right edge (width) of the canvas
let y = random(height * 0.5, height);
Generates a random y position between the middle of the canvas and the bottom edge, concentrating sunflowers in the lower half
let size = random(width * 0.07, width * 0.18);
Generates a random size between 7% and 18% of the canvas width, creating sunflowers of varying sizes for visual interest
sunflowers.push(new Sunflower(x, y, size));
Creates a new Sunflower object with the random position and size, and adds it to the sunflowers array using push()

Sunflower (class)

The Sunflower class encapsulates all the geometry of a single flower into a reusable blueprint. Every time a new Sunflower is created, it gets its own size, position, petal length, and petal width. The display() method contains the complex drawing logic: stem using bezier curves, leaves with veins, 60 rotating petals each drawn individually, and the iconic golden-angle spiral center using phyllotaxis math. This is object-oriented design in action—one class makes 50 flowers look natural because each has random variation.

🔬 These two lines add randomness to each petal's dimensions (±5% for length, ±10% for width). What happens if you remove the random() calls entirely and set currentPetalLength = this.petalLength? The petals will all be identical—try it and see if it looks more or less natural.

      let currentPetalLength = this.petalLength * random(0.95, 1.05);
      let currentPetalWidth = this.petalWidth * random(0.9, 1.1);

🔬 This alternates petal colors to create visual depth. What happens if you change i % 2 to i % 3? Every third petal will match, creating a 3-color repeating pattern. Or try always using petalColor1 to make all petals the same yellow.

      if (i % 2 === 0) {
        fill(petalColor1);
      } else {
        fill(petalColor2);
      }
class Sunflower {
  constructor(x, y, size) {
    this.x = x;
    this.y = y;
    this.size = size;
    this.petalLength = random(this.size * 0.4, this.size * 0.6);
    this.petalWidth = random(this.size * 0.1, this.size * 0.15);
    this.numPetals = 60;
  }

  display() {
    push();
    translate(this.x, this.y);

    stroke(stemColor);
    strokeWeight(this.size * 0.04);
    noFill();
    bezier(0, 0,
           this.size * 0.1, this.size * 0.5,
           -this.size * 0.1, this.size * 1.0,
           0, this.size * 1.5);

    noStroke();
    fill(leafColor);

    beginShape();
    vertex(0, this.size * 0.6);
    bezierVertex(-this.size * 0.3, this.size * 0.3,
                 -this.size * 0.6, this.size * 0.5,
                 -this.size * 0.5, this.size * 1.0);
    bezierVertex(-this.size * 0.2, this.size * 1.3,
                 -this.size * 0.1, this.size * 1.2,
                 0, this.size * 1.2);
    endShape(CLOSE);
    stroke(stemColor);
    strokeWeight(1);
    line(-this.size * 0.2, this.size * 0.7, -this.size * 0.5, this.size * 1.0);
    line(-this.size * 0.1, this.size * 0.9, -this.size * 0.4, this.size * 1.2);

    noStroke();
    fill(leafColor);
    beginShape();
    vertex(0, this.size * 0.8);
    bezierVertex(this.size * 0.3, this.size * 0.5,
                 this.size * 0.6, this.size * 0.7,
                 this.size * 0.5, this.size * 1.2);
    bezierVertex(this.size * 0.2, this.size * 1.5,
                 this.size * 0.1, this.size * 1.4,
                 0, this.size * 1.4);
    endShape(CLOSE);
    stroke(stemColor);
    strokeWeight(1);
    line(this.size * 0.2, this.size * 0.9, this.size * 0.5, this.size * 1.2);
    line(this.size * 0.1, this.size * 1.1, this.size * 0.4, this.size * 1.4);

    noStroke();
    let angleStep = TWO_PI / this.numPetals;
    
    for (let i = 0; i < this.numPetals; i++) {
      rotate(angleStep);
      
      let currentPetalLength = this.petalLength * random(0.95, 1.05);
      let currentPetalWidth = this.petalWidth * random(0.9, 1.1);

      noStroke();
      fill(200, 160, 0, 150);
      ellipse(0, currentPetalLength / 2, currentPetalWidth * 1.1, currentPetalLength * 1.1);

      if (i % 2 === 0) {
        fill(petalColor1);
      } else {
        fill(petalColor2);
      }
      
      stroke(200, 160, 0);
      strokeWeight(0.5);
      
      beginShape();
      vertex(0, 0);
      bezierVertex(-currentPetalWidth * 0.3, currentPetalLength * 0.2,
                   -currentPetalWidth * 0.5, currentPetalLength * 0.4,
                   0, currentPetalLength);
      bezierVertex(currentPetalWidth * 0.5, currentPetalLength * 0.4,
                   currentPetalWidth * 0.3, currentPetalLength * 0.2,
                   0, 0);
      endShape(CLOSE);
    }

    noStroke();
    
    const c = this.size * 0.005;
    const totalFlorets = 800;
    
    for (let i = 0; i < totalFlorets; i++) {
      let angle = i * 137.5;
      let r = c * sqrt(i);
      let floretX = r * cos(angle);
      let floretY = r * sin(angle);
      
      if (i % 2 === 0) {
        fill(centerColor1);
      } else {
        fill(centerColor2);
      }
      
      let floretSize = this.size * 0.025 * (1 + random(-0.1, 0.1));
      ellipse(floretX, floretY, floretSize);
    }

    fill(centerColor1.levels[0] - 20, centerColor1.levels[1] - 20, centerColor1.levels[2] - 20);
    ellipse(0, 0, this.size * 0.15);

    pop();
  }
}
Line-by-line explanation (60 lines)

🔧 Subcomponents:

calculation Constructor Initialization constructor(x, y, size) {

Initializes a new Sunflower with position (x, y) and size, and randomizes petal length and width for variety

calculation Stem Drawing bezier(0, 0, this.size * 0.1, this.size * 0.5, -this.size * 0.1, this.size * 1.0, 0, this.size * 1.5);

Draws a curved stem from the center downward using a bezier curve with control points that create a natural arch

calculation Leaf Creation beginShape(); vertex(0, this.size * 0.6); bezierVertex(...)

Creates two leaf shapes using bezier curves and adds vein details with line() to simulate realistic leaf anatomy

for-loop Petal Rendering Loop for (let i = 0; i < this.numPetals; i++) {

Repeats 60 times to draw 60 petals arranged in a circle, rotating the coordinate system each iteration

for-loop Phyllotaxis Golden Spiral for (let i = 0; i < totalFlorets; i++) {

Draws 800 small circles arranged in a golden-angle spiral pattern using trigonometry and the 137.5-degree golden angle

class Sunflower {
Declares a new ES6 class named Sunflower that will hold all the data and methods needed to create and draw a sunflower
constructor(x, y, size) {
The constructor function runs when new Sunflower(...) is called, storing the position and size passed as arguments
this.x = x;
Stores the x position as a property of this Sunflower object, making it available to other methods like display()
this.y = y;
Stores the y position as a property of this Sunflower object
this.size = size;
Stores the size (diameter) as a property, used as a scale factor for all parts of the flower
this.petalLength = random(this.size * 0.4, this.size * 0.6);
Randomizes petal length for each flower between 40% and 60% of the flower's size, making each sunflower unique
this.petalWidth = random(this.size * 0.1, this.size * 0.15);
Randomizes petal width between 10% and 15% of the flower's size for natural variation
this.numPetals = 60;
Sets the number of petals to 60 for all sunflowers—a dense, full appearance that looks natural
display() {
The display() method contains all the code to draw this sunflower; called once per frame for each sunflower
push();
Saves the current drawing state (color, stroke, transform settings) so they can be restored after drawing this flower
translate(this.x, this.y);
Moves the origin (0, 0) to the sunflower's position, so all coordinates inside are relative to the center of the flower
stroke(stemColor);
Sets the stroke color to green (stemColor) for drawing the stem
strokeWeight(this.size * 0.04);
Sets the stroke thickness proportionally to the flower size—4% of diameter—creating a scaled stem
noFill();
Disables fill so the bezier curve draws only as an outline without a filled interior
bezier(0, 0, this.size * 0.1, this.size * 0.5, -this.size * 0.1, this.size * 1.0, 0, this.size * 1.5);
Draws a curved stem from the origin (0, 0) to (0, size * 1.5) downward, with control points that create a natural arch to the right then back
noStroke();
Disables stroke before drawing the leaf, so it won't have an outline
fill(leafColor);
Sets the fill color to light green (leafColor) for the leaf shape
beginShape();
Starts defining a custom shape using vertex() and bezierVertex() calls
vertex(0, this.size * 0.6);
Places the first vertex of the left leaf at the top center, at a position 60% down the flower size
bezierVertex(-this.size * 0.3, this.size * 0.3, -this.size * 0.6, this.size * 0.5, -this.size * 0.5, this.size * 1.0);
Draws a curved line from the vertex toward the left using two control points, creating a natural leaf curve
bezierVertex(-this.size * 0.2, this.size * 1.3, -this.size * 0.1, this.size * 1.2, 0, this.size * 1.2);
Continues the leaf curve back toward the center with another bezier segment, closing the leaf outline
endShape(CLOSE);
Closes the leaf shape by connecting the last vertex back to the first one, creating a solid leaf polygon
stroke(stemColor);
Re-enables stroke with green color to draw leaf veins
strokeWeight(1);
Sets a thin stroke weight of 1 pixel for the delicate veins
line(-this.size * 0.2, this.size * 0.7, -this.size * 0.5, this.size * 1.0);
Draws one vein from the left leaf surface down toward the tip, adding anatomical detail
line(-this.size * 0.1, this.size * 0.9, -this.size * 0.4, this.size * 1.2);
Draws a second vein on the left leaf for a more realistic, veined appearance
let angleStep = TWO_PI / this.numPetals;
Calculates the angle between each petal: 360 degrees (TWO_PI radians) divided by 60 petals ≈ 6 degrees per petal
for (let i = 0; i < this.numPetals; i++) {
Loops 60 times to draw all petals, rotating the coordinate system between each iteration
rotate(angleStep);
Rotates the entire coordinate system by 6 degrees, so the next petal draws at the next angular position
let currentPetalLength = this.petalLength * random(0.95, 1.05);
Adds slight random variation (±5%) to this petal's length, making each petal slightly different in length for realism
let currentPetalWidth = this.petalWidth * random(0.9, 1.1);
Adds slight random variation (±10%) to this petal's width, creating organic asymmetry
fill(200, 160, 0, 150);
Sets fill to a darker yellow-orange (RGB: 200, 160, 0) with alpha 150 (semi-transparent) for a shadow beneath each petal
ellipse(0, currentPetalLength / 2, currentPetalWidth * 1.1, currentPetalLength * 1.1);
Draws an ellipse beneath each petal to simulate depth and shadow, making the petals appear raised and 3D
if (i % 2 === 0) {
Checks if the petal index is even (0, 2, 4, ...) to alternate petal colors
fill(petalColor1);
For even-numbered petals, uses bright yellow (petalColor1)
} else {
For odd-numbered petals (1, 3, 5, ...), use the next color
fill(petalColor2);
Uses a slightly deeper orange-yellow (petalColor2) for odd petals, creating color variation
stroke(200, 160, 0);
Sets the stroke color to a darker yellow-orange for petal outlines
strokeWeight(0.5);
Sets a very thin stroke of 0.5 pixels to subtly define each petal without overwhelming the fill color
beginShape();
Starts defining the petal shape using custom bezier curves
vertex(0, 0);
Places the first vertex at the origin, which is the base of the petal
bezierVertex(-currentPetalWidth * 0.3, currentPetalLength * 0.2, -currentPetalWidth * 0.5, currentPetalLength * 0.4, 0, currentPetalLength);
Draws the left side of the petal from base to tip using two control points, creating a curved left edge
bezierVertex(currentPetalWidth * 0.5, currentPetalLength * 0.4, currentPetalWidth * 0.3, currentPetalLength * 0.2, 0, 0);
Draws the right side of the petal back to the base, mirroring the left curve to create a symmetrical leaf-like petal shape
endShape(CLOSE);
Closes the petal shape, completing a single petal that will be rotated and repeated 60 times
const c = this.size * 0.005;
Calculates a scaling constant for the spiral—smaller sunflowers have tighter spirals, larger ones have looser spirals
const totalFlorets = 800;
Sets the number of small circles in the golden spiral to 800, creating a dense, detailed center
for (let i = 0; i < totalFlorets; i++) {
Loops 800 times to place 800 small circles in a spiral pattern
let angle = i * 137.5;
Multiplies the iteration number by 137.5 degrees (the golden angle), which naturally creates the Fibonacci spiral seen in sunflowers
let r = c * sqrt(i);
Calculates the distance from center using the square root of the iteration—this creates the expanding spiral shape
let floretX = r * cos(angle);
Converts polar coordinates (angle, r) to Cartesian coordinates using cosine for the x component
let floretY = r * sin(angle);
Converts polar coordinates using sine for the y component, placing florets in a spiral pattern
if (i % 2 === 0) {
Alternates between two floret colors for even (0, 2, 4...) and odd (1, 3, 5...) iterations
fill(centerColor1);
For even florets, uses dark brown (centerColor1)
} else {
For odd florets, switch to the lighter color
fill(centerColor2);
Uses lighter brown-orange (centerColor2), creating a checkered texture in the spiral
let floretSize = this.size * 0.025 * (1 + random(-0.1, 0.1));
Calculates each floret's diameter as 2.5% of the flower size, with ±10% random variation to avoid a too-regular grid
ellipse(floretX, floretY, floretSize);
Draws a small circle at the spiral position, completing one iteration of the 800-floret loop
fill(centerColor1.levels[0] - 20, centerColor1.levels[1] - 20, centerColor1.levels[2] - 20);
Creates a slightly darker brown by subtracting 20 from each RGB component of centerColor1, creating a darkened version
ellipse(0, 0, this.size * 0.15);
Draws a small dark circle at the very center of the flower (origin), defining the center point and tying all elements together
pop();
Restores all drawing settings (colors, strokes, transforms) to what they were before display() was called

📦 Key Variables

targetTimestamp number

Stores the past moment in milliseconds since January 1, 1970, calculated as 128 days and 10 hours ago from setup(). Used in draw() to calculate elapsed time.

let targetTimestamp = Date.now() - (128 * 24 * 60 * 60 * 1000) - (10 * 60 * 60 * 1000);
sunflowers array

Stores an array of 50 Sunflower objects. Each element is a Sunflower instance with its own position and size.

let sunflowers = [];
numSunflowers number

Constant that defines how many Sunflower objects to generate (50). Used in generateSunflowers() to control the loop count.

const numSunflowers = 50;
petalColor1 p5.Color

Stores the bright yellow color (255, 200, 0) used for alternating petals to create color depth.

let petalColor1 = color(255, 200, 0);
petalColor2 p5.Color

Stores the deeper orange-yellow color (255, 170, 0) used for alternating petals.

let petalColor2 = color(255, 170, 0);
centerColor1 p5.Color

Stores the dark brown color (120, 60, 10) used for alternating florets in the golden spiral center.

let centerColor1 = color(120, 60, 10);
centerColor2 p5.Color

Stores the lighter brown-orange color (180, 100, 30) used for alternating florets in the center spiral.

let centerColor2 = color(180, 100, 30);
stemColor p5.Color

Stores the natural green color (50, 160, 50) used for the stem and leaf veins.

let stemColor = color(50, 160, 50);
leafColor p5.Color

Stores the slightly brighter green color (70, 180, 70) used to fill the leaf shapes.

let leafColor = color(70, 180, 70);
backgroundColor p5.Color

Stores the light pastel green color (220, 255, 220) used as the background, simulating a sunny field.

let backgroundColor = color(220, 255, 220);

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

BUG setup() and draw()

Global color variables (petalColor1, centerColor1, etc.) are not declared with 'let' or 'const', making them implicit globals that could cause conflicts in larger projects.

💡 Add 'let' or 'const' to each color declaration: let petalColor1; let petalColor2; etc. at the top of the file, then assign values in setup().

PERFORMANCE Sunflower.display() phyllotaxis loop

Drawing 800 florets for each of 50 sunflowers means 40,000 ellipse() calls per frame—this can slow down the sketch on slower devices.

💡 Reduce totalFlorets from 800 to 400 or use createGraphics() to cache sunflower images, drawing each sunflower to a buffer once instead of redrawing every frame.

STYLE Sunflower class constructor

The hardcoded numPetals = 60 is not randomized, so every sunflower has exactly 60 petals—real sunflowers vary more.

💡 Change to this.numPetals = floor(random(50, 70)) to give each flower 50-70 petals randomly, increasing visual diversity.

FEATURE sketch structure

The target date is hardcoded as 128 days and 10 hours ago, making it impossible to customize without editing the sketch—not user-friendly for couples with different relationship durations.

💡 Add a simple input form in index.html or use a URL parameter (e.g., ?days=100&hours=5) to let users set their own anniversary date without touching code.

BUG draw() text box calculation

The lineHeightFactor of 1.2 is an approximation—on some browsers/fonts, text might overflow the box or leave large gaps depending on line-height settings.

💡 Use textAscent() + textDescent() instead to measure actual text dimensions, or add a small buffer constant (e.g., boxHeight + 10) for safety.

🔄 Code Flow

Code flow showing setup, draw, windowresized, generatesunflowers, sunflower

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> windowresized[windowresized] draw --> colorpalette[color-palette] draw --> timestampcalculation[timestamp-calculation] draw --> textconfiguration[text-configuration] draw --> sunflowerloop[sunflower-loop] draw --> timeconversion[time-conversion] draw --> moduloextraction[modulo-extraction] draw --> boxdimensions[box-dimensions] draw --> boxdrawing[box-drawing] click setup href "#fn-setup" click draw href "#fn-draw" click windowresized href "#fn-windowresized" click colorpalette href "#sub-color-palette" click timestampcalculation href "#sub-timestamp-calculation" click textconfiguration href "#sub-text-configuration" click sunflowerloop href "#sub-sunflower-loop" click timeconversion href "#sub-time-conversion" click moduloextraction href "#sub-modulo-extraction" click boxdimensions href "#sub-box-dimensions" click boxdrawing href "#sub-box-drawing" sunflowerloop --> sunflowercreationloop[sunflower-creation-loop] sunflowercreationloop --> constructor[constructor] constructor --> stemdrawing[stem-drawing] constructor --> leafdrawing[leaf-drawing] constructor --> petalloop[petal-loop] petalloop --> phyllotaxiscenter[phyllotaxis-center] click sunflowercreationloop href "#sub-sunflower-creation-loop" click constructor href "#sub-constructor" click stemdrawing href "#sub-stem-drawing" click leafdrawing href "#sub-leaf-drawing" click petalloop href "#sub-petal-loop" click phyllotaxiscenter href "#sub-phyllotaxis-center" windowresized --> arrayreset[array-reset] arrayreset --> sunflowercreationloop

❓ Frequently Asked Questions

What visual elements are featured in the te amo p5.js sketch?

The te amo sketch creates a vibrant field of sunflowers, utilizing a realistic color palette to depict their petals, centers, and stems against a light green background.

Is there any user interaction in the te amo sketch?

The current version of the sketch does not include interactive elements; it focuses on generating and displaying a static field of sunflowers.

What creative coding concepts are showcased in the te amo sketch?

This sketch demonstrates the use of object-oriented programming to create and manage multiple sunflower instances, along with responsive design techniques for text and visuals.

Preview

te amo - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of te amo - Code flow showing setup, draw, windowresized, generatesunflowers, sunflower
Code Flow Diagram