Changeset 3348419
- Timestamp:
- 08/22/2025 03:52:32 AM (7 months ago)
- Location:
- joan/trunk
- Files:
-
- 6 edited
-
assets/css/joan.css (modified) (8 diffs)
-
assets/js/joan.js (modified) (20 diffs)
-
includes/admin-menu.php (modified) (7 diffs)
-
includes/crud.php (modified) (3 diffs)
-
joan.php (modified) (3 diffs)
-
readme.txt (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
-
joan/trunk/assets/css/joan.css
r3343873 r3348419 151 151 font-family: "Courier New", monospace; 152 152 font-weight: 600; 153 } 154 155 /* Dark mode toggle button */ 156 .joan-dark-mode-toggle { 157 position: absolute; 158 top: 8px; 159 right: 8px; 160 background: transparent; 161 border: 1px solid #ddd; 162 border-radius: 4px; 163 padding: 4px 8px; 164 font-size: 12px; 165 cursor: pointer; 166 transition: all 0.2s ease; 167 z-index: 10; 168 } 169 170 .joan-dark-mode-toggle:hover { 171 background: #f0f0f0; 172 border-color: #999; 153 173 } 154 174 … … 464 484 } 465 485 486 /* ======================================================================== 487 CLASS-BASED DARK MODE STYLES 488 ======================================================================== */ 489 490 /* Dark mode main widget */ 491 .joan-dark-mode .joan-now-playing { 492 background: #2c3e50; 493 border-color: #34495e; 494 color: #ecf0f1; 495 } 496 497 .joan-dark-mode .current-show { 498 color: #ecf0f1; 499 } 500 501 .joan-dark-mode .current-show a { 502 color: #3498db; 503 } 504 505 .joan-dark-mode .current-show a:hover { 506 color: #2980b9; 507 } 508 509 .joan-dark-mode .joan-jock, 510 .joan-dark-mode .upcoming-show { 511 color: #bdc3c7; 512 } 513 514 .joan-dark-mode .show-time { 515 color: #95a5a6; 516 } 517 518 .joan-dark-mode .upcoming-show { 519 border-top-color: #34495e; 520 } 521 522 .joan-dark-mode .joan-timezone-selector select { 523 background: #34495e; 524 color: #ecf0f1; 525 border-color: #4a5c6a; 526 } 527 528 .joan-dark-mode .joan-timezone-selector { 529 background: #34495e; 530 border-color: #4a5c6a; 531 color: #ecf0f1; 532 } 533 534 .joan-dark-mode .joan-timezone-selector .joan-timezone-time { 535 color: #bdc3c7; 536 } 537 538 .joan-dark-mode .joan-dark-mode-toggle { 539 background: #34495e; 540 border-color: #4a5c6a; 541 color: #ecf0f1; 542 } 543 544 .joan-dark-mode .joan-dark-mode-toggle:hover { 545 background: #4a5c6a; 546 border-color: #5a6c7a; 547 } 548 549 .joan-dark-mode .joan-schedule-day-table, 550 .joan-dark-mode table.joan-schedule { 551 background: #2c3e50; 552 color: #ecf0f1; 553 } 554 555 .joan-dark-mode .joan-schedule-day-table th, 556 .joan-dark-mode table.joan-schedule th { 557 background: #34495e; 558 } 559 560 .joan-dark-mode .joan-schedule-day-table td, 561 .joan-dark-mode table.joan-schedule td { 562 border-bottom-color: #4a5c6a; 563 } 564 565 .joan-dark-mode .joan-schedule-day-table tr:hover, 566 .joan-dark-mode table.joan-schedule tr:hover { 567 background: #34495e; 568 } 569 570 .joan-dark-mode .joan-schedule-day-table tr:nth-child(even), 571 .joan-dark-mode table.joan-schedule tr:nth-child(even) { 572 background: #2c3e50; 573 } 574 575 .joan-dark-mode .joan-schedule-empty-day { 576 background: #34495e; 577 border-color: #4a5c6a; 578 color: #bdc3c7; 579 } 580 581 .joan-dark-mode .joan-schedule-controls { 582 background: #34495e; 583 border-color: #4a5c6a; 584 color: #ecf0f1; 585 } 586 587 .joan-dark-mode .joan-schedule-controls select { 588 background: #2c3e50; 589 border-color: #4a5c6a; 590 color: #ecf0f1; 591 } 592 593 .joan-dark-mode .joan-schedule-controls label { 594 color: #ecf0f1; 595 } 596 597 .joan-dark-mode .joan-loading { 598 color: #bdc3c7; 599 } 600 601 .joan-dark-mode .joan-error { 602 background: #4a2c2a; 603 border-color: #6a4c4a; 604 color: #e74c3c; 605 } 606 607 .joan-dark-mode .joan-error a { 608 color: #e74c3c; 609 } 610 611 .joan-dark-mode .joan-status-message { 612 color: #bdc3c7; 613 } 614 615 .joan-dark-mode .joan-upcoming-item { 616 background: #34495e; 617 border-left-color: #3498db; 618 color: #ecf0f1; 619 } 620 621 /* Dark mode overrides for highlights */ 622 .joan-dark-mode .joan-today-highlight { 623 background: #34495e !important; 624 border-left-color: #3498db; 625 } 626 627 .joan-dark-mode .joan-current-show-highlight { 628 background: #2c3e50 !important; 629 border-left-color: #1abc9c; 630 } 631 632 /* Dark mode overrides for day headers */ 633 .joan-dark-mode .joan-schedule-day-header { 634 background: #34495e !important; 635 color: #ecf0f1 !important; 636 } 637 638 .joan-dark-mode .joan-sunday-header { 639 background: #34495e !important; 640 } 641 642 .joan-dark-mode .joan-monday-header { 643 background: #34495e !important; 644 } 645 646 .joan-dark-mode .joan-tuesday-header { 647 background: #34495e !important; 648 } 649 650 .joan-dark-mode .joan-wednesday-header { 651 background: #34495e !important; 652 } 653 654 .joan-dark-mode .joan-thursday-header { 655 background: #34495e !important; 656 } 657 658 .joan-dark-mode .joan-friday-header { 659 background: #34495e !important; 660 } 661 662 .joan-dark-mode .joan-saturday-header { 663 background: #34495e !important; 664 } 665 666 /* ======================================================================== 667 LIGHT MODE OVERRIDES (Counteract system preference dark mode) 668 ======================================================================== */ 669 670 /* Light mode overrides to counteract system preference dark mode */ 671 .joan-light-mode .joan-now-playing { 672 background: #f9f9f9 !important; 673 border-color: #ddd !important; 674 color: #333 !important; 675 } 676 677 .joan-light-mode .current-show { 678 color: #333 !important; 679 } 680 681 .joan-light-mode .current-show a { 682 color: #0073aa !important; 683 } 684 685 .joan-light-mode .current-show a:hover { 686 color: #005a87 !important; 687 } 688 689 .joan-light-mode .joan-jock, 690 .joan-light-mode .upcoming-show { 691 color: #666 !important; 692 } 693 694 .joan-light-mode .show-time { 695 color: #555 !important; 696 } 697 698 .joan-light-mode .upcoming-show { 699 border-top-color: #eee !important; 700 } 701 702 .joan-light-mode .upcoming-show strong { 703 color: #333 !important; 704 } 705 706 .joan-light-mode .joan-timezone-selector { 707 background: #f8f9fa !important; 708 border-color: #e9ecef !important; 709 color: #666 !important; 710 } 711 712 .joan-light-mode .joan-timezone-selector select { 713 background: white !important; 714 color: #333 !important; 715 border-color: #ccc !important; 716 } 717 718 .joan-light-mode .joan-timezone-selector .joan-timezone-time { 719 color: #555 !important; 720 } 721 722 .joan-light-mode .joan-dark-mode-toggle { 723 background: transparent !important; 724 border-color: #ddd !important; 725 color: #333 !important; 726 } 727 728 .joan-light-mode .joan-dark-mode-toggle:hover { 729 background: #f0f0f0 !important; 730 border-color: #999 !important; 731 } 732 733 .joan-light-mode .joan-today-highlight { 734 background: #e7f3ff !important; 735 border-left-color: #0073aa !important; 736 } 737 738 .joan-light-mode .joan-current-show-highlight { 739 background: #d1ecf1 !important; 740 border-left-color: #17a2b8 !important; 741 } 742 743 .joan-light-mode .joan-schedule-day-header { 744 color: white !important; 745 } 746 747 .joan-light-mode .joan-sunday-header { 748 background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%) !important; 749 } 750 751 .joan-light-mode .joan-monday-header { 752 background: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 100%) !important; 753 } 754 755 .joan-light-mode .joan-tuesday-header { 756 background: linear-gradient(135deg, #ffd3a5 0%, #ffeebc 100%) !important; 757 } 758 759 .joan-light-mode .joan-wednesday-header { 760 background: linear-gradient(135deg, #a8c8ec 0%, #c3d5f5 100%) !important; 761 } 762 763 .joan-light-mode .joan-thursday-header { 764 background: linear-gradient(135deg, #c8a2c8 0%, #e1bee7 100%) !important; 765 } 766 767 .joan-light-mode .joan-friday-header { 768 background: linear-gradient(135deg, #ffb3ba 0%, #ffcccc 100%) !important; 769 } 770 771 .joan-light-mode .joan-saturday-header { 772 background: linear-gradient(135deg, #bae1ff 0%, #d4f1ff 100%) !important; 773 } 774 775 .joan-light-mode .joan-schedule-day-table, 776 .joan-light-mode table.joan-schedule { 777 background: white !important; 778 color: #333 !important; 779 } 780 781 .joan-light-mode .joan-schedule-day-table th, 782 .joan-light-mode table.joan-schedule th { 783 color: white !important; 784 } 785 786 .joan-light-mode .joan-schedule-day-table td, 787 .joan-light-mode table.joan-schedule td { 788 border-bottom-color: #eee !important; 789 } 790 791 .joan-light-mode .joan-schedule-day-table tr:hover, 792 .joan-light-mode table.joan-schedule tr:hover { 793 background: #f8f9fa !important; 794 } 795 796 .joan-light-mode .joan-schedule-day-table tr:nth-child(even), 797 .joan-light-mode table.joan-schedule tr:nth-child(even) { 798 background: #fafafa !important; 799 } 800 801 .joan-light-mode .joan-schedule-day-table tr:nth-child(even):hover, 802 .joan-light-mode table.joan-schedule tr:nth-child(even):hover { 803 background: #f0f0f0 !important; 804 } 805 806 .joan-light-mode .joan-schedule-empty-day { 807 background: #f9f9f9 !important; 808 border-color: #ddd !important; 809 color: #666 !important; 810 } 811 812 .joan-light-mode .joan-schedule-controls { 813 background: #f8f9fa !important; 814 border-color: #e9ecef !important; 815 color: #495057 !important; 816 } 817 818 .joan-light-mode .joan-schedule-controls select { 819 background: white !important; 820 border-color: #ced4da !important; 821 color: #495057 !important; 822 } 823 824 .joan-light-mode .joan-schedule-controls label { 825 color: #495057 !important; 826 } 827 828 .joan-light-mode .joan-loading { 829 color: #999 !important; 830 } 831 832 .joan-light-mode .joan-error { 833 background: #ffeaea !important; 834 border-color: #f0c2c2 !important; 835 color: #d63638 !important; 836 } 837 838 .joan-light-mode .joan-error a { 839 color: #d63638 !important; 840 } 841 842 .joan-light-mode .joan-status-message { 843 color: #666 !important; 844 } 845 846 .joan-light-mode .joan-upcoming-item { 847 background: #f9f9f9 !important; 848 border-left-color: #0073aa !important; 849 color: #333 !important; 850 } 851 852 /* Light mode overrides for day-specific table headers */ 853 .joan-light-mode .joan-sunday-table th { 854 background-color: #ff6b87 !important; 855 color: white !important; 856 } 857 858 .joan-light-mode .joan-monday-table th { 859 background-color: #4caf50 !important; 860 color: white !important; 861 } 862 863 .joan-light-mode .joan-tuesday-table th { 864 background-color: #ff9800 !important; 865 color: white !important; 866 } 867 868 .joan-light-mode .joan-wednesday-table th { 869 background-color: #2196f3 !important; 870 color: white !important; 871 } 872 873 .joan-light-mode .joan-thursday-table th { 874 background-color: #9c27b0 !important; 875 color: white !important; 876 } 877 878 .joan-light-mode .joan-friday-table th { 879 background-color: #f44336 !important; 880 color: white !important; 881 } 882 883 .joan-light-mode .joan-saturday-table th { 884 background-color: #00bcd4 !important; 885 color: white !important; 886 } 887 888 /* Light mode overrides for filtered schedule */ 889 .joan-light-mode .joan-filtered-schedule table.joan-schedule th { 890 background: #28a745 !important; /* Green for "What's on today" */ 891 color: white !important; 892 } 893 894 /* Light mode overrides for general schedule table headers */ 895 .joan-light-mode table.joan-schedule th { 896 background: #0073aa !important; 897 color: white !important; 898 } 899 900 /* Additional comprehensive light mode overrides */ 901 .joan-light-mode .joan-current-time { 902 color: #777 !important; 903 } 904 905 .joan-light-mode .upcoming-time { 906 color: #777 !important; 907 } 908 909 .joan-light-mode .joan-status-suspended { 910 background: #fff3cd !important; 911 border-color: #ffeaa7 !important; 912 } 913 914 .joan-light-mode .joan-status-off_air { 915 background: #f8d7da !important; 916 border-color: #f5c6cb !important; 917 } 918 919 /* Additional specific light mode overrides for stubborn elements */ 920 .joan-light-mode .joan-schedule-day-table th { 921 color: white !important; 922 background-color: inherit !important; 923 } 924 925 .joan-light-mode .joan-schedule-day-table td { 926 color: #333 !important; 927 background: white !important; 928 } 929 930 .joan-light-mode .joan-schedule-day-table tr:nth-child(even) td { 931 background: #fafafa !important; 932 color: #333 !important; 933 } 934 935 .joan-light-mode .joan-schedule-day-table tr:hover td { 936 background: #f8f9fa !important; 937 color: #333 !important; 938 } 939 940 .joan-light-mode .joan-schedule-day-table tr:nth-child(even):hover td { 941 background: #f0f0f0 !important; 942 color: #333 !important; 943 } 944 945 /* Comprehensive table overrides */ 946 .joan-light-mode table.joan-schedule td { 947 color: #333 !important; 948 background: white !important; 949 } 950 951 .joan-light-mode table.joan-schedule tr:nth-child(even) td { 952 background: #fafafa !important; 953 color: #333 !important; 954 } 955 956 .joan-light-mode table.joan-schedule tr:hover td { 957 background: #f8f9fa !important; 958 color: #333 !important; 959 } 960 961 .joan-light-mode table.joan-schedule tr:nth-child(even):hover td { 962 background: #f0f0f0 !important; 963 color: #333 !important; 964 } 965 966 /* Nuclear option - override everything with maximum specificity */ 967 .joan-light-mode.joan-light-mode .joan-schedule-day-table { 968 background: white !important; 969 color: #333 !important; 970 } 971 972 .joan-light-mode.joan-light-mode .joan-schedule-day-table th { 973 color: white !important; 974 } 975 976 .joan-light-mode.joan-light-mode .joan-schedule-day-table td { 977 color: #333 !important; 978 background: white !important; 979 border-bottom-color: #eee !important; 980 } 981 982 .joan-light-mode.joan-light-mode .joan-schedule-day-table tr { 983 background: white !important; 984 color: #333 !important; 985 } 986 987 .joan-light-mode.joan-light-mode .joan-schedule-day-table tr:nth-child(even) { 988 background: #fafafa !important; 989 color: #333 !important; 990 } 991 992 .joan-light-mode.joan-light-mode .joan-schedule-day-table tr:hover { 993 background: #f8f9fa !important; 994 color: #333 !important; 995 } 996 997 .joan-light-mode.joan-light-mode .joan-schedule-day-table tr:nth-child(even):hover { 998 background: #f0f0f0 !important; 999 color: #333 !important; 1000 } 1001 1002 /* Apply same nuclear approach to standard tables */ 1003 .joan-light-mode.joan-light-mode table.joan-schedule { 1004 background: white !important; 1005 color: #333 !important; 1006 } 1007 1008 .joan-light-mode.joan-light-mode table.joan-schedule th { 1009 color: white !important; 1010 } 1011 1012 .joan-light-mode.joan-light-mode table.joan-schedule td { 1013 color: #333 !important; 1014 background: white !important; 1015 border-bottom-color: #eee !important; 1016 } 1017 1018 .joan-light-mode.joan-light-mode table.joan-schedule tr { 1019 background: white !important; 1020 color: #333 !important; 1021 } 1022 1023 .joan-light-mode.joan-light-mode table.joan-schedule tr:nth-child(even) { 1024 background: #fafafa !important; 1025 color: #333 !important; 1026 } 1027 1028 .joan-light-mode.joan-light-mode table.joan-schedule tr:hover { 1029 background: #f8f9fa !important; 1030 color: #333 !important; 1031 } 1032 1033 .joan-light-mode.joan-light-mode table.joan-schedule tr:nth-child(even):hover { 1034 background: #f0f0f0 !important; 1035 color: #333 !important; 1036 } 1037 1038 /* ======================================================================== 1039 SYSTEM PREFERENCE DARK MODE SUPPORT (Fallback) 1040 ======================================================================== */ 1041 1042 /* Dark mode support for browsers that don't have JS or as fallback */ 1043 @media (prefers-color-scheme: dark) { 1044 .joan-now-playing:not(.joan-light-mode) { 1045 background: #2c3e50; 1046 border-color: #34495e; 1047 color: #ecf0f1; 1048 } 1049 1050 .joan-now-playing:not(.joan-light-mode) .current-show { 1051 color: #ecf0f1; 1052 } 1053 1054 .joan-now-playing:not(.joan-light-mode) .current-show a { 1055 color: #3498db; 1056 } 1057 1058 .joan-now-playing:not(.joan-light-mode) .current-show a:hover { 1059 color: #2980b9; 1060 } 1061 1062 .joan-now-playing:not(.joan-light-mode) .joan-jock, 1063 .joan-now-playing:not(.joan-light-mode) .upcoming-show { 1064 color: #bdc3c7; 1065 } 1066 1067 .joan-now-playing:not(.joan-light-mode) .show-time { 1068 color: #95a5a6; 1069 } 1070 1071 .joan-now-playing:not(.joan-light-mode) .upcoming-show { 1072 border-top-color: #34495e; 1073 } 1074 1075 .joan-now-playing:not(.joan-light-mode) .joan-timezone-selector select { 1076 background: #34495e; 1077 color: #ecf0f1; 1078 border-color: #4a5c6a; 1079 } 1080 1081 .joan-now-playing:not(.joan-light-mode) .joan-timezone-selector { 1082 background: #34495e; 1083 border-color: #4a5c6a; 1084 color: #ecf0f1; 1085 } 1086 1087 .joan-now-playing:not(.joan-light-mode) .joan-timezone-selector .joan-timezone-time { 1088 color: #bdc3c7; 1089 } 1090 1091 .joan-schedule-day-table:not(.joan-light-mode), 1092 table.joan-schedule:not(.joan-light-mode) { 1093 background: #2c3e50; 1094 color: #ecf0f1; 1095 } 1096 1097 .joan-schedule-day-table:not(.joan-light-mode) th, 1098 table.joan-schedule:not(.joan-light-mode) th { 1099 background: #34495e; 1100 } 1101 1102 .joan-schedule-day-table:not(.joan-light-mode) td, 1103 table.joan-schedule:not(.joan-light-mode) td { 1104 border-bottom-color: #4a5c6a; 1105 } 1106 1107 .joan-schedule-day-table:not(.joan-light-mode) tr:hover, 1108 table.joan-schedule:not(.joan-light-mode) tr:hover { 1109 background: #34495e; 1110 } 1111 1112 .joan-schedule-day-table:not(.joan-light-mode) tr:nth-child(even), 1113 table.joan-schedule:not(.joan-light-mode) tr:nth-child(even) { 1114 background: #2c3e50; 1115 } 1116 1117 .joan-schedule-empty-day:not(.joan-light-mode) { 1118 background: #34495e; 1119 border-color: #4a5c6a; 1120 color: #bdc3c7; 1121 } 1122 1123 .joan-schedule-controls:not(.joan-light-mode) { 1124 background: #34495e; 1125 border-color: #4a5c6a; 1126 color: #ecf0f1; 1127 } 1128 1129 .joan-schedule-controls:not(.joan-light-mode) select { 1130 background: #2c3e50; 1131 border-color: #4a5c6a; 1132 color: #ecf0f1; 1133 } 1134 1135 /* System preference dark mode overrides for highlights */ 1136 .joan-today-highlight:not(.joan-light-mode) { 1137 background: #34495e !important; 1138 border-left-color: #3498db; 1139 } 1140 1141 .joan-current-show-highlight:not(.joan-light-mode) { 1142 background: #2c3e50 !important; 1143 border-left-color: #1abc9c; 1144 } 1145 1146 /* System preference dark mode overrides for day headers */ 1147 .joan-schedule-day-header:not(.joan-light-mode) { 1148 background: #34495e !important; 1149 color: #ecf0f1 !important; 1150 } 1151 1152 .joan-sunday-header:not(.joan-light-mode), 1153 .joan-monday-header:not(.joan-light-mode), 1154 .joan-tuesday-header:not(.joan-light-mode), 1155 .joan-wednesday-header:not(.joan-light-mode), 1156 .joan-thursday-header:not(.joan-light-mode), 1157 .joan-friday-header:not(.joan-light-mode), 1158 .joan-saturday-header:not(.joan-light-mode) { 1159 background: #34495e !important; 1160 } 1161 } 1162 466 1163 /* Responsive design */ 467 1164 @media (max-width: 768px) { … … 478 1175 .joan-thumb-large { 479 1176 max-width: 200px; 1177 } 1178 1179 .joan-dark-mode-toggle { 1180 position: static; 1181 display: block; 1182 margin: 0 auto 10px auto; 1183 width: auto; 480 1184 } 481 1185 … … 571 1275 572 1276 .joan-timezone-selector, 573 .joan-schedule-controls { 1277 .joan-schedule-controls, 1278 .joan-dark-mode-toggle { 574 1279 display: none; 575 1280 } … … 594 1299 } 595 1300 596 /* Dark mode support */597 @media (prefers-color-scheme: dark) {598 .joan-now-playing {599 background: #2c3e50;600 border-color: #34495e;601 color: #ecf0f1;602 }603 604 .current-show {605 color: #ecf0f1;606 }607 608 .current-show a {609 color: #3498db;610 }611 612 .current-show a:hover {613 color: #2980b9;614 }615 616 .joan-jock,617 .upcoming-show {618 color: #bdc3c7;619 }620 621 .show-time {622 color: #95a5a6;623 }624 625 .upcoming-show {626 border-top-color: #34495e;627 }628 629 .joan-timezone-selector select {630 background: #34495e;631 color: #ecf0f1;632 border-color: #4a5c6a;633 }634 635 .joan-timezone-selector {636 background: #34495e;637 border-color: #4a5c6a;638 color: #ecf0f1;639 }640 641 .joan-timezone-selector .joan-timezone-time {642 color: #bdc3c7;643 }644 645 .joan-schedule-day-table,646 table.joan-schedule {647 background: #2c3e50;648 color: #ecf0f1;649 }650 651 .joan-schedule-day-table th,652 table.joan-schedule th {653 background: #34495e;654 }655 656 .joan-schedule-day-table td,657 table.joan-schedule td {658 border-bottom-color: #4a5c6a;659 }660 661 .joan-schedule-day-table tr:hover,662 table.joan-schedule tr:hover {663 background: #34495e;664 }665 666 .joan-schedule-day-table tr:nth-child(even),667 table.joan-schedule tr:nth-child(even) {668 background: #2c3e50;669 }670 671 .joan-schedule-empty-day {672 background: #34495e;673 border-color: #4a5c6a;674 color: #bdc3c7;675 }676 677 .joan-schedule-controls {678 background: #34495e;679 border-color: #4a5c6a;680 color: #ecf0f1;681 }682 683 .joan-schedule-controls select {684 background: #2c3e50;685 border-color: #4a5c6a;686 color: #ecf0f1;687 }688 }689 690 1301 /* Accessibility improvements */ 691 1302 .joan-now-playing:focus-within { … … 700 1311 } 701 1312 702 .joan-schedule-controls select:focus { 1313 .joan-schedule-controls select:focus, 1314 .joan-dark-mode-toggle:focus { 703 1315 outline: 2px solid #0073aa; 704 1316 outline-offset: 1px; … … 708 1320 @media (prefers-reduced-motion: reduce) { 709 1321 .joan-thumb, 710 .current-show a { 1322 .current-show a, 1323 .joan-dark-mode-toggle { 711 1324 transition: none; 712 1325 } … … 740 1353 border: 2px solid #000; 741 1354 } 742 } 1355 1356 .joan-dark-mode-toggle { 1357 border-width: 2px; 1358 } 1359 } -
joan/trunk/assets/js/joan.js
r3343873 r3348419 12 12 const CACHE_DURATION = 60000; // 1 minute cache 13 13 14 function initializeDarkMode() { 15 const settings = window.joan_ajax ? window.joan_ajax.settings : {}; 16 const darkModeSetting = settings.joan_dark_mode || 'auto'; 17 const allowOverride = settings.joan_dark_mode_override === '1'; 18 19 // Get stored user preference if override is allowed 20 let userPreference = null; 21 if (allowOverride) { 22 try { 23 userPreference = localStorage.getItem('joan_dark_mode_preference'); 24 } catch (e) { 25 // localStorage not available 26 } 27 } 28 29 // Determine if dark mode should be active 30 let shouldUseDarkMode = false; 31 32 switch (darkModeSetting) { 33 case 'dark': 34 shouldUseDarkMode = true; 35 break; 36 case 'light': 37 shouldUseDarkMode = false; 38 break; 39 case 'disabled': 40 shouldUseDarkMode = false; 41 break; 42 case 'auto': 43 default: 44 // Check user preference first, then system preference 45 if (userPreference === 'dark') { 46 shouldUseDarkMode = true; 47 } else if (userPreference === 'light') { 48 shouldUseDarkMode = false; 49 } else { 50 // Fall back to system preference 51 shouldUseDarkMode = window.matchMedia && 52 window.matchMedia('(prefers-color-scheme: dark)').matches; 53 } 54 break; 55 } 56 57 // Apply dark mode class 58 applyDarkMode(shouldUseDarkMode); 59 60 // Add toggle buttons if override is allowed 61 if (allowOverride && darkModeSetting !== 'disabled') { 62 addDarkModeToggles(shouldUseDarkMode); 63 } 64 65 // Listen for system preference changes if in auto mode 66 if (darkModeSetting === 'auto' && window.matchMedia && !userPreference) { 67 const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 68 const handleChange = (e) => { 69 if (!localStorage.getItem('joan_dark_mode_preference')) { 70 applyDarkMode(e.matches); 71 updateToggleButtons(e.matches); 72 } 73 }; 74 75 // Use modern addEventListener if available, fallback to addListener 76 if (mediaQuery.addEventListener) { 77 mediaQuery.addEventListener('change', handleChange); 78 } else if (mediaQuery.addListener) { 79 mediaQuery.addListener(handleChange); 80 } 81 } 82 } 83 84 function applyDarkMode(isDark) { 85 const body = document.body; 86 87 if (isDark) { 88 body.classList.add('joan-dark-mode'); 89 body.classList.remove('joan-light-mode'); 90 } else { 91 body.classList.remove('joan-dark-mode'); 92 body.classList.add('joan-light-mode'); 93 } 94 95 // Apply to ALL current elements - comprehensive targeting with specific day classes 96 const selectors = [ 97 '.joan-now-playing', 98 '.joan-schedule-container', 99 'table.joan-schedule', 100 '.joan-schedule-day-section', 101 '.joan-schedule-day-table', 102 '.joan-schedule-day-header', 103 '.joan-schedule-controls', 104 '.joan-schedule-empty-day', 105 // Add specific day classes 106 '.joan-sunday-header', 107 '.joan-monday-header', 108 '.joan-tuesday-header', 109 '.joan-wednesday-header', 110 '.joan-thursday-header', 111 '.joan-friday-header', 112 '.joan-saturday-header', 113 '.joan-sunday-table', 114 '.joan-monday-table', 115 '.joan-tuesday-table', 116 '.joan-wednesday-table', 117 '.joan-thursday-table', 118 '.joan-friday-table', 119 '.joan-saturday-table' 120 ]; 121 122 selectors.forEach(selector => { 123 const elements = document.querySelectorAll(selector); 124 elements.forEach(element => { 125 if (isDark) { 126 element.classList.add('joan-dark-mode'); 127 element.classList.remove('joan-light-mode'); 128 } else { 129 element.classList.remove('joan-dark-mode'); 130 element.classList.add('joan-light-mode'); 131 } 132 }); 133 }); 134 } 135 136 function addDarkModeToggles(currentState) { 137 $('.joan-now-playing').each(function() { 138 const $container = $(this); 139 140 // Don't add toggle if one already exists 141 if ($container.find('.joan-dark-mode-toggle').length > 0) { 142 return; 143 } 144 145 const $toggle = $('<button type="button" class="joan-dark-mode-toggle"></button>'); 146 updateToggleButton($toggle, currentState); 147 148 $toggle.on('click', function(e) { 149 e.preventDefault(); 150 toggleDarkMode(); 151 }); 152 153 $container.append($toggle); 154 }); 155 } 156 157 function updateToggleButton($button, isDark) { 158 $button.text(isDark ? '🌕' : '☀️') 159 .attr('title', isDark ? 'Switch to light mode' : 'Switch to dark mode') 160 .attr('aria-label', isDark ? 'Switch to light mode' : 'Switch to dark mode'); 161 } 162 163 function updateToggleButtons(isDark) { 164 $('.joan-dark-mode-toggle').each(function() { 165 updateToggleButton($(this), isDark); 166 }); 167 } 168 169 function toggleDarkMode() { 170 const isDarkMode = document.body.classList.contains('joan-dark-mode'); 171 const newState = !isDarkMode; 172 173 // Apply the change 174 applyDarkMode(newState); 175 updateToggleButtons(newState); 176 177 // Store user preference 178 try { 179 localStorage.setItem('joan_dark_mode_preference', newState ? 'dark' : 'light'); 180 } catch (e) { 181 // localStorage not available 182 } 183 } 184 14 185 function getPreferredTimezone() { 15 186 return localStorage.getItem("joan_timezone") || Intl.DateTimeFormat().resolvedOptions().timeZone; … … 258 429 statusIcon = '<span class="joan-status-icon">⏸️</span>'; 259 430 } else if (status === 'off_air') { 260 statusIcon = '<span class="joan-status-icon"> 📴</span>';431 statusIcon = '<span class="joan-status-icon">🔴</span>'; 261 432 } 262 433 … … 296 467 } 297 468 469 // Apply dark mode if appropriate 470 const isDarkMode = document.body.classList.contains('joan-dark-mode'); 471 if (isDarkMode) { 472 $container.addClass('joan-dark-mode'); 473 } else { 474 $container.removeClass('joan-dark-mode'); 475 } 476 298 477 // Check for off-air conditions or non-active status 299 478 if (response.schedule_status !== 'active' || !response.current_show) { … … 304 483 if (!jockOnlyMode && response.schedule_status !== 'off_air' && allowTimezoneSelector) { 305 484 buildTimezoneDropdown(getPreferredTimezone(), $container, settings); 485 } 486 487 // Add dark mode toggle if appropriate 488 const allowOverride = settings.joan_dark_mode_override === '1'; 489 const darkModeSetting = settings.joan_dark_mode || 'auto'; 490 if (allowOverride && darkModeSetting !== 'disabled') { 491 addDarkModeToggles(isDarkMode); 306 492 } 307 493 … … 335 521 jockOnlyHtml += imageHtml; 336 522 337 // DISABLED: Add current time display only if timezone selector is not enabled338 // const allowTimezoneSelector = settings.joan_allow_timezone_selector === '1';339 // if (settings.joan_show_local_time === '1' && !allowTimezoneSelector) {340 // jockOnlyHtml = '<div class="joan-current-time"></div>' + jockOnlyHtml;341 // }342 343 523 $container.html(jockOnlyHtml); 344 524 … … 347 527 if (allowTimezoneSelector) { 348 528 buildTimezoneDropdown(getPreferredTimezone(), $container, settings); 529 } 530 531 // Add dark mode toggle 532 const allowOverride = settings.joan_dark_mode_override === '1'; 533 const darkModeSetting = settings.joan_dark_mode || 'auto'; 534 if (allowOverride && darkModeSetting !== 'disabled') { 535 addDarkModeToggles(isDarkMode); 349 536 } 350 537 }); … … 355 542 </div>`; 356 543 357 // DISABLED: Add current time display only if timezone selector is not enabled358 // const allowTimezoneSelector = settings.joan_allow_timezone_selector === '1';359 // if (settings.joan_show_local_time === '1' && !allowTimezoneSelector) {360 // html = '<div class="joan-current-time"></div>' + html;361 // }362 363 544 $container.html(html); 364 545 … … 367 548 if (allowTimezoneSelector) { 368 549 buildTimezoneDropdown(getPreferredTimezone(), $container, settings); 550 } 551 552 // Add dark mode toggle 553 const allowOverride = settings.joan_dark_mode_override === '1'; 554 const darkModeSetting = settings.joan_dark_mode || 'auto'; 555 if (allowOverride && darkModeSetting !== 'disabled') { 556 addDarkModeToggles(isDarkMode); 369 557 } 370 558 } … … 398 586 } 399 587 400 // DISABLED: Add current time display only if timezone selector is not enabled401 // const allowTimezoneSelector = settings.joan_allow_timezone_selector === '1';402 // if (settings.joan_show_local_time === '1' && !allowTimezoneSelector) {403 // html = '<div class="joan-current-time"></div>' + html;404 // }405 406 588 $container.html(html); 407 589 … … 410 592 if (allowTimezoneSelector) { 411 593 buildTimezoneDropdown(getPreferredTimezone(), $container, settings); 594 } 595 596 // Add dark mode toggle if appropriate 597 const allowOverride = settings.joan_dark_mode_override === '1'; 598 const darkModeSetting = settings.joan_dark_mode || 'auto'; 599 if (allowOverride && darkModeSetting !== 'disabled') { 600 addDarkModeToggles(isDarkMode); 412 601 } 413 602 }); … … 435 624 } 436 625 437 // Find all joan-now-playing containers 438 //CodeSig: sgketg 626 //CodeSig: sgketg 439 627 $('.joan-now-playing').each(function() { 440 628 const $container = $(this); … … 627 815 const uniqueId = 'joan-day-filter-' + Math.random().toString(36).substr(2, 9); 628 816 629 // Add day filter controls 817 // Add day filter controls - DO NOT apply dark mode classes during creation 630 818 const $controls = $(` 631 819 <div class="joan-schedule-controls"> … … 649 837 const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 650 838 const dayEmojis = { 651 'Sunday': '☀️',652 'Monday': '📅',653 'Tuesday': '📋',654 'Wednesday': '🔄',655 'Thursday': '⚡',656 'Friday': '🎉',657 'Saturday': '🎊'839 'Sunday': 'He', 840 'Monday': 'Ag', 841 'Tuesday': 'Fe', 842 'Wednesday': 'Hg', 843 'Thursday': 'Au', 844 'Friday': 'Cu', 845 'Saturday': 'Pb' 658 846 }; 659 847 848 const dayTooltips = { 849 'Sunday': 'Helium (He) - Second most abundant element in the universe, powers the sun through nuclear fusion', 850 'Monday': 'Silver (Ag) - Precious metal with highest electrical conductivity, reflects light like moonlight', 851 'Tuesday': 'Iron (Fe) - Most abundant metal on Earth, essential for steel and human blood', 852 'Wednesday': 'Mercury (Hg) - Only metal that is liquid at room temperature, used in thermometers', 853 'Thursday': 'Gold (Au) - Noble metal that never tarnishes, symbol of value and permanence', 854 'Friday': 'Copper (Cu) - Reddish metal essential for electrical wiring, naturally antibacterial', 855 'Saturday': 'Lead (Pb) - Dense metal historically used for protection, now known to be toxic' 856 }; 857 660 858 days.forEach(day => { 661 859 const dayShows = dayGroups[day]; … … 663 861 const dayClass = day.toLowerCase(); 664 862 665 // Create day section 863 // Create day section - DO NOT apply dark mode classes during creation 666 864 const $daySection = $(` 667 865 <div class="joan-schedule-day-section" data-day="${day}"> 668 <div class="joan-schedule-day-header joan-${dayClass}-header">669 ${emoji}${day} Schedule670 </div>671 </div>672 `);866 <div class="joan-schedule-day-header joan-${dayClass}-header" title="${dayTooltips[day] || ''}"> 867 [${emoji}] ${day} Schedule 868 </div> 869 </div> 870 `); 673 871 674 872 if (dayShows.length > 0) { 675 // Create table for this day 873 // Create table for this day - DO NOT apply dark mode classes during creation 676 874 const $dayTable = $(` 677 875 <table class="joan-schedule-day-table joan-${dayClass}-table"> … … 702 900 $daySection.append($dayTable); 703 901 } else { 704 // No shows for this day 902 // No shows for this day - DO NOT apply dark mode classes during creation 705 903 $daySection.append(` 706 904 <div class="joan-schedule-empty-day"> … … 716 914 $table.replaceWith($container); 717 915 916 // CRITICAL: Apply current dark/light mode to newly created elements 917 const isDarkMode = document.body.classList.contains('joan-dark-mode'); 918 setTimeout(() => { 919 applyDarkMode(isDarkMode); 920 921 // Additional comprehensive fix - apply to ALL elements within the container 922 const modeClass = isDarkMode ? 'joan-dark-mode' : 'joan-light-mode'; 923 const removeClass = isDarkMode ? 'joan-light-mode' : 'joan-dark-mode'; 924 925 $container.find('*').each(function() { 926 this.classList.add(modeClass); 927 this.classList.remove(removeClass); 928 }); 929 $container[0].classList.add(modeClass); 930 $container[0].classList.remove(removeClass); 931 }, 100); 932 718 933 // Add filter functionality using class instead of ID 719 934 $container.find('.joan-day-filter').on('change', function() { … … 737 952 const initialDelay = typeof rocket_loader !== 'undefined' || window.rocketLoaderDelay ? 2000 : 500; 738 953 setTimeout(() => { 954 // Initialize dark mode first 955 initializeDarkMode(); 956 739 957 fetchCurrentShow(); 740 separateScheduleByDays(); // NEW: Separate schedules by days 958 separateScheduleByDays(); 959 960 // CRITICAL FIX: Apply dark mode to ALL elements after everything is created 961 setTimeout(() => { 962 const isDarkMode = document.body.classList.contains('joan-dark-mode'); 963 applyDarkMode(isDarkMode); 964 }, 300); 741 965 }, initialDelay); 742 966 … … 772 996 window.joanSeparateScheduleByDays = separateScheduleByDays; 773 997 998 // Make dark mode functions globally available 999 window.joanDarkMode = { 1000 initialize: initializeDarkMode, 1001 toggle: toggleDarkMode, 1002 apply: applyDarkMode 1003 }; 1004 774 1005 // Add manual refresh functionality to error messages 775 1006 $(document).on('click', '.joan-error a[onclick*="reload"]', function(e) { … … 826 1057 return; 827 1058 } 828 829 // DISABLED: This code block is now unreachable but preserved for potential future use830 // Show the element if it should be visible831 // element.style.display = 'block';832 833 // const now = new Date();834 // const timeFormat = timeSettings.time_format || '12';835 836 // let formatted;837 // if (timeFormat === '24') {838 // formatted = now.toLocaleTimeString(undefined, {839 // hour: '2-digit',840 // minute: '2-digit',841 // hour12: false,842 // timeZoneName: 'short'843 // });844 // } else {845 // formatted = now.toLocaleTimeString(undefined, {846 // hour: 'numeric',847 // minute: '2-digit',848 // hour12: true,849 // timeZoneName: 'short'850 // });851 // }852 853 // element.textContent = "Local time: " + formatted;854 1059 }); 855 1060 } … … 975 1180 window.joanSeparateScheduleByDays(); 976 1181 } 1182 if (window.joanDarkMode) { 1183 window.joanDarkMode.initialize(); 1184 } 977 1185 }, 500); 978 1186 }); -
joan/trunk/includes/admin-menu.php
r3344195 r3348419 180 180 181 181 <div class="joan-settings-section"> 182 <h2> 🕐Time & Timezone Settings</h2>182 <h2>🕕 Time & Timezone Settings</h2> 183 183 <p class="description">Configure how times are displayed throughout your radio station schedule.</p> 184 184 … … 206 206 <td> 207 207 <select name="timezone" class="regular-text"> 208 <?php 209 $timezones = [ 210 'America/New_York' => 'Eastern Time (New York)', 211 'America/Chicago' => 'Central Time (Chicago)', 212 'America/Denver' => 'Mountain Time (Denver)', 213 'America/Los_Angeles' => 'Pacific Time (Los Angeles)', 214 'America/Phoenix' => 'Arizona Time (Phoenix)', 215 'America/Anchorage' => 'Alaska Time (Anchorage)', 216 'Pacific/Honolulu' => 'Hawaii Time (Honolulu)', 217 'UTC' => 'UTC (Coordinated Universal Time)', 218 'Europe/London' => 'London Time (GMT/BST)', 219 'Europe/Paris' => 'Paris Time (CET/CEST)', 220 'Asia/Tokyo' => 'Tokyo Time (JST)', 221 'Australia/Sydney' => 'Sydney Time (AEST/AEDT)' 222 ]; 208 <optgroup label="🇺🇸 United States & Canada"> 209 <option value="America/New_York" <?php selected($timezone, 'America/New_York'); ?>>Eastern Time (New York)</option> 210 <option value="America/Chicago" <?php selected($timezone, 'America/Chicago'); ?>>Central Time (Chicago)</option> 211 <option value="America/Denver" <?php selected($timezone, 'America/Denver'); ?>>Mountain Time (Denver)</option> 212 <option value="America/Los_Angeles" <?php selected($timezone, 'America/Los_Angeles'); ?>>Pacific Time (Los Angeles)</option> 213 <option value="America/Phoenix" <?php selected($timezone, 'America/Phoenix'); ?>>Arizona Time (Phoenix)</option> 214 <option value="America/Anchorage" <?php selected($timezone, 'America/Anchorage'); ?>>Alaska Time (Anchorage)</option> 215 <option value="Pacific/Honolulu" <?php selected($timezone, 'Pacific/Honolulu'); ?>>Hawaii Time (Honolulu)</option> 216 <option value="America/Toronto" <?php selected($timezone, 'America/Toronto'); ?>>Eastern Canada (Toronto)</option> 217 <option value="America/Vancouver" <?php selected($timezone, 'America/Vancouver'); ?>>Pacific Canada (Vancouver)</option> 218 <option value="America/Halifax" <?php selected($timezone, 'America/Halifax'); ?>>Atlantic Canada (Halifax)</option> 219 <option value="America/St_Johns" <?php selected($timezone, 'America/St_Johns'); ?>>Newfoundland (St. John's)</option> 220 </optgroup> 223 221 224 foreach ($timezones as $tz => $label) { 225 echo '<option value="' . esc_attr($tz) . '"' . selected($timezone, $tz, false) . '>' . esc_html($label) . '</option>'; 226 } 227 ?> 222 <optgroup label="🌍 Europe & UK"> 223 <option value="Europe/London" <?php selected($timezone, 'Europe/London'); ?>>London (GMT/BST)</option> 224 <option value="Europe/Paris" <?php selected($timezone, 'Europe/Paris'); ?>>Paris (CET/CEST)</option> 225 <option value="Europe/Berlin" <?php selected($timezone, 'Europe/Berlin'); ?>>Berlin (CET/CEST)</option> 226 <option value="Europe/Rome" <?php selected($timezone, 'Europe/Rome'); ?>>Rome (CET/CEST)</option> 227 <option value="Europe/Madrid" <?php selected($timezone, 'Europe/Madrid'); ?>>Madrid (CET/CEST)</option> 228 <option value="Europe/Amsterdam" <?php selected($timezone, 'Europe/Amsterdam'); ?>>Amsterdam (CET/CEST)</option> 229 <option value="Europe/Brussels" <?php selected($timezone, 'Europe/Brussels'); ?>>Brussels (CET/CEST)</option> 230 <option value="Europe/Zurich" <?php selected($timezone, 'Europe/Zurich'); ?>>Zurich (CET/CEST)</option> 231 <option value="Europe/Vienna" <?php selected($timezone, 'Europe/Vienna'); ?>>Vienna (CET/CEST)</option> 232 <option value="Europe/Stockholm" <?php selected($timezone, 'Europe/Stockholm'); ?>>Stockholm (CET/CEST)</option> 233 <option value="Europe/Oslo" <?php selected($timezone, 'Europe/Oslo'); ?>>Oslo (CET/CEST)</option> 234 <option value="Europe/Copenhagen" <?php selected($timezone, 'Europe/Copenhagen'); ?>>Copenhagen (CET/CEST)</option> 235 <option value="Europe/Helsinki" <?php selected($timezone, 'Europe/Helsinki'); ?>>Helsinki (EET/EEST)</option> 236 <option value="Europe/Athens" <?php selected($timezone, 'Europe/Athens'); ?>>Athens (EET/EEST)</option> 237 <option value="Europe/Bucharest" <?php selected($timezone, 'Europe/Bucharest'); ?>>Bucharest (EET/EEST)</option> 238 <option value="Europe/Warsaw" <?php selected($timezone, 'Europe/Warsaw'); ?>>Warsaw (CET/CEST)</option> 239 <option value="Europe/Prague" <?php selected($timezone, 'Europe/Prague'); ?>>Prague (CET/CEST)</option> 240 <option value="Europe/Budapest" <?php selected($timezone, 'Europe/Budapest'); ?>>Budapest (CET/CEST)</option> 241 <option value="Europe/Moscow" <?php selected($timezone, 'Europe/Moscow'); ?>>Moscow (MSK)</option> 242 <option value="Europe/Kiev" <?php selected($timezone, 'Europe/Kiev'); ?>>Kiev (EET/EEST)</option> 243 <option value="Europe/Dublin" <?php selected($timezone, 'Europe/Dublin'); ?>>Dublin (GMT/IST)</option> 244 <option value="Europe/Lisbon" <?php selected($timezone, 'Europe/Lisbon'); ?>>Lisbon (WET/WEST)</option> 245 </optgroup> 246 247 <optgroup label="🌏 Asia"> 248 <option value="Asia/Tokyo" <?php selected($timezone, 'Asia/Tokyo'); ?>>Tokyo (JST)</option> 249 <option value="Asia/Shanghai" <?php selected($timezone, 'Asia/Shanghai'); ?>>Shanghai (CST)</option> 250 <option value="Asia/Hong_Kong" <?php selected($timezone, 'Asia/Hong_Kong'); ?>>Hong Kong (HKT)</option> 251 <option value="Asia/Singapore" <?php selected($timezone, 'Asia/Singapore'); ?>>Singapore (SGT)</option> 252 <option value="Asia/Seoul" <?php selected($timezone, 'Asia/Seoul'); ?>>Seoul (KST)</option> 253 <option value="Asia/Taipei" <?php selected($timezone, 'Asia/Taipei'); ?>>Taipei (CST)</option> 254 <option value="Asia/Manila" <?php selected($timezone, 'Asia/Manila'); ?>>Manila (PHT)</option> 255 <option value="Asia/Jakarta" <?php selected($timezone, 'Asia/Jakarta'); ?>>Jakarta (WIB)</option> 256 <option value="Asia/Bangkok" <?php selected($timezone, 'Asia/Bangkok'); ?>>Bangkok (ICT)</option> 257 <option value="Asia/Ho_Chi_Minh" <?php selected($timezone, 'Asia/Ho_Chi_Minh'); ?>>Ho Chi Minh City (ICT)</option> 258 <option value="Asia/Kuala_Lumpur" <?php selected($timezone, 'Asia/Kuala_Lumpur'); ?>>Kuala Lumpur (MYT)</option> 259 <option value="Asia/Mumbai" <?php selected($timezone, 'Asia/Mumbai'); ?>>Mumbai (IST)</option> 260 <option value="Asia/Kolkata" <?php selected($timezone, 'Asia/Kolkata'); ?>>Kolkata (IST)</option> 261 <option value="Asia/Dhaka" <?php selected($timezone, 'Asia/Dhaka'); ?>>Dhaka (BST)</option> 262 <option value="Asia/Karachi" <?php selected($timezone, 'Asia/Karachi'); ?>>Karachi (PKT)</option> 263 <option value="Asia/Dubai" <?php selected($timezone, 'Asia/Dubai'); ?>>Dubai (GST)</option> 264 <option value="Asia/Riyadh" <?php selected($timezone, 'Asia/Riyadh'); ?>>Riyadh (AST)</option> 265 <option value="Asia/Tehran" <?php selected($timezone, 'Asia/Tehran'); ?>>Tehran (IRST)</option> 266 <option value="Asia/Baghdad" <?php selected($timezone, 'Asia/Baghdad'); ?>>Baghdad (AST)</option> 267 <option value="Asia/Jerusalem" <?php selected($timezone, 'Asia/Jerusalem'); ?>>Jerusalem (IST)</option> 268 <option value="Asia/Beirut" <?php selected($timezone, 'Asia/Beirut'); ?>>Beirut (EET)</option> 269 <option value="Asia/Istanbul" <?php selected($timezone, 'Asia/Istanbul'); ?>>Istanbul (TRT)</option> 270 </optgroup> 271 272 <optgroup label="🇦🇺 Australia & Pacific"> 273 <option value="Australia/Sydney" <?php selected($timezone, 'Australia/Sydney'); ?>>Sydney (AEST/AEDT)</option> 274 <option value="Australia/Melbourne" <?php selected($timezone, 'Australia/Melbourne'); ?>>Melbourne (AEST/AEDT)</option> 275 <option value="Australia/Brisbane" <?php selected($timezone, 'Australia/Brisbane'); ?>>Brisbane (AEST)</option> 276 <option value="Australia/Perth" <?php selected($timezone, 'Australia/Perth'); ?>>Perth (AWST)</option> 277 <option value="Australia/Adelaide" <?php selected($timezone, 'Australia/Adelaide'); ?>>Adelaide (ACST/ACDT)</option> 278 <option value="Australia/Darwin" <?php selected($timezone, 'Australia/Darwin'); ?>>Darwin (ACST)</option> 279 <option value="Australia/Hobart" <?php selected($timezone, 'Australia/Hobart'); ?>>Hobart (AEST/AEDT)</option> 280 <option value="Pacific/Auckland" <?php selected($timezone, 'Pacific/Auckland'); ?>>Auckland (NZST/NZDT)</option> 281 <option value="Pacific/Fiji" <?php selected($timezone, 'Pacific/Fiji'); ?>>Fiji (FJT)</option> 282 <option value="Pacific/Tahiti" <?php selected($timezone, 'Pacific/Tahiti'); ?>>Tahiti (TAHT)</option> 283 <option value="Pacific/Guam" <?php selected($timezone, 'Pacific/Guam'); ?>>Guam (ChST)</option> 284 </optgroup> 285 286 <optgroup label="🌎 South America"> 287 <option value="America/Sao_Paulo" <?php selected($timezone, 'America/Sao_Paulo'); ?>>São Paulo (BRT/BRST)</option> 288 <option value="America/Buenos_Aires" <?php selected($timezone, 'America/Buenos_Aires'); ?>>Buenos Aires (ART)</option> 289 <option value="America/Santiago" <?php selected($timezone, 'America/Santiago'); ?>>Santiago (CLT/CLST)</option> 290 <option value="America/Lima" <?php selected($timezone, 'America/Lima'); ?>>Lima (PET)</option> 291 <option value="America/Bogota" <?php selected($timezone, 'America/Bogota'); ?>>Bogotá (COT)</option> 292 <option value="America/Caracas" <?php selected($timezone, 'America/Caracas'); ?>>Caracas (VET)</option> 293 <option value="America/La_Paz" <?php selected($timezone, 'America/La_Paz'); ?>>La Paz (BOT)</option> 294 <option value="America/Montevideo" <?php selected($timezone, 'America/Montevideo'); ?>>Montevideo (UYT)</option> 295 <option value="America/Asuncion" <?php selected($timezone, 'America/Asuncion'); ?>>Asunción (PYT/PYST)</option> 296 <option value="America/Guyana" <?php selected($timezone, 'America/Guyana'); ?>>Georgetown (GYT)</option> 297 </optgroup> 298 299 <optgroup label="🌍 Africa"> 300 <option value="Africa/Cairo" <?php selected($timezone, 'Africa/Cairo'); ?>>Cairo (EET)</option> 301 <option value="Africa/Lagos" <?php selected($timezone, 'Africa/Lagos'); ?>>Lagos (WAT)</option> 302 <option value="Africa/Johannesburg" <?php selected($timezone, 'Africa/Johannesburg'); ?>>Johannesburg (SAST)</option> 303 <option value="Africa/Nairobi" <?php selected($timezone, 'Africa/Nairobi'); ?>>Nairobi (EAT)</option> 304 <option value="Africa/Casablanca" <?php selected($timezone, 'Africa/Casablanca'); ?>>Casablanca (WET)</option> 305 <option value="Africa/Algiers" <?php selected($timezone, 'Africa/Algiers'); ?>>Algiers (CET)</option> 306 <option value="Africa/Tunis" <?php selected($timezone, 'Africa/Tunis'); ?>>Tunis (CET)</option> 307 <option value="Africa/Accra" <?php selected($timezone, 'Africa/Accra'); ?>>Accra (GMT)</option> 308 <option value="Africa/Addis_Ababa" <?php selected($timezone, 'Africa/Addis_Ababa'); ?>>Addis Ababa (EAT)</option> 309 <option value="Africa/Kinshasa" <?php selected($timezone, 'Africa/Kinshasa'); ?>>Kinshasa (WAT)</option> 310 </optgroup> 311 312 <optgroup label="🌐 Other & UTC"> 313 <option value="UTC" <?php selected($timezone, 'UTC'); ?>>UTC (Coordinated Universal Time)</option> 314 <option value="Atlantic/Azores" <?php selected($timezone, 'Atlantic/Azores'); ?>>Azores (AZOT/AZOST)</option> 315 <option value="Atlantic/Cape_Verde" <?php selected($timezone, 'Atlantic/Cape_Verde'); ?>>Cape Verde (CVT)</option> 316 <option value="Indian/Mauritius" <?php selected($timezone, 'Indian/Mauritius'); ?>>Mauritius (MUT)</option> 317 <option value="Indian/Maldives" <?php selected($timezone, 'Indian/Maldives'); ?>>Maldives (MVT)</option> 318 </optgroup> 228 319 </select> 229 <p class="description">This is your station's local timezone for schedule display and calculations. </p>320 <p class="description">This is your station's local timezone for schedule display and calculations. This setting is completely separate from WordPress's global timezone and only affects JOAN schedule displays.</p> 230 321 </td> 231 322 </tr> … … 309 400 } 310 401 311 // Display Options Tab 402 // Display Options Tab - UPDATED with Dark Mode Settings 312 403 function joan_render_display_tab() { 313 404 $show_next_show = get_option('joan_show_next_show', '1'); … … 317 408 $custom_widget_title = get_option('joan_custom_widget_title', 'On Air Now'); 318 409 $jock_only_mode = get_option('joan_jock_only_mode', '0'); 410 411 // NEW: Dark mode settings 412 $dark_mode_setting = get_option('joan_dark_mode', 'auto'); 413 $dark_mode_override = get_option('joan_dark_mode_override', '0'); 319 414 320 415 ?> … … 372 467 </fieldset> 373 468 <p class="description">Widget width range: 200-500px. Custom title appears as default for new widgets.</p> 469 </td> 470 </tr> 471 472 <!-- NEW: Dark Mode Settings --> 473 <tr> 474 <th scope="row">Dark Mode</th> 475 <td> 476 <fieldset> 477 <label> 478 <input type="radio" name="dark_mode" value="auto" <?php checked($dark_mode_setting, 'auto'); ?>> 479 <strong>Auto</strong> - Follow visitor's system preference (recommended) 480 </label> 481 <br> 482 <label> 483 <input type="radio" name="dark_mode" value="light" <?php checked($dark_mode_setting, 'light'); ?>> 484 <strong>Light Mode</strong> - Always use light theme 485 </label> 486 <br> 487 <label> 488 <input type="radio" name="dark_mode" value="dark" <?php checked($dark_mode_setting, 'dark'); ?>> 489 <strong>Dark Mode</strong> - Always use dark theme 490 </label> 491 <br> 492 <label> 493 <input type="radio" name="dark_mode" value="disabled" <?php checked($dark_mode_setting, 'disabled'); ?>> 494 <strong>Disabled</strong> - Never use dark mode (force light theme) 495 </label> 496 <br><br> 497 <label> 498 <input type="checkbox" name="dark_mode_override" <?php checked($dark_mode_override, '1'); ?>> 499 Allow visitors to manually override dark mode preference 500 </label> 501 </fieldset> 502 <p class="description"> 503 <strong>Auto:</strong> Respects visitor's system dark/light mode preference<br> 504 <strong>Light/Dark:</strong> Forces that theme for all visitors<br> 505 <strong>Disabled:</strong> Never shows dark mode even if visitor prefers it<br> 506 <strong>Override:</strong> Shows a toggle button for manual control 507 </p> 374 508 </td> 375 509 </tr> … … 800 934 } 801 935 802 // Handle settings form submission based on tab 936 // Handle settings form submission based on tab - UPDATED with Dark Mode 803 937 function joan_handle_settings_save($tab) { 804 938 switch ($tab) { … … 822 956 update_option('joan_custom_widget_title', sanitize_text_field($_POST['custom_widget_title'])); 823 957 update_option('joan_jock_only_mode', isset($_POST['jock_only_mode']) ? '1' : '0'); 958 959 // NEW: Save dark mode settings 960 $dark_mode_setting = sanitize_text_field($_POST['dark_mode']); 961 if (in_array($dark_mode_setting, ['auto', 'light', 'dark', 'disabled'])) { 962 update_option('joan_dark_mode', $dark_mode_setting); 963 } 964 update_option('joan_dark_mode_override', isset($_POST['dark_mode_override']) ? '1' : '0'); 824 965 break; 825 966 -
joan/trunk/includes/crud.php
r3344195 r3348419 256 256 'time_format' => get_option('joan_time_format', '12'), 257 257 'widget_max_width' => get_option('joan_widget_max_width', '300'), 258 'station_timezone' => get_option('joan_timezone', 'America/New_York') 258 'station_timezone' => get_option('joan_timezone', 'America/New_York'), 259 260 // NEW: Dark mode settings 261 'joan_dark_mode' => get_option('joan_dark_mode', 'auto'), 262 'joan_dark_mode_override' => get_option('joan_dark_mode_override', '0') 259 263 ); 260 264 } … … 386 390 if ($status !== 'active') { 387 391 $class = $status === 'suspended' ? 'notice-warning' : 'notice-error'; 388 $icon = $status === 'suspended' ? '⏸️' : ' 📴';392 $icon = $status === 'suspended' ? '⏸️' : '🔴'; 389 393 $message = $status === 'suspended' 390 394 ? 'JOAN Schedule is currently suspended. Visitors will see a custom message instead of the schedule.' … … 407 411 408 412 if ($status !== 'active') { 409 $icon = $status === 'suspended' ? '⏸️' : ' 📴';413 $icon = $status === 'suspended' ? '⏸️' : '🔴'; 410 414 $title = $icon . ' JOAN: ' . ucfirst($status); 411 415 -
joan/trunk/joan.php
r3345157 r3348419 5 5 * Description: Display your station's current and upcoming on-air schedule in real-time with timezone awareness, Elementor and Visual Composer/WPBakery Page Builder integration support. Your site visitors can keep track of your on air schedule and their favorite shows. 6 6 * Author: G & D Enterprises, Inc. 7 * Version: 6.0. 27 * Version: 6.0.3 8 8 * Author URI: https://www.gandenterprisesinc.com 9 9 * Text Domain: joan … … 17 17 define( 'JOAN_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 18 18 define( 'JOAN_VERSION', '6.0.0' ); 19 20 // Block automatic updates for destructive version 6.0.0 21 add_filter('auto_update_plugin', function($update, $item) { 22 if (isset($item->slug) && $item->slug === 'joan') { 23 $current_version = get_option('joan_plugin_version', '0.0.0'); 24 $new_version = $item->new_version; 25 26 // Block auto-update if jumping from 5.x to 6.x 27 if (version_compare($current_version, '6.0.0', '<') && 28 version_compare($new_version, '6.0.0', '>=')) { 29 return false; // Block automatic update 30 } 31 } 32 return $update; 33 }, 10, 2); 19 34 20 35 // Version detection and migration handling … … 191 206 wp_enqueue_script( 'joan-script', JOAN_PLUGIN_URL . 'assets/js/joan.js', ['jquery'], JOAN_VERSION, true ); 192 207 wp_localize_script('joan-script', 'joan_ajax', [ 193 'ajaxurl' => admin_url('admin-ajax.php'), 194 'nonce' => wp_create_nonce('joan_frontend_nonce'), 195 'settings' => [ 196 'show_next_show' => get_option('joan_show_next_show', '1'), 197 'show_jock_image' => get_option('joan_show_jock_image', '1'), 198 'joan_show_local_time' => get_option('joan_show_local_time', '1'), 199 'joan_allow_timezone_selector' => get_option('joan_allow_timezone_selector', '1'), 200 'time_format' => get_option('joan_time_format', '12'), 201 'widget_max_width' => get_option('joan_widget_max_width', '300') 202 ] 203 ]); 208 'ajaxurl' => admin_url('admin-ajax.php'), 209 'nonce' => wp_create_nonce('joan_frontend_nonce'), 210 'settings' => [ 211 'show_next_show' => get_option('joan_show_next_show', '1'), 212 'show_jock_image' => get_option('joan_show_jock_image', '1'), 213 'joan_show_local_time' => get_option('joan_show_local_time', '1'), 214 'joan_allow_timezone_selector' => get_option('joan_allow_timezone_selector', '1'), 215 'time_format' => get_option('joan_time_format', '12'), 216 'widget_max_width' => get_option('joan_widget_max_width', '300'), 217 'joan_dark_mode' => get_option('joan_dark_mode', 'auto'), 218 'joan_dark_mode_override' => get_option('joan_dark_mode_override', '0') 219 ] 220 ]); 204 221 } 205 222 add_action( 'wp_enqueue_scripts', 'joan_enqueue_assets' ); -
joan/trunk/readme.txt
r3345157 r3348419 1 === Jock On Air Now (JOAN) ===1 === Jock On Air Now (JOAN) === 2 2 Contributors: ganddser 3 3 Donate link: https://gandenterprisesinc.com … … 6 6 Tested up to: 6.8 7 7 Requires PHP: 7.2 8 Stable tag: 6.0. 28 Stable tag: 6.0.3 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 23 23 **🎯 Smart Display System** 24 24 - **NEW**: Intelligent image positioning 25 - **NEW**: Dark mode support with manual toggle and system preference detection 25 26 - **UPDATED**: Customizable widget title centering and custom text options 26 27 - **NEW**: Real-time green/red notification system with visual feedback 27 28 - **NEW**: Auto-timezone detection with visitor override capability 28 29 - **ENHANCED**: Mobile-optimized responsive design for all devices 29 - **ENHANCED**: Clean, professional styling with modern UX 30 - **ENHANCED**: Clean, professional styling with modern UX and accessibility features 30 31 31 32 **📅 Advanced Schedule Management** … … 39 40 - **NEW**: Three powerful shortcodes: `[joan-now]`, `[joan-schedule]`, `[schedule-today]` 40 41 - **NEW**: Real-time AJAX updates with proper error handling 42 - **NEW**: Adaptive dark/light mode for improved viewing comfort 41 43 - **UPDATED**: "What's on today" focused daily schedule view 42 44 - **ENHANCED**: WordPress widgets integration with customization options … … 54 56 - **NEW**: Multisite compatibility with improved performance 55 57 - **NEW**: SEO-friendly markup throughout 58 - **NEW**: Cross-browser dark mode compatibility 56 59 - **IMPROVED**: Cross-browser compatibility and WordPress optimization 57 60 - **NEW**: Button loading states and notification positioning … … 81 84 82 85 = How does the smart image positioning work? = 83 JOAN automatically positions images based on their dimensions: 84 - **100-250px wide**: Displayed to the right of show information 85 - **250-300px wide**: Centered below show details, above "Up Next" 86 - **Other sizes**: Standard placement with 300px maximum width 86 JOAN automatically positions images 87 - **Centered below show details, above "Up Next" 88 89 = How does the dark mode feature work? = 90 JOAN includes intelligent dark mode support with multiple options: 91 - **Automatic Detection**: Respects your visitors' system preferences (dark/light mode) 92 - **Manual Toggle**: Visitors can manually switch between dark and light modes using the toggle button 93 - **Settings Storage**: User preferences are remembered across sessions 94 - **Accessibility**: Dark mode maintains full accessibility and contrast standards 87 95 88 96 = Can I customize the widget title and centering? = … … 101 109 = Can visitors see times in their local timezone? = 102 110 Yes! JOAN detects visitor timezones automatically and provides a dropdown for manual override. All times display in the visitor's preferred timezone. 111 112 = Does JOAN's timezone setting affect my WordPress site's timezone? = 113 No, JOAN's timezone setting is completely separate from WordPress's global timezone. JOAN stores its timezone as `joan_timezone` and only uses it for radio schedule displays and calculations. Your WordPress site's main timezone setting (found in Settings > General) remains unchanged and continues to control post timestamps, comments, and other WordPress core functions. You can safely set JOAN to use a different timezone than your WordPress site. 103 114 104 115 = My schedule from version 5.x isn't showing = … … 126 137 9. **Custom CSS Editor** - Use your own CSS to style JOAN widgets and schedules 127 138 10.**Help Tab** - We've added a help tab so you don't have to leave the plugin to get basic assitance for JOAN 139 11. **Dark Mode Display** - Automatic dark mode support with manual toggle for optimal viewing comfort 128 140 129 141 == Changelog == 142 143 = 6.0.3 - 2025-08-21 = 144 **🌙 NEW DARK MODE FEATURES** 145 * **NEW**: Comprehensive dark mode support with manual toggle button 146 * **NEW**: Automatic system preference detection (respects user's OS settings) 147 * **NEW**: User preference storage for consistent experience across sessions 148 * **NEW**: Dark mode compatibility for all widgets, schedules, and admin interfaces 149 * **IMPROVED**: Enhanced accessibility with high contrast support in dark mode 150 * **IMPROVED**: Mobile-responsive dark mode toggle positioning 151 * **ADDED**: Fallback support for browsers without JavaScript 152 * **ENHANCED**: Cross-browser dark mode compatibility testing 153 154 **🔧 TECHNICAL IMPROVEMENTS** 155 * **IMPROVED**: CSS optimization for better performance 156 * **ENHANCED**: WordPress coding standards compliance 157 * **FIXED**: Minor display issues in certain theme combinations 158 * **UPDATED**: Accessibility improvements for screen readers 130 159 131 160 = 6.0.0 - 2025-08-06 = … … 187 216 == Upgrade Notice == 188 217 218 = 6.0.3 = 219 Recommended update adding comprehensive dark mode support with automatic system preference detection and manual toggle. Includes enhanced accessibility features and cross-browser compatibility improvements. This update maintains full backward compatibility with version 6.0.x settings and schedules. 220 189 221 = 6.0.0 = 190 222 **⚠️ CRITICAL UPGRADE NOTICE**: This is a complete plugin redesign with major new features including smart image positioning, widget customization, advertisement management, and enhanced display options. **BACKUP YOUR CURRENT SCHEDULE** before upgrading! Schedules from versions 5.9.0 and below cannot be automatically imported and will need to be re-entered. This version includes major improvements in user experience, functionality, modern coding standards, and professional broadcasting capabilities. … … 210 242 * **24-Hour Format Support** - Professional time display options 211 243 * **Custom Styling Options** - Brand colors, fonts, and layout customization 244 * **Premium Dark Mode Themes** - Additional dark mode styling options and custom color schemes 212 245 213 246 **⚡ Professional Tools**
Note: See TracChangeset
for help on using the changeset viewer.