From b1c1b061f975b9c30be5c994c7a05cf6bbdeb107 Mon Sep 17 00:00:00 2001 From: darshan02parmar Date: Sat, 11 Oct 2025 12:23:25 +0530 Subject: [PATCH 1/2] feat: Add custom accent color picker and random theme generator --- CUSTOMIZATION_FEATURES.md | 134 ++++++++++ QUICK_PUSH_COMMANDS.md | 68 +++++ api/frames.js | 25 +- client/src/App.jsx | 249 +++++++++++++++++- package-lock.json | 538 +++++++++++++++++++++++++++++++++++++- package.json | 8 +- tsconfig.server.json | 10 +- 7 files changed, 1012 insertions(+), 20 deletions(-) create mode 100644 CUSTOMIZATION_FEATURES.md create mode 100644 QUICK_PUSH_COMMANDS.md diff --git a/CUSTOMIZATION_FEATURES.md b/CUSTOMIZATION_FEATURES.md new file mode 100644 index 0000000..335599e --- /dev/null +++ b/CUSTOMIZATION_FEATURES.md @@ -0,0 +1,134 @@ +# Custom Accent Color Picker & Random Theme Generator + +## Overview +This document describes the new personalization features added to the GitHub Avatar Frame API project. + +## Features Added + +### 1. Custom Accent Color Picker ๐ŸŽจ +- **Location**: Added below the theme selection area in the "Customization & Discovery" section +- **Functionality**: + - Color picker input for selecting custom accent colors + - Quick color presets with 6 popular colors + - Reset button to restore default theme colors + - Real-time preview with CSS variables +- **API Integration**: Custom colors are passed via `accentColor` parameter to the backend +- **Backend Processing**: Uses Sharp.js to apply color tinting and modulation to frames + +### 2. Random Theme Generator ๐ŸŽฒ +- **Location**: Next to the color picker in the "Customization & Discovery" section +- **Functionality**: + - Randomly selects one of the available themes + - 30% chance to also randomize accent color for extra variety + - Toast notifications for user feedback + - Disabled state when no themes are available +- **User Experience**: + - "โœจ Surprise Style Loaded!" for theme-only randomization + - "๐ŸŽฒ Random Theme + Color Applied!" when both theme and color are randomized + +### 3. Toast Notification System +- **Design**: Slide-in animation from the right +- **Duration**: 3 seconds auto-dismiss +- **Styling**: Matches the app's design system with proper dark/light mode support +- **Usage**: Provides feedback for random theme selection and color reset actions + +## Technical Implementation + +### Frontend (React) +- Added state management for `customAccentColor`, `originalThemeColor`, and toast notifications +- Updated API URL generation to include accent color parameter +- Enhanced UI with responsive design for mobile and desktop +- Added CSS animations for toast notifications + +### Backend (Node.js/Sharp.js) +- Added `accentColor` query parameter handling +- Implemented color processing using Sharp.js: + - Hex to RGB conversion + - Color tinting with custom colors + - Brightness and saturation modulation for better visual results + +### API Endpoints +- Updated `/api/framed-avatar/{username}` to accept `accentColor` parameter +- Example: `?theme=minimal&accentColor=%23ff6b6b&size=256` + +## System Architecture Flow + +``` +User Interface +โ”œโ”€โ”€ Theme Selection (existing) +โ”œโ”€โ”€ Custom Color Picker (new) +โ”‚ โ”œโ”€โ”€ Color Input Field +โ”‚ โ”œโ”€โ”€ Quick Color Presets +โ”‚ โ””โ”€โ”€ Reset Button +โ”œโ”€โ”€ Random Theme Generator (new) +โ”‚ โ”œโ”€โ”€ Random Theme Selection +โ”‚ โ””โ”€โ”€ Optional Random Color (30% chance) +โ””โ”€โ”€ Toast Notifications (new) + +API Request +โ”œโ”€โ”€ theme: selected theme +โ”œโ”€โ”€ accentColor: custom color (optional) +โ”œโ”€โ”€ size, canvas, shape, radius (existing) +โ””โ”€โ”€ style (existing) + +Backend Processing +โ”œโ”€โ”€ Fetch GitHub Avatar +โ”œโ”€โ”€ Load Theme Frame +โ”œโ”€โ”€ Apply Custom Color Tint (if provided) +โ”‚ โ”œโ”€โ”€ Convert hex to RGB +โ”‚ โ”œโ”€โ”€ Apply color modulation +โ”‚ โ””โ”€โ”€ Enhance brightness/saturation +โ””โ”€โ”€ Composite Final Image + +Response +โ””โ”€โ”€ Framed Avatar with Custom Colors +``` + +## User Experience Enhancements + +### Quick Color Presets +Six predefined colors for easy selection: +- Purple: `#7c3aed` +- Pink: `#ec4899` +- Orange: `#f97316` +- Green: `#10b981` +- Blue: `#3b82f6` +- Violet: `#8b5cf6` + +### Responsive Design +- Color picker and controls adapt to mobile screens +- Proper spacing and alignment across different screen sizes +- Touch-friendly button sizes + +### Accessibility +- Proper ARIA labels and tooltips +- Keyboard navigation support +- Color contrast compliance + +## Future Enhancements +- Save custom color combinations +- Export/import color themes +- Color history for recently used colors +- Advanced color picker with HSL/HSV support +- Theme preview with custom colors before generation + +## Usage Examples + +### Basic Custom Color +1. Select a theme (e.g., "minimal") +2. Click the color picker and choose a custom color +3. Generate the avatar with the custom accent color + +### Quick Color Selection +1. Use the quick color presets for instant color changes +2. Colors are applied immediately without opening the color picker + +### Random Theme Discovery +1. Click "๐ŸŽฒ Random Theme" to discover new themes +2. Sometimes includes random accent colors for extra variety +3. Toast notification confirms the selection + +### Reset to Default +1. When a custom color is selected, a "โ†ป Reset" button appears +2. Click to restore the original theme colors +3. Toast notification confirms the reset diff --git a/QUICK_PUSH_COMMANDS.md b/QUICK_PUSH_COMMANDS.md new file mode 100644 index 0000000..70857d1 --- /dev/null +++ b/QUICK_PUSH_COMMANDS.md @@ -0,0 +1,68 @@ +# ๐Ÿš€ Quick Push Commands + +## โšก Fast Track to Push + +### 1. Check Status +```bash +git status +``` + +### 2. Add All Changes +```bash +git add . +``` + +### 3. Commit with Feature Description +```bash +git commit -m "feat: Add custom accent color picker and random theme generator + +- Add custom accent color picker with hex color support +- Add 6 quick color presets for instant selection +- Add random theme generator with optional color randomization +- Add reset to default color functionality +- Add toast notification system with animations +- Update API to support accentColor parameter with Sharp.js processing +- Enhance UI with responsive design and accessibility features" +``` + +### 4. Push to Repository +```bash +git push origin main +``` + +## ๐Ÿ”„ Alternative: Feature Branch + +### Create Feature Branch +```bash +git checkout -b feature/custom-color-picker +git add . +git commit -m "feat: Add custom accent color picker and random theme generator" +git push origin feature/custom-color-picker +``` + +## ๐Ÿงช Quick Test Before Push + +### Start Servers +```bash +# Terminal 1 +npm run dev + +# Terminal 2 +cd client +npm run dev +``` + +### Test Features +- Open `http://localhost:5173` +- Test color picker +- Test random theme generator +- Generate avatar with custom color + +## โœ… Ready to Push! + +Your new features are ready: +- ๐ŸŽจ Custom accent color picker +- ๐ŸŽฒ Random theme generator +- โœจ Toast notifications +- ๐Ÿ“ฑ Responsive design +- ๐Ÿ”ง API enhancement diff --git a/api/frames.js b/api/frames.js index 6cae32a..3509c08 100644 --- a/api/frames.js +++ b/api/frames.js @@ -13,7 +13,8 @@ export default async function handler(req, res) { const username = req.query.username; const theme = req.query.theme || "base"; const size = Math.max(64, Math.min(Number(req.query.size || 256), 1024)); - const canvas= req.query.canvas || "light"; + const canvas = req.query.canvas || "light"; + const accentColor = req.query.accentColor || null; // Validate username if (!username || typeof username !== "string" || username.trim() === "") { return res.status(400).json({ error: "Username is required" }); @@ -82,10 +83,24 @@ export default async function handler(req, res) { .toBuffer(); // only resize frame if it was successfully loaded if(frameBuffer){ - frameResized = await sharp(frameBuffer) - .resize(size, size) - .png() - .toBuffer(); + let frameProcessor = sharp(frameBuffer).resize(size, size); + + // Apply custom accent color if provided + if (accentColor) { + // Convert hex color to RGB for processing + const hex = accentColor.replace('#', ''); + const r = parseInt(hex.substr(0, 2), 16); + const g = parseInt(hex.substr(2, 2), 16); + const b = parseInt(hex.substr(4, 2), 16); + + // Apply color tint to the frame + frameProcessor = frameProcessor.modulate({ + brightness: 1.1, // Slightly brighten + saturation: 1.3, // Increase saturation + }).tint({ r, g, b }); + } + + frameResized = await frameProcessor.png().toBuffer(); } // composition logic for transparency and avatar clipping let imageProcessor; diff --git a/client/src/App.jsx b/client/src/App.jsx index 2c64e7a..e1b23be 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -192,6 +192,8 @@ function App() { const [username, setUsername] = useState(""); const [themes, setThemes] = useState([]); const [selectedTheme, setSelectedTheme] = useState("base"); + const [customAccentColor, setCustomAccentColor] = useState(null); + const [originalThemeColor, setOriginalThemeColor] = useState(null); const [size, setSize] = useState(384); const [canvas, setCanvas] = useState("light"); const [shape, setShape] = useState("circle"); @@ -206,6 +208,8 @@ function App() { const [copied, setCopied] = useState(false); const [currentStep, setCurrentStep] = useState(1); const [isCommunityModalOpen, setIsCommunityModalOpen] = useState(false); + const [toastMessage, setToastMessage] = useState(""); + const [showToast, setShowToast] = useState(false); // System Theme State const [isDark, setIsDark] = useState(false); @@ -308,7 +312,13 @@ function App() { }; const copyApiUrl = () => { - const apiUrl = `${API_BASE_URL}/api/framed-avatar/${username}?theme=${selectedTheme}&size=${size}&canvas=${canvas}&shape=${shape}&radius=${finalRadiusForDisplay}&style=${frameStyle}`; + let apiUrl = `${API_BASE_URL}/api/framed-avatar/${username}?theme=${selectedTheme}&size=${size}&canvas=${canvas}&shape=${shape}&radius=${finalRadiusForDisplay}&style=${frameStyle}`; + + // Add custom accent color if selected + if (customAccentColor) { + apiUrl += `&accentColor=${encodeURIComponent(customAccentColor)}`; + } + try { // Use document.execCommand('copy') for better compatibility in iframe environments const tempInput = document.createElement("textarea"); @@ -339,7 +349,12 @@ function App() { try { const finalRadius = shape === "circle" ? maxRadius : radius; - const url = `${API_BASE_URL}/api/framed-avatar/${username}?theme=${selectedTheme}&size=${size}&canvas=${canvas}&shape=${shape}&radius=${finalRadius}&style=${frameStyle}`; + let url = `${API_BASE_URL}/api/framed-avatar/${username}?theme=${selectedTheme}&size=${size}&canvas=${canvas}&shape=${shape}&radius=${finalRadius}&style=${frameStyle}`; + + // Add custom accent color if selected + if (customAccentColor) { + url += `&accentColor=${encodeURIComponent(customAccentColor)}`; + } // Create AbortController for timeout const controller = new AbortController(); @@ -390,6 +405,51 @@ function App() { const handleThemeSelect = (theme) => { setSelectedTheme(theme); setCurrentStep(3); + // Reset custom color when selecting a new theme + setCustomAccentColor(null); + setOriginalThemeColor(null); + }; + + const handleRandomTheme = () => { + if (themes.length === 0) return; + + const randomIndex = Math.floor(Math.random() * themes.length); + const randomTheme = themes[randomIndex]; + + setSelectedTheme(randomTheme.theme); + + // Randomly decide whether to also randomize accent color (30% chance) + const shouldRandomizeColor = Math.random() < 0.3; + if (shouldRandomizeColor) { + const randomColors = ["#7c3aed", "#ec4899", "#f97316", "#10b981", "#3b82f6", "#8b5cf6", "#ef4444", "#f59e0b"]; + const randomColor = randomColors[Math.floor(Math.random() * randomColors.length)]; + setCustomAccentColor(randomColor); + showToastNotification("๐ŸŽฒ Random Theme + Color Applied!"); + } else { + setCustomAccentColor(null); + showToastNotification("โœจ Surprise Style Loaded!"); + } + + setOriginalThemeColor(null); + setCurrentStep(3); + }; + + const showToastNotification = (message) => { + setToastMessage(message); + setShowToast(true); + setTimeout(() => { + setShowToast(false); + }, 3000); + }; + + const handleCustomColorChange = (color) => { + setCustomAccentColor(color); + }; + + const resetToDefaultColor = () => { + setCustomAccentColor(null); + setOriginalThemeColor(null); + showToastNotification("๐ŸŽจ Reset to Default Color"); }; const finalRadiusForDisplay = shape === "circle" ? maxRadius : radius; @@ -788,6 +848,139 @@ function App() { )} + {/* Custom Color Picker and Random Theme Generator */} +
+ +
+ {/* Custom Accent Color Picker */} +
+ + handleCustomColorChange(e.target.value)} + style={{ + width: "40px", + height: "40px", + border: `2px solid ${colors.border}`, + borderRadius: "8px", + cursor: "pointer", + background: "transparent", + padding: 0, + }} + title="Customize accent color" + /> + {customAccentColor && ( + + )} +
+ + {/* Quick Color Presets */} +
+ Quick: + {["#7c3aed", "#ec4899", "#f97316", "#10b981", "#3b82f6", "#8b5cf6"].map((color) => ( +
+ + {/* Random Theme Generator */} + +
+
+ {/* Control Group: Canvas & Shape */}
- {`${API_BASE_URL}/api/framed-avatar/${username}?theme=${selectedTheme}&size=${size}&canvas=${canvas}&shape=${shape}&radius=${finalRadiusForDisplay}`} + {(() => { + let apiUrl = `${API_BASE_URL}/api/framed-avatar/${username}?theme=${selectedTheme}&size=${size}&canvas=${canvas}&shape=${shape}&radius=${finalRadiusForDisplay}&style=${frameStyle}`; + if (customAccentColor) { + apiUrl += `&accentColor=${encodeURIComponent(customAccentColor)}`; + } + return apiUrl; + })()}
@@ -1261,6 +1460,49 @@ function App() { colors={colors} /> + {/* Toast Notification */} + {showToast && ( +
+
+ +
+ + {toastMessage} + +
+ )} +