fix: invert macOS and PlayStation icons in dark mode #64
@@ -2298,15 +2298,32 @@ export default defineConfig({
|
|||||||
<p>Follow these steps to add Pulse through GTM:</p>
|
<p>Follow these steps to add Pulse through GTM:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Go to <strong>Tags → New → Custom HTML</strong></li>
|
<li>Go to <strong>Tags → New → Custom HTML</strong></li>
|
||||||
<li>Paste the Pulse script</li>
|
<li>Paste the snippet below (replace <code>your-site.com</code> with your domain)</li>
|
||||||
<li>Set the trigger to <strong>All Pages</strong></li>
|
<li>Set the trigger to <strong>All Pages</strong></li>
|
||||||
<li>Publish your container</li>
|
<li>Publish your container</li>
|
||||||
</ol>
|
</ol>
|
||||||
<CodeBlock filename="GTM → Custom HTML Tag">{`<script
|
<CodeBlock filename="GTM → Custom HTML Tag (recommended)">{`<script>
|
||||||
|
window.pulseConfig = { domain: "your-site.com" };
|
||||||
|
</script>
|
||||||
|
<script defer src="https://pulse.ciphera.net/script.js"></script>`}</CodeBlock>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This uses a global config object so that GTM does not need to preserve{' '}
|
||||||
|
<code>data-*</code> attributes on the injected script element. You can
|
||||||
|
also pass <code>api</code>, <code>storage</code>, and other options via{' '}
|
||||||
|
<code>pulseConfig</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary className="cursor-pointer text-sm text-neutral-500 dark:text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-300">
|
||||||
|
Alternative: inline data attributes (may not work in all GTM setups)
|
||||||
|
</summary>
|
||||||
|
<CodeBlock filename="GTM → Custom HTML Tag (alternative)">{`<script
|
||||||
defer
|
defer
|
||||||
data-domain="your-site.com"
|
data-domain="your-site.com"
|
||||||
src="https://pulse.ciphera.net/script.js"
|
src="https://pulse.ciphera.net/script.js"
|
||||||
></script>`}</CodeBlock>
|
></script>`}</CodeBlock>
|
||||||
|
</details>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
For more details, see the{' '}
|
For more details, see the{' '}
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ export function getBrowserIcon(browserName: string) {
|
|||||||
return <img src={src} alt={browserName} width={18} height={18} className="inline-block" style={{ verticalAlign: '-0.125em' }} />
|
return <img src={src} alt={browserName} width={18} height={18} className="inline-block" style={{ verticalAlign: '-0.125em' }} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const OS_DARK_INVERT = new Set(['macos', 'playstation'])
|
||||||
|
|
||||||
const OS_ICON_MAP: Record<string, string> = {
|
const OS_ICON_MAP: Record<string, string> = {
|
||||||
'windows': 'windows',
|
'windows': 'windows',
|
||||||
'macos': 'macos',
|
'macos': 'macos',
|
||||||
@@ -81,7 +83,8 @@ export function getOSIcon(osName: string) {
|
|||||||
if (!osName) return <Question className="text-neutral-400" />
|
if (!osName) return <Question className="text-neutral-400" />
|
||||||
const file = OS_ICON_MAP[osName.toLowerCase()]
|
const file = OS_ICON_MAP[osName.toLowerCase()]
|
||||||
if (!file) return <Question className="text-neutral-400" />
|
if (!file) return <Question className="text-neutral-400" />
|
||||||
return <img src={`/icons/os/${file}.png`} alt={osName} width={18} height={18} className="inline-block" style={{ verticalAlign: '-0.125em' }} />
|
const cls = OS_DARK_INVERT.has(file) ? 'inline-block dark:invert' : 'inline-block'
|
||||||
|
return <img src={`/icons/os/${file}.png`} alt={osName} width={18} height={18} className={cls} style={{ verticalAlign: '-0.125em' }} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDeviceIcon(deviceName: string) {
|
export function getDeviceIcon(deviceName: string) {
|
||||||
|
|||||||
@@ -19,18 +19,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// * Get domain from script tag
|
// * Get config from script tag, or fall back to window.pulseConfig for GTM / tag managers
|
||||||
const script = document.currentScript || document.querySelector('script[data-domain]');
|
// * GTM Custom HTML tags may not preserve data-* attributes on the injected <script> element,
|
||||||
if (!script || !script.getAttribute('data-domain')) {
|
// * so we also search by src URL and support a global config object.
|
||||||
|
const script = document.currentScript
|
||||||
|
|| document.querySelector('script[data-domain]')
|
||||||
|
|| document.querySelector('script[src*="pulse.ciphera.net/script"]');
|
||||||
|
|
||||||
|
const globalConfig = window.pulseConfig || {};
|
||||||
|
|
||||||
|
// * Helper: read a config value from script data-* attribute or globalConfig
|
||||||
|
function attr(name) {
|
||||||
|
// * Support both "storage-ttl" (data-attr style) and "storageTtl" (camelCase config style)
|
||||||
|
var camel = name.replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });
|
||||||
|
return (script && script.getAttribute('data-' + name)) || globalConfig[name] || globalConfig[camel] || null;
|
||||||
|
}
|
||||||
|
function hasAttr(name) {
|
||||||
|
// * Support both "no-scroll" (data-attr style) and "noScroll" (camelCase config style)
|
||||||
|
var camel = name.replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });
|
||||||
|
return (script && script.hasAttribute('data-' + name)) || globalConfig[name] === true || globalConfig[camel] === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domain = attr('domain');
|
||||||
|
if (!domain) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = script.getAttribute('data-domain');
|
const apiUrl = attr('api') || 'https://pulse-api.ciphera.net';
|
||||||
const apiUrl = script.getAttribute('data-api') || 'https://pulse-api.ciphera.net';
|
|
||||||
// * Visitor ID storage: "local" (default, cross-tab) or "session" (ephemeral per-tab)
|
// * Visitor ID storage: "local" (default, cross-tab) or "session" (ephemeral per-tab)
|
||||||
const storageMode = (script.getAttribute('data-storage') || 'local').toLowerCase() === 'session' ? 'session' : 'local';
|
const storageMode = (attr('storage') || 'local').toLowerCase() === 'session' ? 'session' : 'local';
|
||||||
// * When storage is "local", optional TTL in hours; after TTL the ID is regenerated (e.g. 24 = one day)
|
// * When storage is "local", optional TTL in hours; after TTL the ID is regenerated (e.g. 24 = one day)
|
||||||
const ttlHours = storageMode === 'local' ? parseFloat(script.getAttribute('data-storage-ttl') || '24') : 0;
|
const ttlHours = storageMode === 'local' ? parseFloat(attr('storage-ttl') || '24') : 0;
|
||||||
const ttlMs = ttlHours > 0 ? ttlHours * 60 * 60 * 1000 : 0;
|
const ttlMs = ttlHours > 0 ? ttlHours * 60 * 60 * 1000 : 0;
|
||||||
|
|
||||||
let currentEventId = null;
|
let currentEventId = null;
|
||||||
@@ -343,7 +362,7 @@
|
|||||||
// * Auto-track scroll depth at 25%, 50%, 75%, and 100% (on by default)
|
// * Auto-track scroll depth at 25%, 50%, 75%, and 100% (on by default)
|
||||||
// * Each threshold fires once per pageview; resets on SPA navigation
|
// * Each threshold fires once per pageview; resets on SPA navigation
|
||||||
// * Opt-out: add data-no-scroll to the script tag
|
// * Opt-out: add data-no-scroll to the script tag
|
||||||
var trackScroll = !script.hasAttribute('data-no-scroll');
|
var trackScroll = !hasAttr('no-scroll');
|
||||||
|
|
||||||
if (trackScroll) {
|
if (trackScroll) {
|
||||||
var scrollThresholds = [25, 50, 75, 100];
|
var scrollThresholds = [25, 50, 75, 100];
|
||||||
@@ -378,8 +397,8 @@
|
|||||||
|
|
||||||
// * Auto-track outbound link clicks and file downloads (on by default)
|
// * Auto-track outbound link clicks and file downloads (on by default)
|
||||||
// * Opt-out: add data-no-outbound or data-no-downloads to the script tag
|
// * Opt-out: add data-no-outbound or data-no-downloads to the script tag
|
||||||
var trackOutbound = !script.hasAttribute('data-no-outbound');
|
var trackOutbound = !hasAttr('no-outbound');
|
||||||
var trackDownloads = !script.hasAttribute('data-no-downloads');
|
var trackDownloads = !hasAttr('no-downloads');
|
||||||
|
|
||||||
if (trackOutbound || trackDownloads) {
|
if (trackOutbound || trackDownloads) {
|
||||||
var FILE_EXT_REGEX = /\.(pdf|zip|gz|tar|xlsx|xls|csv|docx|doc|pptx|ppt|mp4|mp3|wav|avi|mov|exe|dmg|pkg|deb|rpm|iso|7z|rar)($|\?|#)/i;
|
var FILE_EXT_REGEX = /\.(pdf|zip|gz|tar|xlsx|xls|csv|docx|doc|pptx|ppt|mp4|mp3|wav|avi|mov|exe|dmg|pkg|deb|rpm|iso|7z|rar)($|\?|#)/i;
|
||||||
|
|||||||
Reference in New Issue
Block a user