Spaces:
Runtime error
Runtime error
Upload PromptForge v1.0 — Structured prompt generator for Google AI Studio
Browse files- frontend/client.js +18 -327
- frontend/index.html +173 -61
- frontend/style.css +197 -282
- frontend/style.css.bak +284 -0
frontend/client.js
CHANGED
|
@@ -1,332 +1,23 @@
|
|
| 1 |
/**
|
| 2 |
-
* PromptForge — client.js
|
| 3 |
-
*
|
| 4 |
*/
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
let currentPromptId = null; // Tracks the active manifest
|
| 8 |
-
|
| 9 |
-
// ── DOM refs ──────────────────────────────────────────────────────
|
| 10 |
const $ = id => document.getElementById(id);
|
| 11 |
-
const
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
function hide(el) { el.classList.add("hidden"); }
|
| 21 |
-
function showStep(name) {
|
| 22 |
-
Object.values(steps).forEach(hide);
|
| 23 |
-
show(steps[name]);
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
function toast(msg, type = "info") {
|
| 27 |
-
const t = $("toast");
|
| 28 |
-
t.textContent = msg;
|
| 29 |
-
t.className = `toast ${type}`;
|
| 30 |
-
show(t);
|
| 31 |
-
setTimeout(() => hide(t), 3500);
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
function setLoading(btn, loading) {
|
| 35 |
-
btn.disabled = loading;
|
| 36 |
-
btn.dataset.originalText ??= btn.textContent;
|
| 37 |
-
btn.textContent = loading ? "⏳ Working…" : btn.dataset.originalText;
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
async function apiFetch(path, method = "GET", body = null) {
|
| 41 |
-
const opts = {
|
| 42 |
-
method,
|
| 43 |
-
headers: { "Content-Type": "application/json" },
|
| 44 |
-
};
|
| 45 |
-
if (body) opts.body = JSON.stringify(body);
|
| 46 |
-
const resp = await fetch(API_BASE + path, opts);
|
| 47 |
-
if (!resp.ok) {
|
| 48 |
-
const err = await resp.json().catch(() => ({ detail: resp.statusText }));
|
| 49 |
-
throw new Error(err.detail || "Unknown error");
|
| 50 |
-
}
|
| 51 |
-
return resp.json();
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
// ── Step 1: Generate ──────────────────────────────────────────────
|
| 55 |
-
$("btn-generate").addEventListener("click", async () => {
|
| 56 |
-
const instruction = $("instruction").value.trim();
|
| 57 |
-
if (!instruction || instruction.length < 5) {
|
| 58 |
-
toast("Please enter a meaningful instruction (min 5 characters).", "error");
|
| 59 |
-
return;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
const provider = $("provider").value;
|
| 63 |
-
const apiKey = $("api-key").value.trim();
|
| 64 |
-
const extraCtx = $("extra-context").value.trim();
|
| 65 |
-
const enhance = provider !== "none" && !!apiKey;
|
| 66 |
-
|
| 67 |
-
const btn = $("btn-generate");
|
| 68 |
-
setLoading(btn, true);
|
| 69 |
-
try {
|
| 70 |
-
const data = await apiFetch("/api/generate", "POST", {
|
| 71 |
-
instruction,
|
| 72 |
-
output_format: "both",
|
| 73 |
-
provider,
|
| 74 |
-
api_key: apiKey || null,
|
| 75 |
-
enhance,
|
| 76 |
-
extra_context: extraCtx || null,
|
| 77 |
-
});
|
| 78 |
-
|
| 79 |
-
currentPromptId = data.prompt_id;
|
| 80 |
-
renderManifest(data.manifest);
|
| 81 |
-
show(steps.manifest);
|
| 82 |
-
steps.manifest.scrollIntoView({ behavior: "smooth" });
|
| 83 |
-
toast("✅ Manifest generated — review and approve.", "success");
|
| 84 |
-
} catch (e) {
|
| 85 |
-
toast(`Error: ${e.message}`, "error");
|
| 86 |
-
} finally {
|
| 87 |
-
setLoading(btn, false);
|
| 88 |
-
}
|
| 89 |
-
});
|
| 90 |
-
|
| 91 |
-
// ── Provider selector shows/hides API key field ───────────────────
|
| 92 |
-
$("provider").addEventListener("change", () => {
|
| 93 |
-
const group = $("api-key-group");
|
| 94 |
-
group.style.display = $("provider").value !== "none" ? "flex" : "none";
|
| 95 |
-
});
|
| 96 |
-
|
| 97 |
-
// ── Render manifest into editable grid ───────────────────────────
|
| 98 |
-
function renderManifest(manifest) {
|
| 99 |
-
const sp = manifest.structured_prompt;
|
| 100 |
-
const grid = $("manifest-grid");
|
| 101 |
-
grid.innerHTML = "";
|
| 102 |
-
|
| 103 |
-
const fields = [
|
| 104 |
-
{ key: "role", label: "Role", value: sp.role, full: false },
|
| 105 |
-
{ key: "style", label: "Style & Tone", value: sp.style, full: false },
|
| 106 |
-
{ key: "task", label: "Task", value: sp.task, full: true },
|
| 107 |
-
{ key: "input_format", label: "Input Format", value: sp.input_format, full: false },
|
| 108 |
-
{ key: "output_format", label: "Output Format", value: sp.output_format, full: false },
|
| 109 |
-
{ key: "constraints", label: "Constraints", value: sp.constraints.join("\n"), full: true },
|
| 110 |
-
{ key: "safety", label: "Safety", value: sp.safety.join("\n"), full: true },
|
| 111 |
-
];
|
| 112 |
-
|
| 113 |
-
fields.forEach(f => {
|
| 114 |
-
const div = document.createElement("div");
|
| 115 |
-
div.className = `manifest-field${f.full ? " full" : ""}`;
|
| 116 |
-
div.innerHTML = `<label>${f.label}</label>
|
| 117 |
-
<textarea id="field-${f.key}" rows="${f.full ? 3 : 2}">${escapeHtml(f.value)}</textarea>`;
|
| 118 |
-
grid.appendChild(div);
|
| 119 |
-
});
|
| 120 |
-
|
| 121 |
-
$("manifest-json").textContent = JSON.stringify(manifest, null, 2);
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
// ── Step 2/3: Approve ─────────────────────────────────────────────
|
| 125 |
-
$("btn-approve").addEventListener("click", async () => {
|
| 126 |
-
if (!currentPromptId) return;
|
| 127 |
-
|
| 128 |
-
// Collect edited field values
|
| 129 |
-
const edits = {};
|
| 130 |
-
["role", "style", "task", "input_format", "output_format"].forEach(key => {
|
| 131 |
-
const el = $(`field-${key}`);
|
| 132 |
-
if (el) edits[key] = el.value.trim();
|
| 133 |
-
});
|
| 134 |
-
const cEl = $("field-constraints");
|
| 135 |
-
if (cEl) edits.constraints = cEl.value.trim().split("\n").filter(Boolean);
|
| 136 |
-
const sEl = $("field-safety");
|
| 137 |
-
if (sEl) edits.safety = sEl.value.trim().split("\n").filter(Boolean);
|
| 138 |
-
|
| 139 |
-
const btn = $("btn-approve");
|
| 140 |
-
setLoading(btn, true);
|
| 141 |
-
try {
|
| 142 |
-
const data = await apiFetch("/api/approve", "POST", {
|
| 143 |
-
prompt_id: currentPromptId,
|
| 144 |
-
edits,
|
| 145 |
-
});
|
| 146 |
-
|
| 147 |
-
renderFinalizedPrompt(data.finalized_prompt);
|
| 148 |
-
hide(steps.manifest);
|
| 149 |
-
show(steps.finalized);
|
| 150 |
-
steps.finalized.scrollIntoView({ behavior: "smooth" });
|
| 151 |
-
toast("✅ Prompt approved and finalized!", "success");
|
| 152 |
-
} catch (e) {
|
| 153 |
-
toast(`Approval failed: ${e.message}`, "error");
|
| 154 |
-
} finally {
|
| 155 |
-
setLoading(btn, false);
|
| 156 |
-
}
|
| 157 |
-
});
|
| 158 |
-
|
| 159 |
-
// ── Render finalized prompt ────────────────────────────────────────
|
| 160 |
-
function renderFinalizedPrompt(sp) {
|
| 161 |
-
$("finalized-text").textContent = sp.raw_prompt_text;
|
| 162 |
-
$("finalized-json").textContent = JSON.stringify(sp, null, 2);
|
| 163 |
-
}
|
| 164 |
-
|
| 165 |
-
// ── Tabs ──────────────────────────────────────────────────────────
|
| 166 |
-
document.querySelectorAll(".tab").forEach(tab => {
|
| 167 |
-
tab.addEventListener("click", () => {
|
| 168 |
-
const target = tab.dataset.tab;
|
| 169 |
-
document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
|
| 170 |
-
tab.classList.add("active");
|
| 171 |
-
document.querySelectorAll(".tab-panel").forEach(p => hide(p));
|
| 172 |
-
show($(`tab-${target}`));
|
| 173 |
-
});
|
| 174 |
-
});
|
| 175 |
-
|
| 176 |
-
// ── Copy buttons ──────────────────────────────────────────────────
|
| 177 |
-
document.querySelectorAll(".btn-copy").forEach(btn => {
|
| 178 |
-
btn.addEventListener("click", () => {
|
| 179 |
-
const pre = $(btn.dataset.target);
|
| 180 |
-
if (!pre) return;
|
| 181 |
-
navigator.clipboard.writeText(pre.textContent).then(() => {
|
| 182 |
-
const orig = btn.textContent;
|
| 183 |
-
btn.textContent = "✓ Copied!";
|
| 184 |
-
setTimeout(() => (btn.textContent = orig), 1800);
|
| 185 |
-
});
|
| 186 |
-
});
|
| 187 |
});
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
const data = await apiFetch("/api/export", "POST", {
|
| 194 |
-
prompt_id: currentPromptId,
|
| 195 |
-
export_format: format,
|
| 196 |
-
});
|
| 197 |
-
const content = typeof data.data === "string" ? data.data : JSON.stringify(data.data, null, 2);
|
| 198 |
-
const ext = format === "text" ? "txt" : "json";
|
| 199 |
-
downloadFile(`prompt_${currentPromptId.slice(0,8)}.${ext}`, content);
|
| 200 |
-
toast(`⬇ Exported as .${ext}`, "success");
|
| 201 |
-
} catch (e) {
|
| 202 |
-
toast(`Export failed: ${e.message}`, "error");
|
| 203 |
-
}
|
| 204 |
-
}
|
| 205 |
-
|
| 206 |
-
$("btn-export-json").addEventListener("click", () => exportPrompt("json"));
|
| 207 |
-
$("btn-export-txt").addEventListener("click", () => exportPrompt("text"));
|
| 208 |
-
|
| 209 |
-
function downloadFile(filename, content) {
|
| 210 |
-
const a = document.createElement("a");
|
| 211 |
-
a.href = URL.createObjectURL(new Blob([content], { type: "text/plain" }));
|
| 212 |
-
a.download = filename;
|
| 213 |
-
a.click();
|
| 214 |
-
URL.revokeObjectURL(a.href);
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
// ── Refine ────────────────────────────────────────────────────────
|
| 218 |
-
$("btn-refine").addEventListener("click", () => {
|
| 219 |
-
show(steps.refine);
|
| 220 |
-
steps.refine.scrollIntoView({ behavior: "smooth" });
|
| 221 |
-
});
|
| 222 |
-
$("btn-cancel-refine").addEventListener("click", () => hide(steps.refine));
|
| 223 |
-
|
| 224 |
-
$("btn-submit-refine").addEventListener("click", async () => {
|
| 225 |
-
if (!currentPromptId) return;
|
| 226 |
-
const feedback = $("feedback").value.trim();
|
| 227 |
-
if (!feedback) { toast("Please enter feedback.", "error"); return; }
|
| 228 |
-
|
| 229 |
-
const btn = $("btn-submit-refine");
|
| 230 |
-
setLoading(btn, true);
|
| 231 |
-
try {
|
| 232 |
-
const data = await apiFetch("/api/refine", "POST", {
|
| 233 |
-
prompt_id: currentPromptId,
|
| 234 |
-
feedback,
|
| 235 |
-
provider: $("provider").value,
|
| 236 |
-
api_key: $("api-key").value.trim() || null,
|
| 237 |
-
});
|
| 238 |
-
|
| 239 |
-
currentPromptId = data.prompt_id;
|
| 240 |
-
renderManifest(data.manifest);
|
| 241 |
-
hide(steps.finalized);
|
| 242 |
-
hide(steps.refine);
|
| 243 |
-
show(steps.manifest);
|
| 244 |
-
$("feedback").value = "";
|
| 245 |
-
steps.manifest.scrollIntoView({ behavior: "smooth" });
|
| 246 |
-
toast(`🔁 Refined to v${data.manifest.version} — please re-approve.`, "success");
|
| 247 |
-
} catch (e) {
|
| 248 |
-
toast(`Refine failed: ${e.message}`, "error");
|
| 249 |
-
} finally {
|
| 250 |
-
setLoading(btn, false);
|
| 251 |
-
}
|
| 252 |
});
|
| 253 |
-
|
| 254 |
-
// ── Reset / New ───────────────────────────────────────────────────
|
| 255 |
-
function resetAll() {
|
| 256 |
-
currentPromptId = null;
|
| 257 |
-
$("instruction").value = "";
|
| 258 |
-
$("extra-context").value = "";
|
| 259 |
-
$("feedback").value = "";
|
| 260 |
-
Object.values(steps).forEach(hide);
|
| 261 |
-
show(steps.input);
|
| 262 |
-
window.scrollTo({ top: 0, behavior: "smooth" });
|
| 263 |
-
}
|
| 264 |
-
|
| 265 |
-
$("btn-reset").addEventListener("click", resetAll);
|
| 266 |
-
$("btn-new").addEventListener("click", resetAll);
|
| 267 |
-
|
| 268 |
-
// ── History ────────────────────────────────────────────────────────
|
| 269 |
-
$("btn-load-history").addEventListener("click", loadHistory);
|
| 270 |
-
|
| 271 |
-
async function loadHistory() {
|
| 272 |
-
try {
|
| 273 |
-
const data = await apiFetch("/api/history");
|
| 274 |
-
const tbody = $("history-body");
|
| 275 |
-
|
| 276 |
-
if (data.total === 0) {
|
| 277 |
-
tbody.innerHTML = `<tr><td colspan="6" class="muted center">No prompts generated yet.</td></tr>`;
|
| 278 |
-
return;
|
| 279 |
-
}
|
| 280 |
-
|
| 281 |
-
tbody.innerHTML = data.entries.map(e => `
|
| 282 |
-
<tr>
|
| 283 |
-
<td><code>${e.prompt_id.slice(0, 8)}…</code></td>
|
| 284 |
-
<td>${escapeHtml(e.instruction.slice(0, 60))}${e.instruction.length > 60 ? "…" : ""}</td>
|
| 285 |
-
<td>v${e.version}</td>
|
| 286 |
-
<td><span class="status-badge status-${e.status}">${e.status}</span></td>
|
| 287 |
-
<td>${new Date(e.created_at).toLocaleString()}</td>
|
| 288 |
-
<td>
|
| 289 |
-
<button class="btn-sm btn-secondary" onclick="loadPromptById('${e.prompt_id}')">Load</button>
|
| 290 |
-
<button class="btn-sm btn-secondary" onclick="deletePrompt('${e.prompt_id}')">🗑</button>
|
| 291 |
-
</td>
|
| 292 |
-
</tr>`).join("");
|
| 293 |
-
} catch (e) {
|
| 294 |
-
toast(`Could not load history: ${e.message}`, "error");
|
| 295 |
-
}
|
| 296 |
-
}
|
| 297 |
-
|
| 298 |
-
window.loadPromptById = async function(id) {
|
| 299 |
-
try {
|
| 300 |
-
const manifest = await apiFetch(`/api/history/${id}`);
|
| 301 |
-
currentPromptId = manifest.prompt_id;
|
| 302 |
-
renderManifest(manifest);
|
| 303 |
-
show(steps.manifest);
|
| 304 |
-
steps.manifest.scrollIntoView({ behavior: "smooth" });
|
| 305 |
-
toast("Manifest loaded from history.", "success");
|
| 306 |
-
} catch (e) {
|
| 307 |
-
toast(`Load failed: ${e.message}`, "error");
|
| 308 |
-
}
|
| 309 |
-
};
|
| 310 |
-
|
| 311 |
-
window.deletePrompt = async function(id) {
|
| 312 |
-
if (!confirm("Delete this prompt from history?")) return;
|
| 313 |
-
try {
|
| 314 |
-
await apiFetch(`/api/history/${id}`, "DELETE");
|
| 315 |
-
toast("Prompt deleted.", "success");
|
| 316 |
-
loadHistory();
|
| 317 |
-
} catch (e) {
|
| 318 |
-
toast(`Delete failed: ${e.message}`, "error");
|
| 319 |
-
}
|
| 320 |
-
};
|
| 321 |
-
|
| 322 |
-
// ── Helpers ────────────────────────────────────────────────────────
|
| 323 |
-
function escapeHtml(str) {
|
| 324 |
-
return String(str)
|
| 325 |
-
.replace(/&/g, "&")
|
| 326 |
-
.replace(/</g, "<")
|
| 327 |
-
.replace(/>/g, ">")
|
| 328 |
-
.replace(/"/g, """);
|
| 329 |
-
}
|
| 330 |
-
|
| 331 |
-
// ── Init ──────────────────────────────────────────────────────────
|
| 332 |
-
loadHistory();
|
|
|
|
| 1 |
/**
|
| 2 |
+
* PromptForge — client.js v2.0
|
| 3 |
+
* Custom cursor, toast system, step progress, full API flow.
|
| 4 |
*/
|
| 5 |
+
const API = "";
|
| 6 |
+
let currentPromptId = null;
|
|
|
|
|
|
|
|
|
|
| 7 |
const $ = id => document.getElementById(id);
|
| 8 |
+
const show = el => el?.classList.remove("hidden");
|
| 9 |
+
const hide = el => el?.classList.add("hidden");
|
| 10 |
+
|
| 11 |
+
/* Custom Cursor */
|
| 12 |
+
const dot = $("cursor-dot"), ring = $("cursor-ring");
|
| 13 |
+
let mx=0,my=0,rx=0,ry=0;
|
| 14 |
+
document.addEventListener("mousemove", e => {
|
| 15 |
+
mx=e.clientX; my=e.clientY;
|
| 16 |
+
dot.style.left=mx+"px"; dot.style.top=my+"px";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
});
|
| 18 |
+
(function animRing(){ rx+=(mx-rx)*.14; ry+=(my-ry)*.14;
|
| 19 |
+
ring.style.left=rx+"px"; ring.style.top=ry+"px"; requestAnimationFrame(animRing); })();
|
| 20 |
+
document.querySelectorAll("button,a,select,textarea,input,summary").forEach(el=>{
|
| 21 |
+
el.addEventListener("mouseenter",()=>{ dot.style.width="14px";dot.style.height="14px";dot.style.background="var(--magenta)";ring.style.width="54px";ring.style.height="54px";ring.style.borderColor="rgba(247,37,133,.45)"; });
|
| 22 |
+
el.addEventListener("mouseleave",()=>{ dot.style.width="8px";dot.style.height="8px";dot.style.background="var(--cyan)";ring.style.width="36px";ring.style.height="36px";ring.style.borderColor="rgba(0,245,212,.45)"; });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/index.html
CHANGED
|
@@ -1,54 +1,129 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
-
<meta charset="UTF-8"
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"
|
| 6 |
-
<title>PromptForge —
|
| 7 |
-
<link rel="stylesheet" href="/static/style.css"
|
|
|
|
| 8 |
</head>
|
| 9 |
<body>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
<header>
|
| 11 |
<div class="header-inner">
|
| 12 |
-
<div class="logo">
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
</div>
|
| 15 |
</header>
|
| 16 |
|
| 17 |
<main>
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
<section id="step-input" class="card">
|
| 20 |
-
<
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
<
|
| 23 |
-
|
| 24 |
-
|
|
|
|
| 25 |
|
| 26 |
-
<
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
<div class="
|
| 31 |
-
<div class="field
|
| 32 |
-
<label
|
| 33 |
<select id="provider">
|
| 34 |
-
<option value="none">
|
| 35 |
-
<option value="google">Google Gemini</option>
|
| 36 |
-
<option value="huggingface">Hugging Face</option>
|
| 37 |
</select>
|
|
|
|
| 38 |
</div>
|
| 39 |
-
<div class="field
|
| 40 |
-
<label
|
| 41 |
-
<input id="api-key"
|
|
|
|
| 42 |
</div>
|
| 43 |
</div>
|
| 44 |
|
| 45 |
-
<
|
|
|
|
|
|
|
| 46 |
</section>
|
| 47 |
|
| 48 |
-
<!-- ──
|
| 49 |
-
<section id="step-manifest" class="card hidden">
|
| 50 |
-
<
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
<div id="manifest-grid" class="manifest-grid"></div>
|
| 54 |
|
|
@@ -59,71 +134,108 @@
|
|
| 59 |
|
| 60 |
<div class="action-row">
|
| 61 |
<button id="btn-approve" class="btn-primary">✅ Approve & Finalize</button>
|
| 62 |
-
<button id="btn-reset"
|
| 63 |
</div>
|
| 64 |
</section>
|
| 65 |
|
| 66 |
-
<!-- ──
|
| 67 |
-
<section id="step-finalized" class="card hidden">
|
| 68 |
-
<
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
<div class="tab-bar">
|
| 72 |
<button class="tab active" data-tab="text">📄 Plain Text</button>
|
| 73 |
-
<button class="tab"
|
| 74 |
</div>
|
| 75 |
|
| 76 |
<div id="tab-text" class="tab-panel">
|
| 77 |
<pre id="finalized-text"></pre>
|
| 78 |
-
<button class="btn-copy" data-target="finalized-text">📋 Copy</button>
|
| 79 |
</div>
|
| 80 |
<div id="tab-json" class="tab-panel hidden">
|
| 81 |
<pre id="finalized-json"></pre>
|
| 82 |
-
<button class="btn-copy" data-target="finalized-json">📋 Copy</button>
|
| 83 |
</div>
|
| 84 |
|
|
|
|
|
|
|
| 85 |
<div class="action-row">
|
| 86 |
<button id="btn-export-json" class="btn-secondary">⬇ Export JSON</button>
|
| 87 |
-
<button id="btn-export-txt"
|
| 88 |
-
<button id="btn-refine"
|
| 89 |
-
<button id="btn-new"
|
| 90 |
</div>
|
| 91 |
</section>
|
| 92 |
|
| 93 |
-
<!-- ──
|
| 94 |
-
<section id="step-refine" class="card hidden">
|
| 95 |
-
<
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
<div class="action-row">
|
| 100 |
<button id="btn-submit-refine" class="btn-primary">🔁 Submit Refinement</button>
|
| 101 |
<button id="btn-cancel-refine" class="btn-secondary">Cancel</button>
|
| 102 |
</div>
|
| 103 |
</section>
|
| 104 |
|
| 105 |
-
<!-- ── History ─────────────────────────────────────────────────
|
| 106 |
<section id="section-history" class="card">
|
| 107 |
-
<
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
<
|
| 114 |
-
<
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
</section>
|
| 118 |
-
</main>
|
| 119 |
|
| 120 |
-
<
|
| 121 |
-
<div id="toast" class="toast hidden"></div>
|
| 122 |
|
|
|
|
| 123 |
<footer>
|
| 124 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
</footer>
|
| 126 |
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
</body>
|
| 129 |
</html>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
+
<meta charset="UTF-8"/>
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 6 |
+
<title>PromptForge — Neural Prompt Generator</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/style.css"/>
|
| 8 |
+
<meta name="description" content="Transform raw instructions into structured, ready-to-use prompts for Google AI Studio."/>
|
| 9 |
</head>
|
| 10 |
<body>
|
| 11 |
+
|
| 12 |
+
<!-- ── Custom Cursor ─────────────────────────────────────────────── -->
|
| 13 |
+
<div id="cursor-dot"></div>
|
| 14 |
+
<div id="cursor-ring"></div>
|
| 15 |
+
|
| 16 |
+
<!-- ── Background Layers ─────────────────────────────────────────── -->
|
| 17 |
+
<div class="bg-mesh"></div>
|
| 18 |
+
<div class="bg-grid"></div>
|
| 19 |
+
<div class="orb orb-1"></div>
|
| 20 |
+
<div class="orb orb-2"></div>
|
| 21 |
+
<div class="orb orb-3"></div>
|
| 22 |
+
|
| 23 |
+
<div id="app">
|
| 24 |
+
|
| 25 |
+
<!-- ── Header ──────────────────────────────────────────────────── -->
|
| 26 |
<header>
|
| 27 |
<div class="header-inner">
|
| 28 |
+
<div class="logo-group">
|
| 29 |
+
<div class="logo-icon">⚙️</div>
|
| 30 |
+
<span class="logo-text">PromptForge</span>
|
| 31 |
+
<span class="logo-tag">v2.0</span>
|
| 32 |
+
</div>
|
| 33 |
+
<div class="header-meta">
|
| 34 |
+
<div class="status-pill">
|
| 35 |
+
<div class="status-dot"></div>
|
| 36 |
+
<span>ONLINE</span>
|
| 37 |
+
</div>
|
| 38 |
+
<a class="nav-link" href="/docs" target="_blank">⚡ API Docs</a>
|
| 39 |
+
</div>
|
| 40 |
</div>
|
| 41 |
</header>
|
| 42 |
|
| 43 |
<main>
|
| 44 |
+
|
| 45 |
+
<!-- ── Step Progress ─────────────────────────────────────────── -->
|
| 46 |
+
<div class="step-progress">
|
| 47 |
+
<div class="prog-step active" id="pstep-1">
|
| 48 |
+
<div class="prog-node">1</div>
|
| 49 |
+
<div class="prog-label">Input</div>
|
| 50 |
+
</div>
|
| 51 |
+
<div class="prog-line" id="pline-1"></div>
|
| 52 |
+
<div class="prog-step" id="pstep-2">
|
| 53 |
+
<div class="prog-node">2</div>
|
| 54 |
+
<div class="prog-label">Review</div>
|
| 55 |
+
</div>
|
| 56 |
+
<div class="prog-line" id="pline-2"></div>
|
| 57 |
+
<div class="prog-step" id="pstep-3">
|
| 58 |
+
<div class="prog-node">3</div>
|
| 59 |
+
<div class="prog-label">Finalize</div>
|
| 60 |
+
</div>
|
| 61 |
+
<div class="prog-line" id="pline-3"></div>
|
| 62 |
+
<div class="prog-step" id="pstep-4">
|
| 63 |
+
<div class="prog-node">4</div>
|
| 64 |
+
<div class="prog-label">Export</div>
|
| 65 |
+
</div>
|
| 66 |
+
<div class="prog-line" id="pline-4"></div>
|
| 67 |
+
<div class="prog-step" id="pstep-5">
|
| 68 |
+
<div class="prog-node">5</div>
|
| 69 |
+
<div class="prog-label">Refine</div>
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<!-- ── STEP 1: Input ─────────────────────────────────────────── -->
|
| 74 |
<section id="step-input" class="card">
|
| 75 |
+
<div class="card-header">
|
| 76 |
+
<h2>Enter Your Instruction</h2>
|
| 77 |
+
<span class="step-badge">STEP 01</span>
|
| 78 |
+
</div>
|
| 79 |
|
| 80 |
+
<div class="info-banner">
|
| 81 |
+
<span class="info-icon">💡</span>
|
| 82 |
+
<span>Describe any task in plain English. PromptForge will transform it into a fully structured prompt ready for Google AI Studio.</span>
|
| 83 |
+
</div>
|
| 84 |
|
| 85 |
+
<div class="field">
|
| 86 |
+
<label><span class="lbl-dot"></span> Instruction</label>
|
| 87 |
+
<textarea id="instruction" rows="5"
|
| 88 |
+
placeholder="e.g. Generate a TypeScript React component with TailwindCSS and unit tests."></textarea>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
<div class="field">
|
| 92 |
+
<label><span class="lbl-dot"></span> Additional Context <span class="lbl-opt">optional</span></label>
|
| 93 |
+
<textarea id="extra-context" rows="2"
|
| 94 |
+
placeholder="e.g. Support dark mode, must be accessible (WCAG AA), use React hooks only."></textarea>
|
| 95 |
+
</div>
|
| 96 |
|
| 97 |
+
<div class="input-grid">
|
| 98 |
+
<div class="field">
|
| 99 |
+
<label><span class="lbl-dot"></span> AI Enhancement</label>
|
| 100 |
<select id="provider">
|
| 101 |
+
<option value="none">⚡ Local Engine (no API)</option>
|
| 102 |
+
<option value="google">🌐 Google Gemini</option>
|
| 103 |
+
<option value="huggingface">🤗 Hugging Face</option>
|
| 104 |
</select>
|
| 105 |
+
<div class="field-note">Optionally enhance via AI after generation.</div>
|
| 106 |
</div>
|
| 107 |
+
<div class="field hidden" id="api-key-group">
|
| 108 |
+
<label><span class="lbl-dot"></span> API Key <span class="lbl-opt">never stored</span></label>
|
| 109 |
+
<input type="password" id="api-key" placeholder="Paste your API key here…"/>
|
| 110 |
+
<div class="field-note">Transmitted securely, never logged.</div>
|
| 111 |
</div>
|
| 112 |
</div>
|
| 113 |
|
| 114 |
+
<div class="action-row">
|
| 115 |
+
<button id="btn-generate" class="btn-primary">⚡ Generate Prompt Manifest</button>
|
| 116 |
+
</div>
|
| 117 |
</section>
|
| 118 |
|
| 119 |
+
<!-- ── STEP 2: Manifest Review ───────────────────────────────── -->
|
| 120 |
+
<section id="step-manifest" class="card hidden fade-in">
|
| 121 |
+
<div class="card-header">
|
| 122 |
+
<h2>Review & Edit Manifest</h2>
|
| 123 |
+
<span class="step-badge">STEP 02</span>
|
| 124 |
+
</div>
|
| 125 |
+
|
| 126 |
+
<p class="muted">Every field is editable. Tweak anything before approving — the final prompt will regenerate automatically.</p>
|
| 127 |
|
| 128 |
<div id="manifest-grid" class="manifest-grid"></div>
|
| 129 |
|
|
|
|
| 134 |
|
| 135 |
<div class="action-row">
|
| 136 |
<button id="btn-approve" class="btn-primary">✅ Approve & Finalize</button>
|
| 137 |
+
<button id="btn-reset" class="btn-secondary">↩ Start Over</button>
|
| 138 |
</div>
|
| 139 |
</section>
|
| 140 |
|
| 141 |
+
<!-- ── STEP 3: Finalized Prompt ──────────────────────────────── -->
|
| 142 |
+
<section id="step-finalized" class="card hidden fade-in">
|
| 143 |
+
<div class="card-header">
|
| 144 |
+
<h2>Finalized Prompt</h2>
|
| 145 |
+
<span class="step-badge">STEP 03</span>
|
| 146 |
+
</div>
|
| 147 |
+
|
| 148 |
+
<p class="muted">Your structured prompt is ready. Copy it directly into Google AI Studio or export it below.</p>
|
| 149 |
|
| 150 |
<div class="tab-bar">
|
| 151 |
<button class="tab active" data-tab="text">📄 Plain Text</button>
|
| 152 |
+
<button class="tab" data-tab="json">{ } JSON</button>
|
| 153 |
</div>
|
| 154 |
|
| 155 |
<div id="tab-text" class="tab-panel">
|
| 156 |
<pre id="finalized-text"></pre>
|
| 157 |
+
<button class="btn-copy" data-target="finalized-text">📋 Copy to Clipboard</button>
|
| 158 |
</div>
|
| 159 |
<div id="tab-json" class="tab-panel hidden">
|
| 160 |
<pre id="finalized-json"></pre>
|
| 161 |
+
<button class="btn-copy" data-target="finalized-json">📋 Copy to Clipboard</button>
|
| 162 |
</div>
|
| 163 |
|
| 164 |
+
<div class="divider"></div>
|
| 165 |
+
|
| 166 |
<div class="action-row">
|
| 167 |
<button id="btn-export-json" class="btn-secondary">⬇ Export JSON</button>
|
| 168 |
+
<button id="btn-export-txt" class="btn-secondary">⬇ Export Text</button>
|
| 169 |
+
<button id="btn-refine" class="btn-secondary">🔁 Refine with Feedback</button>
|
| 170 |
+
<button id="btn-new" class="btn-primary">➕ New Prompt</button>
|
| 171 |
</div>
|
| 172 |
</section>
|
| 173 |
|
| 174 |
+
<!-- ── STEP 5: Refine ────────────────────────────────────────── -->
|
| 175 |
+
<section id="step-refine" class="card hidden fade-in">
|
| 176 |
+
<div class="card-header">
|
| 177 |
+
<h2>Refine Prompt</h2>
|
| 178 |
+
<span class="step-badge">STEP 05</span>
|
| 179 |
+
</div>
|
| 180 |
+
<p class="muted">Describe what to change. PromptForge will generate a new version (v+1) of the manifest for re-approval.</p>
|
| 181 |
+
|
| 182 |
+
<div class="field">
|
| 183 |
+
<label><span class="lbl-dot"></span> Your Feedback</label>
|
| 184 |
+
<textarea id="feedback" rows="3"
|
| 185 |
+
placeholder="e.g. Add ARIA labels, keyboard navigation, and a dark-mode variant prop."></textarea>
|
| 186 |
+
</div>
|
| 187 |
+
|
| 188 |
<div class="action-row">
|
| 189 |
<button id="btn-submit-refine" class="btn-primary">🔁 Submit Refinement</button>
|
| 190 |
<button id="btn-cancel-refine" class="btn-secondary">Cancel</button>
|
| 191 |
</div>
|
| 192 |
</section>
|
| 193 |
|
| 194 |
+
<!-- ── History ───────────────────────────────────────────────── -->
|
| 195 |
<section id="section-history" class="card">
|
| 196 |
+
<div class="card-header">
|
| 197 |
+
<h2>Prompt History</h2>
|
| 198 |
+
<button id="btn-load-history" class="btn-secondary btn-sm">↺ Refresh</button>
|
| 199 |
+
</div>
|
| 200 |
+
|
| 201 |
+
<div class="table-wrap">
|
| 202 |
+
<table id="history-table">
|
| 203 |
+
<thead>
|
| 204 |
+
<tr>
|
| 205 |
+
<th>ID</th>
|
| 206 |
+
<th>Instruction</th>
|
| 207 |
+
<th>Ver</th>
|
| 208 |
+
<th>Status</th>
|
| 209 |
+
<th>Date</th>
|
| 210 |
+
<th>Actions</th>
|
| 211 |
+
</tr>
|
| 212 |
+
</thead>
|
| 213 |
+
<tbody id="history-body">
|
| 214 |
+
<tr><td class="empty-msg" colspan="6">Click ↺ Refresh to load history.</td></tr>
|
| 215 |
+
</tbody>
|
| 216 |
+
</table>
|
| 217 |
+
</div>
|
| 218 |
</section>
|
|
|
|
| 219 |
|
| 220 |
+
</main>
|
|
|
|
| 221 |
|
| 222 |
+
<!-- ── Footer ──────────────────────────────────────────────────── -->
|
| 223 |
<footer>
|
| 224 |
+
<div class="footer-inner">
|
| 225 |
+
<span class="footer-copy">© 2025 PromptForge — Neural Prompt Generator</span>
|
| 226 |
+
<div class="footer-links">
|
| 227 |
+
<a href="/docs" target="_blank">API Docs</a>
|
| 228 |
+
<a href="/redoc" target="_blank">ReDoc</a>
|
| 229 |
+
<a href="/health" target="_blank">Health</a>
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
</footer>
|
| 233 |
|
| 234 |
+
</div><!-- #app -->
|
| 235 |
+
|
| 236 |
+
<!-- ── Toast container ─────────────────────────────────────────── -->
|
| 237 |
+
<div id="toast-container"></div>
|
| 238 |
+
|
| 239 |
+
<script src="/static/client.js"></script>
|
| 240 |
</body>
|
| 241 |
</html>
|
frontend/style.css
CHANGED
|
@@ -1,284 +1,199 @@
|
|
| 1 |
-
/*
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
-
--bg: #0f1117;
|
| 5 |
-
--surface: #1a1d27;
|
| 6 |
-
--surface2: #22263a;
|
| 7 |
-
--border: #2e3350;
|
| 8 |
-
--accent: #5b8df6;
|
| 9 |
-
--accent-hover: #3d6fe0;
|
| 10 |
-
--success: #34d399;
|
| 11 |
-
--warning: #fbbf24;
|
| 12 |
-
--danger: #f87171;
|
| 13 |
-
--text: #e4e7f0;
|
| 14 |
-
--text-muted: #7c84a3;
|
| 15 |
-
--radius: 10px;
|
| 16 |
-
--font: 'Inter', system-ui, sans-serif;
|
| 17 |
-
--mono: 'Fira Code', 'Cascadia Code', monospace;
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 21 |
-
|
| 22 |
-
body {
|
| 23 |
-
background: var(--bg);
|
| 24 |
-
color: var(--text);
|
| 25 |
-
font-family: var(--font);
|
| 26 |
-
font-size: 15px;
|
| 27 |
-
line-height: 1.6;
|
| 28 |
-
min-height: 100vh;
|
| 29 |
-
}
|
| 30 |
-
|
| 31 |
-
/* ── Header ──────────────────────────────────────────────────── */
|
| 32 |
-
header {
|
| 33 |
-
background: linear-gradient(135deg, #1e2336 0%, #151824 100%);
|
| 34 |
-
border-bottom: 1px solid var(--border);
|
| 35 |
-
padding: 14px 24px;
|
| 36 |
-
position: sticky;
|
| 37 |
-
top: 0;
|
| 38 |
-
z-index: 100;
|
| 39 |
-
backdrop-filter: blur(6px);
|
| 40 |
-
}
|
| 41 |
-
.header-inner { display: flex; align-items: center; gap: 16px; max-width: 960px; margin: 0 auto; }
|
| 42 |
-
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.3px; }
|
| 43 |
-
.tagline { color: var(--text-muted); font-size: 0.85rem; }
|
| 44 |
-
|
| 45 |
-
/* ── Main layout ──────────────────────────────────────────────── */
|
| 46 |
-
main {
|
| 47 |
-
max-width: 960px;
|
| 48 |
-
margin: 32px auto;
|
| 49 |
-
padding: 0 20px;
|
| 50 |
-
display: flex;
|
| 51 |
-
flex-direction: column;
|
| 52 |
-
gap: 24px;
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
/* ── Cards ──────────────────────────────────────────────────────── */
|
| 56 |
-
.card {
|
| 57 |
-
background: var(--surface);
|
| 58 |
-
border: 1px solid var(--border);
|
| 59 |
-
border-radius: var(--radius);
|
| 60 |
-
padding: 28px 30px;
|
| 61 |
-
}
|
| 62 |
-
.card h2 {
|
| 63 |
-
font-size: 1.05rem;
|
| 64 |
-
font-weight: 600;
|
| 65 |
-
margin-bottom: 18px;
|
| 66 |
-
display: flex;
|
| 67 |
-
align-items: center;
|
| 68 |
-
gap: 10px;
|
| 69 |
-
}
|
| 70 |
-
.step-badge {
|
| 71 |
-
background: var(--accent);
|
| 72 |
-
color: #fff;
|
| 73 |
-
font-size: 0.65rem;
|
| 74 |
-
font-weight: 700;
|
| 75 |
-
letter-spacing: 0.5px;
|
| 76 |
-
padding: 3px 8px;
|
| 77 |
-
border-radius: 20px;
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
/* ── Form elements ─────────────────────────────────────────────── */
|
| 81 |
-
label {
|
| 82 |
-
display: block;
|
| 83 |
-
margin-bottom: 6px;
|
| 84 |
-
font-size: 0.875rem;
|
| 85 |
-
color: var(--text-muted);
|
| 86 |
-
font-weight: 500;
|
| 87 |
-
}
|
| 88 |
-
textarea, input[type="password"], select {
|
| 89 |
-
width: 100%;
|
| 90 |
-
background: var(--surface2);
|
| 91 |
-
border: 1px solid var(--border);
|
| 92 |
-
border-radius: 7px;
|
| 93 |
-
color: var(--text);
|
| 94 |
-
font-family: var(--font);
|
| 95 |
-
font-size: 0.9rem;
|
| 96 |
-
padding: 10px 14px;
|
| 97 |
-
resize: vertical;
|
| 98 |
-
transition: border-color 0.2s;
|
| 99 |
-
margin-bottom: 16px;
|
| 100 |
-
}
|
| 101 |
-
textarea:focus, input:focus, select:focus {
|
| 102 |
-
outline: none;
|
| 103 |
-
border-color: var(--accent);
|
| 104 |
-
}
|
| 105 |
-
select option { background: var(--surface2); }
|
| 106 |
-
|
| 107 |
-
.row-two {
|
| 108 |
-
display: grid;
|
| 109 |
-
grid-template-columns: 1fr 1fr;
|
| 110 |
-
gap: 16px;
|
| 111 |
-
}
|
| 112 |
-
.field-group { display: flex; flex-direction: column; }
|
| 113 |
-
.field-group label { margin-bottom: 6px; }
|
| 114 |
-
.field-group textarea,
|
| 115 |
-
.field-group input,
|
| 116 |
-
.field-group select { margin-bottom: 0; }
|
| 117 |
-
|
| 118 |
-
/* ── Buttons ────────────────────────────────────────────────────── */
|
| 119 |
-
.btn-primary, .btn-secondary {
|
| 120 |
-
display: inline-flex;
|
| 121 |
-
align-items: center;
|
| 122 |
-
gap: 6px;
|
| 123 |
-
border: none;
|
| 124 |
-
border-radius: 7px;
|
| 125 |
-
cursor: pointer;
|
| 126 |
-
font-family: var(--font);
|
| 127 |
-
font-size: 0.9rem;
|
| 128 |
-
font-weight: 600;
|
| 129 |
-
padding: 10px 20px;
|
| 130 |
-
transition: background 0.2s, transform 0.1s;
|
| 131 |
-
}
|
| 132 |
-
.btn-primary {
|
| 133 |
-
background: var(--accent);
|
| 134 |
-
color: #fff;
|
| 135 |
-
}
|
| 136 |
-
.btn-primary:hover { background: var(--accent-hover); transform: translateY(-1px); }
|
| 137 |
-
.btn-primary:active { transform: translateY(0); }
|
| 138 |
-
|
| 139 |
-
.btn-secondary {
|
| 140 |
-
background: var(--surface2);
|
| 141 |
-
color: var(--text);
|
| 142 |
-
border: 1px solid var(--border);
|
| 143 |
-
}
|
| 144 |
-
.btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
|
| 145 |
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
.
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
font-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
}
|
| 175 |
-
.
|
| 176 |
-
.
|
| 177 |
-
.
|
| 178 |
-
.
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
}
|
| 184 |
-
|
| 185 |
-
/*
|
| 186 |
-
|
| 187 |
-
.
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
}
|
| 198 |
-
.
|
| 199 |
-
|
| 200 |
-
/*
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
}
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
.
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
}
|
| 239 |
-
|
| 240 |
-
.
|
| 241 |
-
.
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
.
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
.
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
}
|
| 264 |
-
.
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ═══════════════════════════════════════════════════════════════
|
| 2 |
+
PROMPTFORGE — Neural Forge UI v2.0
|
| 3 |
+
Aesthetic: Deep Space Cyberpunk + Glassmorphism
|
| 4 |
+
═══════════════════════════════════════════════════════════════ */
|
| 5 |
|
| 6 |
+
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=DM+Mono:ital,wght@0,300;0,400;0,500;1,400&family=Outfit:wght@300;400;500;600&display=swap');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
+
:root {
|
| 9 |
+
--void:#020408; --deep:#060c18;
|
| 10 |
+
--surface-1:rgba(8,15,32,.85); --surface-2:rgba(12,22,48,.75); --surface-3:rgba(16,28,58,.6);
|
| 11 |
+
--cyan:#00f5d4; --cyan-dim:rgba(0,245,212,.12); --cyan-glow:rgba(0,245,212,.4);
|
| 12 |
+
--magenta:#f72585; --magenta-dim:rgba(247,37,133,.12);
|
| 13 |
+
--violet:#7209b7; --violet-dim:rgba(114,9,183,.18); --gold:#ffd60a;
|
| 14 |
+
--text:#e8edf8; --text-soft:#a8b4cc; --text-muted:#5a6a8a; --text-dim:#3a4a6a;
|
| 15 |
+
--border:rgba(0,245,212,.1); --border-hot:rgba(0,245,212,.32);
|
| 16 |
+
--font-display:'Syne',sans-serif; --font-body:'Outfit',sans-serif; --font-mono:'DM Mono',monospace;
|
| 17 |
+
--radius-sm:8px; --radius-md:14px; --radius-lg:20px;
|
| 18 |
+
--shadow-card:0 0 0 1px rgba(0,245,212,.07),0 24px 64px rgba(0,0,0,.65),0 4px 16px rgba(0,0,0,.4);
|
| 19 |
+
--shadow-hover:0 0 0 1px rgba(0,245,212,.22),0 32px 80px rgba(0,0,0,.7),0 0 40px rgba(0,245,212,.05);
|
| 20 |
+
--glow-cyan:0 0 24px rgba(0,245,212,.55),0 0 60px rgba(0,245,212,.2);
|
| 21 |
+
}
|
| 22 |
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
| 23 |
+
html{scroll-behavior:smooth;scrollbar-width:thin;scrollbar-color:var(--cyan-dim) transparent}
|
| 24 |
+
::-webkit-scrollbar{width:4px}::-webkit-scrollbar-thumb{background:var(--cyan-dim);border-radius:4px}
|
| 25 |
+
body{background:var(--void);color:var(--text);font-family:var(--font-body);font-size:15px;line-height:1.65;min-height:100vh;overflow-x:hidden;cursor:none}
|
| 26 |
+
@media(max-width:680px){body{cursor:auto}button{cursor:pointer!important}}
|
| 27 |
+
|
| 28 |
+
/* Cursor */
|
| 29 |
+
#cursor-dot{position:fixed;width:8px;height:8px;background:var(--cyan);border-radius:50%;pointer-events:none;z-index:9999;transform:translate(-50%,-50%);transition:width .2s,height .2s,background .2s;box-shadow:var(--glow-cyan)}
|
| 30 |
+
#cursor-ring{position:fixed;width:36px;height:36px;border:1px solid rgba(0,245,212,.45);border-radius:50%;pointer-events:none;z-index:9998;transform:translate(-50%,-50%);transition:all .1s ease}
|
| 31 |
+
@media(max-width:680px){#cursor-dot,#cursor-ring{display:none}}
|
| 32 |
+
|
| 33 |
+
/* Backgrounds */
|
| 34 |
+
.bg-mesh{position:fixed;inset:0;z-index:0;pointer-events:none;background:radial-gradient(ellipse 80% 50% at 15% 15%,rgba(0,245,212,.065) 0%,transparent 60%),radial-gradient(ellipse 60% 40% at 85% 85%,rgba(247,37,133,.05) 0%,transparent 60%),radial-gradient(ellipse 50% 60% at 60% 5%,rgba(114,9,183,.04) 0%,transparent 55%);animation:meshAnim 22s ease-in-out infinite alternate}
|
| 35 |
+
@keyframes meshAnim{0%{filter:hue-rotate(0deg) brightness(1)}50%{filter:hue-rotate(18deg) brightness(1.08)}100%{filter:hue-rotate(-8deg) brightness(.96)}}
|
| 36 |
+
.bg-grid{position:fixed;inset:0;z-index:0;pointer-events:none;background-image:linear-gradient(rgba(0,245,212,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(0,245,212,.025) 1px,transparent 1px);background-size:60px 60px;mask-image:radial-gradient(ellipse 100% 80% at 50% 0%,black 30%,transparent 75%)}
|
| 37 |
+
.orb{position:fixed;border-radius:50%;filter:blur(110px);pointer-events:none;z-index:0;animation:orbFloat 28s ease-in-out infinite}
|
| 38 |
+
.orb-1{width:700px;height:700px;background:radial-gradient(circle,rgba(0,245,212,.055) 0%,transparent 70%);top:-250px;left:-250px;animation-duration:32s}
|
| 39 |
+
.orb-2{width:500px;height:500px;background:radial-gradient(circle,rgba(247,37,133,.045) 0%,transparent 70%);bottom:-150px;right:-150px;animation-duration:24s;animation-delay:-12s}
|
| 40 |
+
.orb-3{width:400px;height:400px;background:radial-gradient(circle,rgba(114,9,183,.038) 0%,transparent 70%);top:45%;left:50%;animation-duration:36s;animation-delay:-20s}
|
| 41 |
+
@keyframes orbFloat{0%,100%{transform:translate(0,0) scale(1)}33%{transform:translate(70px,-50px) scale(1.12)}66%{transform:translate(-50px,70px) scale(.9)}}
|
| 42 |
+
|
| 43 |
+
/* Layout */
|
| 44 |
+
#app{position:relative;z-index:1;min-height:100vh;display:flex;flex-direction:column}
|
| 45 |
+
main{max-width:900px;width:100%;margin:0 auto;padding:20px 20px 80px;display:flex;flex-direction:column;gap:20px;flex:1}
|
| 46 |
+
|
| 47 |
+
/* Header */
|
| 48 |
+
header{position:sticky;top:0;z-index:100;backdrop-filter:blur(28px) saturate(180%);-webkit-backdrop-filter:blur(28px) saturate(180%);background:rgba(2,4,8,.88);border-bottom:1px solid rgba(0,245,212,.07)}
|
| 49 |
+
.header-inner{max-width:900px;margin:0 auto;padding:13px 20px;display:flex;align-items:center;justify-content:space-between;gap:16px}
|
| 50 |
+
.logo-group{display:flex;align-items:center;gap:12px}
|
| 51 |
+
.logo-icon{width:40px;height:40px;flex-shrink:0;background:linear-gradient(135deg,var(--cyan),var(--violet));border-radius:11px;display:grid;place-items:center;font-size:18px;box-shadow:0 0 22px rgba(0,245,212,.28);animation:logoPulse 4s ease-in-out infinite}
|
| 52 |
+
@keyframes logoPulse{0%,100%{box-shadow:0 0 22px rgba(0,245,212,.28)}50%{box-shadow:0 0 40px rgba(0,245,212,.5),0 0 80px rgba(0,245,212,.1)}}
|
| 53 |
+
.logo-text{font-family:var(--font-display);font-size:1.25rem;font-weight:800;letter-spacing:-.3px;background:linear-gradient(90deg,var(--cyan),#b8f0e6);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
| 54 |
+
.logo-tag{font-family:var(--font-mono);font-size:.6rem;color:var(--cyan);background:var(--cyan-dim);border:1px solid rgba(0,245,212,.18);padding:2px 8px;border-radius:20px;letter-spacing:1px}
|
| 55 |
+
.header-meta{display:flex;align-items:center;gap:14px}
|
| 56 |
+
.status-pill{display:flex;align-items:center;gap:7px;font-family:var(--font-mono);font-size:.72rem;color:var(--text-muted);background:rgba(0,245,212,.04);border:1px solid var(--border);padding:5px 12px;border-radius:20px}
|
| 57 |
+
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--cyan);box-shadow:0 0 8px var(--cyan);animation:blink 2.2s ease-in-out infinite}
|
| 58 |
+
@keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}
|
| 59 |
+
.nav-link{font-family:var(--font-mono);font-size:.72rem;color:var(--text-muted);text-decoration:none;display:flex;align-items:center;gap:5px;padding:5px 12px;border:1px solid var(--border);border-radius:6px;transition:all .2s}
|
| 60 |
+
.nav-link:hover{color:var(--cyan);border-color:var(--border-hot);background:var(--cyan-dim)}
|
| 61 |
+
|
| 62 |
+
/* Step Progress */
|
| 63 |
+
.step-progress{display:flex;align-items:center;justify-content:center;padding:18px 0 2px;gap:0}
|
| 64 |
+
.prog-step{display:flex;flex-direction:column;align-items:center;gap:5px}
|
| 65 |
+
.prog-node{width:30px;height:30px;border-radius:50%;border:1px solid var(--border);background:var(--surface-1);display:grid;place-items:center;font-family:var(--font-mono);font-size:.7rem;color:var(--text-muted);transition:all .4s cubic-bezier(.34,1.56,.64,1);position:relative;z-index:1}
|
| 66 |
+
.prog-step.active .prog-node{border-color:var(--cyan);background:var(--cyan-dim);color:var(--cyan);box-shadow:0 0 18px rgba(0,245,212,.4);transform:scale(1.18)}
|
| 67 |
+
.prog-step.done .prog-node{border-color:var(--cyan);background:var(--cyan);color:var(--void);font-weight:700}
|
| 68 |
+
.prog-label{font-size:.6rem;color:var(--text-dim);font-family:var(--font-mono);letter-spacing:.5px;text-transform:uppercase}
|
| 69 |
+
.prog-step.active .prog-label{color:var(--cyan)}.prog-step.done .prog-label{color:var(--text-muted)}
|
| 70 |
+
.prog-line{width:55px;height:1px;background:var(--border);margin-bottom:20px;position:relative;overflow:hidden}
|
| 71 |
+
.prog-line::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,var(--cyan),var(--magenta));transform:scaleX(0);transform-origin:left;transition:transform .6s ease}
|
| 72 |
+
.prog-line.filled::after{transform:scaleX(1)}
|
| 73 |
+
|
| 74 |
+
/* Cards */
|
| 75 |
+
.card{backdrop-filter:blur(20px) saturate(160%);-webkit-backdrop-filter:blur(20px) saturate(160%);background:var(--surface-1);border:1px solid var(--border);border-radius:var(--radius-lg);padding:30px 34px;box-shadow:var(--shadow-card);position:relative;overflow:hidden;transition:box-shadow .4s,border-color .4s,transform .3s;animation:cardIn .5s cubic-bezier(.22,1,.36,1) both}
|
| 76 |
+
@keyframes cardIn{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}
|
| 77 |
+
.card:hover{box-shadow:var(--shadow-hover);border-color:rgba(0,245,212,.16)}
|
| 78 |
+
.card::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,rgba(0,245,212,.5),transparent);opacity:.6}
|
| 79 |
+
.card::after{content:'';position:absolute;top:-50px;right:-50px;width:130px;height:130px;border-radius:50%;background:radial-gradient(circle,rgba(0,245,212,.035) 0%,transparent 70%);pointer-events:none}
|
| 80 |
+
.card-header{display:flex;align-items:center;gap:12px;margin-bottom:26px}
|
| 81 |
+
.card-header h2{font-family:var(--font-display);font-size:1rem;font-weight:700;color:var(--text);letter-spacing:-.2px;flex:1}
|
| 82 |
+
.step-badge{font-family:var(--font-mono);font-size:.58rem;font-weight:500;letter-spacing:1.5px;text-transform:uppercase;color:var(--void);background:linear-gradient(90deg,var(--cyan),#00c4a8);padding:3px 10px;border-radius:20px;box-shadow:0 0 12px rgba(0,245,212,.38)}
|
| 83 |
+
|
| 84 |
+
/* Forms */
|
| 85 |
+
.field{margin-bottom:20px}
|
| 86 |
+
label{display:flex;align-items:center;gap:7px;font-family:var(--font-mono);font-size:.7rem;font-weight:500;letter-spacing:1px;text-transform:uppercase;color:var(--text-muted);margin-bottom:7px}
|
| 87 |
+
.lbl-dot{width:4px;height:4px;border-radius:50%;background:var(--cyan);box-shadow:0 0 6px var(--cyan)}
|
| 88 |
+
.lbl-opt{color:var(--text-dim);font-size:.62rem;margin-left:auto;letter-spacing:.5px;text-transform:none}
|
| 89 |
+
textarea,input[type="password"],select{width:100%;background:rgba(4,9,20,.85);border:1px solid rgba(0,245,212,.1);border-radius:var(--radius-md);color:var(--text);font-family:var(--font-body);font-size:.9rem;padding:12px 16px;resize:vertical;outline:none;transition:border-color .3s,box-shadow .3s,background .3s;box-shadow:inset 0 1px 0 rgba(255,255,255,.02),0 1px 4px rgba(0,0,0,.35)}
|
| 90 |
+
textarea:focus,input:focus,select:focus{border-color:var(--cyan);background:rgba(0,245,212,.025);box-shadow:0 0 0 3px rgba(0,245,212,.07),0 0 20px rgba(0,245,212,.08)}
|
| 91 |
+
textarea::placeholder,input::placeholder{color:var(--text-dim);font-style:italic}
|
| 92 |
+
textarea{min-height:110px;line-height:1.7}
|
| 93 |
+
select{appearance:none;background-image:url("data:image/svg+xml,%3Csvg width='11' height='7' viewBox='0 0 11 7' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5.5 6L10 1' stroke='%2300f5d4' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 14px center;padding-right:38px}
|
| 94 |
+
select option{background:#06101e}
|
| 95 |
+
.input-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
| 96 |
+
.field-note{font-size:.72rem;color:var(--text-dim);margin-top:5px;font-family:var(--font-mono)}
|
| 97 |
+
|
| 98 |
+
/* Buttons */
|
| 99 |
+
button{cursor:none}
|
| 100 |
+
.btn-primary{display:inline-flex;align-items:center;justify-content:center;gap:8px;border:none;border-radius:var(--radius-md);font-family:var(--font-display);font-size:.9rem;font-weight:700;letter-spacing:.3px;padding:13px 28px;position:relative;overflow:hidden;background:linear-gradient(135deg,var(--cyan) 0%,#00d4b4 40%,#00a88e 70%,var(--violet) 100%);background-size:250% 250%;color:var(--void);animation:gradFlow 5s ease infinite;transition:transform .3s cubic-bezier(.34,1.56,.64,1),box-shadow .3s}
|
| 101 |
+
@keyframes gradFlow{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
|
| 102 |
+
.btn-primary::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,rgba(255,255,255,.2),transparent);opacity:0;transition:opacity .3s}
|
| 103 |
+
.btn-primary::after{content:'';position:absolute;top:0;left:-100%;width:55%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.22),transparent);transform:skewX(-20deg)}
|
| 104 |
+
.btn-primary:hover{transform:translateY(-2px) scale(1.02);box-shadow:0 10px 32px var(--cyan-glow),0 0 60px rgba(0,245,212,.12)}
|
| 105 |
+
.btn-primary:hover::before{opacity:1}
|
| 106 |
+
.btn-primary:hover::after{animation:shimmer .65s ease forwards}
|
| 107 |
+
@keyframes shimmer{to{left:150%}}
|
| 108 |
+
.btn-primary:active{transform:translateY(0) scale(.99)}
|
| 109 |
+
.btn-primary:disabled{opacity:.6;transform:none;cursor:not-allowed;animation:none}
|
| 110 |
+
.btn-secondary{display:inline-flex;align-items:center;justify-content:center;gap:8px;border:1px solid var(--border);border-radius:var(--radius-md);background:var(--surface-2);color:var(--text-soft);font-family:var(--font-body);font-size:.875rem;font-weight:500;padding:11px 22px;transition:all .25s;backdrop-filter:blur(8px);position:relative;overflow:hidden}
|
| 111 |
+
.btn-secondary::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,var(--cyan-dim),transparent);opacity:0;transition:opacity .3s}
|
| 112 |
+
.btn-secondary:hover{border-color:var(--border-hot);color:var(--cyan);transform:translateY(-1px);box-shadow:0 4px 18px rgba(0,245,212,.1)}
|
| 113 |
+
.btn-secondary:hover::before{opacity:1}
|
| 114 |
+
.btn-danger:hover{border-color:var(--magenta);color:var(--magenta);background:var(--magenta-dim);box-shadow:0 4px 18px rgba(247,37,133,.12)}
|
| 115 |
+
.btn-sm{font-size:.76rem;padding:6px 13px;border-radius:var(--radius-sm)}
|
| 116 |
+
.action-row{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px;align-items:center}
|
| 117 |
+
.btn-copy{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--border);border-radius:8px;background:var(--surface-3);color:var(--text-muted);font-family:var(--font-mono);font-size:.73rem;padding:7px 14px;margin-top:10px;transition:all .25s}
|
| 118 |
+
.btn-copy:hover{color:var(--cyan);border-color:var(--border-hot);background:var(--cyan-dim)}
|
| 119 |
+
.btn-copy.copied{color:var(--cyan);border-color:var(--cyan);background:var(--cyan-dim)}
|
| 120 |
+
|
| 121 |
+
/* Manifest */
|
| 122 |
+
.manifest-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:22px}
|
| 123 |
+
.manifest-field{display:flex;flex-direction:column;gap:5px}
|
| 124 |
+
.manifest-field.full{grid-column:1/-1}
|
| 125 |
+
.manifest-field label{font-size:.65rem;letter-spacing:1.5px;color:var(--cyan)}
|
| 126 |
+
.manifest-field textarea,.manifest-field input{font-size:.84rem;min-height:62px;border-radius:var(--radius-sm);padding:10px 13px}
|
| 127 |
+
|
| 128 |
+
/* Details */
|
| 129 |
+
details{border:1px solid var(--border);border-radius:var(--radius-md);overflow:hidden;transition:border-color .3s}
|
| 130 |
+
details[open]{border-color:rgba(0,245,212,.18)}
|
| 131 |
+
details summary{padding:11px 16px;cursor:none;font-family:var(--font-mono);font-size:.78rem;color:var(--text-muted);background:var(--surface-2);display:flex;align-items:center;gap:8px;list-style:none;user-select:none;transition:all .2s}
|
| 132 |
+
details summary::-webkit-details-marker{display:none}
|
| 133 |
+
details summary::before{content:'▶';font-size:.5rem;color:var(--cyan);transition:transform .3s}
|
| 134 |
+
details[open] summary::before{transform:rotate(90deg)}
|
| 135 |
+
details summary:hover{color:var(--text);background:var(--surface-3)}
|
| 136 |
+
|
| 137 |
+
/* Pre */
|
| 138 |
+
pre{background:rgba(2,5,12,.92);border:1px solid rgba(0,245,212,.06);border-radius:var(--radius-md);color:#a0d0c4;font-family:var(--font-mono);font-size:.79rem;line-height:1.6;overflow:auto;padding:20px;white-space:pre-wrap;word-break:break-word;max-height:420px;position:relative}
|
| 139 |
+
pre::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,rgba(0,245,212,.25),transparent)}
|
| 140 |
+
|
| 141 |
+
/* Tabs */
|
| 142 |
+
.tab-bar{display:flex;gap:4px;margin-bottom:16px;background:rgba(4,9,20,.7);padding:4px;border-radius:var(--radius-md);border:1px solid var(--border);width:fit-content}
|
| 143 |
+
.tab{border:none;border-radius:9px;background:none;color:var(--text-muted);font-family:var(--font-mono);font-size:.76rem;letter-spacing:.5px;padding:7px 18px;transition:all .25s}
|
| 144 |
+
.tab.active{background:linear-gradient(135deg,rgba(0,245,212,.18),rgba(0,245,212,.06));color:var(--cyan);border:1px solid rgba(0,245,212,.22);box-shadow:0 0 14px rgba(0,245,212,.14)}
|
| 145 |
+
.tab:not(.active):hover{color:var(--text-soft)}
|
| 146 |
+
|
| 147 |
+
/* Table */
|
| 148 |
+
.table-wrap{margin-top:14px;border:1px solid var(--border);border-radius:var(--radius-md);overflow:hidden}
|
| 149 |
+
table{width:100%;border-collapse:collapse;font-size:.82rem}
|
| 150 |
+
thead{background:rgba(0,245,212,.03);border-bottom:1px solid var(--border)}
|
| 151 |
+
th{padding:11px 15px;text-align:left;font-family:var(--font-mono);font-size:.64rem;letter-spacing:1px;text-transform:uppercase;color:var(--text-muted);font-weight:400}
|
| 152 |
+
td{padding:11px 15px;border-bottom:1px solid rgba(0,245,212,.03);vertical-align:middle;color:var(--text-soft);transition:background .2s}
|
| 153 |
+
tr:last-child td{border-bottom:none}
|
| 154 |
+
tbody tr:hover td{background:rgba(0,245,212,.025)}
|
| 155 |
+
td code{font-family:var(--font-mono);font-size:.76rem;color:var(--cyan);background:var(--cyan-dim);padding:2px 8px;border-radius:4px}
|
| 156 |
+
td.empty-msg{padding:36px;text-align:center;color:var(--text-dim);font-style:italic}
|
| 157 |
+
|
| 158 |
+
/* Badges */
|
| 159 |
+
.badge{display:inline-flex;align-items:center;gap:5px;border-radius:20px;font-family:var(--font-mono);font-size:.64rem;letter-spacing:.5px;text-transform:uppercase;padding:3px 10px}
|
| 160 |
+
.badge::before{content:'';width:5px;height:5px;border-radius:50%;flex-shrink:0}
|
| 161 |
+
.badge-pending{background:rgba(255,214,10,.1);color:var(--gold);border:1px solid rgba(255,214,10,.2)}
|
| 162 |
+
.badge-pending::before{background:var(--gold);box-shadow:0 0 5px var(--gold)}
|
| 163 |
+
.badge-approved{background:rgba(0,245,212,.1);color:var(--cyan);border:1px solid rgba(0,245,212,.2)}
|
| 164 |
+
.badge-approved::before{background:var(--cyan);box-shadow:0 0 5px var(--cyan)}
|
| 165 |
+
.badge-exported{background:rgba(247,37,133,.1);color:var(--magenta);border:1px solid rgba(247,37,133,.2)}
|
| 166 |
+
.badge-exported::before{background:var(--magenta);box-shadow:0 0 5px var(--magenta)}
|
| 167 |
+
|
| 168 |
+
/* Info banner */
|
| 169 |
+
.info-banner{display:flex;align-items:flex-start;gap:11px;background:rgba(0,245,212,.04);border:1px solid rgba(0,245,212,.1);border-radius:var(--radius-md);padding:13px 16px;font-size:.83rem;color:var(--text-soft);margin-bottom:20px}
|
| 170 |
+
.info-icon{font-size:15px;flex-shrink:0;margin-top:1px}
|
| 171 |
+
.divider{height:1px;background:linear-gradient(90deg,transparent,var(--border),transparent);margin:22px 0}
|
| 172 |
+
|
| 173 |
+
/* Toasts */
|
| 174 |
+
#toast-container{position:fixed;bottom:26px;right:26px;z-index:9990;display:flex;flex-direction:column;gap:9px;pointer-events:none}
|
| 175 |
+
.toast{pointer-events:all;backdrop-filter:blur(22px);background:rgba(6,12,24,.96);border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:0 8px 36px rgba(0,0,0,.55);display:flex;align-items:center;gap:11px;font-size:.84rem;max-width:360px;padding:13px 16px;animation:toastIn .35s cubic-bezier(.22,1,.36,1) both}
|
| 176 |
+
.toast.leaving{animation:toastOut .3s ease forwards}
|
| 177 |
+
.toast-icon{width:28px;height:28px;border-radius:8px;display:grid;place-items:center;font-size:13px;flex-shrink:0}
|
| 178 |
+
.toast.success{border-color:rgba(0,245,212,.22)}.toast.success .toast-icon{background:var(--cyan-dim)}
|
| 179 |
+
.toast.error{border-color:rgba(247,37,133,.22)}.toast.error .toast-icon{background:var(--magenta-dim)}
|
| 180 |
+
@keyframes toastIn{from{opacity:0;transform:translateX(16px) scale(.96)}to{opacity:1;transform:none}}
|
| 181 |
+
@keyframes toastOut{to{opacity:0;transform:translateX(16px) scale(.96);max-height:0;padding:0;overflow:hidden}}
|
| 182 |
+
|
| 183 |
+
/* Footer */
|
| 184 |
+
footer{position:relative;z-index:1;border-top:1px solid rgba(0,245,212,.05);padding:22px 20px}
|
| 185 |
+
.footer-inner{max-width:900px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px}
|
| 186 |
+
.footer-copy{font-family:var(--font-mono);font-size:.7rem;color:var(--text-dim)}
|
| 187 |
+
.footer-links{display:flex;gap:16px}
|
| 188 |
+
.footer-links a{font-family:var(--font-mono);font-size:.7rem;color:var(--text-muted);text-decoration:none;transition:color .2s}
|
| 189 |
+
.footer-links a:hover{color:var(--cyan)}
|
| 190 |
+
|
| 191 |
+
/* Utility */
|
| 192 |
+
.hidden{display:none!important}
|
| 193 |
+
.muted{color:var(--text-muted);font-size:.84rem;margin-bottom:14px;line-height:1.55}
|
| 194 |
+
.fade-in{animation:fadeIn .45s cubic-bezier(.22,1,.36,1) both}
|
| 195 |
+
@keyframes fadeIn{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
|
| 196 |
+
|
| 197 |
+
/* Responsive */
|
| 198 |
+
@media(max-width:680px){.input-grid,.manifest-grid{grid-template-columns:1fr}.manifest-field.full{grid-column:1}.card{padding:22px 18px}main{padding:14px 14px 60px}.prog-line{width:28px}.footer-inner{flex-direction:column;align-items:center}}
|
| 199 |
+
@media(max-width:420px){.header-meta{display:none}}
|
frontend/style.css.bak
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* PromptForge — style.css */
|
| 2 |
+
|
| 3 |
+
:root {
|
| 4 |
+
--bg: #0f1117;
|
| 5 |
+
--surface: #1a1d27;
|
| 6 |
+
--surface2: #22263a;
|
| 7 |
+
--border: #2e3350;
|
| 8 |
+
--accent: #5b8df6;
|
| 9 |
+
--accent-hover: #3d6fe0;
|
| 10 |
+
--success: #34d399;
|
| 11 |
+
--warning: #fbbf24;
|
| 12 |
+
--danger: #f87171;
|
| 13 |
+
--text: #e4e7f0;
|
| 14 |
+
--text-muted: #7c84a3;
|
| 15 |
+
--radius: 10px;
|
| 16 |
+
--font: 'Inter', system-ui, sans-serif;
|
| 17 |
+
--mono: 'Fira Code', 'Cascadia Code', monospace;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 21 |
+
|
| 22 |
+
body {
|
| 23 |
+
background: var(--bg);
|
| 24 |
+
color: var(--text);
|
| 25 |
+
font-family: var(--font);
|
| 26 |
+
font-size: 15px;
|
| 27 |
+
line-height: 1.6;
|
| 28 |
+
min-height: 100vh;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
/* ── Header ──────────────────────────────────────────────────── */
|
| 32 |
+
header {
|
| 33 |
+
background: linear-gradient(135deg, #1e2336 0%, #151824 100%);
|
| 34 |
+
border-bottom: 1px solid var(--border);
|
| 35 |
+
padding: 14px 24px;
|
| 36 |
+
position: sticky;
|
| 37 |
+
top: 0;
|
| 38 |
+
z-index: 100;
|
| 39 |
+
backdrop-filter: blur(6px);
|
| 40 |
+
}
|
| 41 |
+
.header-inner { display: flex; align-items: center; gap: 16px; max-width: 960px; margin: 0 auto; }
|
| 42 |
+
.logo { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.3px; }
|
| 43 |
+
.tagline { color: var(--text-muted); font-size: 0.85rem; }
|
| 44 |
+
|
| 45 |
+
/* ── Main layout ──────────────────────────────────────────────── */
|
| 46 |
+
main {
|
| 47 |
+
max-width: 960px;
|
| 48 |
+
margin: 32px auto;
|
| 49 |
+
padding: 0 20px;
|
| 50 |
+
display: flex;
|
| 51 |
+
flex-direction: column;
|
| 52 |
+
gap: 24px;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
/* ── Cards ──────────────────────────────────────────────────────── */
|
| 56 |
+
.card {
|
| 57 |
+
background: var(--surface);
|
| 58 |
+
border: 1px solid var(--border);
|
| 59 |
+
border-radius: var(--radius);
|
| 60 |
+
padding: 28px 30px;
|
| 61 |
+
}
|
| 62 |
+
.card h2 {
|
| 63 |
+
font-size: 1.05rem;
|
| 64 |
+
font-weight: 600;
|
| 65 |
+
margin-bottom: 18px;
|
| 66 |
+
display: flex;
|
| 67 |
+
align-items: center;
|
| 68 |
+
gap: 10px;
|
| 69 |
+
}
|
| 70 |
+
.step-badge {
|
| 71 |
+
background: var(--accent);
|
| 72 |
+
color: #fff;
|
| 73 |
+
font-size: 0.65rem;
|
| 74 |
+
font-weight: 700;
|
| 75 |
+
letter-spacing: 0.5px;
|
| 76 |
+
padding: 3px 8px;
|
| 77 |
+
border-radius: 20px;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
/* ── Form elements ─────────────────────────────────────────────── */
|
| 81 |
+
label {
|
| 82 |
+
display: block;
|
| 83 |
+
margin-bottom: 6px;
|
| 84 |
+
font-size: 0.875rem;
|
| 85 |
+
color: var(--text-muted);
|
| 86 |
+
font-weight: 500;
|
| 87 |
+
}
|
| 88 |
+
textarea, input[type="password"], select {
|
| 89 |
+
width: 100%;
|
| 90 |
+
background: var(--surface2);
|
| 91 |
+
border: 1px solid var(--border);
|
| 92 |
+
border-radius: 7px;
|
| 93 |
+
color: var(--text);
|
| 94 |
+
font-family: var(--font);
|
| 95 |
+
font-size: 0.9rem;
|
| 96 |
+
padding: 10px 14px;
|
| 97 |
+
resize: vertical;
|
| 98 |
+
transition: border-color 0.2s;
|
| 99 |
+
margin-bottom: 16px;
|
| 100 |
+
}
|
| 101 |
+
textarea:focus, input:focus, select:focus {
|
| 102 |
+
outline: none;
|
| 103 |
+
border-color: var(--accent);
|
| 104 |
+
}
|
| 105 |
+
select option { background: var(--surface2); }
|
| 106 |
+
|
| 107 |
+
.row-two {
|
| 108 |
+
display: grid;
|
| 109 |
+
grid-template-columns: 1fr 1fr;
|
| 110 |
+
gap: 16px;
|
| 111 |
+
}
|
| 112 |
+
.field-group { display: flex; flex-direction: column; }
|
| 113 |
+
.field-group label { margin-bottom: 6px; }
|
| 114 |
+
.field-group textarea,
|
| 115 |
+
.field-group input,
|
| 116 |
+
.field-group select { margin-bottom: 0; }
|
| 117 |
+
|
| 118 |
+
/* ── Buttons ────────────────────────────────────────────────────── */
|
| 119 |
+
.btn-primary, .btn-secondary {
|
| 120 |
+
display: inline-flex;
|
| 121 |
+
align-items: center;
|
| 122 |
+
gap: 6px;
|
| 123 |
+
border: none;
|
| 124 |
+
border-radius: 7px;
|
| 125 |
+
cursor: pointer;
|
| 126 |
+
font-family: var(--font);
|
| 127 |
+
font-size: 0.9rem;
|
| 128 |
+
font-weight: 600;
|
| 129 |
+
padding: 10px 20px;
|
| 130 |
+
transition: background 0.2s, transform 0.1s;
|
| 131 |
+
}
|
| 132 |
+
.btn-primary {
|
| 133 |
+
background: var(--accent);
|
| 134 |
+
color: #fff;
|
| 135 |
+
}
|
| 136 |
+
.btn-primary:hover { background: var(--accent-hover); transform: translateY(-1px); }
|
| 137 |
+
.btn-primary:active { transform: translateY(0); }
|
| 138 |
+
|
| 139 |
+
.btn-secondary {
|
| 140 |
+
background: var(--surface2);
|
| 141 |
+
color: var(--text);
|
| 142 |
+
border: 1px solid var(--border);
|
| 143 |
+
}
|
| 144 |
+
.btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
|
| 145 |
+
|
| 146 |
+
.btn-sm { font-size: 0.8rem; padding: 7px 14px; }
|
| 147 |
+
|
| 148 |
+
.btn-copy {
|
| 149 |
+
background: var(--surface2);
|
| 150 |
+
border: 1px solid var(--border);
|
| 151 |
+
border-radius: 6px;
|
| 152 |
+
color: var(--text-muted);
|
| 153 |
+
cursor: pointer;
|
| 154 |
+
font-size: 0.8rem;
|
| 155 |
+
margin-top: 8px;
|
| 156 |
+
padding: 6px 14px;
|
| 157 |
+
transition: color 0.2s;
|
| 158 |
+
}
|
| 159 |
+
.btn-copy:hover { color: var(--success); border-color: var(--success); }
|
| 160 |
+
|
| 161 |
+
.action-row {
|
| 162 |
+
display: flex;
|
| 163 |
+
flex-wrap: wrap;
|
| 164 |
+
gap: 10px;
|
| 165 |
+
margin-top: 20px;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
/* ── Manifest grid ──────────────────────────────────────────────── */
|
| 169 |
+
.manifest-grid {
|
| 170 |
+
display: grid;
|
| 171 |
+
grid-template-columns: 1fr 1fr;
|
| 172 |
+
gap: 16px;
|
| 173 |
+
margin-bottom: 20px;
|
| 174 |
+
}
|
| 175 |
+
.manifest-field { display: flex; flex-direction: column; }
|
| 176 |
+
.manifest-field.full { grid-column: 1 / -1; }
|
| 177 |
+
.manifest-field label { font-size: 0.78rem; color: var(--accent); text-transform: uppercase; letter-spacing: 0.4px; margin-bottom: 4px; }
|
| 178 |
+
.manifest-field textarea,
|
| 179 |
+
.manifest-field input {
|
| 180 |
+
margin-bottom: 0;
|
| 181 |
+
font-size: 0.85rem;
|
| 182 |
+
min-height: 70px;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
/* ── Tabs ───────────────────────────────────────────────────────── */
|
| 186 |
+
.tab-bar { display: flex; gap: 4px; margin-bottom: 14px; }
|
| 187 |
+
.tab {
|
| 188 |
+
background: none;
|
| 189 |
+
border: 1px solid var(--border);
|
| 190 |
+
border-radius: 6px;
|
| 191 |
+
color: var(--text-muted);
|
| 192 |
+
cursor: pointer;
|
| 193 |
+
font-family: var(--font);
|
| 194 |
+
font-size: 0.85rem;
|
| 195 |
+
padding: 7px 16px;
|
| 196 |
+
transition: all 0.2s;
|
| 197 |
+
}
|
| 198 |
+
.tab.active { background: var(--accent); border-color: var(--accent); color: #fff; }
|
| 199 |
+
|
| 200 |
+
/* ── Pre / code blocks ──────────────────────────────────────────── */
|
| 201 |
+
pre {
|
| 202 |
+
background: #0a0c14;
|
| 203 |
+
border: 1px solid var(--border);
|
| 204 |
+
border-radius: 8px;
|
| 205 |
+
color: #c9d1d9;
|
| 206 |
+
font-family: var(--mono);
|
| 207 |
+
font-size: 0.82rem;
|
| 208 |
+
line-height: 1.5;
|
| 209 |
+
overflow-x: auto;
|
| 210 |
+
padding: 16px 18px;
|
| 211 |
+
white-space: pre-wrap;
|
| 212 |
+
word-break: break-word;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
details summary {
|
| 216 |
+
cursor: pointer;
|
| 217 |
+
color: var(--text-muted);
|
| 218 |
+
font-size: 0.85rem;
|
| 219 |
+
margin-bottom: 10px;
|
| 220 |
+
user-select: none;
|
| 221 |
+
}
|
| 222 |
+
details[open] summary { color: var(--text); }
|
| 223 |
+
|
| 224 |
+
/* ── History table ──────────────────────────────────────────────── */
|
| 225 |
+
table { width: 100%; border-collapse: collapse; margin-top: 14px; font-size: 0.85rem; }
|
| 226 |
+
th { color: var(--text-muted); font-weight: 600; padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--border); }
|
| 227 |
+
td { padding: 9px 10px; border-bottom: 1px solid #1e2236; vertical-align: middle; }
|
| 228 |
+
tr:hover td { background: var(--surface2); }
|
| 229 |
+
td.center { text-align: center; }
|
| 230 |
+
|
| 231 |
+
.status-badge {
|
| 232 |
+
border-radius: 20px;
|
| 233 |
+
font-size: 0.72rem;
|
| 234 |
+
font-weight: 700;
|
| 235 |
+
letter-spacing: 0.3px;
|
| 236 |
+
padding: 3px 9px;
|
| 237 |
+
text-transform: uppercase;
|
| 238 |
+
}
|
| 239 |
+
.status-pending { background: #2d2a1a; color: var(--warning); }
|
| 240 |
+
.status-approved { background: #162820; color: var(--success); }
|
| 241 |
+
.status-exported { background: #1a1f36; color: var(--accent); }
|
| 242 |
+
|
| 243 |
+
/* ── Utility ────────────────────────────────────────────────────── */
|
| 244 |
+
.hidden { display: none !important; }
|
| 245 |
+
.muted { color: var(--text-muted); font-size: 0.875rem; margin-bottom: 12px; }
|
| 246 |
+
|
| 247 |
+
/* ── Toast ──────────────────────────────────────────────────────── */
|
| 248 |
+
.toast {
|
| 249 |
+
position: fixed;
|
| 250 |
+
bottom: 28px;
|
| 251 |
+
right: 28px;
|
| 252 |
+
background: var(--surface2);
|
| 253 |
+
border: 1px solid var(--border);
|
| 254 |
+
border-radius: 9px;
|
| 255 |
+
box-shadow: 0 4px 24px rgba(0,0,0,0.4);
|
| 256 |
+
color: var(--text);
|
| 257 |
+
font-size: 0.875rem;
|
| 258 |
+
font-weight: 500;
|
| 259 |
+
max-width: 340px;
|
| 260 |
+
padding: 13px 18px;
|
| 261 |
+
z-index: 999;
|
| 262 |
+
animation: slideIn 0.25s ease;
|
| 263 |
+
}
|
| 264 |
+
.toast.success { border-color: var(--success); color: var(--success); }
|
| 265 |
+
.toast.error { border-color: var(--danger); color: var(--danger); }
|
| 266 |
+
@keyframes slideIn { from { opacity:0; transform: translateY(10px); } to { opacity:1; transform: translateY(0); } }
|
| 267 |
+
|
| 268 |
+
/* ── Footer ──────────────────────────────────────────────────────── */
|
| 269 |
+
footer {
|
| 270 |
+
text-align: center;
|
| 271 |
+
color: var(--text-muted);
|
| 272 |
+
font-size: 0.8rem;
|
| 273 |
+
padding: 24px 0 32px;
|
| 274 |
+
}
|
| 275 |
+
footer a { color: var(--accent); text-decoration: none; }
|
| 276 |
+
footer a:hover { text-decoration: underline; }
|
| 277 |
+
|
| 278 |
+
/* ── Responsive ──────────────────────────────────────────────────── */
|
| 279 |
+
@media (max-width: 640px) {
|
| 280 |
+
.row-two, .manifest-grid { grid-template-columns: 1fr; }
|
| 281 |
+
.manifest-field.full { grid-column: 1; }
|
| 282 |
+
.card { padding: 20px 16px; }
|
| 283 |
+
main { margin: 16px auto; }
|
| 284 |
+
}
|