lookpassword

LostApp is a cyberpunk-themed secure vault application that requires login credentials to unlock. Once authenticated, users can store encrypted entries called 'flies' with optional media attachments, manage a 180-minute storage budget, upload custom background music, and email reports—all while animated floating dots create a neon aesthetic in the background.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the login credentials — Modify LOGIN_NAME and PASSWORD to your own values, then try logging in with the new credentials to see authentication work with custom data.
  2. Increase the storage time budget — Change MAX_MINUTES to 300 or more so users can store much more data. Watch the feedback in addFly() allow higher totals before rejecting entries.
  3. Make the floating dots faster and larger — Increase the dot speed range and size in the setup loop, creating a denser, more intense cyberpunk background.
  4. Change the background color from red to blue — Modify the background() call in draw() to create a completely different mood—from cyberpunk red to cool blue.
  5. Make the floating dots invisible — Set the dot fill color's alpha (last number) to 0 so they don't render—you'll see the red background alone without the neon dots.
  6. Add a fourth sample fly — In seedSampleFlies(), add another object to the flies array with your own description and media URLs to expand the demo data.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates a fully-functional secure application interface called LostApp. It requires users to enter correct login credentials before unlocking access to a vault where they can store entries called 'flies'—text descriptions paired with optional images, videos, and audio files. The app enforces a strict 180-minute storage time budget, allows users to upload custom background music, and can generate email reports of all stored entries. Behind all this sits an animated cyberpunk backdrop of floating dots that drift upward, creating an immersive visual atmosphere.

The code is organized into three main sections: the canvas animation system (setup and draw handle the floating dots), the user interface layer (setupUI creates all input fields and buttons), and the vault logic (functions like handleUnlock, addFly, and renderFlyList manage authentication, storage, and display). By studying this sketch you will learn how to build a multi-screen application in p5.js using the p5.dom library, implement state-based UI visibility, validate user input, manage complex data structures as arrays of objects, and create interactive experiences that go far beyond simple drawing—including real file uploads and email integration.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, populates an array of 200 floating dot objects with random positions and speeds, loads a default ambient background track (initially hidden), and calls setupUI() to render the login interface.
  2. The draw loop runs 60 times per second: it fills the background with dark red, then iterates through every floating dot to draw it as a circle and animate it upward. When a dot drifts off the top of the canvas, it wraps to the bottom with a random new x position.
  3. The user enters a login name and password, then clicks 'Unlock LostApp'. The handleUnlock() function compares their input against hardcoded constants LOGIN_NAME and PASSWORD.
  4. If credentials match, vaultUnlocked becomes true, sample flies are loaded, background music starts playing, and updateUI() reveals all the vault controls (input fields, buttons, the fly list display).
  5. Users can now add new 'flies' by filling in a description, storage time (0–180 minutes), and optional media URLs or file uploads. When they click 'Add to LostApp', the addFly() function validates that the total stored minutes won't exceed the MAX_MINUTES budget, then adds the new fly object to the flies array and re-renders the list.
  6. The renderFlyList() function loops through every fly, creating a styled div for each entry that displays its description, remaining storage time, and any attached media (images, videos, audio players). The timeStatus text updates to show current vs. maximum stored minutes.
  7. At any time, users can click 'Email LostApp Report' to open their email client with a pre-filled message containing all vault entries and their metadata. They can also upload custom background music using the dedicated file input, which replaces the default ambient track.

🎓 Concepts You'll Learn

State management and conditional visibilityForm input validation and data collectionArrays of objects for complex data storageFile uploads and media handlingp5.dom for UI element creationAnimation loops and particle systemsString formatting and email integrationResponsive canvas resizing

📝 Code Breakdown

setup()

setup() runs once when the sketch first loads. Use it to initialize your canvas, load assets (like audio), populate data structures, and build your UI. The function is called by p5.js automatically before draw() starts running.

🔬 This loop creates all the floating dots. What happens if you change 'random(2, 5)' to 'random(1, 20)' so dots have wildly different sizes? Or change 'random(0.2, 0.6)' to '0.3' so all dots move at exactly the same speed?

  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)
    });
  }
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)
    });
  }

  backgroundMusic = createAudio('https://cdn.pixabay.com/download/audio/2022/03/15/audio_9d00c6f89c.mp3?filename=ambient-110057.mp3');
  backgroundMusic.volume(0.35);
  backgroundMusic.loop();
  backgroundMusic.stop();
  backgroundMusic.hideControls();

  setupUI();
}
Line-by-line explanation (13 lines)

🔧 Subcomponents:

function-call Canvas initialization createCanvas(windowWidth, windowHeight);

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

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

Populates the bgDots array with 200 objects, each storing x/y position, size, and upward speed

function-call Background music initialization backgroundMusic = createAudio('https://cdn.pixabay.com/download/audio/2022/03/15/audio_9d00c6f89c.mp3?filename=ambient-110057.mp3');

Loads the default ambient track from a CDN and prepares it for playback

createCanvas(windowWidth, windowHeight);
Creates a canvas that matches the full browser window dimensions, making the app responsive to window size
for (let i = 0; i < 200; i++) {
Loop that runs 200 times, creating 200 separate dot objects to store in the bgDots array
bgDots.push({
Adds a new object to the bgDots array with four properties: position (x, y), visual size, and animation speed
x: random(width),
Sets each dot's starting x position to a random value between 0 and canvas width
y: random(height),
Sets each dot's starting y position to a random value between 0 and canvas height
size: random(2, 5),
Assigns each dot a random size between 2 and 5 pixels, creating visual variety
speed: random(0.2, 0.6)
Assigns each dot a random upward speed between 0.2 and 0.6 pixels per frame
backgroundMusic = createAudio('https://cdn.pixabay.com/download/audio/2022/03/15/audio_9d00c6f89c.mp3?filename=ambient-110057.mp3');
Uses p5.sound to load an ambient background track from an external URL
backgroundMusic.volume(0.35);
Sets the track volume to 35% so it doesn't overwhelm the user
backgroundMusic.loop();
Configures the audio to loop infinitely when playing
backgroundMusic.stop();
Immediately stops the track so it doesn't play until the user unlocks the vault
backgroundMusic.hideControls();
Hides the browser's default audio player controls to keep the UI clean
setupUI();
Calls setupUI() to create all the login interface elements and input fields

draw()

draw() runs continuously at 60 frames per second. Every call, it clears the background and redraws all dots at their new positions. The -= operator moves dots upward; when they exit the top, they wrap to the bottom with fresh x positions. This creates an endless upward flow that defines LostApp's cyberpunk atmosphere.

🔬 This loop is the heartbeat of the animation. What happens if you change 'dot.y -= dot.speed' to 'dot.y += dot.speed' so the dots fall downward instead of rising? Or change 'dot.x = random(width)' to 'dot.x = width / 2' so all wrapping dots return to the center horizontally?

  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(140, 0, 0);

  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);
    }
  }
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

function-call Background color background(140, 0, 0);

Clears the canvas each frame and fills it with a dark red color (RGB: 140, 0, 0)

for-loop Animate and draw floating dots for (const dot of bgDots) {

Iterates through every dot object, draws it, moves it upward, and wraps it when it leaves the top of the canvas

conditional Dot boundary wrap if (dot.y < -10) {

Checks if a dot has drifted off the top of the canvas and, if so, resets it to the bottom with a new random x position

background(140, 0, 0);
Fills the entire canvas with dark red (RGB values: 140 red, 0 green, 0 blue). This happens every frame, clearing away the previous frame's dots and creating the appearance of smooth animation.
noStroke();
Turns off outlines for the shapes that follow, so the dots will be solid circles without borders
fill(0, 255, 159, 80);
Sets the fill color to neon cyan (RGB: 0, 255, 159) with 80/255 transparency. The low alpha makes the dots semi-transparent so they blend softly into the background.
for (const dot of bgDots) {
Loops through each dot object stored in the bgDots array, giving us access to its x, y, size, and speed properties
circle(dot.x, dot.y, dot.size);
Draws a circle at the dot's current x and y position with its unique size as the diameter
dot.y -= dot.speed;
Moves the dot upward each frame by subtracting its speed value from its y position. Lower y values move up on screen.
if (dot.y < -10) {
Checks whether the dot has drifted more than 10 pixels above the top of the canvas (y < -10)
dot.y = height + 10;
Resets the dot's y position to just below the bottom of the canvas so it enters from the bottom and starts moving up again
dot.x = random(width);
Assigns the dot a new random x position, so dots don't return to the same horizontal lane when they wrap

setupUI()

setupUI() is called once at the end of setup() to build the entire interface using p5.dom functions (createInput, createButton, createDiv, etc.). Every input field and button created here is stored in a global variable so other functions can access it later. The .hide() calls are crucial: they create the UI elements but make them invisible until the user logs in, at which point updateUI() calls .show() to reveal them.

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();

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

  uploadInput = createFileInput(handleMediaUpload, false);
  uploadInput.attribute('accept', 'image/*,video/*,audio/*');
  uploadInput.parent(uiWrapper);
  uploadInput.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();

  bgMusicStatus = createP('Default ambient track ready.');
  bgMusicStatus.parent(uiWrapper);
  bgMusicStatus.hide();

  bgMusicUploadInput = createFileInput(handleBackgroundUpload, false);
  bgMusicUploadInput.attribute('accept', 'audio/*');
  bgMusicUploadInput.parent(uiWrapper);
  bgMusicUploadInput.hide();

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

🔧 Subcomponents:

function-call Main UI wrapper uiWrapper = createDiv().id('ui-container');

Creates the main container div that will hold all UI elements and applies the 'ui-container' CSS class for styling

sequential-elements Login form elements userInput = createInput('', 'text'); passInput = createInput('', 'password'); passButton = createButton('Unlock LostApp');

Creates username input, password input, and login button, all attached to the wrapper and configured with placeholder text and click handlers

sequential-elements Vault controls (initially hidden) flyInput.hide(); timeInput.hide(); imageInput.hide(); videoInput.hide(); audioInput.hide(); uploadInput.hide();

Creates input fields and buttons for adding new flies, but hides them until the user successfully logs in

uiWrapper = createDiv().id('ui-container');
Creates a div container and assigns it the ID 'ui-container' so CSS can style it with the dark background, cyan border, and glow effect
const title = createElement('h2', 'LostApp Access');
Creates an h2 heading element with the text 'LostApp Access' to label the login section
title.parent(uiWrapper);
Attaches the heading to the uiWrapper div, making it a child element that inherits styles and appears in the correct location
statusText = createP('Enter your login name and password to unlock LostApp.');
Creates a paragraph element that displays status messages (login errors, success notifications, warnings) to the user
userInput = createInput('', 'text');
Creates an empty text input field for the user to type their login name
userInput.attribute('placeholder', 'Login name');
Sets placeholder text that appears inside the input field when it's empty, guiding the user what to enter
passInput = createInput('', 'password');
Creates a password input field that hides typed characters behind dots for security
passButton = createButton('Unlock LostApp');
Creates a button labeled 'Unlock LostApp' that the user clicks to attempt login
passButton.mousePressed(handleUnlock);
Connects the button so that clicking it calls the handleUnlock() function, which checks credentials
flyInput.hide();
Initially hides the fly description input field so it only becomes visible after successful login
timeInput.attribute('max', MAX_MINUTES);
Sets the maximum allowed value for the time input to 180 (the MAX_MINUTES constant), preventing users from entering higher values
uploadInput = createFileInput(handleMediaUpload, false);
Creates a file picker that, when a user selects a file, calls handleMediaUpload() with that file's data
uploadInput.attribute('accept', 'image/*,video/*,audio/*');
Restricts the file picker to only show and accept image, video, and audio files, filtering out incompatible types
bgMusicUploadInput = createFileInput(handleBackgroundUpload, false);
Creates a separate file picker specifically for background music that calls handleBackgroundUpload() when a user selects an audio file
flyListDiv = createDiv();
Creates an empty div that will hold all rendered fly entries when the user is logged in

handleUnlock()

handleUnlock() is called when the user clicks the 'Unlock LostApp' button. It validates credentials, manages the vaultUnlocked state, and orchestrates the transition from login screen to vault interface. The if (!seeded) check is a guard that prevents losing user data on re-login.

🔬 This is the core unlock logic. What happens if you change 'vaultUnlocked = true' to 'vaultUnlocked = false'? The credentials would match but the vault would stay locked! What if you remove the 'if (!seeded)' check and just always call seedSampleFlies()—would the sample data reload on every login, losing any entries the user added?

  if (attemptUser === LOGIN_NAME && attemptPass === PASSWORD) {
    vaultUnlocked = true;
    if (!seeded) {
      seedSampleFlies();
      seeded = true;
    }
    startBackgroundMusic();
    updateUI();
function handleUnlock() {
  const attemptUser = userInput.value().trim();
  const attemptPass = passInput.value().trim();

  if (attemptUser === LOGIN_NAME && attemptPass === PASSWORD) {
    vaultUnlocked = true;
    if (!seeded) {
      seedSampleFlies();
      seeded = true;
    }
    startBackgroundMusic();
    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 (15 lines)

🔧 Subcomponents:

variable-assignment Extract user inputs const attemptUser = userInput.value().trim(); const attemptPass = passInput.value().trim();

Reads the user's typed input, removes whitespace, and stores the clean values for comparison

conditional Login validation if (attemptUser === LOGIN_NAME && attemptPass === PASSWORD) {

Checks if both the username AND password match the hardcoded constants; only allows login if both are correct

conditional-block Successful login branch vaultUnlocked = true; seedSampleFlies(); startBackgroundMusic(); updateUI();

If credentials match, unlocks the vault, loads sample data, starts music, and reveals all vault controls

const attemptUser = userInput.value().trim();
Gets the text the user typed into the username field, then .trim() removes any accidental spaces before or after
const attemptPass = passInput.value().trim();
Gets the password text and trims whitespace the same way
if (attemptUser === LOGIN_NAME && attemptPass === PASSWORD) {
Uses === to compare the user's inputs against the hardcoded constants. Both must match (&&) for login to succeed.
vaultUnlocked = true;
Sets the global vaultUnlocked flag to true, signaling that the user is now authenticated
if (!seeded) {
Checks if sample flies have already been loaded (seeded). The ! means 'if NOT seeded' so we only load them once per session
seedSampleFlies();
Populates the flies array with three example entries so the user has something to see immediately after login
seeded = true;
Sets seeded to true so if the user logs out and back in, we don't reload the sample data
startBackgroundMusic();
Calls startBackgroundMusic() to begin playing the ambient track
updateUI();
Calls updateUI() to show all the hidden vault controls (input fields, buttons, fly list)
statusText.html('✅ LostApp unlocked! Add new entries below.');
Updates the status message to confirm successful login and guide the user toward adding entries
} else {
If the credentials did NOT match, this else block runs
vaultUnlocked = false;
Keeps vaultUnlocked as false, preventing access to vault functions
statusText.html('❌ Invalid login name or password. LostApp stays sealed.');
Shows an error message so the user knows their credentials were wrong
userInput.value('');
Clears the username field by setting its value to an empty string, removing the user's input
passInput.value('');
Clears the password field so the user doesn't see their failed attempt

addFly()

addFly() is called when the user clicks 'Add to LostApp'. It validates inputs (non-empty and within budget), constructs a fly object, adds it to the flies array, clears input fields, and triggers renderFlyList() to update the display. The function demonstrates input validation patterns, data structure creation, and user feedback—three essential skills for any interactive app.

🔬 This validation block prevents saving empty entries using && (AND). What happens if you change && to || (OR) so the check triggers if ANY field is empty instead of ALL? Users couldn't save if they forgot even one field. Can you predict the difference in user experience?

  if (
    description.length === 0 &&
    imgURL.length === 0 &&
    vidURL.length === 0 &&
    audURL.length === 0
  ) {
    statusText.html('⚠️ Add text or media before saving.');
    return;
  }

🔬 This is the storage budget enforcement. What happens if you change '> MAX_MINUTES' to '=== MAX_MINUTES' so users can ONLY add entries if they exactly fill the budget? Or what if you remove this check entirely and users can store unlimited time?

  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;
  }
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();
  const audURL = audioInput.value().trim();

  if (
    description.length === 0 &&
    imgURL.length === 0 &&
    vidURL.length === 0 &&
    audURL.length === 0
  ) {
    statusText.html('⚠️ Add text or media 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,
    audio: audURL
  });

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

🔧 Subcomponents:

variable-assignment Extract and sanitize inputs const description = flyInput.value().trim(); const minutes = isNaN(timeValue) ? 0 : constrain(timeValue, 0, MAX_MINUTES);

Collects all user inputs, removes whitespace, parses numbers, and ensures time values stay within bounds

conditional Validate non-empty entry if (description.length === 0 && imgURL.length === 0 && vidURL.length === 0 && audURL.length === 0) {

Prevents saving completely empty entries by checking that at least one field (text or media) has content

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

Ensures adding the new entry won't exceed the 180-minute total storage limit

object-creation Create and push fly object flies.push({ desc: description || '(silent fly thoughts)', minutes, image: imgURL, video: vidURL, audio: audURL });

Constructs a new fly object with all entered data and adds it to the flies array

const description = flyInput.value().trim();
Gets the text the user typed in the fly description field and removes surrounding whitespace
const timeValue = parseInt(timeInput.value(), 10);
Reads the time input and converts it to an integer using base-10 math. If the input isn't a number, this returns NaN (Not a Number)
const minutes = isNaN(timeValue) ? 0 : constrain(timeValue, 0, MAX_MINUTES);
Ternary operator: if timeValue is NaN, use 0. Otherwise, use constrain() to force the value between 0 and MAX_MINUTES, clamping invalid inputs
const imgURL = imageInput.value().trim();
Extracts the image URL and trims whitespace
const vidURL = videoInput.value().trim();
Extracts the video URL and trims whitespace
const audURL = audioInput.value().trim();
Extracts the audio URL and trims whitespace
if (description.length === 0 && imgURL.length === 0 && vidURL.length === 0 && audURL.length === 0) {
Checks if all four fields are empty. The && means ALL must be empty to trigger this error. If any field has content, this condition is false and execution continues.
statusText.html('⚠️ Add text or media before saving.');
Shows a warning message instructing the user to add at least something before saving
return;
Exits the function early, preventing any fly from being saved
const currentMinutes = getTotalStoredMinutes();
Calls getTotalStoredMinutes() to sum up all the minutes in existing flies
if (currentMinutes + minutes > MAX_MINUTES) {
Checks if adding this new fly's minutes would exceed the 180-minute limit
statusText.html(`⚠️ Not enough storage left. You can only store ${MAX_MINUTES - currentMinutes} more minute(s).`);
Shows an error message that calculates how many minutes remain and displays it to the user using template literal syntax
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
flies.push({ desc: ..., minutes, image: imgURL, video: vidURL, audio: audURL });
The new fly object stores all the collected data in a structured format
flyInput.value(''); timeInput.value(''); imageInput.value(''); videoInput.value(''); audioInput.value('');
Clears all input fields by setting their values to empty strings, preparing for the next entry
statusText.html('✅ Entry stored in LostApp!');
Shows a success message confirming the entry was saved
renderFlyList();
Calls renderFlyList() to redraw the display of all flies, showing the newly added entry

renderFlyList()

renderFlyList() is the display engine of LostApp. It runs after every change to the flies array (adding an entry, loading samples, etc.). Using forEach, it loops through all flies and constructs HTML elements dynamically. The conditional checks (if fly.image, if fly.video, if fly.audio) only render media if URLs exist, preventing broken elements. This is the core pattern for turning data into visible UI in p5.js.

🔬 These if-statements conditionally display media IF URLs exist. What happens if you comment out the 'if' and let images/videos display even when their URLs are empty? You'd get broken image icons. What if you change 'preload' from 'metadata' to 'auto'—videos would start downloading even if the user never clicks play, which wastes bandwidth but loads faster when clicked.

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

    if (fly.video) {
      const videoEl = createElement('video');
      videoEl.attribute('src', fly.video);
      videoEl.attribute('controls', true);
      videoEl.attribute('preload', 'metadata');
      videoEl.parent(entry);
    }
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` : '';
    createP(`${fly.desc}${minutesText}`).parent(entry);

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

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

    if (fly.audio) {
      const audioEl = createElement('audio');
      audioEl.attribute('src', fly.audio);
      audioEl.attribute('controls', true);
      audioEl.parent(entry);
    }
  });

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

🔧 Subcomponents:

function-call Clear previous list flyListDiv.html('');

Empties the fly list container so old entries are removed before displaying the updated list

for-loop Loop through all flies flies.forEach((fly, idx) => {

Iterates over every fly object, creating a styled div for each one with its data displayed

conditional Conditional media rendering if (fly.image) { ... } if (fly.video) { ... } if (fly.audio) { ... }

Only displays image, video, or audio elements if the fly object contains URLs for those media types

flyListDiv.html('');
Clears all HTML content inside flyListDiv, removing previously rendered entries so we start fresh
flies.forEach((fly, idx) => {
Iterates through the flies array. For each fly object, idx is its position (0, 1, 2, ...). The => syntax defines an arrow function.
const entry = createDiv();
Creates a new div element that will hold all the content for this single fly
entry.addClass('fly-entry');
Adds the CSS class 'fly-entry' to the div so it inherits styling (border, padding, background color defined in style.css)
entry.parent(flyListDiv);
Makes this entry div a child of flyListDiv, so it appears inside the list container
const label = createElement('strong', `LostApp Entry #${idx + 1}`);
Creates a strong (bold) heading that numbers each entry starting from 1 (not 0). Template literal with ${idx + 1} inserts the number.
label.parent(entry);
Adds the label as a child of 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 showing the minutes. Otherwise, create an empty string. This avoids showing '0 min stored'.
createP(`${fly.desc}${minutesText}`).parent(entry);
Creates a paragraph combining the fly's description and the minutes text, then adds it to the entry
if (fly.image) {
Checks if the fly object has an image URL
createImg(fly.image, `Entry ${idx + 1} image`).attribute('loading', 'lazy').parent(entry);
If yes, creates an img element with the URL, sets lazy loading for performance, and adds it to the entry
if (fly.video) {
Checks if the fly has a video URL
const videoEl = createElement('video');
Creates an HTML5 video element
videoEl.attribute('src', fly.video);
Sets the video's source URL
videoEl.attribute('controls', true);
Enables the browser's default video controls (play, pause, volume, progress bar)
videoEl.attribute('preload', 'metadata');
Tells the browser to load only the video's metadata (duration, etc.) without downloading the full file until the user presses play
videoEl.parent(entry);
Adds the video element to the entry
if (fly.audio) {
Checks if the fly has an audio URL
const audioEl = createElement('audio'); audioEl.attribute('src', fly.audio); audioEl.attribute('controls', true); audioEl.parent(entry);
Creates an audio player with controls and adds it to the entry, similar to the video logic
updateTimeStatus();
After rendering all flies, calls updateTimeStatus() to recalculate and display the total stored minutes

updateUI()

updateUI() is the state synchronizer. Whenever vaultUnlocked changes (after login or logout), this function is called to update every element's visibility and button behavior to match the current state. It demonstrates the power of keeping UI tied to a single state variable—change vaultUnlocked, call updateUI(), and the entire interface transforms.

🔬 This if-statement drives all UI visibility. What happens if you change 'if (vaultUnlocked)' to 'if (!vaultUnlocked)' (adding !)? The show/hide logic would flip—locked controls would appear and unlocked ones would hide. Can you see how this same code structure works for both states?

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

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

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

    flyInput.hide();
    timeInput.hide();
    imageInput.hide();
    videoInput.hide();
    audioInput.hide();
    uploadInput.hide();
    addFlyButton.hide();
    flyListDiv.hide();
    timeStatus.hide();
    sendEmailButton.hide();
    bgMusicUploadInput.hide();
    bgMusicStatus.hide();
  }
}
Line-by-line explanation (9 lines)

🔧 Subcomponents:

conditional-block If vault is unlocked if (vaultUnlocked) { ... all elements shown ... }

Shows all vault controls and changes button label/behavior to lock mode when the user is authenticated

conditional-block If vault is locked } else { ... all elements hidden ... }

Hides all vault controls and resets button to login mode when the user logs out

if (vaultUnlocked) {
Checks the vaultUnlocked boolean. If true, the vault is open and we should show controls.
passButton.html('Lock LostApp');
Changes the button text from 'Unlock LostApp' to 'Lock LostApp' so the user knows they can now logout
passButton.mousePressed(lockVault);
Rebinds the button's click handler to lockVault() instead of handleUnlock(), so clicking it now logs out
flyInput.show(); timeInput.show(); imageInput.show(); videoInput.show(); audioInput.show(); uploadInput.show(); addFlyButton.show(); flyListDiv.show(); timeStatus.show(); sendEmailButton.show(); bgMusicUploadInput.show(); bgMusicStatus.show();
Makes all the vault control elements visible by calling .show() on each one
renderFlyList();
Immediately draws the fly list (either with sample data or previously saved entries)
} else {
If vaultUnlocked is false, the vault is locked
passButton.html('Unlock LostApp');
Resets button text to 'Unlock LostApp'
passButton.mousePressed(handleUnlock);
Rebinds the button to handleUnlock() so it attempts login again
flyInput.hide(); timeInput.hide(); ...
Hides all vault controls by calling .hide() on each element, showing only the login form

getTotalStoredMinutes()

getTotalStoredMinutes() is a pure utility function that calculates the sum of all minutes across all flies using reduce(). It's called by addFly() to check the budget and by renderFlyList() to display the status. Using reduce() instead of a for loop shows functional programming patterns that make code concise and expressive.

🔬 This one-liner uses reduce(), a powerful array method. What if you replaced it with a simple for loop? How would you sum all minutes manually? Or what happens if you change '|| 0' to '|| 100'—if a fly has no minutes value, use 100 instead of 0. This would make empty entries take up lots of budget!

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

🔧 Subcomponents:

array-method Array reduce for summation return flies.reduce((sum, fly) => sum + (fly.minutes || 0), 0);

Uses the reduce() array method to loop through all flies and accumulate their minutes into a single total

return flies.reduce((sum, fly) => sum + (fly.minutes || 0), 0);
Reduces the flies array into a single number. Starting at 0, for each fly, add its minutes (or 0 if undefined) to the running sum. Returns the final total.

sendLostAppReport()

sendLostAppReport() demonstrates client-side email generation. It builds a formatted text report and opens the user's email client via a mailto: link. The encodeURIComponent() function is crucial—it escapes special characters so the report text is safely embedded in a URL. This pattern works for any data export scenario, not just email.

🔬 This loop builds the report text by concatenating strings. What happens if you remove all the 'if' statements for media so only descriptions and minutes appear in the email? What if you add a timestamp or entry ID to each line? You could change the format to be more structured or less detailed depending on needs.

  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}`;
    if (fly.audio) report += `\n   Audio: ${fly.audio}`;
    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}`;
    if (fly.audio) report += `\n   Audio: ${fly.audio}`;
    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 (17 lines)

🔧 Subcomponents:

conditional Validate non-empty vault if (flies.length === 0) { ... return; }

Prevents emailing an empty report by checking if the flies array contains any entries

for-loop Build report text flies.forEach((fly, idx) => { report += ... });

Loops through all flies and constructs a formatted text report with descriptions, times, and media URLs

variable-assignment Build mailto link const mailtoLink = `mailto:${REPORT_EMAIL}?subject=${subject}&body=${body}`;

Constructs a mailto: link that pre-fills the email client with the report text

if (flies.length === 0) {
Checks if the flies array is empty by testing its length property
statusText.html('ℹ️ Add at least one entry before emailing a report.');
If empty, shows an info message telling the user they need entries first
return;
Exits the function early, preventing the email from being sent
const totalMinutes = getTotalStoredMinutes();
Calls getTotalStoredMinutes() to calculate the total stored time for inclusion in the report
let report = `Stored time: ${totalMinutes} / ${MAX_MINUTES} minutes\n\nEntries:\n`;
Initializes the report string with a header showing total vs. maximum minutes. \n creates line breaks.
flies.forEach((fly, idx) => {
Loops through every fly, numbering it with idx
report += `${idx + 1}. ${fly.desc}`;
Appends the fly's number and description to the report
if (fly.minutes) report += ` (${fly.minutes} min)`;
If the fly has a minutes value, appends it in parentheses
if (fly.image) report += `\n Image: ${fly.image}`;
If the fly has an image URL, adds it on a new indented line
if (fly.video) report += `\n Video: ${fly.video}`;
If the fly has a video URL, adds it on a new indented line
if (fly.audio) report += `\n Audio: ${fly.audio}`;
If the fly has an audio URL, adds it on a new indented line
report += '\n\n';
Adds blank lines between entries for readability
const subject = encodeURIComponent('LostApp Vault Report');
Encodes the email subject using encodeURIComponent() to safely insert it into a URL (spaces become %20, etc.)
const body = encodeURIComponent(report);
Encodes the entire report text so it's safe to pass in a URL
const mailtoLink = `mailto:${REPORT_EMAIL}?subject=${subject}&body=${body}`;
Constructs a mailto: link with the email address, subject, and body as URL parameters
window.open(mailtoLink, '_blank');
Opens the default email client with the pre-filled email, or opens it in a new browser tab if no email client is available
statusText.html('📧 Report draft opened—review and send from your email client.');
Confirms to the user that the email draft is ready to send

windowResized()

windowResized() is a p5.js lifecycle function that fires whenever the window dimensions change. By calling resizeCanvas(), we ensure the animated background dots always fill the entire viewport, even on phones or resized browser windows. Without this function, the canvas would stay its original size even if the window grew or shrank.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
p5.js calls this function automatically whenever the browser window is resized. It resizes the canvas to match the new window dimensions, making the app responsive.

lockVault()

lockVault() is the logout function, called when the user clicks the 'Lock LostApp' button. It resets vaultUnlocked, stops the music, and triggers updateUI() to hide all controls. It mirrors handleUnlock() but in reverse, demonstrating symmetrical state transitions.

function lockVault() {
  vaultUnlocked = false;
  statusText.html('LostApp locked. Enter your credentials to unlock again.');
  if (backgroundMusic) backgroundMusic.stop();
  updateUI();
}
Line-by-line explanation (4 lines)
vaultUnlocked = false;
Sets the vaultUnlocked flag to false, locking the vault
statusText.html('LostApp locked. Enter your credentials to unlock again.');
Updates the status message to inform the user the vault is now locked
if (backgroundMusic) backgroundMusic.stop();
Stops the background music. The if-check ensures backgroundMusic exists before trying to stop it.
updateUI();
Calls updateUI() to hide all vault controls and restore the login form

startBackgroundMusic()

startBackgroundMusic() manages the audio lifecycle. The musicStarted flag prevents creating multiple loops if the user logs out and back in repeatedly. The dual-branch logic handles both first-time play and resumption.

function startBackgroundMusic() {
  if (backgroundMusic && !musicStarted) {
    backgroundMusic.loop();
    musicStarted = true;
  } else if (backgroundMusic && musicStarted) {
    backgroundMusic.play();
  }
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

conditional First-time play if (backgroundMusic && !musicStarted) { backgroundMusic.loop(); musicStarted = true; }

On first unlock, starts the music in loop mode and sets musicStarted flag

conditional Resume playback } else if (backgroundMusic && musicStarted) { backgroundMusic.play(); }

If music was already loaded, simply resume it

if (backgroundMusic && !musicStarted) {
Checks if backgroundMusic exists AND musicStarted is false (using ! to negate). Only runs on first unlock.
backgroundMusic.loop();
Starts playing the music in infinite loop mode
musicStarted = true;
Sets the musicStarted flag so this branch won't run again
} else if (backgroundMusic && musicStarted) {
Alternative: if backgroundMusic exists and musicStarted is already true
backgroundMusic.play();
Simply resumes playing (useful if user logs out and back in)

handleBackgroundUpload()

handleBackgroundUpload() is the callback for the file picker. When a user selects a file, p5.js passes it here. The function validates the file type, cleans up the old audio, loads the new file into a createAudio() element, and provides feedback. This demonstrates file upload handling and resource cleanup.

function handleBackgroundUpload(file) {
  if (!file) return;

  if (file.type.startsWith('audio')) {
    if (backgroundMusic) {
      backgroundMusic.stop();
      backgroundMusic.remove();
    }
    backgroundMusic = createAudio(file.data);
    backgroundMusic.volume(0.35);
    backgroundMusic.loop();
    backgroundMusic.hideControls();
    musicStarted = true;
    bgMusicStatus.html('🎧 Custom background track playing.');
  } else {
    bgMusicStatus.html('⚠️ Please select an audio file.');
  }
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

conditional Check file exists if (!file) return;

Exits early if no file was selected

conditional Verify audio format if (file.type.startsWith('audio')) { ... } else { ... }

Only processes if the selected file is an audio type; shows error otherwise

if (!file) return;
If no file was selected (user clicked cancel), exit the function early
if (file.type.startsWith('audio')) {
Checks if the file's type starts with 'audio' (e.g., 'audio/mp3', 'audio/wav')
if (backgroundMusic) {
If a music track was already loaded, prepare to replace it
backgroundMusic.stop(); backgroundMusic.remove();
Stops and removes the old audio element from memory
backgroundMusic = createAudio(file.data);
Creates a new audio element using the uploaded file's data
backgroundMusic.volume(0.35); backgroundMusic.loop(); backgroundMusic.hideControls();
Sets volume, enables looping, and hides player controls (same as the default track)
musicStarted = true;
Sets musicStarted flag so the music is marked as active
bgMusicStatus.html('🎧 Custom background track playing.');
Updates the status text to confirm the custom track is playing
} else {
If the file is not audio
bgMusicStatus.html('⚠️ Please select an audio file.');
Shows an error message directing the user to select audio only

handleMediaUpload()

handleMediaUpload() is called by the createFileInput('', false) in setupUI(). When the user picks a file, this function routes it to the correct input field (image, video, or audio) based on MIME type. It demonstrates multi-type file handling and provides immediate feedback via status messages.

function handleMediaUpload(file) {
  if (!file) return;

  if (file.type.startsWith('image')) {
    imageInput.value(file.data || '');
    statusText.html('🖼️ Image attached from upload.');
  } else if (file.type.startsWith('video')) {
    videoInput.value(file.data || '');
    statusText.html('🎬 Video attached from upload.');
  } else if (file.type.startsWith('audio')) {
    audioInput.value(file.data || '');
    statusText.html('🎵 Audio attached from upload.');
  } else {
    statusText.html('⚠️ Unsupported file type. Use image, video, or audio.');
  }
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

if-else-chain File type routing if (file.type.startsWith('image')) { ... } else if (file.type.startsWith('video')) { ... } else if (file.type.startsWith('audio')) { ... } else { ... }

Routes the uploaded file to the correct input field based on its type

if (!file) return;
Exits early if no file was selected
if (file.type.startsWith('image')) {
Checks if the file type is an image (e.g., 'image/jpeg', 'image/png')
imageInput.value(file.data || '');
Sets the imageInput field to the file's data URL (or empty string if unavailable)
statusText.html('🖼️ Image attached from upload.');
Confirms the image was attached with a status message
} else if (file.type.startsWith('video')) {
Checks if the file type is a video
videoInput.value(file.data || '');
Sets the videoInput field to the file's data URL
statusText.html('🎬 Video attached from upload.');
Confirms the video was attached
} else if (file.type.startsWith('audio')) {
Checks if the file type is audio
audioInput.value(file.data || '');
Sets the audioInput field to the file's data URL
statusText.html('🎵 Audio attached from upload.');
Confirms the audio was attached
} else {
If the file type is none of the above
statusText.html('⚠️ Unsupported file type. Use image, video, or audio.');
Shows an error message rejecting unsupported formats

seedSampleFlies()

seedSampleFlies() is called once after the user logs in (if !seeded). It populates the flies array with three pre-made entries using external image and video URLs. This shows new users what the vault looks like with data, and the data persists during their session. The sample data uses real URLs from Unsplash and MDN, demonstrating resource linking.

function seedSampleFlies() {
  flies = [
    {
      desc: 'Recon fly returned with sunset intel.',
      minutes: 30,
      image: 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&w=600',
      video: '',
      audio: ''
    },
    {
      desc: 'Silent fly patrol over neon swamp.',
      minutes: 45,
      image: '',
      video: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
      audio: ''
    },
    {
      desc: 'Encrypted whisper from Fly #8.',
      minutes: 20,
      image: 'https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?auto=format&w=600',
      video: '',
      audio: ''
    }
  ];
  updateTimeStatus();
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

array-initialization Initialize sample flies flies = [ { ... }, { ... }, { ... } ];

Creates an array of three pre-made fly objects with example data (descriptions, media URLs, storage times)

flies = [
Initializes the flies array with three sample entries
{ desc: 'Recon fly returned with sunset intel.', minutes: 30, image: '...', video: '', audio: '' },
First sample fly: a 30-minute entry with description and image URL, but no video or audio
{ desc: 'Silent fly patrol over neon swamp.', minutes: 45, image: '', video: '...', audio: '' },
Second sample fly: a 45-minute entry with video but no image or audio
{ desc: 'Encrypted whisper from Fly #8.', minutes: 20, image: '...', video: '', audio: '' }
Third sample fly: a 20-minute entry with image, no video or audio
updateTimeStatus();
Recalculates and displays the total stored time (30 + 45 + 20 = 95 minutes out of 180)

updateTimeStatus()

updateTimeStatus() is a utility function that recalculates and displays the storage budget status. It's called after adding a new fly, loading samples, and rendering the list. It always shows the up-to-date ratio of used to available time.

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 calculate the sum of all minutes across all flies
timeStatus.html(`Stored time: ${total} / ${MAX_MINUTES} min`);
Updates the timeStatus text element with the current vs. maximum minutes using a template literal

📦 Key Variables

LOGIN_NAME string

The hardcoded username required to unlock LostApp (set to 'ddos1337')

const LOGIN_NAME = 'ddos1337';
PASSWORD string

The hardcoded password required to unlock LostApp (set to '123456789')

const PASSWORD = '123456789';
MAX_MINUTES number

The total storage time budget in minutes that users can allocate across all entries (set to 180)

const MAX_MINUTES = 180;
REPORT_EMAIL string

The email address where reports are sent when the user clicks 'Email LostApp Report'

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

State flag: true when the user is authenticated and can access vault controls; false when locked at login screen

let vaultUnlocked = false;
seeded boolean

State flag: true after sample flies are loaded once, preventing reload on re-login

let seeded = false;
bgDots array

Stores 200 objects, each with x, y, size, and speed properties for the animated floating dots background

let bgDots = [];
flies array

Stores all vault entries (called 'flies'), each with description, storage time, and optional image/video/audio URLs

let flies = [];
backgroundMusic p5.sound audio object

Holds the audio element for the ambient background track, can be replaced with user uploads

let backgroundMusic;
musicStarted boolean

State flag: true after music begins playing, prevents re-initialization on re-login

let musicStarted = false;
uiWrapper p5.Renderer

The main container div holding all UI elements (login form, inputs, buttons, fly list)

let uiWrapper;
userInput, passInput, flyInput, timeInput, imageInput, videoInput, audioInput, uploadInput, bgMusicUploadInput p5.dom input elements

Text, password, and file input fields for user data entry and file uploads

let userInput, passInput, passButton, statusText;
passButton, addFlyButton, sendEmailButton p5.dom button elements

Clickable buttons for unlock/lock, adding entries, and emailing reports

let passButton, addFlyButton, sendEmailButton;
statusText, timeStatus, bgMusicStatus p5.dom paragraph elements

Text elements that display status messages, storage budget info, and music upload confirmation

let statusText, timeStatus, bgMusicStatus;
flyListDiv p5.dom div element

Container that holds all rendered fly entries with their descriptions and attached media

let flyListDiv;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

BUG sketch.js end of file

There are two duplicate AI Addition comments with unexecuted backgroundMusic initializations at the end of sketch.js that do nothing and should be removed

💡 Delete the lines '// AI Addition: backgroundMusic = createAudio(...);' at the very end—they're leftover comments that serve no purpose and clutter the code

SECURITY handleUnlock()

Credentials are hardcoded in plain text in the source code, visible to anyone who inspects it. This is suitable for a demo but never for production.

💡 For a real app, use a server-side authentication system with hashed passwords. For now, this is fine for learning, but be aware of the limitation.

FEATURE addFly()

Users can add entries but there's no way to delete or edit existing ones, so mistakes are permanent for the session

💡 Add a delete button next to each fly in renderFlyList() that removes it from the flies array and re-renders the list. Also consider an edit mode.

STYLE constants at top of sketch.js

Magic strings like 'https://cdn.pixabay.com/...' are embedded inline in setup(), making the code less maintainable

💡 Define a constant like 'const DEFAULT_AUDIO_URL = ...' at the top with other constants, then use it in setup()

PERFORMANCE renderFlyList()

Large media URLs (especially high-res images and videos) can make the UI sluggish if many flies are stored

💡 Consider pagination (show 5 flies at a time with next/previous buttons) or lazy-loading images only when they scroll into view

UX handleMediaUpload() and handleBackgroundUpload()

When files are uploaded, the app uses file.data (a data URL), which can become very large and slow down the app for large files

💡 For production, upload files to a server and store only the URL, not the full file data. Or add a file size check and reject files over a threshold.

🔄 Code Flow

Code flow showing setup, draw, setupui, handleunlock, addfly, renderflylist, updateui, gettoalstoredminutes, sendlostappreport, windowresized, lockvault, startbackgroundmusic, handlebackgroundupload, handlemediaupload, seedsampleflies, updatetimestatus

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> background-fill[Background Fill] draw --> dot-drawing-loop[Dot Drawing Loop] dot-drawing-loop --> dot-wrap-condition[Dot Wrap Condition] dot-wrap-condition --> draw click setup href "#fn-setup" click draw href "#fn-draw" click background-fill href "#sub-background-fill" click dot-drawing-loop href "#sub-dot-drawing-loop" click dot-wrap-condition href "#sub-dot-wrap-condition" setup --> canvas-creation[Canvas Creation] setup --> audio-setup[Audio Setup] setup --> setupui[setupUI] click canvas-creation href "#sub-canvas-creation" click audio-setup href "#sub-audio-setup" click setupui href "#fn-setupui" setupui --> ui-container-creation[UI Container Creation] ui-container-creation --> login-form-setup[Login Form Setup] login-form-setup --> vault-controls-setup[Vault Controls Setup] click ui-container-creation href "#sub-ui-container-creation" click login-form-setup href "#sub-login-form-setup" click vault-controls-setup href "#sub-vault-controls-setup" draw --> handleunlock[handleUnlock] handleunlock --> credential-extraction[Credential Extraction] credential-extraction --> credential-comparison[Credential Comparison] credential-comparison --> success-branch[Success Branch] success-branch --> seedsampleflies[Seed Sample Flies] success-branch --> startbackgroundmusic[Start Background Music] success-branch --> updateui[Update UI] click handleunlock href "#fn-handleunlock" click credential-extraction href "#sub-credential-extraction" click credential-comparison href "#sub-credential-comparison" click success-branch href "#sub-success-branch" click seedsampleflies href "#fn-seedsampleflies" click startbackgroundmusic href "#fn-startbackgroundmusic" click updateui href "#fn-updateui" draw --> addfly[addFly] addfly --> input-extraction[Input Extraction] input-extraction --> empty-entry-check[Empty Entry Check] empty-entry-check --> budget-check[Budget Check] budget-check --> fly-creation[Fly Creation] fly-creation --> clear-display[Clear Display] clear-display --> renderflylist[Render Fly List] click addfly href "#fn-addfly" click input-extraction href "#sub-input-extraction" click empty-entry-check href "#sub-empty-entry-check" click budget-check href "#sub-budget-check" click fly-creation href "#sub-fly-creation" click clear-display href "#sub-clear-display" click renderflylist href "#fn-renderflylist" renderflylist --> entry-loop[Entry Loop] entry-loop --> media-conditionals[Media Conditionals] click entry-loop href "#sub-entry-loop" click media-conditionals href "#sub-media-conditionals" draw --> updateui[Update UI] updateui --> unlocked-branch[Unlocked Branch] unlocked-branch --> vault-controls-setup updateui --> locked-branch[Locked Branch] locked-branch --> vault-controls-setup click updateui href "#fn-updateui" click unlocked-branch href "#sub-unlocked-branch" click locked-branch href "#sub-locked-branch" draw --> windowresized[windowResized] click windowresized href "#fn-windowresized" addfly --> gettoalstoredminutes[getTotalStoredMinutes] click gettoalstoredminutes href "#fn-gettoalstoredminutes" addfly --> handlemediaupload[handleMediaUpload] handlemediaupload --> file-type-check[File Type Check] file-type-check --> audio-type-check[Audio Type Check] click handlemediaupload href "#fn-handlemediaupload" click file-type-check href "#sub-file-type-check" click audio-type-check href "#sub-audio-type-check" addfly --> handlebackgroundupload[handleBackgroundUpload] handlebackgroundupload --> file-validation[File Validation] click handlebackgroundupload href "#fn-handlebackgroundupload" click file-validation href "#sub-file-validation" sendlostappreport[sendLostAppReport] --> empty-check[Empty Check] empty-check --> report-building-loop[Report Building Loop] report-building-loop --> mailto-construction[Mailto Construction] click sendlostappreport href "#fn-sendlostappreport" click empty-check href "#sub-empty-check" click report-building-loop href "#sub-report-building-loop" click mailto-construction href "#sub-mailto-construction" draw --> updatetimestatus[Update Time Status] click updatetimestatus href "#fn-updatetimestatus"

❓ Frequently Asked Questions

What visual effects does the lookpassword sketch create?

The sketch generates a dynamic background filled with floating colored dots that move upward, creating a visually engaging and interactive experience.

How can users interact with the lookpassword creative coding sketch?

Users can enter their login name and password to unlock the app, and they have the option to upload a custom background music track after logging in.

What creative coding techniques are demonstrated in the lookpassword sketch?

This sketch showcases techniques such as particle animation for the background dots and user input handling for authentication and media uploads.

Preview

lookpassword - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of lookpassword - Code flow showing setup, draw, setupui, handleunlock, addfly, renderflylist, updateui, gettoalstoredminutes, sendlostappreport, windowresized, lockvault, startbackgroundmusic, handlebackgroundupload, handlemediaupload, seedsampleflies, updatetimestatus
Code Flow Diagram