|
8 | 8 | <!-- Bootstrap 5 CSS --> |
9 | 9 | <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> |
10 | 10 |
|
11 | | - <style> |
12 | | - :root{ |
13 | | - --accent: #0d6efd; |
14 | | - --card-radius: 12px; |
15 | | - } |
16 | | - body { |
17 | | - min-height: 100vh; |
18 | | - background: linear-gradient(135deg,#f8fafc 0%, #e9f2ff 40%, #fff 100%); |
19 | | - color:#0f172a; |
20 | | - -webkit-font-smoothing:antialiased; |
21 | | - -moz-osx-font-smoothing:grayscale; |
22 | | - } |
23 | | - .hero { |
24 | | - border-radius: 14px; |
25 | | - background: linear-gradient(180deg, rgba(255,255,255,0.8), rgba(255,255,255,0.65)); |
26 | | - box-shadow: 0 6px 30px rgba(13, 110, 253, 0.08); |
27 | | - backdrop-filter: blur(6px); |
28 | | - } |
29 | | - .app-card { |
30 | | - border-radius: var(--card-radius); |
31 | | - transition: transform .18s ease, box-shadow .18s ease; |
32 | | - } |
33 | | - .app-card:hover { |
34 | | - transform: translateY(-6px); |
35 | | - box-shadow: 0 10px 30px rgba(13,110,253,0.12); |
36 | | - } |
37 | | - footer { opacity: .85; } |
38 | | - .search-field { |
39 | | - max-width: 540px; |
40 | | - } |
41 | | - .muted-small { color: #6b7280; font-size: .9rem; } |
42 | | - </style> |
| 11 | + |
43 | 12 | </head> |
44 | 13 | <body> |
45 | 14 |
|
@@ -103,142 +72,8 @@ <h2 class="h5">About</h2> |
103 | 72 | <!-- Bootstrap JS (with Popper) --> |
104 | 73 | <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> |
105 | 74 |
|
106 | | - <script> |
| 75 | + <script src="main.js"></script> |
107 | 76 | // Data for projects - keep paths as in your filesystem |
108 | | - const projects = [ |
109 | | - { title: 'Calculator', desc: 'Basic calculator with keyboard support.', path: 'calculator/index.html' }, |
110 | | - { title: 'Bookmark', desc: 'Save and manage quick links locally.', path: 'Bookmark/index.html' }, |
111 | | - { title: 'CGPA Calculator', desc: 'Calculate GPA/CGPA for semesters.', path: 'CGPA calci/index.html' }, |
112 | | - { title: 'Employee App', desc: 'Simple CRUD demo for employees.', path: 'EmployeeApp/index.html' }, |
113 | | - { title: 'Memory Game', desc: 'Classic card matching game.', path: 'Memory_Game/index.html' }, |
114 | | - { title: 'Movie App', desc: 'Search and browse movies.', path: 'Movie_APP/index.html' } |
115 | | - ]; |
116 | | - |
117 | | - // Utility: sanitize text for search |
118 | | - const normalize = s => s.toLowerCase(); |
119 | | - |
120 | | - // Render project cards |
121 | | - function renderProjects(list) { |
122 | | - const grid = document.getElementById('projectsGrid'); |
123 | | - grid.innerHTML = ''; |
124 | | - if (!list.length) { |
125 | | - grid.innerHTML = '<div class="col-12"><div class="alert alert-info mb-0">No projects found.</div></div>'; |
126 | | - return; |
127 | | - } |
128 | | - list.forEach(p => { |
129 | | - const col = document.createElement('div'); |
130 | | - col.className = 'col-12 col-sm-6 col-lg-4'; |
131 | | - col.innerHTML = ` |
132 | | - <div class="card app-card h-100"> |
133 | | - <div class="card-body d-flex flex-column"> |
134 | | - <h5 class="card-title mb-1">${escapeHtml(p.title)}</h5> |
135 | | - <p class="card-text text-muted small mb-3">${escapeHtml(p.desc)}</p> |
136 | | - <div class="mt-auto d-flex justify-content-between align-items-center"> |
137 | | - <a href="${encodeURI(p.path)}" class="btn btn-outline-primary btn-sm" aria-label="Open ${escapeHtml(p.title)}">Open</a> |
138 | | - <button class="btn btn-sm btn-outline-secondary copy-btn" data-path="${encodeURI(p.path)}" type="button" title="Copy path">Copy path</button> |
139 | | - </div> |
140 | | - </div> |
141 | | - </div> |
142 | | - `; |
143 | | - grid.appendChild(col); |
144 | | - }); |
145 | | - } |
146 | | - |
147 | | - // Simple HTML escaper for safety |
148 | | - function escapeHtml(str) { |
149 | | - return String(str) |
150 | | - .replace(/&/g, '&') |
151 | | - .replace(/"/g, '"') |
152 | | - .replace(/'/g, ''') |
153 | | - .replace(/</g, '<') |
154 | | - .replace(/>/g, '>'); |
155 | | - } |
156 | | - |
157 | | - // Live filter |
158 | | - function filterProjects(query) { |
159 | | - const q = normalize(query.trim()); |
160 | | - if (!q) return projects.slice(); |
161 | | - return projects.filter(p => normalize(p.title).includes(q) || normalize(p.desc).includes(q)); |
162 | | - } |
163 | | - |
164 | | - document.addEventListener('DOMContentLoaded', () => { |
165 | | - const search = document.getElementById('search'); |
166 | | - const clearBtn = document.getElementById('clearSearch'); |
167 | | - |
168 | | - // Initial render |
169 | | - renderProjects(projects); |
170 | | - |
171 | | - // Search input handler |
172 | | - search.addEventListener('input', () => { |
173 | | - const val = search.value; |
174 | | - const results = filterProjects(val); |
175 | | - renderProjects(results); |
176 | | - clearBtn.classList.toggle('d-none', !val); |
177 | | - }); |
178 | | - |
179 | | - // Clear button |
180 | | - clearBtn.addEventListener('click', () => { |
181 | | - search.value = ''; |
182 | | - search.dispatchEvent(new Event('input')); |
183 | | - search.focus(); |
184 | | - clearBtn.classList.add('d-none'); |
185 | | - }); |
186 | | - |
187 | | - // Keyboard shortcut: press "/" to focus search (if not typing in an input) |
188 | | - document.addEventListener('keydown', (e) => { |
189 | | - if (e.key === '/' && !['INPUT','TEXTAREA'].includes(document.activeElement.tagName)) { |
190 | | - e.preventDefault(); |
191 | | - search.focus(); |
192 | | - search.select(); |
193 | | - } |
194 | | - }); |
195 | | - |
196 | | - // Smooth scroll for internal links |
197 | | - document.querySelectorAll('a[href^="#"]').forEach(link => { |
198 | | - link.addEventListener('click', (e) => { |
199 | | - const target = document.querySelector(link.getAttribute('href')); |
200 | | - if (target) { |
201 | | - e.preventDefault(); |
202 | | - target.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
203 | | - } |
204 | | - }); |
205 | | - }); |
206 | | - |
207 | | - // Copy path buttons (event delegation) |
208 | | - document.getElementById('projectsGrid').addEventListener('click', (e) => { |
209 | | - const btn = e.target.closest('.copy-btn'); |
210 | | - if (!btn) return; |
211 | | - const path = decodeURI(btn.getAttribute('data-path') || ''); |
212 | | - if (navigator.clipboard) { |
213 | | - navigator.clipboard.writeText(path).then(() => { |
214 | | - btn.textContent = 'Copied'; |
215 | | - setTimeout(() => btn.textContent = 'Copy path', 1200); |
216 | | - }, () => { |
217 | | - alert('Unable to copy to clipboard.'); |
218 | | - }); |
219 | | - } else { |
220 | | - // Fallback |
221 | | - const ta = document.createElement('textarea'); |
222 | | - ta.value = path; |
223 | | - document.body.appendChild(ta); |
224 | | - ta.select(); |
225 | | - try { document.execCommand('copy'); btn.textContent = 'Copied'; } |
226 | | - catch { alert('Unable to copy to clipboard.'); } |
227 | | - document.body.removeChild(ta); |
228 | | - setTimeout(() => btn.textContent = 'Copy path', 1200); |
229 | | - } |
230 | | - }); |
231 | | - |
232 | | - // Optional: allow Enter in search to open first result |
233 | | - search.addEventListener('keydown', (e) => { |
234 | | - if (e.key === 'Enter') { |
235 | | - const firstLink = document.querySelector('#projectsGrid a.btn'); |
236 | | - if (firstLink) { |
237 | | - firstLink.click(); |
238 | | - } |
239 | | - } |
240 | | - }); |
241 | | - }); |
242 | | - </script> |
| 77 | + |
243 | 78 | </body> |
244 | 79 | </html> |
0 commit comments