Created
February 27, 2026 19:26
-
-
Save kguay/c248b49bb4b4922631a7da18332bd2b8 to your computer and use it in GitHub Desktop.
Google profile icon maker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.16/dist/tailwind.min.css" rel="stylesheet"> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Yantramanav:wght@400;500;700&display=swap" rel="stylesheet"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/fontfaceobserver/2.1.0/fontfaceobserver.standalone.js"></script> | |
| <div id="body" class="bg-gray-100 min-h-screen theme-light"> | |
| <div class="container mx-auto px-4 py-12 relative"> | |
| <!-- Theme toggle: top-right modern icon --> | |
| <button | |
| id="themeToggleBtn" | |
| type="button" | |
| class="theme-toggle-icon absolute top-4 right-4" | |
| aria-label="Toggle theme" | |
| title="Toggle theme" | |
| > | |
| <!-- Auto (system) icon --> | |
| <span class="theme-icon theme-icon-auto"> | |
| <svg viewBox="0 0 24 24" class="w-5 h-5" stroke="currentColor" fill="none" stroke-width="1.5"> | |
| <rect x="4" y="5" width="16" height="10" rx="2"></rect> | |
| <line x1="12" y1="15" x2="12" y2="19"></line> | |
| <line x1="8" y1="19" x2="16" y2="19"></line> | |
| </svg> | |
| </span> | |
| <!-- Light icon (clean minimal sun) --> | |
| <span class="theme-icon theme-icon-light hidden"> | |
| <svg viewBox="0 0 24 24" class="w-5 h-5" stroke="currentColor" fill="none" stroke-width="1.5"> | |
| <circle cx="12" cy="12" r="4"></circle> | |
| <line x1="12" y1="2" x2="12" y2="6"></line> | |
| <line x1="12" y1="18" x2="12" y2="22"></line> | |
| <line x1="2" y1="12" x2="6" y2="12"></line> | |
| <line x1="18" y1="12" x2="22" y2="12"></line> | |
| <line x1="4.5" y1="4.5" x2="6.8" y2="6.8"></line> | |
| <line x1="17.2" y1="17.2" x2="19.5" y2="19.5"></line> | |
| <line x1="4.5" y1="19.5" x2="6.8" y2="17.2"></line> | |
| <line x1="17.2" y1="6.8" x2="19.5" y2="4.5"></line> | |
| </svg> | |
| </span> | |
| <!-- Dark icon --> | |
| <span class="theme-icon theme-icon-dark hidden"> | |
| <svg viewBox="0 0 24 24" class="w-5 h-5" stroke="currentColor" fill="none" stroke-width="1.5"> | |
| <path d="M21 12.8A9 9 0 1111.2 3a7 7 0 0010 9.8z"></path> | |
| </svg> | |
| </span> | |
| </button> | |
| <h1 class="text-4xl font-bold text-center mb-8 text-gray-800">Profile Icon Generator</h1> | |
| <div class="app-container p-8 mx-auto bg-white shadow-lg rounded-xl"> | |
| <div class="grid grid-cols-7 gap-4 mb-8" id="letterPicker"></div> | |
| <br> | |
| <div class="grid grid-cols-7 gap-4 mb-8" id="colorPicker"></div> | |
| <br> | |
| <div class="flex justify-center mb-8"> | |
| <canvas id="letterCanvas" width="200" height="200"></canvas> | |
| </div> | |
| <br> | |
| <div class="text-center"> | |
| <button id="downloadBtn" class="bg-blue-700 text-white font-bold py-2 px-4 rounded">Download Icon</button> | |
| </div> | |
| </div> | |
| <div class="mt-8 text-center"> | |
| <h2 class="text-xl font-bold mb-4 text-gray-700">How to Set Your Google Profile Icon</h2> | |
| <ol class="text-sm list-decimal list-inside text-left mx-auto max-w-xl text-gray-600"> | |
| <li>Download the image by clicking the "Download Icon" button.</li> | |
| <li>Sign in to your Google Account.</li> | |
| <li>Open your Google Account settings by clicking your profile picture in the top-right corner and selecting "Manage your Google Account."</li> | |
| <li>Under the "Personal info" tab, click on the profile icon or the current profile picture.</li> | |
| <li>Click on "Set Profile Picture" or "Change" (if you already have a profile picture).</li> | |
| <li>Select the downloaded image from your device and click "Open."</li> | |
| <li>Adjust the cropping if necessary, then click "Done" to save your new profile icon.</li> | |
| </ol> | |
| </div> | |
| </div> | |
| <footer class="text-center text-gray-400 text-sm pb-4"> | |
| <p>Created by <a href="https://www.kevinguay.com" target="_blank" class="underline hover:text-gray-900">Kevin Guay</a>. <a href="mailto:iconmaker@kevinguay.com" target="_blank" class="underline hover:text-gray-900">Feedback</a> welcome.</p> | |
| <p>This site runs entirely in your browser — no cookies, no tracking, and no server processing.</p> | |
| </footer> | |
| </div> | |
| <style> | |
| :root { | |
| color-scheme: light; | |
| } | |
| .selected { | |
| border: 1.5px solid white; | |
| } | |
| .app-container { | |
| max-width: 680px; | |
| } | |
| #letterPicker, | |
| #colorPicker { | |
| display: flex; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| margin-bottom: 20px; | |
| } | |
| .letterSquare, | |
| .colorSquare { | |
| width: 30px; | |
| height: 30px; | |
| margin: 5px; | |
| cursor: pointer; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| border-radius: 50%; | |
| overflow: hidden; | |
| } | |
| .letterSquare { | |
| color: #ffffff; | |
| font-size: 18px; | |
| } | |
| #letterCanvas { | |
| border-radius: 50%; | |
| overflow: hidden; | |
| display: inline-block; | |
| width: 100px; | |
| height: 100px; | |
| } | |
| /* THEME: LIGHT / DARK OVERRIDES */ | |
| #body.theme-light { | |
| background-color: #f7fafc; /* bg-gray-100 */ | |
| color: #1f2933; | |
| } | |
| #body.theme-dark { | |
| background-color: #111827; /* gray-900-ish */ | |
| color: #e5e7eb; | |
| } | |
| #body.theme-dark .app-container { | |
| background-color: #1f2937; /* gray-800 */ | |
| color: #e5e7eb; | |
| } | |
| #body.theme-dark h1, | |
| #body.theme-dark h2 { | |
| color: #f9fafb; | |
| } | |
| #body.theme-dark ol, | |
| #body.theme-dark li, | |
| #body.theme-dark p { | |
| color: #d1d5db; | |
| } | |
| #body.theme-dark footer { | |
| color: #9ca3af; | |
| } | |
| #body.theme-dark a { | |
| color: #e5e7eb; | |
| } | |
| #body.theme-dark a:hover { | |
| color: #ffffff; | |
| } | |
| #body.theme-dark .bg-white { | |
| background-color: #1f2937 !important; | |
| } | |
| #body.theme-dark #downloadBtn { | |
| background-color: #2563eb; /* blue-600 */ | |
| } | |
| #body.theme-dark #downloadBtn:hover { | |
| background-color: #1d4ed8; /* blue-700 */ | |
| } | |
| #body.theme-dark .underline.hover\:text-gray-900:hover { | |
| color: #f9fafb !important; | |
| } | |
| #body.theme-dark { | |
| --tw-ring-color: rgba(59, 130, 246, 0.5); | |
| color-scheme: dark; | |
| } | |
| /* Theme toggle button (PaperMod-style) */ | |
| .theme-toggle-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 9999px; | |
| border: 1px solid #d1d5db; /* gray-300 */ | |
| background-color: #ffffff; | |
| cursor: pointer; | |
| box-shadow: 0 1px 2px rgba(0,0,0,0.05); | |
| transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.05s ease; | |
| } | |
| .theme-toggle-btn:hover { | |
| background-color: #f3f4f6; /* gray-100 */ | |
| } | |
| .theme-toggle-btn:active { | |
| transform: scale(0.96); | |
| } | |
| .theme-icon svg { | |
| display: block; | |
| fill: currentColor; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| #body.theme-dark .theme-toggle-btn { | |
| background-color: #111827; | |
| border-color: #4b5563; /* gray-600 */ | |
| color: #e5e7eb; | |
| box-shadow: 0 1px 2px rgba(0,0,0,0.4); | |
| } | |
| #body.theme-dark .theme-toggle-btn:hover { | |
| background-color: #1f2937; | |
| } | |
| /* Minimal modern icon-only theme toggle */ | |
| .theme-toggle-icon { | |
| background: none; | |
| border: none; | |
| padding: 0; | |
| cursor: pointer; | |
| opacity: 0.65; | |
| transition: opacity 0.15s ease; | |
| } | |
| .theme-toggle-icon:hover { | |
| opacity: 1; | |
| } | |
| .theme-icon svg { | |
| display: block; | |
| stroke: currentColor; | |
| fill: currentColor; | |
| } | |
| /* Optional: adjust icon color in dark mode */ | |
| #body.theme-dark .theme-toggle-icon { | |
| color: #e5e7eb; /* gray-200 */ | |
| } | |
| #body.theme-light .theme-toggle-icon { | |
| color: #374151; /* gray-700 */ | |
| } | |
| </style> | |
| <script> | |
| // THEME HANDLING | |
| const THEME_KEY = 'theme-preference'; | |
| const themeToggleBtn = document.getElementById('themeToggleBtn'); | |
| const iconAuto = document.querySelector('.theme-icon-auto'); | |
| const iconLight = document.querySelector('.theme-icon-light'); | |
| const iconDark = document.querySelector('.theme-icon-dark'); | |
| function getStoredTheme() { | |
| const stored = localStorage.getItem(THEME_KEY); | |
| return stored || 'auto'; | |
| } | |
| function getEffectiveTheme(themeSetting) { | |
| if (themeSetting === 'auto') { | |
| const prefersDark = window.matchMedia && | |
| window.matchMedia('(prefers-color-scheme: dark)').matches; | |
| return prefersDark ? 'dark' : 'light'; | |
| } | |
| return themeSetting; | |
| } | |
| function updateThemeIcon(themeSetting) { | |
| if (!iconAuto || !iconLight || !iconDark || !themeToggleBtn) return; | |
| iconAuto.classList.add('hidden'); | |
| iconLight.classList.add('hidden'); | |
| iconDark.classList.add('hidden'); | |
| if (themeSetting === 'auto') { | |
| iconAuto.classList.remove('hidden'); | |
| themeToggleBtn.title = 'Auto (system)'; | |
| } else if (themeSetting === 'light') { | |
| iconLight.classList.remove('hidden'); | |
| themeToggleBtn.title = 'Light theme'; | |
| } else if (themeSetting === 'dark') { | |
| iconDark.classList.remove('hidden'); | |
| themeToggleBtn.title = 'Dark theme'; | |
| } | |
| } | |
| function applyTheme(themeSetting) { | |
| const effectiveTheme = getEffectiveTheme(themeSetting); | |
| // use #body div instead of <body>, but fall back just in case | |
| const bodyEl = document.getElementById('body') || document.body; | |
| if (bodyEl) { | |
| bodyEl.classList.remove('theme-light', 'theme-dark'); | |
| bodyEl.classList.add(effectiveTheme === 'dark' ? 'theme-dark' : 'theme-light'); | |
| } | |
| document.documentElement.setAttribute('data-theme', themeSetting); | |
| if (effectiveTheme === 'dark') { | |
| document.documentElement.style.colorScheme = 'dark'; | |
| } else { | |
| document.documentElement.style.colorScheme = 'light'; | |
| } | |
| updateThemeIcon(themeSetting); | |
| } | |
| function cycleTheme(current) { | |
| if (current === 'auto') return 'light'; | |
| if (current === 'light') return 'dark'; | |
| return 'auto'; | |
| } | |
| // Initialize theme on load | |
| let currentTheme = getStoredTheme(); | |
| applyTheme(currentTheme); | |
| // Button click cycles through auto -> light -> dark -> auto | |
| if (themeToggleBtn) { | |
| themeToggleBtn.addEventListener('click', () => { | |
| currentTheme = cycleTheme(currentTheme); | |
| localStorage.setItem(THEME_KEY, currentTheme); | |
| applyTheme(currentTheme); | |
| }); | |
| } | |
| // React to system changes when in auto mode | |
| if (window.matchMedia) { | |
| const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); | |
| mediaQuery.addEventListener('change', () => { | |
| const stored = getStoredTheme(); | |
| currentTheme = stored; | |
| if (stored === 'auto') { | |
| applyTheme('auto'); | |
| } | |
| }); | |
| } | |
| // EXISTING APP LOGIC | |
| const canvas = document.getElementById('letterCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const colorPicker = document.getElementById('colorPicker'); | |
| const letterPicker = document.getElementById('letterPicker'); | |
| const colors = [ | |
| 'AA47BC', '7A1FA2', '78909C', '465A65', 'EC407A', 'C2175B', '5C6BC0', '0288D1', | |
| '00579C', '0098A6', '00887A', '004C3F', '689F39', '33691E', '8C6E63', '5D4038', | |
| '7E57C2', '512DA7', 'EF6C00', 'F5511E', 'BF360C' | |
| ]; | |
| const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; | |
| const defaultColor = '00579C'; | |
| const defaultLetter = 'A'; | |
| let selectedColor = defaultColor; | |
| let selectedLetter = defaultLetter; | |
| let selectedColorSquare; | |
| let selectedLetterSquare; | |
| ctx.font = '550px Yantramanav'; | |
| ctx.fillStyle = 'white'; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| function drawLetter(letter, color) { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| canvas.width = 1000; | |
| canvas.height = 1000; | |
| ctx.fillStyle = color; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = 'white'; | |
| ctx.font = '550px Yantramanav'; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| const metrics = ctx.measureText(letter); | |
| const yOffset = metrics.actualBoundingBoxAscent / 2 - metrics.actualBoundingBoxDescent / 2; | |
| ctx.fillText(letter, canvas.width / 2, canvas.height / 2 + yOffset); | |
| } | |
| drawLetter(defaultLetter, `#${defaultColor}`); | |
| function createColorPicker() { | |
| for (const color of colors) { | |
| const colorSquare = document.createElement('div'); | |
| colorSquare.classList.add('colorSquare'); | |
| colorSquare.style.backgroundColor = `#${color}`; | |
| colorSquare.addEventListener('click', () => { | |
| if (selectedColorSquare) { | |
| selectedColorSquare.classList.remove('selected'); | |
| } | |
| selectedColorSquare = colorSquare; | |
| selectedColorSquare.classList.add('selected'); | |
| drawLetter(selectedLetter, `#${color}`); | |
| selectedColor = color; | |
| }); | |
| if (color === defaultColor) { | |
| selectedColorSquare = colorSquare; | |
| selectedColorSquare.classList.add('selected'); | |
| } | |
| colorPicker.appendChild(colorSquare); | |
| } | |
| } | |
| function createLetterPicker() { | |
| for (const letter of letters) { | |
| const letterSquare = document.createElement('div'); | |
| letterSquare.classList.add('letterSquare'); | |
| letterSquare.style.backgroundColor = `#666`; | |
| letterSquare.textContent = letter; | |
| letterSquare.addEventListener('click', () => { | |
| if (selectedLetterSquare) { | |
| selectedLetterSquare.classList.remove('selected'); | |
| } | |
| selectedLetterSquare = letterSquare; | |
| selectedLetterSquare.classList.add('selected'); | |
| selectedLetter = letter; | |
| drawLetter(letter, `#${selectedColor}`); | |
| }); | |
| if (letter === defaultLetter) { | |
| selectedLetterSquare = letterSquare; | |
| selectedLetterSquare.classList.add('selected'); | |
| } | |
| letterPicker.appendChild(letterSquare); | |
| } | |
| } | |
| const fontObserver = new FontFaceObserver('Yantramanav'); | |
| fontObserver.load().then(() => { | |
| drawLetter(defaultLetter, `#${defaultColor}`); | |
| createLetterPicker(); | |
| }).catch((err) => { | |
| console.error('Error loading Yantramanav font:', err); | |
| }); | |
| createColorPicker(); | |
| downloadBtn.addEventListener('click', () => { | |
| const link = document.createElement('a'); | |
| link.href = canvas.toDataURL('image/png'); | |
| link.download = `google-icon-${selectedLetter}.png`; | |
| link.click(); | |
| }); | |
| const privacyPolicyModal = document.getElementById('privacyPolicyModal'); | |
| const closeModal = document.getElementById('closeModal'); | |
| const privacyPolicyLink = document.getElementById('privacyPolicyLink'); | |
| if (privacyPolicyModal && closeModal && privacyPolicyLink) { | |
| privacyPolicyLink.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| privacyPolicyModal.classList.remove('hidden'); | |
| }); | |
| closeModal.addEventListener('click', () => { | |
| privacyPolicyModal.classList.add('hidden'); | |
| }); | |
| window.addEventListener('click', (e) => { | |
| if (e.target === privacyPolicyModal) { | |
| privacyPolicyModal.classList.add('hidden'); | |
| } | |
| }); | |
| } | |
| </script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment