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> |