Conversation
…ines Adds a /brand page for press and media use, featuring: - Logo section with dark/light previews and SVG/PNG downloads - Customizable logo preview with accent color picker and background toggle - Core brand color palette with click-to-copy hex and OKLch values - Typography specimens for Geist Sans and Geist Mono - Usage guidelines with do's and don'ts - Right-click context menu on header logo (copy SVG, browse brand kit) - Full i18n support - Navigation links in footer and mobile menu
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new Brand documentation area: a Nuxt page at app/pages/brand.vue, a Brand customization component (app/components/Brand/Customize.vue), a context menu wrapper for logos (app/components/LogoContextMenu.vue), and an SVG→PNG composable (app/composables/useSvgToPng.ts). Registers /brand for prerendering and exempts it from canonical redirects. Surfaces the Brand route in header and footer and wraps header logos with LogoContextMenu. Adds i18n keys and schema entries for brand-related strings and skips two client-only components in a11y component-coverage tests. Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Use display:contents so the wrapper div doesn't participate in flex layout.
- Add Brand/Customize and LogoContextMenu to a11y SKIPPED_COMPONENTS - Replace dynamic i18n keys with static $t() calls for color names
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
app/composables/useSvgToPng.ts (1)
20-20: Consider handling null canvas context for defensive coding.While
getContext('2d')returningnullis extremely rare in modern browsers, the non-null assertion could mask issues in edge cases (e.g., resource constraints, unsupported canvas contexts in some environments).🛡️ Optional: Add null check
const ctx = canvas.getContext('2d') + if (!ctx) throw new Error('Failed to get canvas 2D context') - const ctx = canvas.getContext('2d')! ctx.scale(scale, scale)app/components/Brand/Customize.vue (2)
3-3: Remove unused import.
_convertis imported fromuseSvgToPng()but never used. The PNG conversion is implemented inline indownloadCustomPng()rather than using this function.♻️ Remove unused destructured variable
-const { convert: _convert, download: downloadBlob } = useSvgToPng() +const { download: downloadBlob } = useSvgToPng()
45-79: Consider using theuseSvgToPngcomposable for PNG conversion.This function duplicates the logic from
useSvgToPng().convert(): waiting for fonts, loading an Image, drawing to canvas, and callingtoBlob. The only difference is the background fill and using a data URL from a Blob rather than an external SVG URL.While the current implementation works, consolidating this logic would reduce duplication. However, since the composable's
convertexpects a URL and this needs an SVG string, the current approach is acceptable.app/components/LogoContextMenu.vue (2)
36-44: Consider handling fetch errors gracefully.If the fetch for
/logo.svgfails (network error or non-200 response), the error will propagate silently and the user receives no feedback. The menu closes viafinally, but the copy operation fails without indication.🛡️ Add error handling with user feedback
async function copySvg() { try { const res = await fetch('/logo.svg') + if (!res.ok) throw new Error('Failed to fetch logo') const svg = await res.text() await copy(svg) + } catch { + // Optionally: show toast or log error + console.error('Failed to copy logo SVG') } finally { close() } }
53-55: Minor: Redundant escape key handler.The
onKeyStroke('Escape', ...)at lines 53-55 already handles closing the menu globally when Escape is pressed. The@keydown.escape="close"on line 78 is redundant since both achieve the same result.♻️ Remove redundant handler
:style="{ left: `${x}px`, top: `${y}px` }" - `@keydown.escape`="close" >Also applies to: 78-78
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ea36b128-de30-4d15-b876-69f94853e1fe
📒 Files selected for processing (9)
app/components/AppFooter.vueapp/components/AppHeader.vueapp/components/Brand/Customize.vueapp/components/LogoContextMenu.vueapp/composables/useSvgToPng.tsapp/pages/brand.vuei18n/locales/en.jsonnuxt.config.tsserver/middleware/canonical-redirects.global.ts
There was a problem hiding this comment.
♻️ Duplicate comments (2)
app/pages/brand.vue (2)
11-15:⚠️ Potential issue | 🟡 MinorLocalise OG image title/description to match active locale.
Line 13 and Line 14 are hard-coded English, so social previews can diverge from translated page metadata.
🌍 Proposed fix
defineOgImageComponent('Default', { primaryColor: '#51c8fc', - title: 'npmx brand', - description: 'logos, colors, typography, and usage guidelines', + title: $t('brand.title'), + description: $t('brand.meta_description'), })Based on learnings: In this Nuxt project, page components should rely on auto-imported
$t()in<script setup>, including callbacks like metadata definitions.
74-84:⚠️ Potential issue | 🟡 MinorTrack PNG export loading per logo, not globally.
Using a single
pngLoadingtoken allows one export to clear another export’s loading state too early.🛠️ Proposed fix
-const pngLoading = ref<string | null>(null) +const pngLoading = ref(new Set<string>()) async function handlePngDownload(logo: (typeof logos)[number]) { - pngLoading.value = logo.src + if (pngLoading.value.has(logo.src)) return + pngLoading.value.add(logo.src) try { const blob = await convert(logo.src, logo.width, logo.height) const filename = logo.src.replace(/^\//, '').replace('.svg', '.png') downloadPng(blob, filename) } finally { - pngLoading.value = null + pngLoading.value.delete(logo.src) } }-:disabled="pngLoading === logo.src" +:disabled="pngLoading.has(logo.src)"Also applies to: 170-170
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1d8009eb-6e5d-4e10-b1d6-5793de384132
📒 Files selected for processing (2)
app/pages/brand.vuetest/unit/a11y-component-coverage.spec.ts
- Localise OG image metadata with $t() instead of hardcoded strings - Track PNG loading per-logo with a Set to prevent race conditions - Add safe fallback for empty accentColors array
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
app/components/Brand/Customize.vue (1)
56-59: PreferaddEventListener()overon-property assignment.The linter flags these event handler assignments. Using
addEventListeneris the recommended pattern.♻️ Suggested refactor
const loaded = new Promise<void>((resolve, reject) => { - img.onload = () => resolve() - img.onerror = () => reject(new Error('Failed to load custom SVG')) + img.addEventListener('load', () => resolve()) + img.addEventListener('error', () => reject(new Error('Failed to load custom SVG'))) })
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 655116fe-f57d-4712-bf76-565cfa75ac1a
📒 Files selected for processing (3)
app/components/Brand/Customize.vueapp/pages/brand.vuei18n/schema.json
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
app/components/Brand/Customize.vue (2)
32-42: Consider using the importeddownloadBlobhelper to reduce duplication.The
downloadBlobfunction fromuseSvgToPng()is already imported but unused here. The manual download logic duplicates what that helper provides.♻️ Proposed refactor
function downloadCustomSvg() { const svg = getCustomSvgString() if (!svg) return const blob = new Blob([svg], { type: 'image/svg+xml' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `npmx-logo-${activeAccentId.value}.svg` - a.click() - URL.revokeObjectURL(url) + downloadBlob(blob, `npmx-logo-${activeAccentId.value}.svg`) }
55-61: PreferaddEventListenerover direct event handler properties.Static analysis correctly flags that
addEventListenershould be used instead of assigning toonload/onerrorproperties directly.♻️ Proposed refactor
const img = new Image() const loaded = new Promise<void>((resolve, reject) => { - img.onload = () => resolve() - img.onerror = () => reject(new Error('Failed to load custom SVG')) + img.addEventListener('load', () => resolve()) + img.addEventListener('error', () => reject(new Error('Failed to load custom SVG'))) }) img.src = url await loaded
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b45a838a-515d-4fc2-92f9-3cddf9aa662c
📒 Files selected for processing (1)
app/components/Brand/Customize.vue
…uidelines - Remove app icon (irrelevant to branding) - Remove colors section (not needed for asset page) - Replace do's/don'ts with a single accessibility-focused blockquote - Move "copied" key to logo_menu namespace
- Each dark/light logo preview now has its own SVG/PNG download buttons
- Increased spacing between logo cards
- Guidelines reworded to a friendly blockquote ("just a note")
- Removed app icon from logos (not relevant to branding)
- Removed colors section
Sounds good, or perhaps we go full Nuxt design-kit style? Display a toast on successful copy, wonder if that's overkill or not Screen.Recording.2026-03-22.at.20.28.07.mov |
Keep the menu open for 800ms after copying the SVG so the user can see the "Copied!" label before it disappears. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
nvm, went with delay |
I've seen this code used all over the place and so this is a good chance to make a util for it proper - I'll leave the chart stuff though for this PR as it needs more involved testing that I don't have time for tonight
Since it doesn't hold any state, it seems like it's better as a utility fn - we can always add it back if needed later
| async function copySvg() { | ||
| const res = await fetch('/logo.svg') | ||
| const svg = await res.text() | ||
| await copy(svg) | ||
| setTimeout(close, 1000) | ||
| } |
There was a problem hiding this comment.
this won't work on Safari but I'm not going to block this right now - we can fix this at the same time we fix #2151
There was a problem hiding this comment.
I added a fix for this here. 😄
ghostdevv
left a comment
There was a problem hiding this comment.
this is awesome!!
I pushed a couple commits that fixed some lint errors, and removed the composable since it didn't have any state and seems better suited as a util function - just a couple more things then we can merge!
| description: $t('brand.meta_description'), | ||
| }) | ||
|
|
||
| const logos = [ |
There was a problem hiding this comment.
Hmm, the word mark or the logo mark?
There was a problem hiding this comment.
All of them - I saw you pushed a change but for me the slashes are still blue?
There was a problem hiding this comment.
Oh!
I think in first iteration we removed app logo https://discord.com/channels/1464542801676206113/1485225112461643786/1485278281077882952. Do we want to create for these or we allow a customizer for any type of color? 🤔
- Fix Safari clipboard by using ClipboardItem with promise blob - Add loading spinner to PNG download button in customize section - Fix logo height mismatch between dark/light variants - Force canonical sky accent color on light logo previews
- Add logo-mark-light.svg with dark accent (#006fc2) and black square - Use srcLight variant instead of filter: invert(1) for light logo mark - Add loading spinners to PNG download buttons in logo grid
|
Just a thought @ghostdevv, The SVG download buttons currently use Should we convert the SVG downloads to |
Hmm, how much work is it to make the link here look like the button? It seems pretty close already - otherwise we should probably just use buttonbase yea |
…ading spinners Use ButtonBase consistently for all download buttons, add spinner loading states to SVG download buttons, create light-mode wordmark SVG, and ensure light variant downloads use the correct srcLight file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Made the change 👍 |
|
Awesome work @Adebesin-Cell. Love how the idea was randomly thrown around I think yesterday, and now we already have an amazing implementation 🙌 Not sure if it has been mentioned already (sorry I didn't read through all the threads), but I think it would be nice if the big logo at the landing page would also support the same options as the small logo in the header on all other pages. IFF it is possible in an accessible way! |




This PR adds a dedicated
/brandpage for press, media, and community use, taking inspiration from Nuxt’s design kit and IQ Wiki’s branding page.What’s included
Logo showcase
A full set of logo variants (wordmark, mark), displayed on both light and dark backgrounds, with quick SVG and PNG downloads.
Customize your logo
An interactive preview where you can adjust the accent color and toggle between light/dark backgrounds. You can download the customized logo as SVG or PNG, with all colors baked in (no CSS variables).
Color palette
Core brand colors (Background, Foreground, Accent) with one-click copy for both HEX and OKLch values, plus screen reader-friendly
aria-livefeedback.Typography specimens
Geist Sans and Geist Mono are shown across multiple sizes, including pangrams and number samples.
Usage guidelines
Clear do’s and don’ts to help people use the logo correctly.
Header logo context menu
Right-click the header logo anywhere in the app to quickly “Copy logo as SVG” or jump to the brand kit, mirroring the pattern from nuxt.com.
Media
Screen.Recording.2026-03-22.at.17.52.14.mov