PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / 2.0.0
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) v2.0.0
2.1.3 2.1.2 2.1.1 2.1.0 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 trunk 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 All 57 releases
darkify / src / assets / js / client_main.js

client_main.js in Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) 2.0.0, at src/assets/js/client_main.js

2,096 lines 71.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 "use strict";
2
3 // Skip ALL Darkify initialization when this page is running inside a frontend
4 // iframe and the parent site has "Frontend Iframe Dark Mode" turned OFF.
5 // This is the only reliable guard for same-origin embedded pages that have
6 // their own copy of Darkify running — they must not self-initialize dark mode.
7 var _dkf_iframe_disabled = (
8 window !== window.top &&
9 typeof darkify_is_this_admin_panel !== "undefined" &&
10 darkify_is_this_admin_panel !== "1" &&
11 typeof darkify_enable_frontend_iframe_dark_mode !== "undefined" &&
12 darkify_enable_frontend_iframe_dark_mode !== "1"
13 );
14
15 let has_process_run_at_least_once = false;
16 let old_transition = "";
17 let has_background_img_url = false;
18 let darken_level = parseInt(darkify_bg_image_darken_to) / 100;
19 darken_level = darken_level.toFixed(1);
20 let darkify_secondary_bg_color = "";
21
22 darkify_init_keyboard_shortcut_listener();
23 darkify_init_os_mode_change_listener();
24
25 const darkify_observer = new MutationObserver(function (mutationsList) {
26 darkify_init_processes();
27
28 darkify_process_iframes(); // �
29 darkify proceed iframe
30 });
31
32 const elements_class_changed = new MutationObserver((mutationsList) => {
33 if (document.readyState !== "loading") {
34 mutationsList.forEach((mutation) => {
35 const target = mutation.target;
36
37 if (target.classList.contains("darkify_processed")) {
38 if (!target.hasAttribute("data-darkify_preserved_classes")) {
39 target.dataset.darkify_preserved_classes =
40 target.classList.toString();
41 } else {
42 if (
43 target.dataset.darkify_preserved_classes ===
44 target.classList.toString()
45 ) {
46 return;
47 }
48 }
49
50 target.dataset.darkify_preserved_classes = target.classList.toString();
51 elements_class_changed.disconnect();
52 target.classList.remove("darkify_processed");
53 darkify_process_element(target);
54
55 document
56 .querySelectorAll(
57 "*:not(head, title, link, meta, script, style, defs, filter)",
58 )
59 .forEach((element) => {
60 elements_class_changed.observe(element, {
61 attributes: true,
62 attributeFilter: ["class"],
63 });
64 });
65 }
66 });
67 }
68 });
69
70 const dark_mode_status_changed = new MutationObserver((mutationsList) => {
71 mutationsList.forEach((mutation) => {
72 if (mutation.type === "attributes" && mutation.attributeName === "class") {
73 document
74 .querySelectorAll(
75 "*:not(head, title, link, meta, script, style, defs, filter)",
76 )
77 .forEach((element) => {
78 if (element.classList.contains("darkify_processed")) {
79 if (
80 darkify_disallowed_elements.length > 0 &&
81 element.matches(darkify_disallowed_elements)
82 ) {
83 return;
84 }
85 if (darkify_enable_bg_image_darken === "1") {
86 darkify_darken_bg_image(element, darken_level);
87 }
88 if (
89 (darkify_enable_low_image_brightness === "1" ||
90 darkify_enable_image_grayscale === "1") &&
91 element.nodeName.toLowerCase() === "img"
92 ) {
93 darkify_img_brightness_and_grayscale(element);
94 }
95 if (
96 darkify_enable_invert_inline_svg === "1" &&
97 element.nodeName.toLowerCase() === "svg"
98 ) {
99 darkify_invert_inline_svg(element);
100 }
101 if (
102 darkify_enable_low_video_brightness === "1" ||
103 darkify_enable_video_grayscale === "1"
104 ) {
105 if (element.nodeName.toLowerCase() === "video") {
106 darkify_video_brightness_and_grayscale(element);
107 }
108 if (
109 element.nodeName.toLowerCase() === "iframe" &&
110 element.getAttribute("src") != null
111 ) {
112 const srcAttribute = element.getAttribute("src");
113 if (
114 srcAttribute.includes("youtube") ||
115 srcAttribute.includes("vimeo") ||
116 srcAttribute.includes("dailymotion")
117 ) {
118 darkify_video_brightness_and_grayscale(element);
119 }
120 }
121 }
122 if (element.hasAttribute("data-darkify_alpha_bg")) {
123 darkify_fix_background_color_alpha(element);
124 }
125 }
126 });
127 }
128 });
129 });
130
131 function darkify_change_state() {
132 if (darkify_is_this_admin_panel === "1") {
133 localStorage.darkify_admin_panel_last_state = document
134 .getElementsByTagName("html")[0]
135 .classList.contains("darkify_dark_mode_enabled")
136 ? "1"
137 : "0";
138 } else {
139 localStorage.darkify_last_state = document
140 .getElementsByTagName("html")[0]
141 .classList.contains("darkify_dark_mode_enabled")
142 ? "1"
143 : "0";
144 }
145 }
146
147 function darkify_switch_trigger() {
148 if (!has_process_run_at_least_once) {
149 darkify_init_processes();
150 darkify_init_observer();
151 }
152
153 const htmlElement = document.getElementsByTagName("html")[0];
154
155 if (htmlElement.classList.contains("darkify_dark_mode_enabled")) {
156 htmlElement.classList.remove("darkify_dark_mode_enabled");
157 } else {
158 htmlElement.classList.add("darkify_dark_mode_enabled");
159 }
160
161 darkify_change_state();
162
163 darkify_process_iframes(); // �
164 darkify proceed iframe
165 }
166
167 function darkify_theme_select(theme) {
168 if (!darkify_is_block_editor_context()) return;
169 if (!has_process_run_at_least_once) {
170 darkify_init_processes();
171 darkify_init_observer();
172 }
173
174 const htmlElement = document.documentElement;
175
176 // �
177 Remove all previous theme classes (IMPORTANT)
178 htmlElement.classList.forEach((cls) => {
179 if (cls.startsWith("darkify-")) {
180 htmlElement.classList.remove(cls);
181 }
182 });
183
184 // �
185 Save in localStorage
186 localStorage.darkify_selected_theme = theme;
187
188 // �
189 Update all select dropdowns
190 darkify_update_theme_selectors(theme);
191
192 if (darkify_is_this_admin_panel === "1") {
193 if (localStorage.darkify_admin_panel_last_state === "1") {
194 htmlElement.classList.add("darkify_dark_mode_enabled");
195 htmlElement.classList.add("darkify-" + theme);
196 } else {
197 htmlElement.classList.remove("darkify_dark_mode_enabled");
198 }
199 }
200
201 darkify_change_state();
202
203 // Apply the new palette to the parent first, then sync iframes so they pick
204 // up the updated variables rather than the previous palette.
205 darkify_apply_palette(theme);
206
207 darkify_process_iframes();
208 }
209
210 document.addEventListener("DOMContentLoaded", function () {
211 if (!darkify_is_block_editor_context()) return;
212
213 let theme = localStorage.getItem("darkify_selected_theme") || "set1";
214
215 darkify_theme_select(theme);
216 });
217
218 function darkify_update_theme_selectors(theme) {
219 document
220 .querySelectorAll(".darkify-theme-selector")
221 .forEach(function (select) {
222 if (select.value !== theme) {
223 select.value = theme;
224 }
225 });
226 }
227
228 function darkify_is_block_editor_context() {
229 return (
230 typeof document !== "undefined" &&
231 document.body &&
232 (document.body.classList.contains("block-editor-page") ||
233 document.querySelector(".edit-post-visual-editor") !== null ||
234 document.querySelector(".block-editor") !== null)
235 );
236 }
237
238 function darkify_restore_selected_theme() {
239 if (!darkify_is_block_editor_context()) return;
240 const storedTheme = localStorage.darkify_selected_theme;
241
242 if (!storedTheme) return;
243
244 darkify_update_theme_selectors(storedTheme);
245 darkify_theme_select(storedTheme);
246 }
247
248 function darkify_apply_palette(theme) {
249 if (!darkify_is_block_editor_context()) return;
250 const palettes = {
251 set1: {
252 bg: "#0F0F0F",
253 secondary_bg: "#171717",
254 text_color: "#BEBEBE",
255 link_color: "#E7E7E7",
256 link_hover_color: "#BEBEBE",
257 input_bg: "#2D2D2D",
258 input_text_color: "#BEBEBE",
259 input_placeholder_color: "#BEBEBE",
260 border_color: "#4A4A4A",
261 btn_text_color: "#BEBEBE",
262 btn_bg: "#4A4A4A",
263 btn_text_hover_color: "#BEBEBE",
264 btn_hover_bg: "#2D2D2D",
265 },
266 set3: {
267 bg: "#211e3c",
268 secondary_bg: "#302C57",
269 text_color: "#B1BBD8",
270 link_color: "#8071fb",
271 link_hover_color: "#B1BBD8",
272 input_bg: "#2A264D",
273 input_text_color: "#B1BBD8",
274 input_placeholder_color: "#B1BBD8",
275 border_color: "#4E478D",
276 btn_text_color: "#B1BBD8",
277 btn_bg: "#4E478D",
278 btn_text_hover_color: "#B1BBD8",
279 btn_hover_bg: "#2A264D",
280 },
281
282 set6: {
283 bg: "#082032",
284 secondary_bg: "#061825",
285 text_color: "#B5D9F3",
286 link_color: "#61bbff",
287 link_hover_color: "#B5D9F3",
288 input_bg: "#0E3755",
289 input_text_color: "#B5D9F3",
290 input_placeholder_color: "#B5D9F3",
291 border_color: "#144E78",
292 btn_text_color: "#B5D9F3",
293 btn_bg: "#144E78",
294 btn_text_hover_color: "#B5D9F3",
295 btn_hover_bg: "#0E3755",
296 },
297
298 set9: {
299 bg: "#04261d",
300 secondary_bg: "#021e16",
301 text_color: "#C1D2BB",
302 link_color: "#00d29a",
303 link_hover_color: "#C1D2BB",
304 input_bg: "#073d2f",
305 input_text_color: "#C1D2BB",
306 input_placeholder_color: "#C1D2BB",
307 border_color: "#095541",
308 btn_text_color: "#C1D2BB",
309 btn_bg: "#095541",
310 btn_text_hover_color: "#C1D2BB",
311 btn_hover_bg: "#073d2f",
312 },
313
314 set10: {
315 bg: "#171004",
316 secondary_bg: "#211706",
317 text_color: "#E0D2BD",
318 link_color: "#e09525",
319 link_hover_color: "#E0D2BD",
320 input_bg: "#372911",
321 input_text_color: "#E0D2BD",
322 input_placeholder_color: "#E0D2BD",
323 border_color: "#5D4010",
324 btn_text_color: "#E0D2BD",
325 btn_bg: "#5D4010",
326 btn_text_hover_color: "#E0D2BD",
327 btn_hover_bg: "#372911",
328 },
329 };
330
331 const palette = palettes[theme] || palettes["set1"];
332
333 document.documentElement.style.setProperty(
334 "--darkify_dark_mode_bg",
335 palette.bg,
336 );
337 document.documentElement.style.setProperty(
338 "--darkify_dark_mode_secondary_bg",
339 palette.secondary_bg,
340 );
341 document.documentElement.style.setProperty(
342 "--darkify_dark_mode_text_color",
343 palette.text_color,
344 );
345 document.documentElement.style.setProperty(
346 "--darkify_dark_mode_link_color",
347 palette.link_color,
348 );
349 document.documentElement.style.setProperty(
350 "--darkify_dark_mode_link_hover_color",
351 palette.link_hover_color,
352 );
353 document.documentElement.style.setProperty(
354 "--darkify_dark_mode_input_bg",
355 palette.input_bg,
356 );
357 document.documentElement.style.setProperty(
358 "--darkify_dark_mode_input_text_color",
359 palette.input_text_color,
360 );
361 document.documentElement.style.setProperty(
362 "--darkify_dark_mode_input_placeholder_color",
363 palette.input_placeholder_color,
364 );
365 document.documentElement.style.setProperty(
366 "--darkify_dark_mode_border_color",
367 palette.border_color,
368 );
369 document.documentElement.style.setProperty(
370 "--darkify_dark_mode_btn_bg",
371 palette.btn_bg,
372 );
373 document.documentElement.style.setProperty(
374 "--darkify_dark_mode_btn_text_color",
375 palette.btn_text_color,
376 );
377 document.documentElement.style.setProperty(
378 "--darkify_dark_mode_btn_hover_bg",
379 palette.btn_hover_bg,
380 );
381 document.documentElement.style.setProperty(
382 "--darkify_dark_mode_btn_text_hover_color",
383 palette.btn_text_hover_color,
384 );
385 }
386
387 // ---------------------------------------------------------------------------
388 // Iframe dark mode
389 //
390 // Same-origin iframes are isolated documents: the parent's :root CSS variables,
391 // stylesheet and dark-mode class do not cascade into them. To keep an iframe in
392 // sync with the parent theme we mirror three things into the iframe document:
393 // 1. the darkify_dark_mode_enabled class on <html>
394 // 2. the theme CSS variables (kept live so colour/palette changes propagate)
395 // 3. the plugin stylesheet, plus a run of the element classifier so inner
396 // content (cards, sections, etc.) is darkened, not just <body>/<a>/inputs
397 // Colour changes only need the variables refreshed; the var-driven class rules
398 // then re-theme everything instantly without re-walking the DOM.
399 // ---------------------------------------------------------------------------
400
401 const DARKIFY_IFRAME_THEME_VARS = [
402 "--darkify_dark_mode_bg", "--darkify_dark_mode_secondary_bg",
403 "--darkify_dark_mode_text_color", "--darkify_dark_mode_link_color",
404 "--darkify_dark_mode_link_hover_color", "--darkify_dark_mode_input_bg",
405 "--darkify_dark_mode_input_text_color", "--darkify_dark_mode_input_placeholder_color",
406 "--darkify_dark_mode_border_color", "--darkify_dark_mode_btn_bg",
407 "--darkify_dark_mode_btn_text_color", "--darkify_dark_mode_btn_hover_bg",
408 "--darkify_dark_mode_btn_text_hover_color",
409 ];
410
411 // Tracks per-iframe-document observers so we don't attach duplicates and can
412 // react to dynamically injected content (e.g. React/SPA pages inside the frame).
413 const darkify_iframe_doc_observers = new WeakMap();
414
415 // Tracks per-iframe-document "keep our style last" observers so we attach only
416 // one per document. Keyed on iframeDoc.
417 const darkify_editor_head_watchers = new WeakMap();
418
419 // Serialise the parent's current theme variables as a :root {} rule. Computed
420 // style is used so the value reflects the active theme regardless of whether it
421 // was set inline (block editor) or via an inline <style> block (frontend).
422 function darkify_serialize_root_vars() {
423 const inline = document.documentElement.style;
424 const computed = getComputedStyle(document.documentElement);
425 let rootVars = ":root {";
426 DARKIFY_IFRAME_THEME_VARS.forEach(function (varName) {
427 const val = (
428 inline.getPropertyValue(varName) || computed.getPropertyValue(varName)
429 ).trim();
430 if (val) rootVars += varName + ": " + val + ";";
431 });
432 return rootVars + "}";
433 }
434
435 // Whether dark mode may apply to iframe content. The "Frontend Iframe Dark Mode"
436 // option only affects the frontend — the Gutenberg editor canvas is unaffected.
437 // Treated as enabled when the flag is absent (backward compatible: existing users
438 // who haven't re-saved settings keep the previous default-on behaviour).
439 function darkify_iframe_dark_enabled() {
440 if (darkify_is_this_admin_panel === "1") return true;
441 return (
442 typeof darkify_enable_frontend_iframe_dark_mode === "undefined" ||
443 darkify_enable_frontend_iframe_dark_mode === "1"
444 );
445 }
446
447 // Build the theme payload broadcast to iframes via postMessage. This is the
448 // only channel that works for CROSS-ORIGIN iframes (e.g. an app served from a
449 // different host/port), where the browser forbids touching contentDocument.
450 // The receiving page applies these variables to its own theme.
451 function darkify_build_theme_payload() {
452 const inline = document.documentElement.style;
453 const computed = getComputedStyle(document.documentElement);
454 const vars = {};
455 DARKIFY_IFRAME_THEME_VARS.forEach(function (varName) {
456 const val = (
457 inline.getPropertyValue(varName) || computed.getPropertyValue(varName)
458 ).trim();
459 if (val) vars[varName] = val;
460 });
461
462 return {
463 source: "darkify",
464 type: "darkify-theme",
465 // Honour the Frontend Iframe Dark Mode option here too, so the handshake
466 // reply can't push dark mode into a cross-origin iframe when it is disabled.
467 enabled:
468 darkify_iframe_dark_enabled() &&
469 document.documentElement.classList.contains("darkify_dark_mode_enabled"),
470 vars: vars,
471 };
472 }
473
474 // Post the current theme to an iframe window. Works regardless of origin and is
475 // silently ignored by frames that don't run the darkify receiver snippet.
476 function darkify_post_theme_to_iframe(iframe, payload) {
477 try {
478 const win = iframe.contentWindow;
479 if (win) win.postMessage(payload || darkify_build_theme_payload(), "*");
480 } catch (e) {
481 // ignore — frame not ready / inaccessible window reference
482 }
483 }
484
485 // Copy / refresh the theme variables inside an iframe document. Cheap and
486 // idempotent — called on every theme change so colours stay in sync.
487 function darkify_sync_iframe_vars(iframeDoc) {
488 const head = iframeDoc.head || iframeDoc.documentElement;
489 let style = iframeDoc.getElementById("darkify-iframe-vars");
490 if (!style) {
491 style = iframeDoc.createElement("style");
492 style.id = "darkify-iframe-vars";
493 head.appendChild(style);
494 }
495 style.textContent = darkify_serialize_root_vars();
496 }
497
498 // Resolve the URL of the plugin's main stylesheet as loaded in the parent, so
499 // the same var-driven .darkify_* rules can be injected into the iframe.
500 function darkify_get_main_css_href() {
501 const link = document.querySelector('link[href*="client_main"]');
502 return link ? link.href : null;
503 }
504
505 // Inject the plugin stylesheet (full class-based engine rules) plus a small
506 // baseline so the frame is themed immediately, before/independent of the
507 // element classifier pass.
508 function darkify_inject_css_into_iframe(iframeDoc) {
509 const head = iframeDoc.head || iframeDoc.documentElement;
510
511 if (!iframeDoc.getElementById("darkify-iframe-main-css")) {
512 const href = darkify_get_main_css_href();
513 if (href) {
514 const link = iframeDoc.createElement("link");
515 link.id = "darkify-iframe-main-css";
516 link.rel = "stylesheet";
517 link.href = href;
518 head.appendChild(link);
519 }
520 }
521
522 if (iframeDoc.getElementById("darkify-iframe-css")) return;
523
524 const style = iframeDoc.createElement("style");
525 style.id = "darkify-iframe-css";
526 style.textContent = `
527 html.darkify_dark_mode_enabled,
528 html.darkify_dark_mode_enabled body {
529 background: var(--darkify_dark_mode_secondary_bg) !important;
530 color: var(--darkify_dark_mode_text_color) !important;
531 }
532
533 html.darkify_dark_mode_enabled a {
534 color: var(--darkify_dark_mode_link_color) !important;
535 }
536 html.darkify_dark_mode_enabled a:hover {
537 color: var(--darkify_dark_mode_link_hover_color) !important;
538 }
539
540 html.darkify_dark_mode_enabled input,
541 html.darkify_dark_mode_enabled select,
542 html.darkify_dark_mode_enabled textarea {
543 background: var(--darkify_dark_mode_input_bg) !important;
544 color: var(--darkify_dark_mode_input_text_color) !important;
545 border-color: var(--darkify_dark_mode_border_color) !important;
546 }
547
548 html.darkify_dark_mode_enabled input::placeholder,
549 html.darkify_dark_mode_enabled textarea::placeholder {
550 color: var(--darkify_dark_mode_input_placeholder_color) !important;
551 }
552
553 /* TinyMCE editor body */
554 html.darkify_dark_mode_enabled body#tinymce,
555 html.darkify_dark_mode_enabled .mce-content-body {
556 background: var(--darkify_dark_mode_secondary_bg) !important;
557 color: var(--darkify_dark_mode_text_color) !important;
558 }
559 `;
560 head.appendChild(style);
561 }
562 function darkify_inject_block_editor_css_into_iframe(iframeDoc) {
563 const head = iframeDoc.head || iframeDoc.documentElement;
564 let style = iframeDoc.getElementById("darkify-block-editor-css");
565 if (!style) {
566 style = iframeDoc.createElement("style");
567 style.id = "darkify-block-editor-css";
568 }
569
570 style.textContent = `
571 /* ── Primary containers (covers all themes) ─────────────────── */
572 html.darkify_dark_mode_enabled,
573 html.darkify_dark_mode_enabled body,
574 html.darkify_dark_mode_enabled body.editor-styles-wrapper,
575 html.darkify_dark_mode_enabled body.block-editor-iframe__body,
576 html.darkify_dark_mode_enabled .editor-styles-wrapper,
577 html.darkify_dark_mode_enabled .is-root-container,
578 html.darkify_dark_mode_enabled .wp-block-post-content,
579 html.darkify_dark_mode_enabled .block-editor-block-list__layout,
580 html.darkify_dark_mode_enabled .block-editor-iframe__body,
581 html.darkify_dark_mode_enabled .wp-site-blocks,
582 html.darkify_dark_mode_enabled .entry-content,
583 html.darkify_dark_mode_enabled .site-content {
584 background: var(--darkify_dark_mode_bg) !important;
585 background-color: var(--darkify_dark_mode_bg) !important;
586 color: var(--darkify_dark_mode_text_color) !important;
587 }
588
589 /* ── Override CSS variables used by themes to drive backgrounds ─
590 Kadence: --global-palette9 (bg), --global-palette1 (text)
591 WordPress Global Styles: --wp--style--color--background */
592 html.darkify_dark_mode_enabled body {
593 --wp--style--color--background: var(--darkify_dark_mode_bg);
594 --wp--preset--color--background: var(--darkify_dark_mode_bg);
595 --wp--preset--color--base: var(--darkify_dark_mode_bg);
596 --wp--preset--color--contrast: var(--darkify_dark_mode_text_color);
597 --global-palette9: var(--darkify_dark_mode_bg);
598 --global-palette8: var(--darkify_dark_mode_secondary_bg);
599 --global-palette7: var(--darkify_dark_mode_secondary_bg);
600 --global-palette1: var(--darkify_dark_mode_text_color);
601 --global-palette2: var(--darkify_dark_mode_text_color);
602 --global-palette3: var(--darkify_dark_mode_link_color);
603 --global-palette6: var(--darkify_dark_mode_border_color);
604 }
605
606 /* ── Text elements ──────────────────────────────────────────── */
607 html.darkify_dark_mode_enabled p,
608 html.darkify_dark_mode_enabled h1,
609 html.darkify_dark_mode_enabled h2,
610 html.darkify_dark_mode_enabled h3,
611 html.darkify_dark_mode_enabled h4,
612 html.darkify_dark_mode_enabled h5,
613 html.darkify_dark_mode_enabled h6,
614 html.darkify_dark_mode_enabled li,
615 html.darkify_dark_mode_enabled td,
616 html.darkify_dark_mode_enabled th,
617 html.darkify_dark_mode_enabled blockquote,
618 html.darkify_dark_mode_enabled pre,
619 html.darkify_dark_mode_enabled span {
620 color: var(--darkify_dark_mode_text_color) !important;
621 }
622
623 /* ── Links ──────────────────────────────────────────────────── */
624 html.darkify_dark_mode_enabled a {
625 color: var(--darkify_dark_mode_link_color) !important;
626 }
627 html.darkify_dark_mode_enabled a:hover {
628 color: var(--darkify_dark_mode_link_hover_color) !important;
629 }
630
631 /* ── Form elements ──────────────────────────────────────────── */
632 html.darkify_dark_mode_enabled input,
633 html.darkify_dark_mode_enabled select,
634 html.darkify_dark_mode_enabled textarea {
635 background: var(--darkify_dark_mode_input_bg) !important;
636 color: var(--darkify_dark_mode_input_text_color) !important;
637 border-color: var(--darkify_dark_mode_border_color) !important;
638 }
639 html.darkify_dark_mode_enabled input::placeholder,
640 html.darkify_dark_mode_enabled textarea::placeholder {
641 color: var(--darkify_dark_mode_input_placeholder_color) !important;
642 }
643
644 /* ── Blocks ─────────────────────────────────────────────────── */
645 html.darkify_dark_mode_enabled .wp-block {
646 color: var(--darkify_dark_mode_text_color) !important;
647 }
648 html.darkify_dark_mode_enabled img {
649 filter: brightness(80%);
650 }
651 `;
652
653 // Always move to end of <head> so our rules load after any theme stylesheet.
654 // head.appendChild is a no-op-safe move: if the element is already in the
655 // tree it is first removed then re-inserted at the end.
656 head.appendChild(style);
657
658 // Keep it last: observe for new <link>/<style> tags Kadence (or any theme)
659 // injects after us and immediately re-append our style to the end.
660 darkify_keep_editor_style_last(iframeDoc, style);
661 }
662
663 // MutationObserver that keeps darkify-block-editor-css as the last stylesheet
664 // in the editor iframe <head>. Called once per iframeDoc (guarded by WeakMap).
665
666 function darkify_keep_editor_style_last(iframeDoc, ourStyle) {
667 if (darkify_editor_head_watchers.has(iframeDoc)) return;
668 const head = iframeDoc.head;
669 if (!head) return;
670
671 const obs = new MutationObserver(function () {
672 // If our style is already last, nothing to do.
673 if (head.lastElementChild === ourStyle) return;
674 // A new sheet was added after ours — move us to the end.
675 head.appendChild(ourStyle);
676 });
677
678 obs.observe(head, { childList: true });
679 darkify_editor_head_watchers.set(iframeDoc, obs);
680 }
681
682 // Run the element classifier across an iframe document and keep watching it for
683 // dynamically added nodes. window.getComputedStyle resolves styles of
684 // same-origin iframe elements, so the existing engine works unchanged.
685 function darkify_run_engine_on_iframe(iframeDoc) {
686 const selector =
687 "* :not(head, title, link, meta, script, style, defs, filter, .darkify_processed)";
688
689 const processAll = function () {
690 iframeDoc.querySelectorAll(selector).forEach(function (element) {
691 try {
692 darkify_process_element(element);
693 } catch (e) {
694 // skip elements that can't be processed
695 }
696 });
697 };
698
699 processAll();
700
701 if (!darkify_iframe_doc_observers.has(iframeDoc)) {
702 const observer = new MutationObserver(processAll);
703 observer.observe(iframeDoc.documentElement, {
704 childList: true,
705 subtree: true,
706 });
707 darkify_iframe_doc_observers.set(iframeDoc, observer);
708 }
709 }
710
711 // Apply CSS invert filter to a cross-origin iframe element as a fallback dark
712 // mode technique — the only browser-allowed approach when the embedded site
713 // does not run the darkify receiver script.
714 function darkify_apply_filter_to_iframe(iframe, enabled) {
715 if (enabled) {
716 iframe.style.filter = "brightness(0.6)";
717 } else {
718 iframe.style.filter = "";
719 }
720 }
721
722 // Detect whether an iframe is cross-origin by attempting to access its document.
723 function darkify_is_cross_origin_iframe(iframe) {
724 try {
725 // Accessing contentDocument throws SecurityError for cross-origin frames.
726 void (iframe.contentDocument || iframe.contentWindow?.document);
727 return false;
728 } catch (e) {
729 return true;
730 }
731 }
732 function darkify_apply_dark_to_iframe(iframe) {
733 const isEditorCanvas = iframe.name === "editor-canvas";
734 if (darkify_is_this_admin_panel === "1" && !isEditorCanvas) return;
735
736 const enabled = document.documentElement.classList.contains(
737 "darkify_dark_mode_enabled",
738 );
739
740 // Same-origin path: directly inject styles + run the engine. Throws a
741 // SecurityError for cross-origin frames, which we swallow — those are handled
742 // via postMessage + CSS filter fallback below.
743 const applyDirect = function () {
744 let iframeDoc;
745 try {
746 iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
747 } catch (e) {
748 // Cross-origin: apply CSS filter to the iframe element as fallback.
749 if (!isEditorCanvas && darkify_is_this_admin_panel !== "1") {
750 darkify_apply_filter_to_iframe(
751 iframe,
752 document.documentElement.classList.contains(
753 "darkify_dark_mode_enabled",
754 ),
755 );
756 }
757 return;
758 }
759 if (!iframeDoc || !iframeDoc.documentElement) return;
760
761 // Same-origin: clear any filter that was applied before we could access the doc.
762 iframe.style.filter = "";
763
764 if (enabled) {
765 iframeDoc.documentElement.classList.add("darkify_dark_mode_enabled");
766 darkify_inject_css_into_iframe(iframeDoc);
767 darkify_sync_iframe_vars(iframeDoc);
768 if (isEditorCanvas) {
769 darkify_inject_block_editor_css_into_iframe(iframeDoc);
770 } else if (darkify_is_this_admin_panel !== "1") {
771 darkify_run_engine_on_iframe(iframeDoc);
772 }
773 } else {
774 iframeDoc.documentElement.classList.remove("darkify_dark_mode_enabled");
775 }
776 };
777
778 // Always broadcast the current theme first. This MUST run independently of the
779 // (throwing) contentDocument access below, otherwise live toggles/palette
780 // changes never reach a cross-origin iframe — they'd only sync on reload.
781 darkify_post_theme_to_iframe(iframe);
782
783 // Re-broadcast + re-apply on every (re)load/navigation so content swapped
784 // inside the frame is re-themed. Bind once per iframe to avoid stacking.
785 if (!iframe.dataset.darkifyIframeBound) {
786 iframe.dataset.darkifyIframeBound = "1";
787 iframe.addEventListener("load", function () {
788 darkify_post_theme_to_iframe(iframe);
789 applyDirect();
790 });
791 }
792
793 applyDirect();
794 }
795 function darkify_process_iframes() {
796 if (darkify_is_this_admin_panel === "1") {
797 // Admin panel: only target the Gutenberg editor-canvas iframe
798 const editorCanvas = document.querySelector('iframe[name="editor-canvas"]');
799 if (editorCanvas) darkify_apply_dark_to_iframe(editorCanvas);
800 return;
801 }
802
803 // Respect the "Frontend Iframe Dark Mode" setting.
804 // Existing users without this option saved get the default-on behaviour.
805 if (!darkify_iframe_dark_enabled()) return;
806
807 document.querySelectorAll("iframe").forEach(darkify_apply_dark_to_iframe);
808 }
809
810 // Watch the parent for theme changes (dark toggle, palette switch, customizer
811 // live edits) and re-sync every iframe. darkify_process_iframes re-runs the
812 // var sync, which is what propagates new colours into the frames.
813 let darkify_parent_theme_observer = null;
814 let darkify_iframe_sync_scheduled = false;
815 function darkify_schedule_iframe_sync() {
816 if (darkify_iframe_sync_scheduled) return;
817 darkify_iframe_sync_scheduled = true;
818 requestAnimationFrame(function () {
819 darkify_iframe_sync_scheduled = false;
820 darkify_process_iframes();
821 });
822 }
823 function darkify_watch_parent_theme() {
824 if (darkify_parent_theme_observer) return;
825
826 darkify_parent_theme_observer = new MutationObserver(darkify_schedule_iframe_sync);
827
828 // <html> class (dark on/off) and inline style (block-editor palette vars).
829 darkify_parent_theme_observer.observe(document.documentElement, {
830 attributes: true,
831 attributeFilter: ["class", "style"],
832 });
833
834 // Frontend palette variables live in an inline <style> block; watch its text
835 // so customizer / dynamic edits to the :root variables are picked up too.
836 const inlineCss = document.querySelector("style.darkify_inline_css");
837 if (inlineCss) {
838 darkify_parent_theme_observer.observe(inlineCss, {
839 childList: true,
840 characterData: true,
841 subtree: true,
842 });
843 }
844
845 // In the admin panel, Gutenberg inserts the editor-canvas iframe into the DOM
846 // asynchronously — after the initial darkify_process_iframes() call has
847 // already run. Watch document.body so we re-sync the moment it appears.
848 if (darkify_is_this_admin_panel === "1" && document.body) {
849 var darkify_canvas_dom_observer = new MutationObserver(function () {
850 if (document.querySelector('iframe[name="editor-canvas"]')) {
851 darkify_schedule_iframe_sync();
852 }
853 });
854 darkify_canvas_dom_observer.observe(document.body, {
855 childList: true,
856 subtree: true,
857 });
858 }
859 }
860
861 if (!_dkf_iframe_disabled) {
862 if (document.readyState !== "loading") {
863 darkify_watch_parent_theme();
864 } else {
865 document.addEventListener("DOMContentLoaded", darkify_watch_parent_theme);
866 }
867 }
868
869 // Handshake: an iframe that loads after — or before — the parent is ready can
870 // ask for the current theme, and we reply to that frame directly. This makes
871 // initial sync reliable regardless of which side finishes loading first.
872 window.addEventListener("message", function (event) {
873 const data = event.data;
874 if (!data || data.source !== "darkify" || data.type !== "darkify-request-theme")
875 return;
876 try {
877 if (event.source) {
878 event.source.postMessage(darkify_build_theme_payload(), "*");
879 }
880 } catch (e) {
881 // ignore unreachable source window
882 }
883 });
884
885 function darkify_init_keyboard_shortcut_listener() {
886 if (darkify_enable_keyboard_shortcut === "1") {
887 // The combo is a normalized string set in the admin (e.g. "ctrl+alt+d"):
888 // modifiers in any order plus one key. Match on the PHYSICAL key (event.code)
889 // so macOS Option-diacritics don't break it.
890 var combo =
891 typeof darkify_keyboard_shortcut_keys === "string" &&
892 darkify_keyboard_shortcut_keys
893 ? darkify_keyboard_shortcut_keys.toLowerCase()
894 : "ctrl+alt+d";
895 var parts = combo.split("+");
896 var need_ctrl = parts.indexOf("ctrl") !== -1;
897 var need_alt = parts.indexOf("alt") !== -1;
898 var need_shift = parts.indexOf("shift") !== -1;
899 var need_meta = parts.indexOf("meta") !== -1;
900 var need_key = parts[parts.length - 1];
901 var expected_code = null;
902 if (/^[a-z]$/.test(need_key)) {
903 expected_code = "key" + need_key;
904 } else if (/^[0-9]$/.test(need_key)) {
905 expected_code = "digit" + need_key;
906 }
907 document.onkeydown = function (event) {
908 var key_matches =
909 (expected_code &&
910 typeof event.code === "string" &&
911 event.code.toLowerCase() === expected_code) ||
912 (typeof event.key === "string" &&
913 event.key.toLowerCase() === need_key);
914 if (
915 event.ctrlKey === need_ctrl &&
916 event.altKey === need_alt &&
917 event.shiftKey === need_shift &&
918 event.metaKey === need_meta &&
919 key_matches
920 ) {
921 event.preventDefault();
922 darkify_switch_trigger();
923 }
924 };
925 }
926 }
927
928 function darkify_init_os_mode_change_listener() {
929 if (darkify_is_this_admin_panel === "0" && darkify_enable_os_aware === "1") {
930 window
931 .matchMedia("(prefers-color-scheme: dark)")
932 .addEventListener("change", (event) => {
933 const mode = event.matches ? "dark" : "light";
934 const htmlElement = document.getElementsByTagName("html")[0];
935 if (mode === "dark") {
936 htmlElement.classList.add("darkify_dark_mode_enabled");
937 } else if (mode === "light") {
938 htmlElement.classList.remove("darkify_dark_mode_enabled");
939 }
940
941 darkify_change_state();
942 });
943 }
944 }
945
946 function darkify_init_alternative_dark_mode_switch() {
947 if (darkify_alternative_dark_mode_switch.length > 0) {
948 const elements = document.querySelectorAll(
949 darkify_alternative_dark_mode_switch,
950 );
951 for (let i = 0; i < elements.length; i++) {
952 const element = elements[i];
953 element.addEventListener("click", () => {
954 darkify_switch_trigger();
955 });
956 }
957 }
958 }
959
960 function darkify_init_attention_effect() {
961 if (darkify_enable_switch_attention !== "1") return;
962 if (!darkify_switch_attention_effect || darkify_switch_attention_effect === "none") return;
963 var switchEl = document.getElementById("darkify_switch_" + darkify_switch_unique_id);
964 if (switchEl) {
965 switchEl.classList.add("darkify_attention_" + darkify_switch_attention_effect);
966 }
967 }
968
969 function get_bg_color_to_preserve(element, fromDataset) {
970 let color = window.getComputedStyle(element, null).backgroundColor;
971 if (!fromDataset) {
972 color = element.dataset.darkify_preserved_bg;
973 }
974 if (
975 (color === "transparent" ||
976 color === "rgba(0, 0, 0, 0)" ||
977 color === "rgba(255,255,255,0)") &&
978 element.parentNode.nodeType === 1
979 ) {
980 color = get_bg_color_to_preserve(element.parentNode, false);
981 } else if (
982 element.parentNode.nodeType === 1 &&
983 element.parentNode.hasAttribute("data-darkify_preserved_bg") &&
984 window.getComputedStyle(element.parentNode, null).backgroundColor === color
985 ) {
986 color = get_bg_color_to_preserve(element.parentNode, false);
987 }
988 return color;
989 }
990
991 function get_txt_color_to_preserve(element, fromDataset) {
992 let color = window.getComputedStyle(element, null).color;
993 if (!fromDataset) {
994 color = element.dataset.darkify_preserved_color;
995 }
996 if (
997 (color === "transparent" ||
998 color === "rgba(0, 0, 0, 0)" ||
999 color === "rgba(255,255,255,0)") &&
1000 element.parentNode.nodeType === 1
1001 ) {
1002 color = get_txt_color_to_preserve(element.parentNode, false);
1003 } else if (
1004 element.parentNode.nodeType === 1 &&
1005 element.parentNode.hasAttribute("data-darkify_preserved_color") &&
1006 window.getComputedStyle(element.parentNode, null).color === color
1007 ) {
1008 color = get_txt_color_to_preserve(element.parentNode, false);
1009 }
1010 return color;
1011 }
1012
1013 function darkify_darken_bg_image(element, level) {
1014 if (
1015 document
1016 .getElementsByTagName("html")[0]
1017 .classList.contains("darkify_dark_mode_enabled")
1018 ) {
1019 const mainStyle = window.getComputedStyle(element, null);
1020 const beforeStyle = window.getComputedStyle(element, ":before");
1021 const afterStyle = window.getComputedStyle(element, ":after");
1022
1023 if (
1024 mainStyle.backgroundImage !== "none" &&
1025 mainStyle.backgroundImage.includes("url") &&
1026 !mainStyle.backgroundImage.includes("rgba(0, 0, 0, " + level + ")")
1027 ) {
1028 element.style.setProperty(
1029 "background-image",
1030 "linear-gradient(rgba(0, 0, 0, " +
1031 level +
1032 "), rgba(0, 0, 0, " +
1033 level +
1034 ")), " +
1035 mainStyle.backgroundImage,
1036 );
1037 }
1038
1039 // Process :before pseudo-element
1040 if (
1041 beforeStyle.backgroundImage !== "none" &&
1042 beforeStyle.backgroundImage.includes("url") &&
1043 !beforeStyle.backgroundImage.includes("rgba(0, 0, 0, " + level + ")")
1044 ) {
1045 // Create a style element for this specific element
1046 const styleId = `darkify-before-${Math.random()
1047 .toString(36)
1048 .substr(2, 9)}`;
1049 element.setAttribute("data-darkify-style-id", styleId);
1050
1051 let styleElement = document.getElementById(styleId);
1052 if (!styleElement) {
1053 styleElement = document.createElement("style");
1054 styleElement.id = styleId;
1055 document.head.appendChild(styleElement);
1056 }
1057
1058 // Store original background for reset
1059 element.dataset.darkifyOriginalBeforeBg = beforeStyle.backgroundImage;
1060
1061 // Add the styles for :before
1062 const cssText = `
1063 .darkify_dark_mode_enabled [data-darkify-style-id="${styleId}"]::before {
1064 background-image: linear-gradient(rgba(0, 0, 0, ${level}), rgba(0, 0, 0, ${level})), ${beforeStyle.backgroundImage} !important;
1065 }
1066 `;
1067 styleElement.textContent = cssText;
1068
1069 // Ensure position relative on parent
1070 if (window.getComputedStyle(element).position === "static") {
1071 element.style.position = "relative";
1072 }
1073 }
1074 // Process :after pseudo-element
1075 if (
1076 afterStyle.backgroundImage !== "none" &&
1077 afterStyle.backgroundImage.includes("url") &&
1078 !afterStyle.backgroundImage.includes("rgba(0, 0, 0, " + level + ")")
1079 ) {
1080 // Create a style element for this specific element
1081 const styleId = `darkify-after-${Math.random()
1082 .toString(36)
1083 .substr(2, 9)}`;
1084 element.setAttribute("data-darkify-style-id", styleId);
1085
1086 let styleElement = document.getElementById(styleId);
1087 if (!styleElement) {
1088 styleElement = document.createElement("style");
1089 styleElement.id = styleId;
1090 document.head.appendChild(styleElement);
1091 }
1092
1093 // Store original background for reset
1094 element.dataset.darkifyOriginalAfterBg = afterStyle.backgroundImage;
1095
1096 // Add the styles for :after
1097 const cssText = `
1098 .darkify_dark_mode_enabled [data-darkify-style-id="${styleId}"]::after {
1099 background-image: linear-gradient(rgba(0, 0, 0, ${level}), rgba(0, 0, 0, ${level})), ${afterStyle.backgroundImage} !important;
1100 }
1101 `;
1102 styleElement.textContent = cssText;
1103
1104 // Ensure position relative on parent
1105 if (window.getComputedStyle(element).position === "static") {
1106 element.style.position = "relative";
1107 }
1108 }
1109 } else if (
1110 window.getComputedStyle(element, null).backgroundImage !== "none" &&
1111 window
1112 .getComputedStyle(element, null)
1113 .backgroundImage.includes("rgba(0, 0, 0, " + level + ")")
1114 ) {
1115 element.style.setProperty(
1116 "background-image",
1117 window
1118 .getComputedStyle(element, null)
1119 .backgroundImage.replace(
1120 "linear-gradient(rgba(0, 0, 0, " +
1121 level +
1122 "), rgba(0, 0, 0, " +
1123 level +
1124 ")), ",
1125 "",
1126 ),
1127 );
1128 }
1129 }
1130
1131 function darkify_img_brightness_and_grayscale(element) {
1132 if (
1133 document
1134 .getElementsByTagName("html")[0]
1135 .classList.contains("darkify_dark_mode_enabled")
1136 ) {
1137 if (
1138 !element.classList.contains("darkify_changed_brightness_and_grayscale")
1139 ) {
1140 element.dataset.darkify_preserved_filter = element.style.filter;
1141 element.classList.add("darkify_changed_brightness_and_grayscale");
1142
1143 if (
1144 darkify_enable_low_image_brightness === "1" &&
1145 darkify_enable_image_grayscale === "1"
1146 ) {
1147 element.style.filter =
1148 "brightness(" +
1149 darkify_image_brightness_to +
1150 "%)" +
1151 " " +
1152 "grayscale(" +
1153 darkify_image_grayscale_to +
1154 "%)";
1155 } else {
1156 if (darkify_enable_low_image_brightness === "1") {
1157 element.style.filter =
1158 "brightness(" + darkify_image_brightness_to + "%)";
1159 } else if (darkify_enable_image_grayscale === "1") {
1160 element.style.filter =
1161 "grayscale(" + darkify_image_grayscale_to + "%)";
1162 }
1163 }
1164 }
1165 } else if (
1166 element.classList.contains("darkify_changed_brightness_and_grayscale")
1167 ) {
1168 element.style.filter = element.dataset.darkify_preserved_filter;
1169 element.classList.remove("darkify_changed_brightness_and_grayscale");
1170 delete element.dataset.darkify_preserved_filter;
1171 }
1172 }
1173
1174 function darkify_invert_inline_svg(element) {
1175 if (document.body.classList.contains("block-editor-page")) return;
1176 if (
1177 document
1178 .getElementsByTagName("html")[0]
1179 .classList.contains("darkify_dark_mode_enabled")
1180 ) {
1181 element.style.filter = "invert(1)";
1182 element.classList.add("darkify_inverted_inline_svg");
1183 } else if (element.classList.contains("darkify_inverted_inline_svg")) {
1184 element.style.filter = element.style.filter.replace("invert(1)", "");
1185 element.classList.remove("darkify_inverted_inline_svg");
1186 }
1187 }
1188
1189 function darkify_video_brightness_and_grayscale(element) {
1190 if (
1191 document
1192 .getElementsByTagName("html")[0]
1193 .classList.contains("darkify_dark_mode_enabled")
1194 ) {
1195 if (
1196 !element.classList.contains(
1197 "darkify_changed_video_brightness_and_grayscale",
1198 )
1199 ) {
1200 element.dataset.darkify_preserved_filter = element.style.filter;
1201 element.classList.add("darkify_changed_video_brightness_and_grayscale");
1202 if (
1203 darkify_enable_low_video_brightness === "1" &&
1204 darkify_enable_video_grayscale === "1"
1205 ) {
1206 element.style.filter =
1207 "brightness(" +
1208 darkify_video_brightness_to +
1209 "%)" +
1210 " " +
1211 "grayscale(" +
1212 darkify_video_grayscale_to +
1213 "%)";
1214 } else {
1215 if (darkify_enable_low_video_brightness === "1") {
1216 element.style.filter =
1217 "brightness(" + darkify_video_brightness_to + "%)";
1218 } else if (darkify_enable_video_grayscale === "1") {
1219 element.style.filter =
1220 "grayscale(" + darkify_video_grayscale_to + "%)";
1221 }
1222 }
1223 }
1224 } else if (
1225 element.classList.contains("darkify_changed_video_brightness_and_grayscale")
1226 ) {
1227 element.style.filter = element.dataset.darkify_preserved_filter;
1228 element.classList.remove("darkify_changed_video_brightness_and_grayscale");
1229 delete element.dataset.darkify_preserved_filter;
1230 }
1231 }
1232
1233 function darkify_replace_video(videoElement, videos) {
1234 if (
1235 document
1236 .getElementsByTagName("html")[0]
1237 .classList.contains("darkify_dark_mode_enabled")
1238 ) {
1239 for (let i = 0; i < videos.length; i++) {
1240 const normalVideo = videos[i].normal_video;
1241 const normalVideoPath = new URL(normalVideo).pathname;
1242 const darkVideo = videos[i].dark_video;
1243 const darkVideoPath = new URL(darkVideo).pathname;
1244
1245 if (
1246 videoElement.getAttribute("src") != null &&
1247 videoElement.getAttribute("src").includes(normalVideoPath)
1248 ) {
1249 videoElement.src = darkVideo;
1250 videoElement.classList.add("darkify_replaced_video");
1251 }
1252
1253 if (videoElement.querySelectorAll("source") != null) {
1254 let sources = videoElement.querySelectorAll("source");
1255 for (let j = 0; j < sources.length; j++) {
1256 if (
1257 sources[j].getAttribute("src") != null &&
1258 sources[j].getAttribute("src").includes(normalVideoPath)
1259 ) {
1260 sources[j].src = darkVideo + "?_=" + Date.now();
1261 videoElement.classList.add("darkify_replaced_video");
1262 videoElement.load();
1263 }
1264 }
1265 }
1266 }
1267 } else {
1268 if (videoElement.classList.contains("darkify_replaced_video")) {
1269 for (let i = 0; i < videos.length; i++) {
1270 const normalVideo = videos[i].normal_video;
1271 const normalVideoPath = new URL(normalVideo).pathname;
1272 const darkVideo = videos[i].dark_video;
1273 const darkVideoPath = new URL(darkVideo).pathname;
1274
1275 if (
1276 videoElement.getAttribute("src") != null &&
1277 videoElement.getAttribute("src").includes(darkVideoPath)
1278 ) {
1279 videoElement.src = normalVideo;
1280 videoElement.classList.remove("darkify_replaced_video");
1281 }
1282
1283 if (videoElement.querySelectorAll("source") != null) {
1284 let sources = videoElement.querySelectorAll("source");
1285 for (let j = 0; j < sources.length; j++) {
1286 if (
1287 sources[j].getAttribute("src") != null &&
1288 sources[j].getAttribute("src").includes(darkVideoPath)
1289 ) {
1290 sources[j].src = normalVideo + "?_=" + Date.now();
1291 videoElement.classList.remove("darkify_replaced_video");
1292 videoElement.load();
1293 }
1294 }
1295 }
1296 }
1297 }
1298 }
1299 }
1300
1301 function darkify_process_pseudo_bg(element) {
1302 const isTransparent = (color) =>
1303 !color ||
1304 color === "transparent" ||
1305 color === "rgba(0, 0, 0, 0)" ||
1306 color === "rgba(255, 255, 255, 0)";
1307
1308 const beforeBg = window.getComputedStyle(element, "::before").backgroundColor;
1309 const afterBg = window.getComputedStyle(element, "::after").backgroundColor;
1310
1311 if (!isTransparent(beforeBg)) {
1312 element.setAttribute("data-darkify-pseudo-before-bg", beforeBg);
1313 }
1314 if (!isTransparent(afterBg)) {
1315 element.setAttribute("data-darkify-pseudo-after-bg", afterBg);
1316 }
1317 }
1318
1319 function darkify_apply_pseudo_bg_styles() {
1320 document
1321 .querySelectorAll(
1322 "[data-darkify-pseudo-before-bg], [data-darkify-pseudo-after-bg]",
1323 )
1324 .forEach((element) => {
1325 const hasBefore = element.hasAttribute("data-darkify-pseudo-before-bg");
1326 const hasAfter = element.hasAttribute("data-darkify-pseudo-after-bg");
1327
1328 let pseudoId = element.getAttribute("data-darkify-pseudo-bg-id");
1329 if (!pseudoId) {
1330 pseudoId = "dkf-" + Math.random().toString(36).substr(2, 9);
1331 element.setAttribute("data-darkify-pseudo-bg-id", pseudoId);
1332 }
1333
1334 let styleEl = document.getElementById("darkify-pseudo-bg-" + pseudoId);
1335 if (!styleEl) {
1336 styleEl = document.createElement("style");
1337 styleEl.id = "darkify-pseudo-bg-" + pseudoId;
1338 document.head.appendChild(styleEl);
1339 }
1340
1341 let css = "";
1342
1343 if (hasBefore) {
1344 const beforeBg = element.getAttribute("data-darkify-pseudo-before-bg");
1345 const isSecondary =
1346 darkify_secondary_bg_color !== "" &&
1347 beforeBg !== darkify_secondary_bg_color;
1348 const bgVar = isSecondary
1349 ? "--darkify_dark_mode_secondary_bg"
1350 : "--darkify_dark_mode_bg";
1351 css += `
1352 .darkify_dark_mode_enabled [data-darkify-pseudo-bg-id="${pseudoId}"]::before {
1353 background: var(${bgVar}) !important;
1354 background-color: var(${bgVar}) !important;
1355 }
1356 `;
1357 }
1358
1359 if (hasAfter) {
1360 const afterBg = element.getAttribute("data-darkify-pseudo-after-bg");
1361 const isSecondary =
1362 darkify_secondary_bg_color !== "" &&
1363 afterBg !== darkify_secondary_bg_color;
1364 const bgVar = isSecondary
1365 ? "--darkify_dark_mode_secondary_bg"
1366 : "--darkify_dark_mode_bg";
1367 css += `
1368 .darkify_dark_mode_enabled [data-darkify-pseudo-bg-id="${pseudoId}"]::after {
1369 background: var(${bgVar}) !important;
1370 background-color: var(${bgVar}) !important;
1371 }
1372 `;
1373 }
1374
1375 styleEl.textContent = css;
1376 });
1377 }
1378
1379 function darkify_fix_background_color_alpha(element) {
1380 if (
1381 document
1382 .getElementsByTagName("html")[0]
1383 .classList.contains("darkify_dark_mode_enabled")
1384 ) {
1385 if (element.hasAttribute("data-darkify_alpha_bg")) {
1386 var alphaValue = element.dataset.darkify_alpha_bg
1387 .replace("rgba(", "")
1388 .replace(")", "")
1389 .split(",")[3]
1390 .trim();
1391 var backgroundColor = window.getComputedStyle(
1392 element,
1393 null,
1394 ).backgroundColor;
1395
1396 if (!backgroundColor.includes("rgba")) {
1397 element.style.setProperty(
1398 "background-color",
1399 backgroundColor
1400 .replace(")", ", " + alphaValue + ")")
1401 .replace("rgb", "rgba"),
1402 "important",
1403 );
1404 }
1405 }
1406 } else if (element.hasAttribute("data-darkify_alpha_bg")) {
1407 element.style.backgroundColor = "";
1408 }
1409 }
1410
1411 function darkify_implement_secondary_bg() {
1412 let maxAreaElement = null;
1413 let maxArea = 0;
1414
1415 const elements = document.querySelectorAll(
1416 "* :not(head, title, link, meta, script, style, defs, filter)",
1417 );
1418
1419 for (let i = 0; i < elements.length; i++) {
1420 const element = elements[i];
1421 if (element.hasAttribute("data-darkify_secondary_bg_finder")) {
1422 const secondaryBgColor = element.dataset.darkify_secondary_bg_finder;
1423 if (
1424 secondaryBgColor !== "transparent" &&
1425 secondaryBgColor !== "rgba(0, 0, 0, 0)"
1426 ) {
1427 const boundingRect = element.getBoundingClientRect();
1428 const area = boundingRect.width * boundingRect.height;
1429 if (area > maxArea) {
1430 maxArea = area;
1431 maxAreaElement = secondaryBgColor;
1432 }
1433 }
1434 }
1435 }
1436
1437 for (let i = 0; i < elements.length; i++) {
1438 const element = elements[i];
1439 if (element.hasAttribute("data-darkify_secondary_bg_finder")) {
1440 if (
1441 element.classList.contains("darkify_style_all") ||
1442 element.classList.contains("darkify_style_bg_txt") ||
1443 element.classList.contains("darkify_style_bg_border") ||
1444 element.classList.contains("darkify_style_bg")
1445 ) {
1446 const isDifferentSecondaryBg =
1447 maxAreaElement !== element.dataset.darkify_secondary_bg_finder;
1448 if (isDifferentSecondaryBg) {
1449 element.classList.add("darkify_style_secondary_bg");
1450 }
1451 }
1452 delete element.dataset.darkify_secondary_bg_finder;
1453 }
1454 }
1455
1456 darkify_secondary_bg_color = maxAreaElement;
1457 }
1458
1459 function darkify_recheck_on_css_loaded_later() {
1460 document
1461 .querySelectorAll(
1462 ".darkify_style_txt_border, .darkify_style_txt, .darkify_style_border",
1463 )
1464 .forEach(function (element) {
1465 const computedStyle = window.getComputedStyle(element, null);
1466 const backgroundColor = computedStyle.backgroundColor;
1467 if (
1468 backgroundColor !== "rgba(0, 0, 0, 0)" &&
1469 backgroundColor !== "rgba(255, 255, 255, 0)"
1470 ) {
1471 darkify_process_element(element);
1472 }
1473 });
1474 }
1475
1476 function darkify_check_preloading() {
1477 let isPreloaded = false;
1478 const lastState = localStorage.darkify_last_state
1479 ? localStorage.darkify_last_state
1480 : "not_set";
1481 const adminPanelLastState = localStorage.darkify_admin_panel_last_state
1482 ? localStorage.darkify_admin_panel_last_state
1483 : "not_set";
1484
1485 if (darkify_is_this_admin_panel === "1") {
1486 if (adminPanelLastState === "1") {
1487 isPreloaded = true;
1488 }
1489 } else {
1490 if (lastState === "1" || lastState === "0") {
1491 if (lastState === "1") {
1492 isPreloaded = true;
1493 }
1494 } else {
1495 if (darkify_enable_default_dark_mode === "1") {
1496 isPreloaded = true;
1497 }
1498 if (darkify_enable_time_based_dark === "1") {
1499 const currentDate = new Date();
1500 const darkStart = new Date();
1501 const darkStop = new Date();
1502 darkStart.setHours(
1503 parseInt(darkify_time_based_dark_start.split(":")[0]),
1504 );
1505 darkStart.setMinutes(
1506 parseInt(darkify_time_based_dark_start.split(":")[1]),
1507 );
1508 darkStop.setHours(parseInt(darkify_time_based_dark_stop.split(":")[0]));
1509 darkStop.setMinutes(
1510 parseInt(darkify_time_based_dark_stop.split(":")[1]),
1511 );
1512
1513 if (
1514 parseInt(darkify_time_based_dark_stop.split(":")[0]) >=
1515 parseInt(darkify_time_based_dark_start.split(":")[0])
1516 ) {
1517 if (
1518 currentDate.getTime() > darkStart.getTime() &&
1519 currentDate.getTime() < darkStop.getTime()
1520 ) {
1521 isPreloaded = true;
1522 }
1523 } else if (currentDate.getHours() > 12) {
1524 if (
1525 currentDate.getTime() > darkStart.getTime() &&
1526 currentDate.getTime() > darkStop.getTime()
1527 ) {
1528 isPreloaded = true;
1529 }
1530 } else if (
1531 currentDate.getTime() < darkStart.getTime() &&
1532 currentDate.getTime() < darkStop.getTime()
1533 ) {
1534 isPreloaded = true;
1535 }
1536 }
1537 }
1538 }
1539
1540 if (
1541 darkify_is_this_admin_panel === "0" &&
1542 darkify_enable_os_aware === "1" &&
1543 window.matchMedia &&
1544 window.matchMedia("(prefers-color-scheme: dark)").matches &&
1545 lastState !== "1" &&
1546 lastState !== "0"
1547 ) {
1548 isPreloaded = true;
1549 }
1550
1551 return isPreloaded;
1552 }
1553
1554 /* ── Self-theming app detection ───────────────────────────────────────────
1555 *
1556 * Darkify darkens a page by reading each element's colours and stamping an
1557 * `!important` override on top. That is right for classic admin markup, which
1558 * takes its colours from the cascade — but a modern admin app (React, Vue, …)
1559 * built on design tokens does not. It ships its own complete dark theme keyed
1560 * on `dark` on <html>, which Darkify now sets, so by the time the engine walks
1561 * the page that app has ALREADY themed itself. Repainting it then does not help;
1562 * it flattens the palette, collapsing cards, popovers and page background into
1563 * one flat grey and fighting a theme that was already correct.
1564 *
1565 * So the engine needs to recognise "this subtree already handled it" — without
1566 * knowing anything about which plugin drew it. The signal used here is the same
1567 * convention that made the app dark in the first place:
1568 *
1569 * 1. The page ships a stylesheet rule that references the `dark` class AND
1570 * declares custom properties — i.e. a `.dark { --background: … }` token
1571 * block. That is the fingerprint of a class-switched token theme
1572 * (Tailwind's class strategy, shadcn/ui, and Darkify's own React admin).
1573 * 2. An element whose resolved colour IS one of those token values is being
1574 * painted by that theme, so it is left alone, along with its subtree.
1575 *
1576 * Both halves are structural, not nominal: no plugin name, no container id, no
1577 * DOM-shape assumption. A page with no such token block yields an empty set and
1578 * every element takes exactly the path it took before, so classic admin screens
1579 * are untouched.
1580 */
1581
1582 var darkify_dark_token_names = null;
1583 var darkify_dark_token_colors = null;
1584 var darkify_dark_token_signature = null;
1585
1586 /**
1587 * Names of the custom properties a `dark`-keyed rule declares, read once from
1588 * the page's own stylesheets.
1589 *
1590 * A rule only counts when it BOTH references the class and declares `--*`
1591 * properties. That pairing is what separates a theme's token block from an
1592 * ordinary dark variant utility (Tailwind compiles `dark:bg-card` to a selector
1593 * that also mentions the class but only sets `background-color`), and it is why
1594 * the class-name test does not need to be clever.
1595 */
1596 function darkify_collect_dark_token_names() {
1597 if (darkify_dark_token_names !== null) {
1598 return darkify_dark_token_names;
1599 }
1600
1601 var names = {};
1602 // `.dark` not followed by a word character or hyphen, so Darkify's own
1603 // `.darkify_*` classes (and any `.dark-theme` of someone else's) don't count.
1604 var dark_class = /\.dark(?![\w-])/;
1605
1606 function scan(rules) {
1607 for (var i = 0; i < rules.length; i++) {
1608 var rule = rules[i];
1609
1610 if (
1611 rule.selectorText &&
1612 rule.style &&
1613 dark_class.test(rule.selectorText)
1614 ) {
1615 for (var j = 0; j < rule.style.length; j++) {
1616 var prop = rule.style[j];
1617 if (prop.charAt(0) === "-" && prop.charAt(1) === "-") {
1618 names[prop] = true;
1619 }
1620 }
1621 }
1622
1623 // @media / @supports nest their own rule lists — and so, since CSS
1624 // Nesting shipped, does an ordinary style rule, which now exposes an
1625 // empty `cssRules` of its own. Recursing on existence rather than on
1626 // length therefore swallowed EVERY top-level rule (each one looked like a
1627 // group with no children), which is why this found nothing at all.
1628 if (rule.cssRules && rule.cssRules.length) {
1629 scan(rule.cssRules);
1630 }
1631 }
1632 }
1633
1634 var sheets = document.styleSheets;
1635 for (var s = 0; s < sheets.length; s++) {
1636 try {
1637 if (sheets[s].cssRules) {
1638 scan(sheets[s].cssRules);
1639 }
1640 } catch (e) {
1641 // Cross-origin stylesheet — unreadable by design, and never one of ours.
1642 }
1643 }
1644
1645 darkify_dark_token_names = Object.keys(names);
1646 return darkify_dark_token_names;
1647 }
1648
1649 /**
1650 * Those tokens resolved to real colours, as a lookup keyed by computed value.
1651 *
1652 * Custom properties compute to their raw token text (`oklch(…)`, `#0c1116`),
1653 * which never string-matches the `rgb(…)` an element reports, so each one is
1654 * resolved through a probe element and compared in that normalised form. The
1655 * probe lives in the document so it inherits the same theme the app sees.
1656 *
1657 * Cached against the root's class list: that is what carries `dark` and the
1658 * palette classes, so the cache drops exactly when the resolved values change.
1659 */
1660 function darkify_dark_theme_colors() {
1661 var names = darkify_collect_dark_token_names();
1662 if (names.length === 0 || !document.body) {
1663 return null;
1664 }
1665
1666 var signature = document.documentElement.className;
1667 if (darkify_dark_token_colors !== null && darkify_dark_token_signature === signature) {
1668 return darkify_dark_token_colors;
1669 }
1670
1671 var probe = document.createElement("span");
1672 // Excluded from the engine and from layout; purely a colour resolver.
1673 probe.className = "darkify_ignore";
1674 probe.style.cssText =
1675 "position:absolute;left:-9999px;top:-9999px;width:0;height:0;visibility:hidden;pointer-events:none;";
1676 document.body.appendChild(probe);
1677
1678 var colors = {};
1679 for (var i = 0; i < names.length; i++) {
1680 probe.style.color = "";
1681 probe.style.color = "var(" + names[i] + ")";
1682 var resolved = window.getComputedStyle(probe).color;
1683 // Tokens that aren't colours (radii, spacing) simply don't resolve to one.
1684 if (resolved && resolved.indexOf("rgb") === 0) {
1685 colors[resolved] = true;
1686 }
1687 }
1688
1689 probe.parentNode.removeChild(probe);
1690
1691 darkify_dark_token_colors = colors;
1692 darkify_dark_token_signature = signature;
1693 return colors;
1694 }
1695
1696 /** Whether self-theming detection should run at all on this page. */
1697 function darkify_self_theming_active() {
1698 return (
1699 typeof darkify_is_this_admin_panel !== "undefined" &&
1700 darkify_is_this_admin_panel === "1" &&
1701 // Only meaningful while the class is on: with it off the tokens resolve to
1702 // the app's LIGHT values, and matching those would skip the very elements
1703 // that still need darkening.
1704 document.documentElement.classList.contains("dark")
1705 );
1706 }
1707
1708 /** Cheap ancestor check — an already-identified subtree is skipped wholesale. */
1709 function darkify_in_self_themed_subtree(element) {
1710 return (
1711 darkify_self_theming_active() &&
1712 !!element.closest &&
1713 !!element.closest(".darkify_self_themed")
1714 );
1715 }
1716
1717 /**
1718 * Mark `element` when its own colours come from the page's dark token theme.
1719 *
1720 * Marking the element rather than testing every node keeps this O(1) per
1721 * subtree: document order means the outermost themed container is reached
1722 * first, and everything below it then short-circuits on the ancestor check —
1723 * including nodes React mounts later, which is what makes SPA route changes and
1724 * late-rendered components work without re-detection.
1725 */
1726 function darkify_mark_if_self_themed(element, computedStyle) {
1727 if (!darkify_self_theming_active()) {
1728 return false;
1729 }
1730
1731 // Never hand the whole document over: <html>/<body> belong to wp-admin, and
1732 // the engine still owns the page backdrop behind any app.
1733 var nodeName = element.nodeName.toLowerCase();
1734 if (nodeName === "html" || nodeName === "body") {
1735 return false;
1736 }
1737
1738 var colors = darkify_dark_theme_colors();
1739 if (!colors) {
1740 return false;
1741 }
1742
1743 if (colors[computedStyle.color] || colors[computedStyle.backgroundColor]) {
1744 element.classList.add("darkify_self_themed");
1745 return true;
1746 }
1747
1748 return false;
1749 }
1750
1751 function darkify_process_element(element) {
1752 // Before any style read: everything under an app that themes itself is left
1753 // exactly as that app painted it.
1754 if (darkify_in_self_themed_subtree(element)) {
1755 return;
1756 }
1757
1758 var computedStyle = window.getComputedStyle(element, null);
1759 var old_transition = "";
1760
1761 // if (computedStyle.transition !== "all 0s ease 0s") {
1762 // old_transition = computedStyle.transition;
1763 // // element.style.setProperty("transition", "none");
1764 // }
1765
1766 if (
1767 element.classList.contains("darkify_style_all") ||
1768 element.classList.contains("darkify_style_bg_txt") ||
1769 element.classList.contains("darkify_style_bg_border") ||
1770 element.classList.contains("darkify_style_txt_border") ||
1771 element.classList.contains("darkify_style_bg") ||
1772 element.classList.contains("darkify_style_txt") ||
1773 element.classList.contains("darkify_style_border") ||
1774 element.classList.contains("darkify_style_secondary_bg")
1775 ) {
1776 element.classList.remove("darkify_style_all");
1777 element.classList.remove("darkify_style_bg_txt");
1778 element.classList.remove("darkify_style_bg_border");
1779 element.classList.remove("darkify_style_txt_border");
1780 element.classList.remove("darkify_style_bg");
1781 element.classList.remove("darkify_style_txt");
1782 element.classList.remove("darkify_style_border");
1783 element.classList.remove("darkify_style_secondary_bg");
1784 }
1785
1786 var nodeName = element.nodeName.toLowerCase();
1787 var backgroundColor = computedStyle.backgroundColor;
1788 var color = computedStyle.color;
1789 var borderColor = computedStyle.borderColor;
1790 var backgroundImage = computedStyle.backgroundImage;
1791
1792 if (
1793 nodeName === "body" &&
1794 (backgroundColor === "rgba(0, 0, 0, 0)" ||
1795 backgroundColor === "rgba(255, 255, 255, 0)")
1796 ) {
1797 element.style.setProperty("background-color", "rgb(255, 255, 255)");
1798 backgroundColor = window.getComputedStyle(element, null).backgroundColor;
1799 }
1800
1801 if (darkify_disallowed_elements.length > 0) {
1802 if (element.matches(darkify_disallowed_elements)) {
1803 // if (old_transition !== "") {
1804 // element.style.setProperty("transition", old_transition);
1805 // }
1806 // element.classList.remove("darkify_processed");
1807 return;
1808 }
1809 }
1810
1811 // The element's colours resolve to the page's own dark tokens, so its theme
1812 // has already dressed it — and its whole subtree with it.
1813 if (darkify_mark_if_self_themed(element, computedStyle)) {
1814 return;
1815 }
1816
1817 var has_background_img_url = false;
1818 if (backgroundImage !== "none" && backgroundImage.includes("url")) {
1819 has_background_img_url = true;
1820 if (darkify_enable_bg_image_darken === "1") {
1821 darkify_darken_bg_image(element, darken_level);
1822 }
1823 }
1824 if (
1825 backgroundColor !== "rgba(0, 0, 0, 0)" &&
1826 backgroundColor !== "rgba(255, 255, 255, 0)" &&
1827 !has_background_img_url
1828 ) {
1829 if (!element.hasAttribute("data-darkify_secondary_bg_finder")) {
1830 element.dataset.darkify_secondary_bg_finder = backgroundColor;
1831 }
1832 if (darkify_secondary_bg_color !== "") {
1833 var isSecondaryBgColorDifferent =
1834 darkify_secondary_bg_color !==
1835 element.dataset.darkify_secondary_bg_finder;
1836 if (isSecondaryBgColorDifferent) {
1837 element.classList.add("darkify_style_secondary_bg");
1838 }
1839 delete element.dataset.darkify_secondary_bg_finder;
1840 }
1841 }
1842 if (
1843 backgroundColor !== "rgba(0, 0, 0, 0)" &&
1844 color !== "rgba(0, 0, 0, 0)" &&
1845 borderColor !== "rgba(0, 0, 0, 0)" &&
1846 backgroundColor !== "rgba(255, 255, 255, 0)" &&
1847 color !== "rgba(255, 255, 255, 0)" &&
1848 borderColor !== "rgba(255, 255, 255, 0)" &&
1849 has_background_img_url === false
1850 ) {
1851 element.classList.add("darkify_style_all");
1852 } else {
1853 if (
1854 backgroundColor !== "rgba(0, 0, 0, 0)" &&
1855 color !== "rgba(0, 0, 0, 0)" &&
1856 backgroundColor !== "rgba(255, 255, 255, 0)" &&
1857 color !== "rgba(255, 255, 255, 0)" &&
1858 has_background_img_url === false
1859 ) {
1860 element.classList.add("darkify_style_bg_txt");
1861 } else {
1862 if (
1863 backgroundColor !== "rgba(0, 0, 0, 0)" &&
1864 borderColor !== "rgba(0, 0, 0, 0)" &&
1865 backgroundColor !== "rgba(255, 255, 255, 0)" &&
1866 borderColor !== "rgba(255, 255, 255, 0)" &&
1867 has_background_img_url === false
1868 ) {
1869 element.classList.add("darkify_style_bg_border");
1870 } else {
1871 if (
1872 color !== "rgba(0, 0, 0, 0)" &&
1873 borderColor !== "rgba(0, 0, 0, 0)" &&
1874 color !== "rgba(255, 255, 255, 0)" &&
1875 borderColor !== "rgba(255, 255, 255, 0)"
1876 ) {
1877 element.classList.add("darkify_style_txt_border");
1878 } else {
1879 if (
1880 backgroundColor !== "rgba(0, 0, 0, 0)" &&
1881 backgroundColor !== "rgba(255, 255, 255, 0)" &&
1882 has_background_img_url === false
1883 ) {
1884 element.classList.add("darkify_style_bg");
1885 } else {
1886 if (
1887 color !== "rgba(0, 0, 0, 0)" &&
1888 color !== "rgba(255, 255, 255, 0)"
1889 ) {
1890 element.classList.add("darkify_style_txt");
1891 } else if (
1892 borderColor !== "rgba(0, 0, 0, 0)" &&
1893 borderColor !== "rgba(255, 255, 255, 0)"
1894 ) {
1895 element.classList.add("darkify_style_border");
1896 }
1897 }
1898 }
1899 }
1900 }
1901 }
1902 if (
1903 backgroundImage !== "none" &&
1904 !has_background_img_url &&
1905 !element.classList.contains("darkify_style_all") &&
1906 !element.classList.contains("darkify_style_bg_txt") &&
1907 !element.classList.contains("darkify_style_bg_border") &&
1908 !element.classList.contains("darkify_style_bg")
1909 ) {
1910 element.classList.add("darkify_style_secondary_bg");
1911 }
1912
1913 if (nodeName === "a") {
1914 element.classList.add("darkify_style_link");
1915 }
1916
1917 if (
1918 nodeName === "input" ||
1919 nodeName === "select" ||
1920 nodeName === "textarea"
1921 ) {
1922 element.classList.add("darkify_style_form_element");
1923 }
1924
1925 const hasTargetClass = darkify_allowed_btn_class.some((cls) =>
1926 element.classList.contains(cls),
1927 );
1928
1929 if (nodeName === "button" || hasTargetClass || element.type === "submit") {
1930 element.classList.add("darkify_style_button");
1931 element.classList.remove("darkify_style_secondary_bg");
1932 element.classList.remove("darkify_style_all");
1933 element.classList.remove("darkify_style_link");
1934 }
1935
1936 if (
1937 (darkify_enable_low_image_brightness === "1" ||
1938 darkify_enable_image_grayscale === "1") &&
1939 nodeName === "img"
1940 ) {
1941 darkify_img_brightness_and_grayscale(element);
1942 }
1943
1944 if (darkify_enable_invert_inline_svg === "1" && nodeName === "svg") {
1945 darkify_invert_inline_svg(element);
1946 }
1947
1948 if (
1949 darkify_enable_low_video_brightness === "1" ||
1950 darkify_enable_video_grayscale === "1"
1951 ) {
1952 if (nodeName === "video") {
1953 darkify_video_brightness_and_grayscale(element);
1954 }
1955
1956 if (nodeName === "iframe") {
1957 const srcAttribute = element.getAttribute("src");
1958 if (srcAttribute !== null) {
1959 if (
1960 srcAttribute.includes("youtube") ||
1961 srcAttribute.includes("vimeo") ||
1962 srcAttribute.includes("dailymotion")
1963 ) {
1964 darkify_video_brightness_and_grayscale(element);
1965 }
1966 }
1967 }
1968 }
1969
1970 if (backgroundColor.includes("rgba")) {
1971 element.dataset.darkify_alpha_bg = backgroundColor;
1972 darkify_fix_background_color_alpha(element);
1973 }
1974
1975 darkify_process_pseudo_bg(element);
1976
1977 // if (old_transition !== "") {
1978 // setTimeout(function () {
1979 // element.style.setProperty("transition", old_transition);
1980 // }, 0);
1981 // }
1982
1983 setTimeout(function () {
1984 elements_class_changed.observe(element, {
1985 attributes: true,
1986 attributeFilter: ["class"],
1987 });
1988 }, 0);
1989
1990 element.classList.add("darkify_processed");
1991 }
1992
1993 function darkify_init_processes() {
1994 has_process_run_at_least_once = true;
1995 document
1996 .querySelectorAll(
1997 "* :not(head, title, link, meta, script, style, defs, filter, .darkify_processed)",
1998 )
1999 .forEach(function (element) {
2000 darkify_process_element(element);
2001 });
2002 }
2003
2004 /**
2005 * Mirror the engine's dark state onto `dark` on <html> — admin panel only.
2006 *
2007 * A React-based admin screen doesn't take its colours from the cascade, so the
2008 * class-stamping the engine does to the rest of wp-admin can't reach it: it
2009 * reads design tokens that only switch when `dark` is on an ancestor
2010 * (Tailwind's class strategy, shadcn/ui, and Darkify's own React admin all key
2011 * on exactly that class). The <head> snippet in header_script.php sets it for
2012 * the first paint; this keeps it in step afterwards, so the admin-bar switch
2013 * re-themes those screens live instead of only after a reload.
2014 *
2015 * Deliberately generic — it mirrors our own state onto a shared convention and
2016 * names no plugin, so any admin app following that convention inherits the
2017 * theme. Screens with no such stylesheet loaded simply have an inert class.
2018 */
2019 function darkify_sync_react_dark_class() {
2020 if (
2021 typeof darkify_is_this_admin_panel === "undefined" ||
2022 darkify_is_this_admin_panel !== "1"
2023 ) {
2024 return;
2025 }
2026
2027 var html = document.documentElement;
2028 var should_be_dark = html.classList.contains("darkify_dark_mode_enabled");
2029
2030 // The no-op guard matters: this runs from an observer watching the same
2031 // element's class list, so toggling unconditionally would re-trigger it in a
2032 // loop. Bailing when nothing changes breaks the cycle.
2033 if (html.classList.contains("dark") === should_be_dark) {
2034 return;
2035 }
2036
2037 html.classList.toggle("dark", should_be_dark);
2038 }
2039
2040 function darkify_init_observer() {
2041 darkify_observer.observe(document, {
2042 attributes: false,
2043 childList: true,
2044 characterData: false,
2045 subtree: true,
2046 });
2047
2048 dark_mode_status_changed.observe(document.getElementsByTagName("html")[0], {
2049 attributes: true,
2050 });
2051
2052 // Keep `dark` in step with every route that flips `darkify_dark_mode_enabled`
2053 // (the admin-bar switch, the keyboard shortcut, OS/time-based changes, the
2054 // theme picker) without having to patch each one.
2055 darkify_sync_react_dark_class();
2056 new MutationObserver(darkify_sync_react_dark_class).observe(
2057 document.documentElement,
2058 { attributes: true, attributeFilter: ["class"] },
2059 );
2060
2061 if (document.readyState !== "loading") {
2062 if (!has_process_run_at_least_once) {
2063 darkify_init_processes();
2064 }
2065 darkify_implement_secondary_bg();
2066 darkify_apply_pseudo_bg_styles();
2067 darkify_recheck_on_css_loaded_later();
2068 darkify_restore_selected_theme();
2069 } else {
2070 document.addEventListener("DOMContentLoaded", function () {
2071 if (!has_process_run_at_least_once) {
2072 darkify_init_processes();
2073 }
2074 darkify_implement_secondary_bg();
2075 darkify_apply_pseudo_bg_styles();
2076 darkify_recheck_on_css_loaded_later();
2077 darkify_restore_selected_theme();
2078 });
2079 }
2080 }
2081
2082 if (!_dkf_iframe_disabled && darkify_check_preloading()) {
2083 document
2084 .getElementsByTagName("html")[0]
2085 .classList.add("darkify_dark_mode_enabled");
2086 darkify_init_observer();
2087 darkify_process_iframes(); // �
2088 darkify proceed iframe
2089 }
2090
2091 if (document.readyState !== "loading") {
2092 darkify_restore_selected_theme();
2093 } else {
2094 document.addEventListener("DOMContentLoaded", darkify_restore_selected_theme);
2095 }
2096