Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"theme_color": "#1a1a2e",
"background_color": "#1a1a2e",
"display": "standalone",
"start_url": "/ScriptHammer/",
"scope": "/ScriptHammer/",
"start_url": "/",
"scope": "/",
"orientation": "portrait-primary",
"categories": [
"productivity",
Expand All @@ -17,76 +17,76 @@
"prefer_related_applications": false,
"icons": [
{
"src": "/ScriptHammer/icon-72.svg",
"src": "/icon-72.svg",
"sizes": "72x72",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/ScriptHammer/icon-96.svg",
"src": "/icon-96.svg",
"sizes": "96x96",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/ScriptHammer/icon-128.svg",
"src": "/icon-128.svg",
"sizes": "128x128",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/ScriptHammer/icon-144.svg",
"src": "/icon-144.svg",
"sizes": "144x144",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/ScriptHammer/icon-152.svg",
"src": "/icon-152.svg",
"sizes": "152x152",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/ScriptHammer/icon-192.svg",
"src": "/icon-192.svg",
"sizes": "192x192",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/ScriptHammer/icon-384.svg",
"src": "/icon-384.svg",
"sizes": "384x384",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/ScriptHammer/icon-512.svg",
"src": "/icon-512.svg",
"sizes": "512x512",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/ScriptHammer/icon-192x192-maskable.png",
"src": "/icon-192x192-maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/ScriptHammer/icon-512x512-maskable.png",
"src": "/icon-512x512-maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"screenshots": [
{
"src": "/ScriptHammer/screenshots/desktop.png",
"src": "/screenshots/desktop.png",
"sizes": "1920x1080",
"type": "image/png",
"form_factor": "wide",
"label": "Desktop view"
},
{
"src": "/ScriptHammer/screenshots/mobile.png",
"src": "/screenshots/mobile.png",
"sizes": "390x844",
"type": "image/png",
"form_factor": "narrow",
Expand All @@ -96,33 +96,33 @@
"shortcuts": [
{
"name": "Components",
"url": "/ScriptHammer/components",
"url": "/components",
"description": "View all components",
"icons": [
{
"src": "/ScriptHammer/icon-96.svg",
"src": "/icon-96.svg",
"sizes": "96x96"
}
]
},
{
"name": "Contact",
"url": "/ScriptHammer/contact",
"url": "/contact",
"description": "Send us a message",
"icons": [
{
"src": "/ScriptHammer/icon-96.svg",
"src": "/icon-96.svg",
"sizes": "96x96"
}
]
},
{
"name": "Themes",
"url": "/ScriptHammer/themes",
"url": "/themes",
"description": "Choose your theme",
"icons": [
{
"src": "/ScriptHammer/icon-96.svg",
"src": "/icon-96.svg",
"sizes": "96x96"
}
]
Expand All @@ -131,7 +131,7 @@
"related_applications": [],
"protocol_handlers": [],
"share_target": {
"action": "/ScriptHammer/share",
"action": "/share",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
Expand Down
15 changes: 7 additions & 8 deletions src/components/calendar/providers/CalComProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Cal, { getCalApi } from '@calcom/embed-react';
import { useEffect } from 'react';
import { createLogger } from '@/lib/logger';
import { isDarkTheme } from '@/utils/theme-utils';
import { useEmbedThemeColor } from '@/hooks/useEmbedThemeColor';

interface CalComProviderProps {
calLink: string;
Expand Down Expand Up @@ -49,12 +49,11 @@ export function CalComProvider({
})();
}, []);

// Auto-detect theme
const theme =
typeof window !== 'undefined'
? document.documentElement.getAttribute('data-theme')
: 'light';
const isDark = isDarkTheme(theme);
// Theme-aware brand color (issue #39). brandColor is the active DaisyUI
// theme's --color-primary, applied to the embed on mount; the binary
// light/dark `theme` prop is unchanged. (The Cal.com iframe initializes once,
// so an already-rendered embed keeps its color until it re-initializes.)
const { hexWithHash: brandColor, isDark } = useEmbedThemeColor('p');

if (mode === 'popup') {
return (
Expand Down Expand Up @@ -85,7 +84,7 @@ export function CalComProvider({
...config,
theme: isDark ? 'dark' : 'light',
branding: {
brandColor: '#00a2ff',
brandColor,
},
}}
/>
Expand Down
24 changes: 7 additions & 17 deletions src/components/calendar/providers/CalendlyProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import {
PopupWidget,
useCalendlyEventListener,
} from 'react-calendly';
import { useEffect } from 'react';
import { createLogger } from '@/lib/logger';
import { isDarkTheme } from '@/utils/theme-utils';
import { useEmbedThemeColor } from '@/hooks/useEmbedThemeColor';

interface CalendlyProviderProps {
url: string;
Expand Down Expand Up @@ -44,26 +43,17 @@ export function CalendlyProvider({
},
});

// Apply theme-aware styles
useEffect(() => {
const theme = document.documentElement.getAttribute('data-theme');
const isDark = isDarkTheme(theme);

// Theme will be applied through pageSettings below
logger.debug('Theme detected', { theme, isDark });
}, []);

const theme =
typeof window !== 'undefined'
? document.documentElement.getAttribute('data-theme')
: 'light';
const isDark = isDarkTheme(theme);
// Theme-aware brand color (issue #39). The accent is the active DaisyUI
// theme's --color-primary, applied to the widget on mount; bg/text stay on
// the dark/light split. (react-calendly builds the iframe once on mount, so
// an already-rendered widget keeps its color until it next re-initializes.)
const { hex: primaryColor, isDark } = useEmbedThemeColor('p');

const pageSettings = {
backgroundColor: isDark ? '1a1a1a' : 'ffffff',
hideEventTypeDetails: false,
hideLandingPageDetails: false,
primaryColor: '00a2ff',
primaryColor,
textColor: isDark ? 'ffffff' : '000000',
};

Expand Down
66 changes: 46 additions & 20 deletions src/components/molecular/DisqusComments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@

import { useEffect, useRef, useState } from 'react';
import Script from 'next/script';
import { isDarkTheme } from '@/utils/theme-utils';
import { useEmbedThemeColor } from '@/hooks/useEmbedThemeColor';
import { getAccessibleEmbedColor } from '@/utils/embed-theme';

// Disqus thread background (hardcoded RGB so Disqus's embed.js never sees an
// OKLCH value it can't parse). Link text must stay legible against these.
const DISQUS_BG_DARK = '#111827'; // rgb(17, 24, 39)
const DISQUS_BG_LIGHT = '#ffffff'; // rgb(255, 255, 255)
// Legible link fallbacks used when a theme's primary fails AA on the bg above
// (issue #46 NFR-002 — many DaisyUI primaries are pale accents). Both clear
// WCAG AA (4.5:1) on their respective bg: blue-300 9.84:1 on dark, blue-600
// 5.17:1 on light. (The previous blue-500 #3b82f6 was only 3.68:1 — a latent
// a11y gap this contrast work surfaced.)
const DISQUS_LINK_FALLBACK_DARK = '#93c5fd'; // Tailwind blue-300
const DISQUS_LINK_FALLBACK_LIGHT = '#2563eb'; // Tailwind blue-600

interface DisqusCommentsProps {
slug: string;
Expand Down Expand Up @@ -40,6 +53,19 @@ export default function DisqusComments({
const containerRef = useRef<HTMLDivElement>(null);
const observerRef = useRef<IntersectionObserver | null>(null);

// Theme-aware link color + dark/light scheme (issue #46). The hook
// re-renders on data-theme change so the injected CSS + Disqus colorScheme
// re-apply across all 34 DaisyUI themes. The link color uses the theme
// primary only when it clears WCAG AA against the Disqus bg, else a legible
// fallback (NFR-002) — many DaisyUI primaries are pale accents that would be
// illegible as link text. Disqus's embed.js can't parse OKLCH, so this is hex.
const { isDark: dark } = useEmbedThemeColor('p');
const linkColor = getAccessibleEmbedColor(
dark ? DISQUS_BG_DARK : DISQUS_BG_LIGHT,
dark ? DISQUS_LINK_FALLBACK_DARK : DISQUS_LINK_FALLBACK_LIGHT,
'p'
);

// Generate production URL - hardcoded for GitHub Actions compatibility
const productionUrl = url?.startsWith('http')
? url
Expand Down Expand Up @@ -68,26 +94,29 @@ export default function DisqusComments({
};
}, [shortname]);

// Configure Disqus when visible
// Mark loaded so the script tag renders (one-shot gate).
useEffect(() => {
if (!isVisible || !shortname || isLoaded) return;
setIsLoaded(true);
}, [isVisible, shortname, isLoaded]);

// Build/refresh window.disqus_config and (re)initialize Disqus. `dark` is in
// the deps so a post-load theme switch rebuilds the config with the new
// colorScheme and calls DISQUS.reset to re-render the embed in that scheme
// (issue #46). The config builder must be rebuilt here — not behind the
// one-shot isLoaded gate above — or colorScheme would freeze at first load.
useEffect(() => {
if (!scriptReady || !isLoaded || !shortname) return;

// Set global Disqus configuration
window.disqus_config = function (this: any) {
this.page = this.page || {};
this.page.url = productionUrl;
this.page.identifier = slug;
this.page.title = title;
// Follow the DaisyUI dark/light split (issue #46).
this.page.colorScheme = dark ? 'dark' : 'light';
};

setIsLoaded(true);
}, [isVisible, shortname, slug, title, productionUrl, isLoaded]);

// Initialize or reset Disqus when script is ready
useEffect(() => {
if (!scriptReady || !isLoaded || !shortname) return;

// Check if DISQUS is available and reset it
const initializeDisqus = () => {
if (window.DISQUS) {
try {
Expand All @@ -106,7 +135,7 @@ export default function DisqusComments({
const timeout = setTimeout(initializeDisqus, 1000);

return () => clearTimeout(timeout);
}, [scriptReady, isLoaded, shortname]);
}, [scriptReady, isLoaded, shortname, slug, title, productionUrl, dark]);

// Cleanup on unmount
useEffect(() => {
Expand Down Expand Up @@ -137,21 +166,18 @@ export default function DisqusComments({
useEffect(() => {
if (!isVisible) return;

// Get computed styles to determine if we're in a dark theme
const dark = isDarkTheme(
document.documentElement.getAttribute('data-theme')
);

const style = document.createElement('style');
style.textContent = `
/* Minimal override for Disqus OKLCH compatibility
Only set what's absolutely necessary to prevent conflicts */

:root {
/* Override CSS variables with RGB fallbacks for Disqus */
/* Override CSS variables with RGB fallbacks for Disqus.
bg/text follow the dark/light split; link tracks the active
DaisyUI theme's primary color (hex — Disqus can't parse OKLCH). */
--disqus-bg: ${dark ? 'rgb(17, 24, 39)' : 'rgb(255, 255, 255)'};
--disqus-text: ${dark ? 'rgb(243, 244, 246)' : 'rgb(31, 41, 55)'};
--disqus-link: ${dark ? 'rgb(147, 197, 253)' : 'rgb(59, 130, 246)'};
--disqus-link: ${linkColor};
}

#disqus_thread {
Expand Down Expand Up @@ -187,7 +213,7 @@ export default function DisqusComments({
document.head.removeChild(styleToRemove);
}
};
}, [isVisible]);
}, [isVisible, dark, linkColor]);

// Don't render if no shortname
if (!shortname) {
Expand Down
Loading
Loading