Skip to content

Feature - Dark Mode Generator tool#720

Open
markmead wants to merge 36 commits into
mainfrom
feature/dark-mode-generator
Open

Feature - Dark Mode Generator tool#720
markmead wants to merge 36 commits into
mainfrom
feature/dark-mode-generator

Conversation

@markmead

@markmead markmead commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Introduces a /tools section to HyperUI, evolving the site from a component library into a component and tools library
  • Adds a Dark Mode Generator: paste Tailwind HTML, get back the same HTML with dark: variants added — fully browser-based, nothing leaves the client
  • Extracts a shared ESM core (src/lib/dark-mode/) used by both the GUI and the existing CLI script

What's included

Shared core

  • src/constants/dark-mode.jsSHADE_MAP, COLOR_MAP, COLOR_FAMILIES
  • src/lib/dark-mode/transform-class.js — per-class transform logic with element-aware rule matching
  • src/lib/dark-mode/transform-html.jstransformHtmlString (regex, CLI/Node) + transformHtmlDom (DOMParser, browser)
  • src/lib/dark-mode/config.jsDEFAULT_CONFIG, localStorage persistence, normalizer, import/export

Pages & component

  • src/pages/tools/index.astro — tools landing page
  • src/pages/tools/dark-mode-generator.astro — page wrapper
  • src/components/DarkModeGenerator.astro — full GUI with sidebar config, live preview, and code output

Other

  • src/constants/seo.js — replaces deleted src/consts.ts; all 8 importers updated
  • scripts/generate-dark-variants.js — refactored to import from shared core (behavior unchanged)
  • src/components/SiteHeader.astro + DropdownMenu.astro — Tools nav link added

Safety model

Risk Mitigation
XSS from rendered HTML Output written via textContent, never innerHTML
Script execution during transform DOMParser builds a detached document — no scripts run
Script execution in preview <iframe sandbox="allow-scripts"> without allow-same-origin — cannot reach parent
Corrupt localStorage Versioned key + normalizeConfig() always returns a safe default

Test plan

  • Visit /tools — landing card present
  • Visit /tools/dark-mode-generator — tool loads, config persists across reload
  • Paste a component — output matches CLI behavior (bg-whitebg-white dark:bg-gray-900)
  • Add a rule excluding button for shade 600<button class="bg-blue-600"> is untouched
  • Paste <script>alert(1)</script> and <img src=x onerror=alert(1)> — nothing executes
  • Light / Dark / Split / Code preview modes all work
  • Export and re-import config round-trips correctly
  • Reset clears config back to defaults
  • pnpm generate:dark-variants output is identical to before the refactor

🤖 Generated with Claude Code

markmead and others added 5 commits June 6, 2026 13:23
Extracts SHADE_MAP, COLOR_MAP, COLOR_FAMILIES into src/constants/dark-mode.js
and creates src/lib/dark-mode/ with transform-class.js, transform-html.js,
and config.js (with localStorage persistence, normalizer, import/export).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the TypeScript consts file with a plain ESM JS module and updates
all 8 importers to use the new path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces inline SHADE_MAP/COLOR_MAP/transform logic in generate-dark-variants.js
with imports from src/lib/dark-mode/. Behavior is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Tools entry to the desktop header and mobile dropdown menu.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces the /tools section with a Dark Mode Generator: a browser-based
tool that transforms Tailwind HTML to include dark: variants. Features a
configurable sidebar (shade map, utility toggles, rules), sandboxed
light/dark/split/code preview, and localStorage persistence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 6, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
hyperui 38a67a6 Commit Preview URL

Branch Preview URL
Jun 10 2026, 08:09 AM

@markmead markmead added the enhancement New feature or request label Jun 6, 2026
@markmead markmead linked an issue Jun 6, 2026 that may be closed by this pull request
markmead and others added 22 commits June 6, 2026 13:49
config.shadeMap was never read — both the shade gate and the dark shade
fallback were referencing the imported SHADE_MAP constant directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply defensive coding and formatting across dark-mode utilities and the generator UI. Key changes:

- Add ESLint rules (curly, eqeqeq).
- Add explicit braces and block returns to many functions (normalizeConfig, normalizeShadeMap, normalizeColorMap, normalizeUtilities, normalizeRule, loadConfig, saveConfig, transform-class, transform-html, and component methods) to improve readability and avoid ambiguous control flow.
- Strengthen null/undefined checks and early returns (file import, clipboard copy, dialog close, iframe handling, element queries, etc.).
- Minor DOM/markup formatting and class/attribute reflows in DarkModeGenerator.astro (line wrapping, small class name tweak for alignment, SVG path inline).
- Small behavior/comments tweak: avoid clearing timers without braces and make clipboard catch a no-op.
- Update git metadata newline at EOF.

No core algorithm changes — these edits are primarily safety, style, and maintainability improvements.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adjust DarkModeGenerator layout and sizing (increase gap, add textarea text-sm, remove extra padding, reduce preview/code heights). Fix import file handler to properly short-circuit on missing/failed imports and tidy event listener formatting. Consolidate form input classes and update SVG icon constants (add xmlns/size attributes and lucide classes). Simplify preview-document to return a template literal HTML (embed Tailwind script tag) and remove inline background style. Minor formatting/whitespace cleanup in rule-renderer.
…ughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ons to group

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ons by default

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Visual and class updates for the dark-mode panel and controls:

- src/components/DarkModeSidePanel.astro: add border to the options panel wrapper and update the info button styles (hover/background/text color) for better contrast and consistency.
- src/lib/dark-mode/element-constants.js: simplify FORM_INPUT_CLASS (removed resize-none) to align input styling.
- src/lib/dark-mode/ui-renderer.js: tweak shade input classes (remove extra padding/focus classes, adjust alignment) and improve the empty rules message typography for readability. Also a small formatting fix in the aria-label setAttribute call.

These changes are UI-focused to improve visual consistency and legibility across the dark-mode controls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a data-last-saved element to the DarkModeSidePanel and update layout to place it alongside the Reset/Export/Import controls. Update DarkModeGenerator.saveConfiguration() to set the paragraph text to a formatted "Last saved: HH:MM" using toLocaleTimeString so users get immediate visual feedback after saving.
markmead and others added 9 commits June 10, 2026 08:13
Replaces the per-rule <details> accordion (with its innerHTML template
strings and per-rule querySelector chains) with a compact list row per
rule and a single persistent bottom-sheet dialog for editing. The 7
fields are authored once as static Astro markup in DarkModeInspector,
bound on open via bindInspector using AbortController to avoid listener
leaks. Inspector closes on outside-click, delete, add, reset, and import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes modal rounding, replaces the Name grid field with a borderless
inline input in the header (edit in place, no toggle logic), and
restructures the 6 fields into a grid-cols-6 layout with Apply when /
In dark mode / Skip when section headings aligned above their fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the borderless transparent input with a rendered span + pencil
icon button. Clicking the button swaps to a standard input (pre-filled),
Enter/blur commits, Escape discards without triggering onChange.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Silent name loss on rule switch: per-element Symbol keys replace the
  module-level AbortController (fixes multi-instance sharing); a flush
  function stored on the inspector element commits any pending name edit
  before the previous binding is aborted
- Listener accumulation: dismiss AbortController added as instance field,
  used for both the close-button click and document pointerdown listeners,
  and aborted in disconnectedCallback
- syncOverwriteExisting re-register: split into bindOverwriteExisting
  (registers listener once in connectedCallback) and syncOverwriteExisting
  (updates checked state only, called on reset/import)
- O(N) list rebuild on every field change: refreshRules() moved from
  onChange to a new onNameChange callback; non-name field changes no longer
  tear down the whole rule list
- Inconsistent name fallbacks: bindInspector now receives ruleIndex and
  uses 'Rule N' as the display fallback, matching buildRuleListItem
- discardingNameEdit flag removed: Escape resets nameInput.value before
  blur, so the blur handler uses value comparison to detect genuine commits
- Dead state removed: currentInspectorRuleId and currentPreviewMode fields
  dropped (write-only, never read)
- Duplicated input class strings: DarkModeInspector.astro imports
  FORM_INPUT_CLASS and NUMBER_INPUT_CLASS from element-constants.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All null checks and optional chaining on statically-present template elements
are replaced with non-null assertions. Deleted syncOverwriteExisting (inlined
its one-liner at both call sites), removed section-divider comments, removed
explanatory comments from rule-renderer.js, and deleted the implemented plan doc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renamed all single-character, abbreviated, and single-word variables:
r → existingRule, signal → abortSignal, inspectorEl → inspectorElement,
_ → _fullMatchString, classAttr → classAttribute, shadeNum → shadeNumber

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
shadeNum, _ (discard convention), and signal (AbortController property name)
do not need renaming — they are clear as-is. Also fixes classAttributeibute
corruption caused by replace_all matching inside an already-renamed identifier.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
white should map to black (and vice versa), not gray-900. The hardcoded
gray-900 → white bypass is also removed so gray-900 falls through to the
shade map and correctly produces gray-50.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Single-page layout builder

1 participant