|
7 | 7 |
|
8 | 8 | <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet"> |
9 | 9 | <link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css" rel="stylesheet"> |
10 | | - |
11 | | -<style> |
12 | | -body{margin:0;font-family:'Inter',sans-serif;background:#0f1720;color:#e6eef8;line-height:1.6;} |
13 | | -.wrap{max-width:1000px;margin:32px auto;padding:20px;} |
14 | | -h1{text-align:center;margin-bottom:32px;} |
15 | | -.articles-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:20px;} |
16 | | -.article-card{background:rgba(255,255,255,0.02);border-radius:14px;padding:20px;border:1px solid rgba(255,255,255,0.03);cursor:pointer;transition:0.2s;} |
17 | | -.article-card:hover{transform:translateY(-4px);box-shadow:0 12px 24px rgba(0,0,0,0.3);} |
18 | | -.article-card h2{margin-top:0;color:#7c5cff;} |
19 | | -.article-card p{color:#9aa4b2;} |
20 | | -.article-card .meta{font-size:0.85rem;color:#9aa4b2;margin-top:10px;} |
21 | | -.article-card .tags{margin-top:8px;font-size:0.8rem;color:#7c5cff;} |
22 | | - |
23 | | -/* Modal */ |
24 | | -#article-modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(15,23,32,0.95);color:#e6eef8;overflow:auto;display:none;flex-direction:column;padding:20px;z-index:999;} |
25 | | -#article-modal .close{align-self:flex-end;font-size:28px;cursor:pointer;margin-bottom:12px;} |
26 | | -#article-modal h1{margin-top:0;} |
27 | | -#article-modal .article-meta{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px;color:#9aa4b2;font-size:0.9rem;} |
28 | | -#article-modal img.cover{max-width:100%;border-radius:12px;margin:16px 0;} |
29 | | -.article-body h1,h2,h3,h4,h5,h6{color:#e6eef8;margin-top:1.2rem;margin-bottom:0.6rem;} |
30 | | -.article-body p{margin:0.6rem 0;} |
31 | | -.article-body a{color:#7c5cff;} |
32 | | -.article-body pre{background:#1e293b;padding:12px;border-radius:8px;overflow:auto;} |
33 | | - |
34 | | -/* Navigation buttons */ |
35 | | -.nav-buttons{display:flex;justify-content:space-between;margin-top:12px;} |
36 | | -.nav-buttons button{background:#7c5cff;color:#fff;border:none;padding:8px 16px;border-radius:8px;cursor:pointer;transition:0.2s;} |
37 | | -.nav-buttons button:hover{background:#5a3edc;} |
38 | | -</style> |
| 10 | +<link rel="stylesheet" href="./assets/css/style.css"/> |
39 | 11 | </head> |
40 | 12 | <body> |
41 | 13 | <div class="wrap"> |
@@ -65,126 +37,6 @@ <h1 id="modal-title"></h1> |
65 | 37 | <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
66 | 38 | <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script> |
67 | 39 |
|
68 | | -<script> |
69 | | -// --- GitHub Config --- |
70 | | -const GITHUB_USER = 'codebysushil'; |
71 | | -const GITHUB_REPO = "codebysushil.github.io"; |
72 | | -const ARTICLES_PATH = 'articles'; |
73 | | -const BRANCH = 'main'; |
74 | | - |
75 | | -// --- Helpers --- |
76 | | -function parseFrontMatter(md){ |
77 | | - const fmRegex=/^---\s*([\s\S]*?)\s*---\s*/; |
78 | | - const match=md.match(fmRegex); |
79 | | - if(!match) return {fm:{}, content:md}; |
80 | | - const yaml=match[1]; |
81 | | - const content=md.slice(match[0].length); |
82 | | - const lines=yaml.split(/\r?\n/).map(l=>l.trim()).filter(Boolean); |
83 | | - const fm={}; |
84 | | - for(const line of lines){ |
85 | | - const m=line.match(/^([A-Za-z0-9\-_]+)\s*:\s*(.*)$/); |
86 | | - if(m){fm[m[1].toLowerCase()]=m[2].replace(/^["'](.*)["']$/,'$1');} |
87 | | - } |
88 | | - return {fm, content}; |
89 | | -} |
90 | | -function formatDate(iso){try{const d=new Date(iso);return isNaN(d)?iso:d.toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'});}catch(e){return iso}} |
91 | | -function estimateReadTime(text){const words=text.trim().split(/\s+/).length;return Math.max(1,Math.round(words/200))+' min read'} |
92 | | -async function incAndGetViews(key){ |
93 | | - try{ |
94 | | - const res=await fetch(`https://api.countapi.xyz/hit/${GITHUB_USER}-${GITHUB_REPO}/${key}`); |
95 | | - const json=await res.json(); |
96 | | - return json.value||0; |
97 | | - }catch(e){return '—'} |
98 | | -} |
99 | | - |
100 | | -// --- DOM Elements --- |
101 | | -const grid=document.getElementById('articles-grid'); |
102 | | -const modal=document.getElementById('article-modal'); |
103 | | -const modalClose=document.getElementById('modal-close'); |
104 | | -const modalTitle=document.getElementById('modal-title'); |
105 | | -const modalAuthor=document.getElementById('modal-author'); |
106 | | -const modalDate=document.getElementById('modal-date'); |
107 | | -const modalReadtime=document.getElementById('modal-readtime'); |
108 | | -const modalTags=document.getElementById('modal-tags'); |
109 | | -const modalViews=document.getElementById('modal-views'); |
110 | | -const modalCover=document.getElementById('modal-cover'); |
111 | | -const articleContent=document.getElementById('article-content'); |
112 | | -const prevBtn=document.getElementById('prev-article'); |
113 | | -const nextBtn=document.getElementById('next-article'); |
114 | | - |
115 | | -let articlesData=[]; // store all articles |
116 | | -let currentIndex=0; // current article in modal |
117 | | - |
118 | | -// --- Load articles dynamically --- |
119 | | -(async function(){ |
120 | | - try{ |
121 | | - const apiUrl = `https://api.github.com/repos/${GITHUB_USER}/${GITHUB_REPO}/contents/${ARTICLES_PATH}?ref=${BRANCH}`; |
122 | | - const res = await fetch(apiUrl); |
123 | | - if(!res.ok) throw new Error('Failed to fetch articles list'); |
124 | | - const files = await res.json(); |
125 | | - const mdFiles = files.filter(f=>f.name.endsWith('.md')); |
126 | | - |
127 | | - for(const file of mdFiles){ |
128 | | - try{ |
129 | | - const rawUrl = file.download_url; |
130 | | - const resp = await fetch(rawUrl); |
131 | | - if(!resp.ok) continue; |
132 | | - const mdText = await resp.text(); |
133 | | - const {fm, content} = parseFrontMatter(mdText); |
134 | | - |
135 | | - const article = { |
136 | | - fileName: file.name, |
137 | | - title: fm.title||file.name, |
138 | | - description: fm.description||'', |
139 | | - author: fm.author||'Unknown', |
140 | | - date: fm.date?formatDate(fm.date):'', |
141 | | - tags: fm.tags||'', |
142 | | - cover: fm.cover||'', |
143 | | - content |
144 | | - }; |
145 | | - articlesData.push(article); |
146 | | - |
147 | | - // create card |
148 | | - const card = document.createElement('div'); |
149 | | - card.className = 'article-card'; |
150 | | - card.innerHTML=` |
151 | | - <h2>${article.title}</h2> |
152 | | - <p>${article.description}</p> |
153 | | - <div class="meta">By ${article.author} | ${article.date}</div> |
154 | | - <div class="tags">${article.tags}</div> |
155 | | - `; |
156 | | - |
157 | | - card.addEventListener('click',()=>openModal(articlesData.indexOf(article))); |
158 | | - grid.appendChild(card); |
159 | | - |
160 | | - }catch(err){console.error('Error loading file', file.name, err);} |
161 | | - } |
162 | | - }catch(e){console.error('Failed to fetch articles list', e);} |
163 | | -})(); |
164 | | - |
165 | | -// --- Modal functions --- |
166 | | -async function openModal(index){ |
167 | | - currentIndex=index; |
168 | | - const article=articlesData[index]; |
169 | | - modalTitle.textContent=article.title; |
170 | | - modalAuthor.textContent='By '+article.author; |
171 | | - modalDate.textContent=article.date; |
172 | | - modalReadtime.textContent=estimateReadTime(article.content); |
173 | | - modalTags.textContent=article.tags; |
174 | | - if(article.cover){modalCover.src=article.cover; modalCover.style.display='block';} else modalCover.style.display='none'; |
175 | | - articleContent.innerHTML=marked.parse(article.content); |
176 | | - Prism.highlightAll(); |
177 | | - modalViews.textContent='Views: ' + await incAndGetViews(article.fileName.replace(/\.[^/.]+$/,"")); |
178 | | - modal.style.display='flex'; |
179 | | -} |
180 | | - |
181 | | -// Next / Previous |
182 | | -prevBtn.addEventListener('click',()=>openModal((currentIndex-1+articlesData.length)%articlesData.length)); |
183 | | -nextBtn.addEventListener('click',()=>openModal((currentIndex+1)%articlesData.length)); |
184 | | - |
185 | | -// Close modal |
186 | | -modalClose.addEventListener('click',()=>modal.style.display='none'); |
187 | | -window.addEventListener('click',e=>{if(e.target==modal) modal.style.display='none'}); |
188 | | -</script> |
| 40 | +<script src="./assets/js/main.js"></script> |
189 | 41 | </body> |
190 | 42 | </html> |
0 commit comments