From 9d4de5c6311efa836f6b862e0041013a2b58374a Mon Sep 17 00:00:00 2001 From: Guru Kannan Date: Thu, 25 Jun 2026 23:16:33 +0530 Subject: [PATCH 1/6] chore: update frontend node_modules and add favicon asset --- .github/workflows/deploy.yml | 54 ++ backend/app.py | 53 ++ backend/requirements.txt | 4 + frontend/.gitignore | 24 + frontend/.oxlintrc.json | 8 + frontend/README.md | 16 + frontend/index.html | 13 + frontend/package-lock.json | 1323 +++++++++++++++++++++++++++++++++ frontend/package.json | 24 + frontend/public/favicon.svg | 1 + frontend/public/icons.svg | 24 + frontend/src/App.css | 184 +++++ frontend/src/App.jsx | 242 ++++++ frontend/src/assets/hero.png | Bin 0 -> 13057 bytes frontend/src/assets/react.svg | 1 + frontend/src/assets/vite.svg | 1 + frontend/src/index.css | 402 ++++++++++ frontend/src/main.jsx | 10 + frontend/vite.config.js | 8 + render.yaml | 10 + 20 files changed, 2402 insertions(+) create mode 100644 .github/workflows/deploy.yml create mode 100644 backend/app.py create mode 100644 backend/requirements.txt create mode 100644 frontend/.gitignore create mode 100644 frontend/.oxlintrc.json create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/public/favicon.svg create mode 100644 frontend/public/icons.svg create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.jsx create mode 100644 frontend/src/assets/hero.png create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/assets/vite.svg create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.jsx create mode 100644 frontend/vite.config.js create mode 100644 render.yaml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..c2eca7056 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,54 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: + - main + - feature/markitdowm-ui + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + working-directory: frontend + + - name: Build + run: npm run build + working-directory: frontend + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: './frontend/dist' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/backend/app.py b/backend/app.py new file mode 100644 index 000000000..1f3c7ad7d --- /dev/null +++ b/backend/app.py @@ -0,0 +1,53 @@ +# pyrefly: ignore [missing-import] +from fastapi import FastAPI, UploadFile, File, HTTPException +# pyrefly: ignore [missing-import] +from fastapi.middleware.cors import CORSMiddleware +from markitdown import MarkItDown +import tempfile +import os +import shutil + +app = FastAPI(title="MarkItDown API") + +# Allow CORS for GitHub Pages frontend +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # In production, restrict this to your GitHub Pages URL + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +markitdown = MarkItDown() + +@app.post("/api/convert") +async def convert_file(file: UploadFile = File(...)): + if not file.filename: + raise HTTPException(status_code=400, detail="No file provided") + + # Create a temporary file to save the uploaded file + # We use a named temporary file so markitdown can read it + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) + try: + # Copy the uploaded file contents to the temporary file + shutil.copyfileobj(file.file, temp_file) + temp_file.close() + + # Convert using MarkItDown + result = markitdown.convert(temp_file.name) + + return { + "filename": file.filename, + "markdown": result.text_content + } + except Exception as e: + raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}") + finally: + file.file.close() + # Clean up the temporary file + if os.path.exists(temp_file.name): + os.unlink(temp_file.name) + +@app.get("/api/health") +async def health_check(): + return {"status": "ok"} diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 000000000..b5aad1735 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.110.1 +uvicorn==0.29.0 +python-multipart==0.0.9 +markitdown==0.0.1a2 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/.oxlintrc.json b/frontend/.oxlintrc.json new file mode 100644 index 000000000..125507823 --- /dev/null +++ b/frontend/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 000000000..d937833bd --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the Oxlint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and Oxlint's TypeScript related rules in your project. diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 000000000..f94d687d3 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 000000000..9c0bf33a2 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1323 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "lucide-react": "^1.21.0", + "react": "^19.2.7", + "react-dom": "^19.2.7" + }, + "devDependencies": { + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.2", + "oxlint": "^1.69.0", + "vite": "^8.1.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.71.0.tgz", + "integrity": "sha512-ImGmd1njEg4FEJH03jhRnveEegtO3czCtfptvaHivKAZQIYATbVFBrrzbaYMYv0oJioTnxZAZVSyV+oL7W8S2g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.71.0.tgz", + "integrity": "sha512-4A5BEexBrwY1YFF8Kiq/lp/wQPRG79G3BWIE1FuWaM5MvmpYSd+7ZySVcKkHdwo0UDzdQGddp6pD9mpctMqLnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.71.0.tgz", + "integrity": "sha512-9wJA9GJulLwS2usU3CEisI/ESDO1n1z9eyTCvApMDrAkbJ1ve0mORgTMjcWWsKxkzkeZ2N/Gpra5IQE7x8tYgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.71.0.tgz", + "integrity": "sha512-PlLCjS06V0PeJMAJwzjrExw1sYNW9Gch3JtNlcwwZDXGlTYDuwHNN89zYH8LTXFfgkVtsYvs2nv0FqrzyuFDzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.71.0.tgz", + "integrity": "sha512-Lhil7bWre0ncxbUoDoxfS0JzpTz17BRQKW7iwoAUY8GJ66+WwJEfYPCFJ1P0WgVZR5/O/b3Q2pENlHOjeXLOGQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.71.0.tgz", + "integrity": "sha512-Oo9/L58PYD3RC0x05d2upAPLllHytTjHQGsnC06P6Ynn7jKkp5mdImQxXdJ3+FnBaKspNpGogzgVsi6g872LiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.71.0.tgz", + "integrity": "sha512-mSHfyfgJrEbyIR29ejaeS50BdPk+GoNPlC1dckpDiUZbJAIel68sjSMdOt4WY0/gva+ECC7FNITQkxMJU+vSBw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.71.0.tgz", + "integrity": "sha512-n9yY4M2tiy3aij4AqtlnspzpfdpeT5JQfK2/w2d8oyp5W0FRwOb1dIeX99nORNcxGr08iD9bH8N5XFz3I2iy8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.71.0.tgz", + "integrity": "sha512-fJZrs5sDZtTaPIOiemRQQmo82Ezy+vOGXemPc4Ok7iVVsYsFa7SlW6Z5XN819VfsqBHRm3NJ3rTdnR8+bJYJdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.71.0.tgz", + "integrity": "sha512-cwl7VKGERIy9p+G+AvZdfy/06q0aHXaTt/mMRReC751iuNYJgqKjB7NydXSS30nBT9vtr2tunciOtrR4fD6FUA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.71.0.tgz", + "integrity": "sha512-eZ8ieVXvzGi8jr7+ybQGPK2STw3mldfxZlgA2738iflfB/rzA69sE6m5rDRpQaxC7dpm745Enlh1Tod0QAk9Gg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.71.0.tgz", + "integrity": "sha512-puMDbQYe6+NXwfMusojoA7CXGn2b3utukmd23PQqc1E3XhVCwyZ+FueSMzDYeNgDV2dUfIVXAAKZBcFDeCL6sA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.71.0.tgz", + "integrity": "sha512-4NJLxBs1ujISCt3L/1FcywLs73PWtJuw+piD6feK2V6h6OS6P7xu9/sWt1DTRLibe6QCzmfZzmM/2HPORoV/Lg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.71.0.tgz", + "integrity": "sha512-cFDaiR8L3430qp88tfZnvFlt3KotFhR/DlbIL0nHOMMYiG/9Wy4l+6f7t8G8pTa9bd8Lt8+M0y/qjRQ/xcB74g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.71.0.tgz", + "integrity": "sha512-orfixdt76KlpNly9z0PkWBBNfwjKz+JFVLP/7wnVchlKNU9Dpt9InU/ZggeSej6fC7qwHmHNOGlhLnQXcYoGuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.71.0.tgz", + "integrity": "sha512-9emQu2lAp6yhPB3XuI+++vR+l/o6JR1X+EpxwcumPdQXBWXEPAsquPGL7l158EqU8SebQMXTUa/S5zN98juyHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.71.0.tgz", + "integrity": "sha512-bd5kI8spYwTm3BILDtGhi73zoup5dw8MlPQNT8YB3BD5UIsjNe3K9/4ctrzQMX4SZMoK5HgzVLkLJzacEXB7fA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.71.0.tgz", + "integrity": "sha512-W4HvOHGzVLHcrmFu+bMrJlho+/yrlX5ZNdJZqGe8MEldkQG+RHYhxxad9P4jvWAYFmIqUA5i9DQ8QsJqSU9GIw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.71.0.tgz", + "integrity": "sha512-D2kyEIPHk/G/wiZLnwTVC/sVst+T/lKldVOjAFpgTIBUAOlry72e5OiapDbDBF4LfJLkN5ypJb/8Eu6yJzkveQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lucide-react": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.21.0.tgz", + "integrity": "sha512-reEZMXq8Qdd5jg5XYkQ5TR1fB/GiQ7ih4vcrthYDtgjSDwh0i6/YLiGjsWsIwgN49gpAnd4J2elSNzncMEEUUQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oxlint": { + "version": "1.71.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.71.0.tgz", + "integrity": "sha512-U1m1X+C0vDj7DC1e13IoZULzEcPczE7UOMTs8VlZGHUEIUaSTZKo5qkPsQEfzpgnQ29Pea/w3Xntk62UCecxZw==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.71.0", + "@oxlint/binding-android-arm64": "1.71.0", + "@oxlint/binding-darwin-arm64": "1.71.0", + "@oxlint/binding-darwin-x64": "1.71.0", + "@oxlint/binding-freebsd-x64": "1.71.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.71.0", + "@oxlint/binding-linux-arm-musleabihf": "1.71.0", + "@oxlint/binding-linux-arm64-gnu": "1.71.0", + "@oxlint/binding-linux-arm64-musl": "1.71.0", + "@oxlint/binding-linux-ppc64-gnu": "1.71.0", + "@oxlint/binding-linux-riscv64-gnu": "1.71.0", + "@oxlint/binding-linux-riscv64-musl": "1.71.0", + "@oxlint/binding-linux-s390x-gnu": "1.71.0", + "@oxlint/binding-linux-x64-gnu": "1.71.0", + "@oxlint/binding-linux-x64-musl": "1.71.0", + "@oxlint/binding-openharmony-arm64": "1.71.0", + "@oxlint/binding-win32-arm64-msvc": "1.71.0", + "@oxlint/binding-win32-ia32-msvc": "1.71.0", + "@oxlint/binding-win32-x64-msvc": "1.71.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.22.1", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/rolldown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/vite": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", + "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "~1.1.2", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 000000000..9c288884b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "oxlint", + "preview": "vite preview" + }, + "dependencies": { + "lucide-react": "^1.21.0", + "react": "^19.2.7", + "react-dom": "^19.2.7" + }, + "devDependencies": { + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.2", + "oxlint": "^1.69.0", + "vite": "^8.1.0" + } +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 000000000..6893eb132 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg new file mode 100644 index 000000000..e9522193d --- /dev/null +++ b/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 000000000..f90339d8f --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,184 @@ +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 000000000..5cc91cfdd --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,242 @@ +import { useState, useRef, useEffect } from 'react' +import { UploadCloud, FileType, X, Download, Copy, Settings, CheckCircle, AlertCircle, Loader2 } from 'lucide-react' + +function App() { + const [file, setFile] = useState(null) + const [isDragging, setIsDragging] = useState(false) + const [isConverting, setIsConverting] = useState(false) + const [result, setResult] = useState(null) + const [error, setError] = useState(null) + const [backendUrl, setBackendUrl] = useState('http://localhost:8000') // Default for local + const [copied, setCopied] = useState(false) + const fileInputRef = useRef(null) + + // Extract from query params if available (for production) + useEffect(() => { + const params = new URLSearchParams(window.location.search) + const urlParam = params.get('backend') + if (urlParam) setBackendUrl(urlParam) + }, []) + + const handleDragOver = (e) => { + e.preventDefault() + setIsDragging(true) + } + + const handleDragLeave = () => { + setIsDragging(false) + } + + const handleDrop = (e) => { + e.preventDefault() + setIsDragging(false) + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + setFile(e.dataTransfer.files[0]) + setError(null) + setResult(null) + } + } + + const handleFileChange = (e) => { + if (e.target.files && e.target.files.length > 0) { + setFile(e.target.files[0]) + setError(null) + setResult(null) + } + } + + const handleConvert = async () => { + if (!file) return + + setIsConverting(true) + setError(null) + + const formData = new FormData() + formData.append('file', file) + + try { + const response = await fetch(`${backendUrl}/api/convert`, { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.detail || `Server error: ${response.status}`) + } + + const data = await response.json() + setResult(data.markdown) + } catch (err) { + console.error(err) + setError(err.message || 'An unexpected error occurred. Is the backend running?') + } finally { + setIsConverting(false) + } + } + + const handleCopy = () => { + if (!result) return + navigator.clipboard.writeText(result) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + const handleDownload = () => { + if (!result) return + const blob = new Blob([result], { type: 'text/markdown' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + // remove extension and add .md + const filename = file ? file.name.split('.').slice(0, -1).join('.') + '.md' : 'document.md' + a.download = filename + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + + return ( +
+
+

MarkItDown

+

Convert almost any document to Markdown instantly.

+
+ +
+ {/* Left Panel: Upload */} +
+
+

+ + Upload Document +

+
+ +
fileInputRef.current?.click()} + > + + +

Click or drag & drop a file

+

Supports PDF, PPTX, DOCX, XLSX, Images, HTML, CSV, and more.

+
+ + {file && ( +
+
+ + {file.name} + + ({(file.size / 1024).toFixed(1)} KB) + +
+ +
+ )} + +
+

+ + Configuration +

+
+ + setBackendUrl(e.target.value)} + placeholder="https://your-render-url.onrender.com" + /> +
+
+ + + + {error && ( +
+ + {error} +
+ )} + {result && ( +
+ + Conversion successful! +
+ )} +
+ + {/* Right Panel: Preview */} +
+
+

+ Markdown Output +

+
+ + +
+
+ +
+ {result ? ( + result + ) : ( +
+ +

Your generated markdown will appear here.

+
+ )} +
+
+
+
+ ) +} + +export default App diff --git a/frontend/src/assets/hero.png b/frontend/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..02251f4b956c55af2d76fd0788124d7eee2b45eb GIT binary patch literal 13057 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 P00000NkvXXu0mjft=yBf literal 0 HcmV?d00001 diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg new file mode 100644 index 000000000..5101b674d --- /dev/null +++ b/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 000000000..89cc4e829 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,402 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +:root { + --bg-color: #0f172a; + --panel-bg: rgba(30, 41, 59, 0.7); + --text-primary: #f8fafc; + --text-secondary: #94a3b8; + --accent-color: #3b82f6; + --accent-hover: #2563eb; + --border-color: rgba(255, 255, 255, 0.1); + --glass-border: rgba(255, 255, 255, 0.05); + --success-color: #10b981; + --error-color: #ef4444; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + background-color: var(--bg-color); + color: var(--text-primary); + min-height: 100vh; + display: flex; + flex-direction: column; + background-image: + radial-gradient(at 0% 0%, rgba(59, 130, 246, 0.15) 0px, transparent 50%), + radial-gradient(at 100% 100%, rgba(139, 92, 246, 0.15) 0px, transparent 50%); + background-attachment: fixed; + overflow-x: hidden; +} + +#root { + display: flex; + flex-direction: column; + min-height: 100vh; + width: 100%; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; + width: 100%; + flex: 1; + display: flex; + flex-direction: column; +} + +header { + text-align: center; + margin-bottom: 3rem; + animation: fadeInDown 0.8s ease-out; +} + +h1 { + font-size: 3rem; + font-weight: 700; + background: linear-gradient(to right, #60a5fa, #a78bfa); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + margin-bottom: 0.5rem; + letter-spacing: -0.025em; +} + +.subtitle { + color: var(--text-secondary); + font-size: 1.1rem; +} + +.main-content { + display: grid; + grid-template-columns: 1fr; + gap: 2rem; + flex: 1; +} + +@media (min-width: 900px) { + .main-content { + grid-template-columns: 1fr 1fr; + } +} + +.panel { + background: var(--panel-bg); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid var(--glass-border); + border-radius: 1rem; + padding: 2rem; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); + animation: fadeInUp 0.8s ease-out forwards; + display: flex; + flex-direction: column; +} + +.panel:nth-child(2) { + animation-delay: 0.2s; +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + padding-bottom: 1rem; + border-bottom: 1px solid var(--border-color); +} + +.panel-title { + font-size: 1.25rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.upload-area { + flex: 1; + border: 2px dashed var(--border-color); + border-radius: 0.75rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 3rem 2rem; + text-align: center; + transition: all 0.3s ease; + cursor: pointer; + background: rgba(255, 255, 255, 0.02); + position: relative; + overflow: hidden; +} + +.upload-area:hover, .upload-area.drag-active { + border-color: var(--accent-color); + background: rgba(59, 130, 246, 0.05); +} + +.upload-icon { + color: var(--text-secondary); + margin-bottom: 1rem; + transition: color 0.3s ease, transform 0.3s ease; +} + +.upload-area:hover .upload-icon, .upload-area.drag-active .upload-icon { + color: var(--accent-color); + transform: translateY(-5px); +} + +.upload-text { + font-weight: 500; + margin-bottom: 0.5rem; +} + +.upload-hint { + color: var(--text-secondary); + font-size: 0.875rem; +} + +.file-input { + display: none; +} + +.file-info { + margin-top: 1.5rem; + padding: 1rem; + background: rgba(0, 0, 0, 0.2); + border-radius: 0.5rem; + border: 1px solid var(--border-color); + display: flex; + align-items: center; + justify-content: space-between; +} + +.file-name { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.remove-btn { + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + transition: color 0.2s; + display: flex; + align-items: center; + justify-content: center; +} + +.remove-btn:hover { + color: var(--error-color); +} + +.convert-btn { + margin-top: 1.5rem; + background: linear-gradient(to right, var(--accent-color), #6366f1); + color: white; + border: none; + padding: 1rem; + border-radius: 0.5rem; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + width: 100%; +} + +.convert-btn:hover:not(:disabled) { + box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); + transform: translateY(-2px); +} + +.convert-btn:disabled { + opacity: 0.7; + cursor: not-allowed; + filter: grayscale(0.5); +} + +.preview-area { + flex: 1; + background: rgba(0, 0, 0, 0.2); + border-radius: 0.5rem; + border: 1px solid var(--border-color); + padding: 1.5rem; + overflow-y: auto; + max-height: 500px; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 0.875rem; + line-height: 1.6; + white-space: pre-wrap; + color: #e2e8f0; +} + +.preview-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: var(--text-secondary); + text-align: center; + opacity: 0.5; +} + +.action-btns { + display: flex; + gap: 0.5rem; +} + +.icon-btn { + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--glass-border); + color: var(--text-primary); + width: 2.5rem; + height: 2.5rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s; +} + +.icon-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--accent-color); +} + +.settings-panel { + margin-top: 1.5rem; + padding-top: 1.5rem; + border-top: 1px solid var(--border-color); +} + +.settings-title { + font-size: 0.875rem; + color: var(--text-secondary); + margin-bottom: 1rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.setting-row { + margin-bottom: 1rem; +} + +.input-label { + display: block; + font-size: 0.875rem; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.text-input { + width: 100%; + background: rgba(0, 0, 0, 0.2); + border: 1px solid var(--border-color); + color: white; + padding: 0.75rem 1rem; + border-radius: 0.5rem; + font-family: inherit; + transition: border-color 0.2s; +} + +.text-input:focus { + outline: none; + border-color: var(--accent-color); +} + +.status-message { + margin-top: 1rem; + padding: 0.75rem; + border-radius: 0.5rem; + font-size: 0.875rem; + display: flex; + align-items: center; + gap: 0.5rem; + animation: fadeIn 0.3s ease; +} + +.status-error { + background: rgba(239, 68, 68, 0.1); + color: #fca5a5; + border: 1px solid rgba(239, 68, 68, 0.2); +} + +.status-success { + background: rgba(16, 185, 129, 0.1); + color: #6ee7b7; + border: 1px solid rgba(16, 185, 129, 0.2); +} + +.spinner { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Custom Scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.1); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 000000000..b9a1a6dea --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 000000000..3b60cf3b6 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + base: '/markitdown-agent-ui/', +}) diff --git a/render.yaml b/render.yaml new file mode 100644 index 000000000..01f337974 --- /dev/null +++ b/render.yaml @@ -0,0 +1,10 @@ +services: + - type: web + name: markitdown-api + env: python + rootDir: backend + buildCommand: pip install -r requirements.txt + startCommand: uvicorn app:app --host 0.0.0.0 --port $PORT + envVars: + - key: PYTHON_VERSION + value: 3.10.14 From a7cc47b3a640bce4a13ae89e2870e08a7598efb7 Mon Sep 17 00:00:00 2001 From: Guru Kannan Date: Thu, 25 Jun 2026 23:35:29 +0530 Subject: [PATCH 2/6] chore: update default backend URL to live production environment --- frontend/src/App.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5cc91cfdd..f1ee16b27 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -7,7 +7,7 @@ function App() { const [isConverting, setIsConverting] = useState(false) const [result, setResult] = useState(null) const [error, setError] = useState(null) - const [backendUrl, setBackendUrl] = useState('http://localhost:8000') // Default for local + const [backendUrl, setBackendUrl] = useState('https://markitdown-agent-ui.onrender.com') // Default to live backend const [copied, setCopied] = useState(false) const fileInputRef = useRef(null) From 40d5c3da6c5d9bdd5345e67c750731914d3e1c64 Mon Sep 17 00:00:00 2001 From: Guru Kannan Date: Thu, 25 Jun 2026 23:37:31 +0530 Subject: [PATCH 3/6] chore: update dependency installation command from npm ci to npm install in deployment workflow --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c2eca7056..d4926cc9e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -34,7 +34,7 @@ jobs: cache-dependency-path: frontend/package-lock.json - name: Install dependencies - run: npm ci + run: npm install working-directory: frontend - name: Build From 53eaa132a320c02cb35c824c61091ac9e72ce9b6 Mon Sep 17 00:00:00 2001 From: Guru Kannan Date: Thu, 25 Jun 2026 23:57:06 +0530 Subject: [PATCH 4/6] build: upgrade Node.js version to 24 in deployment workflow --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d4926cc9e..25d3e4441 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -29,7 +29,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: 'npm' cache-dependency-path: frontend/package-lock.json From 90b6d05ffd2b650a1cf9c8ab5e38b65ff2418169 Mon Sep 17 00:00:00 2001 From: Guru Kannan Date: Fri, 26 Jun 2026 00:04:25 +0530 Subject: [PATCH 5/6] feat: redesign frontend UI with responsive workspace, improved file handling, and configurable settings --- frontend/index.html | 4 +- frontend/src/App.jsx | 274 ++++++++++--------- frontend/src/index.css | 585 +++++++++++++++++++++++++++-------------- 3 files changed, 535 insertions(+), 328 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index f94d687d3..ad2a7ee6c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,9 @@ - + - frontend + MarkItDown Web UI
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f1ee16b27..ea4b2b0de 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect } from 'react' -import { UploadCloud, FileType, X, Download, Copy, Settings, CheckCircle, AlertCircle, Loader2 } from 'lucide-react' +import { UploadCloud, FileText, X, Download, Copy, Settings, CheckCircle, AlertCircle, Loader2, Github } from 'lucide-react' function App() { const [file, setFile] = useState(null) @@ -7,11 +7,11 @@ function App() { const [isConverting, setIsConverting] = useState(false) const [result, setResult] = useState(null) const [error, setError] = useState(null) - const [backendUrl, setBackendUrl] = useState('https://markitdown-agent-ui.onrender.com') // Default to live backend + const [backendUrl, setBackendUrl] = useState('https://markitdown-agent-ui.onrender.com') const [copied, setCopied] = useState(false) + const [showSettings, setShowSettings] = useState(false) const fileInputRef = useRef(null) - // Extract from query params if available (for production) useEffect(() => { const params = new URLSearchParams(window.location.search) const urlParam = params.get('backend') @@ -88,8 +88,19 @@ function App() { const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url - // remove extension and add .md - const filename = file ? file.name.split('.').slice(0, -1).join('.') + '.md' : 'document.md' + + // Robust filename extraction to handle files with or without extensions + let filename = 'document.md'; + if (file && file.name) { + const parts = file.name.split('.'); + if (parts.length > 1) { + parts.pop(); // Remove the old extension + filename = parts.join('.') + '.md'; + } else { + filename = file.name + '.md'; + } + } + a.download = filename document.body.appendChild(a) a.click() @@ -97,144 +108,151 @@ function App() { URL.revokeObjectURL(url) } + const clearFile = () => { + setFile(null) + setResult(null) + setError(null) + if (fileInputRef.current) fileInputRef.current.value = "" + } + return ( -
-
-

MarkItDown

-

Convert almost any document to Markdown instantly.

-
- -
- {/* Left Panel: Upload */} -
-
-

- - Upload Document -

-
+
+ + +
+
+

Universal File Conversion

+

Transform PDFs, Word docs, Excel, Images, and more into clean Markdown instantly.

+
-
fileInputRef.current?.click()} - > + {showSettings && ( +
+ setBackendUrl(e.target.value)} + placeholder="https://your-api-url.com" /> - -

Click or drag & drop a file

-

Supports PDF, PPTX, DOCX, XLSX, Images, HTML, CSV, and more.

+

Must be a valid FastAPI backend running the MarkItDown wrapper.

+ )} - {file && ( -
-
- - {file.name} - - ({(file.size / 1024).toFixed(1)} KB) - -
- +
+ {/* Left / Top Panel - Upload & Controls */} +
+
+

Input Document

- )} + +
+ {!file ? ( +
fileInputRef.current?.click()} + > + +
+ +
+

Upload a file

+

Drag and drop or click to browse

+
+ ) : ( +
+
+
+ +
+
+ {file.name} + {(file.size / 1024).toFixed(1)} KB +
+ +
-
-

- - Configuration -

-
- - setBackendUrl(e.target.value)} - placeholder="https://your-render-url.onrender.com" - /> + + + {error && ( +
+ +

{error}

+
+ )} + {result && ( +
+ +

Conversion successful!

+
+ )} +
+ )}
- - - {error && ( -
- - {error} -
- )} + {/* Right / Bottom Panel - Result */} {result && ( -
- - Conversion successful! +
+
+

Markdown Output

+
+ + +
+
+
+
+                  {result}
+                
+
)} -
- - {/* Right Panel: Preview */} -
-
-

- Markdown Output -

-
- - -
-
- -
- {result ? ( - result - ) : ( -
- -

Your generated markdown will appear here.

-
- )} -
-
+
+ + ) } diff --git a/frontend/src/index.css b/frontend/src/index.css index 89cc4e829..f5b4adcca 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,16 +1,27 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap'); :root { - --bg-color: #0f172a; - --panel-bg: rgba(30, 41, 59, 0.7); - --text-primary: #f8fafc; - --text-secondary: #94a3b8; - --accent-color: #3b82f6; - --accent-hover: #2563eb; - --border-color: rgba(255, 255, 255, 0.1); - --glass-border: rgba(255, 255, 255, 0.05); - --success-color: #10b981; - --error-color: #ef4444; + --bg-main: #09090b; + --bg-card: rgba(24, 24, 27, 0.6); + --bg-input: rgba(39, 39, 42, 0.5); + --bg-hover: rgba(63, 63, 70, 0.4); + + --border-light: rgba(255, 255, 255, 0.08); + --border-focus: rgba(168, 85, 247, 0.5); + + --text-primary: #fafafa; + --text-secondary: #a1a1aa; + --text-muted: #71717a; + + --accent-color: #a855f7; + --accent-glow: rgba(168, 85, 247, 0.25); + --accent-hover: #9333ea; + + --success: #10b981; + --error: #ef4444; + + --font-sans: 'Plus Jakarta Sans', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', monospace; } * { @@ -20,194 +31,353 @@ } body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - background-color: var(--bg-color); + font-family: var(--font-sans); + background-color: var(--bg-main); color: var(--text-primary); min-height: 100vh; - display: flex; - flex-direction: column; + -webkit-font-smoothing: antialiased; background-image: - radial-gradient(at 0% 0%, rgba(59, 130, 246, 0.15) 0px, transparent 50%), - radial-gradient(at 100% 100%, rgba(139, 92, 246, 0.15) 0px, transparent 50%); + radial-gradient(circle at 15% 50%, rgba(168, 85, 247, 0.08), transparent 25%), + radial-gradient(circle at 85% 30%, rgba(59, 130, 246, 0.08), transparent 25%); background-attachment: fixed; overflow-x: hidden; } #root { display: flex; - flex-direction: column; min-height: 100vh; width: 100%; } -.container { - max-width: 1200px; - margin: 0 auto; - padding: 2rem; +.app-wrapper { + display: flex; + flex-direction: column; + width: 100%; + min-height: 100vh; +} + +/* Navbar */ +.navbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; + border-bottom: 1px solid var(--border-light); + background: rgba(9, 9, 11, 0.8); + backdrop-filter: blur(12px); + position: sticky; + top: 0; + z-index: 50; +} + +.logo { + display: flex; + align-items: center; + gap: 0.75rem; + font-weight: 700; + font-size: 1.1rem; + letter-spacing: -0.02em; +} + +.logo-icon { + background: linear-gradient(135deg, var(--accent-color), #3b82f6); + color: white; + width: 32px; + height: 32px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 800; + box-shadow: 0 0 15px var(--accent-glow); +} + +.settings-btn { + background: transparent; + border: 1px solid var(--border-light); + color: var(--text-secondary); + width: 36px; + height: 36px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s; +} + +.settings-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +/* Settings Dropdown */ +.settings-dropdown { + max-width: 1000px; + margin: 0 auto 2rem auto; + width: calc(100% - 4rem); + background: var(--bg-card); + border: 1px solid var(--border-light); + border-radius: 12px; + padding: 1.5rem; + backdrop-filter: blur(8px); +} + +.settings-dropdown label { + display: block; + font-size: 0.875rem; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: 0.5rem; +} + +.settings-dropdown input { width: 100%; + background: var(--bg-input); + border: 1px solid var(--border-light); + color: var(--text-primary); + padding: 0.75rem 1rem; + border-radius: 8px; + font-family: var(--font-mono); + font-size: 0.875rem; + transition: all 0.2s; +} + +.settings-dropdown input:focus { + outline: none; + border-color: var(--border-focus); + box-shadow: 0 0 0 1px var(--border-focus); +} + +.settings-hint { + font-size: 0.75rem; + color: var(--text-muted); + margin-top: 0.5rem; +} + +/* Main Container */ +.main-container { flex: 1; + width: 100%; + max-width: 1400px; + margin: 0 auto; + padding: 3rem 2rem; display: flex; flex-direction: column; } -header { +.hero-section { text-align: center; margin-bottom: 3rem; - animation: fadeInDown 0.8s ease-out; + animation: fade-in-down 0.6s ease-out; } -h1 { - font-size: 3rem; +.hero-section h1 { + font-size: 3.5rem; font-weight: 700; - background: linear-gradient(to right, #60a5fa, #a78bfa); + letter-spacing: -0.04em; + line-height: 1.1; + margin-bottom: 1rem; + background: linear-gradient(to right, #ffffff, #a1a1aa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; - margin-bottom: 0.5rem; - letter-spacing: -0.025em; } -.subtitle { +.hero-section p { color: var(--text-secondary); - font-size: 1.1rem; + font-size: 1.125rem; + max-width: 600px; + margin: 0 auto; } -.main-content { +/* Workspace Layout */ +.workspace { display: grid; - grid-template-columns: 1fr; gap: 2rem; - flex: 1; + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + width: 100%; +} + +.workspace.single-view { + grid-template-columns: minmax(auto, 600px); + justify-content: center; } -@media (min-width: 900px) { - .main-content { - grid-template-columns: 1fr 1fr; +.workspace.split-view { + grid-template-columns: 1fr; +} + +@media (min-width: 1024px) { + .workspace.split-view { + grid-template-columns: 400px 1fr; + align-items: flex-start; } } -.panel { - background: var(--panel-bg); +/* Cards */ +.card { + background: var(--bg-card); + border: 1px solid var(--border-light); + border-radius: 16px; backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border: 1px solid var(--glass-border); - border-radius: 1rem; - padding: 2rem; - box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); - animation: fadeInUp 0.8s ease-out forwards; + overflow: hidden; display: flex; flex-direction: column; + box-shadow: 0 4px 24px -1px rgba(0, 0, 0, 0.2); } -.panel:nth-child(2) { - animation-delay: 0.2s; -} - -.panel-header { +.card-header { + padding: 1.25rem 1.5rem; + border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center; - margin-bottom: 1.5rem; - padding-bottom: 1rem; - border-bottom: 1px solid var(--border-color); + background: rgba(255, 255, 255, 0.02); } -.panel-title { - font-size: 1.25rem; +.card-header h2 { + font-size: 1rem; font-weight: 600; - display: flex; - align-items: center; - gap: 0.5rem; + color: var(--text-primary); } -.upload-area { - flex: 1; - border: 2px dashed var(--border-color); - border-radius: 0.75rem; +.card-body { + padding: 1.5rem; display: flex; flex-direction: column; - align-items: center; - justify-content: center; + flex: 1; +} + +.card-body.no-padding { + padding: 0; +} + +/* Drop Zone */ +.drop-zone { + border: 2px dashed var(--border-light); + border-radius: 12px; padding: 3rem 2rem; text-align: center; - transition: all 0.3s ease; cursor: pointer; - background: rgba(255, 255, 255, 0.02); - position: relative; - overflow: hidden; + transition: all 0.3s ease; + background: rgba(0, 0, 0, 0.2); } -.upload-area:hover, .upload-area.drag-active { +.drop-zone:hover, .drop-zone.dragging { border-color: var(--accent-color); - background: rgba(59, 130, 246, 0.05); + background: var(--bg-hover); +} + +.hidden-input { + display: none; +} + +.drop-icon-wrapper { + width: 80px; + height: 80px; + background: var(--bg-input); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 1.5rem auto; + border: 1px solid var(--border-light); + transition: transform 0.3s ease; +} + +.drop-zone:hover .drop-icon-wrapper { + transform: scale(1.05); + border-color: var(--border-focus); + box-shadow: 0 0 20px var(--accent-glow); } -.upload-icon { +.drop-icon { color: var(--text-secondary); - margin-bottom: 1rem; - transition: color 0.3s ease, transform 0.3s ease; } -.upload-area:hover .upload-icon, .upload-area.drag-active .upload-icon { +.drop-zone:hover .drop-icon { color: var(--accent-color); - transform: translateY(-5px); } -.upload-text { - font-weight: 500; +.drop-zone h3 { + font-size: 1.125rem; + font-weight: 600; margin-bottom: 0.5rem; } -.upload-hint { - color: var(--text-secondary); +.drop-zone p { + color: var(--text-muted); font-size: 0.875rem; } -.file-input { - display: none; +/* File Active State */ +.file-active-state { + display: flex; + flex-direction: column; + gap: 1.5rem; + animation: fade-in 0.4s ease; } -.file-info { - margin-top: 1.5rem; - padding: 1rem; - background: rgba(0, 0, 0, 0.2); - border-radius: 0.5rem; - border: 1px solid var(--border-color); +.file-card { display: flex; align-items: center; - justify-content: space-between; + gap: 1rem; + background: var(--bg-input); + padding: 1rem; + border-radius: 12px; + border: 1px solid var(--border-light); } -.file-name { +.file-icon { + color: var(--accent-color); + background: var(--accent-glow); + padding: 0.5rem; + border-radius: 8px; +} + +.file-details { + flex: 1; display: flex; - align-items: center; - gap: 0.5rem; + flex-direction: column; + overflow: hidden; +} + +.filename { font-weight: 500; + font-size: 0.95rem; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; } -.remove-btn { +.filesize { + font-size: 0.75rem; + color: var(--text-muted); + margin-top: 0.25rem; +} + +.remove-file { background: transparent; border: none; - color: var(--text-secondary); + color: var(--text-muted); cursor: pointer; - transition: color 0.2s; - display: flex; - align-items: center; - justify-content: center; + padding: 0.5rem; + border-radius: 6px; + transition: all 0.2s; } -.remove-btn:hover { - color: var(--error-color); +.remove-file:hover { + background: rgba(239, 68, 68, 0.1); + color: var(--error); } -.convert-btn { - margin-top: 1.5rem; - background: linear-gradient(to right, var(--accent-color), #6366f1); - color: white; +/* Buttons */ +.primary-btn { + background: var(--text-primary); + color: var(--bg-main); border: none; padding: 1rem; - border-radius: 0.5rem; + border-radius: 12px; font-size: 1rem; font-weight: 600; cursor: pointer; @@ -215,59 +385,48 @@ h1 { display: flex; align-items: center; justify-content: center; - gap: 0.5rem; + gap: 0.75rem; width: 100%; + font-family: var(--font-sans); } -.convert-btn:hover:not(:disabled) { - box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); +.primary-btn:hover:not(:disabled) { transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(255, 255, 255, 0.2); } -.convert-btn:disabled { - opacity: 0.7; - cursor: not-allowed; - filter: grayscale(0.5); +.primary-btn:active:not(:disabled) { + transform: translateY(0); } -.preview-area { - flex: 1; - background: rgba(0, 0, 0, 0.2); - border-radius: 0.5rem; - border: 1px solid var(--border-color); - padding: 1.5rem; - overflow-y: auto; - max-height: 500px; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - font-size: 0.875rem; - line-height: 1.6; - white-space: pre-wrap; - color: #e2e8f0; +.primary-btn:disabled { + opacity: 0.5; + cursor: not-allowed; } -.preview-placeholder { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; +.primary-btn.converting { + background: var(--bg-hover); color: var(--text-secondary); - text-align: center; - opacity: 0.5; + border: 1px solid var(--border-light); } -.action-btns { +.spin { + animation: spin 1s linear infinite; +} + +/* Action Group */ +.action-group { display: flex; gap: 0.5rem; } .icon-btn { - background: rgba(255, 255, 255, 0.05); - border: 1px solid var(--glass-border); - color: var(--text-primary); - width: 2.5rem; - height: 2.5rem; - border-radius: 0.5rem; + background: var(--bg-input); + border: 1px solid var(--border-light); + color: var(--text-secondary); + width: 32px; + height: 32px; + border-radius: 8px; display: flex; align-items: center; justify-content: center; @@ -276,93 +435,112 @@ h1 { } .icon-btn:hover { - background: rgba(255, 255, 255, 0.1); - color: var(--accent-color); + background: var(--bg-hover); + color: var(--text-primary); + border-color: var(--border-focus); } -.settings-panel { - margin-top: 1.5rem; - padding-top: 1.5rem; - border-top: 1px solid var(--border-color); +/* Alerts */ +.alert { + padding: 1rem; + border-radius: 12px; + display: flex; + align-items: center; + gap: 0.75rem; + font-size: 0.875rem; + font-weight: 500; } -.settings-title { - font-size: 0.875rem; - color: var(--text-secondary); - margin-bottom: 1rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; +.alert-error { + background: rgba(239, 68, 68, 0.1); + border: 1px solid rgba(239, 68, 68, 0.2); + color: #fca5a5; } -.setting-row { - margin-bottom: 1rem; +.alert-success { + background: rgba(16, 185, 129, 0.1); + border: 1px solid rgba(16, 185, 129, 0.2); + color: #6ee7b7; } -.input-label { - display: block; +/* Result Area */ +.result-card { + height: 100%; + min-height: 500px; +} + +.code-block { + margin: 0; + padding: 1.5rem; + background: transparent; + color: #e2e8f0; + font-family: var(--font-mono); font-size: 0.875rem; - margin-bottom: 0.5rem; - color: var(--text-primary); + line-height: 1.6; + white-space: pre-wrap; + word-break: break-word; + overflow-y: auto; + max-height: 700px; } -.text-input { - width: 100%; - background: rgba(0, 0, 0, 0.2); - border: 1px solid var(--border-color); - color: white; - padding: 0.75rem 1rem; - border-radius: 0.5rem; - font-family: inherit; - transition: border-color 0.2s; +.text-success { + color: var(--success); } -.text-input:focus { - outline: none; - border-color: var(--accent-color); +/* Footer */ +.footer { + margin-top: auto; + padding: 2rem; + border-top: 1px solid var(--border-light); + background: rgba(9, 9, 11, 0.5); + backdrop-filter: blur(12px); } -.status-message { - margin-top: 1rem; - padding: 0.75rem; - border-radius: 0.5rem; - font-size: 0.875rem; +.footer-content { display: flex; align-items: center; - gap: 0.5rem; - animation: fadeIn 0.3s ease; + justify-content: center; + gap: 1rem; + font-size: 0.875rem; + color: var(--text-muted); } -.status-error { - background: rgba(239, 68, 68, 0.1); - color: #fca5a5; - border: 1px solid rgba(239, 68, 68, 0.2); +.footer-content a { + color: var(--text-secondary); + text-decoration: none; + font-weight: 500; + transition: color 0.2s; } -.status-success { - background: rgba(16, 185, 129, 0.1); - color: #6ee7b7; - border: 1px solid rgba(16, 185, 129, 0.2); +.footer-content a:hover { + color: var(--text-primary); } -.spinner { - animation: spin 1s linear infinite; +.author-link { + display: inline-flex; + align-items: center; + gap: 0.4rem; +} + +.divider { + opacity: 0.5; } +/* Animations */ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } -@keyframes fadeIn { +@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } -@keyframes fadeInUp { +@keyframes fade-in-up { from { opacity: 0; - transform: translateY(20px); + transform: translateY(15px); } to { opacity: 1; @@ -370,10 +548,10 @@ h1 { } } -@keyframes fadeInDown { +@keyframes fade-in-down { from { opacity: 0; - transform: translateY(-20px); + transform: translateY(-15px); } to { opacity: 1; @@ -381,22 +559,33 @@ h1 { } } -/* Custom Scrollbar */ +.fade-in { + animation: fade-in 0.4s ease forwards; +} + +.fade-in-up { + animation: fade-in-up 0.5s ease forwards; +} + +.slide-down { + animation: fade-in-down 0.3s ease forwards; +} + +/* Scrollbar */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { - background: rgba(0, 0, 0, 0.1); - border-radius: 4px; + background: transparent; } ::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.1); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.2); } From de1bc4e31bbcf283479ffe06a5acaf7637182754 Mon Sep 17 00:00:00 2001 From: Guru Kannan Date: Fri, 26 Jun 2026 00:14:15 +0530 Subject: [PATCH 6/6] style: enhance UI with smooth animations, updated spacing, and refined styling across the application --- frontend/src/App.jsx | 4 +- frontend/src/index.css | 431 +++++++++++++++++++++++++---------------- 2 files changed, 265 insertions(+), 170 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ea4b2b0de..33d2bd6fa 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect } from 'react' -import { UploadCloud, FileText, X, Download, Copy, Settings, CheckCircle, AlertCircle, Loader2, Github } from 'lucide-react' +import { UploadCloud, FileText, X, Download, Copy, Settings, CheckCircle, AlertCircle, Loader2 } from 'lucide-react' function App() { const [file, setFile] = useState(null) @@ -249,7 +249,7 @@ function App() {

- UI crafted by gurukannan22 + UI crafted by gurukannan22

diff --git a/frontend/src/index.css b/frontend/src/index.css index f5b4adcca..34952fd81 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -2,7 +2,7 @@ :root { --bg-main: #09090b; - --bg-card: rgba(24, 24, 27, 0.6); + --bg-card: rgba(24, 24, 27, 0.65); --bg-input: rgba(39, 39, 42, 0.5); --bg-hover: rgba(63, 63, 70, 0.4); @@ -14,7 +14,7 @@ --text-muted: #71717a; --accent-color: #a855f7; - --accent-glow: rgba(168, 85, 247, 0.25); + --accent-glow: rgba(168, 85, 247, 0.35); --accent-hover: #9333ea; --success: #10b981; @@ -37,10 +37,16 @@ body { min-height: 100vh; -webkit-font-smoothing: antialiased; background-image: - radial-gradient(circle at 15% 50%, rgba(168, 85, 247, 0.08), transparent 25%), - radial-gradient(circle at 85% 30%, rgba(59, 130, 246, 0.08), transparent 25%); + radial-gradient(circle at 15% 50%, rgba(168, 85, 247, 0.12), transparent 30%), + radial-gradient(circle at 85% 30%, rgba(59, 130, 246, 0.12), transparent 30%); background-attachment: fixed; overflow-x: hidden; + animation: ambient-shift 20s ease-in-out infinite alternate; +} + +@keyframes ambient-shift { + 0% { background-position: 0% 0%; } + 100% { background-position: 100% 100%; } } #root { @@ -61,13 +67,15 @@ body { display: flex; justify-content: space-between; align-items: center; - padding: 1rem 2rem; + padding: 1.25rem 2.5rem; border-bottom: 1px solid var(--border-light); - background: rgba(9, 9, 11, 0.8); - backdrop-filter: blur(12px); + background: rgba(9, 9, 11, 0.75); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); position: sticky; top: 0; z-index: 50; + animation: slide-down 0.5s ease-out forwards; } .logo { @@ -75,40 +83,43 @@ body { align-items: center; gap: 0.75rem; font-weight: 700; - font-size: 1.1rem; + font-size: 1.2rem; letter-spacing: -0.02em; } .logo-icon { background: linear-gradient(135deg, var(--accent-color), #3b82f6); color: white; - width: 32px; - height: 32px; - border-radius: 8px; + width: 36px; + height: 36px; + border-radius: 10px; display: flex; align-items: center; justify-content: center; font-weight: 800; box-shadow: 0 0 15px var(--accent-glow); + animation: pulse-glow 3s infinite alternate; } .settings-btn { background: transparent; border: 1px solid var(--border-light); color: var(--text-secondary); - width: 36px; - height: 36px; - border-radius: 8px; + width: 40px; + height: 40px; + border-radius: 10px; display: flex; align-items: center; justify-content: center; cursor: pointer; - transition: all 0.2s; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .settings-btn:hover { background: var(--bg-hover); color: var(--text-primary); + transform: rotate(90deg); + border-color: var(--border-focus); } /* Settings Dropdown */ @@ -118,17 +129,27 @@ body { width: calc(100% - 4rem); background: var(--bg-card); border: 1px solid var(--border-light); - border-radius: 12px; - padding: 1.5rem; - backdrop-filter: blur(8px); + border-radius: 16px; + padding: 2rem; + backdrop-filter: blur(12px); + box-shadow: 0 10px 40px -10px rgba(0,0,0,0.5); + transform-origin: top; + animation: reveal-down 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.1) forwards; +} + +@keyframes reveal-down { + from { opacity: 0; transform: scaleY(0.8) translateY(-20px); } + to { opacity: 1; transform: scaleY(1) translateY(0); } } .settings-dropdown label { display: block; - font-size: 0.875rem; - font-weight: 500; + font-size: 0.9rem; + font-weight: 600; color: var(--text-secondary); - margin-bottom: 0.5rem; + margin-bottom: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; } .settings-dropdown input { @@ -136,23 +157,24 @@ body { background: var(--bg-input); border: 1px solid var(--border-light); color: var(--text-primary); - padding: 0.75rem 1rem; - border-radius: 8px; + padding: 1rem 1.25rem; + border-radius: 10px; font-family: var(--font-mono); - font-size: 0.875rem; - transition: all 0.2s; + font-size: 0.95rem; + transition: all 0.3s ease; } .settings-dropdown input:focus { outline: none; - border-color: var(--border-focus); - box-shadow: 0 0 0 1px var(--border-focus); + border-color: var(--accent-color); + box-shadow: 0 0 0 3px var(--accent-glow); + background: rgba(39, 39, 42, 0.8); } .settings-hint { - font-size: 0.75rem; + font-size: 0.8rem; color: var(--text-muted); - margin-top: 0.5rem; + margin-top: 0.75rem; } /* Main Container */ @@ -161,45 +183,52 @@ body { width: 100%; max-width: 1400px; margin: 0 auto; - padding: 3rem 2rem; + padding: 4rem 2rem; display: flex; flex-direction: column; } .hero-section { text-align: center; - margin-bottom: 3rem; - animation: fade-in-down 0.6s ease-out; + margin-bottom: 4rem; + animation: fade-in-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards; } .hero-section h1 { - font-size: 3.5rem; + font-size: 4rem; font-weight: 700; - letter-spacing: -0.04em; + letter-spacing: -0.05em; line-height: 1.1; - margin-bottom: 1rem; - background: linear-gradient(to right, #ffffff, #a1a1aa); + margin-bottom: 1.25rem; + background: linear-gradient(to right, #ffffff, #a1a1aa, #ffffff); + background-size: 200% auto; -webkit-background-clip: text; -webkit-text-fill-color: transparent; + animation: shine 4s linear infinite; +} + +@keyframes shine { + to { background-position: 200% center; } } .hero-section p { color: var(--text-secondary); - font-size: 1.125rem; - max-width: 600px; + font-size: 1.25rem; + max-width: 650px; margin: 0 auto; + line-height: 1.6; } /* Workspace Layout */ .workspace { display: grid; - gap: 2rem; - transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + gap: 2.5rem; + transition: all 0.7s cubic-bezier(0.16, 1, 0.3, 1); width: 100%; } .workspace.single-view { - grid-template-columns: minmax(auto, 600px); + grid-template-columns: minmax(auto, 650px); justify-content: center; } @@ -209,8 +238,8 @@ body { @media (min-width: 1024px) { .workspace.split-view { - grid-template-columns: 400px 1fr; - align-items: flex-start; + grid-template-columns: 450px 1fr; + align-items: stretch; } } @@ -218,31 +247,39 @@ body { .card { background: var(--bg-card); border: 1px solid var(--border-light); - border-radius: 16px; - backdrop-filter: blur(12px); + border-radius: 20px; + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); overflow: hidden; display: flex; flex-direction: column; - box-shadow: 0 4px 24px -1px rgba(0, 0, 0, 0.2); + box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.5); + transition: transform 0.4s ease, box-shadow 0.4s ease; + animation: fade-in-scale 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.card:hover { + box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.6); } .card-header { - padding: 1.25rem 1.5rem; + padding: 1.5rem 2rem; border-bottom: 1px solid var(--border-light); display: flex; justify-content: space-between; align-items: center; - background: rgba(255, 255, 255, 0.02); + background: linear-gradient(to right, rgba(255, 255, 255, 0.03), transparent); } .card-header h2 { - font-size: 1rem; + font-size: 1.1rem; font-weight: 600; color: var(--text-primary); + letter-spacing: -0.01em; } .card-body { - padding: 1.5rem; + padding: 2rem; display: flex; flex-direction: column; flex: 1; @@ -255,17 +292,43 @@ body { /* Drop Zone */ .drop-zone { border: 2px dashed var(--border-light); - border-radius: 12px; - padding: 3rem 2rem; + border-radius: 16px; + padding: 4rem 2rem; text-align: center; cursor: pointer; - transition: all 0.3s ease; + transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); background: rgba(0, 0, 0, 0.2); + position: relative; + overflow: hidden; +} + +.drop-zone::before { + content: ''; + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + background: radial-gradient(circle at center, var(--accent-glow) 0%, transparent 70%); + opacity: 0; + transition: opacity 0.4s ease; + pointer-events: none; +} + +.drop-zone:hover::before, .drop-zone.dragging::before { + opacity: 1; } .drop-zone:hover, .drop-zone.dragging { border-color: var(--accent-color); - background: var(--bg-hover); + transform: scale(1.02); +} + +.drop-zone.dragging { + animation: pulse-border 1.5s infinite; +} + +@keyframes pulse-border { + 0% { box-shadow: 0 0 0 0 var(--accent-glow); } + 70% { box-shadow: 0 0 0 15px transparent; } + 100% { box-shadow: 0 0 0 0 transparent; } } .hidden-input { @@ -273,8 +336,8 @@ body { } .drop-icon-wrapper { - width: 80px; - height: 80px; + width: 88px; + height: 88px; background: var(--bg-input); border-radius: 50%; display: flex; @@ -282,17 +345,21 @@ body { justify-content: center; margin: 0 auto 1.5rem auto; border: 1px solid var(--border-light); - transition: transform 0.3s ease; + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + position: relative; + z-index: 1; } .drop-zone:hover .drop-icon-wrapper { - transform: scale(1.05); - border-color: var(--border-focus); - box-shadow: 0 0 20px var(--accent-glow); + transform: translateY(-10px) scale(1.1); + border-color: var(--accent-color); + background: rgba(168, 85, 247, 0.1); + box-shadow: 0 10px 30px var(--accent-glow); } .drop-icon { color: var(--text-secondary); + transition: color 0.3s ease; } .drop-zone:hover .drop-icon { @@ -300,14 +367,18 @@ body { } .drop-zone h3 { - font-size: 1.125rem; + font-size: 1.25rem; font-weight: 600; - margin-bottom: 0.5rem; + margin-bottom: 0.75rem; + position: relative; + z-index: 1; } .drop-zone p { color: var(--text-muted); - font-size: 0.875rem; + font-size: 0.95rem; + position: relative; + z-index: 1; } /* File Active State */ @@ -315,24 +386,41 @@ body { display: flex; flex-direction: column; gap: 1.5rem; - animation: fade-in 0.4s ease; + animation: slide-up-fade 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +@keyframes slide-up-fade { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } } .file-card { display: flex; align-items: center; - gap: 1rem; - background: var(--bg-input); - padding: 1rem; - border-radius: 12px; + gap: 1.25rem; + background: linear-gradient(to right, var(--bg-input), rgba(0,0,0,0.2)); + padding: 1.25rem; + border-radius: 14px; border: 1px solid var(--border-light); + position: relative; + overflow: hidden; +} + +.file-card::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 4px; + background: var(--accent-color); } .file-icon { color: var(--accent-color); - background: var(--accent-glow); - padding: 0.5rem; - border-radius: 8px; + background: rgba(168, 85, 247, 0.1); + padding: 0.75rem; + border-radius: 10px; } .file-details { @@ -343,56 +431,72 @@ body { } .filename { - font-weight: 500; - font-size: 0.95rem; + font-weight: 600; + font-size: 1rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + margin-bottom: 0.25rem; } .filesize { - font-size: 0.75rem; + font-size: 0.8rem; color: var(--text-muted); - margin-top: 0.25rem; } .remove-file { - background: transparent; - border: none; - color: var(--text-muted); + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--border-light); + color: var(--text-secondary); cursor: pointer; padding: 0.5rem; - border-radius: 6px; - transition: all 0.2s; + border-radius: 8px; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .remove-file:hover { - background: rgba(239, 68, 68, 0.1); + background: rgba(239, 68, 68, 0.15); + border-color: rgba(239, 68, 68, 0.3); color: var(--error); + transform: rotate(90deg); } /* Buttons */ .primary-btn { - background: var(--text-primary); + background: linear-gradient(135deg, #ffffff, #e4e4e7); color: var(--bg-main); border: none; - padding: 1rem; - border-radius: 12px; - font-size: 1rem; - font-weight: 600; + padding: 1.125rem; + border-radius: 14px; + font-size: 1.05rem; + font-weight: 700; cursor: pointer; - transition: all 0.3s ease; + transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; font-family: var(--font-sans); + position: relative; + overflow: hidden; +} + +.primary-btn::after { + content: ''; + position: absolute; + top: 0; left: -100%; width: 50%; height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent); + transition: 0.5s; +} + +.primary-btn:hover:not(:disabled)::after { + left: 100%; } .primary-btn:hover:not(:disabled) { - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(255, 255, 255, 0.2); + transform: translateY(-3px); + box-shadow: 0 10px 25px rgba(255, 255, 255, 0.25); } .primary-btn:active:not(:disabled) { @@ -400,14 +504,17 @@ body { } .primary-btn:disabled { - opacity: 0.5; + opacity: 0.6; cursor: not-allowed; + background: var(--bg-input); + color: var(--text-secondary); } .primary-btn.converting { - background: var(--bg-hover); - color: var(--text-secondary); - border: 1px solid var(--border-light); + background: var(--bg-input); + color: var(--text-primary); + border: 1px solid var(--accent-color); + box-shadow: 0 0 20px var(--accent-glow); } .spin { @@ -417,91 +524,111 @@ body { /* Action Group */ .action-group { display: flex; - gap: 0.5rem; + gap: 0.75rem; } .icon-btn { background: var(--bg-input); border: 1px solid var(--border-light); color: var(--text-secondary); - width: 32px; - height: 32px; - border-radius: 8px; + width: 36px; + height: 36px; + border-radius: 10px; display: flex; align-items: center; justify-content: center; cursor: pointer; - transition: all 0.2s; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .icon-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); - border-color: var(--border-focus); + background: var(--accent-color); + color: white; + border-color: var(--accent-color); + transform: translateY(-2px); + box-shadow: 0 4px 15px var(--accent-glow); } /* Alerts */ .alert { - padding: 1rem; + padding: 1.25rem; border-radius: 12px; display: flex; align-items: center; - gap: 0.75rem; - font-size: 0.875rem; + gap: 1rem; + font-size: 0.95rem; font-weight: 500; + backdrop-filter: blur(8px); } .alert-error { - background: rgba(239, 68, 68, 0.1); - border: 1px solid rgba(239, 68, 68, 0.2); + background: rgba(239, 68, 68, 0.08); + border: 1px solid rgba(239, 68, 68, 0.3); color: #fca5a5; + box-shadow: 0 4px 20px rgba(239, 68, 68, 0.1); } .alert-success { - background: rgba(16, 185, 129, 0.1); - border: 1px solid rgba(16, 185, 129, 0.2); + background: rgba(16, 185, 129, 0.08); + border: 1px solid rgba(16, 185, 129, 0.3); color: #6ee7b7; + box-shadow: 0 4px 20px rgba(16, 185, 129, 0.1); } /* Result Area */ .result-card { height: 100%; - min-height: 500px; + min-height: 600px; + animation-delay: 0.2s; } .code-block { margin: 0; - padding: 1.5rem; + padding: 2rem; background: transparent; color: #e2e8f0; font-family: var(--font-mono); - font-size: 0.875rem; - line-height: 1.6; + font-size: 0.9rem; + line-height: 1.7; white-space: pre-wrap; word-break: break-word; overflow-y: auto; + height: 100%; max-height: 700px; } +.code-block::-webkit-scrollbar { + width: 10px; +} +.code-block::-webkit-scrollbar-track { + background: rgba(0,0,0,0.2); +} +.code-block::-webkit-scrollbar-thumb { + background: rgba(255,255,255,0.1); + border-radius: 5px; +} +.code-block::-webkit-scrollbar-thumb:hover { + background: rgba(255,255,255,0.2); +} + .text-success { - color: var(--success); + color: white; } /* Footer */ .footer { margin-top: auto; - padding: 2rem; + padding: 2.5rem 2rem; border-top: 1px solid var(--border-light); - background: rgba(9, 9, 11, 0.5); - backdrop-filter: blur(12px); + background: linear-gradient(to top, rgba(0,0,0,0.8), transparent); } .footer-content { display: flex; align-items: center; justify-content: center; - gap: 1rem; - font-size: 0.875rem; + gap: 1.5rem; + font-size: 0.95rem; color: var(--text-muted); } @@ -509,11 +636,14 @@ body { color: var(--text-secondary); text-decoration: none; font-weight: 500; - transition: color 0.2s; + transition: all 0.3s; + border-bottom: 1px solid transparent; } .footer-content a:hover { - color: var(--text-primary); + color: var(--accent-color); + border-bottom-color: var(--accent-color); + text-shadow: 0 0 10px var(--accent-glow); } .author-link { @@ -523,69 +653,34 @@ body { } .divider { - opacity: 0.5; + opacity: 0.4; } -/* Animations */ +/* Base Animations */ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } -@keyframes fade-in { - from { opacity: 0; } - to { opacity: 1; } +@keyframes pulse-glow { + from { box-shadow: 0 0 10px var(--accent-glow); } + to { box-shadow: 0 0 25px rgba(59, 130, 246, 0.6); } } -@keyframes fade-in-up { - from { - opacity: 0; - transform: translateY(15px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes fade-in-down { - from { - opacity: 0; - transform: translateY(-15px); - } - to { - opacity: 1; - transform: translateY(0); - } +@keyframes fade-in-scale { + from { opacity: 0; transform: scale(0.95) translateY(10px); } + to { opacity: 1; transform: scale(1) translateY(0); } } .fade-in { - animation: fade-in 0.4s ease forwards; + animation: slide-up-fade 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; } .fade-in-up { - animation: fade-in-up 0.5s ease forwards; -} - -.slide-down { - animation: fade-in-down 0.3s ease forwards; -} - -/* Scrollbar */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.1); - border-radius: 4px; + animation: fade-in-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards; } -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.2); +/* Ensure smooth entrance */ +.card, .hero-section { + will-change: transform, opacity; }