From 6181cf2f2201563324af0bf87eb2e8c2807dc0f0 Mon Sep 17 00:00:00 2001 From: Ayrton Teniente Date: Sun, 15 Feb 2026 19:17:40 -0600 Subject: [PATCH] Generate main view using Gemini CLI 0.28.2. --- GEMINI.md | 1 + astro.config.mjs | 7 +- package-lock.json | 64 +++++----- src/components/Base64Encoder.jsx | 78 ++++++++++++ src/components/GUIDGenerator.jsx | 27 ++++ src/components/Ripple.jsx | 66 ++++++++++ src/components/Sidebar.jsx | 44 +++++++ src/components/ToolCard.jsx | 16 +++ src/components/Welcome.astro | 210 ------------------------------- src/layouts/AppLayout.astro | 23 ++++ src/pages/index.astro | 46 +++++-- src/styles/global.css | 46 ++++++- 12 files changed, 372 insertions(+), 256 deletions(-) create mode 100644 src/components/Base64Encoder.jsx create mode 100644 src/components/GUIDGenerator.jsx create mode 100644 src/components/Ripple.jsx create mode 100644 src/components/Sidebar.jsx create mode 100644 src/components/ToolCard.jsx delete mode 100644 src/components/Welcome.astro create mode 100644 src/layouts/AppLayout.astro diff --git a/GEMINI.md b/GEMINI.md index 7c45267..1859dbc 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -15,6 +15,7 @@ ## General Instructions for the code +- Use React components to create components that can be reused. - When you generate new TypeScript code, follow the existing coding style. - Ensure all new functions and classes have JSDoc comments. - Prefer functional programming paradigms where appropriate. diff --git a/astro.config.mjs b/astro.config.mjs index 30ad428..5e8345b 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,15 +1,12 @@ // @ts-check import { defineConfig } from 'astro/config'; - import react from '@astrojs/react'; - import tailwindcss from '@tailwindcss/vite'; // https://astro.build/config export default defineConfig({ integrations: [react()], - vite: { - plugins: [tailwindcss()] - } + plugins: [tailwindcss()], + }, }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c598a69..6a4ed6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -86,6 +86,38 @@ "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@astrojs/react/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==" + }, + "node_modules/@astrojs/react/node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@astrojs/react/node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@astrojs/telemetry": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", @@ -1267,11 +1299,6 @@ "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==" }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==" - }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", @@ -2005,25 +2032,6 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -4603,14 +4611,6 @@ "react": "^19.2.4" } }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readdirp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", diff --git a/src/components/Base64Encoder.jsx b/src/components/Base64Encoder.jsx new file mode 100644 index 0000000..5d6dfde --- /dev/null +++ b/src/components/Base64Encoder.jsx @@ -0,0 +1,78 @@ +import React from 'react'; + +export default function Base64Encoder() { + const [input, setInput] = React.useState(''); + const [output, setOutput] = React.useState(''); + + const encode = () => { + try { + setOutput(btoa(input)); + } catch (e) { + setOutput('Error: Invalid input for base64 encoding.'); + } + }; + + const decode = () => { + try { + setOutput(atob(input)); + } catch (e) { + setOutput('Error: Invalid base64 string.'); + } + }; + + const handleFileChange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + const result = e.target.result; + setInput(result); + setOutput(result.split(',')[1]); + }; + reader.readAsDataURL(file); + } + }; + + return ( +
+
+ + + +
+
+ + +
+
+ ); +} diff --git a/src/components/GUIDGenerator.jsx b/src/components/GUIDGenerator.jsx new file mode 100644 index 0000000..8d1a36c --- /dev/null +++ b/src/components/GUIDGenerator.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +export default function GUIDGenerator() { + const [guid, setGuid] = React.useState(''); + + const generateGuid = () => { + setGuid(crypto.randomUUID()); + }; + + return ( +
+ + +
+ ); +} diff --git a/src/components/Ripple.jsx b/src/components/Ripple.jsx new file mode 100644 index 0000000..bf02392 --- /dev/null +++ b/src/components/Ripple.jsx @@ -0,0 +1,66 @@ +import React, { useState, useEffect } from 'react'; + +const useDebouncedRippleCleanUp = (rippleCount, duration, cleanUpFunction) => { + useEffect(() => { + let bounce = null; + if (rippleCount > 0) { + clearTimeout(bounce); + + bounce = setTimeout(() => { + cleanUpFunction(); + clearTimeout(bounce); + }, duration * 4); + } + + return () => clearTimeout(bounce); + }, [rippleCount, duration, cleanUpFunction]); +}; + + +const Ripple = ({ duration = 850, color = 'bg-white' }) => { + const [rippleArray, setRippleArray] = useState([]); + + useDebouncedRippleCleanUp(rippleArray.length, duration, () => { + setRippleArray([]); + }); + + const addRipple = event => { + const rippleContainer = event.currentTarget.getBoundingClientRect(); + const size = + rippleContainer.width > rippleContainer.height + ? rippleContainer.width + : rippleContainer.height; + const x = event.pageX - rippleContainer.x - size / 2; + const y = event.pageY - rippleContainer.y - size / 2; + const newRipple = { + x, + y, + size + }; + + setRippleArray([...rippleArray, newRipple]); + }; + + return ( +
+ {rippleArray.length > 0 && + rippleArray.map((ripple, index) => { + return ( + + ); + })} +
+ ); +}; + +export default Ripple; diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx new file mode 100644 index 0000000..98566cf --- /dev/null +++ b/src/components/Sidebar.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import ToolCard from './ToolCard'; + +const tools = [ + { name: 'GUID Generator', component: 'GUIDGenerator' }, + { name: 'Base64 Encoder', component: 'Base64Encoder' }, +]; + +export default function Sidebar() { + const [searchTerm, setSearchTerm] = React.useState(''); + const [activeTool, setActiveTool] = React.useState(''); + + const filteredTools = tools.filter(tool => + tool.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const selectTool = (tool) => { + setActiveTool(tool.name); + const event = new CustomEvent('select-tool', { detail: tool }); + document.dispatchEvent(event); + }; + + return ( +
+ setSearchTerm(e.target.value)} + value={searchTerm} + /> +
+ {filteredTools.map(tool => ( + selectTool(tool)} + isActive={activeTool === tool.name} + /> + ))} +
+
+ ); +} diff --git a/src/components/ToolCard.jsx b/src/components/ToolCard.jsx new file mode 100644 index 0000000..5e40efe --- /dev/null +++ b/src/components/ToolCard.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import Ripple from './Ripple'; + +export default function ToolCard({ name, onClick, isActive }) { + const activeClass = isActive ? 'border-[#24e4a7] shadow-[0_0_10px_#24e4a7]' : 'border-[#344154]'; + + return ( +
+

{name}

+ +
+ ); +} diff --git a/src/components/Welcome.astro b/src/components/Welcome.astro deleted file mode 100644 index 52e0333..0000000 --- a/src/components/Welcome.astro +++ /dev/null @@ -1,210 +0,0 @@ ---- -import astroLogo from '../assets/astro.svg'; -import background from '../assets/background.svg'; ---- - -
- -
-
- Astro Homepage -

- To get started, open the
src/pages
directory in your project. -

- -
-
- - - -

What's New in Astro 5.0?

-

- From content layers to server islands, click to learn more about the new features and - improvements in Astro 5.0 -

-
-
- - diff --git a/src/layouts/AppLayout.astro b/src/layouts/AppLayout.astro new file mode 100644 index 0000000..077243b --- /dev/null +++ b/src/layouts/AppLayout.astro @@ -0,0 +1,23 @@ +--- +import "../styles/global.css"; +--- + + + + + + + + Dev Toolkit + + +
+ +
+ +
+
+ + diff --git a/src/pages/index.astro b/src/pages/index.astro index c04f360..c85e886 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,11 +1,41 @@ --- -import Welcome from '../components/Welcome.astro'; -import Layout from '../layouts/Layout.astro'; - -// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build -// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh. +import AppLayout from "../layouts/AppLayout.astro"; +import Sidebar from "../components/Sidebar.jsx"; --- - - - + +
+ +
+
+

Select a tool

+
+
+
+ + diff --git a/src/styles/global.css b/src/styles/global.css index a461c50..d27b2d6 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1 +1,45 @@ -@import "tailwindcss"; \ No newline at end of file +@tailwind base; +@tailwind components; +@tailwind utilities; + +.slide-fade-out { + animation: slide-fade-out 0.3s forwards; +} + +.slide-fade-in { + animation: slide-fade-in 0.3s forwards; +} + +@keyframes slide-fade-out { + from { + opacity: 1; + transform: translateX(0); + } + to { + opacity: 0; + transform: translateX(-20px); + } +} + +@keyframes slide-fade-in { + from { + opacity: 0; + transform: translateX(30px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes ripple { + to { + transform: scale(4); + opacity: 0; + } +} + +.animate-ripple { + transform: scale(0); + animation: ripple 600ms linear; +}