Jonathan Pape

Creative Technologist
Marketing Data & Paid Media
Generative visuals, data-driven storytelling
Full-stack applications, and experiments.

Selected Work

Django · React · TypeScript · AI Agent

URL parameter builder for marketing teams with AI-powered campaign assistance and template management.

React · TypeScript · Finance

Debt payoff calculator and amortization visualization platform with AI scenario analysis.

Python · OpenAI · Analytics

AI-powered brand and competitor visibility measurement system for search intelligence.

About

I build at the intersection of paid media, data engineering, and creative coding.

Google Ads Python React Generative Art

Contact

jon@jonpape.com

{ margin: 0; padding: 0; box-sizing: border-box; } /* Force real fullscreen */ html, body { width: 100%; height: 100%; overflow: hidden; } body { font-family: Arial, sans-serif; background: black; letter-spacing: 0.15em; text-transform: uppercase; color: #ffffff; } /* TRUE fullscreen canvas */ canvas { position: fixed !important; inset: 0; width: 100vw !important; height: 100vh !important; max-width: 100vw !important; max-height: 100vh !important; display: block; z-index: -1; pointer-events: none; } /* Layout */ .main-wrapper { position: relative; z-index: 10; width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; padding: 5vh; } .portfolio-card { max-width: 1100px; width: 100%; background: rgba(0, 0, 0, 0.85); border-radius: 20px; padding: 2.5rem; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.9); backdrop-filter: blur(16px); } .header { display: flex; justify-content: space-between; margin-bottom: 2rem; border-bottom: 1px solid rgba(255, 255, 255, 0.12); padding-bottom: 1.5rem; } .nav { display: flex; gap: 1rem; font-size: 0.7rem; } .nav a { text-decoration: none; color: white; } .content { display: grid; grid-template-columns: 2fr 1fr; gap: 2rem; font-size: 0.7rem; } .projects-grid { display: grid; gap: 1rem; } .project-card { padding: 1rem; border-radius: 12px; background: rgba(255, 255, 255, 0.05); } .pill-row { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.7rem; } .pill { font-size: 0.55rem; padding: 0.25rem 0.6rem; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.25); } .footer-row { display: flex; justify-content: space-between; opacity: 0.6; font-size: 0.55rem; margin-top: 1rem; } Why doesn't this go all the way to the left? Show more 10:55 PM Identified padding as culprit, proposed removal solutions. The padding is coming from .main-wrapper, which has padding: 5vh; on all sides. This creates a 5vh gap on the left (and all other sides). To make it flush left, change this in your CSS: css .main-wrapper { position: relative; z-index: 10; width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; padding: 5vh 5vh 5vh 0; /* Remove left padding */ } Or if you want it flush on all sides: css .main-wrapper { position: relative; z-index: 10; width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; padding: 0; /* Remove all padding */ } You may also want to adjust the .portfolio-card padding to control the internal spacing independently. JS 10:57 PM Deciphered request and architected generative terrain sketch. Here's a sketch.js file that creates the "Crystal Mountains" generative art background: javascript let terrain = []; let cols, rows; let scl = 20; let w = 2400; let h = 2000; let flying = 0; function setup() { createCanvas(windowWidth, windowHeight, WEBGL); colorMode(HSB, 360, 100, 100); cols = w / scl; rows = h / scl; for (let x = 0; x < cols; x++) { terrain[x] = []; for (let y = 0; y < rows; y++) { terrain[x][y] = 0; } } } function draw() { flying -= 0.02; let yoff = flying; for (let y = 0; y < rows; y++) { let xoff = 0; for (let x = 0; x < cols; x++) { terrain[x][y] = map(noise(xoff, yoff), 0, 1, -120, 120); xoff += 0.1; } yoff += 0.1; } background(0); translate(0, 50); rotateX(PI / 3); translate(-w / 2, -h / 2); for (let y = 0; y < rows - 1; y++) { beginShape(TRIANGLE_STRIP); for (let x = 0; x < cols; x++) { let elevation1 = terrain[x][y]; let elevation2 = terrain[x][y + 1]; // HSB color based on elevation let hue = map(elevation1, -120, 120, 180, 280); let sat = map(elevation1, -120, 120, 40, 80); let bri = map(elevation1, -120, 120, 20, 70); fill(hue, sat, bri); stroke(hue, sat, bri + 10); strokeWeight(1); vertex(x * scl, y * scl, elevation1); hue = map(elevation2, -120, 120, 180, 280); sat = map(elevation2, -120, 120, 40, 80); bri = map(elevation2, -120, 120, 20, 70); fill(hue, sat, bri); vertex(x * scl, (y + 1) * scl, elevation2); } endShape(); } } function windowResized() { resizeCanvas(windowWidth, windowHeight); } This creates: Noise-driven terrain that continuously flows toward you HSB color mapping from blue/purple (low elevation) to cyan/violet (high elevation) WebGL rendering for smooth 3D performance Responsive to window resizing The terrain flows endlessly and creates a mesmerizing crystal mountain effect behind your portfolio card. let limit; let count; let space; let minDim; let anchorHue; let hueRange; let smoothness; let mappingDirection; function setup() { createCanvas(windowWidth, windowHeight, WEBGL); pixelDensity(Math.min(window.devicePixelRatio, 2)); noLoop(); noStroke(); angleMode(DEGREES); colorMode(HSB, 360, 100, 100); resetMountain(); draw(); } function resetMountain() { minDim = windowWidth < windowHeight ? windowWidth : windowHeight; limit = minDim / random(1.3, 2); count = random(100, 150); space = windowWidth / count; hueRange = random(160, 300); anchorHue = random(360); smoothness = random(10, 15); mappingDirection = random([true, false]); } const getNoise = (i, j) => { const value = noise(i / smoothness, j / smoothness); return map(value, 0, 1, -limit / 3, limit); }; function mountainMaker() { for (let i = 0; i < count; i++) { for (let j = 0; j < count; j++) { let from = mappingDirection ? -limit / 3 : limit; let to = mappingDirection ? limit : -limit / 3; const hue = map( getNoise(i, j), from, to, anchorHue - hueRange, anchorHue + hueRange ); fill(hue, 100, 100); beginShape(); vertex((i + 1) * space, (j + 1) * space, getNoise(i + 1, j + 1)); vertex((i + 1) * space, (j - 1) * space, getNoise(i + 1, j - 1)); vertex((i - 1) * space, (j - 1) * space, getNoise(i - 1, j - 1)); vertex((i - 1) * space, (j + 1) * space, getNoise(i - 1, j + 1)); endShape(); } } } function draw() { background(0); push(); translate(-windowWidth / 2, minDim / 10); rotateX(random(50, 65)); mountainMaker(); pop(); } function windowResized() { resizeCanvas(windowWidth, windowHeight); resetMountain(); clear(); draw(); } Show more 10:58 PM Analyzed generative art code structure and anticipated user intent. Nice! This is a much more elegant approach than my example. Your code creates a static crystalline mountain range with some really thoughtful design choices: Key features I like: noLoop() for a static composition (saves performance) Randomized parameters on each generation/resize mappingDirection adds variety to the color flow The elevation range (-limit/3 to limit) creates nice depth Pixel density optimization for retina displays One small observation: The vertex order in your beginShape() creates quad faces, but you might want to ensure they're wound consistently for proper face culling: javascript vertex((i + 1) * space, (j + 1) * space, getNoise(i + 1, j + 1)); vertex((i + 1) * space, (j - 1) * space, getNoise(i + 1, j - 1)); vertex((i - 1) * space, (j - 1) * space, getNoise(i - 1, j - 1)); vertex((i - 1) * space, (j + 1) * space, getNoise(i - 1, j + 1)); This looks correct for counter-clockwise winding. Optional enhancement: If you want subtle animation, you could add a time offset to the noise: javascript let timeOffset = 0; // In draw(): timeOffset += 0.001; // In getNoise(): noise(i/smoothness + timeOffset, j/smoothness) Is there something specific you'd like to adjust or add to this? let limit; let count; let space; let minDim; let anchorHue; let hueRange; let smoothness; let mappingDirection; function setup() { createCanvas(windowWidth, windowHeight, WEBGL); pixelDensity(Math.min(window.devicePixelRatio, 2)); noLoop(); noStroke(); angleMode(DEGREES); colorMode(HSB, 360, 100, 100); resetMountain(); draw(); } function resetMountain() { minDim = windowWidth < windowHeight ? windowWidth : windowHeight; limit = minDim / random(1.3, 2); count = random(100, 150); space = windowWidth / count; hueRange = random(160, 300); anchorHue = random(360); smoothness = random(10, 15); mappingDirection = random([true, false]); } const getNoise = (i, j) => { const value = noise(i / smoothness, j / smoothness); return map(value, 0, 1, -limit / 3, limit); }; function mountainMaker() { for (let i = 0; i < count; i++) { for (let j = 0; j < count; j++) { let from = mappingDirection ? -limit / 3 : limit; let to = mappingDirection ? limit : -limit / 3; const hue = map( getNoise(i, j), from, to, anchorHue - hueRange, anchorHue + hueRange ); fill(hue, 100, 100); beginShape(); vertex((i + 1) * space, (j + 1) * space, getNoise(i + 1, j + 1)); vertex((i + 1) * space, (j - 1) * space, getNoise(i + 1, j - 1)); vertex((i - 1) * space, (j - 1) * space, getNoise(i - 1, j - 1)); vertex((i - 1) * space, (j + 1) * space, getNoise(i - 1, j + 1)); endShape(); } } } function draw() { background(0); push(); translate(-windowWidth / 2, minDim / 10); rotateX(random(50, 65)); mountainMaker(); pop(); } function windowResized() { resizeCanvas(windowWidth, windowHeight); resetMountain(); clear(); draw(); } Show more 10:59 PM Recognized ambiguous request and deliberated seeking clarification. This is a really clean implementation! The code creates beautiful, randomized crystal mountains with excellent color variety. What would you like to do with it? Some options: Add interaction - regenerate on click/spacebar Refine the palette - constrain hue ranges for specific moods (cool blues/purples, warm oranges/reds) Animation - subtle rotation or noise evolution Export - save high-res versions Integration tweaks - ensure it works perfectly with your portfolio overlay Or is there a specific issue you're encountering? 1767855654357_image.png Not all the way to the left. 11:01 PM Diagnosed padding conflicts preventing left alignment. To make it flush left, you need to change both the padding AND the alignment in .main-wrapper: css .main-wrapper { position: relative; z-index: 10; width: 100%; height: 100vh; display: flex; justify-content: flex-start; /* Change from center to flex-start */ align-items: center; padding: 5vh 5vh 5vh 0; /* Remove left padding */ } Or to make it completely flush with no padding on the left: css .main-wrapper { position: relative; z-index: 10; width: 100%; height: 100vh; display: flex; justify-content: flex-start; /* Align left */ align-items: center; padding: 5vh 5vh 5vh 0; /* Top, right, bottom padding only */ } The key is both removing the left padding AND changing justify-content from center to flex-start. Jonathan_Pape_Resume - 20250905 (1).pdf pdf The css looks good. Change the html to align with my resume Jonathan Pape — Creative Technologist

Jonathan Pape

Creative Technologist · Marketing Data & Paid Media
Generative visuals, data-driven storytelling, and full-stack experiments.

Selected Work

Crystal Mountains
p5.js · WebGL · Generative Art

Real-time noise-driven terrain rendered in WebGL with HSB elevation mapping.

PlanningPlus.app
React · TypeScript · Finance

Loan payoff and amortization visualization platform.

ChatRank
Python · OpenAI · Analytics

AI-powered brand and competitor visibility measurement system.

About

I build at the intersection of paid media, data engineering, and creative coding.

Google Ads Python React Generative Art

Contact

hello@example.com

Show more 11:03 PM Synthesized resume details to restructure portfolio HTML. Jonathan Pape — Digital Marketing Strategist & Developer

Jonathan Pape

Director of Digital Marketing · Developer · Data Strategist
Enterprise marketing strategy, paid media optimization, and full-stack development.

Selected Work

URLForge.app
Django · React · TypeScript · AI Agent

URL parameter builder for marketing teams with AI-powered campaign assistance and template management.

PlanningPlus.app
React · TypeScript · Finance

Debt payoff calculator and amortization visualization platform with AI scenario analysis.

ChatRank
Python · OpenAI · Analytics

AI-powered brand and competitor visibility measurement system for search intelligence.

Crystal Mountains
p5.js · WebGL · Generative Art

Real-time noise-driven terrain with HSB elevation color mapping.

About

Director of Digital Marketing at Kitsap Credit Union, leading enterprise strategy, paid media operations, and MarTech transformation. Previously at Google/Mandiant, Yahoo, and First Tech Federal Credit Union. MBA and MCIT from University of Pennsylvania.

Google Ads Python React SQL Tableau Django TypeScript Meta Advertising

Contact

jonpape616@gmail.com
LinkedIn

Format this 11:06 PM Devised strategy to restructure and enhance readability. html Jonathan Pape — Digital Marketing Strategist & Developer

Jonathan Pape

Director of Digital Marketing · Developer · Data Strategist
Enterprise marketing strategy, paid media optimization, and full-stack development.

Selected Work

URLForge.app
Django · React · TypeScript · AI Agent

URL parameter builder for marketing teams with AI-powered campaign assistance and template management.

PlanningPlus.app
React · TypeScript · Finance

Debt payoff calculator and amortization visualization platform with AI scenario analysis.

ChatRank
Python · OpenAI · Analytics

AI-powered brand and competitor visibility measurement system for search intelligence.

Crystal Mountains
p5.js · WebGL · Generative Art

Real-time noise-driven terrain with HSB elevation color mapping.

About

Director of Digital Marketing at Kitsap Credit Union, leading enterprise strategy, paid media operations, and MarTech transformation. Previously at Google/Mandiant, Yahoo, and First Tech Federal Credit Union. MBA and MCIT from University of Pennsylvania.

Google Ads Python React SQL Tableau Django TypeScript Meta Advertising

Contact

jonpape616@gmail.com
LinkedIn

Claude is AI and can make mistakes. Please double-check responses.