Add Drake vibe page and project scaffold #4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "2-drake-vibe-page"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Changes
package.json/vite.config.js: Vite 6 + Tone.js, multi-page auto-discovery of vibe directoriessrc/drake/index.html: Page with phrase text, Spotify embed (God's Plan), OG meta tagssrc/drake/style.css: Dark background with purple radial gradients, Playfair Display, CSS reveal animationssrc/drake/main.js: Tone.js ambient — low sine oscillator with reverb + LFO filter sweep, triggered on tapDockerfile: Two-stage build (node for Vite, nginx:alpine for serving).woodpecker.yml: Kaniko build to Harbor on push to mainnginx.conf: Static serving with try_files.gitignore: node_modules, distTest Plan
npm run buildsucceeds — produces dist/drake/index.htmlnpm run devserves page at /drake/ with correct HTMLReview Checklist
Related Notes
my-vibes-world— project this work belongs toPR #4 Review
DOMAIN REVIEW
Tech stack identified: Vanilla JS + Vite 6 + Tone.js, HTML/CSS, Docker (multi-stage), nginx, Woodpecker CI (Kaniko). 10 new files, 1449 additions, 0 deletions.
vite.config.js -- Auto-discovery logic has redundant dead code
getVibeEntries()contains a double-check pattern that is confusing:The
statSync(html, { throwIfNoEntry: false })on line 11 returnsundefined(falsy) if the file doesn't exist, so theifbody is only entered when the file exists. The innertry { statSync(html) } catch {}is therefore dead code -- it re-checks existence of a file we already know exists. Either remove the inner try/catch (relying on the condition) or remove thethrowIfNoEntryfrom the condition and rely solely on the try/catch. Pick one pattern.Additionally,
statSyncwith{ throwIfNoEntry: false }requires Node >= 15.3. The Dockerfile usesnode:22-alpineso this is fine today, but if someone runs this on an older Node, it will throw instead of returning undefined. Considerfs.existsSync()for clarity.src/drake/main.js -- Tone.js audio nodes are never disposed (memory leak)
The
startAmbient()function creates aReverb,Filter,Synth, andLFObut stores none of them in module-scope variables and provides no cleanup path. For a single-page creative site that never navigates away, this is tolerable -- the nodes live for the page lifetime. However:synth.triggerAttack('C2')starts a note that plays indefinitely (sustain: 1, no release triggered). There is notriggerRelease()call and no way for the user to stop the audio once started.Recommendation: Store nodes in module scope and add a
stopAmbient()function that calls.dispose()on each node. Even if not wired up now, it prepares for future use. Not a blocker for an MVP creative page.src/drake/main.js -- No error handling on Tone.start()
Tone.start()can throw if the AudioContext fails to resume (e.g., browser policy blocks it). Theawait Tone.start()is unguarded -- if it rejects,startedis alreadytrueso the user cannot retry by tapping again. The flag should be set AFTERTone.start()succeeds, or the function should catch and reset:src/drake/index.html -- OG meta tags
OG tags look correct for Instagram link previews:
og:title,og:description,og:type,og:urlare all present. Missingog:image-- Instagram and other social platforms will show a blank preview without a share image. This is a creative site so you may want to add one later, but it is not a blocker.Also worth noting:
<title>is generic "myvibes.world" rather than page-specific. Consider "Drake | myvibes.world" for better tab identification when multiple vibe pages exist.src/drake/style.css -- Good responsive design
The CSS is clean.
clamp()for font sizing,100dvhfor dynamic viewport height, mobile breakpoint at 480px. The radial gradient approach for the moody background is tasteful.overflow: hiddenon body prevents scroll but also prevents the user from seeing any content that overflows on very small screens -- worth testing on 320px width devices.The
.tap-hint.hiddenusesopacity: 0 !importantwhich works but the element remains in the DOM and technically interactive. Since it is just a text hint with no click handler, this is fine.Dockerfile -- Clean two-stage build
The Dockerfile is correctly structured: build stage with
node:22-alpine, production stage withnginx:alpine. No unnecessary layers.npm ciis correct for reproducible builds.COPY . .in the build stage copies everything (including.git,node_modulesif present locally) -- consider adding a.dockerignorefile to exclude.git/,node_modules/,dist/,.woodpecker.yml, etc. This reduces build context size and prevents cache busting.nginx.conf -- Minimal and correct
The config is correct for static serving.
try_files $uri $uri/ =404handles both files and directory index lookups. One consideration: no gzip compression is configured. For a static site with CSS/JS assets, addinggzip on; gzip_types text/css application/javascript;would improve load times. Not a blocker.Also missing: cache headers for static assets. Vite generates hashed filenames so you could safely set aggressive caching.
.woodpecker.yml -- Follows platform pattern
The CI config triggers on push to main and manual events, uses Kaniko for building, pushes to Harbor. Secrets are referenced via
from_secret(not hardcoded). Theinsecure: trueandinsecure-registrysettings are for in-cluster Harbor access over HTTP, which is standard for internal registries.One note: there is no step to lint, test, or validate the build before pushing. Currently the pipeline just builds and pushes. If
npm run buildfails inside the Dockerfile, Kaniko will fail the step, so this is implicitly covered, but explicit CI steps would give better error messages.package.json -- Clean
Minimal dependencies.
"private": trueis correct."type": "module"matches the ESM imports inmain.jsandvite.config.js.Spotify embed -- Correct format
The iframe src
https://open.spotify.com/embed/track/6DCZcSspjsKoFjzjrWoCdn?utm_source=generator&theme=0is the correct Spotify embed format. Track ID 6DCZcSspjsKoFjzjrWoCdn corresponds to "God's Plan" by Drake.theme=0is dark theme. Theallowattribute includes the necessary permissions.loading="lazy"is correct.BLOCKERS
None. This is an MVP creative page with no user input, no auth, no server-side logic, no secrets in code. The code quality issues identified are nits for this context.
NITS
try/catchingetVibeEntries()-- pick either thethrowIfNoEntrypattern or the try/catch pattern, not both. (Code clarity)started = trueAFTERTone.start()succeeds, not before, so users can retry on failure. (Resilience).dockerignorefile (.git,node_modules,dist,.woodpecker.yml). (Build performance)og:imagemeta tag for richer social previews. (UX)<title>page-specific, e.g. "Drake | myvibes.world". (UX)overflow: hiddenon very narrow viewports (320px) to ensure no content is clipped. (Accessibility)SOP COMPLIANCE
from_secret, no hardcoded valuespackage-lock.jsonis committed (correct for reproducible builds)PROCESS OBSERVATIONS
VERDICT: APPROVED