Skip to content

Commit 79499e1

Browse files
committed
update conf
1 parent 3f8da4b commit 79499e1

1 file changed

Lines changed: 177 additions & 179 deletions

File tree

fav.html

Lines changed: 177 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -1,207 +1,205 @@
11
<!DOCTYPE html>
22
<html lang="en">
33
<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>
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+
9+
<!-- Fonts -->
10+
<link rel="preconnect" href="https://fonts.googleapis.com">
11+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12+
<link href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@100..900&display=swap" rel="stylesheet">
13+
14+
<!-- Styles -->
15+
<link rel="stylesheet" href="/assets/gallery.css">
16+
<style>
17+
body {
18+
font-family: "Albert Sans", system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
19+
}
20+
a.back-link {
21+
position: fixed;
22+
top: 10px;
23+
left: 10px;
24+
background: white;
25+
color: #000;
26+
padding: 8px 12px;
27+
border: 1px solid #ccc;
28+
border-radius: 5px;
29+
text-decoration: none;
30+
z-index: 1000;
31+
}
32+
.filter-controls {
33+
display: flex;
34+
gap: 8px;
35+
flex-wrap: wrap;
36+
align-items: center;
37+
max-width: 1100px;
38+
margin: 60px auto 12px;
39+
padding: 0 12px;
40+
}
41+
.filter-controls button {
42+
padding: 6px 12px;
43+
border: 1px solid #ccc;
44+
background: #fff;
45+
border-radius: 6px;
46+
cursor: pointer;
47+
}
48+
.filter-controls button.active {
49+
background: #111;
50+
color: #fff;
51+
border-color: #111;
52+
}
53+
.gallery[data-hidden="true"] {
54+
display: none;
55+
}
56+
.desc small {
57+
color: #666;
58+
font-weight: 500;
59+
margin-left: 6px;
60+
}
61+
</style>
2062
</head>
2163
<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">
64+
<!-- Back button -->
65+
<a href="/" class="back-link"></a>
66+
67+
<!-- Filters -->
68+
<div class="filter-controls" aria-label="Filter items">
4569
<button type="button" data-filter="all" class="active">All</button>
4670
<button type="button" data-filter="movie">Movies</button>
4771
<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){
72+
</div>
73+
74+
<!-- Gallery -->
75+
<div class="gallery-container">
76+
<div class="gallery" data-type="movie">
77+
<a href="https://www.imdb.com/title/tt0468569/" target="_blank">
78+
<img alt="The Dark Knight poster" loading="lazy">
79+
</a>
80+
<div class="desc">The Dark Knight <small>Movie</small></div>
81+
</div>
82+
83+
<div class="gallery" data-type="book" data-title="The Great Gatsby" data-isbn="0743273567">
84+
<a href="https://www.goodreads.com/book/show/4671.The_Great_Gatsby" target="_blank">
85+
<img alt="The Great Gatsby cover" loading="lazy">
86+
</a>
87+
<div class="desc">The Great Gatsby <small>Book</small></div>
88+
</div>
89+
</div>
90+
91+
<!-- Scripts -->
92+
<script>
93+
(function(){
94+
const qs = (sel, root=document) => root.querySelector(sel);
95+
const qsa = (sel, root=document) => [...root.querySelectorAll(sel)];
96+
97+
function setActiveFilter(btn) {
98+
qsa('.filter-controls button').forEach(b => b.classList.toggle('active', b === btn));
99+
}
100+
101+
function applyFilter(type) {
81102
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'); }
103+
const match = type === 'all' || card.dataset.type === type;
104+
card.dataset.hidden = match ? 'false' : 'true';
105+
if (match) card.removeAttribute('data-hidden');
86106
});
87-
}
107+
}
88108

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-
}
109+
function extractImdbId(url) {
110+
const match = String(url||'').match(/imdb\.com\/title\/(tt\d{5,9})/i);
111+
return match ? match[1] : null;
112+
}
93113

94-
async function fetchImdbPoster(imdbId){
95-
if(!imdbId) return null;
96-
const cacheKey = `thumb:imdb:${imdbId}`;
114+
async function fetchImdbPoster(id) {
115+
if (!id) return null;
116+
const cacheKey = `thumb:imdb:${id}`;
97117
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-
}
118+
if (cached) return cached;
119+
120+
const key = window.OMDB_API_KEY?.trim();
121+
if (!key) return null;
122+
123+
try {
124+
const res = await fetch(`https://www.omdbapi.com/?i=${id}&apikey=${key}`);
125+
const data = await res.json();
126+
if (data.Poster && data.Poster !== 'N/A') {
127+
localStorage.setItem(cacheKey, data.Poster);
128+
return data.Poster;
129+
}
130+
} catch {}
117131
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+
}
133+
134+
function parseGoodreadsTitle(url) {
135+
try {
136+
const parts = new URL(url).pathname.split('/').filter(Boolean);
137+
const idx = parts.indexOf('show');
138+
if (idx >= 0 && parts[idx+1]) {
139+
const slug = parts[idx+1];
140+
return decodeURIComponent(slug.split('.').slice(1).join('.').replace(/_/g, ' '));
141+
}
142+
} catch {}
132143
return null;
133-
}
144+
}
134145

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);
146+
async function fetchBookCover({ title, isbn }) {
147+
if (isbn) {
148+
const url = `https://covers.openlibrary.org/b/isbn/${isbn}-L.jpg?default=false`;
149+
if ((await fetch(url, { method: 'HEAD' })).ok) return url;
155150
}
151+
if (!title) return null;
152+
try {
153+
const res = await fetch(`https://openlibrary.org/search.json?title=${encodeURIComponent(title)}&limit=1`);
154+
const doc = (await res.json()).docs?.[0];
155+
if (doc?.cover_i) return `https://covers.openlibrary.org/b/id/${doc.cover_i}-L.jpg`;
156+
} catch {}
156157
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-
}
158+
}
159+
160+
function fallbackImage(type) {
161+
const svg = type === 'book'
162+
? `<svg xmlns="http://www.w3.org/2000/svg" width="800" height="1200"><rect width="100%" height="100%" fill="#f3f3f3"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="#999" font-family="sans-serif" font-size="36">Book cover</text></svg>`
163+
: `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1800"><rect width="100%" height="100%" fill="#f3f3f3"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="#999" font-family="sans-serif" font-size="36">Movie poster</text></svg>`;
164+
return `data:image/svg+xml;utf8,${svg}`;
165+
}
166+
167+
async function populateThumbnails() {
168+
for (const card of qsa('.gallery-container .gallery')) {
169+
const type = card.dataset.type;
170+
const link = qs('a', card);
171+
const img = qs('img', card);
172+
let src = '';
173+
174+
try {
175+
if (type === 'movie') {
176+
src = await fetchImdbPoster(card.dataset.imdbId || extractImdbId(link?.href)) || '';
177+
} else if (type === 'book') {
178+
src = await fetchBookCover({
179+
isbn: card.dataset.isbn,
180+
title: card.dataset.title || parseGoodreadsTitle(link?.href)
181+
}) || '';
182+
}
183+
} catch {}
165184

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);
185+
img.src = src || fallbackImage(type);
187186
}
188-
}
187+
}
189188

190-
function initFilters(){
189+
function initFilters() {
191190
qsa('.filter-controls button').forEach(btn => {
192-
btn.addEventListener('click', () => {
193-
setActiveFilter(btn);
194-
applyFilter(btn.dataset.filter);
195-
});
191+
btn.addEventListener('click', () => {
192+
setActiveFilter(btn);
193+
applyFilter(btn.dataset.filter);
194+
});
196195
});
197-
}
196+
}
198197

199-
// init
200-
document.addEventListener('DOMContentLoaded', () => {
198+
document.addEventListener('DOMContentLoaded', () => {
201199
initFilters();
202200
populateThumbnails();
203-
});
204-
})();
205-
</script>
201+
});
202+
})();
203+
</script>
206204
</body>
207205
</html>

0 commit comments

Comments
 (0)