Component

Module

Dark Mode

Handles dark mode by enables CSS transitions to avoid flashing, automatically switching based on OS preference and check for time range (if enabled Eg. 6pm-6am) as a fallback every minute.
Note: It’s better if added the ‘API’ module to your CMS, else you need to include the drkmd.js script manually.

Directory

File Folder Link
default.css \\SYNAS\Allan\DOCUMENTATION\Component\Dark Mode\rckuc\public\themes\rckuc\css
All .js \\SYNAS\Allan\DOCUMENTATION\Component\Dark Mode\rckuc\public\themes\rckuc\js
SettingController.php \\SYNAS\Allan\DOCUMENTATION\Component\Dark Mode\rckuc\wolf\app\controllers

 

Step 1

Update: All Layout in your wolf CMS

// Include the drkmd.js script in every layout just before the closing </body> tag: <?php $dark_mode = ModuleSetting::findSetting('page', 'Dark Mode by OS Preference'); ?> <script src="<?php echo THEME_PATH ?>js/drkmd-js.min-1.0.12.js" data-drkmd-attach data-drkmd-opts='<?php echo json_encode([ "localStorage" => false, "cookie" => false, "autoMatchOsTheme" => ($dark_mode == 1) ? true : false ]); ?>'> </script> // Note: Adding 'data-drkmd-attach' inside your script link to show a dark mode toggle button for developer testing purpose, remove it after testing. // Or if you have 'API' module, insert this sql record in database: INSERT INTO `wolf_api` (`id`, `name`, `description`, `all_page_status`, `page_id`, `section`, `code`, `status`, `sequence`, `created_on`, `updated_on`, `created_by_id`, `updated_by_id`) VALUES (NULL, 'Dark Mode', NULL, '1', NULL, 'body_end', '<?php $dark_mode = ModuleSetting::findSetting('page', 'Dark Mode by OS Preference'); ?> <script src="<?php echo THEME_PATH ?>js/drkmd-js.min-1.0.12.js" data-drkmd-attach data-drkmd-opts='<?php echo json_encode([ "localStorage" => false, "cookie" => false, "autoMatchOsTheme" => ($dark_mode == 1) ? true : false ]); ?>'> </script>', '1', NULL, NULL, NULL, NULL, NULL);
// Include the drkmd.js script in every layout just before the closing </body> tag:
<?php $dark_mode = ModuleSetting::findSetting('page', 'Dark Mode by OS Preference'); ?>
<script src="<?php echo THEME_PATH ?>js/drkmd-js.min-1.0.12.js" 
    data-drkmd-attach 
    data-drkmd-opts='<?php echo json_encode([
        "localStorage" => false,
        "cookie" => false,
        "autoMatchOsTheme" => ($dark_mode == 1) ? true : false
    ]); ?>'>
</script>
// Note: Adding 'data-drkmd-attach' inside your script link to show a dark mode toggle button for developer testing purpose, remove it after testing.



// Or if you have 'API' module, insert this sql record in database:
INSERT INTO `wolf_api` (`id`, `name`, `description`, `all_page_status`, `page_id`, `section`, `code`, `status`, `sequence`, `created_on`, `updated_on`, `created_by_id`, `updated_by_id`) VALUES (NULL, 'Dark Mode', NULL, '1', NULL, 'body_end', '<?php $dark_mode = ModuleSetting::findSetting('page', 'Dark Mode by OS Preference'); ?>
<script src="<?php echo THEME_PATH ?>js/drkmd-js.min-1.0.12.js" 
    data-drkmd-attach 
    data-drkmd-opts='<?php echo json_encode([
        "localStorage" => false,
        "cookie" => false,
        "autoMatchOsTheme" => ($dark_mode == 1) ? true : false
    ]); ?>'>
</script>', '1', NULL, NULL, NULL, NULL, NULL);

Step 2

Update: default.css
Adjust the CSS root color settings to match your website design; here are some example adjustments:
Key Feature:

  • Overwrite background-image URL to swicth to another image on dark mode
  • Transform dark image to white using css filter: invert(1) 
/* ---------- drkmd.js ---------- */ /* Background Color: body → #121212 section → #1e1e1e sub-section → #212121 card → #242424 */ /* Change Root Setting here */ .theme-dark { --bg-color-blue: #1a70d1; } .theme-ready, .theme-ready #header-holder, .theme-ready #search_form, .theme-ready #venue-zone, .theme-ready #highlights, .theme-ready #footer, .theme-ready #menu > #menulist > li > .thumb, .theme-ready #donate-zone, .theme-ready .breadcrumb, .theme-ready #president-message, .theme-ready #quotes, .theme-ready #ry-menu, .theme-ready #search_box, .theme-ready #search_box::placeholder, .theme-ready #search_form button img, .theme-ready #whatsapp, .theme-ready img.bg-light, .theme-ready img.bg-dark { /* For animation transition purpose only */ transition: opacity 1s ease, background-color 1s ease, color 1s ease, border-color 1s ease, filter 1s ease, background-image 1s ease, background-size 1s ease; } /* ----- Title ----- */ .theme-dark h1, .theme-dark h2, .theme-dark h3, .theme-dark h4, .theme-dark h5, .theme-dark h6 { color: #1a70d1; } /* ----- Background Color → body ----- */ body.theme-dark { background: #121212; color: #e5e5e5; } .theme-dark #header-holder { border-bottom-color: #121212; } /* ----- Background Color → section ----- */ .theme-dark #header-holder, .theme-dark #highlights, .theme-dark #menu > #menulist > li > .thumb, .theme-dark #menu ul ul a, .theme-dark #footer { background: #121212; } /* ----- Background Color → sub-section ----- */ .theme-dark #search_form, .theme-dark .breadcrumb, .theme-dark #venue-zone, .theme-dark #president-message, .theme-dark #ry-menu { background: #1e1e1e; } /* ----- Background Color → card ----- */ .theme-dark #donate-zone, .theme-dark #quotes { background: #212121; } /* ----- search box ----- */ .theme-dark #search_box { background: #242424; border-color: #242424; } .theme-dark #search_box::placeholder { color: #e5e5e5; } /* ----- Example to filter black image to white ----- */ .theme-dark #search_form button img { filter: invert(1); } /* ----- <img class="bg-dark"> ----- */ .theme-dark img.bg-light { display: none !important; } .theme-light img.bg-dark { display: none !important; } /* ----- css replace background-image ----- */ .theme-dark #whatsapp { background-color: white; background-size: 2.5rem; background-position: center; background-image: url(themes/icon-whatsapp-dark.png); } /* ----- menu ----- */ .theme-dark #menu #menulist > li > a { color: #1a70d1; } .theme-dark #menu #menulist > li > a:hover, .theme-dark #menu #menulist > li ul > li > a:hover { color: #d89b3a !important; } /* ----- hyper link ----- */ .theme-dark #menu a.active, .theme-dark .copyright a, .theme-dark #footer #webdesign a { color: #d89b3a !important; } /* ----- button -----*/ .theme-dark .btn-blue, .theme-dark a.btn-blue { background-color: #1a70d1; color: #d89b3a; } .theme-dark .btn-blue:hover, .theme-dark a.btn-blue:hover { color: white; background-color: #d89b3a; }
/* ---------- drkmd.js ---------- */
/* Background Color:
	body → #121212
	section → #1e1e1e
	sub-section → #212121
	card → #242424
*/

/* Change Root Setting here */
.theme-dark {
    --bg-color-blue: #1a70d1;
}

.theme-ready,
.theme-ready #header-holder,
.theme-ready #search_form,
.theme-ready #venue-zone,
.theme-ready #highlights,
.theme-ready #footer,
.theme-ready #menu > #menulist > li > .thumb,
.theme-ready #donate-zone,
.theme-ready .breadcrumb,
.theme-ready #president-message,
.theme-ready #quotes,
.theme-ready #ry-menu,
.theme-ready #search_box,
.theme-ready #search_box::placeholder,
.theme-ready #search_form button img,
.theme-ready #whatsapp,
.theme-ready img.bg-light,
.theme-ready img.bg-dark {
	/* For animation transition purpose only */
	transition: opacity 1s ease, background-color 1s ease, color 1s ease, border-color 1s ease, filter 1s ease, background-image 1s ease, background-size 1s ease;
}

/* ----- Title ----- */
.theme-dark h1,
.theme-dark h2,
.theme-dark h3,
.theme-dark h4,
.theme-dark h5,
.theme-dark h6 {
	color: #1a70d1;
}

/* ----- Background Color → body ----- */
body.theme-dark {
	background: #121212;
	color: #e5e5e5;
}

.theme-dark #header-holder {
	border-bottom-color: #121212;
}

/* ----- Background Color → section ----- */
.theme-dark #header-holder,
.theme-dark #highlights,
.theme-dark #menu > #menulist > li > .thumb,
.theme-dark #menu ul ul a,
.theme-dark #footer {
	background: #121212;
}

/* ----- Background Color → sub-section ----- */
.theme-dark	#search_form,
.theme-dark .breadcrumb,
.theme-dark #venue-zone,
.theme-dark #president-message,
.theme-dark	#ry-menu {
	background: #1e1e1e;
}

/* ----- Background Color → card ----- */
.theme-dark #donate-zone,
.theme-dark	#quotes {
	background: #212121;
}

/* ----- search box ----- */
.theme-dark	#search_box {
	background: #242424;
	border-color: #242424;
}

.theme-dark	#search_box::placeholder {
	color: #e5e5e5;
}

/* ----- Example to filter black image to white ----- */
.theme-dark #search_form button img {
	filter: invert(1);
}

/* ----- <img class="bg-dark"> ----- */
.theme-dark img.bg-light {
	display: none !important;
}

.theme-light img.bg-dark {
	display: none !important;
}

/* ----- css replace background-image ----- */
.theme-dark #whatsapp {
	background-color: white;
	background-size: 2.5rem;
	background-position: center;
    background-image: url(themes/icon-whatsapp-dark.png);
}

/* ----- menu ----- */
.theme-dark #menu #menulist > li > a {
	color: #1a70d1;
}

.theme-dark #menu #menulist > li > a:hover,
.theme-dark #menu #menulist > li ul > li > a:hover {
	color: #d89b3a !important;
}

/* ----- hyper link ----- */
.theme-dark #menu a.active,
.theme-dark .copyright a,
.theme-dark #footer #webdesign a {
	color: #d89b3a !important;
}

/* ----- button -----*/
.theme-dark .btn-blue,
.theme-dark a.btn-blue {
    background-color: #1a70d1;
    color: #d89b3a;
}

.theme-dark .btn-blue:hover,
.theme-dark a.btn-blue:hover {
	color: white;
	background-color: #d89b3a;
}

Step 3

(Optional) Update: rckuc.js (Only if you want to apply css transition for dark mode and toggle dark mode by time range)

To enable toggleDarkModeByTimeRange() feature, set timeRange such as '6pm-6am' or '7:30pm-8:30am'. Set it to null to disable toggleDarkModeByTimeRange().

 

In this js file:
We handles dark mode by enables CSS transitions to avoid flashing and check for timeRange (if not null) every minute to toggle dark mode.

// drkmd.js $(document).ready(function () { // Add these 3 line of code only if you want CSS transition for dark mode and dont want toggle dark mode by time range // Enable transitions AFTER initial paint requestAnimationFrame(() => { document.body.classList.add('theme-ready'); }); // You can remove the below code or set 'timeRange' to null if you wish to disable toggleDarkModeByTimeRange() // To check if current OS preference is light const mediaQuery = window.matchMedia('(prefers-color-scheme: light)'); // Set a time range such as '6pm-6am' or '7:30pm-8:30am'. let timeRange = '6pm-6am'; // Set to null to disable toggleDarkModeByTimeRange() let toggle_dark_interval = null; // Check on page load if (mediaQuery.matches && timeRange) { toggleDark(); } // Check if user change OS theme mediaQuery.addEventListener('change', (e) => { if (e.matches && timeRange) { toggleDark(); } else { toggle_dark_interval = null; } }); // Helper function function toggleDark() { if (toggle_dark_interval) return; // Run once on page load in case the exact minute is already matched toggleDarkModeByTimeRange(timeRange); // Check every minute toggle_dark_interval = setInterval(() => { toggleDarkModeByTimeRange(timeRange); }, 60 * 1000); function toggleDarkModeByTimeRange(timeRangeStr) { const now = new Date(); const [startStr, endStr] = timeRangeStr.split('-'); function parseTime(tStr) { const [time, meridian] = tStr.split(/(am|pm)/i); let [hours, minutes] = time.split(':').map(Number); if (!minutes) minutes = 0; // if no minutes provided if (meridian.toLowerCase() === 'pm' && hours < 12) hours += 12; if (meridian.toLowerCase() === 'am' && hours === 12) hours = 0; return { hours, minutes }; } const start = parseTime(startStr); const end = parseTime(endStr); const nowMinutes = now.getHours() * 60 + now.getMinutes(); const startMinutes = start.hours * 60 + start.minutes; const endMinutes = end.hours * 60 + end.minutes; let isDark = false; if (startMinutes < endMinutes) { isDark = nowMinutes >= startMinutes && nowMinutes < endMinutes; } else { isDark = nowMinutes >= startMinutes || nowMinutes < endMinutes; } if (isDark) { darkmode.toDark(); } } } })
// drkmd.js
$(document).ready(function () {
	// Add these 3 line of code only if you want CSS transition for dark mode and dont want toggle dark mode by time range
	// Enable transitions AFTER initial paint
	requestAnimationFrame(() => {
		document.body.classList.add('theme-ready');
	});

	// You can remove the below code or set 'timeRange' to null if you wish to disable toggleDarkModeByTimeRange()
	// To check if current OS preference is light
	const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');
	// Set a time range such as '6pm-6am' or '7:30pm-8:30am'.
	let timeRange = '6pm-6am'; // Set to null to disable toggleDarkModeByTimeRange()
	let toggle_dark_interval = null;

	// Check on page load
	if (mediaQuery.matches && timeRange) {
		toggleDark();
	}
	
	// Check if user change OS theme
	mediaQuery.addEventListener('change', (e) => {
		if (e.matches && timeRange) {
			toggleDark();
		} else {
			toggle_dark_interval = null;
		}
	});
	
	// Helper function
	function toggleDark() {
		if (toggle_dark_interval) return;

		// Run once on page load in case the exact minute is already matched
		toggleDarkModeByTimeRange(timeRange);

		// Check every minute
		toggle_dark_interval = setInterval(() => {
			toggleDarkModeByTimeRange(timeRange);
		}, 60 * 1000);
	
		function toggleDarkModeByTimeRange(timeRangeStr) {
			const now = new Date();
			const [startStr, endStr] = timeRangeStr.split('-');
	
			function parseTime(tStr) {
				const [time, meridian] = tStr.split(/(am|pm)/i);
				let [hours, minutes] = time.split(':').map(Number);
				if (!minutes) minutes = 0; // if no minutes provided
				if (meridian.toLowerCase() === 'pm' && hours < 12) hours += 12;
				if (meridian.toLowerCase() === 'am' && hours === 12) hours = 0;
				return { hours, minutes };
			}
	
			const start = parseTime(startStr);
			const end = parseTime(endStr);
	
			const nowMinutes = now.getHours() * 60 + now.getMinutes();
			const startMinutes = start.hours * 60 + start.minutes;
			const endMinutes = end.hours * 60 + end.minutes;
	
			let isDark = false;
	
			if (startMinutes < endMinutes) {
				isDark = nowMinutes >= startMinutes && nowMinutes < endMinutes;
			} else {
				isDark = nowMinutes >= startMinutes || nowMinutes < endMinutes;
			}
	
			if (isDark) {
				darkmode.toDark();
			}
		}
	}	
})

Step 4

Alternate Images for Dark Mode <img>
To use a different image in dark mode, follow the HTML structure below, add a class 'bg-light' to your original <img>, then add a dark mode <img> with class 'bg-dark'.

Note: It’s better if both images have the same ratio, but it’s not required.

<img class="bg-light" src="https://allan.webdesignkuching.com/rckuc/public/images/love-banner.jpg" /> <img class="bg-dark" src="https://allan.webdesignkuching.com/rckuc/public/images/rotary_cover.webp" />
<img class="bg-light" src="https://allan.webdesignkuching.com/rckuc/public/images/love-banner.jpg" />
<img class="bg-dark" src="https://allan.webdesignkuching.com/rckuc/public/images/rotary_cover.webp" />

Step 5

Insert SQL Record into wolf_module_setting:

INSERT INTO `wolf_module_setting` (`id`, `module`, `name`, `value_type`, `value`, `created_on`, `updated_on`, `created_by_id`, `updated_by_id`) VALUES (NULL, 'page', 'Dark Mode by OS Preference', 'checkbox', '1', NOW(), NULL, NULL, NULL);
INSERT INTO `wolf_module_setting` (`id`, `module`, `name`, `value_type`, `value`, `created_on`, `updated_on`, `created_by_id`, `updated_by_id`) VALUES (NULL, 'page', 'Dark Mode by OS Preference', 'checkbox', '1', NOW(), NULL, NULL, NULL);

Step 6

Copy all related files to your project

Code Copied To Clipboard!