bilingual-jekyll-resume-theme 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bilingual-jekyll-resume-theme might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a160407a794060fef19ecf9df069af68dd152f04903fca2df9be92e6739a0e1
4
- data.tar.gz: 6d8df24a03c4031c201789dafd68785e83841e15c61c096ded7981e431957008
3
+ metadata.gz: 6e8574055be713050db1441e0981a7b13b74baf53ea093a39f25879a41ae059b
4
+ data.tar.gz: 13d1c501de2bc80f4873f07f352d9d592d01e6ee63ed9d359bfc99a8d62f4477
5
5
  SHA512:
6
- metadata.gz: fd7ac5fa5738962bf6a1a2f52b0d76809b7d7ceb195b8e16b6d06ea6767b3e8230520f940654cfb33c09d8b56cfca1dfe552fd7707cde23ce46a6ea97f14abd6
7
- data.tar.gz: e9acabde4734416d1350a24a0aab3356da47bec4f6f37c56752760a667a3387d2eaa2aab57605f35d352a9ef9e25b2a249a7366050b1beacd998f5d5a086a0d8
6
+ metadata.gz: fa85fa7fd0aa1d84fa61e7b59f5ad3d4927c22bde26f4e1cc77503ccfea4fdfd50c8a05e15ce0417b06b8750d5dc40bdefe6271fee76bd92b3dbedc83430ada6
7
+ data.tar.gz: 1a40a6c189b45e7ebd34683da30c9daf720ed342b6662a3f2c33bfd9dce22869daffd133b604791b2aaaa5ff15b267d6a0f38e4d50e989b91c65c6c71ff1fadf
data/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ ## [0.6.0] - 2026-08-21
8
+
9
+ ### Added
10
+ * Add CLAUDE.md for AI coding assistant guidance and project documentation ([`d68bb03`](https://github.com/kmutahar/bilingual-jekyll-resume-theme/commit/d68bb031043eb88dc7bc6eaaa30a00867f7f7afc))
11
+
12
+
13
+ ### Changed
14
+ * Add dark mode toggle with system preference detection ([`2dcb813`](https://github.com/kmutahar/bilingual-jekyll-resume-theme/commit/2dcb8135817d05f97d8705ea09bfb326d244cd3d))
15
+
16
+ * Enhance CLAUDE.md with comprehensive project documentation and guidance for AI assistants ([`b3facba`](https://github.com/kmutahar/bilingual-jekyll-resume-theme/commit/b3facba6cac2caf853d3f70243dd8a9a2f87ab53))
17
+
18
+ * Revise SECURITY.md with version support and monitoring ([`34c78a0`](https://github.com/kmutahar/bilingual-jekyll-resume-theme/commit/34c78a08ee5e96fd0667b9896c07f6e18e380dd0))
19
+
20
+ * Potential fix for code scanning alert no. 1: Workflow does not contain permissions ([`915f9e7`](https://github.com/kmutahar/bilingual-jekyll-resume-theme/commit/915f9e77982a962e3eda844263d65b5a4ffb849b))
21
+
7
22
  ## [0.5.2] - 2026-05-09
8
23
 
9
24
  ### Added
@@ -143,7 +158,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
143
158
 
144
159
  * Initial commit (New Theme Template) ([`00af662`](https://github.com/kmutahar/bilingual-jekyll-resume-theme/commit/00af6628dfec7aefe0ef7d7083bf98c9713a5ffd))
145
160
 
146
- [0.5.2]: https://github.com/kmutahar/bilingual-jekyll-resume-theme/compare/v0.5.1...0.5.2
161
+ [0.6.0]: https://github.com/kmutahar/bilingual-jekyll-resume-theme/compare/v0.5.2...v0.6.0
162
+ [0.5.2]: https://github.com/kmutahar/bilingual-jekyll-resume-theme/compare/v0.5.1...v0.5.2
147
163
  [0.5.1]: https://github.com/kmutahar/bilingual-jekyll-resume-theme/compare/v0.5.0...v0.5.1
148
164
  [0.5.0]: https://github.com/kmutahar/bilingual-jekyll-resume-theme/compare/v0.4.0...v0.5.0
149
165
  [0.4.0]: https://github.com/kmutahar/bilingual-jekyll-resume-theme/compare/v0.3.1...v0.4.0
@@ -0,0 +1,177 @@
1
+ <!-- Dark Mode Toggle Button (Two-state: System Default vs Pinned Override) -->
2
+ <button
3
+ id="dark-mode-toggle"
4
+ class="dark-mode-toggle no-print"
5
+ type="button"
6
+ aria-label="Switch to dark mode"
7
+ title="Switch to dark mode"
8
+ aria-pressed="false"
9
+ data-pinned="false"
10
+ >
11
+ <!-- Sun Icon: Visible in dark mode / indicates switching to light mode -->
12
+ <svg
13
+ class="dark-mode-icon dark-mode-icon-sun"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ width="20"
16
+ height="20"
17
+ viewBox="0 0 24 24"
18
+ fill="none"
19
+ stroke="currentColor"
20
+ stroke-width="2"
21
+ stroke-linecap="round"
22
+ stroke-linejoin="round"
23
+ aria-hidden="true"
24
+ focusable="false"
25
+ >
26
+ <circle cx="12" cy="12" r="5"></circle>
27
+ <line x1="12" y1="1" x2="12" y2="3"></line>
28
+ <line x1="12" y1="21" x2="12" y2="23"></line>
29
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
30
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
31
+ <line x1="1" y1="12" x2="3" y2="12"></line>
32
+ <line x1="21" y1="12" x2="23" y2="12"></line>
33
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
34
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
35
+ </svg>
36
+
37
+ <!-- Moon Icon: Visible in light mode / indicates switching to dark mode -->
38
+ <svg
39
+ class="dark-mode-icon dark-mode-icon-moon"
40
+ xmlns="http://www.w3.org/2000/svg"
41
+ width="20"
42
+ height="20"
43
+ viewBox="0 0 24 24"
44
+ fill="none"
45
+ stroke="currentColor"
46
+ stroke-width="2"
47
+ stroke-linecap="round"
48
+ stroke-linejoin="round"
49
+ aria-hidden="true"
50
+ focusable="false"
51
+ >
52
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
53
+ </svg>
54
+ </button>
55
+
56
+ <script>
57
+ (function () {
58
+ var toggleBtn = document.getElementById('dark-mode-toggle');
59
+ if (!toggleBtn) return;
60
+
61
+ var metaTheme = document.querySelector('meta[name="color-scheme"]');
62
+
63
+ function getSystemTheme() {
64
+ if (window.matchMedia) {
65
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
66
+ }
67
+ return 'light';
68
+ }
69
+
70
+ function getSavedTheme() {
71
+ try {
72
+ var val = localStorage.getItem('color-scheme');
73
+ if (val === 'dark' || val === 'light') {
74
+ return val;
75
+ }
76
+ return null;
77
+ } catch (e) {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ function isArabicRTL() {
83
+ var docEl = document.documentElement;
84
+ var dir = docEl.getAttribute('dir');
85
+ var lang = docEl.getAttribute('lang') || docEl.lang;
86
+ return dir === 'rtl' || lang === 'ar';
87
+ }
88
+
89
+ function syncState() {
90
+ var saved = getSavedTheme();
91
+ if (saved !== null) {
92
+ document.documentElement.setAttribute('data-color-scheme', saved);
93
+ document.documentElement.setAttribute('data-theme', saved);
94
+ if (metaTheme) {
95
+ metaTheme.setAttribute('content', saved);
96
+ }
97
+ } else {
98
+ document.documentElement.removeAttribute('data-color-scheme');
99
+ document.documentElement.removeAttribute('data-theme');
100
+ if (metaTheme) {
101
+ metaTheme.setAttribute('content', 'light dark');
102
+ }
103
+ }
104
+ }
105
+
106
+ function updateUI() {
107
+ var saved = getSavedTheme();
108
+ var isPinned = (saved !== null);
109
+ var effectiveTheme = isPinned ? saved : getSystemTheme();
110
+ var isRTL = isArabicRTL();
111
+ var label = '';
112
+
113
+ toggleBtn.setAttribute('data-pinned', isPinned ? 'true' : 'false');
114
+ toggleBtn.setAttribute('aria-pressed', isPinned ? 'true' : 'false');
115
+ toggleBtn.setAttribute('data-current-theme', effectiveTheme);
116
+
117
+ if (isPinned) {
118
+ label = isRTL
119
+ ? 'إعادة ضبط المظهر إلى الوضع التلقائي للنظام'
120
+ : 'Reset to system theme';
121
+ } else {
122
+ var nextTheme = (effectiveTheme === 'dark') ? 'light' : 'dark';
123
+ if (isRTL) {
124
+ label = (nextTheme === 'dark')
125
+ ? 'تبديل إلى الوضع الداكن'
126
+ : 'تبديل إلى الوضع الفاتح';
127
+ } else {
128
+ label = (nextTheme === 'dark')
129
+ ? 'Switch to dark mode'
130
+ : 'Switch to light mode';
131
+ }
132
+ }
133
+
134
+ toggleBtn.setAttribute('aria-label', label);
135
+ toggleBtn.setAttribute('title', label);
136
+ }
137
+
138
+ function toggleTheme() {
139
+ var saved = getSavedTheme();
140
+ if (saved !== null) {
141
+ // Currently pinned -> reset to system default
142
+ try {
143
+ localStorage.removeItem('color-scheme');
144
+ } catch (e) {}
145
+ syncState();
146
+ } else {
147
+ // Currently unpinned -> pin opposite of current system setting
148
+ var systemTheme = getSystemTheme();
149
+ var targetTheme = (systemTheme === 'dark') ? 'light' : 'dark';
150
+ try {
151
+ localStorage.setItem('color-scheme', targetTheme);
152
+ } catch (e) {}
153
+ syncState();
154
+ }
155
+ updateUI();
156
+ }
157
+
158
+ toggleBtn.addEventListener('click', toggleTheme);
159
+
160
+ if (window.matchMedia) {
161
+ var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
162
+ var mqListener = function () {
163
+ if (!getSavedTheme()) {
164
+ updateUI();
165
+ }
166
+ };
167
+ if (darkQuery.addEventListener) {
168
+ darkQuery.addEventListener('change', mqListener);
169
+ } else if (darkQuery.addListener) {
170
+ darkQuery.addListener(mqListener);
171
+ }
172
+ }
173
+
174
+ syncState();
175
+ updateUI();
176
+ })();
177
+ </script>
@@ -1,6 +1,24 @@
1
1
  <meta charset="UTF-8">
2
2
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
3
3
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, viewport-fit=cover">
4
+ <meta name="color-scheme" content="light dark">
5
+ <script>
6
+ (function() {
7
+ try {
8
+ var saved = localStorage.getItem('color-scheme');
9
+ if (saved === 'dark' || saved === 'light') {
10
+ document.documentElement.setAttribute('data-color-scheme', saved);
11
+ document.documentElement.setAttribute('data-theme', saved);
12
+ var meta = document.querySelector('meta[name="color-scheme"]');
13
+ if (meta) {
14
+ meta.setAttribute('content', saved);
15
+ }
16
+ }
17
+ } catch (e) {
18
+ // Graceful fallback if localStorage is restricted (private browsing / iframe sandboxing)
19
+ }
20
+ })();
21
+ </script>
4
22
 
5
23
  <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
6
24
  <link rel="icon" href="favicon.ico" type="image/x-icon">
@@ -159,6 +159,10 @@ resume_data.experience == site.data['2025-06']['20250621-PM'].experience
159
159
  <body class="theme-{% if site.resume_theme %}{{ site.resume_theme }}{% endif %}">
160
160
 
161
161
  <div class="wrapper" itemscope itemtype="http://schema.org/Person">
162
+ {% if site.resume_dark_mode == "enabled" %}
163
+ {% include dark-mode-toggle.html %}
164
+ {% endif %}
165
+
162
166
  <meta itemprop="telephone" content="{{ site.contact_info.phone }}" />
163
167
  <meta itemprop="address" content="{{ site.contact_info.address }}" />
164
168
 
@@ -159,6 +159,10 @@ resume_data.experience == site.data['2025-06']['20250621-PM'].experience
159
159
  <body class="theme-{% if site.resume_theme %}{{ site.resume_theme }}{% endif %}">
160
160
 
161
161
  <div class="wrapper" itemscope itemtype="http://schema.org/Person">
162
+ {% if site.resume_dark_mode == "enabled" %}
163
+ {% include dark-mode-toggle.html %}
164
+ {% endif %}
165
+
162
166
  <meta itemprop="telephone" content="{{ site.contact_info.phone }}" />
163
167
  <meta itemprop="address" content="{{ site.contact_info.address }}" />
164
168
 
@@ -2,35 +2,25 @@
2
2
 
3
3
  .svg-icon,
4
4
  svg {
5
- fill: #BBB;
5
+ fill: var(--icon-fill-muted, #BBB);
6
6
  width: 30px;
7
7
  }
8
8
 
9
9
  .svg-icon:hover,
10
10
  svg:hover {
11
- fill: #000;
11
+ fill: var(--icon-fill-dark, #000);
12
12
  }
13
13
 
14
14
  /* Following only apply to the resume header contact info icons */
15
15
  .header-contact-info>p>svg {
16
16
  width: 20px;
17
- fill: #000;
17
+ fill: var(--icon-fill-dark, #000);
18
18
  vertical-align: middle;
19
19
  }
20
20
 
21
21
 
22
22
  /* Footer */
23
23
 
24
- /* Old
25
- footer {
26
- color: rgba(0, 0, 0, 1);
27
- width: 100%;
28
- margin: 50px auto;
29
- text-align: center;
30
- flex: none;
31
- }
32
- */
33
-
34
24
  footer,
35
25
  .page-footer {
36
26
  padding-top: 1.5rem;
@@ -41,11 +31,11 @@ footer,
41
31
  .copyright {
42
32
  margin: 0;
43
33
  font-size: .875rem;
44
- color: #999;
34
+ color: var(--text-muted, #999);
45
35
 
46
36
  >a {
47
37
  font-weight: 700;
48
- color: #333;
38
+ color: var(--link-color, #333);
49
39
  }
50
40
  }
51
41
  }
data/_sass/_base.scss CHANGED
@@ -11,12 +11,12 @@
11
11
  }
12
12
 
13
13
  html {
14
- background: variables.$white;
14
+ background: var(--bg-color, variables.$white);
15
15
  }
16
16
 
17
17
  body {
18
18
  @include mixins.serif;
19
- color: variables.$text_color;
19
+ color: var(--text-color, variables.$text_color);
20
20
  font-size: 16px;
21
21
  line-height: 1.5;
22
22
  }
@@ -56,13 +56,13 @@ body {
56
56
  }
57
57
 
58
58
  ::-moz-selection {
59
- background: rgba(51, 51, 51, .8);
60
- color: #fff;
59
+ background: var(--selection-bg, rgba(51, 51, 51, .8));
60
+ color: var(--selection-color, #fff);
61
61
  text-shadow: none;
62
62
  }
63
63
 
64
64
  ::selection {
65
- background: rgba(51, 51, 51, .8);
66
- color: #fff;
65
+ background: var(--selection-bg, rgba(51, 51, 51, .8));
66
+ color: var(--selection-color, #fff);
67
67
  text-shadow: none;
68
68
  }
@@ -0,0 +1,365 @@
1
+ // ==========================================================================
2
+ // Dark Mode System Tokens & Overrides
3
+ // bilingual-jekyll-resume-theme
4
+ // ==========================================================================
5
+
6
+ :root {
7
+ color-scheme: light dark;
8
+
9
+ // --- Background & Primary Typography ---
10
+ --bg-color: #ffffff;
11
+ --text-color: #333;
12
+ --text-muted: #999;
13
+ --text-light: #646464;
14
+ --border-color: #c7c7c7;
15
+ --card-bg: #efefef;
16
+
17
+ // --- Links & Navigation ---
18
+ --link-color: #333;
19
+ --link-hover: #9c9c9c;
20
+ --link-hover-color: var(--link-hover);
21
+
22
+ // --- Accent & Brand Colors ---
23
+ --accent-color: #3064a9;
24
+ --accent-hover: #307EA9;
25
+ --accent-hover-color: var(--accent-hover);
26
+ --social-hover-color: var(--accent-hover);
27
+ --about-color: var(--text-light);
28
+
29
+ // --- Footer ---
30
+ --footer-text-color: var(--text-muted);
31
+ --footer-link-color: var(--link-color);
32
+
33
+ // --- Icons & Graphics ---
34
+ --icon-fill: #333;
35
+ --icon-fill-muted: #BBB;
36
+ --icon-fill-dark: #000;
37
+ --icon-hover-fill: var(--icon-fill-dark);
38
+ --header-icon-fill: var(--icon-fill-dark);
39
+
40
+ // --- Buttons (.contact-button, .cv-button) ---
41
+ --button-bg: #efefef;
42
+ --button-text: #333;
43
+ --button-hover-bg: #333;
44
+ --button-hover-text: #fff;
45
+
46
+ // --- Text Selection ---
47
+ --selection-bg: rgba(51, 51, 51, .8);
48
+ --selection-color: #fff;
49
+
50
+ // --- Dark Mode Toggle Component Tokens ---
51
+ --toggle-btn-bg: transparent;
52
+ --toggle-btn-border: var(--border-color);
53
+ --toggle-btn-color: var(--text-color);
54
+ --toggle-btn-hover-bg: var(--button-bg);
55
+ --toggle-btn-focus-ring: var(--accent-color);
56
+ }
57
+
58
+ // --------------------------------------------------------------------------
59
+ // Automatic System Dark Mode (@media prefers-color-scheme)
60
+ // --------------------------------------------------------------------------
61
+ @media (prefers-color-scheme: dark) {
62
+ :root:not([data-color-scheme="light"]):not([data-theme="light"]) {
63
+ --bg-color: #121212;
64
+ --text-color: #e0e0e0;
65
+ --text-muted: #888888;
66
+ --text-light: #aaaaaa;
67
+ --border-color: #333333;
68
+ --card-bg: #1e1e1e;
69
+
70
+ --link-color: #e0e0e0;
71
+ --link-hover: #ffffff;
72
+ --link-hover-color: var(--link-hover);
73
+
74
+ --accent-color: #6ba4e8;
75
+ --accent-hover: #8cbcf3;
76
+ --accent-hover-color: var(--accent-hover);
77
+ --social-hover-color: var(--accent-hover);
78
+ --about-color: var(--text-light);
79
+
80
+ --footer-text-color: var(--text-muted);
81
+ --footer-link-color: var(--link-color);
82
+
83
+ --icon-fill: #e0e0e0;
84
+ --icon-fill-muted: #666666;
85
+ --icon-fill-dark: #ffffff;
86
+ --icon-hover-fill: var(--icon-fill-dark);
87
+ --header-icon-fill: var(--icon-fill-dark);
88
+
89
+ --button-bg: #2a2a2a;
90
+ --button-text: #e0e0e0;
91
+ --button-hover-bg: #444444;
92
+ --button-hover-text: #ffffff;
93
+
94
+ --selection-bg: rgba(107, 164, 232, .5);
95
+ --selection-color: #ffffff;
96
+
97
+ --toggle-btn-border: var(--border-color);
98
+ --toggle-btn-color: var(--text-color);
99
+ --toggle-btn-hover-bg: var(--button-bg);
100
+ --toggle-btn-focus-ring: var(--accent-color);
101
+ }
102
+ }
103
+
104
+ // --------------------------------------------------------------------------
105
+ // Forced Dark Override (Pinned via toggle or explicit attribute)
106
+ // --------------------------------------------------------------------------
107
+ :root[data-color-scheme="dark"],
108
+ :root[data-theme="dark"],
109
+ [data-color-scheme="dark"],
110
+ [data-theme="dark"],
111
+ html.dark {
112
+ color-scheme: dark;
113
+ --bg-color: #121212;
114
+ --text-color: #e0e0e0;
115
+ --text-muted: #888888;
116
+ --text-light: #aaaaaa;
117
+ --border-color: #333333;
118
+ --card-bg: #1e1e1e;
119
+
120
+ --link-color: #e0e0e0;
121
+ --link-hover: #ffffff;
122
+ --link-hover-color: var(--link-hover);
123
+
124
+ --accent-color: #6ba4e8;
125
+ --accent-hover: #8cbcf3;
126
+ --accent-hover-color: var(--accent-hover);
127
+ --social-hover-color: var(--accent-hover);
128
+ --about-color: var(--text-light);
129
+
130
+ --footer-text-color: var(--text-muted);
131
+ --footer-link-color: var(--link-color);
132
+
133
+ --icon-fill: #e0e0e0;
134
+ --icon-fill-muted: #666666;
135
+ --icon-fill-dark: #ffffff;
136
+ --icon-hover-fill: var(--icon-fill-dark);
137
+ --header-icon-fill: var(--icon-fill-dark);
138
+
139
+ --button-bg: #2a2a2a;
140
+ --button-text: #e0e0e0;
141
+ --button-hover-bg: #444444;
142
+ --button-hover-text: #ffffff;
143
+
144
+ --selection-bg: rgba(107, 164, 232, .5);
145
+ --selection-color: #ffffff;
146
+
147
+ --toggle-btn-border: var(--border-color);
148
+ --toggle-btn-color: var(--text-color);
149
+ --toggle-btn-hover-bg: var(--button-bg);
150
+ --toggle-btn-focus-ring: var(--accent-color);
151
+ }
152
+
153
+ // --------------------------------------------------------------------------
154
+ // Forced Light Override (Pinned via toggle on dark OS)
155
+ // --------------------------------------------------------------------------
156
+ :root[data-color-scheme="light"],
157
+ :root[data-theme="light"],
158
+ [data-color-scheme="light"],
159
+ [data-theme="light"],
160
+ html.light {
161
+ color-scheme: light;
162
+ --bg-color: #ffffff;
163
+ --text-color: #333;
164
+ --text-muted: #999;
165
+ --text-light: #646464;
166
+ --border-color: #c7c7c7;
167
+ --card-bg: #efefef;
168
+
169
+ --link-color: #333;
170
+ --link-hover: #9c9c9c;
171
+ --link-hover-color: var(--link-hover);
172
+
173
+ --accent-color: #3064a9;
174
+ --accent-hover: #307EA9;
175
+ --accent-hover-color: var(--accent-hover);
176
+ --social-hover-color: var(--accent-hover);
177
+ --about-color: var(--text-light);
178
+
179
+ --footer-text-color: var(--text-muted);
180
+ --footer-link-color: var(--link-color);
181
+
182
+ --icon-fill: #333;
183
+ --icon-fill-muted: #BBB;
184
+ --icon-fill-dark: #000;
185
+ --icon-hover-fill: var(--icon-fill-dark);
186
+ --header-icon-fill: var(--icon-fill-dark);
187
+
188
+ --button-bg: #efefef;
189
+ --button-text: #333;
190
+ --button-hover-bg: #333;
191
+ --button-hover-text: #fff;
192
+
193
+ --selection-bg: rgba(51, 51, 51, .8);
194
+ --selection-color: #fff;
195
+
196
+ --toggle-btn-border: var(--border-color);
197
+ --toggle-btn-color: var(--text-color);
198
+ --toggle-btn-hover-bg: var(--button-bg);
199
+ --toggle-btn-focus-ring: var(--accent-color);
200
+ }
201
+
202
+ // --------------------------------------------------------------------------
203
+ // Print Media Override (Always clean light background for printing)
204
+ // --------------------------------------------------------------------------
205
+ @media print {
206
+ :root,
207
+ :root[data-color-scheme="dark"],
208
+ :root[data-color-scheme="light"],
209
+ :root[data-theme="dark"],
210
+ :root[data-theme="light"],
211
+ [data-color-scheme="dark"],
212
+ [data-theme="dark"],
213
+ html.dark,
214
+ html.light {
215
+ color-scheme: light !important;
216
+ --bg-color: #ffffff !important;
217
+ --text-color: #000000 !important;
218
+ --text-muted: #666 !important;
219
+ --text-light: #444 !important;
220
+ --border-color: #c7c7c7 !important;
221
+ --card-bg: #fff !important;
222
+
223
+ --link-color: #000 !important;
224
+ --link-hover: #000 !important;
225
+ --link-hover-color: #000 !important;
226
+
227
+ --accent-color: #000 !important;
228
+ --accent-hover: #000 !important;
229
+ --accent-hover-color: #000 !important;
230
+ --social-hover-color: #000 !important;
231
+ --about-color: #444 !important;
232
+
233
+ --footer-text-color: #666 !important;
234
+ --footer-link-color: #000 !important;
235
+
236
+ --icon-fill: #000 !important;
237
+ --icon-fill-muted: #666 !important;
238
+ --icon-fill-dark: #000 !important;
239
+ --icon-hover-fill: #000 !important;
240
+ --header-icon-fill: #000 !important;
241
+
242
+ --button-bg: #fff !important;
243
+ --button-text: #000 !important;
244
+ --button-hover-bg: #fff !important;
245
+ --button-hover-text: #000 !important;
246
+
247
+ --selection-bg: transparent !important;
248
+ --selection-color: #000 !important;
249
+ }
250
+
251
+ html,
252
+ body {
253
+ background-color: #fff !important;
254
+ color: #000 !important;
255
+ -webkit-print-color-adjust: exact;
256
+ print-color-adjust: exact;
257
+ }
258
+
259
+ .dark-mode-toggle,
260
+ #dark-mode-toggle {
261
+ display: none !important;
262
+ }
263
+ }
264
+
265
+ // --------------------------------------------------------------------------
266
+ // Toggle Button Component Styling
267
+ // --------------------------------------------------------------------------
268
+ .dark-mode-toggle {
269
+ position: fixed;
270
+ top: 1.25rem;
271
+ right: 1.25rem;
272
+ z-index: 1000;
273
+ display: inline-flex;
274
+ align-items: center;
275
+ justify-content: center;
276
+ width: 2.5rem;
277
+ height: 2.5rem;
278
+ padding: 0;
279
+ background: var(--bg-color);
280
+ color: var(--toggle-btn-color);
281
+ border: 1px solid var(--toggle-btn-border);
282
+ border-radius: 50%;
283
+ cursor: pointer;
284
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
285
+ transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
286
+
287
+ &:hover {
288
+ background-color: var(--toggle-btn-hover-bg);
289
+ transform: scale(1.05);
290
+ }
291
+
292
+ &:focus-visible {
293
+ outline: 2px solid var(--toggle-btn-focus-ring);
294
+ outline-offset: 2px;
295
+ }
296
+
297
+ .dark-mode-icon {
298
+ width: 1.25rem;
299
+ height: 1.25rem;
300
+ stroke: currentColor;
301
+ transition: transform 0.3s ease;
302
+ }
303
+
304
+ // Baseline Light Mode: Show Moon, Hide Sun
305
+ .dark-mode-icon-sun {
306
+ display: none;
307
+ }
308
+ .dark-mode-icon-moon {
309
+ display: block;
310
+ }
311
+ }
312
+
313
+ // Media Query System Dark Mode (when not overridden to Light)
314
+ @media (prefers-color-scheme: dark) {
315
+ :root:not([data-color-scheme="light"]):not([data-theme="light"]) {
316
+ .dark-mode-toggle {
317
+ .dark-mode-icon-sun {
318
+ display: block;
319
+ }
320
+ .dark-mode-icon-moon {
321
+ display: none;
322
+ }
323
+ }
324
+ }
325
+ }
326
+
327
+ // Pinned Dark Override: Show Sun, Hide Moon
328
+ :root[data-color-scheme="dark"],
329
+ :root[data-theme="dark"],
330
+ [data-color-scheme="dark"],
331
+ [data-theme="dark"],
332
+ html.dark {
333
+ .dark-mode-toggle {
334
+ .dark-mode-icon-sun {
335
+ display: block;
336
+ }
337
+ .dark-mode-icon-moon {
338
+ display: none;
339
+ }
340
+ }
341
+ }
342
+
343
+ // Pinned Light Override: Show Moon, Hide Sun
344
+ :root[data-color-scheme="light"],
345
+ :root[data-theme="light"],
346
+ [data-color-scheme="light"],
347
+ [data-theme="light"],
348
+ html.light {
349
+ .dark-mode-toggle {
350
+ .dark-mode-icon-sun {
351
+ display: none;
352
+ }
353
+ .dark-mode-icon-moon {
354
+ display: block;
355
+ }
356
+ }
357
+ }
358
+
359
+ // RTL layout positioning
360
+ html[dir="rtl"] {
361
+ .dark-mode-toggle {
362
+ right: auto;
363
+ left: 1.25rem;
364
+ }
365
+ }
data/_sass/_mixins.scss CHANGED
@@ -99,13 +99,13 @@
99
99
  // layout
100
100
 
101
101
  @mixin section_border {
102
- border-top: 4px solid #c7c7c7;
103
- border-bottom: 2px solid #c7c7c7;
102
+ border-top: 4px solid var(--border-color, #c7c7c7);
103
+ border-bottom: 2px solid var(--border-color, #c7c7c7);
104
104
  padding: .2rem 0 .4rem;
105
105
  }
106
106
 
107
107
  @mixin section_border_thin {
108
- border-top: 1px solid #c7c7c7;
109
- border-bottom: 1px solid #c7c7c7;
108
+ border-top: 1px solid var(--border-color, #c7c7c7);
109
+ border-bottom: 1px solid var(--border-color, #c7c7c7);
110
110
  padding: .2rem 0 .2rem;
111
111
  }
data/_sass/_profile.scss CHANGED
@@ -1,5 +1,10 @@
1
+ html {
2
+ background-color: var(--bg-color, #fff);
3
+ }
4
+
1
5
  body {
2
- color: #333;
6
+ background-color: var(--bg-color, #fff);
7
+ color: var(--text-color, #333);
3
8
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
4
9
  line-height: 1;
5
10
  margin: 10% 0 8%;
@@ -24,7 +29,7 @@ body {
24
29
  }
25
30
 
26
31
  .about {
27
- color: #646464;
32
+ color: var(--text-light, #646464);
28
33
  font-size: 87.5%;
29
34
  padding-bottom: 14px;
30
35
  line-height: .6cm;
@@ -38,7 +43,7 @@ body {
38
43
  }
39
44
 
40
45
  a:hover {
41
- color: #3064a9;
46
+ color: var(--accent-color, #3064a9);
42
47
  }
43
48
 
44
49
  .social-links li {
@@ -47,16 +52,16 @@ a:hover {
47
52
  }
48
53
 
49
54
  .social-links li a {
50
- color: #BBB;
55
+ color: var(--icon-fill-muted, #BBB);
51
56
  }
52
57
 
53
58
  .social-links li a:hover {
54
- color: #307EA9;
59
+ color: var(--accent-hover, #307EA9);
55
60
  }
56
61
 
57
62
  .cv-button {
58
63
  border-radius: 3px;
59
- background-color: #efefef;
64
+ background-color: var(--button-bg, #efefef);
60
65
  text-decoration: none;
61
66
  text-align: center;
62
67
  display: block;
@@ -64,12 +69,12 @@ a:hover {
64
69
  width: 220px;
65
70
  font-size: 1.375rem;
66
71
  font-weight: 300;
67
- color: #333;
72
+ color: var(--button-text, #333);
68
73
  line-height: 55px;
69
74
  transition: (all .2s ease);
70
75
  }
71
76
 
72
77
  .cv-button:hover {
73
- background-color: #333;
74
- color: #fff;
78
+ background-color: var(--button-hover-bg, #333);
79
+ color: var(--button-hover-text, #fff);
75
80
  }
@@ -66,4 +66,10 @@ html[dir="rtl"] {
66
66
  .page-footer {
67
67
  text-align: center; // keep centered
68
68
  }
69
+
70
+ // Dark mode toggle positioning for RTL
71
+ .dark-mode-toggle {
72
+ right: auto;
73
+ left: 1.25rem;
74
+ }
69
75
  }
data/_sass/_resume.scss CHANGED
@@ -88,7 +88,7 @@
88
88
  position: relative;
89
89
  top: 4px;
90
90
  text-align: center;
91
- border-top: 2px solid #c7c7c7;
91
+ border-top: 2px solid var(--border-color, #c7c7c7);
92
92
  padding-top: .4rem;
93
93
 
94
94
  @include mixins.media_larger_than_mobile {
@@ -106,7 +106,7 @@
106
106
  display: inline-block;
107
107
 
108
108
  &:hover .icon path {
109
- fill: #333;
109
+ fill: var(--icon-fill, #333);
110
110
  @include mixins.transition(all .2s ease);
111
111
  }
112
112
  }
@@ -118,7 +118,7 @@
118
118
 
119
119
  .contact-button {
120
120
  @include mixins.border-radius(3px);
121
- background-color: #efefef;
121
+ background-color: var(--button-bg, #efefef);
122
122
  text-decoration: none;
123
123
  text-align: center;
124
124
  display: block;
@@ -126,13 +126,13 @@
126
126
  width: 220px;
127
127
  font-size: 1.375rem;
128
128
  @include mixins.sans_light;
129
- color: #333;
129
+ color: var(--button-text, #333);
130
130
  line-height: 55px;
131
131
  @include mixins.transition(all .2s ease);
132
132
 
133
133
  &:hover {
134
- background-color: #333;
135
- color: #fff;
134
+ background-color: var(--button-hover-bg, #333);
135
+ color: var(--button-hover-text, #fff);
136
136
  }
137
137
 
138
138
  &.not-looking {
@@ -172,13 +172,13 @@
172
172
  .resume-item-copy,
173
173
  .resume-item-list {
174
174
  a {
175
- color: #333;
175
+ color: var(--link-color, #333);
176
176
  text-decoration: none;
177
177
  border-bottom: 1px solid;
178
178
 
179
179
  &:hover {
180
180
  border-bottom: none;
181
- color: #9c9c9c;
181
+ color: var(--link-hover, #9c9c9c);
182
182
  }
183
183
  }
184
184
  }
@@ -261,7 +261,7 @@
261
261
  // footer styles
262
262
 
263
263
  .page-footer {
264
- border-top: 4px solid #c7c7c7;
264
+ border-top: 4px solid var(--border-color, #c7c7c7);
265
265
  padding-top: 1.5rem;
266
266
  text-align: center;
267
267
  margin-bottom: 3rem;
@@ -269,11 +269,11 @@
269
269
  .footer-line {
270
270
  margin: 0;
271
271
  font-size: .875rem;
272
- color: #999;
272
+ color: var(--text-muted, #999);
273
273
 
274
274
  >a {
275
275
  font-weight: 700;
276
- color: #333;
276
+ color: var(--link-color, #333);
277
277
  }
278
278
  }
279
279
  }
@@ -5,11 +5,11 @@ $container-width: 980px !default;
5
5
  $grid-gutter: 10px !default;
6
6
 
7
7
  // Colors
8
- $white: #fff;
9
- $black: #333;
8
+ $white: var(--bg-color, #fff);
9
+ $black: var(--text-color, #333);
10
10
 
11
11
  // Styles
12
- $text_color: $black;
12
+ $text_color: var(--text-color, #333);
13
13
 
14
14
  // Font stack
15
15
  $body-font: Helvetica,
@@ -6,6 +6,7 @@
6
6
  @use "normalize";
7
7
  @use "mixins";
8
8
  @use "variables";
9
+ @use "dark-mode";
9
10
 
10
11
  // Generic site base & layout styles
11
12
  @use "base";
data/assets/css/cv.scss CHANGED
@@ -5,6 +5,7 @@
5
5
  @use "normalize";
6
6
  @use "mixins";
7
7
  @use "variables";
8
+ @use "dark-mode";
8
9
 
9
10
  // Generic site base & layout styles
10
11
  @use "base";
data/assets/css/main.scss CHANGED
@@ -2,5 +2,6 @@
2
2
  ---
3
3
  // "Reset" styles and SCSS reusable components
4
4
  @use "normalize";
5
+ @use "dark-mode";
5
6
  @use "profile";
6
7
  @use "all-pages";
data/docs/CONFIG_GUIDE.md CHANGED
@@ -16,6 +16,7 @@ Make your site render correctly with this theme. This guide matches the shipped
16
16
  - [Social links](#social-links)
17
17
  - [Analytics](#analytics)
18
18
  - [Design and authors](#design-and-authors)
19
+ - [Dark Mode](#dark-mode)
19
20
  - [Jekyll build settings](#jekyll-build-settings)
20
21
  - [Full example config](#full-example-config)
21
22
  - [FAQs](#faqs)
@@ -193,6 +194,86 @@ Choose one (do not enable both):
193
194
 
194
195
  ---
195
196
 
197
+ ## Dark Mode
198
+
199
+ The theme features built-in, accessible dark mode support with automatic system preference detection via `prefers-color-scheme` and an optional interactive two-state manual toggle.
200
+
201
+ ### Configuration (`resume_dark_mode`)
202
+
203
+ Configure dark mode behavior in your `_config.yml` using the `resume_dark_mode` option:
204
+
205
+ ```yaml
206
+ # Dark mode configuration (options: auto | enabled)
207
+ resume_dark_mode: auto
208
+ ```
209
+
210
+ #### Accepted Values
211
+
212
+ - **`auto`** (default):
213
+ - Pure CSS-first system detection via `@media (prefers-color-scheme: dark)` and `:root { color-scheme: light dark; }`.
214
+ - Automatically switches between light and dark palettes to match the visitor's operating system / browser theme.
215
+ - **No toggle button is rendered in the HTML**, resulting in zero JavaScript runtime overhead and no layout shifts.
216
+ - If `resume_dark_mode` is omitted or not defined, the theme safely defaults to `auto`.
217
+
218
+ - **`enabled`**:
219
+ - Full automatic system detection **plus** an interactive floating toggle button rendered on resume pages (`resume-en.html` and `resume-ar.html`).
220
+ - Allows visitors to manually override their system color scheme.
221
+ - Preference is persisted across visits and pages via `localStorage`.
222
+
223
+ ### Two-State Toggle & State Machine
224
+
225
+ When `resume_dark_mode: enabled` is configured, the toggle operates as a predictable two-state finite state machine (FSM):
226
+
227
+ 1. **State A: System Default (Unpinned)**
228
+ - Initial state when no preference is stored in `localStorage` (`color-scheme` key is `null`).
229
+ - The theme strictly follows the visitor's OS setting (`prefers-color-scheme`).
230
+ - Live media query listeners (`window.matchMedia`) dynamically update toggle accessibility labels if the OS theme shifts during the session.
231
+ - Clicking the toggle transitions to **State B (Pinned)**, forcing the *opposite* of the active system theme (e.g., clicking on a light OS pins dark mode).
232
+
233
+ 2. **State B: Forced Override (Pinned)**
234
+ - The chosen scheme (`"dark"` or `"light"`) is saved in `localStorage` under the key `color-scheme`.
235
+ - The `<html>` element receives `data-color-scheme` and `data-theme` attributes, and `<meta name="color-scheme">` is updated to match.
236
+ - Clicking the toggle again clears the `localStorage` key and transitions back to **State A (System Default)**.
237
+
238
+ ### Anti-FOUC (Flash of Unstyled Content) Prevention
239
+
240
+ To eliminate any visual flash or layout shift for returning visitors:
241
+ - A synchronous, non-deferred `<script>` tag is executed in the document `<head>` (via `_includes/shared-head.html`) immediately after `<meta name="color-scheme" content="light dark">`.
242
+ - It reads `localStorage.getItem('color-scheme')` before DOM rendering starts and synchronously applies `data-color-scheme` / `data-theme` to the root element.
243
+
244
+ ### Bilingual Accessibility & RTL Parity
245
+
246
+ The toggle component is designed for WCAG 2.1 AA compliance:
247
+ - **Keyboard navigation**: Fully focusable with a high-contrast `:focus-visible` focus ring; toggles via <kbd>Enter</kbd> or <kbd>Space</kbd>.
248
+ - **Screen readers**: Dynamic `aria-label` and `title` attributes announce the current action and target theme state in both English (LTR) and Arabic (RTL):
249
+ - **English (LTR)**:
250
+ - Unpinned (System Light): `"Switch to dark mode"`
251
+ - Unpinned (System Dark): `"Switch to light mode"`
252
+ - Pinned: `"Reset to system theme"`
253
+ - **Arabic (RTL)**:
254
+ - Unpinned (System Light): `"تبديل إلى الوضع الداكن"`
255
+ - Unpinned (System Dark): `"تبديل إلى الوضع الفاتح"`
256
+ - Pinned: `"إعادة ضبط المظهر إلى الوضع التلقائي للنظام"`
257
+ - **RTL positioning**: In Arabic layouts (`resume-ar.html`), the toggle automatically repositions to the top-left (`left: 1.5rem`) to maintain balanced spacing relative to RTL content.
258
+
259
+ ### Print Media Behavior
260
+
261
+ When printing or exporting to PDF:
262
+ - The toggle button is automatically hidden via the `.no-print` class and `@media print { display: none !important; }`.
263
+ - Print styles enforce a clean white background (`--bg-color: #ffffff !important`) and crisp black text (`--text-color: #000000 !important`), overriding both dark mode and manual pins.
264
+
265
+ ### Liquid Integration Example
266
+
267
+ Theme layouts include the toggle conditionally using Liquid:
268
+
269
+ ```liquid
270
+ {% if site.resume_dark_mode == "enabled" %}
271
+ {% include dark-mode-toggle.html %}
272
+ {% endif %}
273
+ ```
274
+
275
+ ---
276
+
196
277
  ## Jekyll build settings
197
278
 
198
279
  - plugins (required): jekyll-feed, jekyll-seo-tag, jekyll-sitemap, jekyll-redirect-from
@@ -291,6 +372,7 @@ social_links:
291
372
  # Theme & analytics
292
373
  authors: []
293
374
  resume_theme: default
375
+ # resume_dark_mode: auto # Options: auto (default: CSS system detection), enabled (manual toggle button)
294
376
  analytics:
295
377
  # gtm: GTM-XXXXXXX
296
378
  # ga: true
@@ -334,3 +416,7 @@ exclude:
334
416
  - Enable `resume_header_intro_en: true` or `resume_header_intro_ar: true` in `_config.yml`, and create `_data/en/header.yml` (or `_data/ar/header.yml` for Arabic) in your active data path with an `intro:` field. You can copy sample files from `docs/_data/en/header.yml` and `docs/_data/ar/header.yml`.
335
417
  - Data for EN/AR lives in different folders.
336
418
  - Point `active_resume_path_en` and `active_resume_path_ar` at the right subtrees (e.g., `en` and `ar`).
419
+ - How do I enable the interactive dark mode toggle?
420
+ - Set `resume_dark_mode: enabled` in your `_config.yml`. By default (`auto` or omitted), dark mode adapts automatically to the user's OS preference without rendering a toggle button.
421
+ - Why doesn't dark mode apply when printing or saving to PDF?
422
+ - Resumes are optimized for clean physical and PDF printing with high-contrast black text on a pure white background. Dark mode tokens and the toggle button are automatically suppressed in print stylesheets.
@@ -24,6 +24,7 @@ This guide assumes basic familiarity with Jekyll and Liquid, but includes exampl
24
24
  - [social-links.html](#9-social-linkshtml)
25
25
  - [print-social-links.html](#10-print-social-linkshtml)
26
26
  - [vendors/ (SVG icon packs)](#11-vendors-svg-icon-packs)
27
+ - [dark-mode-toggle.html](#12-dark-mode-togglehtml)
27
28
  - [Dynamic section rendering (resume-section-*.html)](#dynamic-section-rendering-resume-section-html)
28
29
  - [How it works](#how-it-works)
29
30
  - [Section list and flags](#section-list-and-flags)
@@ -273,6 +274,36 @@ To add a new icon:
273
274
 
274
275
  ---
275
276
 
277
+ ### 12) dark-mode-toggle.html
278
+ Purpose: Self-contained dark mode toggle button rendered conditionally in both resume layouts.
279
+ - Outputs a fixed-position circular button (top-right for LTR, top-left for RTL) with inline sun/moon SVG icons.
280
+ - Implements a **two-state** finite state machine:
281
+ - **System Default** — follows the OS `prefers-color-scheme` preference automatically. No `localStorage` entry.
282
+ - **Pinned** — forces light or dark regardless of OS. Saves `"light"` or `"dark"` to `localStorage["color-scheme"]`.
283
+ - Includes an `addEventListener("change")` listener on `matchMedia("(prefers-color-scheme: dark)")` so the button icon updates in real-time if the user changes their OS theme while in System Default mode.
284
+ - Fully accessible: keyboard focusable, dynamic `aria-label` and `title` updated on state changes, bilingual labels (English and Arabic).
285
+ - Hides automatically in print/PDF output via `.no-print`.
286
+
287
+ Configuration: Controlled by `resume_dark_mode` in `_config.yml`. Only rendered when set to `enabled`.
288
+
289
+ ```yaml
290
+ # _config.yml
291
+ resume_dark_mode: enabled # renders toggle in both EN and AR layouts
292
+ # resume_dark_mode: auto # CSS-only system detection (default, no toggle)
293
+ ```
294
+
295
+ The include is conditionally pulled into both `resume-en.html` and `resume-ar.html`:
296
+
297
+ ```liquid
298
+ {% if site.resume_dark_mode == "enabled" %}
299
+ {% include dark-mode-toggle.html %}
300
+ {% endif %}
301
+ ```
302
+
303
+ Used by: `resume-en.html` and `resume-ar.html`.
304
+
305
+ ---
306
+
276
307
  ## Dynamic section rendering (resume-section-*.html)
277
308
 
278
309
  Two includes power all resume content:
@@ -146,7 +146,7 @@ active_resume_path_ar: ""
146
146
  ## Rendering flow inside resume layouts
147
147
 
148
148
  ### 1) Head, SEO, analytics
149
- - Shared meta from `shared-head.html`
149
+ - Shared meta from `shared-head.html` — includes `<meta name="color-scheme" content="light dark">` and an inline FOUC-prevention script that reads `localStorage["color-scheme"]` and applies the saved theme attribute before first paint
150
150
  - Resume-specific head from `resume-head-*.html` (CSS, fonts, favicons)
151
151
  - `{% seo %}` prints SEO meta and Open Graph/Twitter cards
152
152
  - `analytics-head.html` adds GTM or GA4; `analytics-body.html` adds GTM `<noscript>` in the body
@@ -250,6 +250,11 @@ resume_section_order:
250
250
  # Print behavior
251
251
  resume_print_social_links: true
252
252
 
253
+ # Dark mode (controls whether a toggle button is rendered)
254
+ # "auto" (default): CSS-only system detection, no toggle button
255
+ # "enabled": renders an interactive toggle button in both EN and AR layouts
256
+ resume_dark_mode: auto
257
+
253
258
  # Active data subtree for resume_data (separate for each language)
254
259
  active_resume_path_en: "en"
255
260
  active_resume_path_ar: "ar"
data/docs/SASS_GUIDE.md CHANGED
@@ -22,6 +22,7 @@ A contributor- and user-friendly tour of the theme's styling system: how styles
22
22
  - [_resume-rtl.scss](#7-_resume-rtlscss)
23
23
  - [_profile.scss](#8-_profilescss)
24
24
  - [_all-pages.scss](#9-_all-pagesscss)
25
+ - [_dark-mode.scss](#10-_dark-modescss)
25
26
  - [Responsive, print, and RTL strategy](#responsive-print-and-rtl-strategy)
26
27
  - [Variables reference](#variables-reference)
27
28
  - [Mixins reference](#mixins-reference)
@@ -163,6 +164,38 @@ Purpose: Small shared bits across pages.
163
164
 
164
165
  ---
165
166
 
167
+ ### 10) `_dark-mode.scss`
168
+ Purpose: Dark mode color token system and toggle button styles.
169
+
170
+ This partial is the single source of truth for all theming colors. It defines CSS custom properties on `:root` for the light palette and overrides them for dark mode via two mechanisms:
171
+
172
+ 1. **Automatic** — `@media (prefers-color-scheme: dark)` picks up the user's OS preference with no JavaScript.
173
+ 2. **Manual pin** — `[data-color-scheme="dark"]` / `[data-color-scheme="light"]` attributes on `<html>` are set by the `dark-mode-toggle.html` JS when the user pins a preference.
174
+
175
+ It also includes:
176
+ - **Print reset** — overrides all tokens back to black-on-white under `@media print`, ensuring PDFs are always clean regardless of the active theme.
177
+ - **Toggle button styles** — `.dark-mode-toggle` component: fixed-position, circular, with sun/moon icon visibility rules per theme state and RTL flip via `html[dir="rtl"]`.
178
+
179
+ Key CSS custom properties defined:
180
+
181
+ | Token | Purpose |
182
+ |---|---|
183
+ | `--bg-color` | Page/html background |
184
+ | `--text-color` | Primary body text |
185
+ | `--text-muted` | Secondary/muted text (e.g., footer) |
186
+ | `--border-color` | Section borders |
187
+ | `--card-bg` | Button and card backgrounds |
188
+ | `--link-color` | Link default color |
189
+ | `--link-hover` | Link hover color |
190
+ | `--accent-color` | Focus rings and UI accent |
191
+ | `--button-bg/text/hover-*` | Contact and CV button states |
192
+ | `--icon-fill` | SVG icon fill |
193
+ | `--selection-bg/color` | Text selection highlight |
194
+
195
+ Loaded by: `assets/css/cv.scss`, `assets/css/cv-ar.scss`, and `assets/css/main.scss`.
196
+
197
+ ---
198
+
166
199
  ## Responsive, print, and RTL strategy
167
200
 
168
201
  - Responsive: Mixins `media_larger_than_mobile` and `media_mobile` (600px breakpoint) provide simple adjustments (e.g., stacking → floats).
@@ -131,6 +131,11 @@ resume_print_social_links: true # Include social links on printed version
131
131
  authors: [] # optional
132
132
  resume_theme: default # Theme variant (currently only "default")
133
133
 
134
+ # Dark mode configuration:
135
+ # - "auto" (default): Automatic dark mode based on user's OS/browser preference (no toggle button)
136
+ # - "enabled": Adds an interactive floating toggle button (English LTR and Arabic RTL supported)
137
+ # resume_dark_mode: auto
138
+
134
139
  # Analytics (optional - choose one)
135
140
  analytics:
136
141
  # Google Tag Manager (recommended):
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bilingual-jekyll-resume-theme
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Khaldoon Mutahar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-09 00:00:00.000000000 Z
11
+ date: 2026-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -109,6 +109,7 @@ files:
109
109
  - _includes/analytics-body.html
110
110
  - _includes/analytics-head.html
111
111
  - _includes/ar-date.html
112
+ - _includes/dark-mode-toggle.html
112
113
  - _includes/hreflang.html
113
114
  - _includes/main-head.html
114
115
  - _includes/print-social-links.html
@@ -141,6 +142,7 @@ files:
141
142
  - _layouts/resume-en.html
142
143
  - _sass/_all-pages.scss
143
144
  - _sass/_base.scss
145
+ - _sass/_dark-mode.scss
144
146
  - _sass/_layout.scss
145
147
  - _sass/_mixins.scss
146
148
  - _sass/_normalize.scss