Created
July 23, 2026 17:30
-
-
Save klydeinside/c2c4406b0c8b3af86331684a92240267 to your computer and use it in GitHub Desktop.
index.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>SIMKL Share Card</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> | |
| <style> | |
| body { | |
| margin:0; | |
| background:#111; | |
| font-family:Arial,sans-serif; | |
| color:white; | |
| } | |
| .card { | |
| width:1200px; | |
| height:630px; | |
| margin:40px auto; | |
| position:relative; | |
| overflow:hidden; | |
| border-radius:25px; | |
| background:#181818; | |
| box-shadow:0 20px 60px rgba(0,0,0,.6); | |
| } | |
| .backdrop { | |
| position:absolute; | |
| inset:0; | |
| background-size:cover; | |
| background-position:center; | |
| } | |
| .overlay { | |
| position:absolute; | |
| inset:0; | |
| background: | |
| linear-gradient( | |
| 90deg, | |
| rgba(0,0,0,.96), | |
| rgba(0,0,0,.65) | |
| ); | |
| } | |
| .content { | |
| position:absolute; | |
| inset:0; | |
| display:flex; | |
| align-items:center; | |
| padding:50px; | |
| } | |
| .poster { | |
| width:260px; | |
| height:390px; | |
| object-fit:cover; | |
| border-radius:20px; | |
| background:#333; | |
| box-shadow:0 15px 40px rgba(0,0,0,.5); | |
| } | |
| .info { | |
| margin-left:45px; | |
| max-width:720px; | |
| text-align:center; | |
| } | |
| .title-row { | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| gap:20px; | |
| flex-wrap:wrap; | |
| } | |
| .title { | |
| font-size:65px; | |
| font-weight:900; | |
| } | |
| .status { | |
| display:inline-block; | |
| padding:10px 22px; | |
| border-radius:40px; | |
| background:rgba(255,255,255,.15); | |
| backdrop-filter:blur(12px); | |
| border:1px solid rgba(255,255,255,.25); | |
| font-size:20px; | |
| box-shadow:0 0 20px rgba(80,200,120,.25); | |
| } | |
| .status-row { | |
| margin-top:15px; | |
| display:flex; | |
| justify-content:center; | |
| } | |
| .progress { | |
| margin-top:25px; | |
| display:flex; | |
| justify-content:center; | |
| gap:12px; | |
| flex-wrap:wrap; | |
| } | |
| .info-pill { | |
| padding:10px 18px; | |
| border-radius:30px; | |
| background:rgba(255,255,255,.15); | |
| backdrop-filter:blur(12px); | |
| border:1px solid rgba(255,255,255,.25); | |
| font-size:22px; | |
| } | |
| .details { | |
| margin-top:18px; | |
| display:flex; | |
| justify-content:center; | |
| gap:10px; | |
| flex-wrap:wrap; | |
| } | |
| .detail-pill { | |
| padding:8px 18px; | |
| border-radius:30px; | |
| background:rgba(255,255,255,.15); | |
| backdrop-filter:blur(12px); | |
| border:1px solid rgba(255,255,255,.25); | |
| font-size:18px; | |
| } | |
| .network-pill { | |
| padding:8px 16px; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| } | |
| .network-logo { | |
| height:22px; | |
| max-width:120px; | |
| object-fit:contain; | |
| } | |
| .ratings { | |
| display:flex; | |
| justify-content:center; | |
| gap:12px; | |
| margin-top:20px; | |
| flex-wrap:wrap; | |
| } | |
| .rating { | |
| padding:10px 16px; | |
| border-radius:30px; | |
| background:rgba(255,255,255,.15); | |
| backdrop-filter:blur(12px); | |
| border:1px solid rgba(255,255,255,.25); | |
| font-size:18px; | |
| } | |
| .overview { | |
| margin-top:25px; | |
| color:#ddd; | |
| font-size:22px; | |
| line-height:1.4; | |
| } | |
| .brand { | |
| margin-top:25px; | |
| opacity:.2; | |
| font-family:"Ink Free", cursive; | |
| font-size:28px; | |
| letter-spacing:1px; | |
| } | |
| #saveCard { | |
| position:fixed; | |
| top:20px; | |
| right:20px; | |
| z-index:99999; | |
| padding:12px 22px; | |
| border-radius:30px; | |
| border:1px solid rgba(255,255,255,.3); | |
| background:rgba(255,255,255,.15); | |
| color:white; | |
| cursor:pointer; | |
| font-size:16px; | |
| backdrop-filter:blur(12px); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <button id="saveCard"> | |
| 🔗 Share Card | |
| </button> | |
| <div class="card"> | |
| <div class="backdrop" id="backdrop"></div> | |
| <div class="overlay"></div> | |
| <div class="content"> | |
| <img class="poster" id="poster"> | |
| <div class="info"> | |
| <div class="title-row"> | |
| <div class="title" id="title"> | |
| Loading... | |
| </div> | |
| </div> | |
| <div class="status-row"> | |
| <div class="status" id="status"></div> | |
| </div> | |
| <div class="progress" id="progress"></div> | |
| <div class="details" id="details"></div> | |
| <div class="ratings"> | |
| <div class="rating" id="simkl"> | |
| 🟢 SIMKL -- | |
| </div> | |
| <div class="rating" id="tmdb"> | |
| 🎬 TMDB -- | |
| </div> | |
| <div class="rating" id="imdb"> | |
| ⭐ IMDb -- | |
| </div> | |
| <div class="rating" id="trakt"> | |
| 🔵 Trakt -- | |
| </div> | |
| </div> | |
| <div class="overview" id="overview"></div> | |
| <div class="brand"> | |
| KLYDE × SIMKL | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const TMDB_API_KEY = "59dda053896dfdcc5a98b5fec8d65017"; | |
| const WORKER_URL = "https://simkl.klyde.workers.dev/"; | |
| const params = | |
| new URLSearchParams(location.search); | |
| const raw = | |
| params.get("data"); | |
| let cardReady = false; | |
| async function loadCard(){ | |
| const item = | |
| JSON.parse( | |
| decodeURIComponent(raw) | |
| ); | |
| const show = | |
| item.show; | |
| document.getElementById("title").textContent = | |
| `${show.title} (${show.year})`; | |
| let statusText=item.status; | |
| if(item.status==="watching") | |
| statusText="▶ Currently Watching"; | |
| if(item.status==="completed") | |
| statusText="✓ Completed Watching"; | |
| if(item.status==="plantowatch") | |
| statusText="+ Plan To Watch"; | |
| if(item.status==="dropped") | |
| statusText="× Dropped"; | |
| document.getElementById("status").textContent = | |
| statusText; | |
| document.getElementById("progress").innerHTML = | |
| ` | |
| <div class="info-pill"> | |
| Season ${item.seasons?.[0]?.number || 1} • ${item.last_watched} | |
| </div> | |
| <div class="info-pill"> | |
| Watched ${item.watched_episodes_count}/${item.total_episodes_count} Episodes | |
| </div> | |
| `; | |
| const response = | |
| await fetch( | |
| `https://api.themoviedb.org/3/tv/${show.ids.tmdb}?api_key=${TMDB_API_KEY}` | |
| ); | |
| const tmdb = | |
| await response.json(); | |
| document.getElementById("details").innerHTML = | |
| ` | |
| ${tmdb.networks?.[0]?.logo_path ? | |
| ` | |
| <div class="detail-pill network-pill"> | |
| <img class="network-logo" | |
| src="https://images.weserv.nl/?url=${encodeURIComponent( | |
| "https://image.tmdb.org/t/p/original" + tmdb.networks[0].logo_path | |
| )}"> | |
| </div> | |
| ` | |
| : | |
| item.network ? | |
| ` | |
| <div class="detail-pill"> | |
| ${item.network} | |
| </div> | |
| ` | |
| :"" | |
| } | |
| ${item.genres ? | |
| item.genres.slice(0,4).map(g => | |
| `<div class="detail-pill">${g}</div>` | |
| ).join("") | |
| :""} | |
| `; | |
| if(tmdb.poster_path){ | |
| const poster = | |
| `https://image.tmdb.org/t/p/w500${tmdb.poster_path}`; | |
| document.getElementById("poster").src = | |
| `https://images.weserv.nl/?url=${encodeURIComponent(poster)}`; | |
| } | |
| if(tmdb.backdrop_path){ | |
| const backdrop = | |
| `https://image.tmdb.org/t/p/original${tmdb.backdrop_path}`; | |
| document.getElementById("backdrop").style.backgroundImage = | |
| `url(https://images.weserv.nl/?url=${encodeURIComponent(backdrop)})`; | |
| } | |
| document.getElementById("tmdb").textContent = | |
| `🎬 TMDB ${tmdb.vote_average.toFixed(1)}`; | |
| if(item.imdb_rating){ | |
| document.getElementById("imdb").textContent = | |
| `⭐ IMDb ${item.imdb_rating}`; | |
| } | |
| if(item.simkl_rating){ | |
| document.getElementById("simkl").textContent = | |
| `🟢 SIMKL ${item.simkl_rating}`; | |
| } | |
| if(item.trakt_rating){ | |
| document.getElementById("trakt").textContent = | |
| `🔵 Trakt ${item.trakt_rating}`; | |
| } | |
| document.getElementById("overview").textContent = | |
| tmdb.overview || ""; | |
| setTimeout(()=>{ | |
| cardReady=true; | |
| },1500); | |
| } | |
| async function convertImagesForCanvas(){ | |
| const images = | |
| document.querySelectorAll(".card img"); | |
| for(const img of images){ | |
| try{ | |
| const response = | |
| await fetch(img.src); | |
| const blob = | |
| await response.blob(); | |
| const reader = | |
| new FileReader(); | |
| await new Promise(resolve=>{ | |
| reader.onloadend = ()=>{ | |
| img.src = | |
| reader.result; | |
| resolve(); | |
| }; | |
| reader.readAsDataURL(blob); | |
| }); | |
| } | |
| catch(e){ | |
| console.log( | |
| "Image conversion failed", | |
| e | |
| ); | |
| } | |
| } | |
| const backdrop = | |
| document.querySelector(".backdrop"); | |
| const bg = | |
| getComputedStyle(backdrop).backgroundImage; | |
| if(bg && bg !== "none"){ | |
| const url = | |
| bg.replace(/^url\(["']?/, "") | |
| .replace(/["']?\)$/, ""); | |
| try{ | |
| const response = | |
| await fetch(url); | |
| const blob = | |
| await response.blob(); | |
| const reader = | |
| new FileReader(); | |
| await new Promise(resolve=>{ | |
| reader.onloadend = ()=>{ | |
| backdrop.style.backgroundImage = | |
| `url(${reader.result})`; | |
| resolve(); | |
| }; | |
| reader.readAsDataURL(blob); | |
| }); | |
| } | |
| catch(e){ | |
| console.log( | |
| "Backdrop conversion failed", | |
| e | |
| ); | |
| } | |
| } | |
| } | |
| async function createShareCard(){ | |
| let facebookWindow = null; | |
| if( | |
| new URLSearchParams(location.search).get("autoshare") === "1" | |
| ){ | |
| facebookWindow = window.open( | |
| "about:blank", | |
| "_blank" | |
| ); | |
| } | |
| if(!cardReady){ | |
| alert( | |
| "Card still loading. Try again in a second." | |
| ); | |
| return; | |
| } | |
| const IMGBB_API_KEY = | |
| "0733071f1d8d4150f6d2cfd13aaf954f"; | |
| const button = | |
| document.getElementById("saveCard"); | |
| button.textContent = | |
| "⏳ Preparing..."; | |
| await convertImagesForCanvas(); | |
| const card = | |
| document.querySelector(".card"); | |
| const canvas = | |
| await html2canvas(card,{ | |
| scale:2, | |
| useCORS:false, | |
| allowTaint:false, | |
| backgroundColor:"#181818" | |
| }); | |
| button.textContent = | |
| "⏳ Uploading..."; | |
| const base64 = | |
| canvas.toDataURL("image/jpeg", 0.95) | |
| .split(",")[1]; | |
| const form = | |
| new FormData(); | |
| form.append( | |
| "image", | |
| base64 | |
| ); | |
| try{ | |
| const response = | |
| await fetch( | |
| `https://api.imgbb.com/1/upload?key=${IMGBB_API_KEY}`, | |
| { | |
| method:"POST", | |
| body:form | |
| } | |
| ); | |
| const result = | |
| await response.json(); | |
| if(result.success){ | |
| const imgUrl = | |
| result.data.url; | |
| // Build the worker share link — carries both the SIMKL | |
| // data (for title/description) and the rendered PNG url | |
| // (for og:image), so this is the link that actually goes | |
| // on Facebook. | |
| const shareURL = | |
| `${WORKER_URL}?data=${encodeURIComponent(raw)}&img=${encodeURIComponent(imgUrl)}`; | |
| await navigator.clipboard.writeText(shareURL); | |
| window.open( | |
| `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareURL)}`, | |
| "facebookShare", | |
| "width=700,height=600,scrollbars=yes,resizable=yes" | |
| ); | |
| console.log( | |
| "IMG URL:", | |
| imgUrl | |
| ); | |
| console.log( | |
| "SHARE URL:", | |
| shareURL | |
| ); | |
| button.textContent = | |
| "✅ URL Copied!"; | |
| if(facebookWindow){ | |
| facebookWindow.location.href = | |
| `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareURL)}`; | |
| } | |
| else{ | |
| window.open( | |
| `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareURL)}`, | |
| "_blank" | |
| ); | |
| } | |
| } | |
| else{ | |
| throw new Error( | |
| "ImgBB upload failed" | |
| ); | |
| } | |
| } | |
| catch(error){ | |
| console.error( | |
| error | |
| ); | |
| button.textContent = | |
| "❌ Upload Failed"; | |
| } | |
| setTimeout(()=>{ | |
| button.textContent = | |
| "🔗 Share Card"; | |
| },3000); | |
| } | |
| document.getElementById("saveCard").onclick = | |
| createShareCard; | |
| loadCard(); | |
| const auto = | |
| new URLSearchParams(location.search).get("autoshare"); | |
| if(auto==="1"){ | |
| const check=setInterval(()=>{ | |
| if(cardReady){ | |
| clearInterval(check); | |
| createShareCard(); | |
| } | |
| },250); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment