Ryan Malloy adbdae19c8 Initial commit: TigerStyle Whiskers v1.0.0
Navigate privacy laws with feline precision — detect every boundary,
respect every territory! GDPR compliance and privacy protection for
WordPress.

- Cookie consent management
- Privacy boundary detection
- GDPR-compliant analytics gating
- Cross-plugin consent coordination (integrates with TigerStyle Heat)
- Visitor preference tracking
- Configurable cookie categories

Includes build.sh and .distignore for WordPress-installable release ZIPs.
2026-05-27 14:31:51 -06:00

503 lines
19 KiB
JavaScript

/**
* TigerStyle Whiskers - Cookie Consent JavaScript
* Feline finesse in consent management
*/
(function($) {
'use strict';
// Main consent management object
window.tigerstyleWhiskersConsent = {
// Configuration
config: {
cookieName: 'tigerstyle_whiskers_consent',
cookieExpiry: 365, // days
bannerSelector: '#tigerstyle-whiskers-consent-overlay',
categoriesSelector: '#tw-consent-categories'
},
// Current consent state
consentState: {
necessary: true,
analytics: false,
marketing: false,
preferences: false,
timestamp: null,
version: null
},
// Initialize consent management
init: function() {
this.loadCurrentConsent();
this.bindEvents();
this.detectBoundaries();
// Update client-side boundary detection
if (typeof tigerstyleWhiskers !== 'undefined') {
this.updateBoundaryDetection();
}
console.log('🐱 TigerStyle Whiskers: Consent management initialized with feline precision!');
},
// Load current consent from cookie
loadCurrentConsent: function() {
const consentCookie = this.getCookie(this.config.cookieName);
if (consentCookie) {
try {
this.consentState = Object.assign(this.consentState, JSON.parse(consentCookie));
} catch (e) {
console.warn('TigerStyle Whiskers: Invalid consent cookie, resetting...');
this.clearConsent();
}
}
},
// Bind event handlers
bindEvents: function() {
const self = this;
// Category toggle handlers
$(document).on('change', '.tw-consent-category input[type="checkbox"]', function() {
const category = $(this).attr('name').replace('consent_', '');
const isChecked = $(this).is(':checked');
// Animate the change
self.animateToggle($(this).closest('.tw-consent-category'), isChecked);
});
// Keyboard navigation
$(document).on('keydown', this.config.bannerSelector, function(e) {
if (e.key === 'Escape') {
self.acceptNecessary();
}
});
// Focus management for accessibility
$(document).on('focus', '.tw-consent-switch input', function() {
$(this).closest('.tw-consent-category').addClass('tw-focused');
}).on('blur', '.tw-consent-switch input', function() {
$(this).closest('.tw-consent-category').removeClass('tw-focused');
});
},
// Detect client-side boundaries
detectBoundaries: function() {
const boundaries = {
technical: {
cookies_enabled: navigator.cookieEnabled,
javascript_enabled: true,
local_storage_available: typeof(Storage) !== "undefined",
session_storage_available: typeof(sessionStorage) !== "undefined",
do_not_track: navigator.doNotTrack === "1" || window.doNotTrack === "1",
screen_width: screen.width,
screen_height: screen.height,
color_depth: screen.colorDepth,
timezone: this.getTimezone(),
connection_type: this.getConnectionType(),
device_memory: navigator.deviceMemory || 'unknown',
hardware_concurrency: navigator.hardwareConcurrency || 'unknown'
},
user_preferences: {
language: navigator.language,
languages: navigator.languages || [],
prefers_reduced_motion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
prefers_dark_mode: window.matchMedia('(prefers-color-scheme: dark)').matches,
prefers_high_contrast: window.matchMedia('(prefers-contrast: high)').matches
},
privacy_signals: {
global_privacy_control: navigator.globalPrivacyControl || false,
do_not_track: navigator.doNotTrack === "1",
ad_blocker_detected: this.detectAdBlocker()
}
};
// Store enhanced boundaries
if (boundaries.technical.local_storage_available) {
localStorage.setItem('tigerstyle_whiskers_client_boundaries', JSON.stringify(boundaries));
}
return boundaries;
},
// Update boundary detection with server
updateBoundaryDetection: function() {
const boundaries = this.detectBoundaries();
// Send to server via AJAX
$.ajax({
url: tigerstyleWhiskersConsent.ajaxurl,
type: 'POST',
data: {
action: 'tigerstyle_whiskers_update_boundaries',
nonce: tigerstyleWhiskersConsent.nonce,
boundaries: JSON.stringify(boundaries)
},
success: function(response) {
console.log('🐱 TigerStyle Whiskers: Boundary detection updated');
}
});
},
// Get timezone
getTimezone: function() {
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
} catch (e) {
return 'unknown';
}
},
// Get connection type
getConnectionType: function() {
if (navigator.connection) {
return {
effective_type: navigator.connection.effectiveType,
downlink: navigator.connection.downlink,
rtt: navigator.connection.rtt,
save_data: navigator.connection.saveData
};
}
return 'unknown';
},
// Detect ad blocker
detectAdBlocker: function() {
// Simple ad blocker detection
const adBlockTest = document.createElement('div');
adBlockTest.innerHTML = ' ';
adBlockTest.className = 'adsbox';
adBlockTest.style.position = 'absolute';
adBlockTest.style.left = '-999px';
document.body.appendChild(adBlockTest);
const isBlocked = adBlockTest.offsetHeight === 0;
document.body.removeChild(adBlockTest);
return isBlocked;
},
// Show consent banner
showBanner: function() {
const $banner = $(this.config.bannerSelector);
// Set focus for accessibility
$banner.attr('role', 'dialog');
$banner.attr('aria-labelledby', 'tw-consent-title');
$banner.attr('aria-describedby', 'tw-consent-message');
$banner.fadeIn(300, function() {
// Focus first interactive element
$banner.find('.tw-btn').first().focus();
});
// Track banner shown event
this.trackEvent('consent_banner_shown');
},
// Hide consent banner
hideBanner: function() {
$(this.config.bannerSelector).fadeOut(300);
this.trackEvent('consent_banner_hidden');
},
// Show category customization
showCategories: function() {
const $categories = $(this.config.categoriesSelector);
if ($categories.is(':visible')) {
$categories.slideUp(300);
$('.tw-btn-customize').text(tigerstyleWhiskersConsent.strings.customize);
} else {
$categories.slideDown(300);
$('.tw-btn-customize').text(tigerstyleWhiskersConsent.strings.save_preferences);
$('.tw-btn-customize').off('click').on('click', () => this.saveCustomPreferences());
// Set current values
this.setCurrentValues();
}
this.trackEvent('consent_categories_toggled');
},
// Set current values in form
setCurrentValues: function() {
const self = this;
Object.keys(this.consentState).forEach(function(category) {
if (typeof self.consentState[category] === 'boolean') {
const $checkbox = $(`#consent_${category}`);
$checkbox.prop('checked', self.consentState[category]);
}
});
},
// Accept all cookies
acceptAll: function() {
this.consentState = {
necessary: true,
analytics: true,
marketing: true,
preferences: true,
timestamp: Date.now(),
version: tigerstyleWhiskersConsent.current_consent.version || '1.0'
};
this.saveConsent();
this.hideBanner();
this.triggerConsentCallbacks();
this.trackEvent('consent_all_accepted');
// Show success message
this.showSuccessMessage(tigerstyleWhiskersConsent.strings.accept_all);
},
// Accept necessary only
acceptNecessary: function() {
this.consentState = {
necessary: true,
analytics: false,
marketing: false,
preferences: false,
timestamp: Date.now(),
version: tigerstyleWhiskersConsent.current_consent.version || '1.0'
};
this.saveConsent();
this.hideBanner();
this.triggerConsentCallbacks();
this.trackEvent('consent_necessary_only');
// Show success message
this.showSuccessMessage(tigerstyleWhiskersConsent.strings.accept_necessary);
},
// Save custom preferences
saveCustomPreferences: function() {
const self = this;
// Get values from form
Object.keys(this.consentState).forEach(function(category) {
if (typeof self.consentState[category] === 'boolean') {
const $checkbox = $(`#consent_${category}`);
if ($checkbox.length) {
self.consentState[category] = $checkbox.is(':checked');
}
}
});
this.consentState.timestamp = Date.now();
this.consentState.version = tigerstyleWhiskersConsent.current_consent.version || '1.0';
this.saveConsent();
this.hideBanner();
this.triggerConsentCallbacks();
this.trackEvent('consent_custom_saved');
// Show success message
this.showSuccessMessage(tigerstyleWhiskersConsent.strings.save_preferences);
},
// Save consent to cookie and server
saveConsent: function() {
// Save to cookie
this.setCookie(this.config.cookieName, JSON.stringify(this.consentState), this.config.cookieExpiry);
// Send to server
$.ajax({
url: tigerstyleWhiskersConsent.ajaxurl,
type: 'POST',
data: {
action: 'tigerstyle_whiskers_update_consent',
nonce: tigerstyleWhiskersConsent.nonce,
analytics: this.consentState.analytics,
marketing: this.consentState.marketing,
preferences: this.consentState.preferences
},
success: function(response) {
console.log('🐱 TigerStyle Whiskers: Consent saved with feline precision!');
},
error: function() {
console.warn('🐱 TigerStyle Whiskers: Failed to save consent to server');
}
});
},
// Trigger consent change callbacks
triggerConsentCallbacks: function() {
// Dispatch custom event
const event = new CustomEvent('tigerstyleWhiskersConsentChanged', {
detail: this.consentState
});
document.dispatchEvent(event);
// Legacy callback support
if (typeof window.onTigerstyleWhiskersConsentChanged === 'function') {
window.onTigerstyleWhiskersConsentChanged(this.consentState);
}
// Initialize analytics if consent given
if (this.consentState.analytics && typeof tigerstyleWhiskersInitAnalytics === 'function') {
tigerstyleWhiskersInitAnalytics();
}
},
// Check if analytics consent is given
hasAnalyticsConsent: function() {
return this.consentState.analytics === true;
},
// Check if marketing consent is given
hasMarketingConsent: function() {
return this.consentState.marketing === true;
},
// Check if preferences consent is given
hasPreferencesConsent: function() {
return this.consentState.preferences === true;
},
// Get consent for specific category
getConsent: function(category) {
return this.consentState[category] || false;
},
// Show success message
showSuccessMessage: function(message) {
const $message = $('<div class="tw-success-message">')
.text(message)
.css({
position: 'fixed',
top: '20px',
right: '20px',
background: 'linear-gradient(135deg, #28a745 0%, #20c997 100%)',
color: 'white',
padding: '12px 20px',
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(40, 167, 69, 0.3)',
zIndex: 999999,
fontSize: '14px',
fontWeight: '600',
opacity: 0
});
$('body').append($message);
$message.animate({ opacity: 1 }, 300)
.delay(3000)
.animate({ opacity: 0 }, 300, function() {
$(this).remove();
});
},
// Animate toggle change
animateToggle: function($category, isEnabled) {
if (isEnabled) {
$category.addClass('tw-enabled');
} else {
$category.removeClass('tw-enabled');
}
// Subtle animation
$category.addClass('tw-changing');
setTimeout(() => $category.removeClass('tw-changing'), 300);
},
// Track events for analytics (only if consent given)
trackEvent: function(eventName, eventData = {}) {
if (this.hasAnalyticsConsent() && typeof gtag === 'function') {
gtag('event', eventName, Object.assign({
event_category: 'consent_management',
event_label: 'tigerstyle_whiskers',
value: 1
}, eventData));
}
// Also track locally for audit
const logEntry = {
event: eventName,
data: eventData,
timestamp: Date.now(),
consent_state: this.consentState
};
if (localStorage) {
const logs = JSON.parse(localStorage.getItem('tigerstyle_whiskers_events') || '[]');
logs.push(logEntry);
// Keep only last 100 events
if (logs.length > 100) {
logs.splice(0, logs.length - 100);
}
localStorage.setItem('tigerstyle_whiskers_events', JSON.stringify(logs));
}
},
// Clear consent (for testing)
clearConsent: function() {
this.deleteCookie(this.config.cookieName);
this.consentState = {
necessary: true,
analytics: false,
marketing: false,
preferences: false,
timestamp: null,
version: null
};
if (localStorage) {
localStorage.removeItem('tigerstyle_whiskers_client_boundaries');
localStorage.removeItem('tigerstyle_whiskers_events');
}
},
// Cookie utilities
setCookie: function(name, value, days) {
const expires = new Date();
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = name + '=' + encodeURIComponent(value) +
';expires=' + expires.toUTCString() +
';path=/' +
';SameSite=Lax' +
(location.protocol === 'https:' ? ';Secure' : '');
},
getCookie: function(name) {
const nameEQ = name + "=";
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) {
return decodeURIComponent(c.substring(nameEQ.length, c.length));
}
}
return null;
},
deleteCookie: function(name) {
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/';
}
};
// Initialize when DOM is ready
$(document).ready(function() {
window.tigerstyleWhiskersConsent.init();
});
// Handle Global Privacy Control
if (navigator.globalPrivacyControl) {
console.log('🐱 TigerStyle Whiskers: Global Privacy Control detected - respecting user preferences');
$(document).ready(function() {
// If GPC is enabled, default to necessary only
if (!window.tigerstyleWhiskersConsent.getCookie('tigerstyle_whiskers_consent')) {
window.tigerstyleWhiskersConsent.acceptNecessary();
}
});
}
})(jQuery);