@ihsandeen/aya 1.0.0

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.
Files changed (112) hide show
  1. package/CONTRIBUTING.md +39 -0
  2. package/LICENSE +21 -0
  3. package/README.md +141 -0
  4. package/dist/commands/adab.js +28 -0
  5. package/dist/commands/adhan.js +58 -0
  6. package/dist/commands/anatomy.js +29 -0
  7. package/dist/commands/blame.js +29 -0
  8. package/dist/commands/commit.js +44 -0
  9. package/dist/commands/diff.js +54 -0
  10. package/dist/commands/dua.js +34 -0
  11. package/dist/commands/fast.js +36 -0
  12. package/dist/commands/friday.js +21 -0
  13. package/dist/commands/hero.js +69 -0
  14. package/dist/commands/hijri.js +42 -0
  15. package/dist/commands/history.js +98 -0
  16. package/dist/commands/init.js +54 -0
  17. package/dist/commands/invest.js +26 -0
  18. package/dist/commands/journal.js +84 -0
  19. package/dist/commands/journey.js +24 -0
  20. package/dist/commands/lens.js +58 -0
  21. package/dist/commands/memorize.js +117 -0
  22. package/dist/commands/mirror.js +47 -0
  23. package/dist/commands/names.js +48 -0
  24. package/dist/commands/nature.js +28 -0
  25. package/dist/commands/nazm.js +100 -0
  26. package/dist/commands/parable.js +332 -0
  27. package/dist/commands/prayers.js +63 -0
  28. package/dist/commands/pull.js +28 -0
  29. package/dist/commands/push.js +156 -0
  30. package/dist/commands/qibla.js +118 -0
  31. package/dist/commands/repo.js +34 -0
  32. package/dist/commands/sabr.js +32 -0
  33. package/dist/commands/scene.js +54 -0
  34. package/dist/commands/seek.js +28 -0
  35. package/dist/commands/shukr.js +22 -0
  36. package/dist/commands/sleep.js +26 -0
  37. package/dist/commands/sound.js +35 -0
  38. package/dist/commands/status.js +109 -0
  39. package/dist/commands/sunnah.js +24 -0
  40. package/dist/commands/tafsir.js +89 -0
  41. package/dist/commands/tasbih.js +50 -0
  42. package/dist/commands/wudu.js +22 -0
  43. package/dist/commands/zakat.js +72 -0
  44. package/dist/data/commands-db.js +365 -0
  45. package/dist/data/events.js +105 -0
  46. package/dist/data/gems.js +160 -0
  47. package/dist/data/nak.js +616 -0
  48. package/dist/data/tafsir.js +157 -0
  49. package/dist/data/vocab.js +105 -0
  50. package/dist/index.js +86 -0
  51. package/dist/server.js +140 -0
  52. package/dist/utils/config.js +38 -0
  53. package/dist/utils/logger.js +104 -0
  54. package/dist/utils/printer.js +36 -0
  55. package/docs/index.html +1048 -0
  56. package/docs/repo.html +952 -0
  57. package/package.json +55 -0
  58. package/public/hero.html +285 -0
  59. package/public/index.html +1039 -0
  60. package/public/repo.html +904 -0
  61. package/src/commands/adab.ts +24 -0
  62. package/src/commands/adhan.ts +55 -0
  63. package/src/commands/anatomy.ts +25 -0
  64. package/src/commands/blame.ts +31 -0
  65. package/src/commands/commit.ts +42 -0
  66. package/src/commands/diff.ts +56 -0
  67. package/src/commands/dua.ts +34 -0
  68. package/src/commands/fast.ts +35 -0
  69. package/src/commands/friday.ts +17 -0
  70. package/src/commands/hero.ts +73 -0
  71. package/src/commands/hijri.ts +43 -0
  72. package/src/commands/history.ts +103 -0
  73. package/src/commands/init.ts +53 -0
  74. package/src/commands/invest.ts +22 -0
  75. package/src/commands/journal.ts +97 -0
  76. package/src/commands/journey.ts +20 -0
  77. package/src/commands/lens.ts +58 -0
  78. package/src/commands/memorize.ts +131 -0
  79. package/src/commands/mirror.ts +48 -0
  80. package/src/commands/names.ts +46 -0
  81. package/src/commands/nature.ts +24 -0
  82. package/src/commands/nazm.ts +102 -0
  83. package/src/commands/parable.ts +360 -0
  84. package/src/commands/prayers.ts +65 -0
  85. package/src/commands/pull.ts +28 -0
  86. package/src/commands/push.ts +171 -0
  87. package/src/commands/qibla.ts +127 -0
  88. package/src/commands/repo.ts +34 -0
  89. package/src/commands/sabr.ts +28 -0
  90. package/src/commands/scene.ts +56 -0
  91. package/src/commands/seek.ts +24 -0
  92. package/src/commands/shukr.ts +19 -0
  93. package/src/commands/sleep.ts +23 -0
  94. package/src/commands/sound.ts +34 -0
  95. package/src/commands/status.ts +132 -0
  96. package/src/commands/sunnah.ts +21 -0
  97. package/src/commands/tafsir.ts +86 -0
  98. package/src/commands/tasbih.ts +49 -0
  99. package/src/commands/wudu.ts +19 -0
  100. package/src/commands/zakat.ts +73 -0
  101. package/src/data/commands-db.ts +372 -0
  102. package/src/data/events.ts +113 -0
  103. package/src/data/gems.ts +163 -0
  104. package/src/data/nak.ts +805 -0
  105. package/src/data/tafsir.ts +165 -0
  106. package/src/data/vocab.ts +114 -0
  107. package/src/index.ts +94 -0
  108. package/src/server.ts +128 -0
  109. package/src/utils/config.ts +44 -0
  110. package/src/utils/logger.ts +122 -0
  111. package/src/utils/printer.ts +38 -0
  112. package/tsconfig.json +16 -0
package/docs/repo.html ADDED
@@ -0,0 +1,952 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>My Book of Deeds • aya</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Amiri:wght@400;700&family=Fira+Code:wght@300;400;500&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg-color: #0f1115;
11
+ --card-bg: rgba(22, 27, 34, 0.8);
12
+ --border-color: #30363d;
13
+ --text-primary: #c9d1d9;
14
+ --text-secondary: #8b949e;
15
+ --accent-gold: #d4af37;
16
+ --accent-green: #2ea043;
17
+ --accent-red: #da3633;
18
+ --font-cinematic: 'Cinzel', serif;
19
+ --font-body: 'Playfair Display', serif;
20
+ --font-mono: 'Fira Code', monospace;
21
+ --font-ui: 'Inter', sans-serif;
22
+ }
23
+
24
+ * { margin: 0; padding: 0; box-sizing: border-box; }
25
+
26
+ body {
27
+ background-color: var(--bg-color);
28
+ color: var(--text-primary);
29
+ font-family: var(--font-ui);
30
+ line-height: 1.5;
31
+ overflow-x: hidden;
32
+ }
33
+
34
+ /* Ambient Background */
35
+ .ambient-bg {
36
+ position: fixed;
37
+ top: 0; left: 0; width: 100%; height: 100%;
38
+ z-index: -1;
39
+ background:
40
+ radial-gradient(circle at 10% 10%, rgba(212, 175, 55, 0.05) 0%, transparent 40%),
41
+ radial-gradient(circle at 90% 90%, rgba(46, 160, 67, 0.05) 0%, transparent 40%);
42
+ pointer-events: none;
43
+ }
44
+
45
+ /* Header */
46
+ header {
47
+ background: #161b22;
48
+ padding: 1rem 2rem;
49
+ border-bottom: 1px solid var(--border-color);
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: space-between;
53
+ }
54
+
55
+ .repo-breadcrumb {
56
+ display: flex;
57
+ align-items: center;
58
+ font-size: 1.25rem;
59
+ color: var(--accent-gold);
60
+ font-family: var(--font-cinematic);
61
+ }
62
+
63
+ .repo-breadcrumb svg { margin-right: 0.5rem; }
64
+ .repo-breadcrumb span { margin: 0 0.5rem; color: var(--text-secondary); }
65
+ .repo-name { font-weight: 700; }
66
+ .badge-public {
67
+ border: 1px solid var(--border-color);
68
+ border-radius: 2rem;
69
+ padding: 0.1rem 0.5rem;
70
+ font-size: 0.75rem;
71
+ color: var(--text-secondary);
72
+ margin-left: 1rem;
73
+ font-family: var(--font-ui);
74
+ }
75
+
76
+ /* Tabs */
77
+ .repo-tabs {
78
+ background: #161b22;
79
+ padding: 0 2rem;
80
+ border-bottom: 1px solid var(--border-color);
81
+ display: flex;
82
+ gap: 2rem;
83
+ overflow-x: auto;
84
+ }
85
+
86
+ .tab-item {
87
+ padding: 1rem 0;
88
+ color: var(--text-primary);
89
+ text-decoration: none;
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 0.5rem;
93
+ border-bottom: 2px solid transparent;
94
+ font-size: 0.9rem;
95
+ opacity: 0.7;
96
+ transition: all 0.2s;
97
+ }
98
+
99
+ .tab-item:hover { opacity: 1; }
100
+ .tab-item.active {
101
+ border-bottom-color: var(--accent-gold);
102
+ font-weight: 600;
103
+ opacity: 1;
104
+ }
105
+
106
+ .count-badge {
107
+ background: rgba(110, 118, 129, 0.4);
108
+ border-radius: 2rem;
109
+ padding: 0 0.4rem;
110
+ font-size: 0.75rem;
111
+ }
112
+
113
+ /* Main Content */
114
+ main {
115
+ max-width: 1200px;
116
+ margin: 2rem auto;
117
+ padding: 0 2rem;
118
+ }
119
+
120
+ .code-layout {
121
+ display: grid;
122
+ grid-template-columns: 1fr 300px;
123
+ gap: 2rem;
124
+ }
125
+
126
+ /* File Explorer */
127
+ .file-explorer {
128
+ border: 1px solid var(--border-color);
129
+ border-radius: 6px;
130
+ background: #0d1117;
131
+ overflow: hidden;
132
+ }
133
+
134
+ .commit-header {
135
+ background: #161b22;
136
+ padding: 1rem;
137
+ border-bottom: 1px solid var(--border-color);
138
+ display: flex;
139
+ justify-content: space-between;
140
+ align-items: center;
141
+ font-size: 0.9rem;
142
+ }
143
+
144
+ .commit-info { display: flex; align-items: center; gap: 0.5rem; }
145
+ .avatar {
146
+ width: 24px; height: 24px;
147
+ border-radius: 50%;
148
+ background: var(--accent-gold);
149
+ display: flex; align-items: center; justify-content: center;
150
+ color: #000; font-weight: bold; font-size: 12px;
151
+ }
152
+ .commit-message { color: var(--text-primary); font-weight: 500; }
153
+ .commit-time { color: var(--text-secondary); font-size: 0.8rem; }
154
+ .commit-hash { font-family: var(--font-mono); color: var(--text-secondary); }
155
+
156
+ .file-list { list-style: none; }
157
+ .file-item {
158
+ display: grid;
159
+ grid-template-columns: auto 1fr auto;
160
+ gap: 1rem;
161
+ padding: 0.75rem 1rem;
162
+ border-bottom: 1px solid var(--border-color);
163
+ transition: background 0.2s;
164
+ cursor: pointer;
165
+ }
166
+
167
+ .file-item:last-child { border-bottom: none; }
168
+ .file-item:hover { background: #161b22; }
169
+
170
+ .file-icon { color: var(--text-secondary); }
171
+ .file-icon.folder { color: #54aeff; }
172
+ .file-name { color: var(--text-primary); text-decoration: none; }
173
+ .file-desc { color: var(--text-secondary); font-size: 0.9rem; }
174
+ .file-time { color: var(--text-secondary); font-size: 0.8rem; text-align: right; }
175
+
176
+ /* Readme Section */
177
+ .readme-container {
178
+ margin-top: 2rem;
179
+ border: 1px solid var(--border-color);
180
+ border-radius: 6px;
181
+ }
182
+ .readme-header {
183
+ padding: 0.5rem 1rem;
184
+ background: #161b22;
185
+ border-bottom: 1px solid var(--border-color);
186
+ font-size: 0.8rem;
187
+ font-weight: 600;
188
+ }
189
+ .readme-content {
190
+ padding: 2rem;
191
+ background: #0d1117;
192
+ font-family: var(--font-body);
193
+ }
194
+ .readme-content h1 {
195
+ font-family: var(--font-cinematic);
196
+ color: var(--accent-gold);
197
+ border-bottom: 1px solid var(--border-color);
198
+ padding-bottom: 0.5rem;
199
+ margin-bottom: 1rem;
200
+ }
201
+ .readme-content p { margin-bottom: 1rem; color: var(--text-primary); }
202
+ .readme-content blockquote {
203
+ border-left: 4px solid var(--accent-gold);
204
+ padding-left: 1rem;
205
+ color: var(--text-secondary);
206
+ font-style: italic;
207
+ }
208
+
209
+ /* Sidebar */
210
+ .sidebar h3 {
211
+ font-size: 1rem;
212
+ margin-bottom: 1rem;
213
+ font-weight: 600;
214
+ }
215
+ .sidebar-section {
216
+ padding-bottom: 1.5rem;
217
+ border-bottom: 1px solid var(--border-color);
218
+ margin-bottom: 1.5rem;
219
+ }
220
+ .sidebar-item {
221
+ display: flex;
222
+ justify-content: space-between;
223
+ margin-bottom: 0.5rem;
224
+ font-size: 0.9rem;
225
+ color: var(--text-secondary);
226
+ }
227
+ .lang-bar {
228
+ height: 8px;
229
+ border-radius: 4px;
230
+ overflow: hidden;
231
+ display: flex;
232
+ margin-top: 1rem;
233
+ }
234
+ .lang-segment { height: 100%; }
235
+ .lang-list { margin-top: 0.5rem; list-style: none; }
236
+ .lang-dot {
237
+ display: inline-block;
238
+ width: 8px; height: 8px;
239
+ border-radius: 50%;
240
+ margin-right: 0.5rem;
241
+ }
242
+
243
+ /* Contribution Graph */
244
+ .contribution-graph {
245
+ margin-top: 2rem;
246
+ border: 1px solid var(--border-color);
247
+ border-radius: 6px;
248
+ padding: 1rem;
249
+ background: #0d1117;
250
+ }
251
+ .graph-grid {
252
+ display: grid;
253
+ grid-template-columns: repeat(53, 1fr);
254
+ gap: 3px;
255
+ margin-top: 0.5rem;
256
+ }
257
+ .graph-day {
258
+ width: 10px; height: 10px;
259
+ background: #161b22;
260
+ border-radius: 2px;
261
+ }
262
+ .level-0 { background: #161b22; }
263
+ .level-1 { background: #0e4429; }
264
+ .level-2 { background: #006d32; }
265
+ .level-3 { background: #26a641; }
266
+ .level-4 { background: #39d353; }
267
+
268
+ /* Tab Content Switching */
269
+ .tab-content { display: none; }
270
+ .tab-content.active { display: block; animation: fadeIn 0.3s ease; }
271
+
272
+ @keyframes fadeIn {
273
+ from { opacity: 0; transform: translateY(5px); }
274
+ to { opacity: 1; transform: translateY(0); }
275
+ }
276
+
277
+ /* Issue/PR List Styles */
278
+ .issue-list { list-style: none; border: 1px solid var(--border-color); border-radius: 6px; overflow: hidden; }
279
+ .issue-item {
280
+ padding: 1rem;
281
+ background: #0d1117;
282
+ border-bottom: 1px solid var(--border-color);
283
+ display: flex;
284
+ gap: 1rem;
285
+ align-items: flex-start;
286
+ }
287
+ .issue-item:last-child { border-bottom: none; }
288
+ .issue-status { color: var(--accent-green); font-size: 0.9rem; margin-top: 2px; }
289
+ .issue-status.open { color: var(--accent-green); }
290
+ .issue-status.closed { color: var(--accent-red); }
291
+ .issue-title { font-weight: 600; color: var(--text-primary); display: block; margin-bottom: 0.25rem; }
292
+ .issue-meta { font-size: 0.8rem; color: var(--text-secondary); }
293
+
294
+ /* Toast Notification */
295
+ .toast {
296
+ position: fixed;
297
+ bottom: 2rem;
298
+ right: 2rem;
299
+ background: #1f6feb;
300
+ color: white;
301
+ padding: 1rem 1.5rem;
302
+ border-radius: 6px;
303
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
304
+ display: none;
305
+ z-index: 100;
306
+ font-family: var(--font-ui);
307
+ }
308
+
309
+ @media (max-width: 768px) {
310
+ main { grid-template-columns: 1fr; }
311
+ .file-desc { display: none; }
312
+ }
313
+ </style>
314
+ </head>
315
+ <body>
316
+ <div class="ambient-bg"></div>
317
+
318
+ <header>
319
+ <div class="repo-breadcrumb">
320
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"></path></svg>
321
+ <a href="#" style="color: var(--accent-gold); text-decoration: none;">ServantOfAllah</a>
322
+ <span>/</span>
323
+ <span class="repo-name">book-of-deeds</span>
324
+ <span class="badge-public">Public</span>
325
+ </div>
326
+
327
+ <div style="display: flex; gap: 1rem;">
328
+ <button style="background: #21262d; border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.3rem 0.8rem; border-radius: 6px; cursor: pointer; font-size: 0.8rem;">
329
+ ★ Star <span class="count-badge" id="star-count">0</span>
330
+ </button>
331
+ <button style="background: #21262d; border: 1px solid var(--border-color); color: var(--text-primary); padding: 0.3rem 0.8rem; border-radius: 6px; cursor: pointer; font-size: 0.8rem;">
332
+ ⑂ Fork <span class="count-badge" id="fork-count">0</span>
333
+ </button>
334
+ </div>
335
+ </header>
336
+
337
+ <div class="repo-tabs">
338
+ <a href="#" class="tab-item active" onclick="switchTab(event, 'code')">
339
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path fill-rule="evenodd" d="M4.75 2.5a.25.25 0 00-.25.25v2.5a.25.25 0 00.25.25h2.5a.25.25 0 00.25-.25v-2.5a.25.25 0 00-.25-.25h-2.5zM3.5 2.75C3.5 2.06 4.06 1.5 4.75 1.5h2.5c.69 0 1.25.56 1.25 1.25v2.5c0 .69-.56 1.25-1.25 1.25h-2.5c-.69 0-1.25-.56-1.25-1.25v-2.5z"></path><path fill-rule="evenodd" d="M4.75 9.5a.25.25 0 00-.25.25v2.5a.25.25 0 00.25.25h2.5a.25.25 0 00.25-.25v-2.5a.25.25 0 00-.25-.25h-2.5zM3.5 9.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25v2.5c0 .69-.56 1.25-1.25 1.25h-2.5c-.69 0-1.25-.56-1.25-1.25v-2.5z"></path><path fill-rule="evenodd" d="M9.75 2.5a.25.25 0 00-.25.25v2.5a.25.25 0 00.25.25h2.5a.25.25 0 00.25-.25v-2.5a.25.25 0 00-.25-.25h-2.5zM8.5 2.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25v2.5c0 .69-.56 1.25-1.25 1.25h-2.5c-.69 0-1.25-.56-1.25-1.25v-2.5z"></path></svg>
340
+ Deeds (Code)
341
+ </a>
342
+ <a href="#" class="tab-item" onclick="switchTab(event, 'issues')">
343
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"></path></svg>
344
+ Istighfar (Issues) <span class="count-badge">3</span>
345
+ </a>
346
+ <a href="#" class="tab-item" onclick="switchTab(event, 'pulls')">
347
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 114.5 0 2.25 2.25 0 01-4.5 0zm4.053 5.424c.045-.308-.232-.543-.534-.474-.203.046-.403.108-.597.186-.39.158-.94.44-1.49.96-.54.51-.97 1.21-1.2 1.83a.249.249 0 00.081.28c.15.112.308.203.473.272.507.211 1.252.348 2.065.348.813 0 1.558-.137 2.065-.348.165-.069.323-.16.473-.272a.249.249 0 00.081-.28c-.23-.62-.66-1.32-1.2-1.83-.55-.52-1.1-.802-1.49-.96-.194-.078-.394-.14-.597-.186-.302-.07-.58.166-.534.474a9.09 9.09 0 011.025 0zm-3.328 4.29c.732.305 1.636.462 2.625.462 2.513 0 4.15-1.357 4.854-2.478a2.008 2.008 0 00-.28-2.693c-.85-.807-1.867-1.163-2.73-1.332a7.614 7.614 0 00-3.69 0c-.862.17-1.88.525-2.73 1.332a2.008 2.008 0 00-.28 2.693c.704 1.12 2.341 2.478 4.854 2.478.118 0 .235-.002.35-.007-.117.005-.236.007-.353.007z"></path></svg>
348
+ Duas (Pull Requests) <span class="count-badge">∞</span>
349
+ </a>
350
+ <a href="#" class="tab-item" onclick="switchTab(event, 'actions')">
351
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zM6.379 5.227A.25.25 0 006 5.442v5.117a.25.25 0 00.379.214l4.264-2.559a.25.25 0 000-.428L6.379 5.227z"></path></svg>
352
+ Actions (Rituals)
353
+ </a>
354
+ <a href="#" class="tab-item" onclick="switchTab(event, 'projects')">
355
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path fill-rule="evenodd" d="M0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0114.25 15H1.75A1.75 1.75 0 010 13.25V2.75zm1.75-.25a.25.25 0 00-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25V2.75a.25.25 0 00-.25-.25H1.75zM1.5 10h13v1.5h-13V10zm13-3h-13v1.5h13V7z"></path></svg>
356
+ Projects (Goals)
357
+ </a>
358
+ <a href="#" class="tab-item" onclick="switchTab(event, 'wiki')">
359
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path fill-rule="evenodd" d="M2 2.5a.5.5 0 00-.5.5v1a.5.5 0 00.5.5h1a.5.5 0 00.5-.5V3a.5.5 0 00-.5-.5H2zM3 6H2v1h1V6zm0 3H2v1h1V9zm0 3H2v1h1v-1zm1.5-6h11a.5.5 0 00.5-.5v-1a.5.5 0 00-.5-.5h-11a.5.5 0 00-.5.5v1a.5.5 0 00.5.5zm.5 2.5a.5.5 0 00-.5.5v1a.5.5 0 00.5.5h11a.5.5 0 00.5-.5v-1a.5.5 0 00-.5-.5h-11zM16 12.5a.5.5 0 00-.5-.5h-11a.5.5 0 00-.5.5v1a.5.5 0 00.5.5h11a.5.5 0 00.5-.5v-1z"></path></svg>
360
+ Wiki (Ilm)
361
+ </a>
362
+ </div>
363
+
364
+ <main>
365
+ <div id="code" class="tab-content active code-layout">
366
+ <!-- Main Repo Content -->
367
+ <div class="content-col">
368
+
369
+ <div class="file-explorer">
370
+ <div class="commit-header">
371
+ <div class="commit-info">
372
+ <div class="avatar">U</div>
373
+ <span class="commit-message" id="latest-commit-msg">Loading latest deed...</span>
374
+ <span class="commit-hash" id="latest-commit-hash">...</span>
375
+ </div>
376
+ <div class="commit-time" id="latest-commit-time">Just now</div>
377
+ </div>
378
+
379
+ <ul class="file-list" id="repo-file-list">
380
+ <!-- JS will populate -->
381
+ <li class="file-item" style="justify-content: center; color: var(--text-secondary);">
382
+ Loading deeds...
383
+ </li>
384
+ </ul>
385
+ </div>
386
+
387
+ <div class="readme-container">
388
+ <div class="readme-header">
389
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor" style="vertical-align: text-bottom; margin-right: 0.5rem;"><path fill-rule="evenodd" d="M2 2.5a.5.5 0 00-.5.5v11a.5.5 0 00.5.5h9a.5.5 0 00.5-.5V4.5l-4-4H2zM3 4V3h4.5l.5.5v.5H3zM3 14V5h5v9H3z"></path></svg>
390
+ README.md
391
+ </div>
392
+ <div class="readme-content">
393
+ <h1>Book of Deeds</h1>
394
+ <p>Welcome to the central repository of my life. This project is a continuous integration of faith, actions, and self-improvement.</p>
395
+
396
+ <blockquote>
397
+ "And We have fastened every man's deeds to his neck, and on the Day of Resurrection, We shall bring out for him a book which he will find wide open." (17:13)
398
+ </blockquote>
399
+
400
+ <h3>Mission Statement</h3>
401
+ <p>To compile a bug-free record of good deeds, merge frequent pull requests (Duas), and deploy a successful soul to the Akhirah (Production Environment).</p>
402
+
403
+ <h3>Core Dependencies</h3>
404
+ <ul>
405
+ <li><code>Quran v1.0.0</code> (Immutable Source of Truth)</li>
406
+ <li><code>Sunnah.js</code> (Implementation Framework)</li>
407
+ <li><code>Taqwa-Guard</code> (Security Middleware)</li>
408
+ </ul>
409
+ </div>
410
+ </div>
411
+
412
+ <h3 style="margin-top: 2rem; font-size: 1rem; margin-bottom: 0.5rem;">Contribution Activity</h3>
413
+ <div class="contribution-graph">
414
+ <div class="graph-grid" id="contribution-grid">
415
+ <!-- JS will populate -->
416
+ </div>
417
+ <div style="display: flex; justify-content: flex-end; align-items: center; margin-top: 0.5rem; font-size: 0.75rem; color: var(--text-secondary);">
418
+ <span style="margin-right: 0.5rem;">Less</span>
419
+ <div class="graph-day level-0" style="margin-right: 2px;"></div>
420
+ <div class="graph-day level-1" style="margin-right: 2px;"></div>
421
+ <div class="graph-day level-2" style="margin-right: 2px;"></div>
422
+ <div class="graph-day level-3" style="margin-right: 2px;"></div>
423
+ <div class="graph-day level-4" style="margin-right: 0.5rem;"></div>
424
+ <span>More</span>
425
+ </div>
426
+ </div>
427
+
428
+ </div>
429
+
430
+ <!-- Sidebar -->
431
+ <div class="sidebar">
432
+ <div class="sidebar-section">
433
+ <h3>About</h3>
434
+ <p style="font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 1rem;">
435
+ A lifelong project to attain the pleasure of the Creator. Open source for the Ummah.
436
+ </p>
437
+
438
+ <div class="sidebar-item">
439
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor" style="margin-right: 0.5rem;"><path fill-rule="evenodd" d="M11.5 8a3.5 3.5 0 11-7 0 3.5 3.5 0 017 0zM8 11a3 3 0 100-6 3 3 0 000 6zM5 0a2 2 0 00-2 2v1.5H1.5A1.5 1.5 0 000 5v6a1.5 1.5 0 001.5 1.5h13A1.5 1.5 0 0016 11V5a1.5 1.5 0 00-1.5-1.5H13V2a2 2 0 00-2-2H5z"></path></svg>
440
+ <strong>2</strong> watching
441
+ </div>
442
+ <div class="sidebar-item">
443
+ <svg height="16" viewBox="0 0 16 16" width="16" fill="currentColor" style="margin-right: 0.5rem;"><path fill-rule="evenodd" d="M2.5 7.775V2.75a.25.25 0 01.25-.25h5.075a.75.75 0 00.53-.22l1.562-1.562a1.5 1.5 0 00-1.061-2.56H2.75A2.75 2.75 0 000 2.75v5.025a1.5 1.5 0 002.56 1.06l1.562-1.561a.75.75 0 00-.22-.53zM13.25 8.225a.25.25 0 01.25.25v5.075a.75.75 0 00-.22.53l-1.562 1.562a1.5 1.5 0 002.56 1.061h5.025A2.75 2.75 0 0022 13.96v-5.025a1.5 1.5 0 00-1.06-2.56l-1.562 1.561a.75.75 0 00.53.22zM7.775 2.5a.25.25 0 01.25.25v5.075a.75.75 0 00.22.53l1.562 1.562a1.5 1.5 0 002.56-1.061H7.29a2.75 2.75 0 00-2.75 2.75v5.025a1.5 1.5 0 001.06 2.56l1.562-1.561a.75.75 0 00-.53-.22zM2.5 13.96V8.935a.75.75 0 00-.22-.53l-1.562-1.562A1.5 1.5 0 002.56 4.28h5.025a.25.25 0 01.25.25v5.075a2.75 2.75 0 01-2.75 2.75H2.75a1.5 1.5 0 01-1.06-2.56l1.561-1.562a.75.75 0 01.53.22z"></path></svg>
444
+ 0 forks
445
+ </div>
446
+ </div>
447
+
448
+ <div class="sidebar-section">
449
+ <h3>Languages</h3>
450
+ <div class="lang-bar" id="lang-bar">
451
+ <!-- JS populated -->
452
+ </div>
453
+ <ul class="lang-list" id="lang-list">
454
+ <!-- JS populated -->
455
+ </ul>
456
+ </div>
457
+ </div>
458
+ </div> <!-- End of code tab -->
459
+
460
+ <!-- Other Tabs -->
461
+ <div id="issues" class="tab-content">
462
+ <div style="max-width: 800px; margin: 0 auto; padding-top: 2rem;">
463
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
464
+ <h2 style="font-family: var(--font-cinematic); color: var(--accent-gold);">Istighfar (Issues)</h2>
465
+ <button onclick="createNewIssue()" style="padding: 0.5rem 1rem; background: var(--accent-green); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;">
466
+ New Issue
467
+ </button>
468
+ </div>
469
+
470
+ <div class="issue-list" id="issue-list-container">
471
+ <div style="text-align: center; padding: 3rem; color: var(--text-secondary);">
472
+ Loading sins...
473
+ </div>
474
+ </div>
475
+ </div>
476
+ </div>
477
+
478
+ <div id="pulls" class="tab-content">
479
+ <div style="max-width: 800px; margin: 0 auto; padding-top: 2rem;">
480
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
481
+ <h2 style="font-family: var(--font-cinematic); color: var(--accent-gold);">Duas (Pull Requests)</h2>
482
+ <button onclick="createNewPull()" style="padding: 0.5rem 1rem; background: var(--accent-green); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;">
483
+ New Prayer Request
484
+ </button>
485
+ </div>
486
+
487
+ <div class="issue-list" id="pull-list-container">
488
+ <div style="text-align: center; padding: 3rem; color: var(--text-secondary);">
489
+ Loading duas...
490
+ </div>
491
+ </div>
492
+ </div>
493
+ </div>
494
+
495
+ <div id="actions" class="tab-content">
496
+ <div style="text-align: center; padding: 4rem; color: var(--text-secondary);">Coming Soon inshaAllah</div>
497
+ </div>
498
+ <div id="projects" class="tab-content">
499
+ <div style="text-align: center; padding: 4rem; color: var(--text-secondary);">Coming Soon inshaAllah</div>
500
+ </div>
501
+ <div id="wiki" class="tab-content">
502
+ <div style="text-align: center; padding: 4rem; color: var(--text-secondary);">Coming Soon inshaAllah</div>
503
+ </div>
504
+
505
+ <div id="toast" class="toast"></div>
506
+ </main>
507
+
508
+ <script>
509
+ // Tab Switching Logic
510
+ function switchTab(event, tabId) {
511
+ event.preventDefault();
512
+
513
+ // Update Tab Links
514
+ document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active'));
515
+ event.currentTarget.classList.add('active');
516
+
517
+ // Update Tab Content
518
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
519
+ const activeContent = document.getElementById(tabId);
520
+
521
+ if(activeContent) {
522
+ activeContent.classList.add('active');
523
+ } else {
524
+ showToast(`The ${tabId} section is currently under construction.`);
525
+ }
526
+ }
527
+
528
+ function showToast(message) {
529
+ const toast = document.getElementById('toast');
530
+ if(!toast) return;
531
+ toast.textContent = message;
532
+ toast.style.display = 'block';
533
+ setTimeout(() => {
534
+ toast.style.display = 'none';
535
+ }, 3000);
536
+ }
537
+
538
+ // Utility: Format Date
539
+ function timeAgo(dateString) {
540
+ const date = new Date(dateString);
541
+ const now = new Date();
542
+ const seconds = Math.floor((now - date) / 1000);
543
+
544
+ if (seconds < 60) return "just now";
545
+ const minutes = Math.floor(seconds / 60);
546
+ if (minutes < 60) return `${minutes} minutes ago`;
547
+ const hours = Math.floor(minutes / 60);
548
+ if (hours < 24) return `${hours} hours ago`;
549
+ const days = Math.floor(hours / 24);
550
+ return `${days} days ago`;
551
+ }
552
+
553
+ // Fetch commits from API
554
+ async function loadData() {
555
+ try {
556
+ const response = await fetch('/api/commits');
557
+ if (!response.ok) throw new Error('Network response was not ok');
558
+ const commits = await response.json();
559
+ renderData(commits);
560
+ } catch (e) {
561
+ console.log("Could not load commits, using demo data", e);
562
+ renderDemoData();
563
+ }
564
+
565
+ // Load Issues
566
+ loadIssues();
567
+
568
+ // Load Pulls
569
+ loadPulls();
570
+ }
571
+
572
+ async function loadIssues() {
573
+ const container = document.getElementById('issue-list-container');
574
+ try {
575
+ const res = await fetch('/api/issues');
576
+ if(!res.ok) throw new Error();
577
+ const issues = await res.json();
578
+
579
+ if(issues.length === 0) {
580
+ container.innerHTML = `<div style="text-align: center; padding: 3rem; color: var(--text-secondary);">No open sins recorded. Al-Hamdulillah.</div>`;
581
+ return;
582
+ }
583
+
584
+ let html = '<ul class="issue-list">';
585
+ issues.reverse().forEach(issue => {
586
+ html += `
587
+ <li class="issue-item">
588
+ <div style="padding-top: 4px;">
589
+ <svg height="16" viewBox="0 0 16 16" width="16" style="color: var(--accent-red);"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"></path></svg>
590
+ </div>
591
+ <div>
592
+ <span class="issue-title">${issue.title}</span>
593
+ <div class="issue-meta">
594
+ #${issue.id} opened ${timeAgo(issue.timestamp)} by ServantOfAllah
595
+ </div>
596
+ </div>
597
+ </li>
598
+ `;
599
+ });
600
+ html += '</ul>';
601
+ container.innerHTML = html;
602
+
603
+ } catch (e) {
604
+ renderDemoIssues();
605
+ }
606
+ }
607
+
608
+ async function loadPulls() {
609
+ const container = document.getElementById('pull-list-container');
610
+ try {
611
+ const res = await fetch('/api/pulls');
612
+ if(!res.ok) throw new Error();
613
+ const pulls = await res.json();
614
+
615
+ if(pulls.length === 0) {
616
+ container.innerHTML = `<div style="text-align: center; padding: 3rem; color: var(--text-secondary);">No pending supplications.</div>`;
617
+ return;
618
+ }
619
+ renderPullsList(pulls);
620
+ } catch (e) {
621
+ renderDemoPulls();
622
+ }
623
+ }
624
+
625
+ function renderPullsList(pulls) {
626
+ const container = document.getElementById('pull-list-container');
627
+ let html = '<ul class="issue-list">';
628
+ pulls.reverse().forEach(pull => {
629
+ html += `
630
+ <li class="issue-item">
631
+ <div style="padding-top: 4px;">
632
+ <svg height="16" viewBox="0 0 16 16" width="16" style="color: var(--accent-green);"><path fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 114.5 0 2.25 2.25 0 01-4.5 0zm4.053 5.424c.045-.308-.232-.543-.534-.474-.203.046-.403.108-.597.186-.39.158-.94.44-1.49.96-.54.51-.97 1.21-1.2 1.83a.249.249 0 00.081.28c.15.112.308.203.473.272.507.211 1.252.348 2.065.348.813 0 1.558-.137 2.065-.348.165-.069.323-.16.473-.272a.249.249 0 00.081-.28c-.23-.62-.66-1.32-1.2-1.83-.55-.52-1.1-.802-1.49-.96-.194-.078-.394-.14-.597-.186-.302-.07-.58.166-.534.474a9.09 9.09 0 011.025 0zm-3.328 4.29c.732.305 1.636.462 2.625.462 2.513 0 4.15-1.357 4.854-2.478a2.008 2.008 0 00-.28-2.693c-.85-.807-1.867-1.163-2.73-1.332a7.614 7.614 0 00-3.69 0c-.862.17-1.88.525-2.73 1.332a2.008 2.008 0 00-.28 2.693c.704 1.12 2.341 2.478 4.854 2.478.118 0 .235-.002.35-.007-.117.005-.236.007-.353.007z"></path></svg>
633
+ </div>
634
+ <div>
635
+ <span class="issue-title">${pull.title}</span>
636
+ <div class="issue-meta">
637
+ #${pull.id} opened ${timeAgo(pull.timestamp)} by ServantOfAllah • Pending Review
638
+ </div>
639
+ </div>
640
+ </li>
641
+ `;
642
+ });
643
+ html += '</ul>';
644
+ container.innerHTML = html;
645
+ }
646
+
647
+ function renderIssuesList(issues) {
648
+ const container = document.getElementById('issue-list-container');
649
+ let html = '<ul class="issue-list">';
650
+ issues.reverse().forEach(issue => {
651
+ html += `
652
+ <li class="issue-item">
653
+ <div style="padding-top: 4px;">
654
+ <svg height="16" viewBox="0 0 16 16" width="16" style="color: var(--accent-red);"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"></path></svg>
655
+ </div>
656
+ <div>
657
+ <span class="issue-title">${issue.title}</span>
658
+ <div class="issue-meta">
659
+ #${issue.id} opened ${timeAgo(issue.timestamp)} by ServantOfAllah
660
+ </div>
661
+ </div>
662
+ </li>
663
+ `;
664
+ });
665
+ html += '</ul>';
666
+ container.innerHTML = html;
667
+ }
668
+
669
+ function renderDemoIssues() {
670
+ const demoIssues = [
671
+ { id: "101", title: "Spoke harsh words to a sibling", timestamp: new Date(Date.now() - 3600000).toISOString() },
672
+ { id: "99", title: "Delayed Asr prayer without valid reason", timestamp: new Date(Date.now() - 86400000).toISOString() },
673
+ { id: "42", title: "Wasted time on frivolous entertainment", timestamp: new Date(Date.now() - 172800000).toISOString() }
674
+ ];
675
+ renderIssuesList(demoIssues);
676
+ }
677
+
678
+ function renderDemoPulls() {
679
+ const demoPulls = [
680
+ { id: "777", title: "Grant me beneficial knowledge", timestamp: new Date().toISOString() },
681
+ { id: "555", title: "Forgive my parents and have mercy on them", timestamp: new Date(Date.now() - 7200000).toISOString() },
682
+ { id: "313", title: "Fix the bug in my heart (remove envy)", timestamp: new Date(Date.now() - 43200000).toISOString() }
683
+ ];
684
+ renderPullsList(demoPulls);
685
+ }
686
+
687
+ async function createNewIssue() {
688
+ const title = prompt("Confess a mistake (Log Issue):");
689
+ if(!title) return;
690
+
691
+ try {
692
+ const res = await fetch('/api/issues', {
693
+ method: 'POST',
694
+ headers: { 'Content-Type': 'application/json' },
695
+ body: JSON.stringify({ title })
696
+ });
697
+ if(res.ok) {
698
+ showToast("Issue logged. May Allah forgive you.");
699
+ loadIssues();
700
+ } else { throw new Error(); }
701
+ } catch(e) {
702
+ showToast("Demo Mode: Issue logged (simulated).");
703
+ // In demo mode, we just re-render with the new item added to top locally if we wanted,
704
+ // but for now just showing toast is enough to indicate interaction works.
705
+ }
706
+ }
707
+
708
+ async function createNewPull() {
709
+ const title = prompt("What is your request? (Dua):");
710
+ if(!title) return;
711
+
712
+ try {
713
+ const res = await fetch('/api/pulls', {
714
+ method: 'POST',
715
+ headers: { 'Content-Type': 'application/json' },
716
+ body: JSON.stringify({ title })
717
+ });
718
+ if(res.ok) {
719
+ showToast("Dua submitted. Trust in His plan.");
720
+ loadPulls();
721
+ } else { throw new Error(); }
722
+ } catch(e) { showToast("Demo Mode: Dua submitted (simulated)."); }
723
+ }
724
+
725
+ function renderDemoData() {
726
+ const demoCommits = [
727
+ { timestamp: new Date().toISOString(), message: "Smiled at a stranger", hash: "a1b2c3d" },
728
+ { timestamp: new Date(Date.now() - 86400000).toISOString(), message: "Prayed Fajr on time", hash: "e5f6g7h" },
729
+ { timestamp: new Date(Date.now() - 172800000).toISOString(), message: "Gave charity", hash: "i8j9k0l" },
730
+ { timestamp: new Date(Date.now() - 259200000).toISOString(), message: "Read Surah Kahf", hash: "m1n2o3p" },
731
+ { timestamp: new Date(Date.now() - 345600000).toISOString(), message: "Helped parents", hash: "q4r5s6t" },
732
+ ];
733
+ renderData(demoCommits);
734
+
735
+ // Add a demo badge
736
+ const header = document.querySelector('header');
737
+ if(header) {
738
+ const badge = document.createElement('div');
739
+ badge.style.position = 'absolute';
740
+ badge.style.top = '0';
741
+ badge.style.left = '50%';
742
+ badge.style.transform = 'translateX(-50%)';
743
+ badge.style.background = 'var(--accent-gold)';
744
+ badge.style.color = '#000';
745
+ badge.style.padding = '0.2rem 1rem';
746
+ badge.style.fontSize = '0.7rem';
747
+ badge.style.fontWeight = 'bold';
748
+ badge.style.borderBottomLeftRadius = '4px';
749
+ badge.style.borderBottomRightRadius = '4px';
750
+ badge.textContent = 'DEMO MODE';
751
+ header.appendChild(badge);
752
+ }
753
+ }
754
+
755
+ function renderData(commits) {
756
+ // Update Sidebar Stats
757
+ const starCount = document.getElementById('star-count');
758
+ if(starCount) starCount.textContent = commits.length;
759
+
760
+ const forkCount = document.getElementById('fork-count');
761
+ if(forkCount) forkCount.textContent = "0";
762
+
763
+ if (commits.length > 0) {
764
+ const latest = commits[commits.length - 1];
765
+ document.getElementById('latest-commit-msg').textContent = latest.message;
766
+ document.getElementById('latest-commit-time').textContent = timeAgo(latest.timestamp);
767
+ document.getElementById('latest-commit-hash').textContent = latest.hash;
768
+ } else {
769
+ document.getElementById('latest-commit-msg').textContent = "No deeds recorded yet";
770
+ document.getElementById('latest-commit-hash').textContent = "";
771
+ }
772
+
773
+ renderFileList(commits);
774
+ renderGraph(commits);
775
+ renderLanguages(commits);
776
+ }
777
+
778
+ function renderFileList(commits) {
779
+ const list = document.getElementById('repo-file-list');
780
+ if(!list) return;
781
+ list.innerHTML = '';
782
+
783
+ // Show last 7 deeds as "files"
784
+ // If no deeds, show a placeholder or the "Core" structure
785
+
786
+ let displayCommits = [];
787
+ if (commits.length === 0) {
788
+ // Metaphorical default files if empty
789
+ displayCommits = [
790
+ { message: "Foundation of Tawheed", timestamp: new Date().toISOString(), isStatic: true, path: "core/beliefs" },
791
+ { message: "Intention set", timestamp: new Date().toISOString(), isStatic: true, path: "niyyah.config.json" }
792
+ ];
793
+ } else {
794
+ displayCommits = [...commits].reverse().slice(0, 10);
795
+ }
796
+
797
+ displayCommits.forEach(c => {
798
+ const li = document.createElement('li');
799
+ li.className = 'file-item';
800
+
801
+ // If it's a real commit, generate a path
802
+ let path = c.path;
803
+ let iconColor = "#54aeff"; // folder blue
804
+ let isFolder = false;
805
+
806
+ if (!path) {
807
+ // Generate pseudo-path based on content
808
+ const msg = c.message.toLowerCase();
809
+ if (msg.includes("pray") || msg.includes("salah") || msg.includes("fajr")) {
810
+ path = `src/worship/${c.message.replace(/\s+/g, '_').substring(0,15)}.ts`;
811
+ iconColor = "#2ea043"; // green
812
+ } else if (msg.includes("read") || msg.includes("quran") || msg.includes("study")) {
813
+ path = `src/ilm/${c.message.replace(/\s+/g, '_').substring(0,15)}.md`;
814
+ iconColor = "#d4af37"; // gold
815
+ } else if (msg.includes("help") || msg.includes("charity") || msg.includes("give")) {
816
+ path = `src/sadaqah/${c.message.replace(/\s+/g, '_').substring(0,15)}.tsx`;
817
+ iconColor = "#db6d28"; // orange
818
+ } else {
819
+ path = `src/deeds/${c.message.replace(/\s+/g, '_').substring(0,15)}.ts`;
820
+ }
821
+ }
822
+
823
+ const timeDisplay = c.isStatic ? "Eternal" : timeAgo(c.timestamp);
824
+
825
+ // SVG Icon
826
+ const svgIcon = `<svg aria-label="File" height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path fill-rule="evenodd" d="M3.75 1.5a.25.25 0 00-.25.25v11.5c0 .138.112.25.25.25h8.5a.25.25 0 00.25-.25V6H9.75A1.75 1.75 0 018 4.25V1.5H3.75zm5.75.56v2.19c0 .138.112.25.25.25h2.19L9.5 2.06zM2 1.75C2 .784 2.784 0 3.75 0h6.5c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0113.25 16H3.75A1.75 1.75 0 012 14.25V1.75z"></path></svg>`;
827
+
828
+ li.innerHTML = `
829
+ <span class="file-icon" style="color: ${iconColor}">
830
+ ${svgIcon}
831
+ </span>
832
+ <a href="#" class="file-name" style="color: var(--text-primary); text-decoration: none;">${path}</a>
833
+ <span class="file-desc">${c.message}</span>
834
+ <span class="file-time">${timeDisplay}</span>
835
+ `;
836
+ list.appendChild(li);
837
+ });
838
+
839
+ // Always add README.md
840
+ const readme = document.createElement('li');
841
+ readme.className = 'file-item';
842
+ readme.innerHTML = `
843
+ <span class="file-icon">
844
+ <svg aria-label="File" height="16" viewBox="0 0 16 16" width="16" fill="currentColor"><path fill-rule="evenodd" d="M3.75 1.5a.25.25 0 00-.25.25v11.5c0 .138.112.25.25.25h8.5a.25.25 0 00.25-.25V6H9.75A1.75 1.75 0 018 4.25V1.5H3.75zm5.75.56v2.19c0 .138.112.25.25.25h2.19L9.5 2.06zM2 1.75C2 .784 2.784 0 3.75 0h6.5c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0113.25 16H3.75A1.75 1.75 0 012 14.25V1.75z"></path></svg>
845
+ </span>
846
+ <a href="#" class="file-name">README.md</a>
847
+ <span class="file-desc">Initial commit</span>
848
+ <span class="file-time">Creation</span>
849
+ `;
850
+ list.appendChild(readme);
851
+ }
852
+
853
+ function renderLanguages(commits) {
854
+ const counts = { Tawakkul: 0, Sabr: 0, Jihad: 0, Ibadah: 0, Ilm: 0, Sadaqah: 0, Other: 0 };
855
+
856
+ commits.forEach(c => {
857
+ const msg = c.message.toLowerCase();
858
+ if (msg.includes("trust") || msg.includes("allah") || msg.includes("plan")) counts.Tawakkul++;
859
+ else if (msg.includes("patience") || msg.includes("wait") || msg.includes("sabr") || msg.includes("calm") || msg.includes("anger")) counts.Sabr++;
860
+ else if (msg.includes("struggle") || msg.includes("fight") || msg.includes("hard") || msg.includes("nafs") || msg.includes("sin")) counts.Jihad++;
861
+ else if (msg.includes("pray") || msg.includes("salah") || msg.includes("namaz") || msg.includes("fajr") || msg.includes("dhikr") || msg.includes("fast") || msg.includes("sunnah")) counts.Ibadah++;
862
+ else if (msg.includes("read") || msg.includes("learn") || msg.includes("study") || msg.includes("book") || msg.includes("quran") || msg.includes("listen")) counts.Ilm++;
863
+ else if (msg.includes("give") || msg.includes("help") || msg.includes("money") || msg.includes("smile") || msg.includes("feed") || msg.includes("donate") || msg.includes("mother") || msg.includes("father") || msg.includes("family") || msg.includes("call") || msg.includes("visit")) counts.Sadaqah++;
864
+ else counts.Other++;
865
+ });
866
+
867
+ const total = commits.length || 1; // avoid divide by zero
868
+
869
+ // Convert to array and sort
870
+ const sorted = Object.entries(counts)
871
+ .filter(([_, count]) => count > 0)
872
+ .sort((a, b) => b[1] - a[1]);
873
+
874
+ // If empty, show default
875
+ if (sorted.length === 0) {
876
+ sorted.push(["Niyyah", 1]);
877
+ }
878
+
879
+ const bar = document.getElementById('lang-bar');
880
+ const list = document.getElementById('lang-list');
881
+ if(bar) bar.innerHTML = '';
882
+ if(list) list.innerHTML = '';
883
+
884
+ const colors = {
885
+ Tawakkul: '#d4af37', // Gold
886
+ Sabr: '#2ea043', // Green
887
+ Jihad: '#da3633', // Red
888
+ Ibadah: '#a371f7', // Purple
889
+ Ilm: '#54aeff', // Blue
890
+ Sadaqah: '#db6d28', // Orange
891
+ Other: '#8b949e', // Grey
892
+ Niyyah: '#ffffff'
893
+ };
894
+
895
+ sorted.forEach(([lang, count]) => {
896
+ const percent = ((count / total) * 100).toFixed(1);
897
+
898
+ // Bar segment
899
+ const segment = document.createElement('div');
900
+ segment.className = 'lang-segment';
901
+ segment.style.width = `${percent}%`;
902
+ segment.style.background = colors[lang] || colors.Other;
903
+ bar.appendChild(segment);
904
+
905
+ // List item
906
+ const li = document.createElement('li');
907
+ li.style.fontSize = '0.8rem';
908
+ li.style.color = 'var(--text-secondary)';
909
+ li.innerHTML = `<span class="lang-dot" style="background: ${colors[lang] || colors.Other};"></span>${lang} ${percent}%`;
910
+ list.appendChild(li);
911
+ });
912
+ }
913
+
914
+ function renderGraph(commits) {
915
+ const grid = document.getElementById('contribution-grid');
916
+ grid.innerHTML = '';
917
+
918
+ const activity = {};
919
+ commits.forEach(c => {
920
+ const date = c.timestamp.split('T')[0];
921
+ activity[date] = (activity[date] || 0) + 1;
922
+ });
923
+
924
+ // Get start date (365 days ago)
925
+ const today = new Date();
926
+ const startDate = new Date();
927
+ startDate.setDate(today.getDate() - 365);
928
+
929
+ for (let i = 0; i < 371; i++) {
930
+ const currentDate = new Date(startDate);
931
+ currentDate.setDate(startDate.getDate() + i);
932
+ const dateStr = currentDate.toISOString().split('T')[0];
933
+
934
+ const div = document.createElement('div');
935
+ const count = activity[dateStr] || 0;
936
+
937
+ let level = 'level-0';
938
+ if (count > 0) level = 'level-1';
939
+ if (count > 2) level = 'level-2';
940
+ if (count > 4) level = 'level-3';
941
+ if (count > 6) level = 'level-4';
942
+
943
+ div.className = `graph-day ${level}`;
944
+ div.title = `${count} deeds on ${dateStr}`;
945
+ grid.appendChild(div);
946
+ }
947
+ }
948
+
949
+ loadData();
950
+ </script>
951
+ </body>
952
+ </html>