Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Google Apps Script Generator</title> | |
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"> | |
<style> | |
/* Base body padding for fixed header */ | |
body { | |
padding-top: 5rem; /* Start with a base padding */ | |
} | |
/* Hero section base style */ | |
#heroSection { | |
display: flex; /* Use flex by default */ | |
transition: opacity 0.3s ease-out, max-height 0.3s ease-out; /* Add transition */ | |
opacity: 1; | |
max-height: 500px; /* Estimate max height */ | |
overflow: hidden; | |
} | |
/* Style to hide hero section smoothly */ | |
#heroSection.hidden-section { | |
opacity: 0; | |
max-height: 0; | |
padding-top: 0; | |
padding-bottom: 0; | |
margin-bottom: 0; | |
/* display: none; /* Re-add if transition is not desired or causes layout shifts */ | |
} | |
/* Progress steps base style */ | |
#progressSteps { | |
display: flex; /* Use flex by default */ | |
transition: opacity 0.3s ease-out, max-height 0.3s ease-out; /* Add transition */ | |
opacity: 1; | |
max-height: 100px; /* Estimate max height */ | |
overflow: hidden; | |
} | |
/* Style to hide steps smoothly */ | |
#progressSteps.hidden-section { | |
opacity: 0; | |
max-height: 0; | |
margin-bottom: 0; | |
/* display: none; */ | |
} | |
.code-block { | |
font-family: 'Courier New', Courier, monospace; | |
white-space: pre-wrap; | |
background: #000000; | |
color: #e2e8f0; | |
padding: 1rem; | |
border-radius: 0.5rem; | |
height: calc(80vh - 22rem); /* Adjusted height slightly */ | |
min-height: 150px; /* Adjusted min-height */ | |
overflow-y: auto; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
} | |
.section { display: none; } | |
.section.active { display: block; } | |
.input-field { | |
transition: border-color 0.2s ease, box-shadow 0.2s ease; | |
background: rgba(255, 255, 255, 0.25); | |
color: #1e293b; | |
border: 1px solid rgba(255, 255, 255, 0.4); | |
width: 100%; | |
padding: 0.65rem 0.75rem; /* Adjusted padding slightly */ | |
border-radius: 0.375rem; /* Standard Tailwind rounded-md */ | |
} | |
.input-field::placeholder { color: #4b5563; opacity: 1; } | |
.input-field:focus { | |
border-color: #3b82f6; | |
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4); | |
background: rgba(255, 255, 255, 0.35); | |
} | |
.btn { | |
transition: background-color 0.2s ease, transform 0.1s ease, box-shadow 0.1s ease; | |
backdrop-filter: blur(8px); | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
padding: 0.75rem 1.25rem; /* Default padding */ | |
border-radius: 0.375rem; /* Standard Tailwind rounded-md */ | |
font-weight: 500; /* Medium weight */ | |
text-align: center; | |
box-shadow: 0 1px 2px rgba(0,0,0,0.05); | |
} | |
.btn:hover { | |
transform: translateY(-1px); | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
} | |
.btn:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(0,0,0,0.05); } | |
/* Specific Button Colors */ | |
.btn-blue { background: #2563eb; color: white; } .btn-blue:hover { background: #1d4ed8; } | |
.btn-green { background: #16a34a; color: white; } .btn-green:hover { background: #15803d; } | |
.btn-gray-dark { background: #374151; color: white; } .btn-gray-dark:hover { background: #1f2937; } | |
.btn-gray { background: #4b5563; color: white; } .btn-gray:hover { background: #374151; } | |
.btn-red { background: #dc2626; color: white; } .btn-red:hover { background: #b91c1c; } | |
.btn-light { background: #e5e7eb; color: #1f2937; } .btn-light:hover { background: #d1d5db; } | |
.btn-light-active { background: #dbeafe; color: #1e40af; } /* For active cURL button */ | |
/* Small icon button style (e.g., delete, copy) */ | |
.btn-icon { | |
padding: 0.5rem; /* Smaller padding for icons */ | |
flex-shrink: 0; /* Prevent shrinking */ | |
line-height: 1; /* Ensure icon aligns well */ | |
} | |
.btn-icon svg { | |
width: 1.25rem; /* w-5 */ | |
height: 1.25rem; /* h-5 */ | |
} | |
.step-indicator { | |
background: rgba(255, 255, 255, 0.1); | |
backdrop-filter: blur(5px); | |
border: 1px solid rgba(255, 255, 255, 0.2); | |
width: 2.5rem; /* w-10 */ | |
height: 2.5rem; /* h-10 */ | |
border-radius: 9999px; /* rounded-full */ | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-weight: 600; /* semibold */ | |
color: #4b5563; /* gray-600 text */ | |
transition: all 0.3s ease-in-out; | |
} | |
.step-indicator.active { | |
background: rgba(59, 130, 246, 0.9); | |
color: white; | |
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); | |
} | |
.step-indicator.completed { /* Style for completed steps */ | |
background: rgba(22, 163, 74, 0.8); /* Green */ | |
color: white; | |
} | |
.glass-container { | |
background: rgba(255, 255, 255, 0.15); | |
backdrop-filter: blur(12px); | |
border: 1px solid rgba(255, 255, 255, 0.25); | |
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); | |
border-radius: 0.75rem; /* rounded-xl */ | |
padding: 1.5rem; /* Default padding */ | |
} | |
/* --- Mobile-specific styles --- */ | |
@media (max-width: 640px) { | |
body { | |
padding-top: 4.5rem; /* Adjust for potentially smaller header */ | |
padding-left: 0.5rem; | |
padding-right: 0.5rem; | |
} | |
.glass-container { | |
padding: 1rem; /* Reduce padding slightly */ | |
} | |
.code-block { | |
height: calc(70vh - 15rem); /* Adjust height calc for mobile */ | |
padding: 0.75rem; | |
min-height: 120px; | |
} | |
/* Input row layout */ | |
.column-input-row { | |
flex-direction: row; /* Keep input and button in a row */ | |
align-items: center; | |
gap: 0.75rem; /* space-x-3 equivalent */ | |
} | |
.column-input-row input { | |
flex-grow: 1; /* Allow input to take space */ | |
} | |
.column-input-row .btn-icon { | |
/* Delete button already has flex-shrink-0 */ | |
} | |
/* Button container for main actions */ | |
.button-container-mobile-stack { | |
display: flex; | |
flex-direction: column; | |
width: 100%; | |
gap: 0.75rem; /* space-y-3 */ | |
} | |
.button-container-mobile-stack .btn { | |
width: 100%; /* Make buttons full width */ | |
} | |
/* Make specific containers stack buttons */ | |
#columns .button-container, | |
#code .button-container, | |
#howto .button-container { | |
flex-direction: column; | |
gap: 0.75rem; | |
} | |
#columns .button-container .btn, | |
#code .button-container .btn, | |
#howto .button-container .btn { | |
width: 100%; /* Ensure buttons inside stack full width */ | |
margin-left: 0 ; /* Override potential sm: space-x */ | |
} | |
.step-indicator { | |
width: 2.25rem; height: 2.25rem; /* Slightly smaller */ | |
font-size: 0.875rem; /* text-sm */ | |
} | |
/* Adjust heading sizes for mobile */ | |
.text-4xl { font-size: 1.875rem; line-height: 2.25rem; } /* text-3xl */ | |
.text-2xl { font-size: 1.25rem; line-height: 1.75rem; } /* text-xl */ | |
.text-xl { font-size: 1.125rem; line-height: 1.75rem; } /* text-lg */ | |
.text-lg { font-size: 1rem; line-height: 1.5rem; } /* text-base */ | |
/* Adjust margins */ | |
.mb-12 { margin-bottom: 2rem; } /* mb-8 */ | |
.mb-8 { margin-bottom: 1.5rem; } /* mb-6 */ | |
.mb-6 { margin-bottom: 1rem; } /* mb-4 */ | |
.mt-6 { margin-top: 1rem; } /* mt-4 */ | |
.py-16 { padding-top: 2.5rem; padding-bottom: 2.5rem; } /* py-10 */ | |
/* cURL buttons adjustments */ | |
#howto .flex-wrap { | |
gap: 0.5rem; /* gap-2 */ | |
} | |
#howto .flex-wrap .btn { | |
padding: 0.5rem 0.75rem; /* Smaller padding */ | |
font-size: 0.875rem; /* text-sm */ | |
} | |
/* Copy button on mobile */ | |
#code .flex > .btn-icon { | |
/* padding: 0.6rem; /* Slightly larger tap area if needed */ | |
} | |
} | |
/* Larger screen adjustments */ | |
@media (min-width: 641px) { | |
body { | |
padding-top: 6rem; /* Restore larger padding */ | |
padding-left: 1rem; | |
padding-right: 1rem; | |
} | |
.glass-container { | |
padding: 2rem; /* Restore larger padding */ | |
} | |
/* Restore row layout for buttons on desktop */ | |
#columns .button-container, | |
#code .button-container { | |
flex-direction: row; | |
justify-content: space-between; | |
gap: 1rem; /* space-x-4 */ | |
} | |
#columns .button-container .btn, | |
#code .button-container .btn { | |
width: auto; /* Allow buttons to size naturally */ | |
} | |
/* How-to back button on desktop */ | |
#howto .button-container { | |
justify-content: flex-start; | |
flex-direction: row; /* Ensure it's row */ | |
} | |
#howto .button-container .btn { | |
width: auto; | |
} | |
} | |
</style> | |
</head> | |
<body class="bg-gradient-to-br from-gray-200 via-gray-300 to-gray-400 min-h-screen font-sans text-gray-800"> | |
<header class="fixed top-0 left-0 w-full bg-white shadow-md p-4 z-50"> | |
<div class="max-w-6xl mx-auto text-xl font-bold text-gray-900">GSheet2DB</div> | |
</header> | |
<section id="heroSection" class="flex flex-col justify-center items-center text-center px-6 py-16 mb-12"> | |
<h1 class="text-4xl font-bold text-gray-900">Convert Google Sheets to a Database</h1> | |
<p class="text-lg text-gray-700 mt-3 max-w-2xl"> | |
Generate Google Apps Script code to turn your spreadsheet into a simple REST API. Fast, simple, and free! | |
</p> | |
</section> | |
<div id="progressSteps" class="max-w-xl mx-auto flex justify-center space-x-4 mb-8 px-4"> | |
<div data-step="columns" class="step-indicator">1</div> | |
<div data-step="code" class="step-indicator">2</div> | |
<div data-step="howto" class="step-indicator">3</div> | |
</div> | |
<div class="max-w-4xl mx-auto glass-container mb-12"> | |
<div id="columns" class="section active"> | |
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 text-center">Step 1: Define Your Columns</h2> | |
<div id="columnInputs" class="space-y-4 mb-6"> | |
<div class="flex items-center space-x-3 column-input-row"> | |
<input type="text" class="column-input input-field flex-1" placeholder="Column name (e.g., Status)" value="word"> | |
<button onclick="removeColumn(this)" class="btn btn-icon btn-red" aria-label="Remove column"> | |
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg> | |
</button> | |
</div> | |
<div class="flex items-center space-x-3 column-input-row"> | |
<input type="text" class="column-input input-field flex-1" placeholder="Column name (e.g., Category)" value="meaning"> | |
<button onclick="removeColumn(this)" class="btn btn-icon btn-red" aria-label="Remove column"> | |
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg> | |
</button> | |
</div> | |
</div> | |
<div class="mt-6 button-container"> <button onclick="addColumn()" class="btn btn-blue w-full sm:w-auto">Add Column</button> | |
<button onclick="nextSection('code')" class="btn btn-green w-full sm:w-auto">Next: Generate Code</button> | |
</div> | |
</div> | |
<div id="code" class="section"> | |
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 text-center">Step 2: Generated Apps Script Code</h2> | |
<div class="flex flex-col sm:flex-row justify-between items-center mb-4 gap-3 sm:gap-0"> <h3 class="text-xl font-semibold text-gray-800">Code Preview</h3> | |
<button onclick="copyCode()" class="btn btn-icon btn-gray-dark self-end sm:self-center" title="Copy to clipboard" aria-label="Copy code to clipboard"> | |
<svg class="w-6 h-6 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-2M8 5a2 2 0 002 2h4a2 2 0 002-2M8 5a2 2 0 012-2h4a2 2 0 012 2"></path></svg> | |
<span class="sr-only">Copy Code</span> | |
</button> | |
</div> | |
<div id="codePreview" class="code-block mb-6"></div> | |
<div class="mt-6 button-container"> <button onclick="nextSection('columns')" class="btn btn-gray w-full sm:w-auto">Back</button> | |
<button onclick="nextSection('howto')" class="btn btn-green w-full sm:w-auto">Next: How to Use</button> | |
</div> | |
</div> | |
<div id="howto" class="section"> | |
<h2 class="text-2xl font-extrabold text-gray-900 mb-6 text-center">Step 3: How to Use the API</h2> | |
<h3 class="text-lg font-semibold text-gray-800 mb-3">Example cURL Commands:</h3> | |
<div class="flex flex-wrap gap-2 mb-6 pb-2"> | |
<button class="curl-button btn btn-light text-sm px-3 py-1.5" onclick="showCurlExample('appendSingle', this)">Append Single</button> | |
<button class="curl-button btn btn-light text-sm px-3 py-1.5" onclick="showCurlExample('appendMultiple', this)">Append Multiple</button> | |
<button class="curl-button btn btn-light text-sm px-3 py-1.5" onclick="showCurlExample('fetchAll', this)">Fetch All</button> | |
<button class="curl-button btn btn-light text-sm px-3 py-1.5" onclick="showCurlExample('fetchRange', this)">Fetch Range</button> | |
<button class="curl-button btn btn-light text-sm px-3 py-1.5" onclick="showCurlExample('search', this)">Search</button> | |
<button class="curl-button btn btn-light text-sm px-3 py-1.5" onclick="showCurlExample('update', this)">Update</button> | |
<button class="curl-button btn btn-light text-sm px-3 py-1.5" onclick="showCurlExample('delete', this)">Delete</button> | |
</div> | |
<div id="curlExamples" class="code-block mb-6"></div> | |
<div class="mt-6 flex justify-start button-container"> <button onclick="nextSection('code')" class="btn btn-gray w-full sm:w-auto">Back</button> | |
</div> | |
</div> | |
</div> | |
<section class="px-4 py-16 max-w-5xl mx-auto"> | |
<h2 class="text-3xl font-semibold text-center mb-10">Why Use This Tool?</h2> | |
<div class="grid gap-8 md:grid-cols-3 text-center"> | |
<div> | |
<div class="text-4xl mb-4">⚡</div> | |
<h3 class="text-xl font-medium mb-2">Fast Setup</h3> | |
<p class="text-gray-600 text-sm">Turn your Google Sheet into a REST API in under a minute. No server, no hosting required.</p> | |
</div> | |
<div> | |
<div class="text-4xl mb-4">🧠</div> | |
<h3 class="text-xl font-medium mb-2">No Coding Needed</h3> | |
<p class="text-gray-600 text-sm">Generate the exact Google Apps Script code you need—just copy and paste.</p> | |
</div> | |
<div> | |
<div class="text-4xl mb-4">💸</div> | |
<h3 class="text-xl font-medium mb-2">Completely Free</h3> | |
<p class="text-gray-600 text-sm">No signup, no cost, no limits. Just paste your spreadsheet and go.</p> | |
</div> | |
</div> | |
</section> | |
<!-- FAQ Section --> | |
<section class="px-4 py-16 max-w-2xl mx-auto"> | |
<h2 class="text-3xl font-semibold text-center mb-10">FAQs</h2> | |
<div class="space-y-4"> | |
<!-- FAQ Item --> | |
<div> | |
<button class="w-full text-left flex justify-between items-center text-lg font-medium faq-toggle"> | |
<span>What does this tool do?</span> | |
<svg class="w-5 h-5 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
<path d="M6 9l6 6 6-6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
</svg> | |
</button> | |
<div class="mt-2 text-gray-600 hidden text-sm faq-content"> | |
It generates Google Apps Script code to convert your spreadsheet into a REST API. | |
</div> | |
</div> | |
<div> | |
<button class="w-full text-left flex justify-between items-center text-lg font-medium faq-toggle"> | |
<span>Is it really free?</span> | |
<svg class="w-5 h-5 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
<path d="M6 9l6 6 6-6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
</svg> | |
</button> | |
<div class="mt-2 text-gray-600 hidden text-sm faq-content"> | |
Yes, it's completely free. No signups, no limits. | |
</div> | |
</div> | |
<div> | |
<button class="w-full text-left flex justify-between items-center text-lg font-medium faq-toggle"> | |
<span>Do I need to know coding?</span> | |
<svg class="w-5 h-5 transition-transform duration-200" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
<path d="M6 9l6 6 6-6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> | |
</svg> | |
</button> | |
<div class="mt-2 text-gray-600 hidden text-sm faq-content"> | |
Nope. Just copy the generated code and paste it into your Apps Script editor. | |
</div> | |
</div> | |
</div> | |
</section> | |
<!-- FAQ Toggle Script --> | |
<script> | |
document.querySelectorAll('.faq-toggle').forEach(button => { | |
button.addEventListener('click', () => { | |
const content = button.nextElementSibling; | |
const icon = button.querySelector('svg'); | |
const isOpen = !content.classList.contains('hidden'); | |
// Close all | |
document.querySelectorAll('.faq-content').forEach(el => el.classList.add('hidden')); | |
document.querySelectorAll('.faq-toggle svg').forEach(i => i.classList.remove('rotate-180')); | |
// Open if not already open | |
if (!isOpen) { | |
content.classList.remove('hidden'); | |
icon.classList.add('rotate-180'); | |
} | |
}); | |
}); | |
</script> | |
<script> | |
function nextSection(sectionId) { | |
// Toggle section visibility | |
document.querySelectorAll('.section').forEach(section => section.classList.remove('active')); | |
document.getElementById(sectionId).classList.add('active'); | |
// Update step indicators | |
const steps = ['columns', 'code', 'howto']; | |
const currentStep = steps.indexOf(sectionId); | |
document.querySelectorAll('.step-indicator').forEach((indicator, index) => { | |
indicator.classList.toggle('active', index === currentStep); | |
indicator.classList.toggle('completed', index < currentStep); // Optional: mark previous steps as completed | |
}); | |
// Show hero section only on the first step (columns) | |
const heroSection = document.getElementById('heroSection'); | |
if (sectionId === 'columns') { | |
heroSection.classList.remove('hidden-section'); | |
} else { | |
heroSection.classList.add('hidden-section'); | |
} | |
} | |
// Initialize the first step as active | |
document.querySelector('.step-indicator[data-step="columns"]').classList.add('active'); | |
</script> | |
<script src="script.js"></script> | |
</body> | |
</html> |