lostapp for me only

LostApp is a secure personal vault web application that requires login credentials to access. Once unlocked, users can store text entries, images, and videos with an optional time-budget tracker (maximum 180 minutes total), and email their stored entries as a report.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the login credentials — Different username and password will immediately grant access with your custom credentials
  2. Increase the time budget to 300 minutes — Higher budget lets you store more entries before hitting the limit
  3. Change the background color from dark to lighter — The background() value changes the canvas fill instantly—try 50 for medium gray or 100 for lighter
  4. Make the floating dots move downward instead of upward — Reversing the dot movement creates a different visual effect—dots now drift down and recycle from the top
  5. Change the report email address — Clicking 'Email LostApp Report' will now open your default email client with a different recipient address
  6. Remove the requirement for at least one field to have content — Changes validation so entries save even if all fields are empty (good for testing)
Prefer the full editor? Open it there →

📖 About This Sketch

LostApp is a personal data vault disguised as a p5.js sketch. It combines a animated background (p5.js canvas with floating dots) with a full web interface built entirely in p5.js using createInput(), createButton(), and createDiv(). The sketch enforces a login system with hardcoded credentials, stores entries in an array, validates a 180-minute time budget across all entries, and lets users email a formatted report of everything they've saved. This is a rare example of p5.js stepping beyond graphics to become a complete interactive application.

The code is organized into three layers: a p5.js drawing loop that animates the background, a setupUI() function that creates and configures all form elements once at startup, and a suite of handler functions that respond to button clicks and validate user input. By studying it, you will learn how to use p5.js createElement(), event handlers, state management with global variables, and string manipulation—skills that let you turn sketches into functional web apps.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-screen canvas and populates a bgDots array with 200 particles that will animate in the background. setupUI() then constructs the login form and all input fields, initially hiding the data-entry controls.
  2. The draw() loop runs continuously, moving background dots upward and redrawing them, plus displaying the title 'LostApp Secure Vault' at the bottom.
  3. When the user enters a login name and password and clicks 'Unlock LostApp', handleUnlock() checks them against the hardcoded LOGIN_NAME and PASSWORD constants.
  4. If the credentials match, vaultUnlocked becomes true and updateUI() unhides all the data-entry fields (flyInput, timeInput, imageInput, videoInput) and the 'Add to LostApp' button.
  5. As the user fills in a description, time allocation, and optional media URLs and clicks 'Add to LostApp', addFly() validates that at least one field has content and that the new time doesn't exceed the 180-minute budget. If valid, the entry is pushed to the flies array.
  6. renderFlyList() rebuilds the visible list of all saved entries in the DOM, displaying descriptions, time allocations, and embedded images/videos. sendLostAppReport() formats all entries into an email draft that opens in the user's default mail client.

🎓 Concepts You'll Learn

DOM manipulation and p5.js createElementEvent handling with mousePressed callbacksState management with global variablesForm validation and input constraintsArray operations and data persistenceString encoding and email generation

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It is the perfect place to initialize variables, create the canvas, and build UI elements that exist for the entire program's lifetime.

function setup() {
  createCanvas(windowWidth, windowHeight);
  for (let i = 0; i < 200; i++) {
    bgDots.push({
      x: random(width),
      y: random(height),
      size: random(2, 5),
      speed: random(0.2, 0.6)
    });
  }
  setupUI();
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

function_call Canvas initialization createCanvas(windowWidth, windowHeight);

Creates a p5.js canvas that fills the entire browser window

for-loop Background particles loop for (let i = 0; i < 200; i++) {

Populates bgDots array with 200 randomly positioned particles with varying sizes and speeds

function_call UI construction setupUI();

Calls setupUI() to create all login and data-entry form elements in the DOM

createCanvas(windowWidth, windowHeight);
Creates a full-screen p5.js canvas; windowWidth and windowHeight are p5.js variables that equal the browser window dimensions
for (let i = 0; i < 200; i++) {
Loops 200 times to create 200 background dot particles
x: random(width),
Assigns each dot a random horizontal position between 0 and the canvas width
y: random(height),
Assigns each dot a random vertical position between 0 and the canvas height
size: random(2, 5),
Gives each dot a random diameter between 2 and 5 pixels
speed: random(0.2, 0.6)
Assigns each dot an upward drift speed between 0.2 and 0.6 pixels per frame
setupUI();
Calls the setupUI function to create all HTML form elements that users interact with

draw()

draw() runs about 60 times per second, creating smooth animation. Every frame you must redraw the background and everything you want to animate. The particle recycling pattern (checking if something exits, then resetting it) is a classic technique for infinite scrolling effects.

🔬 This loop draws each dot and moves it upward. What happens if you change dot.y -= dot.speed to dot.y += dot.speed? Which direction will the dots flow?

  for (const dot of bgDots) {
    circle(dot.x, dot.y, dot.size);
    dot.y -= dot.speed;
    if (dot.y < -10) {
      dot.y = height + 10;
      dot.x = random(width);
    }
  }
function draw() {
  background(10);

  noStroke();
  fill(0, 255, 159, 80);
  for (const dot of bgDots) {
    circle(dot.x, dot.y, dot.size);
    dot.y -= dot.speed;
    if (dot.y < -10) {
      dot.y = height + 10;
      dot.x = random(width);
    }
  }

  push();
  textAlign(CENTER, CENTER);
  textSize(32);
  fill(0, 255, 159, 150);
  text('LostApp Secure Vault', width / 2, height - 60);
  pop();
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

function_call Background clear background(10);

Fills the canvas with a dark gray color, erasing the previous frame so dots don't leave trails

for-loop Animate background dots for (const dot of bgDots) {

Iterates through every dot, draws it, moves it upward, and recycles it at the bottom when it exits the canvas

conditional Particle recycling if (dot.y < -10) {

When a dot drifts off the top of the canvas, resets it to the bottom at a random x position

function_call Title text rendering text('LostApp Secure Vault', width / 2, height - 60);

Draws the app title centered horizontally and 60 pixels from the bottom of the canvas

background(10);
Fills the entire canvas with a very dark gray (nearly black, RGB value 10,10,10). This erases everything drawn last frame, preventing trails.
noStroke();
Tells p5.js to draw shapes without outlines—only fill colors will be visible
fill(0, 255, 159, 80);
Sets the fill color to bright cyan (RGB 0,255,159) with transparency 80 out of 255, making dots semi-transparent
for (const dot of bgDots) {
Loops through every dot object in the bgDots array
circle(dot.x, dot.y, dot.size);
Draws a circle at the dot's current x,y position with its diameter set to dot.size
dot.y -= dot.speed;
Decreases the dot's y position by its speed value, moving it upward (lower y = higher on screen in p5.js)
if (dot.y < -10) {
Checks if the dot has drifted more than 10 pixels above the top of the canvas (y < -10 means off-screen)
dot.y = height + 10;
Resets the dot to just below the bottom of the canvas, ready to drift up again
dot.x = random(width);
Gives the recycled dot a new random horizontal position across the full canvas width
textAlign(CENTER, CENTER);
Centers all text horizontally and vertically at the coordinates you specify
textSize(32);
Sets the font size to 32 pixels for the title text
fill(0, 255, 159, 150);
Changes the fill color to bright cyan with higher opacity (150), making the title more visible than the dots
text('LostApp Secure Vault', width / 2, height - 60);
Draws the title string centered horizontally (width/2) and positioned 60 pixels from the bottom

setupUI()

setupUI() is a mega-function that creates every HTML element the app uses. It runs once in setup(). Notice how every element is stored in a global variable (userInput, passButton, etc.) so that other functions can show/hide them or read their values later. The .parent() method welds child elements to a parent container, building the DOM tree.

function setupUI() {
  uiWrapper = createDiv().id('ui-container');

  const title = createElement('h2', 'LostApp Access');
  title.parent(uiWrapper);

  statusText = createP('Enter your login name and password to unlock LostApp.');
  statusText.parent(uiWrapper);

  userInput = createInput('', 'text');
  userInput.attribute('placeholder', 'Login name');
  userInput.parent(uiWrapper);

  passInput = createInput('', 'password');
  passInput.attribute('placeholder', 'Password');
  passInput.parent(uiWrapper);

  passButton = createButton('Unlock LostApp');
  passButton.parent(uiWrapper);
  passButton.mousePressed(handleUnlock);

  flyInput = createInput('', 'text');
  flyInput.attribute('placeholder', 'Describe a fly...');
  flyInput.parent(uiWrapper);
  flyInput.hide();

  timeInput = createInput('', 'number');
  timeInput.attribute('placeholder', 'Minutes to store (0–180)');
  timeInput.attribute('min', 0);
  timeInput.attribute('max', MAX_MINUTES);
  timeInput.parent(uiWrapper);
  timeInput.hide();

  imageInput = createInput('', 'text');
  imageInput.attribute('placeholder', 'Image URL (optional)');
  imageInput.parent(uiWrapper);
  imageInput.hide();

  videoInput = createInput('', 'text');
  videoInput.attribute('placeholder', 'Video URL (optional)');
  videoInput.parent(uiWrapper);
  videoInput.hide();

  addFlyButton = createButton('Add to LostApp');
  addFlyButton.parent(uiWrapper);
  addFlyButton.mousePressed(addFly);
  addFlyButton.hide();

  timeStatus = createP(`Stored time: 0 / ${MAX_MINUTES} min`);
  timeStatus.parent(uiWrapper);
  timeStatus.hide();

  sendEmailButton = createButton('Email LostApp Report');
  sendEmailButton.parent(uiWrapper);
  sendEmailButton.mousePressed(sendLostAppReport);
  sendEmailButton.hide();

  flyListDiv = createDiv();
  flyListDiv.parent(uiWrapper);
  flyListDiv.hide();
}
Line-by-line explanation (21 lines)

🔧 Subcomponents:

function_call Container creation uiWrapper = createDiv().id('ui-container');

Creates a div element and assigns it the CSS ID 'ui-container' so styles apply; stores reference for later parent operations

function_call Title element const title = createElement('h2', 'LostApp Access');

Creates an HTML heading with the text 'LostApp Access' for visual hierarchy

function_call Status message statusText = createP('Enter your login name and password to unlock LostApp.');

Creates a paragraph element that will hold status messages; stored as global so handleUnlock() and other functions can update it

function_call Authentication fields userInput = createInput('', 'text');

Creates text input for username and password input for the password field; both hidden initially until unlock fails or succeeds

function_call Unlock button passButton.mousePressed(handleUnlock);

Attaches a click handler so clicking 'Unlock LostApp' calls handleUnlock()

function_call Data entry fields flyInput = createInput('', 'text');

Creates input fields for entry description, time, image URL, and video URL; all hidden until vault is unlocked

function_call Add entry button addFlyButton.mousePressed(addFly);

Attaches click handler to 'Add to LostApp' button so it calls addFly()

function_call Time status display timeStatus = createP(`Stored time: 0 / ${MAX_MINUTES} min`);

Creates a paragraph that displays current time usage out of the 180-minute budget

function_call Email report button sendEmailButton.mousePressed(sendLostAppReport);

Attaches click handler so 'Email LostApp Report' calls sendLostAppReport()

function_call Entries display area flyListDiv = createDiv();

Creates a container div where renderFlyList() will display all saved entries

uiWrapper = createDiv().id('ui-container');
Creates a new div element and assigns it the CSS ID 'ui-container' (defined in style.css with border, background, and glow). Stores the reference in the global uiWrapper variable.
const title = createElement('h2', 'LostApp Access');
Creates an HTML <h2> heading with text 'LostApp Access' for a prominent title
title.parent(uiWrapper);
Places the title inside the uiWrapper div by setting uiWrapper as its parent
statusText = createP('Enter your login name and password to unlock LostApp.');
Creates a paragraph element for status messages (like '✅ LostApp unlocked!' or '❌ Invalid login'). Stored globally so other functions can change its text.
userInput = createInput('', 'text');
Creates a text input field for the login name; starts empty ('') and is stored globally so handleUnlock() can read its value
userInput.attribute('placeholder', 'Login name');
Adds the placeholder text 'Login name' which shows inside the input when it's empty
passInput = createInput('', 'password');
Creates a password input field (text is hidden as dots) stored globally for handleUnlock() to read
passButton = createButton('Unlock LostApp');
Creates a button with text 'Unlock LostApp' and stores it globally so updateUI() can change it to 'Lock LostApp' later
passButton.mousePressed(handleUnlock);
Attaches a click event listener: whenever the user clicks this button, the handleUnlock() function is called
flyInput = createInput('', 'text');
Creates a text input for entry descriptions, stored globally and initially hidden until the vault is unlocked
timeInput = createInput('', 'number');
Creates a number input field for how many minutes to allocate to this entry; p5.js enforces numeric input
timeInput.attribute('max', MAX_MINUTES);
Sets the HTML max attribute to 180 (MAX_MINUTES), so the input won't accept values higher than 180
imageInput = createInput('', 'text');
Creates a text input for image URLs, initially hidden, optional for entries
videoInput = createInput('', 'text');
Creates a text input for video URLs, initially hidden, optional for entries
addFlyButton = createButton('Add to LostApp');
Creates a button labeled 'Add to LostApp' and stores it globally so it can be hidden/shown by updateUI()
addFlyButton.mousePressed(addFly);
Attaches a click handler: clicking the button calls the addFly() function
timeStatus = createP(`Stored time: 0 / ${MAX_MINUTES} min`);
Creates a paragraph showing time usage; uses template literal to embed MAX_MINUTES (180) into the text
sendEmailButton = createButton('Email LostApp Report');
Creates a button that will trigger email report generation, stored globally so updateUI() can hide/show it
sendEmailButton.mousePressed(sendLostAppReport);
Attaches a click handler so the button calls sendLostAppReport()
flyListDiv = createDiv();
Creates an empty div container where renderFlyList() will dynamically insert HTML for each saved entry
flyListDiv.hide();
Hides the list div initially (CSS display:none) until the vault is unlocked

handleUnlock()

handleUnlock() is called whenever the user clicks the 'Unlock LostApp' button. It's an example of a callback function—code that only runs in response to a user event. Notice how it reads input values with .value(), compares them, and then updates both the global vaultUnlocked flag and the DOM (statusText.html()) to reflect the result. This two-part update (state + UI) is a pattern you'll use constantly in interactive sketches.

🔬 The .trim() method removes spaces. What happens if you remove the .trim() calls? Try entering ' ddos1337 ' (with spaces) as the username—does it match?

  const attemptUser = userInput.value().trim();
  const attemptPass = passInput.value().trim();

  if (attemptUser === LOGIN_NAME && attemptPass === PASSWORD) {
function handleUnlock() {
  const attemptUser = userInput.value().trim();
  const attemptPass = passInput.value().trim();

  if (attemptUser === LOGIN_NAME && attemptPass === PASSWORD) {
    vaultUnlocked = true;
    updateUI();
    statusText.html('✅ LostApp unlocked! Add new entries below.');
  } else {
    vaultUnlocked = false;
    statusText.html('❌ Invalid login name or password. LostApp stays sealed.');
  }

  userInput.value('');
  passInput.value('');
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

calculation Input capture and trimming const attemptUser = userInput.value().trim();

Reads the text from the username input field and removes leading/trailing whitespace

conditional Credential verification if (attemptUser === LOGIN_NAME && attemptPass === PASSWORD) {

Compares entered credentials against hardcoded LOGIN_NAME and PASSWORD; if both match, sets vaultUnlocked = true

calculation Input clearing userInput.value('');

Clears both input fields so the password doesn't remain visible on screen after login attempt

const attemptUser = userInput.value().trim();
Reads the current text from the username input field using .value(), then calls .trim() to remove spaces from the beginning and end
const attemptPass = passInput.value().trim();
Reads the password field text and trims whitespace the same way
if (attemptUser === LOGIN_NAME && attemptPass === PASSWORD) {
Checks if BOTH the entered username equals the hardcoded LOGIN_NAME ('ddos1337') AND the password equals PASSWORD ('123456789'). Only if both are true, the condition passes.
vaultUnlocked = true;
Sets the global vaultUnlocked flag to true, signaling that the vault is now open
updateUI();
Calls updateUI() to show all the hidden input fields and buttons that were created in setupUI()
statusText.html('✅ LostApp unlocked! Add new entries below.');
Updates the status paragraph to display a success message with a checkmark emoji
} else {
If the credentials do NOT match, the else block runs
vaultUnlocked = false;
Sets vaultUnlocked to false (stays sealed)
statusText.html('❌ Invalid login name or password. LostApp stays sealed.');
Updates the status paragraph to display an error message
userInput.value('');
Clears the username input field by setting its value to an empty string, so the password is not left visible
passInput.value('');
Clears the password input field for security

updateUI()

updateUI() is a state-sync function. Whenever vaultUnlocked changes, call this to show or hide UI elements accordingly. It demonstrates a key pattern: always keep your DOM in sync with your application's state (stored in variables). This makes code predictable: if you want to know what the user sees, just check the vaultUnlocked flag.

function updateUI() {
  if (vaultUnlocked) {
    passButton.html('Lock LostApp');
    passButton.mousePressed(lockVault);

    flyInput.show();
    timeInput.show();
    imageInput.show();
    videoInput.show();
    addFlyButton.show();
    flyListDiv.show();
    timeStatus.show();
    sendEmailButton.show();
    renderFlyList();
  } else {
    passButton.html('Unlock LostApp');
    passButton.mousePressed(handleUnlock);

    flyInput.hide();
    timeInput.hide();
    imageInput.hide();
    videoInput.hide();
    addFlyButton.hide();
    flyListDiv.hide();
    timeStatus.hide();
    sendEmailButton.hide();
  }
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

conditional Unlocked state branch if (vaultUnlocked) {

Checks if vault is unlocked; if true, shows all data-entry controls and changes button behavior to lock the vault

conditional Locked state branch } else {

If vault is locked, hides all data-entry controls and resets button to unlock behavior

if (vaultUnlocked) {
Checks the global vaultUnlocked flag; if true, runs the unlock state setup
passButton.html('Lock LostApp');
Changes the button's text from 'Unlock LostApp' to 'Lock LostApp' so the user can now lock it again
passButton.mousePressed(lockVault);
Replaces the click handler: now clicking the button will call lockVault() instead of handleUnlock()
flyInput.show();
Makes the entry description input visible (CSS display changes from none to block)
timeInput.show();
Shows the time allocation number input
imageInput.show();
Shows the image URL input
videoInput.show();
Shows the video URL input
addFlyButton.show();
Shows the 'Add to LostApp' button
flyListDiv.show();
Shows the container where saved entries will be displayed
timeStatus.show();
Shows the time usage tracker paragraph
sendEmailButton.show();
Shows the 'Email LostApp Report' button
renderFlyList();
Calls renderFlyList() to build the initial display of all saved entries (if any)
} else {
If vaultUnlocked is false, this branch runs
passButton.html('Unlock LostApp');
Resets the button text back to 'Unlock LostApp'
passButton.mousePressed(handleUnlock);
Reattaches the handleUnlock() handler so clicking tries to unlock again
flyInput.hide();
Hides all the data-entry inputs and buttons, showing only the login prompt

lockVault()

lockVault() is a simple state reversal: it sets vaultUnlocked to false and calls updateUI() to hide the data-entry interface. It's paired with handleUnlock() as the inverse operation.

function lockVault() {
  vaultUnlocked = false;
  statusText.html('LostApp locked. Enter your credentials to unlock again.');
  updateUI();
}
Line-by-line explanation (3 lines)
vaultUnlocked = false;
Sets the global vaultUnlocked flag to false, marking the vault as locked
statusText.html('LostApp locked. Enter your credentials to unlock again.');
Updates the status paragraph to display a lock message
updateUI();
Calls updateUI() to hide all data-entry controls and show only the login form

addFly()

addFly() is the core save function. It validates three independent checks: (1) at least one field has content, (2) the time fits the budget, and (3) inputs are parsed correctly. It demonstrates careful defensive coding—every input is validated before being stored. The pattern of reading inputs → validating → storing → clearing → updating UI is fundamental to any form-based app.

🔬 This block requires at least ONE field to have content (description OR image OR video). What happens if you change && to || so the entry only saves if ALL three fields are filled? Try adding an entry with just a description.

  if (
    description.length === 0 &&
    imgURL.length === 0 &&
    vidURL.length === 0
  ) {
    statusText.html('⚠️ Add a description, image, or video before saving.');
    return;
  }
function addFly() {
  const description = flyInput.value().trim();
  const timeValue = parseInt(timeInput.value(), 10);
  const minutes = isNaN(timeValue) ? 0 : constrain(timeValue, 0, MAX_MINUTES);
  const imgURL = imageInput.value().trim();
  const vidURL = videoInput.value().trim();

  if (
    description.length === 0 &&
    imgURL.length === 0 &&
    vidURL.length === 0
  ) {
    statusText.html('⚠️ Add a description, image, or video before saving.');
    return;
  }

  const currentMinutes = getTotalStoredMinutes();
  if (currentMinutes + minutes > MAX_MINUTES) {
    statusText.html(`⚠️ Not enough storage left. You can only store ${MAX_MINUTES - currentMinutes} more minute(s).`);
    return;
  }

  flies.push({
    desc: description || '(silent fly thoughts)',
    minutes,
    image: imgURL,
    video: vidURL
  });

  flyInput.value('');
  timeInput.value('');
  imageInput.value('');
  videoInput.value('');
  statusText.html('✅ Entry stored in LostApp!');
  renderFlyList();
}
Line-by-line explanation (22 lines)

🔧 Subcomponents:

calculation Input field capture const description = flyInput.value().trim();

Reads and trims all four input fields (description, time, image URL, video URL)

calculation Time value parsing const timeValue = parseInt(timeInput.value(), 10);

Converts the time input to an integer; parseInt returns NaN if the input is empty or non-numeric

calculation Time constraint const minutes = isNaN(timeValue) ? 0 : constrain(timeValue, 0, MAX_MINUTES);

If time input was invalid (NaN), defaults to 0; otherwise clamps it between 0 and MAX_MINUTES (180) using constrain()

conditional Content validation if ( description.length === 0 && imgURL.length === 0 && vidURL.length === 0 ) {

Ensures the entry has at least one piece of content (description, image, or video); returns early if all three are empty

conditional Time budget validation if (currentMinutes + minutes > MAX_MINUTES) {

Checks if adding this entry would exceed the 180-minute total budget; prevents the save if it would

calculation Entry storage flies.push({

Creates a new object with the entry's description, time allocation, and media URLs, then adds it to the flies array

calculation Input reset flyInput.value('');

Clears all four input fields after a successful save, ready for the next entry

const description = flyInput.value().trim();
Reads the entry description from the flyInput field and removes whitespace
const timeValue = parseInt(timeInput.value(), 10);
Reads the time input, converts it to an integer base-10; returns NaN if empty or non-numeric
const minutes = isNaN(timeValue) ? 0 : constrain(timeValue, 0, MAX_MINUTES);
Uses a ternary operator: if timeValue is NaN (invalid), use 0 minutes; otherwise use constrain() to cap it between 0 and 180
const imgURL = imageInput.value().trim();
Reads the image URL and trims whitespace
const vidURL = videoInput.value().trim();
Reads the video URL and trims whitespace
if ( description.length === 0 && imgURL.length === 0 && vidURL.length === 0 ) {
Checks if all three fields (description, image, video) are empty using .length === 0. If all three are empty, the user must add at least one piece of content.
statusText.html('⚠️ Add a description, image, or video before saving.');
Displays a warning message
return;
Exits the function early without saving, preventing an empty entry from being added
const currentMinutes = getTotalStoredMinutes();
Calls getTotalStoredMinutes() to sum up the time allocated to all existing entries
if (currentMinutes + minutes > MAX_MINUTES) {
Checks if adding the new entry's minutes would push the total over 180. If so, the save is blocked.
statusText.html(`⚠️ Not enough storage left. You can only store ${MAX_MINUTES - currentMinutes} more minute(s).`);
Shows an error message with the exact number of remaining minutes the user can store (180 - currentMinutes)
flies.push({
Creates a new object and adds it to the flies array
desc: description || '(silent fly thoughts)',
Uses the OR operator (||): if description is empty, use the fallback text '(silent fly thoughts)' instead
minutes,
Shorthand for minutes: minutes, storing the validated time value
image: imgURL,
Stores the image URL (empty string if not provided)
video: vidURL
Stores the video URL (empty string if not provided)
flyInput.value('');
Clears the description input so it's ready for the next entry
timeInput.value('');
Clears the time input
imageInput.value('');
Clears the image URL input
videoInput.value('');
Clears the video URL input
statusText.html('✅ Entry stored in LostApp!');
Shows a success message
renderFlyList();
Calls renderFlyList() to rebuild and display the updated list of all entries

renderFlyList()

renderFlyList() is the display function: it clears the list and rebuilds it from scratch every time an entry is added. This is simpler than updating individual DOM elements and ensures the display is always in sync with the flies array. The forEach loop pattern with idx lets you number entries, and the conditional if statements (if fly.image, if fly.video) gracefully handle optional media.

🔬 The ternary operator decides whether to show the time. What happens if you change > 0 to > 60, so time is only displayed for entries with more than 60 minutes?

    const minutesText = fly.minutes > 0 ? ` — ${fly.minutes} min stored` : '';
    const info = createP(`${fly.desc}${minutesText}`);
function renderFlyList() {
  flyListDiv.html('');
  flies.forEach((fly, idx) => {
    const entry = createDiv();
    entry.addClass('fly-entry');
    entry.parent(flyListDiv);

    const label = createElement('strong', `LostApp Entry #${idx + 1}`);
    label.parent(entry);

    const minutesText = fly.minutes > 0 ? ` — ${fly.minutes} min stored` : '';
    const info = createP(`${fly.desc}${minutesText}`);
    info.parent(entry);

    if (fly.image) {
      const img = createImg(fly.image, `Entry ${idx + 1} image`);
      img.parent(entry);
      img.attribute('loading', 'lazy');
    }

    if (fly.video) {
      const videoEl = createElement('video');
      videoEl.attribute('src', fly.video);
      videoEl.attribute('controls', true);
      videoEl.attribute('preload', 'metadata');
      videoEl.parent(entry);
    }
  });

  updateTimeStatus();
}
Line-by-line explanation (21 lines)

🔧 Subcomponents:

function_call List reset flyListDiv.html('');

Clears the entire list container by setting its HTML to empty, removing old entries before rebuilding

for-loop Entry iteration loop flies.forEach((fly, idx) => {

Iterates through every entry in the flies array; idx is the index (0, 1, 2, etc.)

function_call Entry wrapper creation const entry = createDiv();

Creates a new div for each entry to hold its description, media, and metadata

function_call Entry title const label = createElement('strong', `LostApp Entry #${idx + 1}`);

Creates a bold label showing the entry number (1, 2, 3, etc.)

calculation Description and time display const info = createP(`${fly.desc}${minutesText}`);

Creates a paragraph combining the entry description and time allocation (if any)

conditional Image embedding if (fly.image) {

Only creates an img element if the entry has an image URL

conditional Video embedding if (fly.video) {

Only creates a video element if the entry has a video URL

flyListDiv.html('');
Clears the entire list container by setting its inner HTML to an empty string, removing all previously displayed entries
flies.forEach((fly, idx) => {
Loops through the flies array; for each fly entry, idx represents its position (0 for the first, 1 for the second, etc.)
const entry = createDiv();
Creates a new div element for this entry's content
entry.addClass('fly-entry');
Adds the CSS class 'fly-entry' (defined in style.css with dashed border, padding, and background) to style the entry
entry.parent(flyListDiv);
Places the entry div inside flyListDiv so it appears in the list
const label = createElement('strong', `LostApp Entry #${idx + 1}`);
Creates a bold heading with the entry number (1-indexed, so idx + 1). Template literal inserts the number into the string.
label.parent(entry);
Places the label inside this entry's div
const minutesText = fly.minutes > 0 ? ` — ${fly.minutes} min stored` : '';
Ternary operator: if fly.minutes is greater than 0, create a string like ' — 60 min stored'; otherwise use an empty string
const info = createP(`${fly.desc}${minutesText}`);
Creates a paragraph combining the entry description and the minutes text (if any)
info.parent(entry);
Places the paragraph inside this entry
if (fly.image) {
Checks if this entry has an image URL (non-empty string). Only runs if there IS an image.
const img = createImg(fly.image, `Entry ${idx + 1} image`);
Creates an img element with src set to fly.image and alt text set to the entry number
img.parent(entry);
Places the image inside this entry
img.attribute('loading', 'lazy');
Sets the HTML loading attribute to 'lazy', making images load only when they're about to scroll into view (improves performance)
if (fly.video) {
Checks if this entry has a video URL. Only runs if there IS a video.
const videoEl = createElement('video');
Creates an HTML video element
videoEl.attribute('src', fly.video);
Sets the src attribute to the video URL
videoEl.attribute('controls', true);
Adds built-in video player controls (play, pause, volume, fullscreen)
videoEl.attribute('preload', 'metadata');
Sets preload to 'metadata' so the browser loads video information (duration, dimensions) but not the full video file
videoEl.parent(entry);
Places the video element inside this entry
updateTimeStatus();
Calls updateTimeStatus() to refresh the time usage display after all entries are rendered

getTotalStoredMinutes()

getTotalStoredMinutes() is a utility function that uses the reduce() array method to compute a sum. reduce() takes a callback with (accumulator, currentItem) and returns a single aggregated value. It's the standard way to compute totals, averages, or other single values from an array.

function getTotalStoredMinutes() {
  return flies.reduce((sum, fly) => sum + (fly.minutes || 0), 0);
}
Line-by-line explanation (1 lines)

🔧 Subcomponents:

calculation Array reduction return flies.reduce((sum, fly) => sum + (fly.minutes || 0), 0);

Uses reduce() to iterate through all flies, accumulating their minutes into a single sum value

return flies.reduce((sum, fly) => sum + (fly.minutes || 0), 0);
Uses reduce() to sum all minutes: start with 0, then for each fly, add fly.minutes (or 0 if minutes is falsy). Returns the final total.

updateTimeStatus()

updateTimeStatus() is a simple display-update function. It gets the current total and formats it into the status paragraph. Call this whenever the flies array changes (after addFly() or renderFlyList()) to keep the UI in sync.

function updateTimeStatus() {
  const total = getTotalStoredMinutes();
  timeStatus.html(`Stored time: ${total} / ${MAX_MINUTES} min`);
}
Line-by-line explanation (2 lines)
const total = getTotalStoredMinutes();
Calls getTotalStoredMinutes() to get the sum of all minutes in the flies array
timeStatus.html(`Stored time: ${total} / ${MAX_MINUTES} min`);
Updates the timeStatus paragraph to display the current usage (e.g., 'Stored time: 120 / 180 min')

sendLostAppReport()

sendLostAppReport() demonstrates string building and URL encoding for email links. The mailto: protocol is a built-in browser feature that opens the default email client. encodeURIComponent() converts special characters (newlines, spaces, symbols) into safe URL format. This is a clever way to pre-populate emails without a server backend.

🔬 This loop formats each entry. What happens if you add another if-statement like if (fly.image) report += ... to show a custom emoji (like 🖼️) before image URLs? Try: if (fly.image) report += `\n 🖼️ Image: ${fly.image}`;

  flies.forEach((fly, idx) => {
    report += `${idx + 1}. ${fly.desc}`;
    if (fly.minutes) report += ` (${fly.minutes} min)`;
    if (fly.image) report += `\n   Image: ${fly.image}`;
    if (fly.video) report += `\n   Video: ${fly.video}`;
    report += '\n\n';
  });
function sendLostAppReport() {
  if (flies.length === 0) {
    statusText.html('ℹ️ Add at least one entry before emailing a report.');
    return;
  }

  const totalMinutes = getTotalStoredMinutes();
  let report = `Stored time: ${totalMinutes} / ${MAX_MINUTES} minutes\n\nEntries:\n`;
  flies.forEach((fly, idx) => {
    report += `${idx + 1}. ${fly.desc}`;
    if (fly.minutes) report += ` (${fly.minutes} min)`;
    if (fly.image) report += `\n   Image: ${fly.image}`;
    if (fly.video) report += `\n   Video: ${fly.video}`;
    report += '\n\n';
  });

  const subject = encodeURIComponent('LostApp Vault Report');
  const body = encodeURIComponent(report);
  const mailtoLink = `mailto:${REPORT_EMAIL}?subject=${subject}&body=${body}`;

  window.open(mailtoLink, '_blank');
  statusText.html('📧 Report draft opened—review and send from your email client.');
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

conditional Entry existence check if (flies.length === 0) {

Prevents generating an empty report; exits early if there are no entries

for-loop Report text generation flies.forEach((fly, idx) => {

Loops through all entries, formatting each one as a numbered item with description, time, and media URLs

calculation URL encoding const subject = encodeURIComponent('LostApp Vault Report');

Encodes special characters in the subject and body so they can be safely passed in a mailto: URL

function_call Email client launch window.open(mailtoLink, '_blank');

Opens the default email client with the pre-filled report

if (flies.length === 0) {
Checks if the flies array is empty (no entries). If so, prevents report generation.
statusText.html('ℹ️ Add at least one entry before emailing a report.');
Shows a helpful message
return;
Exits the function early so the rest doesn't run
const totalMinutes = getTotalStoredMinutes();
Gets the current time total
let report = `Stored time: ${totalMinutes} / ${MAX_MINUTES} minutes\n\nEntries:\n`;
Initializes a report string with a header showing time usage. \n represents line breaks.
flies.forEach((fly, idx) => {
Loops through each fly entry, with idx as the index (0-based)
report += `${idx + 1}. ${fly.desc}`;
Appends a numbered item (1., 2., etc.) with the entry description
if (fly.minutes) report += ` (${fly.minutes} min)`;
If the entry has minutes, appends the time allocation to the same line
if (fly.image) report += `\n Image: ${fly.image}`;
If there's an image URL, adds a new indented line with the image URL
if (fly.video) report += `\n Video: ${fly.video}`;
If there's a video URL, adds a new indented line with the video URL
report += '\n\n';
Adds two line breaks after each entry for readability
const subject = encodeURIComponent('LostApp Vault Report');
URL-encodes the subject string (converts spaces and special chars to safe URL format)
const body = encodeURIComponent(report);
URL-encodes the entire report text
const mailtoLink = `mailto:${REPORT_EMAIL}?subject=${subject}&body=${body}`;
Creates a mailto: link with the report email address, subject, and body as URL parameters
window.open(mailtoLink, '_blank');
Opens the link in a new tab, which triggers the default email client with pre-filled subject and body
statusText.html('📧 Report draft opened—review and send from your email client.');
Shows a confirmation message

windowResized()

windowResized() is a p5.js built-in function that runs whenever the browser window is resized. Without it, the canvas would stay at its original size and not fill the screen. resizeCanvas() updates the canvas to the new window size.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the current browser window dimensions, keeping the background animation responsive

📦 Key Variables

LOGIN_NAME string

Hardcoded username required to unlock the vault

const LOGIN_NAME = 'ddos1337';
PASSWORD string

Hardcoded password required to unlock the vault

const PASSWORD = '123456789';
MAX_MINUTES number

Total time budget (in minutes) allowed across all stored entries

const MAX_MINUTES = 180;
REPORT_EMAIL string

Email address where reports are sent via mailto: link

const REPORT_EMAIL = 'chrisintorre2@gmail.com';
vaultUnlocked boolean

Global flag tracking whether the user has authenticated; controls which UI elements are visible

let vaultUnlocked = false;
bgDots array

Array of 200 particle objects, each with x, y, size, and speed properties for the animated background

let bgDots = [];
flies array

Array of stored entry objects, each containing desc (description), minutes (time), image (URL), and video (URL)

let flies = [];
uiWrapper HTML element (p5.Renderer)

Container div holding all UI elements (forms, buttons, list); styled with CSS class 'ui-container'

let uiWrapper;
userInput HTML input element (p5.Renderer)

Text input field for the login username

let userInput;
passInput HTML input element (p5.Renderer)

Password input field for authentication

let passInput;
passButton HTML button element (p5.Renderer)

Button that toggles between 'Unlock LostApp' and 'Lock LostApp' based on vaultUnlocked state

let passButton;
statusText HTML element (p5.Renderer)

Paragraph element displaying status messages (login success/failure, validation warnings, etc.)

let statusText;
flyInput HTML input element (p5.Renderer)

Text input for entry descriptions; hidden until vault is unlocked

let flyInput;
timeInput HTML input element (p5.Renderer)

Number input for time allocation (0–180 minutes); hidden until unlocked

let timeInput;
imageInput HTML input element (p5.Renderer)

Text input for optional image URLs; hidden until unlocked

let imageInput;
videoInput HTML input element (p5.Renderer)

Text input for optional video URLs; hidden until unlocked

let videoInput;
addFlyButton HTML button element (p5.Renderer)

Button that calls addFly() to save new entries; hidden until unlocked

let addFlyButton;
flyListDiv HTML element (p5.Renderer)

Container div where renderFlyList() displays all saved entries; hidden until unlocked

let flyListDiv;
timeStatus HTML element (p5.Renderer)

Paragraph displaying current time usage (e.g., 'Stored time: 120 / 180 min'); hidden until unlocked

let timeStatus;
sendEmailButton HTML button element (p5.Renderer)

Button that calls sendLostAppReport() to open email draft; hidden until unlocked

let sendEmailButton;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

SECURITY LOGIN_NAME and PASSWORD constants

Credentials are hardcoded in the client-side code and visible to anyone inspecting the source. This app is for personal use only and is completely insecure for any real data.

💡 For a real app, use a backend server to authenticate users. For this personal project, you could add a pop-up warning on load: 'This app stores data in browser memory only—refresh the page to clear all entries.'

FEATURE flies array and storage

All entries are stored only in memory (the flies array). Refreshing the page erases everything.

💡 Use localStorage or sessionStorage to persist entries across page reloads. Add save/load logic: flies = JSON.parse(localStorage.getItem('flies')) in setup(), and localStorage.setItem('flies', JSON.stringify(flies)) whenever an entry is added.

BUG addFly() time validation

If the user enters a non-numeric value (like 'abc') in the timeInput, parseInt returns NaN, which is then silently converted to 0. No error message is shown.

💡 Add validation feedback: if (isNaN(timeValue) && timeInput.value().trim().length > 0) { statusText.html('⚠️ Time must be a number.'); return; }

PERFORMANCE renderFlyList()

Every time addFly() is called, renderFlyList() clears the entire list and rebuilds it. With 100+ entries, this could be slow.

💡 Instead of clearing and rebuilding, append only the new entry: create the entry DOM once and push it to flyListDiv, then call updateTimeStatus().

STYLE sendLostAppReport() email formatting

The email body uses raw text with line breaks (\n) but no structured formatting. Long entries may be hard to read in some email clients.

💡 Consider generating an HTML email body instead of plain text, or add visual separators like '---' between entries for clarity.

FEATURE flyListDiv display

There is no way to delete entries once saved. The list can only grow until you lock and unlock the vault (which doesn't clear it anyway).

💡 Add a delete button to each entry in renderFlyList(). Create a deleteEntry(idx) function that removes the entry from the flies array and calls renderFlyList() to update the display.

🔄 Code Flow

Code flow showing setup, draw, setupui, handleunlock, updateui, lockvault, addfly, renderflylist, getstorredminutes, updatetimestatus, sendlostapreport, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> clear_background[clear_background] draw --> populate_dots[populate_dots] draw --> dot_loop[dot_loop] dot_loop --> dot_wrap[dot_wrap] dot_wrap --> dot_loop draw --> title_text[title_text] click setup href "#fn-setup" click draw href "#fn-draw" click clear_background href "#sub-clear_background" click populate_dots href "#sub-populate_dots" click dot_loop href "#sub-dot_loop" click dot_wrap href "#sub-dot_wrap" click title_text href "#sub-title_text" setup --> setup_ui_call[setup_ui_call] setup_ui_call --> wrapper_create[wrapper_create] wrapper_create --> title_create[title_create] title_create --> status_create[status_create] status_create --> login_inputs[login_inputs] login_inputs --> unlock_button[unlock_button] unlock_button --> data_inputs[data_inputs] data_inputs --> add_button[add_button] add_button --> time_display[time_display] time_display --> email_button[email_button] email_button --> list_div[list_div] click setup_ui_call href "#sub-setup_ui_call" click wrapper_create href "#sub-wrapper_create" click title_create href "#sub-title_create" click status_create href "#sub-status_create" click login_inputs href "#sub-login_inputs" click unlock_button href "#sub-unlock_button" click data_inputs href "#sub-data_inputs" click add_button href "#sub-add_button" click time_display href "#sub-time_display" click email_button href "#sub-email_button" click list_div href "#sub-list_div" unlock_button --> handleunlock[handleunlock] handleunlock --> read_inputs[read_inputs] read_inputs --> auth_check[auth_check] auth_check --> unlock_state[unlock_state] unlock_state --> updateui[updateui] unlock_state --> clear_fields[clear_fields] lock_state --> lockvault[lockvault] lockvault --> updateui click handleunlock href "#fn-handleunlock" click read_inputs href "#sub-read_inputs" click auth_check href "#sub-auth_check" click unlock_state href "#sub-unlock_state" click lock_state href "#sub-lock_state" click clear_fields href "#sub-clear_fields" add_button --> addfly[addfly] addfly --> input_read[input_read] input_read --> time_parse[time_parse] time_parse --> time_validation[time_validation] time_validation --> empty_check[empty_check] empty_check --> budget_check[budget_check] budget_check --> push_entry[push_entry] push_entry --> clear_inputs[clear_inputs] clear_inputs --> renderflylist[renderflylist] renderflylist --> clear_list[clear_list] clear_list --> loop_entries[loop_entries] loop_entries --> entry_container[entry_container] entry_container --> entry_label[entry_label] entry_label --> entry_info[entry_info] entry_info --> image_render[image_render] entry_info --> video_render[video_render] click addfly href "#fn-addfly" click input_read href "#sub-input_read" click time_parse href "#sub-time_parse" click time_validation href "#sub-time_validation" click empty_check href "#sub-empty_check" click budget_check href "#sub-budget_check" click push_entry href "#sub-push_entry" click clear_inputs href "#sub-clear_inputs" click renderflylist href "#fn-renderflylist" click clear_list href "#sub-clear_list" click loop_entries href "#sub-loop_entries" click entry_container href "#sub-entry_container" click entry_label href "#sub-entry_label" click entry_info href "#sub-entry_info" click image_render href "#sub-image_render" click video_render href "#sub-video_render" renderflylist --> updatetimestatus[updatetimestatus] updatetimestatus --> getstorredminutes[getstorredminutes] getstorredminutes --> reduce_op[reduce_op] reduce_op --> empty_check[empty_check] empty_check --> sendlostapreport[sendlostapreport] sendlostapreport --> report_build[report_build] report_build --> uri_encoding[uri_encoding] uri_encoding --> mailto_open[mailto_open] click updatetimestatus href "#fn-updatetimestatus" click getstorredminutes href "#fn-getstorredminutes" click reduce_op href "#sub-reduce_op" click empty_check href "#sub-empty_check" click sendlostapreport href "#fn-sendlostapreport" click report_build href "#sub-report_build" click uri_encoding href "#sub-uri_encoding" click mailto_open href "#sub-mailto_open" windowresized[windowresized] --> resizeCanvas[resizeCanvas] click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What visual elements does the 'lostapp for me only' sketch create?

The sketch features a dynamic background of floating colored dots that move upwards and reset their position, creating a visually engaging and serene atmosphere.

How can users interact with the 'lostapp for me only' sketch?

Users can unlock the secure vault by entering a login name and password, and then they can add entries describing flies along with optional media.

What creative coding techniques are demonstrated in the 'lostapp for me only' sketch?

The sketch showcases techniques such as particle systems for the moving dots, user input handling for authentication, and dynamic user interface elements.

Preview

lostapp for me only - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of lostapp for me only - Code flow showing setup, draw, setupui, handleunlock, updateui, lockvault, addfly, renderflylist, getstorredminutes, updatetimestatus, sendlostapreport, windowresized
Code Flow Diagram