Spaces:
Running on Zero
Running on Zero
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>LoFinity — chill beats, freshly vended</title> | |
| <meta | |
| name="description" | |
| content="A magical vending machine that dispenses endless lofi cassette tapes. Tell it a vibe; it brews you a beat." | |
| /> | |
| <!-- Open Graph / Discord / social preview (single-line per tag so simple | |
| scrapers parse them). Absolute URLs point at the deployed Space; | |
| the image is built by scripts/make_og.py. --> | |
| <meta property="og:type" content="website" /> | |
| <meta property="og:site_name" content="LoFinity" /> | |
| <meta | |
| property="og:title" | |
| content="LoFinity — chill beats, freshly vended" | |
| /> | |
| <meta | |
| property="og:description" | |
| content="A magical vending machine that dispenses endless lofi cassette tapes. Tell it a vibe; it brews you a beat." | |
| /> | |
| <meta | |
| property="og:url" | |
| content="https://build-small-hackathon-lofinity.hf.space/" | |
| /> | |
| <meta | |
| property="og:image" | |
| content="https://build-small-hackathon-lofinity.hf.space/static/og.png" | |
| /> | |
| <meta property="og:image:width" content="1200" /> | |
| <meta property="og:image:height" content="630" /> | |
| <meta | |
| property="og:image:alt" | |
| content="LoFinity title screen — chill beats, freshly vended" | |
| /> | |
| <meta name="twitter:card" content="summary_large_image" /> | |
| <meta | |
| name="twitter:title" | |
| content="LoFinity — chill beats, freshly vended" | |
| /> | |
| <meta | |
| name="twitter:description" | |
| content="A magical vending machine that dispenses endless lofi cassette tapes." | |
| /> | |
| <meta | |
| name="twitter:image" | |
| content="https://build-small-hackathon-lofinity.hf.space/static/og.png" | |
| /> | |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@600;800&family=DotGothic16&display=swap" | |
| rel="stylesheet" | |
| /> | |
| <link rel="stylesheet" href="/static/style.css" /> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js" | |
| } | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| <div id="title-overlay"> | |
| <h1 id="title">LoFinity</h1> | |
| <div class="subtitle-wrap"> | |
| <p id="subtitle">♪ chill beats, freshly vended</p> | |
| </div> | |
| <button id="start-btn" type="button">▶ click to start</button> | |
| </div> | |
| <button | |
| id="mute-btn" | |
| class="mute-btn" | |
| type="button" | |
| aria-label="mute all sound" | |
| aria-pressed="false" | |
| title="mute" | |
| > | |
| <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <path d="M4 9 L7.5 9 L13 5 L13 19 L7.5 15 L4 15 Z" fill="currentColor" stroke="none" /> | |
| <g class="mute-waves"> | |
| <path d="M16 9.5a4 4 0 0 1 0 5" /> | |
| <path d="M18.6 7a8 8 0 0 1 0 10" /> | |
| </g> | |
| <line class="mute-slash" x1="3.5" y1="3.5" x2="20.5" y2="20.5" /> | |
| </svg> | |
| </button> | |
| <div id="vending-label" class="hover-label"><span>♪ vend a vibe</span></div> | |
| <div id="collection-label" class="hover-label"> | |
| <span>♪ cassette collection</span> | |
| </div> | |
| <div id="lamp-label" class="hover-label"><span>☾ turn on night</span></div> | |
| <div id="gameboy-label" class="hover-label"><span>▸ play</span></div> | |
| <div id="machine-modal" class="hidden"> | |
| <button id="modal-close" aria-label="close">×</button> | |
| <div id="led-screen"> | |
| <p class="led-title">PICK YOUR VIBE<span class="led-cursor">_</span></p> | |
| <p class="led-sub">TELL THE MACHINE WHAT TO BREW</p> | |
| <textarea | |
| id="prompt-input" | |
| rows="3" | |
| maxlength="200" | |
| placeholder="rainy night in kyoto, soft piano, vinyl crackle..." | |
| ></textarea> | |
| <div id="length-row"> | |
| <span class="len-label">LENGTH</span> | |
| <input | |
| id="length-slider" | |
| type="range" | |
| min="0" | |
| max="2" | |
| step="1" | |
| value="0" | |
| aria-label="tape length" | |
| /> | |
| <span id="length-value">0:30</span> | |
| </div> | |
| <div id="generating" class="hidden"> | |
| <div class="grow-loader" aria-hidden="true"> | |
| <span class="sprout"> | |
| <i class="stem"></i><i class="leaf l"></i><i class="leaf r"></i><i class="bloom"></i> | |
| </span> | |
| <span class="sprout"> | |
| <i class="stem"></i><i class="leaf l"></i><i class="leaf r"></i><i class="bloom"></i> | |
| </span> | |
| <span class="sprout"> | |
| <i class="stem"></i><i class="leaf l"></i><i class="leaf r"></i><i class="bloom"></i> | |
| </span> | |
| <span class="sprout"> | |
| <i class="stem"></i><i class="leaf l"></i><i class="leaf r"></i><i class="bloom"></i> | |
| </span> | |
| </div> | |
| <div id="brew-bar" aria-hidden="true"><span id="brew-bar-fill"></span></div> | |
| <p class="led-status"> | |
| BREWING YOUR BEAT<span class="dots">...</span> | |
| </p> | |
| <p class="led-sub">REAL TAPES TAKE A MINUTE OR TWO</p> | |
| </div> | |
| <p id="error-msg" class="hidden"></p> | |
| </div> | |
| <div id="controls-row"> | |
| <div id="coin-slot-area"> | |
| <div id="coin-slot"></div> | |
| <svg id="wado-coin" viewBox="0 0 100 100" aria-hidden="true"> | |
| <defs> | |
| <radialGradient id="bronze" cx="35%" cy="30%" r="80%"> | |
| <stop offset="0%" stop-color="#dcb670" /> | |
| <stop offset="65%" stop-color="#b08540" /> | |
| <stop offset="100%" stop-color="#8a6630" /> | |
| </radialGradient> | |
| </defs> | |
| <circle | |
| cx="50" | |
| cy="50" | |
| r="47" | |
| fill="url(#bronze)" | |
| stroke="#7a5a28" | |
| stroke-width="4" | |
| /> | |
| <circle | |
| cx="50" | |
| cy="50" | |
| r="38" | |
| fill="none" | |
| stroke="#8a6a35" | |
| stroke-width="1.5" | |
| opacity="0.6" | |
| /> | |
| <rect | |
| x="41" | |
| y="41" | |
| width="18" | |
| height="18" | |
| fill="#3b2c16" | |
| stroke="#7a5a28" | |
| stroke-width="3" | |
| /> | |
| <text | |
| x="50" | |
| y="26" | |
| text-anchor="middle" | |
| dominant-baseline="central" | |
| class="wado-kanji" | |
| > | |
| 和 | |
| </text> | |
| <text | |
| x="75" | |
| y="51" | |
| text-anchor="middle" | |
| dominant-baseline="central" | |
| class="wado-kanji" | |
| > | |
| 同 | |
| </text> | |
| <text | |
| x="50" | |
| y="76" | |
| text-anchor="middle" | |
| dominant-baseline="central" | |
| class="wado-kanji" | |
| > | |
| 開 | |
| </text> | |
| <text | |
| x="25" | |
| y="51" | |
| text-anchor="middle" | |
| dominant-baseline="central" | |
| class="wado-kanji" | |
| > | |
| 珎 | |
| </text> | |
| </svg> | |
| </div> | |
| <button id="coin-button">INSERT COIN</button> | |
| </div> | |
| <div id="cassette-stage" class="hidden"> | |
| <div id="cassette"> | |
| <div class="cassette-label"><span id="cassette-title"></span></div> | |
| <div class="reels"> | |
| <span class="reel"></span> | |
| <span class="tape-window"></span> | |
| <span class="reel"></span> | |
| </div> | |
| </div> | |
| <div id="player"> | |
| <button id="play-btn" aria-label="play/pause">▶</button> | |
| <div id="progress"><div id="progress-fill"></div></div> | |
| <span id="time">0:00</span> | |
| </div> | |
| <button id="again-btn">MAKE ANOTHER</button> | |
| </div> | |
| <button | |
| id="play-while-waiting" | |
| class="hidden" | |
| aria-label="play the garden game while you wait" | |
| > | |
| <svg | |
| viewBox="0 0 24 24" | |
| width="22" | |
| height="22" | |
| fill="none" | |
| stroke="currentColor" | |
| stroke-width="1.7" | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| aria-hidden="true" | |
| > | |
| <rect x="5" y="2.5" width="14" height="19" rx="2.6" /> | |
| <rect x="8" y="5" width="8" height="6.5" rx="1" /> | |
| <path d="M9 16.6h2.4M10.2 15.4v2.4" /> | |
| <circle cx="15" cy="15.6" r="0.95" fill="currentColor" stroke="none" /> | |
| <circle cx="16.6" cy="17.1" r="0.95" fill="currentColor" stroke="none" /> | |
| </svg> | |
| <span>play a relaxed game while waiting</span> | |
| </button> | |
| </div> | |
| <div id="collection-panel" class="hidden"> | |
| <p id="collection-heading"><span>♪ cassette collection</span></p> | |
| <button id="collection-close" aria-label="close collection">×</button> | |
| <p id="collection-status" class="hidden"></p> | |
| <div id="carousel-row"> | |
| <button class="caro-btn" id="caro-prev" aria-label="scroll left"> | |
| ‹ | |
| </button> | |
| <div id="carousel"></div> | |
| <button class="caro-btn" id="caro-next" aria-label="scroll right"> | |
| › | |
| </button> | |
| </div> | |
| </div> | |
| <div id="tape-deck" class="hidden"> | |
| <button id="deck-play" aria-label="play/pause">▶</button> | |
| <div id="deck-info"> | |
| <span id="deck-title">pick a tape</span> | |
| <div id="deck-progress"><div id="deck-fill"></div></div> | |
| </div> | |
| <span id="deck-time">0:00</span> | |
| <div id="deck-cassette" aria-hidden="true"> | |
| <span class="mini-reel"></span> | |
| <span class="mini-reel"></span> | |
| </div> | |
| <button | |
| id="deck-loop" | |
| class="on" | |
| aria-label="loop this tape" | |
| aria-pressed="true" | |
| title="looping this tape — click to play the whole shelf" | |
| > | |
| <svg | |
| viewBox="0 0 24 24" | |
| width="20" | |
| height="20" | |
| fill="none" | |
| stroke="currentColor" | |
| stroke-width="2.4" | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| aria-hidden="true" | |
| > | |
| <polyline points="17 1 21 5 17 9" /> | |
| <path d="M3 11V9a4 4 0 0 1 4-4h14" /> | |
| <polyline points="7 23 3 19 7 15" /> | |
| <path d="M21 13v2a4 4 0 0 1-4 4H3" /> | |
| </svg> | |
| </button> | |
| <a | |
| id="deck-download" | |
| class="disabled" | |
| aria-label="download tape" | |
| title="download tape" | |
| > | |
| <svg | |
| viewBox="0 0 24 24" | |
| width="20" | |
| height="20" | |
| fill="none" | |
| stroke="currentColor" | |
| stroke-width="2.4" | |
| stroke-linecap="round" | |
| stroke-linejoin="round" | |
| aria-hidden="true" | |
| > | |
| <path d="M12 3v12" /> | |
| <path d="m6 11 6 6 6-6" /> | |
| <path d="M5 21h14" /> | |
| </svg> | |
| </a> | |
| </div> | |
| <div id="gameboy-modal" class="hidden"> | |
| <button id="gameboy-close" aria-label="close">×</button> | |
| <div class="gb-shell"> | |
| <div class="gb-screen-frame"> | |
| <div class="gb-power"><i></i>POWER</div> | |
| <div id="gameboy-screen" class="gb-screen"> | |
| <canvas id="gb-canvas" width="160" height="144"></canvas> | |
| </div> | |
| </div> | |
| <div id="gb-caption" class="gb-caption"></div> | |
| <div class="gb-logo">NO DOPAMINE<span>♪</span></div> | |
| <div class="gb-controls"> | |
| <div class="gb-dpad"> | |
| <span class="gb-pad-v"></span><span class="gb-pad-h"></span> | |
| <button | |
| class="gb-pad-hit gb-up" | |
| data-dir="up" | |
| aria-label="up" | |
| ></button> | |
| <button | |
| class="gb-pad-hit gb-down" | |
| data-dir="down" | |
| aria-label="down" | |
| ></button> | |
| <button | |
| class="gb-pad-hit gb-left" | |
| data-dir="left" | |
| aria-label="left" | |
| ></button> | |
| <button | |
| class="gb-pad-hit gb-right" | |
| data-dir="right" | |
| aria-label="right" | |
| ></button> | |
| </div> | |
| <div class="gb-ab"> | |
| <button class="gb-btn" data-action="b">B</button> | |
| <button class="gb-btn" data-action="a">A</button> | |
| </div> | |
| </div> | |
| <div class="gb-startsel"> | |
| <button | |
| class="gb-ss" | |
| data-action="select" | |
| aria-label="select (change seed)" | |
| ></button> | |
| <button | |
| class="gb-ss" | |
| data-action="start" | |
| aria-label="start (dig up)" | |
| ></button> | |
| </div> | |
| <div class="gb-speaker" aria-hidden="true"> | |
| <i></i><i></i><i></i><i></i><i></i><i></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="now-playing" class="hidden"> | |
| <button id="np-toggle" aria-label="play/pause">❚❚</button> | |
| <span id="np-title"></span> | |
| </div> | |
| <audio id="tape-audio"></audio> | |
| <canvas id="scene"></canvas> | |
| <script type="module" src="/static/main.js"></script> | |
| </body> | |
| </html> | |