Skip to content

Commit c797a8d

Browse files
committed
feat: simple light mode
1 parent 61638c3 commit c797a8d

File tree

8 files changed

+299
-0
lines changed

8 files changed

+299
-0
lines changed

_includes/header.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
{% include latest-post.html %}
66
<a href="/clients/" class="tab{% if page.url == '/clients/' %} current{% endif %}"><span
77
class="tab-longname">Compare </span>WebViews</a>
8+
<button type="button" id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" aria-live="polite">
9+
<span class="theme-toggle-icon" aria-hidden="true">☀️</span>
10+
<span class="visually-hidden">Current theme: Light mode</span>
11+
</button>
812
</div>
913
<section class="search-wrapper">
1014
{% include search.html %}

_js/_theme.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
class Theme {
2+
3+
constructor() {
4+
this.button = document.getElementById('theme-toggle');
5+
this.darkModeIcon = '🌙';
6+
this.lightModeIcon = '☀️';
7+
8+
if (this.button) {
9+
this.init();
10+
}
11+
}
12+
13+
init() {
14+
// Load saved theme preference
15+
this.loadTheme();
16+
17+
// Add click event to toggle button
18+
this.button.addEventListener('click', () => {
19+
this.toggle();
20+
});
21+
22+
// Add keyboard support
23+
this.button.addEventListener('keydown', (e) => {
24+
// Support Enter and Space keys
25+
if (e.key === 'Enter' || e.key === ' ') {
26+
e.preventDefault();
27+
this.toggle();
28+
}
29+
});
30+
}
31+
32+
loadTheme() {
33+
const savedTheme = localStorage.getItem('theme');
34+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
35+
36+
// Apply light mode if saved, otherwise use system preference
37+
if (savedTheme === 'light' || (savedTheme === null && !prefersDark)) {
38+
this.setLightMode(false);
39+
} else {
40+
this.setDarkMode(false);
41+
}
42+
}
43+
44+
toggle() {
45+
if (document.body.classList.contains('light-mode')) {
46+
this.setDarkMode(true);
47+
localStorage.setItem('theme', 'dark');
48+
} else {
49+
this.setLightMode(true);
50+
localStorage.setItem('theme', 'light');
51+
}
52+
}
53+
54+
setLightMode(announce = true) {
55+
document.body.classList.add('light-mode');
56+
this.updateIcon(this.darkModeIcon);
57+
this.updateAriaLabel('Switch to dark mode', 'Light mode');
58+
59+
if (announce) {
60+
this.announceChange('Light mode enabled');
61+
}
62+
}
63+
64+
setDarkMode(announce = true) {
65+
document.body.classList.remove('light-mode');
66+
this.updateIcon(this.lightModeIcon);
67+
this.updateAriaLabel('Switch to light mode', 'Dark mode');
68+
69+
if (announce) {
70+
this.announceChange('Dark mode enabled');
71+
}
72+
}
73+
74+
updateIcon(icon) {
75+
const iconElement = this.button.querySelector('.theme-toggle-icon');
76+
if (iconElement) {
77+
iconElement.textContent = icon;
78+
}
79+
}
80+
81+
updateAriaLabel(label, currentTheme) {
82+
this.button.setAttribute('aria-label', label);
83+
84+
const visuallyHiddenText = this.button.querySelector('.visually-hidden');
85+
if (visuallyHiddenText) {
86+
visuallyHiddenText.textContent = `Current theme: ${currentTheme}`;
87+
}
88+
}
89+
90+
announceChange(message) {
91+
// Create a temporary live region for screen readers
92+
const announcement = document.createElement('div');
93+
announcement.setAttribute('role', 'status');
94+
announcement.setAttribute('aria-live', 'polite');
95+
announcement.classList.add('visually-hidden');
96+
announcement.textContent = message;
97+
98+
document.body.appendChild(announcement);
99+
100+
// Remove after announcement
101+
setTimeout(() => {
102+
document.body.removeChild(announcement);
103+
}, 1000);
104+
}
105+
}

_js/caniemail.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
layout: null
33
permalink: "/assets/js/caniwebview.js"
44
---
5+
{% include_relative _theme.js %}
56
{% include_relative _search.js %}
67
{% include_relative _settings.js %}
78
{% include_relative _filters.js %}
@@ -11,6 +12,7 @@ permalink: "/assets/js/caniwebview.js"
1112
class Caniwebview {
1213

1314
constructor() {
15+
this.theme = new Theme();
1416
this.search = new Search();
1517
this.settings = new Settings();
1618
this.filters = new Filters();

_sass/_header.scss

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,69 @@ header > .search-wrapper {
152152
top: 0;
153153
bottom: 0;
154154
}
155+
}
156+
157+
// Theme toggle button
158+
.theme-toggle {
159+
display: flex;
160+
align-items: center;
161+
justify-content: center;
162+
margin-left: rem(8px);
163+
padding: 0.5em;
164+
min-width: rem(40px);
165+
min-height: rem(40px);
166+
background-color: rgba(255, 255, 255, 0.1);
167+
border: none;
168+
border-radius: rem(4px);
169+
cursor: pointer;
170+
color: #fff;
171+
font-size: rem(20px);
172+
transition: background-color 0.2s, transform 0.1s;
173+
174+
&:hover {
175+
background-color: $color-blue-dark;
176+
}
177+
178+
&:focus {
179+
background-color: $color-blue-dark;
180+
outline: 2px solid #fff;
181+
outline-offset: 2px;
182+
}
183+
184+
&:active {
185+
transform: scale(0.95);
186+
}
187+
188+
// High contrast mode support
189+
@media (prefers-contrast: high) {
190+
border: 2px solid currentColor;
191+
}
192+
193+
// Reduced motion support
194+
@media (prefers-reduced-motion: reduce) {
195+
transition: none;
196+
197+
&:active {
198+
transform: none;
199+
}
200+
}
201+
202+
.theme-toggle-icon {
203+
display: inline-block;
204+
line-height: 1;
205+
pointer-events: none;
206+
}
207+
208+
body.light-mode & {
209+
color: #333;
210+
211+
&:hover {
212+
background-color: rgba(0, 0, 0, 0.2);
213+
}
214+
215+
&:focus {
216+
background-color: rgba(0, 0, 0, 0.2);
217+
outline-color: #333;
218+
}
219+
}
155220
}

_sass/_reset.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@
66
box-sizing: border-box;
77
}
88

9+
// Visually hidden utility class for screen readers
10+
.visually-hidden {
11+
position: absolute;
12+
width: 1px;
13+
height: 1px;
14+
margin: -1px;
15+
padding: 0;
16+
overflow: hidden;
17+
clip: rect(0, 0, 0, 0);
18+
white-space: nowrap;
19+
border-width: 0;
20+
}
21+
922
:root {
1023
color-scheme: dark;
1124
}
@@ -23,6 +36,12 @@ body {
2336
color: #b1b1b3;
2437
background: $color-background;
2538
scroll-behavior: smooth;
39+
40+
&.light-mode {
41+
color-scheme: light;
42+
color: $color-text-light;
43+
background: $color-background-light;
44+
}
2645
}
2746

2847
a:active,

_sass/_theme.scss

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Light mode theme styles
2+
body.light-mode {
3+
4+
// Links
5+
a {
6+
color: $color-blue;
7+
8+
&:hover {
9+
color: $color-blue-dark;
10+
}
11+
}
12+
13+
// Code blocks
14+
p code {
15+
background-color: rgba(0, 0, 0, 0.05);
16+
color: $color-text-light;
17+
}
18+
19+
// Focus states
20+
a:active,
21+
a:focus {
22+
outline: 1px dotted rgba(0, 0, 0, 0.5);
23+
}
24+
25+
a:focus:not(:active) {
26+
outline: rem(3px) solid #333;
27+
}
28+
29+
// Feature cards and containers
30+
.feature,
31+
.post,
32+
.client {
33+
background: $feature-color-light;
34+
border: 1px solid #e0e0e0;
35+
}
36+
37+
// Text colors
38+
h1, h2, h3, h4, h5, h6 {
39+
color: $color-text-light;
40+
}
41+
42+
// Tables
43+
table {
44+
background: $feature-color-light;
45+
border-color: #e0e0e0;
46+
47+
th, td {
48+
border-color: #e0e0e0;
49+
}
50+
51+
thead {
52+
background: #f0f0f0;
53+
}
54+
}
55+
56+
// Header
57+
.header {
58+
background: $feature-color-light;
59+
border-bottom: 1px solid #e0e0e0;
60+
}
61+
62+
// Footer
63+
.footer {
64+
background: $feature-color-light;
65+
border-top: 1px solid #e0e0e0;
66+
}
67+
68+
// Search
69+
.search-input {
70+
background: $feature-color-light;
71+
border: 1px solid #e0e0e0;
72+
color: $color-text-light;
73+
74+
&::placeholder {
75+
color: $color-text-secondary-light;
76+
}
77+
}
78+
79+
// Lists
80+
.list-item {
81+
background: $feature-color-light;
82+
border: 1px solid #e0e0e0;
83+
}
84+
85+
.main {
86+
background: $feature-color-light;
87+
}
88+
89+
// Tabs in light mode
90+
.tab {
91+
&.current,
92+
&:not(.current):focus,
93+
&:not(.current):hover {
94+
color: #fff;
95+
}
96+
}
97+
}

_sass/_variables.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ $color-yellow: #fbd91b;
1919
$padding-mobile: 1rem;
2020
$padding-desktop: 4rem;
2121
$width-mobile: 900px;
22+
23+
// Light mode colors
24+
$color-background-light: #f5f5f5;
25+
$feature-color-light: #ffffff;
26+
$color-text-light: #333333;
27+
$color-text-secondary-light: #666666;

_sass/main.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@import "_settings";
1313
@import "_compare";
1414
@import "_tags";
15+
@import "_theme";
1516

1617
@import "_pages/_home";
1718
@import "_pages/_features";

0 commit comments

Comments
 (0)