Plugin Directory

Changeset 3348419


Ignore:
Timestamp:
08/22/2025 03:52:32 AM (7 months ago)
Author:
ganddser
Message:

Fixed darkmode detection and added override options.
Disabled automatic update to protect user schedule as previous versions from 5.9.0 cannot be imported from the old database. This is a complete redesign of the plugin.

Location:
joan/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • joan/trunk/assets/css/joan.css

    r3343873 r3348419  
    151151    font-family: "Courier New", monospace;
    152152    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;
    153173}
    154174
     
    464484}
    465485
     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
    4661163/* Responsive design */
    4671164@media (max-width: 768px) {
     
    4781175    .joan-thumb-large {
    4791176        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;
    4801184    }
    4811185   
     
    5711275   
    5721276    .joan-timezone-selector,
    573     .joan-schedule-controls {
     1277    .joan-schedule-controls,
     1278    .joan-dark-mode-toggle {
    5741279        display: none;
    5751280    }
     
    5941299}
    5951300
    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 
    6901301/* Accessibility improvements */
    6911302.joan-now-playing:focus-within {
     
    7001311}
    7011312
    702 .joan-schedule-controls select:focus {
     1313.joan-schedule-controls select:focus,
     1314.joan-dark-mode-toggle:focus {
    7031315    outline: 2px solid #0073aa;
    7041316    outline-offset: 1px;
     
    7081320@media (prefers-reduced-motion: reduce) {
    7091321    .joan-thumb,
    710     .current-show a {
     1322    .current-show a,
     1323    .joan-dark-mode-toggle {
    7111324        transition: none;
    7121325    }
     
    7401353        border: 2px solid #000;
    7411354    }
    742 }
     1355   
     1356    .joan-dark-mode-toggle {
     1357        border-width: 2px;
     1358    }
     1359}
  • joan/trunk/assets/js/joan.js

    r3343873 r3348419  
    1212    const CACHE_DURATION = 60000; // 1 minute cache
    1313
     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   
    14185    function getPreferredTimezone() {
    15186        return localStorage.getItem("joan_timezone") || Intl.DateTimeFormat().resolvedOptions().timeZone;
     
    258429            statusIcon = '<span class="joan-status-icon">⏸️</span>';
    259430        } else if (status === 'off_air') {
    260             statusIcon = '<span class="joan-status-icon">📴</span>';
     431            statusIcon = '<span class="joan-status-icon">🔴</span>';
    261432        }
    262433       
     
    296467        }
    297468
     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
    298477        // Check for off-air conditions or non-active status
    299478        if (response.schedule_status !== 'active' || !response.current_show) {
     
    304483            if (!jockOnlyMode && response.schedule_status !== 'off_air' && allowTimezoneSelector) {
    305484                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);
    306492            }
    307493           
     
    335521                    jockOnlyHtml += imageHtml;
    336522                   
    337                     // DISABLED: Add current time display only if timezone selector is not enabled
    338                     // 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                    
    343523                    $container.html(jockOnlyHtml);
    344524                   
     
    347527                    if (allowTimezoneSelector) {
    348528                        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);
    349536                    }
    350537                });
     
    355542                </div>`;
    356543               
    357                 // DISABLED: Add current time display only if timezone selector is not enabled
    358                 // 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                
    363544                $container.html(html);
    364545               
     
    367548                if (allowTimezoneSelector) {
    368549                    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);
    369557                }
    370558            }
     
    398586            }
    399587
    400             // DISABLED: Add current time display only if timezone selector is not enabled
    401             // 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 
    406588            $container.html(html);
    407589           
     
    410592            if (allowTimezoneSelector) {
    411593                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);
    412601            }
    413602        });
     
    435624        }
    436625
    437         // Find all joan-now-playing containers
    438         //CodeSig: sgketg
     626        //CodeSig: sgketg
    439627        $('.joan-now-playing').each(function() {
    440628            const $container = $(this);
     
    627815            const uniqueId = 'joan-day-filter-' + Math.random().toString(36).substr(2, 9);
    628816           
    629             // Add day filter controls
     817            // Add day filter controls - DO NOT apply dark mode classes during creation
    630818            const $controls = $(`
    631819                <div class="joan-schedule-controls">
     
    649837            const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    650838            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'
    658846            };
    659847           
     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           
    660858            days.forEach(day => {
    661859                const dayShows = dayGroups[day];
     
    663861                const dayClass = day.toLowerCase();
    664862               
    665                 // Create day section
     863                // Create day section - DO NOT apply dark mode classes during creation
    666864                const $daySection = $(`
    667865                    <div class="joan-schedule-day-section" data-day="${day}">
    668                         <div class="joan-schedule-day-header joan-${dayClass}-header">
    669                             ${emoji} ${day} Schedule
    670                         </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            `);
    673871               
    674872                if (dayShows.length > 0) {
    675                     // Create table for this day
     873                    // Create table for this day - DO NOT apply dark mode classes during creation
    676874                    const $dayTable = $(`
    677875                        <table class="joan-schedule-day-table joan-${dayClass}-table">
     
    702900                    $daySection.append($dayTable);
    703901                } else {
    704                     // No shows for this day
     902                    // No shows for this day - DO NOT apply dark mode classes during creation
    705903                    $daySection.append(`
    706904                        <div class="joan-schedule-empty-day">
     
    716914            $table.replaceWith($container);
    717915           
     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           
    718933            // Add filter functionality using class instead of ID
    719934            $container.find('.joan-day-filter').on('change', function() {
     
    737952    const initialDelay = typeof rocket_loader !== 'undefined' || window.rocketLoaderDelay ? 2000 : 500;
    738953    setTimeout(() => {
     954        // Initialize dark mode first
     955        initializeDarkMode();
     956       
    739957        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);
    741965    }, initialDelay);
    742966   
     
    772996    window.joanSeparateScheduleByDays = separateScheduleByDays;
    773997   
     998    // Make dark mode functions globally available
     999    window.joanDarkMode = {
     1000        initialize: initializeDarkMode,
     1001        toggle: toggleDarkMode,
     1002        apply: applyDarkMode
     1003    };
     1004   
    7741005    // Add manual refresh functionality to error messages
    7751006    $(document).on('click', '.joan-error a[onclick*="reload"]', function(e) {
     
    8261057                return;
    8271058            }
    828 
    829             // DISABLED: This code block is now unreachable but preserved for potential future use
    830             // Show the element if it should be visible
    831             // 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;
    8541059        });
    8551060    }
     
    9751180                        window.joanSeparateScheduleByDays();
    9761181                    }
     1182                    if (window.joanDarkMode) {
     1183                        window.joanDarkMode.initialize();
     1184                    }
    9771185                }, 500);
    9781186            });
  • joan/trunk/includes/admin-menu.php

    r3344195 r3348419  
    180180       
    181181        <div class="joan-settings-section">
    182             <h2>🕐 Time & Timezone Settings</h2>
     182            <h2>🕕 Time & Timezone Settings</h2>
    183183            <p class="description">Configure how times are displayed throughout your radio station schedule.</p>
    184184           
     
    206206                    <td>
    207207                        <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>
    223221                           
    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>
    228319                        </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>
    230321                    </td>
    231322                </tr>
     
    309400}
    310401
    311 // Display Options Tab
     402// Display Options Tab - UPDATED with Dark Mode Settings
    312403function joan_render_display_tab() {
    313404    $show_next_show = get_option('joan_show_next_show', '1');
     
    317408    $custom_widget_title = get_option('joan_custom_widget_title', 'On Air Now');
    318409    $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');
    319414   
    320415    ?>
     
    372467                        </fieldset>
    373468                        <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>
    374508                    </td>
    375509                </tr>
     
    800934}
    801935
    802 // Handle settings form submission based on tab
     936// Handle settings form submission based on tab - UPDATED with Dark Mode
    803937function joan_handle_settings_save($tab) {
    804938    switch ($tab) {
     
    822956            update_option('joan_custom_widget_title', sanitize_text_field($_POST['custom_widget_title']));
    823957            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');
    824965            break;
    825966           
  • joan/trunk/includes/crud.php

    r3344195 r3348419  
    256256        'time_format' => get_option('joan_time_format', '12'),
    257257        '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')
    259263    );
    260264}
     
    386390    if ($status !== 'active') {
    387391        $class = $status === 'suspended' ? 'notice-warning' : 'notice-error';
    388         $icon = $status === 'suspended' ? '⏸️' : '📴';
     392        $icon = $status === 'suspended' ? '⏸️' : '🔴';
    389393        $message = $status === 'suspended'
    390394            ? 'JOAN Schedule is currently suspended. Visitors will see a custom message instead of the schedule.'
     
    407411   
    408412    if ($status !== 'active') {
    409         $icon = $status === 'suspended' ? '⏸️' : '📴';
     413        $icon = $status === 'suspended' ? '⏸️' : '🔴';
    410414        $title = $icon . ' JOAN: ' . ucfirst($status);
    411415       
  • joan/trunk/joan.php

    r3345157 r3348419  
    55 * 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.
    66 * Author: G &amp; D Enterprises, Inc.
    7  * Version: 6.0.2
     7 * Version: 6.0.3
    88 * Author URI: https://www.gandenterprisesinc.com
    99 * Text Domain: joan
     
    1717define( 'JOAN_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    1818define( 'JOAN_VERSION', '6.0.0' );
     19
     20// Block automatic updates for destructive version 6.0.0
     21add_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);
    1934
    2035// Version detection and migration handling
     
    191206    wp_enqueue_script( 'joan-script', JOAN_PLUGIN_URL . 'assets/js/joan.js', ['jquery'], JOAN_VERSION, true );
    192207    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    ]);
    204221}
    205222add_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) ===
    22Contributors: ganddser
    33Donate link: https://gandenterprisesinc.com 
     
    66Tested up to: 6.8 
    77Requires PHP: 7.2 
    8 Stable tag: 6.0.2 
     8Stable tag: 6.0.3 
    99License: GPLv2 or later 
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html 
     
    2323**🎯 Smart Display System**
    2424- **NEW**: Intelligent image positioning
     25- **NEW**: Dark mode support with manual toggle and system preference detection
    2526- **UPDATED**: Customizable widget title centering and custom text options
    2627- **NEW**: Real-time green/red notification system with visual feedback
    2728- **NEW**: Auto-timezone detection with visitor override capability
    2829- **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
    3031
    3132**📅 Advanced Schedule Management**
     
    3940- **NEW**: Three powerful shortcodes: `[joan-now]`, `[joan-schedule]`, `[schedule-today]`
    4041- **NEW**: Real-time AJAX updates with proper error handling
     42- **NEW**: Adaptive dark/light mode for improved viewing comfort
    4143- **UPDATED**: "What's on today" focused daily schedule view
    4244- **ENHANCED**: WordPress widgets integration with customization options
     
    5456- **NEW**: Multisite compatibility with improved performance
    5557- **NEW**: SEO-friendly markup throughout
     58- **NEW**: Cross-browser dark mode compatibility
    5659- **IMPROVED**: Cross-browser compatibility and WordPress optimization
    5760- **NEW**: Button loading states and notification positioning
     
    8184
    8285= 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
     86JOAN automatically positions images
     87- **Centered below show details, above "Up Next"
     88
     89= How does the dark mode feature work? =
     90JOAN 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
    8795
    8896= Can I customize the widget title and centering? =
     
    101109= Can visitors see times in their local timezone? = 
    102110Yes! 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? =
     113No, 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.
    103114
    104115= My schedule from version 5.x isn't showing = 
     
    1261379. **Custom CSS Editor** - Use your own CSS to style JOAN widgets and schedules
    12713810.**Help Tab** - We've added a help tab so you don't have to leave the plugin to get basic assitance for JOAN
     13911. **Dark Mode Display** - Automatic dark mode support with manual toggle for optimal viewing comfort
    128140
    129141== 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
    130159
    131160= 6.0.0 - 2025-08-06 =
     
    187216== Upgrade Notice ==
    188217
     218= 6.0.3 =
     219Recommended 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
    189221= 6.0.0 =
    190222**⚠️ 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.
     
    210242* **24-Hour Format Support** - Professional time display options
    211243* **Custom Styling Options** - Brand colors, fonts, and layout customization
     244* **Premium Dark Mode Themes** - Additional dark mode styling options and custom color schemes
    212245
    213246**⚡ Professional Tools**
Note: See TracChangeset for help on using the changeset viewer.