Skip to content

Feat/luna theme - #1

Merged
dryfish09 merged 3 commits into
mainfrom
feat/luna-theme
Jul 30, 2026
Merged

Feat/luna theme#1
dryfish09 merged 3 commits into
mainfrom
feat/luna-theme

Conversation

@dryfish09

Copy link
Copy Markdown
Owner

No description provided.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Your Qodo trial ends soon. Ask your workspace admin to set up billing to keep reviews running after the trial. Manage billing

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add Lunar theme and fix PNG export avatar embedding

✨ Enhancement 🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Add a new “Lunar” UI theme with dedicated styling across all components.
• Extend SVG card rendering to support Lunar-specific visuals and gradients.
• Fix PNG export by preloading and embedding the avatar image before conversion.
Diagram

graph TD
  U["User"] --> UI["Theme/Download UI"] --> TM(["Theme manager"]) --> CG[["SVG card generator"]] --> PV["Card preview"]
  UI --> PNG(["PNG exporter"]) --> CNV["Canvas render"] --> DL["File download"]
  TM --> PNG

  subgraph Legend
    direction LR
    _u["User action"] ~~~ _c(["Controller/logic"]) ~~~ _p[["Generator/renderer"]]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Refactor themes to CSS variables + palette map
  • ➕ Avoids duplicating per-theme CSS selectors and JS conditionals
  • ➕ Makes adding future themes mostly data-driven (single palette definition)
  • ➕ Reduces risk of inconsistent styling across components
  • ➖ Requires a broader refactor of existing CSS rules
  • ➖ May be harder to land as a single PR without visual regression checks
2. Move CSS/JS out of index.html into separate files
  • ➕ Improves maintainability and reviewability (smaller diffs per concern)
  • ➕ Enables caching and easier future testing/linting
  • ➖ Adds build/hosting considerations depending on how the page is deployed
  • ➖ Not necessary if the project intentionally remains single-file
3. Use a dedicated SVG-to-PNG pipeline (e.g., canvg)
  • ➕ More predictable SVG rendering across browsers
  • ➕ May simplify image embedding and rasterization edge cases
  • ➖ Adds a dependency and bundle weight
  • ➖ Not always compatible with all SVG features/animations

Recommendation: The PR’s approach is reasonable for a static single-page tool: it adds Lunar via explicit CSS rules and extends the existing renderer with minimal churn. For long-term scalability (more themes, fewer repeated selectors), consider a follow-up refactor to centralize theme tokens (CSS variables + a palette map) and progressively simplify the conditional branches in SVG generation and PNG export.

Files changed (1) +420 / -83

Enhancement (1) +420 / -83
index.htmlAdd Lunar theme styling, rendering, URL support, and robust PNG export +420/-83

Add Lunar theme styling, rendering, URL support, and robust PNG export

• Introduces a new Lunar theme across CSS (body/container/buttons/inputs/status/footer/etc.) and adds a Lunar theme button. Extends theme selection and color selection logic with a LUNAR_COLORS palette and Lunar-specific SVG visuals (stars, shooting stars, glow/gradient tweaks, minor layout adjustments). Reworks PNG download to preload the avatar, embed it into the cloned SVG (data URL) before rasterization, and adds theme-aware background fills plus a safer fallback conversion path; also allows theme=lunar via URL parameters.

index.html

@dryfish09
dryfish09 merged commit 0b51e6f into main Jul 30, 2026
1 check passed
@dryfish09
dryfish09 deleted the feat/luna-theme branch July 30, 2026 03:41
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Lunar icon transform overridden 🐞 Bug ≡ Correctness
Description
In generateProfileCardSVG, the lunar 🌙/🚀 icons are placed with transform="translate(...)" but
then an animateTransform animates the same transform attribute without additive="sum", so the
base translation is replaced during the animation. This displaces the icons from their intended
bottom positions while the animation runs.
Code

index.html[R1517-1524]

+                svg += '    <g transform="translate(85, ' + (cardHeight - 35) + ')">\n';
+                svg += '        <animateTransform attributeName="transform" type="rotate" values="0 80 305;10 80 305;0 80 305" dur="4s" repeatCount="indefinite"/>\n';
+                svg += '        <text x="0" y="0" font-size="24">🌙</text>\n';
+                svg += '    </g>\n';
+
+                svg += '    <g transform="translate(560, ' + (cardHeight - 35) + ')">\n';
+                svg += '        <animateTransform attributeName="transform" type="translate" values="0 0;5 -5;0 0" dur="3s" repeatCount="indefinite"/>\n';
+                svg += '        <text x="0" y="0" font-size="20">🚀</text>\n';
Evidence
The code sets a base transform="translate(...)" on a <g> and then animates the same transform
attribute on that element, which replaces (does not compose with) the translate by default,
displacing the element while the animation runs.

index.html[1474-1526]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The lunar SVG adds a base `transform="translate(...)"` to position the icon groups, but then animates `attributeName="transform"` on the same element. Because SMIL’s default is `additive="replace"`, the animation overwrites the translate transform, causing the icons to render in the wrong place during animation.

### Issue Context
This affects only the new lunar theme decorative icons (🌙 and 🚀) added in the PR.

### Fix Focus Areas
- index.html[1517-1525]

### Suggested fix
Use one of the following patterns:
1) **Nested groups (most compatible):** keep an outer `<g transform="translate(...)"` and put an inner `<g>` that carries the `animateTransform`.
2) Add `additive="sum"` to `animateTransform` (only if you’re confident your target SVG renderers support it consistently).

Example (nested group):
```js
svg += '<g transform="translate(85, ' + (cardHeight - 35) + ')">\n';
svg += '  <g>\n';
svg += '    <animateTransform attributeName="transform" type="rotate" ... />\n';
svg += '    <text ...>🌙</text>\n';
svg += '  </g>\n';
svg += '</g>\n';
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. PNG export CORS exception 🐞 Bug ☼ Reliability
Description
downloadPNG() draws the avatar into a temporary canvas and calls tempCanvas.toDataURL() without
any error handling; if the avatar image loads but is not CORS-readable, the canvas becomes tainted
and toDataURL() throws. Because this happens inside img.onload (not img.onerror), PNG
generation aborts without reaching the fallback path.
Code

index.html[R1916-1921]

+                    tempCtx.drawImage(img, 0, 0, imgWidth, imgHeight);
+                    
+                    var dataUrl = tempCanvas.toDataURL('image/png');
+                    avatarImg.setAttribute('href', dataUrl);
+                    avatarImg.setAttribute('xlink:href', dataUrl);
+                }
Evidence
The new code path always calls tempCanvas.toDataURL('image/png') after drawing the loaded avatar,
but does not wrap it in a try/catch; this is a known failure point when a canvas is tainted by a
non-CORS-readable image, and it occurs inside img.onload so the img.onerror fallback will not
run.

index.html[1842-1921]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
In `downloadPNG()`, the avatar is loaded into an `Image`, drawn into a temporary canvas, and then converted via `tempCanvas.toDataURL('image/png')`. If the image is cross-origin without usable CORS headers (or otherwise treated as non-exportable), `toDataURL()` throws a `SecurityError`, which is currently unhandled and prevents PNG export (and does not trigger the existing fallback).

### Issue Context
The current fallback only runs on `img.onerror` (avatar load failure). The failure mode here is different: the image can load successfully but still be non-exportable.

### Fix Focus Areas
- index.html[1869-1921]

### Suggested fix
Wrap the avatar canvas conversion (and/or the final canvas conversion) in `try/catch` and, on exception, run the existing fallback logic (serialize SVG to data URL and render that) or show a clear error.

Example:
```js
try {
 tempCtx.drawImage(img, 0, 0, imgWidth, imgHeight);
 var dataUrl = tempCanvas.toDataURL('image/png');
 avatarImg.setAttribute('href', dataUrl);
 avatarImg.setAttribute('xlink:href', dataUrl);
} catch (e) {
 // fall back to the non-avatar-inlining path
 return runSvgFallback(clonedSvg, width, height);
}
```
Where `runSvgFallback(...)` can reuse the code currently inside `img.onerror` to avoid duplication.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread index.html
Comment on lines +1517 to +1524
svg += ' <g transform="translate(85, ' + (cardHeight - 35) + ')">\n';
svg += ' <animateTransform attributeName="transform" type="rotate" values="0 80 305;10 80 305;0 80 305" dur="4s" repeatCount="indefinite"/>\n';
svg += ' <text x="0" y="0" font-size="24">🌙</text>\n';
svg += ' </g>\n';

svg += ' <g transform="translate(560, ' + (cardHeight - 35) + ')">\n';
svg += ' <animateTransform attributeName="transform" type="translate" values="0 0;5 -5;0 0" dur="3s" repeatCount="indefinite"/>\n';
svg += ' <text x="0" y="0" font-size="20">🚀</text>\n';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Lunar icon transform overridden 🐞 Bug ≡ Correctness

In generateProfileCardSVG, the lunar 🌙/🚀 icons are placed with transform="translate(...)" but
then an animateTransform animates the same transform attribute without additive="sum", so the
base translation is replaced during the animation. This displaces the icons from their intended
bottom positions while the animation runs.
Agent Prompt
### Issue description
The lunar SVG adds a base `transform="translate(...)"` to position the icon groups, but then animates `attributeName="transform"` on the same element. Because SMIL’s default is `additive="replace"`, the animation overwrites the translate transform, causing the icons to render in the wrong place during animation.

### Issue Context
This affects only the new lunar theme decorative icons (🌙 and 🚀) added in the PR.

### Fix Focus Areas
- index.html[1517-1525]

### Suggested fix
Use one of the following patterns:
1) **Nested groups (most compatible):** keep an outer `<g transform="translate(...)"` and put an inner `<g>` that carries the `animateTransform`.
2) Add `additive="sum"` to `animateTransform` (only if you’re confident your target SVG renderers support it consistently).

Example (nested group):
```js
svg += '<g transform="translate(85, ' + (cardHeight - 35) + ')">\n';
svg += '  <g>\n';
svg += '    <animateTransform attributeName="transform" type="rotate" ... />\n';
svg += '    <text ...>🌙</text>\n';
svg += '  </g>\n';
svg += '</g>\n';
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread index.html
Comment on lines +1916 to +1921
tempCtx.drawImage(img, 0, 0, imgWidth, imgHeight);

var dataUrl = tempCanvas.toDataURL('image/png');
avatarImg.setAttribute('href', dataUrl);
avatarImg.setAttribute('xlink:href', dataUrl);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Png export cors exception 🐞 Bug ☼ Reliability

downloadPNG() draws the avatar into a temporary canvas and calls tempCanvas.toDataURL() without
any error handling; if the avatar image loads but is not CORS-readable, the canvas becomes tainted
and toDataURL() throws. Because this happens inside img.onload (not img.onerror), PNG
generation aborts without reaching the fallback path.
Agent Prompt
### Issue description
In `downloadPNG()`, the avatar is loaded into an `Image`, drawn into a temporary canvas, and then converted via `tempCanvas.toDataURL('image/png')`. If the image is cross-origin without usable CORS headers (or otherwise treated as non-exportable), `toDataURL()` throws a `SecurityError`, which is currently unhandled and prevents PNG export (and does not trigger the existing fallback).

### Issue Context
The current fallback only runs on `img.onerror` (avatar load failure). The failure mode here is different: the image can load successfully but still be non-exportable.

### Fix Focus Areas
- index.html[1869-1921]

### Suggested fix
Wrap the avatar canvas conversion (and/or the final canvas conversion) in `try/catch` and, on exception, run the existing fallback logic (serialize SVG to data URL and render that) or show a clear error.

Example:
```js
try {
  tempCtx.drawImage(img, 0, 0, imgWidth, imgHeight);
  var dataUrl = tempCanvas.toDataURL('image/png');
  avatarImg.setAttribute('href', dataUrl);
  avatarImg.setAttribute('xlink:href', dataUrl);
} catch (e) {
  // fall back to the non-avatar-inlining path
  return runSvgFallback(clonedSvg, width, height);
}
```
Where `runSvgFallback(...)` can reuse the code currently inside `img.onerror` to avoid duplication.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant