@rahul05ranjan/dhruv-cli 1.4.6 → 1.6.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 (76) hide show
  1. package/.github/workflows/ci.yml +18 -238
  2. package/.github/workflows/contribution.yml +6 -141
  3. package/.github/workflows/dependabot-auto-merge.yml +1 -0
  4. package/.github/workflows/labeler.yml +1 -0
  5. package/.github/workflows/security.yml +3 -0
  6. package/CHANGELOG.md +16 -4
  7. package/README.md +145 -40
  8. package/__tests__/cli-contract.test.ts +170 -0
  9. package/__tests__/core.test.ts +193 -1
  10. package/__tests__/diagnostics.test.ts +179 -0
  11. package/__tests__/file-workflows.test.ts +234 -0
  12. package/__tests__/interactive.test.ts +134 -0
  13. package/__tests__/setup.ts +1 -0
  14. package/__tests__/workflows.test.ts +27 -4
  15. package/dist/commands/generate.d.ts +6 -1
  16. package/dist/commands/generate.js +44 -11
  17. package/dist/commands/health.d.ts +4 -1
  18. package/dist/commands/health.js +59 -16
  19. package/dist/commands/init.js +18 -7
  20. package/dist/commands/menu.js +125 -100
  21. package/dist/commands/metrics.d.ts +5 -1
  22. package/dist/commands/metrics.js +49 -10
  23. package/dist/commands/optimize.js +1 -1
  24. package/dist/commands/review.d.ts +4 -1
  25. package/dist/commands/review.js +66 -9
  26. package/dist/commands/security-check.d.ts +4 -1
  27. package/dist/commands/security-check.js +100 -6
  28. package/dist/commands/status.js +44 -4
  29. package/dist/config/config.d.ts +5 -1
  30. package/dist/config/config.js +24 -7
  31. package/dist/core/ai.d.ts +11 -0
  32. package/dist/core/ai.js +33 -9
  33. package/dist/core/command-catalog.d.ts +10 -0
  34. package/dist/core/command-catalog.js +27 -0
  35. package/dist/core/command-runner.js +106 -21
  36. package/dist/core/logger.js +1 -0
  37. package/dist/core/metrics.d.ts +28 -0
  38. package/dist/core/metrics.js +79 -0
  39. package/dist/index.js +98 -24
  40. package/dist/utils/projectType.d.ts +7 -1
  41. package/dist/utils/projectType.js +91 -13
  42. package/docs/api/assets/highlight.css +4 -4
  43. package/docs/api/index.html +161 -39
  44. package/docs/api/media/CONTRIBUTING.md +60 -0
  45. package/docs/api/media/SECURITY.md +8 -0
  46. package/docs/api/media/dhruv-cli-preview.svg +42 -0
  47. package/docs/api/media/publishing-fix.md +34 -0
  48. package/docs/dhruv-cli-preview.svg +42 -0
  49. package/docs/index.html +631 -533
  50. package/docs/publishing-fix.md +34 -0
  51. package/package.json +1 -1
  52. package/src/commands/generate.ts +50 -11
  53. package/src/commands/health.ts +62 -17
  54. package/src/commands/init.ts +18 -7
  55. package/src/commands/menu.ts +54 -30
  56. package/src/commands/metrics.ts +53 -9
  57. package/src/commands/optimize.ts +1 -1
  58. package/src/commands/review.ts +72 -9
  59. package/src/commands/security-check.ts +111 -6
  60. package/src/commands/status.ts +43 -5
  61. package/src/config/config.ts +26 -7
  62. package/src/core/ai.ts +36 -8
  63. package/src/core/command-catalog.ts +37 -0
  64. package/src/core/command-runner.ts +108 -22
  65. package/src/core/logger.ts +1 -0
  66. package/src/core/metrics.ts +105 -0
  67. package/src/index.ts +97 -24
  68. package/src/utils/projectType.ts +85 -9
  69. package/tsconfig.json +1 -1
  70. package/.github/workflows/auto-assign.yml +0 -14
  71. package/.github/workflows/build-publish.yml +0 -154
  72. package/.github/workflows/deploy.yml +0 -336
  73. package/.github/workflows/monitoring.yml +0 -270
  74. package/PUBLISHING_FIX.md +0 -92
  75. package/logs/.8a99b6cf655346317fdbf29f4fffcf91131432f3-audit.json +0 -15
  76. package/logs/.eee104bf8fff5ecd38a6a2842df260de6470a7c3-audit.json +0 -15
package/docs/index.html CHANGED
@@ -1,537 +1,635 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
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>Dhruv CLI - AI-Powered Developer Assistant</title>
7
- <link href="https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;700&family=Inter:wght@400;700&display=swap" rel="stylesheet">
8
- <link rel="icon" href="https://cdn.jsdelivr.net/gh/rahul05ranjan/dhruv-cli/docs/dhruv-favicon.png">
9
- <style>
10
- :root {
11
- --primary: #eebf63;
12
- --bg: #181c2f;
13
- --bg2: #232946;
14
- --text: #f4f4f4;
15
- --text2: #a1a6b4;
16
- --accent: #5f6fff;
17
- --shadow: 0 4px 24px #0003;
18
- --radius: 1rem;
19
- --transition: 0.2s cubic-bezier(.4,2,.6,1);
20
- }
21
- [data-theme="light"] {
22
- --bg: #f4f4f4;
23
- --bg2: #fff;
24
- --text: #232946;
25
- --text2: #555;
26
- --accent: #5f6fff;
27
- --shadow: 0 4px 24px #0001;
28
- }
29
- html, body {
30
- height: 100%;
31
- margin: 0;
32
- padding: 0;
33
- font-family: 'Inter', Arial, sans-serif;
34
- background: linear-gradient(135deg, var(--bg) 0%, var(--bg2) 100%);
35
- color: var(--text);
36
- transition: background 0.5s, color 0.5s;
37
- }
38
- body {
39
- min-height: 100vh;
40
- display: flex;
41
- flex-direction: column;
42
- }
43
- .sidebar {
44
- position: fixed;
45
- top: 0; left: 0;
46
- width: 220px;
47
- height: 100vh;
48
- background: var(--bg2);
49
- box-shadow: 2px 0 16px #0002;
50
- padding: 2rem 1rem 1rem 1rem;
51
- z-index: 10;
52
- display: flex;
53
- flex-direction: column;
54
- gap: 1.5rem;
55
- transition: left var(--transition);
56
- }
57
- .sidebar h2 {
58
- font-size: 1.3rem;
59
- color: var(--primary);
60
- margin: 0 0 1rem 0;
61
- letter-spacing: 1px;
62
- }
63
- .sidebar nav {
64
- display: flex;
65
- flex-direction: column;
66
- gap: 0.7rem;
67
- }
68
- .sidebar a {
69
- color: var(--text2);
70
- text-decoration: none;
71
- font-weight: 500;
72
- padding: 0.3em 0.7em;
73
- border-radius: 6px;
74
- transition: background var(--transition), color var(--transition);
75
- }
76
- .sidebar a.active, .sidebar a:hover {
77
- background: var(--primary);
78
- color: var(--bg2);
79
- }
80
- .theme-toggle {
81
- margin-top: auto;
82
- display: flex;
83
- align-items: center;
84
- gap: 0.5em;
85
- cursor: pointer;
86
- color: var(--primary);
87
- font-size: 1.1em;
88
- user-select: none;
89
- }
90
- .main {
91
- margin-left: 240px;
92
- padding: 2.5rem 2rem 2rem 2rem;
93
- flex: 1;
94
- transition: margin-left var(--transition);
95
- }
96
- @media (max-width: 900px) {
97
- .sidebar { position: static; width: 100vw; height: auto; flex-direction: row; align-items: center; padding: 1rem; }
98
- .sidebar nav { flex-direction: row; gap: 1rem; }
99
- .main { margin-left: 0; padding: 1.5rem 0.5rem; }
100
- }
101
- .hero {
102
- text-align: center;
103
- margin-bottom: 2.5rem;
104
- }
105
- .hero-logo {
106
- width: 70px; height: 70px; margin-bottom: 1rem;
107
- filter: drop-shadow(0 2px 8px #0005);
108
- border-radius: 50%;
109
- background: var(--bg2);
110
- display: inline-block;
111
- padding: 0.5rem;
112
- }
113
- .hero-title {
114
- font-size: 2.7rem;
115
- font-weight: 700;
116
- margin: 0 0 0.5rem 0;
117
- letter-spacing: 1px;
118
- }
119
- .hero-animated {
120
- font-size: 1.3rem;
121
- color: var(--primary);
122
- min-height: 2.2em;
123
- font-family: 'Fira Mono', monospace;
124
- margin-bottom: 1.2rem;
125
- animation: fadeIn 1.2s;
126
- }
127
- @keyframes fadeIn {
128
- from { opacity: 0; transform: translateY(20px); }
129
- to { opacity: 1; transform: none; }
130
- }
131
- .hero-install {
132
- display: inline-block;
133
- background: var(--primary);
134
- color: var(--bg2);
135
- font-family: 'Fira Mono', monospace;
136
- font-size: 1.1em;
137
- padding: 0.5em 1.2em;
138
- border-radius: 8px;
139
- margin-bottom: 1.5rem;
140
- box-shadow: 0 2px 8px #0002;
141
- cursor: pointer;
142
- transition: background var(--transition), color var(--transition);
143
- border: none;
144
- }
145
- .hero-install:hover { background: var(--accent); color: #fff; }
146
- .badges {
147
- margin: 1.2rem 0 0.5rem 0;
148
- display: flex;
149
- justify-content: center;
150
- gap: 0.7rem;
151
- flex-wrap: wrap;
152
- }
153
- .badges img { height: 28px; }
154
- .terminal-demo {
155
- background: #181c2f;
156
- border-radius: 1rem;
157
- box-shadow: var(--shadow);
158
- max-width: 600px;
159
- margin: 2rem auto 2.5rem auto;
160
- padding: 1.5rem 1.2rem 1.2rem 1.2rem;
161
- font-family: 'Fira Mono', monospace;
162
- color: #eebf63;
163
- position: relative;
164
- overflow: hidden;
165
- }
166
- .terminal-bar {
167
- display: flex;
168
- align-items: center;
169
- gap: 0.5em;
170
- margin-bottom: 1em;
171
- }
172
- .term-dot { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
173
- .term-dot.red { background: #ff5f56; }
174
- .term-dot.yellow { background: #ffbd2e; }
175
- .term-dot.green { background: #27c93f; }
176
- .terminal-content {
177
- min-height: 2.5em;
178
- font-size: 1.1em;
179
- white-space: pre-line;
180
- word-break: break-word;
181
- transition: color 0.2s;
182
- }
183
- .copy-btn {
184
- position: absolute;
185
- top: 1.2rem; right: 1.2rem;
186
- background: var(--primary);
187
- color: var(--bg2);
188
- border: none;
189
- border-radius: 6px;
190
- padding: 0.2em 0.7em;
191
- font-size: 1em;
192
- cursor: pointer;
193
- transition: background var(--transition), color var(--transition);
194
- opacity: 0.85;
195
- }
196
- .copy-btn:hover { background: var(--accent); color: #fff; }
197
- .tabs {
198
- display: flex;
199
- gap: 1.2rem;
200
- margin-bottom: 1.5rem;
201
- border-bottom: 2px solid var(--bg2);
202
- flex-wrap: wrap;
203
- }
204
- .tab {
205
- padding: 0.5em 1.2em;
206
- background: none;
207
- border: none;
208
- color: var(--text2);
209
- font-size: 1.1em;
210
- font-weight: 500;
211
- border-radius: 8px 8px 0 0;
212
- cursor: pointer;
213
- transition: background var(--transition), color var(--transition);
214
- outline: none;
215
- }
216
- .tab.active, .tab:hover {
217
- background: var(--primary);
218
- color: var(--bg2);
219
- }
220
- .tab-content { display: none; animation: fadeIn 0.7s; }
221
- .tab-content.active { display: block; }
222
- .collapsible {
223
- background: var(--bg2);
224
- color: var(--text);
225
- cursor: pointer;
226
- padding: 1em;
227
- width: 100%;
228
- border: none;
229
- text-align: left;
230
- outline: none;
231
- font-size: 1.1em;
232
- border-radius: 8px;
233
- margin-bottom: 0.5em;
234
- transition: background var(--transition);
235
- }
236
- .collapsible.active, .collapsible:hover {
237
- background: var(--primary);
238
- color: var(--bg2);
239
- }
240
- .collapsible-content {
241
- padding: 0 1em 1em 1em;
242
- display: none;
243
- background: var(--bg2);
244
- border-radius: 0 0 8px 8px;
245
- margin-bottom: 1em;
246
- animation: fadeIn 0.5s;
247
- }
248
- .collapsible-content.active { display: block; }
249
- code, pre {
250
- font-family: 'Fira Mono', monospace;
251
- background: #181c2f;
252
- color: #eebf63;
253
- border-radius: 6px;
254
- padding: 0.2em 0.5em;
255
- font-size: 1em;
256
- }
257
- .plugin-example {
258
- background: #181c2f;
259
- border-left: 4px solid var(--primary);
260
- padding: 1rem;
261
- margin: 1rem 0;
262
- border-radius: 0.5rem;
263
- font-size: 1em;
264
- color: var(--text);
265
- }
266
- .community {
267
- margin: 2.5rem 0 1.5rem 0;
268
- text-align: center;
269
- }
270
- .community a {
271
- color: var(--primary);
272
- text-decoration: none;
273
- font-weight: 600;
274
- margin: 0 0.7em;
275
- transition: color var(--transition);
276
- }
277
- .community a:hover { color: var(--accent); }
278
- footer {
279
- text-align: center;
280
- color: var(--text2);
281
- padding: 2rem 0 1rem 0;
282
- font-size: 1rem;
283
- }
284
- @media (max-width: 700px) {
285
- .main { padding: 1rem 0.2rem; }
286
- .sidebar { padding: 0.7rem; }
287
- .hero-title { font-size: 2rem; }
288
- .terminal-demo { padding: 1rem 0.5rem; }
289
- }
290
- </style>
291
- </head>
292
- <body>
293
- <div class="sidebar">
294
- <h2>Dhruv CLI</h2>
295
- <nav>
296
- <a href="#hero" class="active">Home</a>
297
- <a href="#features">Features</a>
298
- <a href="#install">Install</a>
299
- <a href="#usage">Usage</a>
300
- <a href="#plugins">Plugins</a>
301
- <a href="#community">Community</a>
302
- <a href="https://www.npmjs.com/package/@rahul05ranjan/dhruv-cli" target="_blank">NPM</a>
303
- </nav>
304
- <div class="theme-toggle" id="themeToggle" title="Toggle dark/light mode">
305
- <span id="themeIcon">🌙</span> <span id="themeLabel">Dark</span>
306
- </div>
307
- </div>
308
- <main class="main">
309
- <section class="hero" id="hero">
310
- <img src="https://cdn.jsdelivr.net/gh/rahul05ranjan/dhruv-cli/docs/dhruv-favicon.png" alt="Dhruv CLI Logo" class="hero-logo" />
311
- <div class="hero-title">Dhruv CLI</div>
312
- <div class="hero-animated" id="animatedTagline"></div>
313
- <button class="hero-install" onclick="copyInstall()" id="installBtn">npm install -g @rahul05ranjan/dhruv-cli<span style='font-size:0.9em;' id='copyIcon'>📋</span></button>
314
- <div style="margin-top:0.7em;">
315
- <a href="https://www.npmjs.com/package/@rahul05ranjan/dhruv-cli" target="_blank" style="color:#eebf63;font-weight:bold;">View on npm</a>
316
- </div>
317
- <div class="badges">
318
- <img src="https://img.shields.io/github/stars/rahul05ranjan/dhruv-cli?style=social" alt="GitHub stars">
319
- <img src="https://img.shields.io/npm/v/dhruv-cli?color=blue&label=npm" alt="npm version">
320
- <img src="https://img.shields.io/github/workflow/status/rahul05ranjan/dhruv-cli/CI?label=build" alt="build status">
321
- <img src="https://img.shields.io/github/license/rahul05ranjan/dhruv-cli?color=green" alt="license">
322
- </div>
323
- </section>
324
- <section class="tab-content" id="installTab">
325
- <h2>Install</h2>
326
- <pre><code>npm install -g @rahul05ranjan/dhruv-cli</code></pre>
327
- <p>Also available on <a href="https://www.npmjs.com/package/@rahul05ranjan/dhruv-cli" target="_blank">npm</a>.</p>
328
- <p>Requires <a href="https://nodejs.org/" target="_blank">Node.js</a> 18+ and <a href="https://ollama.com/" target="_blank">Ollama</a> for local AI models.</p>
329
- </section>
330
- <section class="tab-content" id="usageTab">
331
- <h2>Usage Examples</h2>
332
- <pre><code>dhruv suggest "deploy react app to vercel"
333
- dhruv explain "git rebase vs merge"
334
- dhruv fix "cors error in express"
335
- dhruv review src/
336
- dhruv optimize package.json
337
- dhruv security-check src/
338
- dhruv generate tests src/utils/helpers.js
339
- dhruv init
340
- dhruv completion bash &gt; dhruv-complete.sh</code></pre>
341
- <h3>All Commands</h3>
342
- <ul>
343
- <li><b>suggest</b> <code>&lt;query&gt;</code> Smart suggestions for any dev task</li>
344
- <li><b>explain</b> <code>&lt;query&gt;</code> — Explain concepts, errors, or commands</li>
345
- <li><b>fix</b> <code>&lt;query&gt;</code> Get AI-powered fixes for errors</li>
346
- <li><b>review</b> <code>&lt;fileOrDir&gt;</code> — Review code for improvements</li>
347
- <li><b>optimize</b> <code>&lt;file&gt;</code> — Optimize files (e.g., <code>package.json</code>)</li>
348
- <li><b>security-check</b> <code>[fileOrDir]</code> — Security analysis of code</li>
349
- <li><b>generate</b> <code>&lt;type&gt; &lt;target&gt;</code> — Generate code/tests</li>
350
- <li><b>init</b> — Interactive setup/configuration wizard</li>
351
- <li><b>completion</b> <code>[shell]</code> — Output shell completion script</li>
352
- <li><b>project-type</b> — Detect project type</li>
353
- <li><b>hello-plugin</b> Example plugin command</li>
354
- </ul>
355
- </section>
356
- <section class="tab-content" id="pluginsTab">
357
- <h2>Plugin Example</h2>
358
- <div class="plugin-example">
359
- <pre><code>export default (program) =&gt; {
360
- program.command('hello-plugin').action(() =&gt; console.log('Hello from plugin!'));
361
- };</code></pre>
362
- </div>
363
- <ul>
364
- <li>Add new commands by dropping ESM modules in the <code>plugins/</code> directory.</li>
365
- <li>Plugins are loaded automatically before CLI parsing.</li>
366
- </ul>
367
- </section>
368
- <section class="tab-content" id="communityTab">
369
- <div class="community" id="community">
370
- <h2>Open Source & Community</h2>
371
- <p>Dhruv CLI is fully open source and welcomes contributions!</p>
372
- <a href="https://github.com/rahul05ranjan/dhruv-cli" target="_blank">GitHub Repo</a>
373
- <a href="https://github.com/rahul05ranjan/dhruv-cli/issues" target="_blank">Issues</a>
374
- <a href="https://github.com/rahul05ranjan/dhruv-cli/pulls" target="_blank">Pull Requests</a>
375
- <a href="https://github.com/rahul05ranjan/dhruv-cli/blob/main/CONTRIBUTING.md" target="_blank">Contributing Guide</a>
376
- <div style="margin-top:1.2em;">
377
- <img src="https://img.shields.io/github/contributors/rahul05ranjan/dhruv-cli?color=blue" alt="contributors">
378
- <img src="https://img.shields.io/github/issues/rahul05ranjan/dhruv-cli?color=orange" alt="issues">
379
- <img src="https://img.shields.io/github/last-commit/rahul05ranjan/dhruv-cli?color=green" alt="last commit">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="theme-color" content="#0b1020" />
7
+ <meta
8
+ name="description"
9
+ content="Dhruv CLI is a local-first AI command center for developers, powered by Ollama."
10
+ />
11
+ <title>Dhruv CLI — Your local AI command center</title>
12
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
14
+ <link
15
+ href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700;800&display=swap"
16
+ rel="stylesheet"
17
+ />
18
+ <style>
19
+ :root {
20
+ --ink: #e8ecf7;
21
+ --muted: #98a4c0;
22
+ --subtle: #6c7895;
23
+ --surface: rgba(22, 30, 53, 0.78);
24
+ --surface-strong: #171f35;
25
+ --line: rgba(146, 163, 205, 0.16);
26
+ --purple: #8d72ff;
27
+ --teal: #20d6c7;
28
+ --gold: #f3c969;
29
+ --shadow: 0 26px 80px rgba(3, 7, 18, 0.36);
30
+ --radius: 22px;
31
+ }
32
+
33
+ * { box-sizing: border-box; }
34
+
35
+ html { scroll-behavior: smooth; }
36
+
37
+ body {
38
+ margin: 0;
39
+ min-width: 320px;
40
+ color: var(--ink);
41
+ background:
42
+ radial-gradient(circle at 10% 0%, rgba(108, 74, 222, 0.19), transparent 32rem),
43
+ radial-gradient(circle at 90% 18%, rgba(32, 214, 199, 0.12), transparent 28rem),
44
+ #0b1020;
45
+ font-family: "Manrope", system-ui, sans-serif;
46
+ line-height: 1.6;
47
+ }
48
+
49
+ body::before {
50
+ position: fixed;
51
+ inset: 0;
52
+ z-index: -1;
53
+ pointer-events: none;
54
+ content: "";
55
+ opacity: 0.26;
56
+ background-image: linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
57
+ linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
58
+ background-size: 72px 72px;
59
+ mask-image: linear-gradient(to bottom, black, transparent 75%);
60
+ }
61
+
62
+ a { color: inherit; }
63
+
64
+ .shell {
65
+ width: min(1160px, calc(100% - 40px));
66
+ margin: 0 auto;
67
+ }
68
+
69
+ .topbar {
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: space-between;
73
+ min-height: 82px;
74
+ border-bottom: 1px solid var(--line);
75
+ }
76
+
77
+ .brand {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ gap: 12px;
81
+ color: var(--ink);
82
+ font-weight: 800;
83
+ letter-spacing: -0.04em;
84
+ text-decoration: none;
85
+ }
86
+
87
+ .brand-mark {
88
+ display: grid;
89
+ width: 34px;
90
+ height: 34px;
91
+ place-items: center;
92
+ border: 1px solid rgba(255, 255, 255, 0.18);
93
+ border-radius: 11px;
94
+ color: #0c1222;
95
+ background: linear-gradient(135deg, var(--gold), #ffe9a6 48%, var(--teal));
96
+ box-shadow: 0 8px 22px rgba(32, 214, 199, 0.2);
97
+ font-size: 18px;
98
+ font-weight: 800;
99
+ }
100
+
101
+ .nav {
102
+ display: flex;
103
+ align-items: center;
104
+ gap: 28px;
105
+ color: var(--muted);
106
+ font-size: 14px;
107
+ font-weight: 600;
108
+ }
109
+
110
+ .nav a { text-decoration: none; transition: color 180ms ease; }
111
+ .nav a:hover { color: var(--ink); }
112
+
113
+ .nav .nav-cta {
114
+ padding: 9px 15px;
115
+ border: 1px solid rgba(141, 114, 255, 0.55);
116
+ border-radius: 999px;
117
+ color: var(--ink);
118
+ background: rgba(141, 114, 255, 0.12);
119
+ }
120
+
121
+ .hero {
122
+ display: grid;
123
+ grid-template-columns: minmax(0, 0.94fr) minmax(0, 1.06fr);
124
+ gap: clamp(36px, 7vw, 96px);
125
+ align-items: center;
126
+ padding: 96px 0 84px;
127
+ }
128
+
129
+ .eyebrow {
130
+ display: inline-flex;
131
+ align-items: center;
132
+ gap: 9px;
133
+ margin-bottom: 22px;
134
+ color: var(--teal);
135
+ font-family: "DM Mono", monospace;
136
+ font-size: 12px;
137
+ letter-spacing: 0.12em;
138
+ text-transform: uppercase;
139
+ }
140
+
141
+ .eyebrow::before {
142
+ width: 7px;
143
+ height: 7px;
144
+ border-radius: 50%;
145
+ background: var(--teal);
146
+ box-shadow: 0 0 0 5px rgba(32, 214, 199, 0.12), 0 0 22px var(--teal);
147
+ content: "";
148
+ }
149
+
150
+ h1, h2, h3, p { margin-top: 0; }
151
+
152
+ h1 {
153
+ max-width: 680px;
154
+ margin-bottom: 22px;
155
+ color: #f6f8ff;
156
+ font-size: clamp(44px, 6vw, 78px);
157
+ font-weight: 800;
158
+ letter-spacing: -0.075em;
159
+ line-height: 0.98;
160
+ }
161
+
162
+ .gradient-text {
163
+ color: transparent;
164
+ background: linear-gradient(105deg, #f3c969 10%, #f6e1a0 48%, #20d6c7 92%);
165
+ -webkit-background-clip: text;
166
+ background-clip: text;
167
+ }
168
+
169
+ .hero-copy {
170
+ max-width: 560px;
171
+ margin-bottom: 32px;
172
+ color: var(--muted);
173
+ font-size: clamp(17px, 2vw, 20px);
174
+ line-height: 1.7;
175
+ }
176
+
177
+ .actions {
178
+ display: flex;
179
+ flex-wrap: wrap;
180
+ gap: 12px;
181
+ margin-bottom: 26px;
182
+ }
183
+
184
+ .button {
185
+ display: inline-flex;
186
+ align-items: center;
187
+ justify-content: center;
188
+ min-height: 46px;
189
+ padding: 0 19px;
190
+ border: 1px solid transparent;
191
+ border-radius: 12px;
192
+ font-size: 14px;
193
+ font-weight: 800;
194
+ text-decoration: none;
195
+ transition: transform 180ms ease, border-color 180ms ease, background 180ms ease;
196
+ }
197
+
198
+ .button:hover { transform: translateY(-2px); }
199
+
200
+ .button-primary {
201
+ color: #0c1222;
202
+ background: linear-gradient(135deg, var(--gold), #ffe4a0);
203
+ box-shadow: 0 12px 30px rgba(243, 201, 105, 0.16);
204
+ }
205
+
206
+ .button-secondary {
207
+ border-color: var(--line);
208
+ color: var(--ink);
209
+ background: rgba(255, 255, 255, 0.04);
210
+ }
211
+
212
+ .install {
213
+ display: flex;
214
+ align-items: center;
215
+ justify-content: space-between;
216
+ gap: 16px;
217
+ width: min(100%, 520px);
218
+ padding: 12px 12px 12px 16px;
219
+ border: 1px solid var(--line);
220
+ border-radius: 13px;
221
+ background: rgba(10, 15, 30, 0.72);
222
+ font-family: "DM Mono", monospace;
223
+ font-size: 13px;
224
+ }
225
+
226
+ .install code { overflow: auto; color: #dfe7ff; white-space: nowrap; }
227
+ .prompt { color: var(--teal); }
228
+
229
+ .copy {
230
+ flex: 0 0 auto;
231
+ padding: 8px 11px;
232
+ border: 1px solid rgba(32, 214, 199, 0.35);
233
+ border-radius: 8px;
234
+ color: var(--teal);
235
+ background: rgba(32, 214, 199, 0.08);
236
+ cursor: pointer;
237
+ font: inherit;
238
+ font-size: 11px;
239
+ font-weight: 500;
240
+ }
241
+
242
+ .hero-art { position: relative; }
243
+
244
+ .hero-art::before {
245
+ position: absolute;
246
+ inset: 10% -8% -8% 8%;
247
+ z-index: -1;
248
+ border-radius: 50%;
249
+ background: radial-gradient(circle, rgba(141, 114, 255, 0.2), transparent 65%);
250
+ content: "";
251
+ filter: blur(20px);
252
+ }
253
+
254
+ .hero-art img {
255
+ display: block;
256
+ width: 100%;
257
+ height: auto;
258
+ border: 1px solid var(--line);
259
+ border-radius: 18px;
260
+ box-shadow: var(--shadow);
261
+ }
262
+
263
+ .stats {
264
+ display: grid;
265
+ grid-template-columns: repeat(3, 1fr);
266
+ gap: 12px;
267
+ margin-top: 26px;
268
+ }
269
+
270
+ .stat {
271
+ padding: 14px 15px;
272
+ border-left: 1px solid rgba(141, 114, 255, 0.5);
273
+ color: var(--ink);
274
+ background: rgba(255, 255, 255, 0.025);
275
+ }
276
+
277
+ .stat strong { display: block; font-size: 13px; }
278
+ .stat span { color: var(--subtle); font-size: 11px; }
279
+
280
+ .section { padding: 92px 0; border-top: 1px solid var(--line); }
281
+
282
+ .section-heading { max-width: 670px; margin-bottom: 38px; }
283
+
284
+ .section-kicker {
285
+ margin-bottom: 12px;
286
+ color: var(--purple);
287
+ font-family: "DM Mono", monospace;
288
+ font-size: 12px;
289
+ letter-spacing: 0.12em;
290
+ text-transform: uppercase;
291
+ }
292
+
293
+ h2 {
294
+ margin-bottom: 13px;
295
+ color: #f6f8ff;
296
+ font-size: clamp(30px, 4vw, 48px);
297
+ letter-spacing: -0.06em;
298
+ line-height: 1.08;
299
+ }
300
+
301
+ .section-heading p { color: var(--muted); font-size: 16px; }
302
+
303
+ .feature-grid {
304
+ display: grid;
305
+ grid-template-columns: repeat(3, 1fr);
306
+ gap: 15px;
307
+ }
308
+
309
+ .feature-card, .command-card, .guide-card {
310
+ border: 1px solid var(--line);
311
+ border-radius: var(--radius);
312
+ background: linear-gradient(145deg, rgba(28, 38, 66, 0.8), rgba(13, 19, 37, 0.84));
313
+ box-shadow: 0 18px 50px rgba(3, 7, 18, 0.13);
314
+ }
315
+
316
+ .feature-card { min-height: 226px; padding: 26px; }
317
+
318
+ .feature-icon {
319
+ display: grid;
320
+ width: 40px;
321
+ height: 40px;
322
+ margin-bottom: 24px;
323
+ place-items: center;
324
+ border: 1px solid rgba(32, 214, 199, 0.3);
325
+ border-radius: 12px;
326
+ color: var(--teal);
327
+ background: rgba(32, 214, 199, 0.09);
328
+ font-family: "DM Mono", monospace;
329
+ }
330
+
331
+ h3 { margin-bottom: 8px; font-size: 18px; letter-spacing: -0.03em; }
332
+ .feature-card p, .guide-card p { margin-bottom: 0; color: var(--muted); font-size: 14px; }
333
+
334
+ .command-layout {
335
+ display: grid;
336
+ grid-template-columns: 0.8fr 1.2fr;
337
+ gap: 18px;
338
+ align-items: stretch;
339
+ }
340
+
341
+ .command-card { padding: 25px; }
342
+
343
+ .command-card h3 { color: var(--gold); }
344
+
345
+ .command-list { display: grid; gap: 10px; margin: 22px 0 0; padding: 0; list-style: none; }
346
+
347
+ .command-list li {
348
+ display: flex;
349
+ align-items: baseline;
350
+ justify-content: space-between;
351
+ gap: 18px;
352
+ padding-bottom: 10px;
353
+ border-bottom: 1px solid var(--line);
354
+ color: var(--muted);
355
+ font-size: 13px;
356
+ }
357
+
358
+ .command-list code { color: var(--ink); font-family: "DM Mono", monospace; font-size: 12px; }
359
+
360
+ .code-window {
361
+ overflow: hidden;
362
+ border: 1px solid var(--line);
363
+ border-radius: var(--radius);
364
+ background: #090e1c;
365
+ }
366
+
367
+ .code-window-bar {
368
+ display: flex;
369
+ align-items: center;
370
+ gap: 7px;
371
+ height: 44px;
372
+ padding: 0 17px;
373
+ border-bottom: 1px solid var(--line);
374
+ color: var(--subtle);
375
+ font-family: "DM Mono", monospace;
376
+ font-size: 11px;
377
+ }
378
+
379
+ .window-dot { width: 8px; height: 8px; border-radius: 50%; background: #ff6b6b; }
380
+ .window-dot:nth-child(2) { background: #f8c15c; }
381
+ .window-dot:nth-child(3) { background: #4dd89a; }
382
+ .code-window-bar span:last-child { margin-left: 7px; }
383
+
384
+ pre {
385
+ overflow: auto;
386
+ margin: 0;
387
+ padding: 26px;
388
+ color: #c8d2ec;
389
+ font: 13px/2 "DM Mono", monospace;
390
+ }
391
+
392
+ .code-prompt { color: var(--teal); }
393
+ .code-command { color: #f5f7ff; }
394
+ .code-comment { color: #687694; }
395
+ .code-highlight { color: var(--gold); }
396
+
397
+ .guide-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }
398
+ .guide-card { min-height: 190px; padding: 25px; }
399
+
400
+ .step {
401
+ display: inline-grid;
402
+ width: 30px;
403
+ height: 30px;
404
+ margin-bottom: 21px;
405
+ place-items: center;
406
+ border-radius: 9px;
407
+ color: #0c1222;
408
+ background: var(--gold);
409
+ font-family: "DM Mono", monospace;
410
+ font-size: 12px;
411
+ font-weight: 500;
412
+ }
413
+
414
+ .guide-card code { color: var(--teal); font-family: "DM Mono", monospace; font-size: 12px; }
415
+
416
+ .closing {
417
+ display: grid;
418
+ grid-template-columns: 1fr auto;
419
+ gap: 28px;
420
+ align-items: center;
421
+ padding: 32px;
422
+ border: 1px solid rgba(141, 114, 255, 0.33);
423
+ border-radius: var(--radius);
424
+ background: linear-gradient(105deg, rgba(141, 114, 255, 0.14), rgba(32, 214, 199, 0.08));
425
+ }
426
+
427
+ .closing h2 { margin-bottom: 8px; font-size: clamp(27px, 4vw, 40px); }
428
+ .closing p { margin-bottom: 0; color: var(--muted); }
429
+
430
+ footer {
431
+ display: flex;
432
+ justify-content: space-between;
433
+ gap: 20px;
434
+ padding: 32px 0 42px;
435
+ color: var(--subtle);
436
+ font-size: 12px;
437
+ }
438
+
439
+ footer a { color: var(--muted); text-decoration: none; }
440
+ footer a:hover { color: var(--ink); }
441
+
442
+ @media (max-width: 900px) {
443
+ .hero, .command-layout { grid-template-columns: 1fr; }
444
+ .hero { padding-top: 70px; }
445
+ .hero-art { max-width: 760px; }
446
+ }
447
+
448
+ @media (max-width: 680px) {
449
+ .shell { width: min(100% - 28px, 1160px); }
450
+ .topbar { min-height: 70px; }
451
+ .nav { gap: 13px; font-size: 12px; }
452
+ .nav a:not(.nav-cta) { display: none; }
453
+ .hero { padding: 63px 0 60px; }
454
+ h1 { font-size: clamp(43px, 14vw, 64px); }
455
+ .stats, .feature-grid, .guide-grid { grid-template-columns: 1fr; }
456
+ .section { padding: 65px 0; }
457
+ .closing { grid-template-columns: 1fr; padding: 25px; }
458
+ footer { flex-direction: column; padding-bottom: 28px; }
459
+ }
460
+
461
+ @media (prefers-reduced-motion: reduce) {
462
+ html { scroll-behavior: auto; }
463
+ *, *::before, *::after { transition-duration: 0.01ms !important; }
464
+ }
465
+ </style>
466
+ </head>
467
+ <body>
468
+ <header class="shell topbar">
469
+ <a class="brand" href="#top" aria-label="Dhruv CLI home">
470
+ <span class="brand-mark">D</span>
471
+ <span>Dhruv CLI</span>
472
+ </a>
473
+ <nav class="nav" aria-label="Primary navigation">
474
+ <a href="#capabilities">Capabilities</a>
475
+ <a href="#commands">Commands</a>
476
+ <a href="#get-started">Get started</a>
477
+ <a href="./api/">API</a>
478
+ <a class="nav-cta" href="https://github.com/rahul05ranjan/dhruv-cli" target="_blank" rel="noreferrer">View on GitHub ↗</a>
479
+ </nav>
480
+ </header>
481
+
482
+ <main id="top">
483
+ <section class="shell hero">
484
+ <div>
485
+ <div class="eyebrow">Local-first · Ollama powered</div>
486
+ <h1>Ship smarter from <span class="gradient-text">your terminal.</span></h1>
487
+ <p class="hero-copy">
488
+ Dhruv is a focused AI command center for developers. Understand code,
489
+ investigate errors, improve projects, and keep your context close to home.
490
+ </p>
491
+ <div class="actions">
492
+ <a class="button button-primary" href="#get-started">Install Dhruv</a>
493
+ <a class="button button-secondary" href="https://github.com/rahul05ranjan/dhruv-cli" target="_blank" rel="noreferrer">Explore the source ↗</a>
494
+ </div>
495
+ <div class="install" aria-label="Install command">
496
+ <code><span class="prompt">$</span> npm install -g @rahul05ranjan/dhruv-cli</code>
497
+ <button class="copy" type="button" data-copy="npm install -g @rahul05ranjan/dhruv-cli">Copy</button>
498
+ </div>
499
+ <div class="stats" aria-label="Dhruv CLI highlights">
500
+ <div class="stat"><strong>Local by design</strong><span>Your model, your machine</span></div>
501
+ <div class="stat"><strong>Built for flow</strong><span>Fast, focused commands</span></div>
502
+ <div class="stat"><strong>Open to extend</strong><span>Plugins included</span></div>
503
+ </div>
380
504
  </div>
381
- </div>
382
- </section>
383
- <div class="section">
384
- <h2>Configuration & Context</h2>
385
- <ul>
386
- <li>Run <code>dhruv init</code> to set model, response format, and verbosity.</li>
387
- <li>Project type is auto-detected for context-aware suggestions.</li>
388
- <li>Works offline with local Ollama models.</li>
389
- </ul>
390
- </div>
391
- </main>
392
- <footer>
393
- <div>MIT License &copy; 2025 &mdash; Dhruv CLI</div>
394
- <div>Made with ❤️ for developers</div>
395
- </footer>
396
- <script>
397
- // Animated tagline
398
- const taglines = [
399
- 'Your AI Pair Programmer in the Terminal',
400
- 'Smart suggestions, code review, and more',
401
- 'Powered by Ollama. Fully offline. Open source.',
402
- 'Beautiful CLI UX. Plugin system. Community-driven.'
403
- ];
404
- let taglineIdx = 0;
405
- function animateTagline() {
406
- const el = document.getElementById('animatedTagline');
407
- el.textContent = '';
408
- let txt = taglines[taglineIdx];
409
- let i = 0;
410
- function type() {
411
- if (i < txt.length) {
412
- el.textContent += txt.charAt(i);
413
- i++;
414
- setTimeout(type, 28);
415
- } else {
416
- setTimeout(() => {
417
- taglineIdx = (taglineIdx + 1) % taglines.length;
418
- animateTagline();
419
- }, 1800);
420
- }
421
- }
422
- type();
423
- }
424
- animateTagline();
425
-
426
- // Terminal demo animation
427
- const terminalLines = [
428
- 'dhruv suggest "deploy react app to vercel"',
429
- 'dhruv explain "git rebase vs merge"',
430
- 'dhruv fix "cors error in express"',
431
- 'dhruv review src/',
432
- 'dhruv optimize package.json',
433
- 'dhruv security-check src/',
434
- 'dhruv generate tests src/utils/helpers.js',
435
- 'dhruv init',
436
- 'dhruv completion bash > dhruv-complete.sh'
437
- ];
438
- let termIdx = 0;
439
- function animateTerminal() {
440
- const el = document.getElementById('terminalContent');
441
- el.textContent = '';
442
- let txt = terminalLines[termIdx];
443
- let i = 0;
444
- function type() {
445
- if (i < txt.length) {
446
- el.textContent += txt.charAt(i);
447
- i++;
448
- setTimeout(type, 18);
449
- } else {
450
- setTimeout(() => {
451
- termIdx = (termIdx + 1) % terminalLines.length;
452
- animateTerminal();
453
- }, 1200);
454
- }
455
- }
456
- type();
457
- }
458
- animateTerminal();
459
-
460
- // Copy install command
461
- function copyInstall() {
462
- navigator.clipboard.writeText('npm install -g @rahul05ranjan/dhruv-cli');
463
- const btn = document.getElementById('installBtn');
464
- const icon = document.getElementById('copyIcon');
465
- icon.textContent = '✅';
466
- setTimeout(() => { icon.textContent = '📋'; }, 1200);
467
- }
468
- // Copy terminal command
469
- function copyTerminal() {
470
- const el = document.getElementById('terminalContent');
471
- navigator.clipboard.writeText(el.textContent);
472
- const btn = document.getElementById('copyTermBtn');
473
- btn.textContent = 'Copied!';
474
- setTimeout(() => { btn.textContent = 'Copy'; }, 1200);
475
- }
476
- // Tabs
477
- const tabs = document.querySelectorAll('.tab');
478
- const tabContents = document.querySelectorAll('.tab-content');
479
- tabs.forEach(tab => {
480
- tab.addEventListener('click', () => {
481
- tabs.forEach(t => t.classList.remove('active'));
482
- tab.classList.add('active');
483
- tabContents.forEach(tc => tc.classList.remove('active'));
484
- document.getElementById(tab.dataset.tab).classList.add('active');
485
- });
486
- });
487
- // Collapsible
488
- const collapsibles = document.querySelectorAll('.collapsible');
489
- collapsibles.forEach(btn => {
490
- btn.addEventListener('click', function() {
491
- this.classList.toggle('active');
492
- const content = this.nextElementSibling;
493
- content.classList.toggle('active');
505
+ <div class="hero-art">
506
+ <img src="./dhruv-cli-preview.svg" alt="Dhruv CLI reviewing a project in the terminal" />
507
+ </div>
508
+ </section>
509
+
510
+ <section class="shell section" id="capabilities">
511
+ <div class="section-heading">
512
+ <div class="section-kicker">A calm layer over complexity</div>
513
+ <h2>Everything useful. Nothing in the way.</h2>
514
+ <p>Dhruv keeps the high-value developer workflows one command away, with local models and a terminal-native experience.</p>
515
+ </div>
516
+ <div class="feature-grid">
517
+ <article class="feature-card">
518
+ <div class="feature-icon">01</div>
519
+ <h3>Think out loud</h3>
520
+ <p>Get clear explanations, practical suggestions, and structured fixes for the questions that slow you down.</p>
521
+ </article>
522
+ <article class="feature-card">
523
+ <div class="feature-icon">02</div>
524
+ <h3>Inspect with context</h3>
525
+ <p>Review files and directories, optimize configurations, and run security analysis close to the source.</p>
526
+ </article>
527
+ <article class="feature-card">
528
+ <div class="feature-icon">03</div>
529
+ <h3>Shape your workflow</h3>
530
+ <p>Use JSON output, shell completion, an interactive command palette, or add your own project plugins.</p>
531
+ </article>
532
+ </div>
533
+ </section>
534
+
535
+ <section class="shell section" id="commands">
536
+ <div class="section-heading">
537
+ <div class="section-kicker">The command surface</div>
538
+ <h2>From first question to final check.</h2>
539
+ <p>Short commands, useful defaults, and a workflow that follows the way developers actually work.</p>
540
+ </div>
541
+ <div class="command-layout">
542
+ <article class="command-card">
543
+ <h3>Ask & understand</h3>
544
+ <ul class="command-list">
545
+ <li><code>suggest &lt;query&gt;</code><span>Explore an approach</span></li>
546
+ <li><code>explain &lt;query&gt;</code><span>Make it clear</span></li>
547
+ <li><code>fix &lt;query&gt;</code><span>Find a way forward</span></li>
548
+ <li><code>generate &lt;type&gt; &lt;target&gt;</code><span>Make a first draft</span></li>
549
+ </ul>
550
+ </article>
551
+ <div class="code-window" aria-label="Dhruv CLI example">
552
+ <div class="code-window-bar">
553
+ <span class="window-dot"></span><span class="window-dot"></span><span class="window-dot"></span>
554
+ <span>example · local session</span>
555
+ </div>
556
+ <pre><span class="code-prompt">$</span> <span class="code-command">dhruv review src/</span>
557
+
558
+ <span class="code-highlight">◈ Reviewing project context...</span>
559
+
560
+ Code quality <span class="code-highlight">86%</span>
561
+ Findings 3 actionable suggestions
562
+ Next step Run dhruv optimize package.json
563
+
564
+ <span class="code-prompt">✓ Local model ready</span> gemma3:270m</pre>
565
+ </div>
566
+ </div>
567
+ </section>
568
+
569
+ <section class="shell section" id="get-started">
570
+ <div class="section-heading">
571
+ <div class="section-kicker">A three-step setup</div>
572
+ <h2>Be up and running in a minute.</h2>
573
+ <p>Bring your own local model, choose your preferences, and start asking better questions.</p>
574
+ </div>
575
+ <div class="guide-grid">
576
+ <article class="guide-card">
577
+ <span class="step">01</span>
578
+ <h3>Install the CLI</h3>
579
+ <p>Requires Node.js 18 or newer.</p>
580
+ <p><code>npm install -g @rahul05ranjan/dhruv-cli</code></p>
581
+ </article>
582
+ <article class="guide-card">
583
+ <span class="step">02</span>
584
+ <h3>Start Ollama</h3>
585
+ <p>Install Ollama, then pull a local model.</p>
586
+ <p><code>ollama pull gemma3:270m</code></p>
587
+ </article>
588
+ <article class="guide-card">
589
+ <span class="step">03</span>
590
+ <h3>Make it yours</h3>
591
+ <p>Use the setup wizard to choose your model, output, and theme.</p>
592
+ <p><code>dhruv init</code></p>
593
+ </article>
594
+ </div>
595
+ </section>
596
+
597
+ <section class="shell section" id="explore">
598
+ <div class="closing">
599
+ <div>
600
+ <h2>Build a sharper loop.</h2>
601
+ <p>Read the API reference, add a plugin, or join the project on GitHub.</p>
602
+ </div>
603
+ <div class="actions" style="margin: 0">
604
+ <a class="button button-primary" href="./api/">Read the API ↗</a>
605
+ <a class="button button-secondary" href="https://github.com/rahul05ranjan/dhruv-cli/issues" target="_blank" rel="noreferrer">Join the conversation ↗</a>
606
+ </div>
607
+ </div>
608
+ </section>
609
+ </main>
610
+
611
+ <footer class="shell">
612
+ <span>MIT © <span id="year">2026</span> Dhruv CLI</span>
613
+ <span><a href="https://www.npmjs.com/package/@rahul05ranjan/dhruv-cli" target="_blank" rel="noreferrer">npm</a> · <a href="https://github.com/rahul05ranjan/dhruv-cli" target="_blank" rel="noreferrer">GitHub</a> · <a href="./publishing-fix.md">Release notes</a></span>
614
+ </footer>
615
+
616
+ <script>
617
+ document.querySelectorAll("[data-copy]").forEach((button) => {
618
+ button.addEventListener("click", async () => {
619
+ const value = button.dataset.copy;
620
+ if (!value) return;
621
+ try {
622
+ await navigator.clipboard.writeText(value);
623
+ button.textContent = "Copied";
624
+ window.setTimeout(() => { button.textContent = "Copy"; }, 1400);
625
+ } catch {
626
+ button.textContent = "Select above";
627
+ window.setTimeout(() => { button.textContent = "Copy"; }, 1400);
628
+ }
629
+ });
494
630
  });
495
- });
496
- // Sidebar nav scrollspy
497
- const navLinks = document.querySelectorAll('.sidebar nav a');
498
- const sections = [
499
- document.getElementById('hero'),
500
- document.getElementById('features'),
501
- document.getElementById('installTab'),
502
- document.getElementById('usageTab'),
503
- document.getElementById('pluginsTab'),
504
- document.getElementById('community')
505
- ];
506
- window.addEventListener('scroll', () => {
507
- let idx = 0;
508
- for (let i = 0; i < sections.length; i++) {
509
- if (sections[i] && sections[i].getBoundingClientRect().top < 120) idx = i;
510
- }
511
- navLinks.forEach(l => l.classList.remove('active'));
512
- if (navLinks[idx]) navLinks[idx].classList.add('active');
513
- });
514
- // Theme toggle
515
- const themeToggle = document.getElementById('themeToggle');
516
- const themeIcon = document.getElementById('themeIcon');
517
- const themeLabel = document.getElementById('themeLabel');
518
- function setTheme(theme) {
519
- document.documentElement.setAttribute('data-theme', theme);
520
- if (theme === 'light') {
521
- themeIcon.textContent = '☀️';
522
- themeLabel.textContent = 'Light';
523
- } else {
524
- themeIcon.textContent = '🌙';
525
- themeLabel.textContent = 'Dark';
526
- }
527
- localStorage.setItem('dhruv-theme', theme);
528
- }
529
- themeToggle.addEventListener('click', () => {
530
- const current = document.documentElement.getAttribute('data-theme') || 'dark';
531
- setTheme(current === 'dark' ? 'light' : 'dark');
532
- });
533
- // Load theme
534
- setTheme(localStorage.getItem('dhruv-theme') || 'dark');
535
- </script>
536
- </body>
631
+
632
+ document.getElementById("year").textContent = String(new Date().getFullYear());
633
+ </script>
634
+ </body>
537
635
  </html>