Skip to content

Commit 3f8da4b

Browse files
committed
update conf
1 parent 21d7bcd commit 3f8da4b

2 files changed

Lines changed: 209 additions & 1 deletion

File tree

fav.html

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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+
<meta name="robots" content="index, follow">
7+
<title>Mo Shakiba | Talks</title>
8+
<link rel="preconnect" href="https://fonts.googleapis.com">
9+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10+
<link href="https://fonts.googleapis.com/css2?family=Albert+Sans:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
11+
<link rel="stylesheet" href="/assets/gallery.css">
12+
<style>
13+
/* Lightweight UI for filters and safe defaults */
14+
.filter-controls { display:flex; gap:8px; align-items:center; flex-wrap:wrap; max-width:1100px; margin:60px auto 12px; padding:0 12px; }
15+
.filter-controls button { padding:6px 12px; border:1px solid #ccc; background:#fff; border-radius:6px; cursor:pointer; font-family: "Albert Sans", system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; }
16+
.filter-controls button.active { background:#111; color:#fff; border-color:#111; }
17+
.gallery[data-hidden="true"] { display:none; }
18+
.desc small { color:#666; font-weight:500; margin-left:6px; }
19+
</style>
20+
</head>
21+
<body>
22+
<a href="/" style="
23+
position: fixed;
24+
top: 10px;
25+
left: 10px;
26+
background-color: white;
27+
color: #000;
28+
padding: 8px 12px;
29+
border: 1px solid #ccc;
30+
border-radius: 5px;
31+
text-decoration: none;
32+
font-family: sans-serif;
33+
z-index: 1000;
34+
">
35+
36+
</a>
37+
38+
<!-- Optional: set your OMDb API key for higher quality IMDb posters
39+
<script>
40+
// window.OMDB_API_KEY = "YOUR_OMDB_KEY";
41+
</script>
42+
-->
43+
44+
<div class="filter-controls" aria-label="Filter items">
45+
<button type="button" data-filter="all" class="active">All</button>
46+
<button type="button" data-filter="movie">Movies</button>
47+
<button type="button" data-filter="book">Books</button>
48+
</div>
49+
50+
<div class="gallery-container">
51+
52+
<!-- Usage examples (replace with your own items): -->
53+
<!-- IMDb movie: set data-type="movie" and link to an IMDb title page; poster is auto-fetched. -->
54+
<div class="gallery" data-type="movie">
55+
<a target="_blank" href="https://www.imdb.com/title/tt0468569/">
56+
<img src="" alt="The Dark Knight poster" loading="lazy">
57+
</a>
58+
<div class="desc">The Dark Knight <small>Movie</small></div>
59+
</div>
60+
61+
<!-- Goodreads book: set data-type="book" and link to a Goodreads book page. Optionally add data-title or data-isbn for better matches. -->
62+
<div class="gallery" data-type="book" data-title="The Great Gatsby" data-isbn="0743273567">
63+
<a target="_blank" href="https://www.goodreads.com/book/show/4671.The_Great_Gatsby">
64+
<img src="" alt="The Great Gatsby cover" loading="lazy">
65+
</a>
66+
<div class="desc">The Great Gatsby <small>Book</small></div>
67+
</div>
68+
69+
</div>
70+
71+
<script>
72+
(function(){
73+
const qs = (s, r=document) => r.querySelector(s);
74+
const qsa = (s, r=document) => Array.from(r.querySelectorAll(s));
75+
76+
function setActiveFilter(btn){
77+
qsa('.filter-controls button').forEach(b=>b.classList.toggle('active', b===btn));
78+
}
79+
80+
function applyFilter(type){
81+
qsa('.gallery-container .gallery').forEach(card => {
82+
const t = card.dataset.type;
83+
const show = (type === 'all') ? true : (t === type);
84+
card.dataset.hidden = show ? 'false' : 'true';
85+
if(show){ card.removeAttribute('data-hidden'); } else { card.setAttribute('data-hidden', 'true'); }
86+
});
87+
}
88+
89+
function extractImdbId(url){
90+
const m = String(url||'').match(/imdb\.com\/title\/(tt\d{5,9})/i);
91+
return m ? m[1] : null;
92+
}
93+
94+
async function fetchImdbPoster(imdbId){
95+
if(!imdbId) return null;
96+
const cacheKey = `thumb:imdb:${imdbId}`;
97+
const cached = localStorage.getItem(cacheKey);
98+
if(cached) return cached;
99+
100+
const key = (typeof window.OMDB_API_KEY === 'string' && window.OMDB_API_KEY.trim()) ? window.OMDB_API_KEY.trim() : null;
101+
if(!key){
102+
console.warn('[gallery] No OMDb API key set; skipping IMDb poster fetch for', imdbId);
103+
return null;
104+
}
105+
try{
106+
const url = `https://www.omdbapi.com/?i=${encodeURIComponent(imdbId)}&plot=short&r=json&apikey=${encodeURIComponent(key)}`;
107+
const res = await fetch(url);
108+
if(!res.ok) throw new Error('HTTP '+res.status);
109+
const data = await res.json();
110+
if(data && data.Poster && data.Poster !== 'N/A'){
111+
localStorage.setItem(cacheKey, data.Poster);
112+
return data.Poster;
113+
}
114+
}catch(err){
115+
console.warn('[gallery] OMDb fetch failed', err);
116+
}
117+
return null;
118+
}
119+
120+
function parseGoodreadsTitleFromUrl(url){
121+
try{
122+
const u = new URL(url);
123+
const parts = u.pathname.split('/').filter(Boolean);
124+
const showIdx = parts.findIndex(p=>p.toLowerCase()==='show');
125+
if(showIdx >= 0 && parts[showIdx+1]){
126+
const slug = parts[showIdx+1];
127+
const dot = slug.indexOf('.')
128+
const namePart = dot>=0 ? slug.slice(dot+1) : slug;
129+
return decodeURIComponent(namePart.replace(/_/g,' '));
130+
}
131+
}catch(_){ /* ignore */ }
132+
return null;
133+
}
134+
135+
async function fetchOpenLibraryCover({ title, isbn }){
136+
// Prefer ISBN direct cover if provided
137+
if(isbn){
138+
const isbnUrl = `https://covers.openlibrary.org/b/isbn/${encodeURIComponent(isbn)}-L.jpg?default=false`;
139+
try{
140+
const head = await fetch(isbnUrl, { method: 'HEAD' });
141+
if(head.ok) return isbnUrl;
142+
}catch(_){/* ignore */}
143+
}
144+
if(!title) return null;
145+
try{
146+
const res = await fetch(`https://openlibrary.org/search.json?title=${encodeURIComponent(title)}&limit=1`);
147+
if(!res.ok) throw new Error('HTTP '+res.status);
148+
const data = await res.json();
149+
const doc = data && data.docs && data.docs[0];
150+
if(doc && doc.cover_i){
151+
return `https://covers.openlibrary.org/b/id/${doc.cover_i}-L.jpg`;
152+
}
153+
}catch(err){
154+
console.warn('[gallery] OpenLibrary search failed', err);
155+
}
156+
return null;
157+
}
158+
159+
function fallbackImage(type){
160+
// neutral placeholders (no external tracking)
161+
return type === 'book'
162+
? 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="800" height="1200"><rect width="100%" height="100%" fill="%23f3f3f3"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="%23999" font-family="sans-serif" font-size="36">Book cover</text></svg>'
163+
: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1800"><rect width="100%" height="100%" fill="%23f3f3f3"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="%23999" font-family="sans-serif" font-size="36">Movie poster</text></svg>';
164+
}
165+
166+
async function populateThumbnails(){
167+
const cards = qsa('.gallery-container .gallery[data-type]');
168+
for(const card of cards){
169+
const type = card.dataset.type;
170+
const linkEl = qs('a', card);
171+
const imgEl = qs('img', card) || document.createElement('img');
172+
if(!imgEl.parentElement){ linkEl.prepend(imgEl); }
173+
let src = '';
174+
try{
175+
if(type === 'movie'){
176+
const imdbId = card.dataset.imdbId || extractImdbId(linkEl?.href);
177+
src = await fetchImdbPoster(imdbId) || '';
178+
} else if(type === 'book'){
179+
const isbn = card.dataset.isbn;
180+
const title = card.dataset.title || parseGoodreadsTitleFromUrl(linkEl?.href);
181+
src = await fetchOpenLibraryCover({ title, isbn }) || '';
182+
}
183+
}catch(err){ console.warn('[gallery] thumbnail generation error', err); }
184+
imgEl.loading = imgEl.loading || 'lazy';
185+
imgEl.alt = imgEl.alt || (type==='book' ? 'Book cover' : type==='movie' ? 'Movie poster' : 'Thumbnail');
186+
imgEl.src = src || imgEl.src || fallbackImage(type);
187+
}
188+
}
189+
190+
function initFilters(){
191+
qsa('.filter-controls button').forEach(btn => {
192+
btn.addEventListener('click', () => {
193+
setActiveFilter(btn);
194+
applyFilter(btn.dataset.filter);
195+
});
196+
});
197+
}
198+
199+
// init
200+
document.addEventListener('DOMContentLoaded', () => {
201+
initFilters();
202+
populateThumbnails();
203+
});
204+
})();
205+
</script>
206+
</body>
207+
</html>

robots.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ Allow: /
33
Disallow: /drive/
44
Disallow: /extra/
55
Disallow: /talks
6-
Disallow: /cert
6+
Disallow: /cert
7+
Disallow: /fav

0 commit comments

Comments
 (0)