Files
dark-mode-by-gracepress/dark-mode-by-gracepress.php
T
2026-08-23 12:11:11 -05:00

333 lines
39 KiB
PHP

<?php
/**
* Plugin Name: Dark Mode by GracePress
* Plugin URI: https://christit.com/gracepress/plugins/dark-mode-by-gracepress/
* Description: A complete dark mode engine with anti-flash loading, OS preference support, visual tuning, transitions, and flexible controls.
* Version: 4.8.18
* Stage: Beta
* Release Channel: WordPress.org
* Public Release: No
* Public Candidate: Yes
* WordPress.org Slug: dark-mode-by-gracepress
* WordPress.org Status: Draft
* Internal Source Slug: dark-mode
* Conversion Status: Lab Testing
* Tested up to: 7.1
* Category: User Interface
* Settings Slug: gracepress-dark-mode
* Author: Jason Eugene Garrison
* Author URI: https://christit.com
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: dark-mode-by-gracepress
* Requires at least: 6.0
* Requires PHP: 7.4
* Privacy: No telemetry
* External Services: None
* Optional Integrations: GracePress Central Hub, OmniLog
* Plugin Check Status: Passed
* Plugin Check Date: 2026-08-23
* Icon: 🌓
*/
if(!defined('ABSPATH'))exit;
if(!defined('GP_DARKMODE_VERSION')) define('GP_DARKMODE_VERSION','4.8.18');
function gp_darkmode_log($message,$event='message',$context=[],$level='INFO'){
$context=is_array($context)?$context:['value'=>$context];
$context['version']=GP_DARKMODE_VERSION;
$context['level']=$level;
try{
if(function_exists('gp_omnilog')) gp_omnilog('dark-mode',$event,array_merge(['message'=>$message],$context));
}catch(\Throwable $e){}
try{
if(function_exists('gp_log')) gp_log($message,'DARKMODE',array_merge(['event'=>$event],$context));
}catch(\Throwable $e){}
}
register_deactivation_hook(__FILE__,function(){
gp_darkmode_log('Dark Mode engine deactivated.','deactivated');
});
register_activation_hook(__FILE__, function() {
if(get_option('gp_dm_brightness') === false) update_option('gp_dm_brightness', 95);
if(get_option('gp_dm_contrast') === false) update_option('gp_dm_contrast', 90);
if(get_option('gp_dm_dim_level') === false) update_option('gp_dm_dim_level', 85);
if(get_option('gracepress_dark_admin_enable') === false) update_option('gracepress_dark_admin_enable', '1');
if(get_option('gp_dm_veil_ms') === false) update_option('gp_dm_veil_ms', 160);
if(get_option('gp_dm_veil_effect') === false) update_option('gp_dm_veil_effect', 'fade');
gp_darkmode_log('Dark Mode engine activated.','activated');
});
add_action('admin_init', function() {
if (get_option('gp_dm_adminbar_toggle_restored') === '1') return;
update_option('gracepress_dark_admin_enable', '1');
update_option('gp_dm_adminbar_toggle_restored', '1');
});
// Dynamic Self-Aware Router
add_action('admin_menu',function(){
$s='gracepress-dark-mode';
$t='Dark Mode';
$is_theme_active = (get_template() === 'gracepress-theme');
$is_theme_required = false;
if (!function_exists('get_plugin_data')) include_once(ABSPATH . 'wp-admin/includes/plugin.php');
$plugin_data = get_plugin_data(__FILE__);
if (isset($plugin_data['Category']) && strtolower(trim($plugin_data['Category'])) === 'theme required') {
$is_theme_required = true;
}
if(class_exists('GracePress_Central_Hub')) {
$parent_slug = ($is_theme_active && $is_theme_required) ? 'gracepress-plugins' : 'gracepress-central';
add_submenu_page($parent_slug, $t, $t, 'manage_options', $s, 'gp_darkmode_admin_page');
} else {
add_options_page($t, $t, 'manage_options', $s, 'gp_darkmode_admin_page');
}
},100);
function gp_darkmode_is_excluded(){
$ex_pages = get_option('gracepress_dark_exclude_pages','');
if(!empty($ex_pages) && (is_page() || is_single())){
$pid = get_the_ID();
$arr = array_map('trim', explode(',', $ex_pages));
if(in_array($pid, $arr)) return true;
}
return false;
}
function gp_darkmode_icons($s=''){
$s=$s?$s:get_option('gracepress_dark_icon_style','classic');
if($s=='minimal')return['l'=>'⚫','d'=>'⚪'];
if($s=='bulbs')return['l'=>'🌑','d'=>'💡'];
if($s=='text')return['l'=>'DARK','d'=>'LIGHT'];
return['l'=>'🌙','d'=>'☀️'];
}
function gp_darkmode_btn($f=false,$s=''){
$sz=get_option('gracepress_dark_button_size','normal');
$s=$s?$s:get_option('gracepress_dark_icon_style','classic');
$i=gp_darkmode_icons($s);$l=$i['l'];$d=$i['d'];$c="gracepress-dark-toggle-trigger";
if($f){$p=get_option('gracepress_dark_button_position','bottom-right');$c.=" gracepress-floating gracepress-pos-$p";}
if($s==='switch'){
$c.=" gracepress-pill-switch gracepress-size-$sz";
return "<div class='$c' data-light='$l' data-dark='$d'><div class='gracepress-pill-knob'>$l</div></div>";
}else{
$c.=" gracepress-dark-toggle-btn gracepress-size-$sz".($s==='text'?' gracepress-text-btn':'');
return "<button class='$c' data-light='$l' data-dark='$d'><span class='gracepress-icon-wrapper'>$l</span></button>";
}
}
add_action('wp_head','gp_darkmode_head',1); add_action('admin_head','gp_darkmode_head',1);
function gp_darkmode_head(){
if(!is_admin()&&gp_darkmode_is_excluded())return;
$ds=get_option('gracepress_dark_default_state','light');$sp=get_option('gracepress_dark_transition_speed','0.3s');$pl=get_option('gracepress_dark_pulse_effect','0');$ex=get_option('gracepress_dark_exclusions','');$cus=get_option('gp_dm_css','');
if(!preg_match('/^(?:\d+(?:\.\d+)?|\.\d+)(?:ms|s)$/',$sp))$sp='0.3s';
$br=get_option('gp_dm_brightness',95);
$ct=get_option('gp_dm_contrast',90);
$dim=get_option('gp_dm_dim_level',85);
$root_br = max(100, (int)$br);
$root_ct = max(100, (int)$ct);
$xstr="";if(!empty($ex)){$xp=explode(',',$ex);$xc=[];foreach($xp as $p){$p=trim($p);if(!empty($p)&&!preg_match('/[{};<>]|\/\*/',$p))$xc[]="html.gracepress-dark-active ".$p;}if(!empty($xc))$xstr=",".implode(',',$xc);}
$veil_ms=max(0,min(800,absint(get_option('gp_dm_veil_ms',160))));
$veil_effect=sanitize_key(get_option('gp_dm_veil_effect','fade'));
if(!in_array($veil_effect,['fade','wipe-right','wipe-down','iris','zoom'],true))$veil_effect='fade';
$site_dt=function_exists('current_datetime')?current_datetime():new DateTimeImmutable('now',function_exists('wp_timezone')?wp_timezone():null);
$site_hour=(int)$site_dt->format('G');
$site_tz=wp_json_encode(function_exists('wp_timezone_string')?wp_timezone_string():'');
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Values are JSON encoded before entering this early inline state script.
echo "<script>!function(){try{var p=new URLSearchParams(window.location.search),stored=localStorage.getItem('gracepressDarkMode'),d=".wp_json_encode($ds).",siteHour=".wp_json_encode($site_hour).",siteTimezone=".$site_tz.",s=null;if(p.get('darkmode')==='1')s='enabled';else if(p.get('lightmode')==='1')s='disabled';else if(d==='auto'){s=(siteHour>=18||siteHour<=6)?'enabled':'disabled';}else if(stored){s=stored;}else if(d==='os')s=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'enabled':'disabled';else s=(d==='dark')?'enabled':'disabled';document.documentElement.setAttribute('data-gp-dark-timezone',siteTimezone||'site');document.documentElement.setAttribute('data-gp-dark-hour',String(siteHour));if(s==='enabled'){document.documentElement.classList.add('gracepress-dark-active');}}catch(e){}}();</script>";
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Numeric, enum and selector values are validated; administrator-authored CSS is an intentional manage_options feature.
echo "<style>:root{--gp-dm-media-opacity:".esc_html((string)($dim/100)).";--gp-dm-veil-ms:".esc_html((string)$veil_ms)."ms;}html{transition:background-color $sp ease;}html.gracepress-dark-active{filter:invert(100%) hue-rotate(180deg) brightness($root_br%) contrast($root_ct%)!important;background-color:#fff!important;}html.gracepress-dark-active body{background-color:#fff!important;}html.gracepress-dark-active img,html.gracepress-dark-active video,html.gracepress-dark-active iframe,html.gracepress-dark-active picture,html.gracepress-dark-active .gracepress-no-invert,html.gracepress-dark-active #adminmenuwrap,html.gracepress-dark-active #adminmenuback,html.gracepress-dark-active #wpadminbar$xstr{filter:invert(100%) hue-rotate(180deg)!important;opacity:var(--gp-dm-media-opacity)!important;}.gracepress-darkmode-veil{position:fixed;inset:0;z-index:2147483647;pointer-events:none;opacity:0;background:#000;clip-path:inset(0);transform:none;transform-origin:center;transition:opacity var(--gp-dm-veil-ms) ease,clip-path var(--gp-dm-veil-ms) cubic-bezier(.65,0,.35,1),transform var(--gp-dm-veil-ms) ease;will-change:opacity,clip-path,transform;}.gracepress-darkmode-veil.is-active{opacity:1;}.gracepress-darkmode-veil[data-effect='wipe-right']{opacity:1;clip-path:inset(0 100% 0 0);}.gracepress-darkmode-veil[data-effect='wipe-right'].is-active{clip-path:inset(0);}.gracepress-darkmode-veil[data-effect='wipe-down']{opacity:1;clip-path:inset(0 0 100% 0);}.gracepress-darkmode-veil[data-effect='wipe-down'].is-active{clip-path:inset(0);}.gracepress-darkmode-veil[data-effect='iris']{opacity:1;clip-path:circle(0 at 50% 50%);}.gracepress-darkmode-veil[data-effect='iris'].is-active{clip-path:circle(150% at 50% 50%);}.gracepress-darkmode-veil[data-effect='zoom']{opacity:0;transform:scale(.92);}.gracepress-darkmode-veil[data-effect='zoom'].is-active{opacity:1;transform:scale(1);}html.gracepress-dark-active .gracepress-darkmode-veil{filter:invert(100%) hue-rotate(180deg)!important;}.gracepress-dark-toggle-btn{background:#222!important;color:#fff!important;border:2px solid #444!important;border-radius:50px!important;cursor:pointer;box-shadow:0 4px 6px rgba(0,0,0,.3)!important;transition:all .3s ease!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;line-height:1!important;text-decoration:none!important;margin:0;z-index:999999;}.gracepress-dark-toggle-btn:hover{background:#444!important;transform:scale(1.05)!important;}.gracepress-pill-switch{background:#cbd5e1;border-radius:50px;position:relative;cursor:pointer;display:inline-block;vertical-align:middle;transition:.3s;box-shadow:inset 0 2px 4px rgba(0,0,0,.1);border:1px solid #94a3b8;box-sizing:content-box;z-index:999999;}.gracepress-pill-knob{background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;display:flex;align-items:center;justify-content:center;transition:.3s;box-shadow:0 2px 5px rgba(0,0,0,.2);}html.gracepress-dark-active .gracepress-pill-switch{background:#0ea5e9;border-color:#0284c7;}.gracepress-text-btn{font-weight:bold;letter-spacing:1px;font-size:12px!important;padding:8px 16px!important;}.gracepress-size-small.gracepress-dark-toggle-btn{padding:6px 10px;font-size:14px;}.gracepress-size-normal.gracepress-dark-toggle-btn{padding:10px 15px;font-size:18px;}.gracepress-size-large.gracepress-dark-toggle-btn{padding:14px 22px;font-size:24px;}.gracepress-size-small.gracepress-pill-switch{width:50px;height:26px;}.gracepress-size-small .gracepress-pill-knob{width:20px;height:20px;font-size:12px;}html.gracepress-dark-active .gracepress-size-small .gracepress-pill-knob{transform:translateX(24px);}.gracepress-size-normal.gracepress-pill-switch{width:64px;height:34px;}.gracepress-size-normal .gracepress-pill-knob{width:28px;height:28px;font-size:16px;}html.gracepress-dark-active .gracepress-size-normal .gracepress-pill-knob{transform:translateX(30px);}.gracepress-size-large.gracepress-pill-switch{width:80px;height:42px;}.gracepress-size-large .gracepress-pill-knob{width:36px;height:36px;font-size:20px;}html.gracepress-dark-active .gracepress-size-large .gracepress-pill-knob{transform:translateX(38px);}.gracepress-floating{position:fixed;z-index:999999;}.gracepress-pos-bottom-right{bottom:25px;right:25px;}.gracepress-pos-bottom-left{bottom:25px;left:25px;}.gracepress-pos-top-right{top:25px;right:25px;}.gracepress-pos-top-left{top:25px;left:25px;}#wpadminbar .gracepress-dark-menu-trigger a{background:transparent;border:none;box-shadow:none;border-radius:0;padding:0 8px;}#wpadminbar .gracepress-dark-menu-trigger a:hover{background:transparent;transform:none;color:#72aee6;}".($pl?"@keyframes gpPulse{0%{box-shadow:0 0 0 0 rgba(100,100,100,.7);}70%{box-shadow:0 0 0 10px rgba(100,100,100,0);}100%{box-shadow:0 0 0 0 rgba(100,100,100,0);}}.gracepress-floating{animation:gpPulse 2.5s infinite;}.gracepress-floating:hover{animation:none;}":"").$cus."</style>";
echo "<style>html.gracepress-dark-active :where(.gp-header-logo-link img,.wp-block-image img,.wp-block-gallery img,.wp-block-media-text img,.wp-block-cover img,.wp-block-cover video,.wp-block-post-featured-image img,.wp-block-embed iframe,.wp-block-video video,.wp-block-audio audio,.gp-singular-hero img,.gp-singular-content img,.gp-video-archive-hero img,.gpb-hero-image,.gpb-post-feature-image,.gpb-post-feature-meta img,.gpb-card-avatar,.gpb-thumb-wrapper img,.gpb-thumb-wrapper video,.gpb-short-card img,.gpb-short-card video,.gpb-sidebar-thumb img,.gpb-series-hero-video,.gp-v-player-container video,.gp-v-next-card img,.g-up-t img,.g-up-t video,.widget img,.widget_media_image img,.widget_media_video video,.wp-caption img,.avatar){filter:invert(100%) hue-rotate(180deg) saturate(1.06) contrast(1.04) brightness(1.015)!important;opacity:1!important;mix-blend-mode:normal!important;}html.gracepress-dark-active .gpb-thumb-wrapper img,html.gracepress-dark-active .gpb-thumb-wrapper video,html.gracepress-dark-active .gpb-short-card img,html.gracepress-dark-active .gpb-short-card video,html.gracepress-dark-active .gpb-post-feature-image,html.gracepress-dark-active .gpb-hero-image,html.gracepress-dark-active .gpb-series-hero-video,html.gracepress-dark-active .gp-v-next-card img,html.gracepress-dark-active .g-up-t img,html.gracepress-dark-active .g-up-t video{filter:invert(100%) hue-rotate(180deg) saturate(1.06) contrast(1.04) brightness(1.015)!important;opacity:1!important;mix-blend-mode:normal!important;}html.gracepress-dark-active .gpb-grid-card.gpb-has-preview.gpb-preview-playing .gpb-thumb-wrapper img,html.gracepress-dark-active .gpb-short-card.gpb-has-preview.gpb-preview-playing img{opacity:0!important;visibility:hidden!important;}html.gracepress-dark-active .gpb-grid-card.gpb-preview-playing .gpb-thumb-wrapper video,html.gracepress-dark-active .gpb-short-card.gpb-preview-playing video{opacity:1!important;visibility:visible!important;}html.gracepress-dark-active .gpc video,html.gracepress-dark-active .plyr video,html.gracepress-dark-active video[controls],html.gracepress-dark-active #gp-ad-video{opacity:1!important;}html.gracepress-dark-active .gpc .plyr__poster,html.gracepress-dark-active .plyr__video-wrapper .plyr__poster{filter:invert(100%) hue-rotate(180deg)!important;}html.gracepress-dark-active .plyr--playing .plyr__poster,html.gracepress-dark-active .plyr--paused:not(.plyr--stopped) .plyr__poster{opacity:0!important;visibility:hidden!important;}html.gracepress-dark-active .gp-ad-play-fallback{filter:invert(100%) hue-rotate(180deg)!important;opacity:1!important;}</style>";
echo "<style>html.gracepress-dark-active :fullscreen video,html.gracepress-dark-active video:fullscreen,html.gracepress-dark-active .gp-live-player:fullscreen video,html.gracepress-dark-active .plyr:fullscreen video,html.gracepress-dark-active :-webkit-full-screen video,html.gracepress-dark-active video:-webkit-full-screen,html.gracepress-dark-active .gp-live-player:-webkit-full-screen video,html.gracepress-dark-active .plyr:-webkit-full-screen video{filter:none!important;opacity:1!important;mix-blend-mode:normal!important;}</style>";
echo "<style>html.gracepress-dark-active .gp-header-logo-link{position:relative!important;z-index:100002!important;isolation:isolate!important;}html.gracepress-dark-active .gp-header-logo-link,html.gracepress-dark-active .gp-header-logo-link *{opacity:1!important;mix-blend-mode:normal!important;}html.gracepress-dark-active .gp-header-logo-link img{filter:invert(100%) hue-rotate(180deg) saturate(1.08) contrast(1.02) brightness(1.02)!important;}</style>";
}
/**
* Builder hero images are CSS backgrounds rather than img elements. Counter the
* root dark-mode inversion on their composed surface so they retain the same
* color and contrast as the original artwork.
*/
function gp_darkmode_builder_hero_correction(){
if(!is_admin()&&gp_darkmode_is_excluded())return;
echo '<style>html.gracepress-dark-active .gpb-hero{filter:invert(100%) hue-rotate(180deg)!important;opacity:1!important;mix-blend-mode:normal!important;}</style>';
}
add_action('wp_head','gp_darkmode_builder_hero_correction',2);
add_action('wp_footer','gp_darkmode_ftr',99); add_action('admin_footer','gp_darkmode_ftr',99);
function gp_darkmode_ftr(){
if(!is_admin()&&gp_darkmode_is_excluded())return;
if(!is_admin()&&get_option('gracepress_dark_frontend_enable','1')==='1')echo wp_kses_post(gp_darkmode_btn(true));
$kbd=get_option('gp_dm_kbd',1)?'true':'false';
$effect_json=wp_json_encode(sanitize_key(get_option('gp_dm_veil_effect','fade')));
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The effect is allowlisted and the keyboard flag is a PHP-generated boolean literal.
echo "<script>document.addEventListener('DOMContentLoaded',function(){const h=document.documentElement;const fx=$effect_json;let v=null,busy=false;function ms(){return Math.max(0,parseInt(getComputedStyle(h).getPropertyValue('--gp-dm-veil-ms'))||160);}function after(t,fn){window.setTimeout(fn,Math.max(60,t)+35);}function veil(n,done){const total=ms();if(total<=0){done();return;}const phase=Math.max(70,Math.round(total/2));if(!v){v=document.createElement('div');v.className='gracepress-darkmode-veil';document.body.appendChild(v);}v.dataset.effect=fx||'fade';v.style.transitionDuration=phase+'ms';v.style.background=n==='enabled'?'#000':'#fff';v.classList.remove('is-active');v.offsetHeight;requestAnimationFrame(function(){v.classList.add('is-active');after(phase,function(){done();requestAnimationFrame(function(){v.classList.remove('is-active');after(phase,function(){busy=false;});});});});}function a(s){if(s==='enabled'){h.classList.add('gracepress-dark-active');}else{h.classList.remove('gracepress-dark-active');}document.querySelectorAll('.gracepress-dark-toggle-trigger, .gracepress-dark-menu-trigger a').forEach(t=>{const l=t.getAttribute('data-light')||'🌙',d=t.getAttribute('data-dark')||'☀️',c=(s==='enabled')?d:l,i=t.querySelector('.gracepress-icon-wrapper');if(i)i.innerHTML=c;const k=t.querySelector('.gracepress-pill-knob');if(k)k.innerHTML=c;});}a(h.classList.contains('gracepress-dark-active')?'enabled':'disabled');function tgl(){if(busy)return;busy=true;const n=h.classList.contains('gracepress-dark-active')?'disabled':'enabled';localStorage.setItem('gracepressDarkMode',n);veil(n,function(){a(n);});}document.body.addEventListener('click',function(e){const t=e.target.closest('.gracepress-dark-toggle-trigger, .gracepress-dark-menu-trigger a');if(t){e.preventDefault();tgl();}});if($kbd){document.addEventListener('keydown',function(e){if(e.ctrlKey&&e.altKey&&(e.key==='d'||e.key==='D')){e.preventDefault();tgl();}});}});</script>";
}
add_shortcode('gracepress_dark_toggle',function($a){$a=shortcode_atts(['style'=>''],is_array($a)?$a:[]);return gp_darkmode_btn(false,$a['style']);});
add_action('admin_bar_menu', function($wp_admin_bar) {
if (!is_user_logged_in() || !is_admin_bar_showing()) return;
$wp_admin_bar->add_node([
'id' => 'gracepress-dark-admin-toggle',
'title' => '<span class="ab-icon dashicons dashicons-lightbulb"></span><span class="ab-label">Dark Mode</span>',
'href' => '#',
'meta' => [
'class' => 'gracepress-dark-menu-trigger',
'title' => 'Toggle GracePress dark mode',
],
]);
}, 95);
add_filter('wp_nav_menu_items',function($i,$a){static $d=false;if(get_option('gracepress_dark_menu_integration','0')==='1'&&!$d){$ic=gp_darkmode_icons();$i.="<li class='menu-item'><a href='#' class='gracepress-dark-menu-trigger' data-light='{$ic['l']}' data-dark='{$ic['d']}'>Theme</a></li>";$d=true;}return $i;},10,2);
// ADMIN UI RESTORATION
function gp_darkmode_admin_page(){
if(!current_user_can('manage_options'))return;
$post_url = esc_url(admin_url('admin.php?page=gracepress-dark-mode'));
if(isset($_POST['gp_dm_reset_filters']) && isset($_POST['_gp_dm_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_gp_dm_nonce'])), 'gp_dm_save_action')){
update_option('gp_dm_brightness', 95);
update_option('gp_dm_contrast', 90);
update_option('gp_dm_dim_level', 85);
gp_darkmode_log('Dark Mode engine reset visual filters to factory defaults.','filters_reset',['brightness'=>95,'contrast'=>90,'media_dim'=>85]);
echo '<div class="notice notice-info is-dismissible" style="border-left-color:#64748b;"><p>Visual filters instantly restored to factory default levels (95/90/85).</p></div>';
} elseif(isset($_POST['gp_dm_save']) && isset($_POST['_gp_dm_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_gp_dm_nonce'])), 'gp_dm_save_action')){
foreach(['gracepress_dark_default_state','gracepress_dark_icon_style','gracepress_dark_button_size','gracepress_dark_button_position','gracepress_dark_transition_speed','gracepress_dark_exclusions','gracepress_dark_exclude_pages'] as $k) if(isset($_POST[$k])) update_option($k,sanitize_text_field(wp_unslash($_POST[$k])));
foreach(['gracepress_dark_frontend_enable','gracepress_dark_pulse_effect','gracepress_dark_menu_integration','gracepress_dark_admin_enable'] as $k) update_option($k,isset($_POST[$k])?'1':'0');
update_option('gp_dm_kbd',isset($_POST['gp_dm_kbd'])?1:0);
update_option('gp_dm_brightness',isset($_POST['gp_dm_brightness'])?absint(wp_unslash($_POST['gp_dm_brightness'])):95);
update_option('gp_dm_contrast',isset($_POST['gp_dm_contrast'])?absint(wp_unslash($_POST['gp_dm_contrast'])):90);
update_option('gp_dm_dim_level',isset($_POST['gp_dm_dim_level'])?absint(wp_unslash($_POST['gp_dm_dim_level'])):85);
update_option('gp_dm_veil_ms',max(0,min(800,absint($_POST['gp_dm_veil_ms'] ?? 160))));
$posted_veil_effect=sanitize_key($_POST['gp_dm_veil_effect'] ?? 'fade');
if(!in_array($posted_veil_effect,['fade','wipe-right','wipe-down','iris','zoom'],true))$posted_veil_effect='fade';
update_option('gp_dm_veil_effect',$posted_veil_effect);
if(isset($_POST['gp_dm_css'])) update_option('gp_dm_css',sanitize_textarea_field(wp_unslash($_POST['gp_dm_css'])));
gp_darkmode_log('Dark Mode configuration updated successfully.','settings_saved',[
'default_state'=>get_option('gracepress_dark_default_state','light'),
'frontend_enabled'=>get_option('gracepress_dark_frontend_enable','1')==='1',
'admin_enabled'=>get_option('gracepress_dark_admin_enable','0')==='1',
'menu_integration'=>get_option('gracepress_dark_menu_integration','0')==='1',
'keyboard_shortcut'=>get_option('gp_dm_kbd',1)==1,
'brightness'=>absint(get_option('gp_dm_brightness',95)),
'contrast'=>absint(get_option('gp_dm_contrast',90)),
'media_dim'=>absint(get_option('gp_dm_dim_level',85)),
'veil_ms'=>absint(get_option('gp_dm_veil_ms',160)),
'veil_effect'=>sanitize_key(get_option('gp_dm_veil_effect','fade')),
]);
echo '<div class="notice notice-success is-dismissible"><p>Dark Mode configurations securely locked in.</p></div>';
}
$ds=get_option('gracepress_dark_default_state','light');$ic=get_option('gracepress_dark_icon_style','classic');$sz=get_option('gracepress_dark_button_size','normal');$ps=get_option('gracepress_dark_button_position','bottom-right');$sp=get_option('gracepress_dark_transition_speed','0.3s');$ex=get_option('gracepress_dark_exclusions','');$exp=get_option('gracepress_dark_exclude_pages','');
$fe=get_option('gracepress_dark_frontend_enable','1');$pl=get_option('gracepress_dark_pulse_effect','0');$mi=get_option('gracepress_dark_menu_integration','0');$ad=get_option('gracepress_dark_admin_enable','0');$kbd=get_option('gp_dm_kbd',1);$cus=get_option('gp_dm_css','');
$br=get_option('gp_dm_brightness',95);
$ct=get_option('gp_dm_contrast',90);
$dim=get_option('gp_dm_dim_level',85);
$veil_ms=max(0,min(800,absint(get_option('gp_dm_veil_ms',160))));
$veil_effect=sanitize_key(get_option('gp_dm_veil_effect','fade'));
if(!in_array($veil_effect,['fade','wipe-right','wipe-down','iris','zoom'],true))$veil_effect='fade';
$admin_sunrise_url=plugin_dir_url(__FILE__).'assets/images/admin-sunrise.png';
?>
<style>
.gp-admin-wrap{max-width:1180px;margin:18px 20px 20px 0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:#334155}.gp-admin-wrap *{box-sizing:border-box}
.gp-header{background:linear-gradient(100deg,rgba(8,18,35,.9) 0%,rgba(15,36,58,.72) 58%,rgba(76,47,24,.5) 100%),url('<?php echo esc_url($admin_sunrise_url)?>') center 58%/cover;color:#fff;padding:26px 30px;border-radius:10px;margin-bottom:18px;border-left:6px solid #0ea5e9;box-shadow:0 7px 18px rgba(15,23,42,.16)}
.gp-header h1{color:#fff;margin:0 0 6px;font-size:27px;font-weight:800;letter-spacing:-.35px;text-shadow:0 2px 4px rgba(0,0,0,.55)}.gp-header p{margin:0;font-size:14px;opacity:.96;line-height:1.55;max-width:860px;text-shadow:0 1px 3px rgba(0,0,0,.55)}
.gp-layout-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:start}.gp-card{background:#fff;padding:20px;border-radius:9px;border:1px solid #dbe3ec;box-shadow:0 2px 8px rgba(15,23,42,.035);margin-bottom:18px}.gp-card h3{margin:0 0 16px;padding:0 0 11px;border-bottom:1px solid #edf1f5;font-size:17px;color:#0f172a;display:flex;align-items:center;gap:8px;font-weight:700}
.gp-f{display:flex;flex-direction:column;gap:5px;margin-bottom:14px}.gp-f label{font-weight:700;font-size:13px;color:#0f172a}.gp-f input[type="text"],.gp-f input[type="number"],.gp-f select{width:min(100%,430px);min-height:34px;padding:4px 8px;border:1px solid #b8c4d1;border-radius:5px;font-size:13px;box-shadow:inset 0 1px 2px rgba(15,23,42,.025)}.gp-f textarea{width:100%;padding:8px;border:1px solid #b8c4d1;border-radius:5px;font-size:13px;box-shadow:inset 0 1px 2px rgba(15,23,42,.025)}.gp-f input[type="range"]{width:100%;margin:2px 0}.gp-f small{line-height:1.4}
.gp-cg{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin-bottom:14px}.gp-cl{display:flex;align-items:center;gap:7px;background:#f8fafc;padding:9px 10px;border-radius:5px;border:1px solid #dbe3ec;cursor:pointer;font-weight:600;font-size:13px}.gp-btn-primary{background:#0ea5e9;color:#fff;border:1px solid #0284c7;padding:9px 18px;font-size:14px;font-weight:700;border-radius:5px;cursor:pointer;display:inline-block;text-decoration:none;transition:.2s}.gp-btn-primary:hover{background:#0284c7;color:#fff;transform:translateY(-1px);box-shadow:0 3px 5px rgba(14,165,233,.2)}.gp-btn-reset{background:#64748b;color:#fff;border:1px solid #526173;padding:9px 16px;font-size:14px;font-weight:700;border-radius:5px;cursor:pointer;display:inline-block;text-decoration:none;transition:.2s;margin-left:8px}.gp-btn-reset:hover{background:#475569;color:#fff;transform:translateY(-1px);box-shadow:0 3px 5px rgba(100,116,139,.2)}.gp-footer{text-align:center;margin-top:24px;padding:24px 20px 8px;border-top:1px solid #e2e8f0}code{background:#f1f5f9;padding:3px 6px;border-radius:4px;font-family:monospace;color:#b4236c;font-size:12px;font-weight:700;border:1px solid #e2e8f0}
.gp-live-preview { width:100%; padding:18px; background:#fff; color:#334155; border:1px dashed #cbd5e1; border-radius:7px; margin-top:12px; transition: 0.3s; }
.gp-live-preview.active { filter: invert(100%) hue-rotate(180deg) brightness(var(--prev-br, 95%)) contrast(var(--prev-ct, 90%)) !important; background:#fff !important; color:#334155 !important; }
@media(max-width:980px){.gp-layout-grid{grid-template-columns:1fr}.gp-admin-wrap{margin-right:12px}.gp-cg{grid-template-columns:1fr 1fr}}
@media(max-width:600px){.gp-header{padding:22px 20px}.gp-card{padding:17px}.gp-cg{grid-template-columns:1fr}.gp-btn-reset{margin:8px 0 0}}
</style>
<div class="gp-admin-wrap"><header class="gp-header"><h1>🌓 Dark Mode Engine</h1><p>Version <?php echo esc_html(GP_DARKMODE_VERSION)?>. Features absolute Anti-FOUC synchronization, hardware-accelerated CSS inversion, precise filter tuning, variable media correction, configurable two-phase transitions, and optional local OmniLog integration.</p></header>
<form method="post" action="<?php echo esc_url($post_url); ?>"><?php wp_nonce_field('gp_dm_save_action', '_gp_dm_nonce'); ?>
<div class="gp-layout-grid">
<div>
<div class="gp-card"><h3><span class="dashicons dashicons-visibility" style="color:#0ea5e9;"></span> 1. Core Behavior</h3>
<div class="gp-f"><label>Default State (First Visit)</label><select name="gracepress_dark_default_state"><option value="light" <?php echo selected('light',$ds)?>>Light Mode (Standard)</option><option value="dark" <?php echo selected('dark',$ds)?>>Dark Mode (Always)</option><option value="os" <?php echo selected('os',$ds)?>>Follow OS Preference (Recommended)</option><option value="auto" <?php echo selected('auto',$ds)?>>Time Based (Sunset to Sunrise)</option></select></div>
<div class="gp-f"><label>Filter Brightness (Current: <span id="br_val"><?php echo esc_html($br)?></span>%)</label><input type="range" id="inp_br" name="gp_dm_brightness" min="0" max="150" value="<?php echo esc_attr($br)?>" oninput="upd_prev()"></div>
<div class="gp-f"><label>Filter Contrast (Current: <span id="ct_val"><?php echo esc_html($ct)?></span>%)</label><input type="range" id="inp_ct" name="gp_dm_contrast" min="0" max="150" value="<?php echo esc_attr($ct)?>" oninput="upd_prev()"></div>
<div class="gp-f"><label>Media Dimming Level (Images/Videos opacity: <?php echo esc_html($dim)?>%)</label><input type="range" name="gp_dm_dim_level" min="0" max="100" value="<?php echo esc_attr($dim)?>"></div>
<div class="gp-cg"><label class="gp-cl"><input type="checkbox" name="gp_dm_kbd" value="1" <?php echo checked($kbd,1)?>> Enable Ctrl+Alt+D Shortcut</label></div>
</div>
<div class="gp-card"><h3><span class="dashicons dashicons-art" style="color:#0ea5e9;"></span> 2. Visual Aesthetics</h3>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;">
<div class="gp-f"><label>Toggle Style</label><select name="gracepress_dark_icon_style"><option value="classic" <?php echo selected('classic',$ic)?>>Classic Emoji (🌙/☀️)</option><option value="minimal" <?php echo selected('minimal',$ic)?>>Minimal Dots (⚫/⚪)</option><option value="bulbs" <?php echo selected('bulbs',$ic)?>>Bulbs (🌑/💡)</option><option value="text" <?php echo selected('text',$ic)?>>Text (DARK/LIGHT)</option><option value="switch" <?php echo selected('switch',$ic)?>>Pill Switch</option></select></div>
<div class="gp-f"><label>Button Size</label><select name="gracepress_dark_button_size"><option value="small" <?php echo selected('small',$sz)?>>Small</option><option value="normal" <?php echo selected('normal',$sz)?>>Normal</option><option value="large" <?php echo selected('large',$sz)?>>Large</option></select></div>
</div>
<div class="gp-f"><label>Floating Position</label><select name="gracepress_dark_button_position"><option value="bottom-right" <?php echo selected('bottom-right',$ps)?>>Bottom Right</option><option value="bottom-left" <?php echo selected('bottom-left',$ps)?>>Bottom Left</option><option value="top-right" <?php echo selected('top-right',$ps)?>>Top Right</option><option value="top-left" <?php echo selected('top-left',$ps)?>>Top Left</option></select></div>
<div class="gp-f"><label>Transition Speed (CSS Time)</label><input type="text" name="gracepress_dark_transition_speed" value="<?php echo esc_attr($sp)?>" placeholder="e.g. 0.3s or 500ms"></div>
<div class="gp-f"><label>Mode Switch Transition</label><select name="gp_dm_veil_effect"><option value="fade" <?php echo selected('fade',$veil_effect)?>>Fade</option><option value="wipe-right" <?php echo selected('wipe-right',$veil_effect)?>>Side Wipe</option><option value="wipe-down" <?php echo selected('wipe-down',$veil_effect)?>>Vertical Wipe</option><option value="iris" <?php echo selected('iris',$veil_effect)?>>Iris</option><option value="zoom" <?php echo selected('zoom',$veil_effect)?>>Zoom Fade</option></select></div>
<div class="gp-f"><label>Mode Switch Duration (ms)</label><input type="number" name="gp_dm_veil_ms" min="0" max="800" step="10" value="<?php echo esc_attr($veil_ms)?>"><small style="color:#64748b;">0 disables the transition veil. The duration is split into a cover phase and a reveal phase, so 220ms feels like a crisp two-way transition.</small></div>
</div>
</div>
<div>
<div class="gp-card"><h3><span class="dashicons dashicons-admin-settings" style="color:#0ea5e9;"></span> 3. Integration & Rendering</h3>
<div class="gp-cg">
<label class="gp-cl"><input type="checkbox" name="gracepress_dark_frontend_enable" value="1" <?php echo checked('1',$fe)?>> Auto-Float on Frontend</label>
<label class="gp-cl"><input type="checkbox" name="gracepress_dark_admin_enable" value="1" <?php echo checked('1',$ad)?>> Enable in WP Admin</label>
<label class="gp-cl"><input type="checkbox" name="gracepress_dark_pulse_effect" value="1" <?php echo checked('1',$pl)?>> Floating Pulse Effect</label>
<label class="gp-cl"><input type="checkbox" name="gracepress_dark_menu_integration" value="1" <?php echo checked('1',$mi)?>> Auto-Inject into Menus</label>
</div>
<div style="background:#f0fdf4;border:1px solid #bbf7d0;padding:15px;border-radius:6px;font-size:13px;color:#166534;line-height:1.5;">
<strong>Manual Shortcode:</strong> Use <code>[gracepress_dark_toggle]</code> to place the button exactly where you want it. You can also override the style locally: <code>[gracepress_dark_toggle style="switch"]</code>
</div>
<div class="gp-live-preview active" id="dm_preview">
<h4 style="margin-top:0; color:#0f172a;">Matrix Filter Preview Hub</h4>
<p style="margin-bottom:0; font-size:13px;">Drag the sliders on the left to real-time sync and balance text opacity variables before saving configurations.</p>
</div>
<div style="text-align:right; margin-top:10px;"><button type="button" class="gp-btn-sm" style="background:#475569; color:#fff; border:none; padding:6px 12px; border-radius:4px; cursor:pointer; font-size:11px;" onclick="localStorage.removeItem('gracepressDarkMode');alert('Cookie memory cleared. Default parameters will trigger on next visit.');">Wipe Browser LocalStorage Cache</button></div>
</div>
<div class="gp-card" style="border-top-color:#f59e0b;"><h3><span class="dashicons dashicons-editor-code" style="color:#0ea5e9;"></span> 4. Advanced Customization</h3>
<div class="gp-f"><label>Exclude Specific Pages (Comma-separated Page IDs)</label><input type="text" name="gracepress_dark_exclude_pages" value="<?php echo esc_attr($exp)?>" placeholder="e.g. 12, 45, 102">
<small style="color:#64748b;">Completely disables the Dark Mode engine on these specific Post/Page IDs.</small></div>
<div class="gp-f"><label>Exclude Specific Elements (Comma-separated CSS Classes/IDs)</label><input type="text" name="gracepress_dark_exclusions" value="<?php echo esc_attr($ex)?>" placeholder=".no-dark, #header-logo, .elementor-image">
<small style="color:#64748b;">Protects specific HTML elements from the inversion filter.</small></div>
<div class="gp-f"><label>Custom Dark CSS Overrides</label><textarea name="gp_dm_css" rows="4" style="font-family:monospace;" placeholder="html.gracepress-dark-active .my-custom-box { background: #333; }"><?php echo esc_textarea($cus)?></textarea></div>
</div>
<div style="text-align:center; padding-top:10px;">
<button type="submit" name="gp_dm_save" class="gp-btn-primary"><span class="dashicons dashicons-lock" style="margin-top:3px;"></span> Lock In Settings</button>
<button type="submit" name="gp_dm_reset_filters" class="gp-btn-reset" onclick="return confirm('Are you sure you want to restore the visual filters to their default factory levels?');"><span class="dashicons dashicons-image-rotate" style="margin-top:3px;"></span> Factory Reset Filters</button>
</div>
</div>
</div>
</form>
<script>
function upd_prev() {
let br = document.getElementById('inp_br').value;
let ct = document.getElementById('inp_ct').value;
document.getElementById('br_val').innerText = br;
document.getElementById('ct_val').innerText = ct;
let box = document.getElementById('dm_preview');
box.style.setProperty('--prev-br', br + '%');
box.style.setProperty('--prev-ct', ct + '%');
}
setTimeout(upd_prev, 150);
</script>
<?php
$gp_footer_support = 'jason [at] christp2p [dot] com';
$gp_footer_donate_url = apply_filters('gracepress_footer_donate_url', 'https://christp2p.com/gracepress/donate/');
$gp_footer_jesus_url = apply_filters('gracepress_footer_about_jesus_url', 'https://christp2p.com/about-jesus-christ/');
$gp_footer_support_url = apply_filters('gracepress_footer_support_url', 'https://christp2p.com/gracepress/support/');
?>
<footer class="gp-footer"><p style="font-style:italic;font-size:16px;color:#1e293b;margin-bottom:15px;">"The light shines in the darkness, and the darkness has not overcome it." — John 1:5</p><div style="display:flex;justify-content:center;gap:14px;flex-wrap:wrap;margin-top:15px;font-weight:bold;color:#0ea5e9;"><a href="<?php echo esc_url($gp_footer_donate_url); ?>" style="color:inherit;text-decoration:none;">Donate</a><a href="<?php echo esc_url($gp_footer_jesus_url); ?>" style="color:inherit;text-decoration:none;">About Jesus Christ</a><a href="<?php echo esc_url($gp_footer_support_url); ?>" style="color:inherit;text-decoration:none;">Support</a></div><div style="margin-top:12px;font-weight:bold;color:#0ea5e9;">Support contact: <?php echo esc_html($gp_footer_support); ?></div><div style="margin-top:30px;font-size:13px;opacity:0.7;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:#0f172a;">All Glory to Jesus Christ<br><span style="font-weight:500;text-transform:none;letter-spacing:0;display:block;margin-top:5px;">Engineered by Christ IT &bull; &copy; <?php echo esc_html(wp_date('Y')); ?></span></div></footer></div>
<?php
}
add_filter('plugin_action_links_'.plugin_basename(__FILE__),function($l){array_unshift($l,'<a href="'.(class_exists('GracePress_Central_Hub')?admin_url('admin.php?page=gracepress-dark-mode'):admin_url('options-general.php?page=gracepress-dark-mode')).'" style="font-weight:bold;color:#0ea5e9;">Configure</a>');return $l;});