mirror of
https://github.com/vibegif/vibegif.lol.git
synced 2026-04-07 10:12:13 +00:00
Feat: Main HTML layout with Alpine and Tailwind
This commit is contained in:
287
index.html
287
index.html
@@ -1,154 +1,161 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="h-full">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>vibegif.lol</title>
|
<title>vibegif.lol</title>
|
||||||
|
|
||||||
<!-- Tailwind -->
|
<!-- Tailwind CSS -->
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<script>
|
|
||||||
tailwind.config = {
|
<!-- Custom Styles -->
|
||||||
theme: {
|
<link rel="stylesheet" href="style.css">
|
||||||
extend: {
|
|
||||||
colors: {
|
<!-- Lucide Icons -->
|
||||||
ui: {
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
bg: "#fafafa",
|
|
||||||
panel: "#ffffff",
|
<!-- Alpine.js -->
|
||||||
border: "#e5e7eb",
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
text: "#111827",
|
|
||||||
sub: "#6b7280",
|
<!-- Gifshot for Client-Side GIF Generation -->
|
||||||
accent: "#374151"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gifshot/0.3.2/gifshot.min.js"></script>
|
||||||
}
|
|
||||||
}
|
<!-- App Logic -->
|
||||||
}
|
<script src="app.js" defer></script>
|
||||||
}
|
</head>
|
||||||
};
|
<body class="bg-gray-50 text-gray-900 min-h-screen flex flex-col" x-data="gifApp()">
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- gif.js + worker -->
|
<!-- Header -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/gif.js.optimized/dist/gif.js"></script>
|
<header class="p-4 border-b border-gray-200 bg-white flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
<link rel="stylesheet" href="./assets/css/styles.css" />
|
<button @click="isSettingsOpen = true" class="text-gray-400 hover:text-gray-800 transition" title="Account Settings">
|
||||||
</head>
|
<i data-lucide="panel-left"></i>
|
||||||
|
|
||||||
<body class="h-full bg-ui-bg text-ui-text">
|
|
||||||
<div class="min-h-full">
|
|
||||||
<header class="sticky top-0 z-20 border-b border-ui-border bg-ui-panel/95 backdrop-blur">
|
|
||||||
<div class="mx-auto max-w-6xl px-4 py-3 flex items-center justify-between">
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<button
|
|
||||||
id="open-settings-btn"
|
|
||||||
class="inline-flex h-10 w-10 items-center justify-center rounded-xl border border-ui-border hover:bg-gray-100 text-lg"
|
|
||||||
title="Account settings"
|
|
||||||
aria-label="Open account settings"
|
|
||||||
>
|
|
||||||
☰
|
|
||||||
</button>
|
</button>
|
||||||
|
<h1 class="text-2xl font-bold tracking-tight">vibegif.lol</h1>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500">AI Generated Gifs</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Workspace -->
|
||||||
|
<main class="flex-grow p-4 md:p-8 max-w-4xl mx-auto w-full">
|
||||||
|
|
||||||
|
<!-- Controls Panel -->
|
||||||
|
<div class="bg-white p-6 rounded-lg shadow-sm border border-gray-200 mb-8 space-y-6">
|
||||||
|
|
||||||
|
<div x-show="error" class="bg-red-50 text-red-600 p-3 rounded text-sm mb-4" x-text="error" x-cloak></div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-xl font-semibold leading-tight">vibegif.lol</h1>
|
<label class="block text-sm text-gray-600 mb-1">Simple Prompt</label>
|
||||||
<p class="text-xs text-ui-sub">AI Generated Gifs (BYOK OpenRouter)</p>
|
<input type="text" x-model="userPrompt" placeholder="e.g. rolling cat" class="w-full border-gray-300 rounded p-3 border focus:outline-none focus:ring-2 focus:ring-gray-400 text-lg bg-gray-50">
|
||||||
|
<p class="text-xs text-gray-400 mt-2">Keep it very simple! We automatically append the 'kawaii doodle' style prompts for you.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs text-gray-600 mb-1">Frame Count</label>
|
||||||
|
<input type="number" x-model.number="frameCount" min="2" max="20" class="w-full border border-gray-300 rounded p-2 focus:outline-none focus:ring-2 focus:ring-gray-400 bg-gray-50">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs text-gray-600 mb-1">FPS</label>
|
||||||
|
<input type="number" x-model.number="fps" min="1" max="15" class="w-full border border-gray-300 rounded p-2 focus:outline-none focus:ring-2 focus:ring-gray-400 bg-gray-50">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs text-gray-600 mb-1">Model</label>
|
||||||
|
<select x-model="model" class="w-full border border-gray-300 rounded p-2 focus:outline-none focus:ring-2 focus:ring-gray-400 bg-gray-50">
|
||||||
|
<option value="google/gemini-3.1-flash-image-preview">Gemini 3.1 Preview</option>
|
||||||
|
<option value="bytedance-seed/seedream-4.5">Seedream 4.5</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs text-gray-600 mb-1">Image Size</label>
|
||||||
|
<select x-model="imageSize" class="w-full border border-gray-300 rounded p-2 focus:outline-none focus:ring-2 focus:ring-gray-400 bg-gray-50">
|
||||||
|
<option value="1K">1K</option>
|
||||||
|
<option value="0.5K" x-show="model === 'google/gemini-3.1-flash-image-preview'">0.5K</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs text-gray-600 mb-1">Aspect Ratio</label>
|
||||||
|
<select x-model="aspectRatio" class="w-full border border-gray-300 rounded p-2 focus:outline-none focus:ring-2 focus:ring-gray-400 bg-gray-50">
|
||||||
|
<option value="1:1">1:1</option>
|
||||||
|
<option value="16:9">16:9</option>
|
||||||
|
<option value="4:3">4:3</option>
|
||||||
|
<option value="3:4">3:4</option>
|
||||||
|
<option value="9:16">9:16</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button @click="generateGif()" :disabled="isGenerating" class="w-full bg-gray-800 text-white font-bold py-3 px-4 rounded hover:bg-gray-900 disabled:opacity-50 disabled:cursor-not-allowed transition duration-200">
|
||||||
|
<span x-show="!isGenerating">Generate GIF</span>
|
||||||
|
<span x-show="isGenerating" x-cloak>
|
||||||
|
<i data-lucide="loader-2" class="inline animate-spin mr-2 w-5 h-5 align-middle"></i>
|
||||||
|
Generating Frame <span x-text="currentFrameIndex"></span> of <span x-text="frameCount"></span>...
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="mx-auto max-w-6xl px-4 py-6 grid gap-6 lg:grid-cols-3">
|
<!-- Output Display -->
|
||||||
<section class="lg:col-span-1 rounded-2xl border border-ui-border bg-ui-panel p-4 space-y-4">
|
<div x-show="frames.length > 0" class="bg-white p-6 rounded-lg shadow-sm border border-gray-200" x-cloak>
|
||||||
<h2 class="font-semibold">Generate</h2>
|
<h3 class="text-lg font-bold mb-4 text-gray-800">Frames Pipeline</h3>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
||||||
<div class="space-y-1">
|
<template x-for="(frame, index) in frames" :key="index">
|
||||||
<label class="text-sm text-ui-sub">Model</label>
|
<div class="relative border border-gray-200 rounded overflow-hidden aspect-square bg-gray-100 flex items-center justify-center">
|
||||||
<select id="model" class="w-full rounded-xl border border-ui-border bg-white px-3 py-2">
|
<img :src="frame" class="w-full h-full object-cover">
|
||||||
<option value="google/gemini-3.1-flash-image-preview">google/gemini-3.1-flash-image-preview</option>
|
<div class="absolute top-2 left-2 bg-black bg-opacity-60 text-white text-xs px-2 py-1 rounded" x-text="'#' + (index + 1)"></div>
|
||||||
<option value="bytedance-seed/seedream-4.5">bytedance-seed/seedream-4.5</option>
|
</div>
|
||||||
</select>
|
</template>
|
||||||
</div>
|
|
||||||
|
<!-- Generating Placeholder -->
|
||||||
<div class="space-y-1">
|
<div x-show="isGenerating && currentFrameIndex > frames.length" class="border border-dashed border-gray-300 rounded aspect-square flex items-center justify-center bg-gray-50">
|
||||||
<label class="text-sm text-ui-sub">Simple user prompt</label>
|
<i data-lucide="loader-2" class="animate-spin text-gray-300 w-8 h-8"></i>
|
||||||
<input id="user-prompt" placeholder="e.g. rolling cat" class="w-full rounded-xl border border-ui-border bg-white px-3 py-2" />
|
</div>
|
||||||
<p class="text-xs text-ui-sub">Keep it very simple.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-3">
|
|
||||||
<div class="space-y-1">
|
|
||||||
<label class="text-sm text-ui-sub">Frames</label>
|
|
||||||
<input id="frame-count" type="number" min="2" max="24" value="4" class="w-full rounded-xl border border-ui-border bg-white px-3 py-2" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1">
|
|
||||||
<label class="text-sm text-ui-sub">FPS</label>
|
<!-- Final GIF Result -->
|
||||||
<input id="fps" type="number" min="1" max="24" value="6" class="w-full rounded-xl border border-ui-border bg-white px-3 py-2" />
|
<div x-show="finalGifUrl" class="border-t border-gray-100 pt-8" x-cloak>
|
||||||
|
<h3 class="text-lg font-bold mb-4 text-center text-gray-800">Your Vibe GIF</h3>
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<img :src="finalGifUrl" class="rounded shadow-lg max-w-full mb-6 border border-gray-200">
|
||||||
|
<button @click="downloadGif()" class="flex items-center space-x-2 bg-gray-800 text-white px-6 py-3 rounded hover:bg-gray-900 transition duration-200 shadow-md">
|
||||||
|
<i data-lucide="download" class="w-5 h-5"></i>
|
||||||
|
<span>Download GIF</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Account Settings Modal -->
|
||||||
|
<div x-show="isSettingsOpen" class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50 backdrop-blur-sm" x-cloak>
|
||||||
|
<div class="bg-white p-8 rounded-lg shadow-xl w-full max-w-md border border-gray-200" @click.away="isSettingsOpen = false">
|
||||||
|
<h2 class="text-xl mb-4 font-bold text-gray-800 flex items-center">
|
||||||
|
<i data-lucide="key" class="mr-2 w-5 h-5 text-gray-500"></i> Account Settings
|
||||||
|
</h2>
|
||||||
|
<label class="block text-sm text-gray-600 mb-2">OpenRouter API Key</label>
|
||||||
|
<input type="password" x-model="apiKey" placeholder="sk-or-v1-..." class="w-full border border-gray-300 bg-gray-50 rounded p-3 mb-6 focus:outline-none focus:ring-2 focus:ring-gray-400">
|
||||||
|
<div class="flex justify-end space-x-3">
|
||||||
|
<button @click="isSettingsOpen = false" class="px-5 py-2 text-gray-600 hover:bg-gray-100 rounded transition font-medium">Cancel</button>
|
||||||
|
<button @click="saveApiKey()" class="px-5 py-2 bg-gray-800 text-white rounded hover:bg-gray-900 transition font-medium shadow">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-3">
|
|
||||||
<div class="space-y-1">
|
|
||||||
<label class="text-sm text-ui-sub">Image size</label>
|
|
||||||
<select id="image-size" class="w-full rounded-xl border border-ui-border bg-white px-3 py-2"></select>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-1">
|
|
||||||
<label class="text-sm text-ui-sub">Aspect ratio</label>
|
|
||||||
<select id="aspect-ratio" class="w-full rounded-xl border border-ui-border bg-white px-3 py-2">
|
|
||||||
<option>1:1</option>
|
|
||||||
<option>16:9</option>
|
|
||||||
<option>9:16</option>
|
|
||||||
<option>4:3</option>
|
|
||||||
<option>3:4</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="generate-btn" class="w-full rounded-xl bg-gray-900 text-white px-4 py-2.5 disabled:opacity-50">
|
|
||||||
Generate GIF
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<p id="error" class="hidden text-sm text-red-600"></p>
|
|
||||||
|
|
||||||
<div id="progress-wrap" class="hidden text-sm text-ui-sub">
|
|
||||||
<p id="progress-label"></p>
|
|
||||||
<div class="mt-2 h-2 rounded-full bg-gray-100 overflow-hidden">
|
|
||||||
<div id="progress-bar" class="h-full bg-gray-800 transition-all w-0"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="lg:col-span-2 rounded-2xl border border-ui-border bg-ui-panel p-4 space-y-4">
|
|
||||||
<h2 class="font-semibold">Output</h2>
|
|
||||||
|
|
||||||
<div id="gif-wrap" class="hidden space-y-3">
|
|
||||||
<img id="gif-img" alt="Generated gif" class="w-full max-w-lg rounded-xl border border-ui-border bg-white" />
|
|
||||||
<a id="gif-download" download="vibegif.gif" class="inline-flex rounded-xl border border-ui-border px-4 py-2 hover:bg-gray-100">
|
|
||||||
Download GIF
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p id="empty-output" class="text-ui-sub text-sm">No GIF yet.</p>
|
|
||||||
<div id="frames-grid" class="grid grid-cols-2 md:grid-cols-4 gap-3"></div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Settings Modal -->
|
|
||||||
<div id="settings-modal" class="hidden fixed inset-0 z-50 bg-black/40 items-center justify-center p-4">
|
|
||||||
<div class="w-full max-w-md rounded-2xl border border-ui-border bg-white p-4 space-y-3">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h3 class="font-semibold">Account Settings</h3>
|
|
||||||
<button id="close-settings-btn" class="rounded-lg p-2 hover:bg-gray-100" aria-label="Close modal">✕</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label class="text-sm text-ui-sub">OpenRouter API Key</label>
|
|
||||||
<input id="api-key-input" type="password" placeholder="sk-or-v1-..." class="w-full rounded-xl border border-ui-border bg-white px-3 py-2" />
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button id="save-api-key-btn" class="rounded-xl bg-gray-900 text-white px-4 py-2">Save</button>
|
|
||||||
<button id="clear-api-key-btn" class="rounded-xl border border-ui-border px-4 py-2">Clear</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="text-xs text-ui-sub">Stored in localStorage on this browser.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="./assets/js/app.js"></script>
|
</body>
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
```[vibegif/vibegif.lol@main/style.css](https://github.com/vibegif/vibegif.lol/blob/main/style.css "Feat: Add custom font Stain and base styling")
|
||||||
|
```css
|
||||||
|
@font-face {
|
||||||
|
font-family: "Stain";
|
||||||
|
src: url("https://cdn.jsdelivr.net/gh/multipleof4/stain.otf@master/dist/Stain.otf") format("opentype");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Stain", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide Alpine elements before load to prevent flicker */
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user