HTML test site: https://www.tutorialrepublic.com/codelab.php validator.w3.org/#validate_by_input htmledit.squarefree.com Cheatsheet: https://pbs.twimg.com/media/FDTBjF5XEAkI6ZZ?format=jpg&name=large BlueGriffon (WYSIWYG Editor that doesn't break CSS) https://www.duplichecker.com/domain-age-checker.php [0HTML examples Backup: GDRIVE:/HTML/00_HTML ] =================== https://ry3yr.github.io/OSTR/release/other/Computerstuff/Commands/CSS_Examples.txt *html-unicodecharas *brk-nz *html-knowledge *host-website *java-scriptz AniGifResizer (FFMpegWebAsm Based) ------------------------- GIF Optimizer - Pro Edition

GIF Resizer & Optimizer

Target Max Size:
Resolution:
W: H:
Dithering (Bayer Scale): 2
-- System Ready --
Imagemagick cmd tool (webasm) -------------------- Magick-WASM Ultimate Tool

MAGICK COMMAND CONSOLE

Sanitize AniGIF (Max 1MB/256px)
50% (Keep Ext)
All to PNG
All to ICO
PDF to PNG
Extract Frames
DROP FILES TO QUEUE
Replyhandler v1.5 (Misskey n Sharkey support) ----------------------------------------- Demo


Fediverse Search n ApiCall ReverseEngineer ----------------------------------------- Fediverse Brute Force Search

🔍 Fediverse Brute Force Search

Trying ALL endpoints until we find the post - Client-side JavaScript version

Enter Details

<?php // fediverse_bruteforce.php // Try EVERY search endpoint if ($_SERVER['REQUEST_METHOD'] === 'POST') { $api_key = trim($_POST['api_key'] ?? ''); $instance = trim($_POST['instance'] ?? ''); $url = trim($_POST['url'] ?? ''); // Clean inputs $url = str_replace(['https://https://', 'http://http://'], ['https://', 'http://'], $url); $instance = rtrim($instance, '/'); $instance = str_replace(['https://', 'http://'], '', $instance); if (empty($api_key) || empty($instance) || empty($url)) { $error = "All fields are required!"; } else { $log = []; $all_responses = []; $found_post = null; $success = false; $log[] = "=== STARTING BRUTE FORCE SEARCH ==="; $log[] = "Time: " . date('Y-m-d H:i:s'); $log[] = "Instance: $instance"; $log[] = "API Key (first 10 chars): " . substr($api_key, 0, 10) . "..."; $log[] = "URL to search: $url"; $log[] = ""; // Define ALL possible endpoints to try $endpoints = [ // Mastodon API v1 endpoints [ 'name' => 'Mastodon v1 Search with resolve', 'url' => "https://$instance/api/v1/search?q=" . urlencode($url) . "&resolve=true&type=statuses" ], [ 'name' => 'Mastodon v1 Search (no type filter)', 'url' => "https://$instance/api/v1/search?q=" . urlencode($url) . "&resolve=true" ], [ 'name' => 'Mastodon v1 Search (URL only)', 'url' => "https://$instance/api/v1/search?q=" . urlencode($url) ], // Mastodon API v2 endpoints (if available) [ 'name' => 'Mastodon v2 Search', 'url' => "https://$instance/api/v2/search?q=" . urlencode($url) . "&resolve=true&type=statuses" ], [ 'name' => 'Mastodon v2 Search (no type)', 'url' => "https://$instance/api/v2/search?q=" . urlencode($url) . "&resolve=true" ], // Try to extract post ID if it looks like a Misskey/Calckey URL [ 'name' => 'Misskey style direct fetch attempt', 'url' => null, // Will be built dynamically 'handler' => 'try_misskey_direct' ], // Try Peertube/Plume style [ 'name' => 'ActivityPub lookup', 'url' => "https://$instance/api/v1/search?q=" . urlencode($url) . "&resolve=true&type=accounts,statuses" ], // Try with just the note ID part [ 'name' => 'Extracted note ID search', 'url' => null, 'handler' => 'try_note_id_only' ], ]; // Make API call function function make_api_call($api_url, $api_key, &$log) { $log[] = "Trying: $api_url"; $start_time = microtime(true); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $api_url, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $api_key, 'Accept: application/json', 'User-Agent: FediverseBruteForce/1.0' ], CURLOPT_HEADER => true, CURLOPT_TIMEOUT => 10, CURLOPT_FOLLOWLOCATION => true, CURLINFO_HEADER_OUT => true ]); $response = curl_exec($ch); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $total_time = curl_getinfo($ch, CURLINFO_TOTAL_TIME); $curl_error = curl_error($ch); $curl_errno = curl_errno($ch); curl_close($ch); $end_time = microtime(true); $result = [ 'url' => $api_url, 'http_code' => $http_code, 'total_time' => round($total_time, 3), 'curl_error' => $curl_error, 'curl_errno' => $curl_errno, 'success' => ($http_code == 200 && !$curl_errno) ]; if ($result['success']) { $headers = substr($response, 0, $header_size); $body = substr($response, $header_size); $result['headers'] = $headers; $result['body'] = $body; $result['body_size'] = strlen($body); $log[] = " ✓ HTTP $http_code in {$result['total_time']}s (Body: {$result['body_size']} bytes)"; } else { $log[] = " ✗ HTTP $http_code in {$result['total_time']}s" . ($curl_error ? " - $curl_error" : ""); } return $result; } // Try Misskey direct fetch (different API structure) function try_misskey_direct($url, $instance, $api_key, &$log) { // Check if it looks like a Misskey URL if (preg_match('/\/notes\/([a-z0-9]+)/i', $url, $matches)) { $note_id = $matches[1]; $log[] = "Extracted Misskey note ID: $note_id"; // Try Misskey API endpoint $misskey_url = "https://$instance/api/notes/show?noteId=$note_id"; $log[] = "Trying Misskey API: $misskey_url"; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $misskey_url, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $api_key, 'Accept: application/json' ], CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode(['noteId' => $note_id]), CURLOPT_HEADER => true, CURLOPT_TIMEOUT => 10 ]); $response = curl_exec($ch); $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code == 200) { $body = substr($response, $header_size); $log[] = " ✓ Misskey API returned HTTP 200"; return [ 'success' => true, 'body' => $body, 'headers' => substr($response, 0, $header_size), 'note_id' => $note_id, 'source' => 'misskey' ]; } } return ['success' => false]; } // Try extracting just the note ID function try_note_id_only($url, $instance, $api_key, &$log) { // Try to extract any ID-looking thing if (preg_match('/([a-z0-9]{8,})/i', $url, $matches)) { $possible_id = $matches[1]; $log[] = "Trying extracted ID: $possible_id"; // Try Mastodon search with just the ID $search_urls = [ "https://$instance/api/v1/search?q=$possible_id&resolve=true", "https://$instance/api/v2/search?q=$possible_id&resolve=true" ]; foreach ($search_urls as $search_url) { $result = make_api_call($search_url, $api_key, $log); if ($result['success']) { return $result; } } } return ['success' => false]; } // TRY EVERYTHING foreach ($endpoints as $endpoint) { $log[] = ""; $log[] = "--- " . $endpoint['name'] . " ---"; if (isset($endpoint['handler'])) { // Special handler $result = call_user_func($endpoint['handler'], $url, $instance, $api_key, $log); } else { // Regular API call $result = make_api_call($endpoint['url'], $api_key, $log); } $all_responses[$endpoint['name']] = $result; // Check if we found a post if ($result['success'] && isset($result['body'])) { $data = json_decode($result['body'], true); // Check Mastodon response format if (isset($data['statuses']) && !empty($data['statuses'])) { $found_post = $data['statuses'][0]; $log[] = " 🎉 FOUND POST in statuses array!"; $success = true; break; } // Check direct status response elseif (isset($data['id']) && isset($data['content'])) { $found_post = $data; $log[] = " 🎉 FOUND POST as direct status!"; $success = true; break; } // Check Misskey response format elseif (isset($data['id']) && isset($data['text'])) { $found_post = $data; $log[] = " 🎉 FOUND POST in Misskey format!"; $success = true; break; } else { $log[] = " ✓ Got response but no post found in expected format"; } } // Small delay to not hammer the server usleep(100000); // 0.1 second } $log[] = ""; $log[] = "=== SEARCH COMPLETE ==="; $log[] = "Total endpoints tried: " . count($endpoints); $log[] = "Success: " . ($success ? "YES 🎉" : "NO 😞"); if (!$success) { $error = "Could not find the post. Here's what we tried:"; } } } ?> <!DOCTYPE html> <html> <head> <title>Fediverse Brute Force Search</title> <style> body { font-family: monospace; margin: 20px; background: #0d1117; color: #c9d1d9; } .container { max-width: 1200px; margin: 0 auto; } .box { background: #161b22; padding: 20px; margin: 10px 0; border: 1px solid #30363d; border-radius: 6px; } input[type="text"] { width: 100%; padding: 8px; margin: 5px 0 15px 0; background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; border-radius: 3px; } button { background: #238636; color: white; border: none; padding: 10px 20px; border-radius: 3px; cursor: pointer; font-weight: bold; } button:hover { background: #2ea043; } pre { background: #0d1117; color: #7ee787; padding: 15px; border-radius: 5px; overflow: auto; max-height: 500px; font-size: 12px; border: 1px solid #30363d; } .error { background: #490202; border-left: 4px solid #ff7b72; padding: 15px; color: #ff7b72; } .success { background: #0c2d1a; border-left: 4px solid #7ee787; padding: 15px; color: #7ee787; } .log { background: #161b22; border-left: 4px solid #58a6ff; padding: 15px; color: #c9d1d9; white-space: pre-wrap; font-size: 12px; } h1, h2, h3 { color: #58a6ff; } .post-info { background: #0d1117; padding: 15px; border-radius: 5px; margin: 10px 0; } .response-box { margin: 20px 0; } .endpoint-try { margin: 10px 0; padding: 10px; background: #0d1117; border-radius: 5px; } .success-tick { color: #7ee787; } .error-cross { color: #ff7b72; } </style> </head> <body> <div class="container"> <h1>🔍 Fediverse Brute Force Search</h1> <h3>Trying ALL endpoints until we find the post</h3> <div class="box"> <h2>Enter Details</h2> <form method="post"> <label>API Key:</label><br> <input type="text" name="api_key" value="<?= htmlspecialchars($_POST['api_key'] ?? '') ?>"><br> <label>Instance (no https://):</label><br> <input type="text" name="instance" value="<?= htmlspecialchars($_POST['instance'] ?? '') ?>" placeholder="mastodon.social"><br> <label>Post URL:</label><br> <input type="text" name="url" value="<?= htmlspecialchars($_POST['url'] ?? '') ?>" placeholder="https://mk.absturztau.be/notes/ah4ir0s0m2pj01ca"><br> <button type="submit">🚀 BRUTE FORCE SEARCH</button> </form> </div> <?php if (isset($error)): ?> <div class="box error"> <h2>❌ Result</h2> <p><?= htmlspecialchars($error) ?></p> <?php if (isset($all_responses)): ?> <h3>📊 All Attempts:</h3> <?php foreach ($all_responses as $name => $response): ?> <div class="endpoint-try"> <strong><?= htmlspecialchars($name) ?></strong><br> URL: <?= htmlspecialchars($response['url'] ?? 'N/A') ?><br> Status: <?php if ($response['success'] ?? false): ?> <span class="success-tick">✓ HTTP <?= $response['http_code'] ?></span> <?php if (isset($response['body_size'])): ?> (<?= $response['body_size'] ?> bytes) <?php endif; ?> <?php else: ?> <span class="error-cross">✗ HTTP <?= $response['http_code'] ?? 'N/A' ?></span> <?php endif; ?> </div> <?php endforeach; ?> <?php endif; ?> </div> <?php endif; ?> <?php if (isset($log)): ?> <div class="box log"> <h2>📋 Execution Log</h2> <pre><?= htmlspecialchars(implode("\n", $log)) ?></pre> </div> <?php endif; ?> <?php if (isset($success) && $success && isset($found_post)): ?> <div class="box success"> <h2>✅ POST FOUND!</h2> <div class="post-info"> <h3>📝 Post Information</h3> <?php if (isset($found_post['account'])): // Mastodon format ?> <p><strong>ID:</strong> <?= htmlspecialchars($found_post['id']) ?></p> <p><strong>Author:</strong> @<?= htmlspecialchars($found_post['account']['acct'] ?? 'unknown') ?></p> <p><strong>Date:</strong> <?= htmlspecialchars($found_post['created_at'] ?? 'unknown') ?></p> <p><strong>Content:</strong></p> <pre style="color: #d2a8ff;"><?= htmlspecialchars(strip_tags($found_post['content'] ?? 'No content')) ?></pre> <?php elseif (isset($found_post['text'])): // Misskey format ?> <p><strong>ID:</strong> <?= htmlspecialchars($found_post['id'] ?? 'unknown') ?></p> <p><strong>Text:</strong></p> <pre style="color: #d2a8ff;"><?= htmlspecialchars($found_post['text'] ?? 'No text') ?></pre> <?php else: ?> <pre><?= htmlspecialchars(json_encode($found_post, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) ?></pre> <?php endif; ?> </div> <?php // Find which response worked $working_response = null; foreach ($all_responses as $name => $resp) { if ($resp['success'] ?? false) { $working_response = $resp; break; } } ?> <?php if ($working_response): ?> <div class="response-box"> <h3>📄 Raw Response Headers (from successful endpoint)</h3> <pre><?= htmlspecialchars($working_response['headers'] ?? 'No headers') ?></pre> <h3>📦 Raw JSON Response</h3> <pre><?php if (isset($working_response['body'])) { $body_data = json_decode($working_response['body'], true); echo htmlspecialchars(json_encode($body_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } else { echo "No body"; } ?></pre> <h3>🔧 Complete Raw Response</h3> <pre><?= htmlspecialchars($working_response['headers'] ?? '') . "\n\n" . htmlspecialchars($working_response['body'] ?? '') ?></pre> </div> <?php endif; ?> </div> <?php endif; ?> <?php if (isset($all_responses) && !empty($all_responses)): ?> <div class="box"> <h2>🔧 All API Responses</h2> <?php foreach ($all_responses as $name => $response): ?> <div class="endpoint-try"> <h4><?= htmlspecialchars($name) ?></h4> <p><strong>URL:</strong> <?= htmlspecialchars($response['url'] ?? 'Special handler') ?></p> <p><strong>Result:</strong> <?php if ($response['success'] ?? false): ?> <span class="success-tick">✓ Success (HTTP <?= $response['http_code'] ?>)</span> <?php else: ?> <span class="error-cross">✗ Failed (HTTP <?= $response['http_code'] ?? 'N/A' ?>)</span> <?php endif; ?> </p> <?php if (isset($response['body'])): ?> <details> <summary>View Response Body (<?= strlen($response['body']) ?> bytes)</summary> <pre style="font-size: 10px; max-height: 200px;"><?= htmlspecialchars(substr($response['body'], 0, 1000)) . (strlen($response['body']) > 1000 ? "\n\n... truncated ..." : '') ?></pre> </details> <?php endif; ?> </div> <?php endforeach; ?> </div> <?php endif; ?> </div> </body> </html> Akkoma Dressup Avatar Gif Tool -------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Dress-Up GIF Export</title> <style> body {margin:0; font-family:system-ui,sans-serif; background:#fff; color:#fff; display:flex; height:100vh;} #toolbar {width:300px; background:#1c1c1c; border-right:1px solid #333; display:flex; flex-direction:column;} #controls {padding:10px;} button {width:100%; margin-bottom:6px; padding:8px; background:#333; color:#fff; border:none; cursor:pointer;} button:hover {background:#444;} button:disabled {background:#222; opacity:0.5; cursor:default;} #browser {flex:1; overflow-y:auto; border-top:1px solid #333; padding:6px;} .thumb {background:#222; margin-bottom:6px; padding:6px; cursor:pointer; font-size:11px;} .thumb img {max-width:100%; max-height:80px; display:block; margin:auto;} #canvasWrap {flex:1; display:flex; justify-content:center; align-items:center;} #avatarWrap {position:relative; width:500px; height:600px; border:1px solid #dff; background:#fff; overflow:hidden;} .layer-wrap {position:absolute; cursor:grab; user-select:none; transform-origin:top left;} .layer-wrap .controls {position:absolute; top:-18px; right:0; display:flex; gap:4px;} .layer-wrap .ctrl {background:#000; border:1px solid #fff; color:#fff; font-size:12px; padding:0 6px; cursor:pointer;} .layer-wrap .remove {position:absolute; bottom:0; left:0; background:#900; border:1px solid #fff; color:#fff; font-size:12px; padding:0 4px; cursor:pointer;} .locked {outline:2px solid #2ecc71;} #cropRect {position:absolute; border:2px dashed #0f0; cursor:move; display:none; background:rgba(0,0,0,0.1);} #cropControls {position:absolute; top:-18px; right:0; display:flex; gap:2px;} #cropControls button {background:#0a0; border:none; color:#fff; cursor:pointer; font-size:12px; padding:1px 4px;} #previewCanvas {border:2px solid #0f0; margin:10px auto; display:block;} #recordStatus {font-size:12px; color:#0f0; margin-top:10px;} #zipOption {margin-top:8px; font-size:13px;} .hide-ui .layer-wrap .controls, .hide-ui .layer-wrap .remove, .hide-ui #cropRect, .hide-ui #cropControls, .hide-ui .locked {display:none !important;} .hide-ui .layer-wrap {cursor:default;} </style> </head> <body> <div id="toolbar"> <div id="controls"> <button id="clearViewBtn">👁 Toggle Clear View</button> <button id="lockBtn">🔒 Lock Main Image</button> <button id="exportBtn">✨ Looking good (URL)</button> <button id="gifBtn">🎬 GIF Please (Crop)</button> <button id="previewBtn">👁 Preview Crop</button> <button id="downloadGifBtn">💾 Record & Download</button> <div id="zipOption"> <label><input type="checkbox" id="zipCheckbox"> Zip download (individual PNG frames instead of GIF)</label> </div> <div id="recordStatus"></div> <p style="font-size:11px;opacity:.6">Drag layers • +/− to scale • crop with green box<br>Add animated clothing/accessories for animation</p> </div> <div id="browser"></div> <canvas id="previewCanvas"></canvas> </div> <div id="canvasWrap"> <div id="avatarWrap"> <div id="cropRect" data-html2canvas-ignore> <div id="cropControls"> <button id="cropPlus">+</button> <button id="cropMinus">−</button> </div> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script> <script> const avatarWrap = document.getElementById("avatarWrap"); const browser = document.getElementById("browser"); const cropRect = document.getElementById("cropRect"); const previewCanvas = document.getElementById("previewCanvas"); const previewCtx = previewCanvas.getContext("2d"); const recordStatus = document.getElementById("recordStatus"); const downloadGifBtn = document.getElementById("downloadGifBtn"); const zipCheckbox = document.getElementById("zipCheckbox"); const params = new URLSearchParams(location.search); const DEFAULT_WEBDIR = "https://alcea-wisteria.de/z_files/akkomadressup/"; const webdir = params.get("webdir") || DEFAULT_WEBDIR; let layers = [], active = null, offsetX = 0, offsetY = 0; let mainLocked = false, zCounter = 10; let cropActive = false; let clearViewMode = false; cropRect.setAttribute('data-html2canvas-ignore', ''); document.getElementById("clearViewBtn").onclick = () => { clearViewMode = !clearViewMode; if (clearViewMode) { avatarWrap.classList.add("hide-ui"); document.getElementById("clearViewBtn").textContent = "👁 Restore UI"; } else { avatarWrap.classList.remove("hide-ui"); document.getElementById("clearViewBtn").textContent = "👁 Toggle Clear View"; } }; function createLayer(src, isMain = false, behind = false, scale = 1, x = null, y = null) { const wrap = document.createElement("div"); wrap.className = "layer-wrap"; wrap.dataset.main = isMain ? "1" : "0"; wrap.dataset.behind = behind ? "1" : "0"; wrap.dataset.scale = scale; const img = document.createElement("img"); img.crossOrigin = "anonymous"; img.src = src; img.style.transform = `scale(${scale})`; wrap.style.left = (x !== null ? x : 50) + "px"; wrap.style.top = (y !== null ? y : 50) + "px"; wrap.style.zIndex = isMain ? 5 : (behind ? 1 : ++zCounter); wrap.appendChild(img); if (!isMain) { const ctrls = document.createElement("div"); ctrls.className = "controls"; const plus = document.createElement("div"); plus.className = "ctrl"; plus.textContent = "+"; plus.onclick = e => { e.stopPropagation(); scaleLayer(wrap, 1.1); }; const minus = document.createElement("div"); minus.className = "ctrl"; minus.textContent = "−"; minus.onclick = e => { e.stopPropagation(); scaleLayer(wrap, 0.9); }; ctrls.append(minus, plus); wrap.appendChild(ctrls); ctrls.setAttribute('data-html2canvas-ignore', ''); const remove = document.createElement("div"); remove.className = "remove"; remove.textContent = "×"; remove.onclick = e => { e.stopPropagation(); avatarWrap.removeChild(wrap); layers = layers.filter(l => l !== wrap); }; wrap.appendChild(remove); remove.setAttribute('data-html2canvas-ignore', ''); } wrap.addEventListener("mousedown", e => { if (isMain && mainLocked) return; if (clearViewMode) return; // Disable dragging in clear view mode active = wrap; const rect = wrap.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; wrap.style.zIndex = ++zCounter; }); avatarWrap.appendChild(wrap); layers.push(wrap); // Apply clear view mode to new layer if active if (clearViewMode) { avatarWrap.classList.add("hide-ui"); } } function scaleLayer(wrap, factor) { if (clearViewMode) return; // Disable scaling in clear view mode let s = parseFloat(wrap.dataset.scale) * factor; s = Math.max(0.1, Math.min(5, s)); wrap.dataset.scale = s; wrap.querySelector("img").style.transform = `scale(${s})`; } document.addEventListener("mousemove", e => { if (!active || clearViewMode) return; // Disable dragging in clear view mode let newX = e.clientX - avatarWrap.getBoundingClientRect().left - offsetX; let newY = e.clientY - avatarWrap.getBoundingClientRect().top - offsetY; newX = Math.max(0, Math.min(newX, avatarWrap.clientWidth - active.offsetWidth)); newY = Math.max(0, Math.min(newY, avatarWrap.clientHeight - active.offsetHeight)); active.style.left = newX + "px"; active.style.top = newY + "px"; }); document.addEventListener("mouseup", () => active = null); document.getElementById("lockBtn").onclick = () => { if (clearViewMode) return; // Disable locking in clear view mode mainLocked = !mainLocked; const main = layers.find(l => l.dataset.main === "1"); if (main) main.classList.toggle("locked", mainLocked); }; document.getElementById("exportBtn").onclick = () => { if (clearViewMode) return; // Disable export in clear view mode const out = new URL(location.origin + location.pathname); let i = 1; out.searchParams.set("webdir", webdir); layers.forEach(l => { const x = parseInt(l.style.left), y = parseInt(l.style.top); if (l.dataset.main === "1") { out.searchParams.set("img", l.querySelector("img").src); out.searchParams.set("imgx", x); out.searchParams.set("imgy", y); } else { out.searchParams.set("dressup" + i, l.querySelector("img").src); out.searchParams.set("dx" + i, x); out.searchParams.set("dy" + i, y); out.searchParams.set("scale" + i, l.dataset.scale); out.searchParams.set("behind" + i, l.dataset.behind); i++; } }); navigator.clipboard.writeText(out.toString()); alert("Scene URL copied to clipboard!"); }; params.forEach((v, k) => { if (k === "img") createLayer(v, true, false, 1, parseInt(params.get("imgx")) || 0, parseInt(params.get("imgy")) || 0); if (k.startsWith("dressup")) { const i = k.replace("dressup", ""); createLayer(v, false, params.get("behind" + i) === "1", parseFloat(params.get("scale" + i)) || 1, parseInt(params.get("dx" + i)) || 50, parseInt(params.get("dy" + i)) || 50); } }); fetch(webdir).then(r => r.text()).then(html => { const doc = new DOMParser().parseFromString(html, "text/html"); const seen = new Set(); doc.querySelectorAll("a[href]").forEach(a => { const href = a.getAttribute("href"); if (!href || !href.match(/\.(png|jpg|jpeg|gif|apng)$/i)) return; const full = new URL(href, webdir).href; if (seen.has(full)) return; seen.add(full); const div = document.createElement("div"); div.className = "thumb"; div.innerHTML = `<img src="${full}"><div>${full.split("/").pop()}</div>`; div.onclick = () => createLayer(full); browser.appendChild(div); }); }); document.getElementById("gifBtn").onclick = () => { if (clearViewMode) return; // Disable crop in clear view mode cropRect.style.display = "block"; cropRect.style.left = "50px"; cropRect.style.top = "50px"; cropRect.style.width = "380px"; cropRect.style.height = "500px"; }; // Drag / resize crop cropRect.addEventListener("mousedown", e => { if (e.target.tagName === "BUTTON" || clearViewMode) return; // Disable in clear view cropActive = true; const rect = cropRect.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; }); document.addEventListener("mousemove", e => { if (!cropActive || clearViewMode) return; // Disable in clear view let newX = e.clientX - avatarWrap.getBoundingClientRect().left - offsetX; let newY = e.clientY - avatarWrap.getBoundingClientRect().top - offsetY; newX = Math.max(0, Math.min(newX, 400 - parseInt(cropRect.style.width))); newY = Math.max(0, Math.min(newY, 600 - parseInt(cropRect.style.height))); cropRect.style.left = newX + "px"; cropRect.style.top = newY + "px"; }); document.addEventListener("mouseup", () => cropActive = false); document.getElementById("cropPlus").onclick = () => { if (clearViewMode) return; // Disable in clear view let w = parseInt(cropRect.style.width) + 20; let h = parseInt(cropRect.style.height) + 20; w = Math.min(w, 500 - parseInt(cropRect.style.left)); h = Math.min(h, 600 - parseInt(cropRect.style.top)); cropRect.style.width = w + "px"; cropRect.style.height = h + "px"; }; document.getElementById("cropMinus").onclick = () => { if (clearViewMode) return; // Disable in clear view let w = Math.max(50, parseInt(cropRect.style.width) - 20); let h = Math.max(50, parseInt(cropRect.style.height) - 20); cropRect.style.width = w + "px"; cropRect.style.height = h + "px"; }; document.getElementById("previewBtn").onclick = async () => { if (clearViewMode) return; // Disable preview in clear view mode const cx = parseInt(cropRect.style.left); const cy = parseInt(cropRect.style.top); const cw = parseInt(cropRect.style.width); const ch = parseInt(cropRect.style.height); const fullCanvas = await html2canvas(avatarWrap, { backgroundColor: null, useCORS: true, allowTaint: false, ignoreElements: el => el.hasAttribute('data-html2canvas-ignore') }); previewCanvas.width = cw; previewCanvas.height = ch; previewCtx.drawImage(fullCanvas, cx, cy, cw, ch, 0, 0, cw, ch); }; downloadGifBtn.onclick = async () => { if (clearViewMode) return; // Disable download in clear view mode if (cropRect.style.display === 'none') { alert('Please activate the crop area first with "GIF Please (Crop)"'); return; } downloadGifBtn.disabled = true; recordStatus.textContent = 'Preparing...'; const cx = parseInt(cropRect.style.left); const cy = parseInt(cropRect.style.top); const cw = parseInt(cropRect.style.width); const ch = parseInt(cropRect.style.height); const animatedLayers = layers.filter(l => { const img = l.querySelector('img'); return img && img.src.toLowerCase().endsWith('.gif'); }); let frameDelay = 100; // default 10fps if (animatedLayers.length > 0) { const allDelays = []; for (const layer of animatedLayers) { const img = layer.querySelector('img'); const response = await fetch(img.src, { mode: 'cors' }); const buffer = await response.arrayBuffer(); const bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.length - 6; i++) { if (bytes[i] === 0x21 && bytes[i+1] === 0xF9) { const delay = (bytes[i+4] | (bytes[i+5] << 8)) * 10; if (delay > 0) allDelays.push(delay); } } } if (allDelays.length > 0) { frameDelay = Math.min(...allDelays); } } const maxFrames = 100; const useZip = zipCheckbox.checked; const capturedFrames = []; // store crop canvases or blobs recordStatus.textContent = `Capturing ${maxFrames} frames...`; // Restart animations to sync const loadPromises = []; for (const layer of animatedLayers) { const img = layer.querySelector('img'); const baseSrc = img.src.split('?')[0]; img.src = ''; img.src = baseSrc + '?t=' + Date.now(); loadPromises.push(new Promise(res => img.addEventListener('load', res, {once: true}))); } await Promise.all(loadPromises); // Small delay to ensure animation starts await new Promise(res => setTimeout(res, 50)); for (let f = 0; f < maxFrames; f++) { const fullCanvas = await html2canvas(avatarWrap, { backgroundColor: null, useCORS: true, allowTaint: false, ignoreElements: el => el.hasAttribute('data-html2canvas-ignore') }); const cropCanvas = document.createElement('canvas'); cropCanvas.width = cw; cropCanvas.height = ch; const ctx = cropCanvas.getContext('2d'); ctx.drawImage(fullCanvas, cx, cy, cw, ch, 0, 0, cw, ch); if (useZip) { const blob = await new Promise(resolve => cropCanvas.toBlob(resolve, 'image/png')); capturedFrames.push(blob); } else { capturedFrames.push(ctx); } recordStatus.textContent = `Captured ${f+1}/${maxFrames} frames...`; await new Promise(res => setTimeout(res, frameDelay)); } if (useZip) { recordStatus.textContent = 'Creating ZIP (this may take a moment)...'; const zip = new JSZip(); capturedFrames.forEach((blob, i) => { zip.file(`frame_${String(i+1).padStart(3, '0')}.png`, blob); }); const zipBlob = await zip.generateAsync({type: "blob"}); downloadBlob(zipBlob, 'my-animated-frames.zip'); recordStatus.textContent = 'ZIP downloaded!'; } else { recordStatus.textContent = 'Rendering GIF (this may take a while)...'; const gif = new GIF({ workers: 4, quality: 10, width: cw, height: ch }); capturedFrames.forEach(ctx => { gif.addFrame(ctx, {copy: true, delay: frameDelay}); }); gif.on('finished', blob => { downloadBlob(blob, 'my-animated-avatar.gif'); recordStatus.textContent = 'GIF downloaded!'; }); gif.render(); } downloadGifBtn.disabled = false; }; function downloadBlob(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } </script> </body> </html> 2026-01-02-Code-replace-tool --------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Delimiter Replace Tool</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .row { display: flex; gap: 15px; margin-bottom: 15px; } textarea { width: 100%; height: 160px; font-family: monospace; } .small-input { width: 180px; } button { padding: 8px 14px; cursor: pointer; } .preview { border: 1px solid #ccc; padding: 10px; height: 120px; overflow: auto; font-family: monospace; font-size: 11px; /* zoomed out */ white-space: pre-wrap; background: #f9f9f9; } .column { flex: 1; } h3 { margin: 6px 0; } </style> </head> <body> <h2>Delimiter Replace Tool</h2> <div class="row"> <div class="column"> <h3>Original Text</h3> <textarea id="originalText"></textarea> </div> <div class="column"> <h3>Replacement Text</h3> <textarea id="replacementText"></textarea> </div> </div> <div class="row"> <input id="startDelim" class="small-input" placeholder="Start delimiter"> <input id="endDelim" class="small-input" placeholder="End delimiter"> <button onclick="extractDelimiters()">Extract</button> <button onclick="replaceText()">Replace</button> </div> <div class="row"> <div class="column"> <h3>Before (zoomed out)</h3> <div id="beforePreview" class="preview"></div> <button onclick="copyText('beforePreview')">Copy Old</button> </div> <div class="column"> <h3>After (zoomed out)</h3> <div id="afterPreview" class="preview"></div> <button onclick="copyText('afterPreview')">Copy New</button> </div> </div> <script> function escapeRegex(text) { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function replaceText() { const original = document.getElementById("originalText").value; const replacement = document.getElementById("replacementText").value; const start = document.getElementById("startDelim").value; const end = document.getElementById("endDelim").value; if (!start || !end) { alert("Please enter both delimiters."); return; } const pattern = new RegExp( escapeRegex(start) + "[\\s\\S]*?" + escapeRegex(end), "g" ); const updated = original.replace(pattern, replacement); document.getElementById("beforePreview").textContent = original; document.getElementById("afterPreview").textContent = updated; } function extractDelimiters() { const text = document.getElementById("replacementText").value; // Split lines and remove empty lines at start/end const lines = text .split(/\r?\n/) .map(l => l.trim()) .filter(l => l.length > 0); if (lines.length < 2) { alert("Replacement text must contain at least two lines."); return; } document.getElementById("startDelim").value = lines[0]; document.getElementById("endDelim").value = lines[lines.length - 1]; } function copyText(elementId) { const text = document.getElementById(elementId).textContent; navigator.clipboard.writeText(text); } </script> </body> </html> Subtitle-Styler-srt2aegissub ----------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Karaoke Designer</title> <style> :root { --bg: #0f0f0f; --panel: #1a1a1a; --accent: #00f2ff; --selection: #ff3e81 } body { margin: 0; background: var(--bg); color: #eee; font-family: sans-serif; display: flex; height: 100vh; overflow: hidden } #sidebar { width: 420px; background: var(--panel); border-right: 1px solid #333; display: flex; flex-direction: column } .scroll-area { flex-grow: 1; overflow-y: auto; padding: 20px } .file-inputs { margin-bottom: 15px } .file-row { display: flex; gap: 10px; margin-bottom: 10px; align-items: center } .file-label { font-size: 11px; color: #888; width: 70px } .file-btn, .url-input { flex: 1; padding: 8px; background: #222; color: #eee; border: 1px solid #444; border-radius: 4px; font-size: 11px } .file-btn { text-align: center; cursor: pointer } .file-btn:hover { background: #333 } .url-input { color: #fff } .drop-zone { border: 2px dashed #444; padding: 15px; text-align: center; border-radius: 8px; margin-bottom: 10px; cursor: pointer; font-size: 12px } .drop-zone.dragover { border-color: var(--accent); background: rgba(0, 242, 255, 0.1) } .checkbox-row { display: flex; align-items: center; gap: 8px; margin: 8px 0; padding: 5px; background: #222; border-radius: 4px } .checkbox-row label { margin: 0; font-size: 11px; color: #ccc } .section-title { font-size: 10px; font-weight: bold; text-transform: uppercase; color: var(--accent); letter-spacing: 1px; margin: 15px 0 8px; border-bottom: 1px solid #333; padding-bottom: 3px } .line-item { margin-bottom: 10px; padding: 10px; background: #222; border-radius: 8px; cursor: pointer; border: 1px solid transparent; position: relative } .line-item.active-line { border-color: var(--selection) } .timestamp { font-family: monospace; font-size: 10px; color: #666; display: block; margin-bottom: 5px } .speed-control { position: absolute; right: 10px; top: 30px; display: flex; align-items: center; gap: 5px; background: rgba(0, 0, 0, 0.7); padding: 3px 8px; border-radius: 4px; border: 1px solid #444 } .speed-control.hidden { display: none } .speed-control label { font-size: 8px; color: #aaa; white-space: nowrap } .speed-control input { width: 40px; padding: 2px 4px; font-size: 10px; background: #333; color: #fff; border: 1px solid #555; border-radius: 3px } .word-token { display: inline-block; padding: 3px 6px; margin: 2px; border-radius: 4px; background: #333; cursor: pointer; font-size: 12px; position: relative } .word-token.active-selection { outline: 2px solid var(--selection); background: #444 } .word-time-tag { position: absolute; top: -12px; left: 0; font-size: 8px; background: var(--selection); color: #fff; padding: 1px 3px; border-radius: 2px; pointer-events: none; display: none } .has-timing .word-time-tag { display: block } #viewer { flex-grow: 1; display: flex; flex-direction: column; background: #000; position: relative } #stage { position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden } video { width: 90%; max-height: 80%; border-radius: 4px; border: 1px solid #333 } #sub-overlay { position: absolute; bottom: 15%; width: 100%; text-align: center; font-weight: 900; pointer-events: none; display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 0.3em } .movable-word { display: inline-block; position: relative; transition: color 0.2s, transform 0.2s } .word-inactive { opacity: 0.4; filter: grayscale(1) } .transition-bounce .word-active { animation: bounce 0.3s ease } .transition-fade .word-inactive { opacity: 0 } .transition-fade .word-active { opacity: 1; animation: fadeIn 0.3s ease } .transition-fade .word-past { opacity: 0.2 } .transition-spin .word-active { animation: spin 0.5s ease } .transition-ball .word-active { animation: ballBounce 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) } .transition-zoom .word-active { animation: zoomIn 0.3s ease } .transition-slide .word-active { animation: slideUp 0.4s ease } .transition-typing .word-active { animation: typing 0.3s steps(4, end) } .transition-wave .word-active { animation: wave 0.5s ease } .transition-single .word { opacity: 0; transform: scale(0.8) } .transition-single .word-active { opacity: 1 !important; transform: scale(1) !important; animation: singleWordIn 0.3s ease forwards } .transition-single .word-past { opacity: 0 !important; transform: scale(0.8) !important; animation: singleWordOut 0.3s ease forwards } .transition-single .word-inactive { opacity: 0 !important; transform: scale(0.8) !important } .transition-line-word .word { position: relative; transition: opacity 0.2s ease, transform 0.2s ease } .transition-line-word .word-active { opacity: 1 !important } .transition-line-word .word-past, .transition-line-word .word-inactive { opacity: 0 !important; pointer-events: none } @keyframes bounce { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-15px) } } @keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } } @keyframes spin { from { transform: rotate(0deg) scale(0.5); opacity: 0 } to { transform: rotate(360deg) scale(1); opacity: 1 } } @keyframes ballBounce { 0%, 100% { transform: translateY(0) } 25% { transform: translateY(-30px) scale(1.1) } 50% { transform: translateY(0) scale(1) } 75% { transform: translateY(-10px) scale(1.05) } } @keyframes zoomIn { from { transform: scale(0.3); opacity: 0 } to { transform: scale(1); opacity: 1 } } @keyframes slideUp { from { transform: translateY(30px); opacity: 0 } to { transform: translateY(0); opacity: 1 } } @keyframes typing { from { width: 0; overflow: hidden } to { width: auto } } @keyframes wave { 0% { transform: translateY(0) } 25% { transform: translateY(-10px) } 50% { transform: translateY(0) } 75% { transform: translateY(-5px) } 100% { transform: translateY(0) } } @keyframes singleWordIn { from { opacity: 0; transform: scale(0.8) } to { opacity: 1; transform: scale(1) } } @keyframes singleWordOut { from { opacity: 1; transform: scale(1) } to { opacity: 0; transform: scale(0.8) } } @keyframes blink { 0%, 100% { opacity: 1 } 50% { opacity: 0 } } .blink-05 { animation: blink 0.5s infinite } .blink-10 { animation: blink 1s infinite } .blink-15 { animation: blink 1.5s infinite } .controls { padding: 15px; background: #111; border-top: 1px solid #333 } .control-row { display: flex; gap: 8px; margin-bottom: 8px } label { font-size: 9px; color: #888; display: block; margin-bottom: 3px } select, input { width: 100%; padding: 6px; background: #222; color: #fff; border: 1px solid #444; border-radius: 4px; font-size: 11px } .btn-group { display: flex; gap: 5px; margin-top: 5px } .btn-sm { flex: 1; padding: 5px; font-size: 9px; background: #333; color: #eee; border: none; cursor: pointer; border-radius: 4px } .btn-sm:hover { background: #444 } .btn-accent { background: var(--selection) !important; color: #fff !important } .btn-download { background: var(--accent); color: #000; border: none; padding: 12px; width: 100%; font-weight: bold; cursor: pointer; border-radius: 4px; margin-top: 8px } </style> </head> <body> <div id="sidebar"> <div class="scroll-area"> <h2 style="margin-top:0">Editor</h2> <div class="file-inputs"> <div class="file-row"> <div class="file-label">Video:</div> <input type="file" id="video-file" accept="video/*" style="display:none"> <div class="file-btn" onclick="document.getElementById('video-file').click()">Choose Video</div> <input type="text" id="video-url" class="url-input" placeholder="Or paste URL"> </div> <div class="file-row"> <div class="file-label">Subtitles:</div> <input type="file" id="sub-file" accept=".srt" style="display:none"> <div class="file-btn" onclick="document.getElementById('sub-file').click()">Choose SRT</div> <input type="text" id="sub-url" class="url-input" placeholder="Or paste URL"> </div> </div> <div class="drop-zone" id="drop-zone">Or Drag Video & SRT here</div> <div class="checkbox-row"> <input type="checkbox" id="translucent-toggle" checked> <label for="translucent-toggle">Show translucent words before active</label> </div> <div class="section-title">Global Master Style</div> <div class="control-row"> <div> <label>Transition Effect</label> <select id="g-transition"> <option value="none">None</option> <option value="bounce">Bounce</option> <option value="fade">Fade</option> <option value="spin">Spin</option> <option value="ball">Ball Bounce</option> <option value="zoom">Zoom</option> <option value="slide">Slide</option> <option value="typing">Typing</option> <option value="wave">Wave</option> <option value="single">One Word</option> <option value="line-word">Line Word by Word</option> </select> </div> </div> <div class="control-row"> <div> <label>Font</label> <select id="g-font"> <option>Arial Black</option> <option>Impact</option> <option>Verdana</option> </select> </div> <div> <label>Size</label> <input type="number" id="g-size" value="50"> </div> </div> <div class="control-row"> <div> <label>Global Effect</label> <select id="g-effect"> <option value="">None</option> <option value="huge">Huge</option> <option value="outline">Outline</option> <option value="italic">Italic</option> <option value="glow">Glow</option> <option value="blink-05">Blink 0.5s</option> <option value="blink-10">Blink 1.0s</option> <option value="blink-15">Blink 1.5s</option> <option value="strike">Strike</option> <option value="under">Under</option> </select> </div> <div> <label>Color</label> <input type="color" id="g-color" value="#ffffff"> </div> </div> <div class="section-title">Timeline Controls</div> <div class="control-row"> <div> <label>Timing Mode</label> <select id="timing-mode"> <option value="uniform" selected>Uniform (equal time per word)</option> <option value="syllable">Syllable-based (longer words last longer)</option> </select> </div> </div> <div class="btn-group"> <button class="btn-sm btn-accent" onclick="distributeAll()">Auto-Fill All Timing</button> <button class="btn-sm" onclick="resetAllTiming()">Reset All</button> </div> <div id="lines-list">Waiting for files...</div> </div> <div class="controls" id="word-panel"> <div class="section-title" id="sel-label" style="margin-top:0;color:var(--selection)">No selection</div> <div id="timing-controls" style="display:none;background:#1a1a1a;padding:10px;border-radius:6px;margin-bottom:10px;border:1px solid #333"> <label style="color:var(--accent)">Word Timing</label> <div class="control-row"> <div><label>Start</label><input type="number" id="w-start" step="0.1"></div> <div><label>End</label><input type="number" id="w-end" step="0.1"></div> <div><label>Linger (s)</label><input type="number" id="w-linger" step="0.1" min="0" value="0"></div> </div> </div> <div class="control-row"> <div><label>Font</label><select id="w-font"><option value="inherit">Inherit</option><option>Arial Black</option><option>Impact</option><option>Verdana</option></select></div> <div><label>Color</label><input type="color" id="w-color" value="#ffffff"></div> </div> <div class="control-row"> <div><label>Effect</label><select id="w-effect"><option value="inherit">Inherit</option><option value="">None</option><option value="huge">Huge</option><option value="outline">Outline</option><option value="italic">Italic</option><option value="glow">Glow</option><option value="blink-05">Blink 0.5s</option><option value="blink-10">Blink 1.0s</option><option value="blink-15">Blink 1.5s</option><option value="strike">Strike</option><option value="under">Under</option></select></div> <div style="display:flex;align-items:flex-end"><button class="btn-sm" onclick="resetWordPos()">Reset Pos</button></div> </div> <button class="btn-download" onclick="exportASS()">Download .ASS</button> </div> </div> <div id="viewer"> <div id="stage" onmousedown="startMove(event)" onmousemove="doMove(event)" onmouseup="endMove()"> <video id="v-player" controls></video> <div id="sub-overlay"></div> </div> </div> <script> let subData = [], selectedWordId = null, activeLineIdx = null, isMoving = false const vPlayer = document.getElementById('v-player'), overlay = document.getElementById('sub-overlay'), stage = document.getElementById('stage'), dz = document.getElementById('drop-zone'), videoFileInput = document.getElementById('video-file'), subFileInput = document.getElementById('sub-file'), videoUrlInput = document.getElementById('video-url'), subUrlInput = document.getElementById('sub-url'), translucentToggle = document.getElementById('translucent-toggle') document.addEventListener('DOMContentLoaded', () => { const urlParams = new URLSearchParams(window.location.search) const videoParam = urlParams.get('video') const subtitleParam = urlParams.get('subtitle') if (videoParam) { videoUrlInput.value = videoParam; loadVideoFromURL() } if (subtitleParam) { subUrlInput.value = subtitleParam; loadSubFromURL() } }) videoFileInput.addEventListener('change', e => { if (e.target.files[0]) vPlayer.src = URL.createObjectURL(e.target.files[0]) }) subFileInput.addEventListener('change', e => { if (e.target.files[0]) e.target.files[0].text().then(parseSRT) }) function loadVideoFromURL() { const url = videoUrlInput.value.trim(); if (url) vPlayer.src = url } async function loadSubFromURL() { const url = subUrlInput.value.trim(); if (url) { try { const response = await fetch(url); const text = await response.text(); parseSRT(text) } catch (e) { alert('Failed to load subtitles') } } } dz.addEventListener('dragover', e => { e.preventDefault(); dz.classList.add('dragover') }) dz.addEventListener('drop', e => { e.preventDefault(); dz.classList.remove('dragover'); Array.from(e.dataTransfer.files).forEach(file => { if (file.name.endsWith('.srt')) { file.text().then(parseSRT); subUrlInput.value = '' } else { vPlayer.src = URL.createObjectURL(file); videoUrlInput.value = '' } }) }) function parseSRT(text) { subData = text.trim().split(/\n\s*\n/).map((block, lIdx) => { const lines = block.split('\n') if (lines.length < 3) return null const words = lines.slice(2).join(' ').split(' ').filter(w => w).map((w, wIdx) => ({ text: w.replace(/[^a-zA-Z0-9']/g, ''), // Clean punctuation for syllable count rawText: w, color: null, font: 'inherit', effect: 'inherit', size: 'inherit', offX: 0, offY: 0, kStart: 0, kEnd: 0, linger: 0, id: `w-${lIdx}-${wIdx}` })) return { start: lines[1].split(' --> ')[0].trim().replace(',', '.'), end: lines[1].split(' --> ')[1].trim().replace(',', '.'), words, speed: 1.0 } }).filter(x => x) renderEditor() } function timeToSec(t) { const parts = t.split(/[:,.]/); if (parts.length !== 4) return 0; return parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]) + parseInt(parts[3]) / 1000 } function countSyllables(word) { if (!word) return 1; word = word.toLowerCase(); if (word.length <= 3) return 1; word = word.replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, ''); word = word.replace(/^y/, ''); const matches = word.match(/[aeiouy]{1,2}/g); return matches ? matches.length : 1; } function renderEditor() { const gCol = document.getElementById('g-color').value document.getElementById('lines-list').innerHTML = subData.map((line, idx) => { const hasTiming = line.words.some(w => w.kEnd > 0) const hasAutoTiming = line.words.every(w => w.kEnd > 0) return `<div class="line-item ${activeLineIdx === idx ? 'active-line' : ''} ${hasTiming ? 'has-timing' : ''}" onclick="selectLine(${idx},event)"> <span class="timestamp">${line.start}</span> <div style="position:absolute;right:10px;top:10px;"> <button class="btn-sm" onclick="distributeLine(${idx},event)">Auto-Time</button> <button class="btn-sm" onclick="resetLineTiming(${idx},event)">Reset</button> </div> <div class="speed-control ${hasAutoTiming ? '' : 'hidden'}" id="speed-control-${idx}"> <label>Speed:</label> <input type="number" id="speed-input-${idx}" value="${line.speed}" step="0.1" min="0.1" max="10" onchange="applySpeedMultiplier(${idx},this.value)" onclick="event.stopPropagation()"> </div> ${line.words.map(w => `<span class="word-token ${w.id === selectedWordId ? 'active-selection' : ''}" id="${w.id}" onclick="selectWord('${w.id}',event)" style="color:${w.color || gCol}"> <span class="word-time-tag">${(w.kStart * line.speed).toFixed(1)}s${w.linger > 0 ? '+' + w.linger.toFixed(1) : ''}</span>${w.rawText} </span>`).join('')} </div>` }).join('') } function applySpeedMultiplier(lineIdx, speedValue) { const speed = parseFloat(speedValue); if (isNaN(speed) || speed <= 0) return; const line = subData[lineIdx]; line.speed = speed; distributeLine(lineIdx); renderEditor(); renderPreview(); } function selectLine(idx, e) { if (e && (e.target.tagName === 'BUTTON' || e.target.classList.contains('word-token') || e.target.classList.contains('speed-control') || e.target.type === 'number')) return; activeLineIdx = idx; selectedWordId = null; document.getElementById('timing-controls').style.display = 'none'; vPlayer.currentTime = timeToSec(subData[idx].start); renderEditor(); renderPreview() } function selectWord(id, e) { e.stopPropagation(); selectedWordId = id; const word = findWord(id); document.getElementById('timing-controls').style.display = 'block'; document.getElementById('w-start').value = word.kStart; document.getElementById('w-end').value = word.kEnd; document.getElementById('w-color').value = word.color || document.getElementById('g-color').value; document.getElementById('w-font').value = word.font; document.getElementById('w-effect').value = word.effect; document.getElementById('w-linger').value = word.linger || 0; document.getElementById('sel-label').innerText = `Editing: "${word.rawText}"`; renderEditor(); renderPreview() } function findWord(id) { for (let l of subData) { let w = l.words.find(x => x.id === id); if (w) return w } } function distributeLine(idx, e) { if (e) e.stopPropagation(); const line = subData[idx]; const duration = timeToSec(line.end) - timeToSec(line.start); const timingMode = document.getElementById('timing-mode').value; let current = 0; if (timingMode === 'uniform') { const segment = duration / line.words.length / line.speed; line.words.forEach((w, i) => { w.kStart = parseFloat((i * segment).toFixed(3)); w.kEnd = parseFloat(((i + 1) * segment).toFixed(3)); }); } else { // syllable-based let totalSyl = 0; line.words.forEach(w => { totalSyl += countSyllables(w.text); }); if (totalSyl === 0) totalSyl = 1; const segment = duration / totalSyl / line.speed; line.words.forEach(w => { const syl = countSyllables(w.text); w.kStart = parseFloat(current.toFixed(3)); current += syl * segment; w.kEnd = parseFloat(current.toFixed(3)); }); } renderEditor(); renderPreview() } function distributeAll() { subData.forEach((_, i) => distributeLine(i)) } function resetLineTiming(idx, e) { if (e) e.stopPropagation(); subData[idx].words.forEach(w => { w.kStart = 0; w.kEnd = 0; w.linger = 0 }); subData[idx].speed = 1.0; renderEditor(); renderPreview() } function resetAllTiming() { subData.forEach((line, i) => { line.words.forEach(w => { w.kStart = 0; w.kEnd = 0; w.linger = 0 }); line.speed = 1.0 }); renderEditor(); renderPreview() } document.getElementById('w-start').oninput = document.getElementById('w-end').oninput = document.getElementById('w-color').oninput = document.getElementById('w-font').onchange = document.getElementById('w-effect').onchange = document.getElementById('w-linger').oninput = function autoApply() { if (!selectedWordId) return const word = findWord(selectedWordId) word.kStart = parseFloat(document.getElementById('w-start').value) || 0 word.kEnd = parseFloat(document.getElementById('w-end').value) || 0 word.color = document.getElementById('w-color').value word.font = document.getElementById('w-font').value word.effect = document.getElementById('w-effect').value word.linger = parseFloat(document.getElementById('w-linger').value) || 0 const lineIdx = parseInt(selectedWordId.split('-')[1]) const wordIdx = parseInt(selectedWordId.split('-')[2]) const line = subData[lineIdx] if (wordIdx < line.words.length - 1) { const nextWord = line.words[wordIdx + 1]; const currentWordEnd = word.kEnd * line.speed; const nextWordStart = nextWord.kStart * line.speed; const maxLinger = nextWordStart - currentWordEnd; if (word.linger > maxLinger) { word.linger = Math.max(0, maxLinger); document.getElementById('w-linger').value = word.linger } } renderEditor() renderPreview() } let moveStartX = 0, moveStartY = 0, originalOffX = 0, originalOffY = 0 function startMove(e) { if (!selectedWordId) return; e.preventDefault(); const word = findWord(selectedWordId); if (!word) return; moveStartX = e.clientX; moveStartY = e.clientY; originalOffX = word.offX || 0; originalOffY = word.offY || 0; isMoving = true; document.body.style.cursor = 'grabbing' } function endMove() { isMoving = false; document.body.style.cursor = 'default' } function doMove(e) { if (!isMoving || !selectedWordId) return; e.preventDefault(); const word = findWord(selectedWordId); if (!word) return; const deltaX = e.clientX - moveStartX; const deltaY = e.clientY - moveStartY; word.offX = originalOffX + deltaX / 2; word.offY = originalOffY + deltaY / 2; renderPreview(); renderEditor() } function resetWordPos() { if (selectedWordId) { const w = findWord(selectedWordId); w.offX = 0; w.offY = 0; renderPreview(); renderEditor() } } vPlayer.ontimeupdate = renderPreview document.getElementById('g-transition').onchange = document.getElementById('g-font').onchange = document.getElementById('g-size').oninput = document.getElementById('g-effect').onchange = document.getElementById('g-color').oninput = translucentToggle.onchange = renderPreview function applyEffectCSS(eff, color) { let css = "", className = "" if (eff === "huge") css += `transform:scale(1.6);margin:0 10px;` if (eff === "italic") css += `font-style:italic;` if (eff === "glow") css += `filter:drop-shadow(0 0 10px ${color});` if (eff === "outline") css += `-webkit-text-stroke:2px ${color};color:white;` if (eff.startsWith('blink')) className = eff if (eff === "strike") css += `text-decoration:line-through;color:${color};` if (eff === "under") css += `text-decoration:underline;color:${color};` return { css, className } } function renderPreview() { const now = vPlayer.currentTime const activeLine = subData.find(s => now >= timeToSec(s.start) && now <= timeToSec(s.end)) const gFont = document.getElementById('g-font').value, gSize = document.getElementById('g-size').value, gEff = document.getElementById('g-effect').value, gCol = document.getElementById('g-color').value, gTransition = document.getElementById('g-transition').value, showTranslucent = translucentToggle.checked overlay.className = '' overlay.classList.add(`transition-${gTransition}`) if (activeLine) { const elapsed = now - timeToSec(activeLine.start) const hasAnyK = activeLine.words.some(w => w.kEnd > 0) overlay.innerHTML = activeLine.words.map(w => { const color = w.color || gCol, font = w.font === 'inherit' ? gFont : w.font, size = gSize, eff = w.effect === 'inherit' ? gEff : w.effect const { css: effCSS, className: effClass } = applyEffectCSS(eff, color) const effectiveKStart = w.kStart * activeLine.speed, effectiveKEnd = w.kEnd * activeLine.speed, effectiveLinger = w.linger || 0 let kClass = "word-inactive" if (hasAnyK) { if (gTransition === 'line-word') { const isActive = elapsed >= effectiveKStart && elapsed < effectiveKEnd const isPast = elapsed >= effectiveKEnd kClass = isActive ? "word-active" : (isPast ? "word-past" : "word-inactive") } else { if (elapsed < effectiveKStart) { kClass = showTranslucent ? "word-inactive" : "word-inactive" } else if (elapsed >= effectiveKStart && elapsed < effectiveKEnd) { kClass = "word-active" } else if (elapsed >= effectiveKEnd && elapsed < effectiveKEnd + effectiveLinger) { kClass = gTransition === 'single' ? "word-active" : "word-past" } else { kClass = "word-past" } } } else { kClass = "word-active" } let style = `color:${color};font-family:'${font}';font-size:${size}px;` style += `transform:translate(${w.offX}px,${w.offY}px);` style += effCSS const baseClass = (gTransition === 'single' || gTransition === 'line-word') ? 'word ' : '', fullClass = `${baseClass}movable-word ${w.id === selectedWordId ? 'is-selected' : ''} ${effClass} ${kClass}` return `<span class="${fullClass}" style="${style}">${w.rawText}</span>` }).join('') if (gTransition === 'line-word' && hasAnyK) { let currentActiveWord = null for (let i = activeLine.words.length - 1; i >= 0; i--) { const w = activeLine.words[i] const effectiveKStart = w.kStart * activeLine.speed const effectiveKEnd = w.kEnd * activeLine.speed if (elapsed >= effectiveKStart && elapsed < effectiveKEnd) { currentActiveWord = w; break } } activeLine.words.forEach(w => { const wordEl = overlay.querySelector(`[id="${w.id}"]`) if (wordEl) { if (currentActiveWord && w.id !== currentActiveWord.id) { wordEl.classList.add('word-past'); wordEl.classList.remove('word-active', 'word-inactive') } else if (currentActiveWord && w.id === currentActiveWord.id) { wordEl.classList.add('word-active'); wordEl.classList.remove('word-past', 'word-inactive') } } }) } } else { overlay.innerHTML = "" } } function exportASS() { if (subData.length === 0) { alert("No subtitle data!"); return } const gFont = document.getElementById('g-font').value, gSize = document.getElementById('g-size').value, gCol = document.getElementById('g-color').value, gEff = document.getElementById('g-effect').value, gTransition = document.getElementById('g-transition').value, showTranslucent = translucentToggle.checked let assContent = `[Script Info] Title: Karaoke Export ScriptType: v4.00+ PlayResX: 1920 PlayResY: 1080 ScaledBorderAndShadow: yes [V4+ Styles] Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,OutlineColour,BackColour,Bold,Italic,Underline,StrikeOut,ScaleX,ScaleY,Spacing,Angle,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,Encoding Style: Default,${gFont},${gSize},&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,3,2,10,10,10,1 [Events] Format: Layer,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text ` function getResetTags() { return '\\u0\\s0\\i0\\bord2\\shad0\\blur0' } function getEffectTags(effect, color) { let tags = '' switch (effect) { case 'huge': tags += '\\fs' + Math.round(parseInt(gSize) * 1.5) + '\\fscx150\\fscy150'; break case 'outline': tags += '\\bord4\\3c&H' + hexToASSColor(color); break case 'italic': tags += '\\i1'; break case 'glow': tags += '\\shad8\\blur3'; break case 'blink-05': tags += '\\t(0,500,\\alpha&HFF&)\\t(500,1000,\\alpha&H00&)'; break case 'blink-10': tags += '\\t(0,1000,\\alpha&HFF&)\\t(1000,2000,\\alpha&H00&)'; break case 'blink-15': tags += '\\t(0,1500,\\alpha&HFF&)\\t(1500,3000,\\alpha&H00&)'; break case 'strike': tags += '\\s1'; break case 'under': tags += '\\u1'; break } return tags } function getTransitionTags(transition, duration, color) { let tags = '' const durationMs = Math.round(duration * 1000) switch (transition) { case 'bounce': tags += `\\t(0,${Math.round(durationMs / 2)},\\fscy120)\\t(${Math.round(durationMs / 2)},${durationMs},\\fscy100)`; break case 'fade': tags += `\\t(0,${durationMs},\\alpha&H00&\\alpha&HFF&)`; break case 'spin': tags += `\\t(0,${durationMs},\\frx360\\fry360)`; break case 'ball': tags += `\\t(0,${Math.round(durationMs / 4)},\\fscy130)\\t(${Math.round(durationMs / 4)},${Math.round(durationMs / 2)},\\fscy100)\\t(${Math.round(durationMs / 2)},${Math.round(durationMs * 3 / 4)},\\fscy110)\\t(${Math.round(durationMs * 3 / 4)},${durationMs},\\fscy100)`; break case 'zoom': tags += `\\t(0,${durationMs},\\fscx30\\fscy30\\fscx100\\fscy100)`; break case 'slide': tags += `\\t(0,${durationMs},\\fsp-50\\fsp0)`; break case 'typing': tags += `\\t(0,${durationMs},\\alpha&H00&\\alpha&HFF&)`; break case 'wave': tags += `\\t(0,${Math.round(durationMs / 4)},\\fscy110)\\t(${Math.round(durationMs / 4)},${Math.round(durationMs / 2)},\\fscy100)\\t(${Math.round(durationMs / 2)},${Math.round(durationMs * 3 / 4)},\\fscy105)\\t(${Math.round(durationMs * 3 / 4)},${durationMs},\\fscy100)`; break } return tags } function getWordTags(word) { let tags = '' if (gTransition === 'line-word') { tags = `\\c&H${hexToASSColor(word.color || gCol)}` if (word.font !== 'inherit' && word.font !== gFont) { tags += `\\fn${word.font}` } else { tags += `\\fn${gFont}` } } else { tags = getResetTags() if (word.color && word.color !== gCol) { tags += `\\c&H${hexToASSColor(word.color)}` } else { tags += `\\c&H${hexToASSColor(gCol)}` } if (word.font !== 'inherit' && word.font !== gFont) { tags += `\\fn${word.font}` } else { tags += `\\fn${gFont}` } } if (word.effect !== 'inherit' && word.effect !== gEff) { tags += getEffectTags(word.effect, word.color || gCol) } if (word.offX !== 0 || word.offY !== 0) { const posX = 960 + word.offX * 2 const posY = 540 + word.offY * 2 tags += `\\pos(${Math.round(posX)},${Math.round(posY)})` } return tags } function buildKaraokeText(line, currentWordIndex, lineStartTime, wordStartTime, wordEndTime) { let text = '' const sungStyle = `\\c&H${hexToASSColor(gCol)}80` const wordDuration = wordEndTime - wordStartTime line.words.forEach((w, i) => { const wordTags = getWordTags(w) let finalTags = wordTags if (i === currentWordIndex && gTransition !== 'none' && gTransition !== 'single' && gTransition !== 'line-word') { const transitionTags = getTransitionTags(gTransition, wordDuration, w.color || gCol) finalTags = transitionTags + wordTags } if (i < currentWordIndex) { text += `{${sungStyle}${wordTags}}${w.rawText}` } else if (i === currentWordIndex) { const duration = (wordEndTime - wordStartTime) * 100; const kTag = Math.round(duration); text += `{\\k${kTag}${finalTags}}${w.rawText}` } else { if (showTranslucent) { text += `{\\alpha&H80&${wordTags}}${w.rawText}` } else { text += `{${wordTags}}${w.rawText}` } } if (i < line.words.length - 1) text += ' ' }) return text } function buildSingleWordText(line, currentWordIndex, lineStartTime, wordStartTime, wordEndTime) { const word = line.words[currentWordIndex] let wordTags = getWordTags(word) const duration = (wordEndTime - wordStartTime) * 100 const kTag = Math.round(duration) wordTags = wordTags.replace(/\\pos\([^)]+\)/g, '') const fadeInTags = `\\alpha&H00&\\t(0,200,\\alpha&HFF&)` const scaleTags = `\\fscx80\\fscy80\\t(0,200,\\fscx100\\fscy100)` return `{\\k${kTag}${fadeInTags}${scaleTags}${wordTags}}${word.rawText}` } subData.forEach(line => { const lineStart = formatASSTime(timeToSec(line.start)), lineEnd = formatASSTime(timeToSec(line.end)), hasTiming = line.words.some(w => w.kEnd > 0), lineStartTime = timeToSec(line.start) if (!hasTiming) { if (gTransition === 'single' || gTransition === 'line-word') { let text = '' line.words.forEach((w, i) => { const wordTags = getWordTags(w) text += `{${wordTags}}${w.rawText}` if (i < line.words.length - 1) text += ' {}' }) if (gEff && gEff !== 'inherit') { const globalTags = getEffectTags(gEff, gCol); if (globalTags) text = `{${globalTags}}${text}` } assContent += `Dialogue: 0,${lineStart},${lineEnd},Default,,0,0,0,,${text}\n` } else { let text = '' line.words.forEach((w, i) => { const wordTags = getWordTags(w) let finalTags = wordTags if (gTransition !== 'none') { const transitionTags = getTransitionTags(gTransition, 0.3, gCol); finalTags = transitionTags + wordTags } text += finalTags ? `{${finalTags}}${w.rawText}` : w.rawText if (i < line.words.length - 1) text += ' {}' }) if (gEff && gEff !== 'inherit') { const globalTags = getEffectTags(gEff, gCol); if (globalTags) text = `{${globalTags}}${text}` } assContent += `Dialogue: 0,${lineStart},${lineEnd},Default,,0,0,0,,${text}\n` } } else { if (gTransition === 'single') { for (let i = 0; i < line.words.length; i++) { const word = line.words[i], effectiveKStart = word.kStart * line.speed, effectiveKEnd = word.kEnd * line.speed, wordStartTime = lineStartTime + effectiveKStart, wordEndTime = lineStartTime + effectiveKEnd + (word.linger || 0) if (effectiveKEnd <= effectiveKStart) continue if (i < line.words.length - 1) { const nextWord = line.words[i + 1] const nextWordStart = lineStartTime + (nextWord.kStart * line.speed) if (wordEndTime > nextWordStart) wordEndTime = nextWordStart } let text = buildSingleWordText(line, i, lineStartTime, wordStartTime, wordEndTime - (word.linger || 0)) if (gEff && gEff !== 'inherit') { const globalTags = getEffectTags(gEff, gCol); if (globalTags) text = `{${globalTags}}${text}` } assContent += `Dialogue: 0,${formatASSTime(wordStartTime)},${formatASSTime(wordEndTime)},Default,,0,0,0,,${text}\n` } } else if (gTransition === 'line-word') { const lineWords = line.words for (let i = 0; i < lineWords.length; i++) { const word = lineWords[i] const effectiveKStart = word.kStart * line.speed const effectiveKEnd = word.kEnd * line.speed const wordStartTime = lineStartTime + effectiveKStart const wordEndTime = lineStartTime + effectiveKEnd if (effectiveKEnd <= effectiveKStart) continue let text = '' for (let j = 0; j < lineWords.length; j++) { const currentWord = lineWords[j] const wordTags = getWordTags(currentWord) if (j <= i) { text += `{${wordTags}}${currentWord.rawText}` } else { text += `{\\alpha&HFF${wordTags}}${currentWord.rawText}` } if (j < lineWords.length - 1) text += ' {}' } if (gEff && gEff !== 'inherit') { const globalTags = getEffectTags(gEff, gCol); if (globalTags) text = `{${globalTags}}${text}` } assContent += `Dialogue: 0,${formatASSTime(wordStartTime)},${formatASSTime(wordEndTime)},Default,,0,0,0,,${text}\n` } const lastWordEndTime = lineStartTime + (lineWords[lineWords.length - 1].kEnd * line.speed) if (lastWordEndTime < timeToSec(line.end)) { let finalText = '' for (let j = 0; j < lineWords.length; j++) { const currentWord = lineWords[j] const wordTags = getWordTags(currentWord) finalText += `{${wordTags}}${currentWord.rawText}` if (j < lineWords.length - 1) finalText += ' {}' } if (gEff && gEff !== 'inherit') { const globalTags = getEffectTags(gEff, gCol); if (globalTags) finalText = `{${globalTags}}${finalText}` } assContent += `Dialogue: 0,${formatASSTime(lastWordEndTime)},${lineEnd},Default,,0,0,0,,${finalText}\n` } } else { for (let i = 0; i < line.words.length; i++) { const word = line.words[i], effectiveKStart = word.kStart * line.speed, effectiveKEnd = word.kEnd * line.speed, wordStartTime = lineStartTime + effectiveKStart, wordEndTime = lineStartTime + effectiveKEnd if (effectiveKEnd <= effectiveKStart) continue let text = buildKaraokeText(line, i, lineStartTime, wordStartTime, wordEndTime) if (gEff && gEff !== 'inherit') { const globalTags = getEffectTags(gEff, gCol); if (globalTags) text = `{${globalTags}}${text}` } assContent += `Dialogue: 0,${formatASSTime(wordStartTime)},${formatASSTime(wordEndTime)},Default,,0,0,0,,${text}\n` } const lastWord = line.words[line.words.length - 1], lastWordEndTime = lineStartTime + (lastWord.kEnd * line.speed) if (lastWordEndTime < timeToSec(line.end)) { let finalText = '' line.words.forEach((w, i) => { const wordTags = getWordTags(w) finalText += `{\\c&H${hexToASSColor(gCol)}80${wordTags}}${w.rawText}` if (i < line.words.length - 1) finalText += ' {}' }) if (gEff && gEff !== 'inherit') { const globalTags = getEffectTags(gEff, gCol); if (globalTags) finalText = `{${globalTags}}${finalText}` } assContent += `Dialogue: 0,${formatASSTime(lastWordEndTime)},${lineEnd},Default,,0,0,0,,${finalText}\n` } } } }) assContent += `\n;Generated by Karaoke Designer\n;Translucent:${showTranslucent ? 'Yes' : 'No'}\n;Style:${gFont} ${gSize}px,${gCol},${gEff || 'None'}\n;Transition:${gTransition}\n` const blob = new Blob([assContent], { type: 'text/plain' }), url = URL.createObjectURL(blob), a = document.createElement('a') a.href = url a.download = 'karaoke.ass' document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } function formatASSTime(seconds) { const hours = Math.floor(seconds / 3600), minutes = Math.floor((seconds % 3600) / 60), secs = Math.floor(seconds % 60), centiseconds = Math.round((seconds % 1) * 100), cs = Math.min(99, Math.max(0, centiseconds)) return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${cs.toString().padStart(2, '0')}` } function hexToASSColor(hex) { hex = hex.replace('#', '') if (hex.length === 3) { hex = hex.split('').map(c => c + c).join('') } if (hex.length === 6) { const r = hex.substring(0, 2); const g = hex.substring(2, 4); const b = hex.substring(4, 6); return `${b}${g}${r}` } return 'FFFFFF' } </script> </body> </html> Subtitle Editor (Timeshift) -------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SRT Master - URL Loader</title> <style> :root { --accent: #007bff; --bg: #0f0f0f; --card: #1e1e1e; --text: #e0e0e0; } body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); display: flex; flex-direction: column; height: 100vh; margin: 0; overflow: hidden; } #toolbar { padding: 10px 20px; background: #222; display: flex; align-items: center; gap: 20px; border-bottom: 1px solid #333; z-index: 100; } .file-btn { background: #333; padding: 6px 12px; border-radius: 4px; cursor: pointer; border: 1px solid #444; font-size: 12px; color: white; } .export-btn { background: var(--accent); color: white; font-weight: bold; margin-left: auto; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; } #workspace { display: flex; flex: 1; overflow: hidden; flex-direction: column; } #top-row { display: flex; flex: 3; overflow: hidden; min-height: 0; } #video-section { flex: 1; background: #000; position: relative; display: flex; justify-content: center; } video { height: 100%; max-width: 100%; } #sub-overlay { position: absolute; bottom: 12%; left: 50%; transform: translateX(-50%); text-align: center; width: 80%; pointer-events: none; text-shadow: 2px 2px 4px #000; font-size: 24px; font-weight: bold; color: white; background: rgba(0,0,0,0.6); padding: 8px 15px; border-radius: 4px; display: none; z-index: 10; } #sidebar { width: 400px; background: #181818; border-left: 1px solid #333; display: flex; flex-direction: column; } #sub-list { flex: 1; overflow-y: auto; padding: 12px; scroll-behavior: smooth; } .sub-card { background: var(--card); border-radius: 6px; padding: 12px; margin-bottom: 12px; border: 2px solid transparent; } .sub-card.active { border-color: var(--accent); background: #2a2a2a; } .time-row { display: flex; align-items: center; gap: 4px; margin-bottom: 8px; font-family: monospace; font-size: 11px; } .time-row input { background: #000; border: 1px solid #444; color: #0f0; width: 70px; padding: 3px; border-radius: 3px; } .snap-btn { background: #444; border: 1px solid #666; color: white; padding: 2px 6px; border-radius: 3px; cursor: pointer; font-size: 12px; } .snap-btn:hover { background: var(--accent); } textarea { width: 100%; background: #111; color: #fff; border: 1px solid #333; border-radius: 4px; padding: 6px; resize: none; min-height: 40px; box-sizing: border-box; font-size: 13px; } .card-btns { display: flex; gap: 5px; margin-top: 8px; } .btn-sm { flex: 1; padding: 5px; font-size: 10px; cursor: pointer; border: none; border-radius: 3px; background: #333; color: white; } .btn-del { background: #500; } #timeline-container { height: 100px; background: #111; border-top: 2px solid #333; position: relative; overflow-x: auto; overflow-y: hidden; } #timeline-canvas { display: block; } #playhead { position: absolute; top: 0; width: 2px; height: 100%; background: #ff0000; pointer-events: none; z-index: 20; } #nudge-layer { position: absolute; top: 0; left: 0; pointer-events: none; width: 100%; height: 100%; } .nudge-group { position: absolute; display: flex; gap: 2px; z-index: 30; pointer-events: auto; } .nudge-btn { width: 20px; height: 20px; background: #333; color: white; border: 1px solid #555; border-radius: 2px; font-size: 14px; display: flex; align-items: center; justify-content: center; cursor: pointer; user-select: none; } </style> </head> <body> <div id="toolbar"> <label class="file-btn">📁 Video <input type="file" id="videoInput" accept="video/*" hidden></label> <label class="file-btn">📄 SRT <input type="file" id="srtInput" accept=".srt" hidden></label> <div style="font-size:12px; color:#aaa">Zoom: <input type="range" id="zoomRange" min="50" max="300" value="100"></div> <button class="export-btn" onclick="exportSRT()">Export SRT</button> </div> <div id="workspace"> <div id="top-row"> <div id="video-section"> <video id="videoPlayer" controls></video> <div id="sub-overlay"></div> </div> <div id="sidebar"> <div id="sub-list"></div> <button onclick="addSubtitle()" style="margin:10px; padding:10px; background:#333; color:white; border:none; border-radius:4px; cursor:pointer;">+ Add Subtitle</button> </div> </div> <div id="timeline-container"> <div id="playhead"></div> <div id="nudge-layer"></div> <canvas id="timeline-canvas"></canvas> </div> </div> <script> let subtitles = []; const video = document.getElementById('videoPlayer'); const canvas = document.getElementById('timeline-canvas'); const ctx = canvas.getContext('2d'); const playhead = document.getElementById('playhead'); const subList = document.getElementById('sub-list'); const timelineContainer = document.getElementById('timeline-container'); const nudgeLayer = document.getElementById('nudge-layer'); const overlay = document.getElementById('sub-overlay'); let pixelsPerSecond = 100; const trackY = 30; // --- URL Parameter Handling --- window.onload = async () => { const params = new URLSearchParams(window.location.search); const videoUrl = params.get('video'); const srtUrl = params.get('subtitle'); if (videoUrl) { video.src = videoUrl; console.log("Loading video from URL..."); } if (srtUrl) { try { const response = await fetch(srtUrl); const text = await response.text(); subtitles = parseSRT(text); refreshUI(); console.log("Loading subtitles from URL..."); } catch (e) { console.error("Failed to fetch subtitle URL. Ensure CORS is enabled on the source.", e); } } }; // --- Standard Handlers --- document.getElementById('zoomRange').oninput = (e) => { pixelsPerSecond = parseInt(e.target.value); refreshUI(); }; document.getElementById('videoInput').onchange = (e) => { if(e.target.files[0]) video.src = URL.createObjectURL(e.target.files[0]); }; document.getElementById('srtInput').onchange = (e) => { const reader = new FileReader(); reader.onload = (ev) => { subtitles = parseSRT(ev.target.result); refreshUI(); }; reader.readAsText(e.target.files[0]); }; video.ontimeupdate = () => { const time = video.currentTime; const x = time * pixelsPerSecond; playhead.style.left = x + "px"; const cWidth = timelineContainer.clientWidth; if (x > timelineContainer.scrollLeft + cWidth * 0.8 || x < timelineContainer.scrollLeft) { timelineContainer.scrollLeft = x - cWidth * 0.2; } const active = subtitles.find(s => time >= s.start && time <= s.end); if (active) { overlay.innerText = active.text; overlay.style.display = 'block'; document.querySelectorAll('.sub-card').forEach(c => c.classList.remove('active')); const card = document.querySelector(`[data-id="${active.id}"]`); if (card) { card.classList.add('active'); card.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } } else { overlay.style.display = 'none'; } }; // --- Core UI Logic --- function refreshUI() { canvas.width = (video.duration || 600) * pixelsPerSecond; canvas.height = 100; drawTimeline(); renderNudgeButtons(); renderSidebar(); } function drawTimeline() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.strokeStyle = '#222'; for(let i=0; i < canvas.width; i += pixelsPerSecond) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, canvas.height); ctx.stroke(); } subtitles.forEach((sub) => { const x = sub.start * pixelsPerSecond; const w = (sub.end - sub.start) * pixelsPerSecond; ctx.fillStyle = '#444'; ctx.fillRect(x, trackY, w, 25); ctx.fillStyle = '#fff'; ctx.font = '10px sans-serif'; ctx.fillText(sub.text.substring(0, 15), x + 5, trackY + 16); }); } function renderNudgeButtons() { nudgeLayer.innerHTML = ''; subtitles.forEach((sub) => { nudgeLayer.appendChild(createNudge(sub.id, 'start', sub.start * pixelsPerSecond - 45, trackY)); nudgeLayer.appendChild(createNudge(sub.id, 'end', sub.end * pixelsPerSecond + 5, trackY)); }); } function createNudge(id, type, x, y) { const div = document.createElement('div'); div.className = 'nudge-group'; div.style.left = x + 'px'; div.style.top = y + 'px'; div.innerHTML = `<div class="nudge-btn">-</div><div class="nudge-btn">+</div>`; div.children[0].onclick = () => updateTime(id, type, -0.1); div.children[1].onclick = () => updateTime(id, type, 0.1); return div; } function updateTime(id, type, delta) { const sub = subtitles.find(s => s.id === id); if (sub) { sub[type] = Math.max(0, sub[type] + delta); if (sub.start >= sub.end) sub.end = sub.start + 0.1; refreshUI(); } } function renderSidebar() { subList.innerHTML = ''; subtitles.forEach(sub => { const card = document.createElement('div'); card.className = 'sub-card'; card.dataset.id = sub.id; card.innerHTML = ` <div class="time-row"> <button class="snap-btn" onclick="snapTime('${sub.id}','start')">⇤</button> <input type="number" step="0.1" value="${sub.start.toFixed(2)}" onchange="updateSubVal('${sub.id}','start',this.value)"> <span>→</span> <input type="number" step="0.1" value="${sub.end.toFixed(2)}" onchange="updateSubVal('${sub.id}','end',this.value)"> <button class="snap-btn" onclick="snapTime('${sub.id}','end')">⇥</button> </div> <textarea oninput="subtitles.find(s=>s.id==='${sub.id}').text=this.value; drawTimeline();">${sub.text}</textarea> <div class="card-btns"> <button class="btn-sm" onclick="video.currentTime=${sub.start}; video.play()">▶ Jump</button> <button class="btn-sm btn-del" onclick="deleteSub('${sub.id}')">Delete</button> </div> `; subList.appendChild(card); }); } function snapTime(id, field) { const sub = subtitles.find(s => s.id === id); if (sub) { sub[field] = video.currentTime; if (sub.start >= sub.end) sub.end = sub.start + 1; refreshUI(); } } function updateSubVal(id, field, val) { const sub = subtitles.find(s => s.id === id); if (sub) { sub[field] = parseFloat(val); refreshUI(); } } function deleteSub(id) { subtitles = subtitles.filter(s => s.id !== id); refreshUI(); } function addSubtitle() { subtitles.push({ id: 's'+Math.random(), start: video.currentTime, end: video.currentTime + 2, text: "" }); subtitles.sort((a,b) => a.start - b.start); refreshUI(); } function parseSRT(data) { return data.split(/\n\s*\n/).filter(b => b.trim()).map(block => { const lines = block.trim().split('\n'); const tm = (lines[1] || "").match(/(\d+:\d+:\d+,\d+) --> (\d+:\d+:\d+,\d+)/); return { id: 's'+Math.random(), start: tm ? sToSec(tm[1]) : 0, end: tm ? sToSec(tm[2]) : 2, text: lines.slice(2).join('\n') }; }).sort((a,b) => a.start - b.start); } function sToSec(t) { const p = t.split(':'); return parseFloat(p[0])*3600 + parseFloat(p[1])*60 + parseFloat(p[2].replace(',','.')); } function formatSRTTime(s) { const ms = Math.floor((s % 1) * 1000); return new Date(Math.max(0, s) * 1000).toISOString().substr(11, 8) + ',' + ms.toString().padStart(3, '0'); } function exportSRT() { let out = ''; subtitles.sort((a,b) => a.start - b.start).forEach((s, i) => { out += `${i+1}\n${formatSRTTime(s.start)} --> ${formatSRTTime(s.end)}\n${s.text}\n\n`; }); const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([out], {type: 'text/plain'})); a.download = 'edited.srt'; a.click(); } </script> </body> </html> Fx FileExplorer Route ----------------- Based on RE of FX's Browse Intents: https://codeberg.org/alceawisteria/AppAndProgramHacking/src/branch/main/Android/FxFileExplorer <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Route Explorer Pro</title> <style> body { font-family: sans-serif; background: #1a1a1a; color: #e0e0e0; padding: 15px; line-height: 1.4; } .container { max-width: 900px; margin: auto; background: #2a2a2a; padding: 20px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.5); } label { display: block; margin-bottom: 5px; font-weight: bold; color: #4dabf7; font-size: 0.9em; } select, input[type="text"] { width: 100%; padding: 12px; margin-bottom: 15px; border-radius: 4px; border: 1px solid #444; background: #333; color: white; box-sizing: border-box; } .checkbox-group { display: flex; gap: 15px; margin-bottom: 15px; } .checkbox-container { display: flex; align-items: center; background: #383838; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 0.85em; flex: 1; } .checkbox-container input { margin-right: 8px; } .breadcrumb { background: #111; padding: 10px; border-radius: 4px; margin-bottom: 1px; font-family: monospace; font-size: 0.85em; color: #888; border-bottom: 1px solid #333; } .breadcrumb span { color: #4dabf7; cursor: pointer; font-weight: bold; } #pathList { max-height: 450px; overflow-y: auto; border: 1px solid #444; background: #222; border-radius: 0 0 4px 4px; } .path-row { display: flex; align-items: center; border-bottom: 1px solid #333; } .path-row:hover { background: #2d2d2d; } .path-name { flex-grow: 1; padding: 12px; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9em; } .btn-target { padding: 12px 18px; cursor: pointer; background: #333; color: #fab005; border: none; border-left: 1px solid #444; font-size: 1.1em; } .btn-target:hover { background: #fab005; color: #000; } .is-file { color: #bbb; } .is-folder { color: #4dabf7; font-weight: 500; } .output-box { background: #000; border-left: 4px solid #fab005; padding: 15px; margin-top: 15px; word-break: break-all; font-family: monospace; font-size: 0.85em; color: #0f0; } .hidden { display: none; } .button-group { display: flex; gap: 10px; margin-top: 10px; } .btn-ui { padding: 14px; font-weight: bold; border-radius: 4px; text-decoration: none; text-align: center; flex: 1; color: black; font-family: sans-serif; border: none; font-size: 1em; cursor: pointer;} .btn-copy { background: #a5d8ff; } .btn-send { background: #fab005; } #intentLinkContainer { margin-top: 15px; text-align: center; padding: 10px; background: #111; border: 1px dashed #444; border-radius: 4px; } #intentLink { color: #fab005; font-family: monospace; font-weight: bold; text-decoration: underline; font-size: 1.1em; } .searching-hint { font-size: 0.7em; color: #fab005; float: right; margin-top: -10px; margin-bottom: 5px; } </style> </head> <body> <div class="container"> <label>Route Configuration:</label> <select id="routePicker"><option value="">-- Select Device/Route --</option></select> <div id="explorerSection" class="hidden"> <input type="text" id="searchBar" placeholder="Search... (waits for you to stop typing)"> <div id="searchHint" class="searching-hint hidden">Typing...</div> <div class="checkbox-group"> <label class="checkbox-container"><input type="checkbox" id="stripFileToggle" checked> Strip Filename</label> <label class="checkbox-container"><input type="checkbox" id="urlEncodeToggle"> Tasker Encode</label> </div> <div class="breadcrumb" id="breadcrumb">Root</div> <div id="pathList"></div> </div> <div id="outputSection" class="hidden"> <div id="commandOutput" class="output-box"></div> <div class="button-group"> <button class="btn-ui btn-copy" onclick="copyToClipboard()">COPY ADB</button> <button id="sendBtn" class="btn-ui btn-send" onclick="handleSendToTasker()">SEND TO TASKER</button> </div> <div id="intentLinkContainer" class="hidden"> <a id="intentLink" href="tasker://IntentReceive">IntentSend</a> </div> </div> </div> <script> const ROUTES_FILE = 'routes.txt'; let routeData = {}; let masterPathList = []; let currentDir = ""; let searchTimer; async function init() { try { const res = await fetch(ROUTES_FILE); const text = await res.text(); const lines = text.split('\n').map(l => l.trim()).filter(l => l !== ""); const picker = document.getElementById('routePicker'); for (let i = 0; i < lines.length; i += 2) { routeData[lines[i]] = lines[i+1]; picker.add(new Option(lines[i], lines[i])); } } catch (e) { console.error("Initialization failed"); } } document.getElementById('routePicker').onchange = async (e) => { if (!e.target.value) return; const res = await fetch(`${e.target.value}.txt`); const text = await res.text(); masterPathList = text.split('\n').map(l => l.trim()).filter(l => l !== ""); currentDir = ""; document.getElementById('explorerSection').classList.remove('hidden'); renderExplorer(); }; document.getElementById('searchBar').addEventListener('input', () => { const hint = document.getElementById('searchHint'); hint.classList.remove('hidden'); clearTimeout(searchTimer); searchTimer = setTimeout(() => { hint.classList.add('hidden'); renderExplorer(); }, 400); }); function renderExplorer() { const listDiv = document.getElementById('pathList'); const searchTerm = document.getElementById('searchBar').value.toLowerCase(); listDiv.innerHTML = ''; const bc = document.getElementById('breadcrumb'); bc.innerHTML = `<span onclick="navigate('')">Root</span> ${currentDir ? ' / ' + currentDir.split('/').map((seg, i, arr) => `<span onclick="navigate('${arr.slice(0,i+1).join('/')}')">${seg}</span>`).join(' / ') : ''}`; let itemsToDisplay = []; if (searchTerm) { itemsToDisplay = masterPathList .filter(p => p.toLowerCase().includes(searchTerm)) .slice(0, 200) .map(p => ({ path: p, isSearch: true })); } else { const children = new Set(); masterPathList.forEach(p => { if (currentDir === "") { children.add(p.split('/')[0]); } else if (p.startsWith(currentDir + '/')) { const relative = p.substring(currentDir.length + 1); children.add(relative.split('/')[0]); } }); itemsToDisplay = Array.from(children).sort().map(name => ({ path: currentDir ? `${currentDir}/${name}` : name, isSearch: false, displayName: name })); } const fragment = document.createDocumentFragment(); itemsToDisplay.forEach(item => { const isFolder = masterPathList.some(p => p.startsWith(item.path + '/')); const display = item.isSearch ? item.path : item.displayName; const row = document.createElement('div'); row.className = 'path-row'; const nameSpan = document.createElement('span'); nameSpan.className = `path-name ${isFolder ? 'is-folder' : 'is-file'}`; nameSpan.textContent = (isFolder ? '📁 ' : '📄 ') + display; nameSpan.onclick = () => { if (item.isSearch) { document.getElementById('searchBar').value = ''; navigate(isFolder ? item.path : item.path.substring(0, item.path.lastIndexOf('/'))); } else { if (isFolder) navigate(item.path); else selectPath(item.path); } }; const targetBtn = document.createElement('button'); targetBtn.className = 'btn-target'; targetBtn.innerHTML = '🎯'; targetBtn.onclick = (e) => { e.stopPropagation(); selectPath(item.path); }; row.appendChild(nameSpan); row.appendChild(targetBtn); fragment.appendChild(row); }); listDiv.appendChild(fragment); } function navigate(path) { currentDir = path; renderExplorer(); } document.getElementById('stripFileToggle').onchange = () => updateOutput(window.lastPath); document.getElementById('urlEncodeToggle').onchange = () => updateOutput(window.lastPath); function selectPath(path) { window.lastPath = path; updateOutput(path); document.getElementById('outputSection').classList.remove('hidden'); } function updateOutput(path) { if (!path) return; let p = path; if (document.getElementById('stripFileToggle').checked) { if (p.includes('/') && !masterPathList.some(full => full.startsWith(p + '/'))) { p = p.substring(0, p.lastIndexOf('/')); } } const routeName = document.getElementById('routePicker').value; const cmd = routeData[routeName].replace('$path', p); document.getElementById('commandOutput').textContent = cmd; // Hide the intent link when path changes so it only appears on press document.getElementById('intentLinkContainer').classList.add('hidden'); } async function handleSendToTasker() { const cmd = document.getElementById('commandOutput').textContent; if (!cmd) return; // 1. Copy text to clipboard try { await navigator.clipboard.writeText(cmd); const btn = document.getElementById('sendBtn'); const originalText = btn.textContent; btn.textContent = "COPIED & SENT!"; setTimeout(() => btn.textContent = originalText, 1500); } catch (err) { console.error('Clip error', err); } // 2. Send text to PHP script const phpUrl = `https://alceawis.de/other/extra/scripts/fakesocialmedia/clip.php?clip=${encodeURIComponent(cmd)}`; fetch(phpUrl, { mode: 'no-cors' }).catch(e => console.log("PHP Notify Handled")); // 3. Make IntentSend URL appear document.getElementById('intentLinkContainer').classList.remove('hidden'); } function copyToClipboard() { navigator.clipboard.writeText(document.getElementById('commandOutput').textContent); const btn = document.querySelector('.btn-copy'); btn.textContent = "COPIED!"; setTimeout(() => btn.textContent = "COPY ADB", 1500); } init(); </script> <details><summary>PrerequisiteForSelfHost</summary><pre> clip.php ________ &lt;?php if (isset($_POST['send'])) { $content = $_POST['send']; } elseif (isset($_GET['clip'])) { $content = $_GET['clip']; } else { exit("No content provided."); } file_put_contents('clip.txt', $content); echo "Content saved to 'clip.txt'."; exit; ?&gt; &lt;?php if (isset($_POST['send'])) { $content = $_POST['send']; file_put_contents('clip.txt', $content); } exit; ?&gt; IntentReceive.xml.tsk ______________________ &lt;TaskerData sr="" dvi="1" tv="4.9u4m"&gt; &lt;Task sr="task14"&gt; &lt;cdate&gt;1767128445686&lt;/cdate&gt; &lt;edate&gt;1767129026350&lt;/edate&gt; &lt;id&gt;14&lt;/id&gt; &lt;nme&gt;IntentReceive&lt;/nme&gt; &lt;pri&gt;100&lt;/pri&gt; &lt;Action sr="act0" ve="7"&gt; &lt;code&gt;123&lt;/code&gt; &lt;on&gt;false&lt;/on&gt; &lt;Str sr="arg0" ve="3"&gt;sh /sdcard/exec/intentreceive.sh&lt;/Str&gt; &lt;Int sr="arg1" val="0"/&gt; &lt;Int sr="arg2" val="0"/&gt; &lt;Str sr="arg3" ve="3"/&gt; &lt;Str sr="arg4" ve="3"/&gt; &lt;Str sr="arg5" ve="3"/&gt; &lt;/Action&gt; &lt;Action sr="act1" ve="7"&gt; &lt;code&gt;123&lt;/code&gt; &lt;Str sr="arg0" ve="3"&gt;#!/bin/bash # URL of the script SCRIPT_URL="https&#58;//alceawis.de/other/extra/scripts/fakesocialmedia/clip.txt" # Fetch the script into a variable SCRIPT=$(curl -fsSL "$SCRIPT_URL") # Check if the script was successfully downloaded if [ -n "$SCRIPT" ]; then echo "Script content (flashed)&#58;" echo "$SCRIPT" # "Flash" the content directly to the terminal # Execute the command using su -c (with root privileges) su -c "$SCRIPT" else echo "Failed to download script or script is empty." fi&lt;/Str&gt; &lt;Int sr="arg1" val="0"/&gt; &lt;Int sr="arg2" val="0"/&gt; &lt;Str sr="arg3" ve="3"/&gt; &lt;Str sr="arg4" ve="3"/&gt; &lt;Str sr="arg5" ve="3"/&gt; &lt;/Action&gt; &lt;/Task&gt; &lt;/TaskerData&gt; apk: https://m.apkpure.com/tasker-url-launcher/com.aledthomas.taskerurllauncher https://alceawis.de/other/extra/scripts/fakesocialmedia/commentload.html?number=80000&text=Oh%20nice%20%0D%0Ahttps%3A%2F%2Fm.apkpure.com%2Ftasker-url-launcher%2Fcom.aled routes.txt __________ sdcard am start -a nextapp.fx.intent.action.OPEN_LOCAL -n nextapp.fx/.ui.ExplorerActivity --es "nextapp.fx.intent.extra.PATH" "$path" -f 0x10000000 microsd am start -a nextapp.fx.intent.action.OPEN_LOCAL -n nextapp.fx/.ui.ExplorerActivity --es "nextapp.fx.intent.extra.PATH" "/storage/4041-393D/$path" -f 0x10000000 alceawis.de am start -a nextapp.fx.intent.action.OPEN_NETWORK -n nextapp.fx/.ui.ExplorerActivity --es "nextapp.fx.intent.extra.HOST" "27" --es "nextapp.fx.intent.extra.PATH" "/path" -f 0x10000000 m7350 am start -a nextapp.fx.intent.action.OPEN_NETWORK -n nextapp.fx/.ui.ExplorerActivity --es "nextapp.fx.intent.extra.HOST" "25" --es "nextapp.fx.intent.extra.PATH" "/$path" -f 0x10000000 </pre> </details> </body> </html> ZipFileShow (Preview a File inzip inbrowser) -------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>ZIP File Explorer</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> <style> body { font-family: Arial, sans-serif; margin: 20px; } #header { margin-bottom: 10px; } input { width: 80%; padding: 6px; } button { padding: 6px 10px; margin-left: 5px; } #explorer { border: 1px solid #ccc; margin-top: 15px; padding: 10px; max-height: 300px; overflow-y: auto; font-family: monospace; } .item { cursor: pointer; padding: 4px; } .item:hover { background: #eee; } .folder::before { content: "📁 "; } .file::before { content: "📄 "; } .back::before { content: "⬅️ "; } #pathBar { font-weight: bold; margin-bottom: 8px; } #fileDisplay { margin-top: 20px; } </style> </head> <body> <div id="header"> <h2>ZIP File Viewer</h2> <label>ZIP URL:</label><br> <input id="zipUrl" placeholder="https://example.com/files.zip"> <button onclick="loadZip()">Browse ZIP</button> <br><br> <label>Path in ZIP:</label><br> <input id="filePath" placeholder="path/to/file.ext"> <button onclick="openInNewTab()">Open File</button> </div> <div id="explorer" style="display:none;"> <div id="pathBar"></div> <div id="list"></div> </div> <div id="fileDisplay"></div> <script> const header = document.getElementById("header"); const zipUrlInput = document.getElementById("zipUrl"); const filePathInput = document.getElementById("filePath"); const explorer = document.getElementById("explorer"); const list = document.getElementById("list"); const pathBar = document.getElementById("pathBar"); const fileDisplay = document.getElementById("fileDisplay"); let zipData = null; let currentPath = ""; function getQueryParams() { const p = new URLSearchParams(location.search); return { zipUrl: p.get("zipUrl"), filePath: p.get("filePath") }; } function loadZip() { if (!zipUrlInput.value) return alert("Enter ZIP URL"); fetch(zipUrlInput.value) .then(r => r.arrayBuffer()) .then(b => JSZip.loadAsync(b)) .then(zip => { zipData = zip; currentPath = ""; explorer.style.display = "block"; renderFolder(); }) .catch(() => alert("Failed to load ZIP")); } function renderFolder() { list.innerHTML = ""; pathBar.textContent = "/" + currentPath; const folders = new Set(); const files = []; zipData.forEach((path) => { if (!path.startsWith(currentPath)) return; const rest = path.slice(currentPath.length); if (!rest) return; const parts = rest.split("/"); if (parts.length > 1) { folders.add(parts[0]); } else { files.push(parts[0]); } }); /* BACK */ if (currentPath) { const back = document.createElement("div"); back.className = "item back"; back.textContent = ".."; back.onclick = () => { currentPath = currentPath.split("/").slice(0, -2).join("/"); if (currentPath) currentPath += "/"; renderFolder(); }; list.appendChild(back); } /* FOLDERS */ [...folders].sort().forEach(f => { const div = document.createElement("div"); div.className = "item folder"; div.textContent = f; div.onclick = () => { currentPath += f + "/"; renderFolder(); }; list.appendChild(div); }); /* FILES */ files.sort().forEach(f => { const div = document.createElement("div"); div.className = "item file"; div.textContent = f; div.onclick = () => { filePathInput.value = currentPath + f; }; list.appendChild(div); }); } function openInNewTab() { if (!zipUrlInput.value || !filePathInput.value) { alert("ZIP URL and file path required"); return; } const url = location.pathname + "?zipUrl=" + encodeURIComponent(zipUrlInput.value) + "&filePath=" + encodeURIComponent(filePathInput.value); window.open(url, "_blank"); } function fetchAndDisplayFile(zipUrl, filePath) { fetch(zipUrl) .then(r => r.arrayBuffer()) .then(b => JSZip.loadAsync(b)) .then(zip => zip.file(filePath).async("blob")) .then(blob => displayFile(blob, filePath)) .catch(() => fileDisplay.textContent = "Unable to load file"); } function displayFile(blob, filePath) { fileDisplay.innerHTML = ""; const ext = filePath.split(".").pop().toLowerCase(); const url = URL.createObjectURL(blob); if (["png","jpg","jpeg","gif","webp","svg"].includes(ext)) { const img = document.createElement("img"); img.src = url; img.style.maxWidth = "100%"; img.style.maxHeight = "80vh"; img.style.display = "block"; img.style.margin = "0 auto"; fileDisplay.appendChild(img); return; } if (["mp3","wav","ogg"].includes(ext)) { const a = document.createElement("audio"); a.controls = true; a.src = url; fileDisplay.appendChild(a); return; } if (["mp4","webm","ogg"].includes(ext)) { const v = document.createElement("video"); v.controls = true; v.src = url; v.style.maxWidth = "100%"; fileDisplay.appendChild(v); return; } if (ext === "html" || ext === "htm") { const i = document.createElement("iframe"); i.src = url; i.style.width = "100%"; i.style.height = "500px"; fileDisplay.appendChild(i); return; } const r = new FileReader(); r.onload = () => { const pre = document.createElement("pre"); pre.textContent = r.result; fileDisplay.appendChild(pre); }; r.readAsText(blob); } window.onload = () => { const q = getQueryParams(); if (q.zipUrl && q.filePath) { // Hide header and input UI header.style.display = "none"; zipUrlInput.value = q.zipUrl; filePathInput.value = q.filePath; fetchAndDisplayFile(q.zipUrl, q.filePath); } }; </script> </body> </html> Fediverse Instance Emoji Compare ----------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Emoji Instance Comparator</title> <style> body { font-family: sans-serif; padding: 1rem; } input { width: 340px; margin-bottom: 0.5rem; } button { margin: 0.5rem 0; } .emoji { display: inline-block; text-align: center; margin: 4px; width: 64px; cursor: pointer; user-select: none; } .emoji:hover { background: #eee; } img { width: 32px; height: 32px; display: block; margin: auto; } .copied { color: green; font-size: 0.8em; } </style> </head> <body> <h3>Compare Instance Emojis</h3> <input id="url1" placeholder="https://instance-one.tld" /><br /> <input id="url2" placeholder="https://instance-two.tld" /><br /> <button onclick="compare()">Compare</button> <h4>Shared Emojis (click to copy)</h4> <div id="result"></div> <script> const CORS_PROXY = "/cors.php?query="; function getQueryParams() { const p = new URLSearchParams(window.location.search); return { url1: p.get("url1"), url2: p.get("url2") }; } async function fetchWithFallback(url) { try { const res = await fetch(url); if (!res.ok) throw new Error(); return await res.json(); } catch { const res = await fetch(CORS_PROXY + encodeURIComponent(url)); if (!res.ok) throw new Error(); return await res.json(); } } async function getEmojis(instanceUrl) { const host = new URL(instanceUrl).host; const key = `emojis:${host}`; const cached = localStorage.getItem(key); if (cached) return JSON.parse(cached); const emojis = await fetchWithFallback( `${instanceUrl}/api/v1/custom_emojis` ); localStorage.setItem(key, JSON.stringify(emojis)); return emojis; } function copyToClipboard(text, el) { navigator.clipboard.writeText(text).then(() => { const note = document.createElement("div"); note.className = "copied"; note.textContent = "copied!"; el.appendChild(note); setTimeout(() => note.remove(), 800); }); } function imgFallback(img) { if (img.dataset.fallbackUsed) return; img.dataset.fallbackUsed = "1"; img.src = img.dataset.altSrc; } async function compare() { const u1 = document.getElementById("url1").value.trim(); const u2 = document.getElementById("url2").value.trim(); const out = document.getElementById("result"); out.innerHTML = "Loading…"; if (!u1 || !u2) { out.innerHTML = "Both URLs are required."; return; } try { const [e1, e2] = await Promise.all([ getEmojis(u1), getEmojis(u2) ]); const map2 = new Map(e2.map(e => [e.shortcode, e])); const shared = e1 .filter(e => map2.has(e.shortcode)) .map(e => ({ shortcode: e.shortcode, url1: e.url, url2: map2.get(e.shortcode).url })); out.innerHTML = shared.length ? shared.map(e => ` <div class="emoji" onclick="copyToClipboard(':${e.shortcode}:', this)"> <img src="${e.url1}" data-alt-src="${e.url2}" onerror="imgFallback(this)" /> :${e.shortcode}: </div> `).join("") : "<em>No shared emojis</em>"; } catch (err) { out.innerHTML = "Error loading emojis."; } } window.addEventListener("DOMContentLoaded", () => { const { url1, url2 } = getQueryParams(); if (url1) document.getElementById("url1").value = url1; if (url2) document.getElementById("url2").value = url2; if (url1 && url2) compare(); }); </script> </body> </html> File Diff Check/Compare ------------------------ <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Aligned Side-by-Side Diff (Preserve Alignment)</title> <style> body { font-family: monospace; margin: 10px; background: #f5f5f5; height: 100vh; display: flex; flex-direction: column; overflow: hidden; /* Prevent body scroll, use wrapper instead */ } h3 { margin-bottom: 15px; color: #333; flex-shrink: 0; } .input-area { background: white; padding: 15px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 15px; flex-shrink: 0; } textarea { width: 100%; height: 60px; margin-bottom: 10px; padding: 10px; border: 1px solid #ccc; border-radius: 3px; font-family: monospace; box-sizing: border-box; resize: vertical; } textarea:focus { outline: none; border-color: #4a90e2; box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2); } button { background: #4a90e2; color: white; border: none; padding: 10px 20px; border-radius: 3px; cursor: pointer; font-size: 14px; transition: background 0.2s; } button:hover { background: #3a7bc8; } button:active { transform: translateY(1px); } button:disabled { background: #cccccc; cursor: not-allowed; } /* Container for the diff */ .scroll-wrapper { flex: 1; overflow: auto; /* Handles both vertical and horizontal scroll */ border: 1px solid #ccc; border-radius: 3px; background: white; } /* Perfect alignment via Fixed Table Layout */ .diff-table { width: 100%; border-collapse: collapse; table-layout: fixed; /* Forces columns to stay exactly 50% */ min-width: 800px; /* Prevents table from getting too squished */ } .diff-table td { vertical-align: top; padding: 0; border-right: 1px solid #ddd; width: 50%; overflow: hidden; /* Critical: clips content that would otherwise bleed */ } .diff-table td:last-child { border-right: none; } .line-container { display: table-row; } .line-cell { height: 1.4em; min-height: 1.4em; border-bottom: 1px solid #f0f0f0; position: relative; } .line-number { display: inline-block; width: 45px; color: #999; background: #fafafa; text-align: right; padding-right: 8px; user-select: none; border-right: 1px solid #eee; font-size: 11px; line-height: 1.8em; /* Centers number vertically with text */ } .line-content { display: inline-block; white-space: pre; /* Preserves spaces/tabs without wrapping */ padding: 0 10px; font-size: 13px; line-height: 1.4em; vertical-align: top; /* This prevents the text from pushing outside the 50% width */ max-width: calc(100% - 65px); overflow: hidden; text-overflow: ellipsis; /* Adds '...' to very long lines */ } /* Colors */ .same { background: #ffffff; } .removed { background: #ffeef0; color: #b31d28; } .added { background: #e6ffed; color: #22863a; } /* Utilities */ .loading { display: none; color: #666; padding: 20px; text-align: center; } .error { color: #c62828; padding: 10px; background: #ffebee; border-radius: 3px; margin-top: 10px; display: none; } .status { margin-top: 10px; color: #666; font-size: 14px; } </style> </head> <body> <h3>Aligned Side-by-Side Diff (Unchanged Lines Stay Aligned)</h3> <div class="input-area"> <textarea id="url1" placeholder="Enter URL 1 (e.g., https://example.com/file1.txt)"></textarea> <textarea id="url2" placeholder="Enter URL 2 (e.g., https://example.com/file2.txt)"></textarea> <button onclick="run()" id="compareBtn">Compare</button> <button onclick="clearAll()" style="background: #666; margin-left: 10px;">Clear</button> <div class="loading" id="loading">Loading and comparing files...</div> <div class="error" id="error"></div> <div class="status" id="status"></div> </div> <!-- Single scrolling container --> <div class="scroll-wrapper" id="scrollWrapper"> <table class="diff-table" id="diffTable"> <tbody id="diffBody"></tbody> </table> </div> <script> // Read query parameters function getQueryParams() { const params = {}; const query = window.location.search.substring(1); if (!query) return params; query.split("&").forEach(p => { const [k, v] = p.split("="); if(k) params[decodeURIComponent(k)] = decodeURIComponent(v || ""); }); return params; } // Escape HTML and preserve spaces/tabs function escape(s = "") { return s.replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;") .replace(/ /g, "&nbsp;") .replace(/\t/g, "&nbsp;&nbsp;&nbsp;&nbsp;"); } // Create a table row for a line function createLineRow(leftText, rightText, leftClass, rightClass, leftNum = "", rightNum = "") { const leftNumHtml = leftNum ? `<span class="line-number">${leftNum}</span>` : '<span class="line-number"></span>'; const rightNumHtml = rightNum ? `<span class="line-number">${rightNum}</span>` : '<span class="line-number"></span>'; return ` <tr class="line-container"> <td class="line-cell ${leftClass}"> ${leftNumHtml}<span class="line-content">${escape(leftText)}</span> </td> <td class="line-cell ${rightClass}"> ${rightNumHtml}<span class="line-content">${escape(rightText)}</span> </td> </tr> `; } // LCS diff that returns aligned pairs function alignedDiff(a, b) { const m = a.length, n = b.length; const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); // Build LCS matrix for(let i = 1; i <= m; i++) { for(let j = 1; j <= n; j++) { dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] + 1 : Math.max(dp[i - 1][j], dp[i][j - 1]); } } const out = []; let i = m, j = n; // Backtrack to find alignment while(i > 0 || j > 0) { if(i > 0 && j > 0 && a[i - 1] === b[j - 1]) { out.unshift({ left: a[i - 1], right: b[j - 1], type: "same" }); i--; j--; } else if(j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) { out.unshift({ left: "", right: b[j - 1], type: "added" }); j--; } else { out.unshift({ left: a[i - 1], right: "", type: "removed" }); i--; } } return out; } // Run diff async function run() { const u1 = document.getElementById("url1").value.trim(); const u2 = document.getElementById("url2").value.trim(); if(!u1 || !u2) { showError("Please enter both URLs"); return; } // Validate URLs try { new URL(u1); new URL(u2); } catch { showError("Please enter valid URLs"); return; } const compareBtn = document.getElementById("compareBtn"); const loading = document.getElementById("loading"); const error = document.getElementById("error"); const status = document.getElementById("status"); const diffBody = document.getElementById("diffBody"); // Reset UI error.style.display = "none"; loading.style.display = "block"; compareBtn.disabled = true; compareBtn.textContent = "Comparing..."; diffBody.innerHTML = ""; try { // Fetch both files in parallel const [response1, response2] = await Promise.all([ fetch(u1).then(r => { if (!r.ok) throw new Error(`Failed to fetch ${u1}: ${r.status} ${r.statusText}`); return r.text(); }), fetch(u2).then(r => { if (!r.ok) throw new Error(`Failed to fetch ${u2}: ${r.status} ${r.statusText}`); return r.text(); }) ]); const a = response1.split("\n"); const b = response2.split("\n"); const diff = alignedDiff(a, b); let leftLineNum = 1, rightLineNum = 1; let html = ""; diff.forEach(d => { const leftNum = d.left !== "" ? leftLineNum++ : ""; const rightNum = d.right !== "" ? rightLineNum++ : ""; // Determine CSS classes const leftClass = d.type === "added" ? "" : d.type; const rightClass = d.type === "removed" ? "" : d.type; html += createLineRow(d.left, d.right, leftClass, rightClass, leftNum, rightNum); }); diffBody.innerHTML = html; // Update status const changes = diff.filter(d => d.type !== "same").length; status.textContent = `Total lines: ${diff.length} | Changes: ${changes} | Identical lines: ${diff.length - changes}`; // Update URL with current comparison const url = new URL(window.location); url.searchParams.set('url1', u1); url.searchParams.set('url2', u2); window.history.replaceState({}, '', url.toString()); } catch (err) { showError(err.message); console.error("Comparison error:", err); } finally { loading.style.display = "none"; compareBtn.disabled = false; compareBtn.textContent = "Compare"; } } function showError(message) { const errorDiv = document.getElementById("error"); errorDiv.textContent = message; errorDiv.style.display = "block"; // Auto-hide error after 5 seconds setTimeout(() => { errorDiv.style.display = "none"; }, 5000); } function clearAll() { document.getElementById("url1").value = ""; document.getElementById("url2").value = ""; document.getElementById("diffBody").innerHTML = ""; document.getElementById("error").style.display = "none"; document.getElementById("status").textContent = ""; // Clear URL parameters const url = new URL(window.location); url.searchParams.delete('url1'); url.searchParams.delete('url2'); window.history.replaceState({}, '', url.toString()); } // Auto-run from query string window.addEventListener("DOMContentLoaded", () => { const params = getQueryParams(); const url1Input = document.getElementById("url1"); const url2Input = document.getElementById("url2"); if(params.url1) url1Input.value = params.url1; if(params.url2) url2Input.value = params.url2; if(params.url1 && params.url2) { // Small delay to ensure DOM is ready setTimeout(() => run(), 100); } }); // Allow pressing Enter in textareas to run comparison document.getElementById("url1").addEventListener("keypress", (e) => { if (e.key === "Enter" && e.ctrlKey) run(); }); document.getElementById("url2").addEventListener("keypress", (e) => { if (e.key === "Enter" && e.ctrlKey) run(); }); </script> </body> </html> Listmaker & Renderer (here: Drawingtablets) ----------------------- listmaker.html -- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>List Maker</title> <style> body { font-family: Arial, sans-serif; padding: 20px; } input, select, textarea, button { margin: 5px 0; width: 100%; } .entry { border: 1px solid #ccc; padding: 10px; margin-top: 10px; } .small { font-size: 0.9em; color: #555; } </style> </head> <body> <h2>List Maker</h2> <form id="entryForm"> <label>Name</label> <input type="text" id="name" required> <label>Date (yyymmddhhss)</label> <input type="text" id="date" readonly> <label>Description</label> <textarea id="desc"></textarea> <label>Rating (0–10)</label> <input type="number" id="rating" min="0" max="10"> <label>Screen</label> <input type="text" id="screen" placeholder="e.g. 6.5 inch AMOLED"> <label>RAM</label> <input type="text" id="ram" placeholder="e.g. 8GB"> <label>GPU</label> <input type="text" id="gpu" placeholder="e.g. Adreno 650"> <label>Link Name (optional)</label> <input type="text" id="linkName" placeholder="e.g. Wikipedia"> <label>Link URL (optional)</label> <input type="url" id="linkUrl" placeholder="e.g. https://example.com"> <label>Rootable</label> <select id="rootable"> <option value="viaMagisk" selected>viaMagisk</option> <option value="yes">yes</option> <option value="no">no</option> </select> <label>LineageOS</label> <select id="lineage"> <option value="no" selected>no</option> <option value="yes">yes</option> </select> <button type="submit">Add Entry</button> </form> <hr> <h3>Entries</h3> <div id="list"></div> <hr> <h3>Export / Import</h3> <label>Query String Export</label> <textarea id="queryBox" rows="3" readonly></textarea> <button onclick="exportQuery()">Export to Query String</button> <button onclick="loadFromQuery()">Load from Query String</button> <br><br> <label>Load from list.txt</label> <input type="file" id="fileInput" accept=".txt"> <button onclick="downloadList()">Download list.txt</button> <script> let entries = []; function getTimestamp() { const d = new Date(); const yy = String(d.getFullYear()).slice(2); const MM = String(d.getMonth() + 1).padStart(2, '0'); const dd = String(d.getDate()).padStart(2, '0'); const hh = String(d.getHours()).padStart(2, '0'); const ss = String(d.getSeconds()).padStart(2, '0'); return `${yy}${MM}${dd}${hh}${ss}`; } document.getElementById("date").value = getTimestamp(); document.getElementById("entryForm").addEventListener("submit", e => { e.preventDefault(); const entry = { name: document.getElementById("name").value, date: document.getElementById("date").value, desc: document.getElementById("desc").value, rating: document.getElementById("rating").value || "", screen: document.getElementById("screen").value || "-", ram: document.getElementById("ram").value || "-", gpu: document.getElementById("gpu").value || "-", linkName: document.getElementById("linkName").value || "", linkUrl: document.getElementById("linkUrl").value || "", rootable: document.getElementById("rootable").value || "viaMagisk", lineage: document.getElementById("lineage").value || "no" }; entries.push(entry); render(); document.getElementById("entryForm").reset(); document.getElementById("date").value = getTimestamp(); }); function render() { const list = document.getElementById("list"); list.innerHTML = ""; entries.forEach(e => { const entryDiv = document.createElement("div"); entryDiv.className = "entry"; const nameElem = document.createElement("strong"); nameElem.textContent = e.name; const dateElem = document.createElement("div"); dateElem.className = "small"; dateElem.textContent = e.date; const descElem = document.createElement("div"); descElem.textContent = e.desc; const ratingElem = document.createElement("div"); ratingElem.textContent = `Rating: ${e.rating}`; const screenElem = document.createElement("div"); screenElem.textContent = `Screen: ${e.screen}`; const ramElem = document.createElement("div"); ramElem.textContent = `RAM: ${e.ram}`; const gpuElem = document.createElement("div"); gpuElem.textContent = `GPU: ${e.gpu}`; const linkElem = document.createElement("div"); if (e.linkName && e.linkUrl) { const link = document.createElement("a"); link.href = e.linkUrl; link.textContent = e.linkName; link.target = "_blank"; link.rel = "noopener noreferrer"; linkElem.textContent = "Link: "; linkElem.appendChild(link); } else if (e.linkName) { linkElem.textContent = `Link Name: ${e.linkName}`; } else if (e.linkUrl) { const link = document.createElement("a"); link.href = e.linkUrl; link.textContent = "Link"; link.target = "_blank"; link.rel = "noopener noreferrer"; linkElem.textContent = "Link: "; linkElem.appendChild(link); } else { linkElem.textContent = "Link: -"; } const rootableElem = document.createElement("div"); rootableElem.textContent = `Rootable: ${e.rootable}`; const lineageElem = document.createElement("div"); lineageElem.textContent = `LineageOS: ${e.lineage}`; entryDiv.appendChild(nameElem); entryDiv.appendChild(dateElem); entryDiv.appendChild(descElem); entryDiv.appendChild(ratingElem); entryDiv.appendChild(screenElem); entryDiv.appendChild(ramElem); entryDiv.appendChild(gpuElem); entryDiv.appendChild(linkElem); entryDiv.appendChild(rootableElem); entryDiv.appendChild(lineageElem); list.appendChild(entryDiv); }); } function exportQuery() { const encoded = encodeURIComponent(JSON.stringify(entries)); document.getElementById("queryBox").value = `?list=${encoded}`; window.history.replaceState(null, null, `?list=${encoded}`); } function loadFromQuery() { const params = new URLSearchParams(window.location.search); if (params.has("list")) { try { const decoded = decodeURIComponent(params.get("list")); entries = JSON.parse(decoded); render(); } catch (error) { console.error("Error parsing query string:", error); alert("Error loading from query string. Invalid format."); } } } document.getElementById("fileInput").addEventListener("change", e => { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = ev => { try { entries = JSON.parse(ev.target.result); render(); } catch (error) { console.error("Error parsing file:", error); alert("Error parsing file. Please ensure it's a valid JSON file."); } }; reader.readAsText(file); }); function downloadList() { const blob = new Blob([JSON.stringify(entries, null, 2)], { type: "text/plain" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "list.txt"; a.click(); } window.addEventListener('DOMContentLoaded', loadFromQuery); </script> </body> </html> index.html -- <a target="_blank" href="listmaker.html" style=color:blue>(+)</a><br><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Drawing Tablets</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: #f5f7fa; color: #333; padding: 20px; line-height: 1.6; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 10px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); overflow: hidden; } header { background: linear-gradient(135deg, #4b6cb7 0%, #182848 100%); color: white; padding: 25px; text-align: center; } h1 { font-size: 2.2rem; margin-bottom: 10px; } .subtitle { font-size: 1rem; opacity: 0.8; margin-bottom: 15px; } .filters { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; margin-top: 15px; } .filter-group { display: flex; flex-direction: column; gap: 5px; } .filter-label { font-size: 0.8rem; opacity: 0.9; } .filter-buttons { display: flex; gap: 5px; } .filter-btn { background: rgba(255, 255, 255, 0.2); border: none; color: white; padding: 6px 12px; border-radius: 15px; cursor: pointer; transition: all 0.3s; font-size: 0.85rem; } .filter-btn:hover { background: rgba(255, 255, 255, 0.3); } .filter-btn.active { background: white; color: #182848; } .legend { display: flex; justify-content: center; gap: 20px; margin: 20px 0; flex-wrap: wrap; } .legend-item { display: flex; align-items: center; font-size: 0.9rem; } .legend-color { width: 20px; height: 20px; border-radius: 4px; margin-right: 8px; } .table-container { overflow-x: auto; padding: 0 15px 20px; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } thead { background-color: #f1f5f9; } th { padding: 15px 12px; text-align: left; font-weight: 600; border-bottom: 2px solid #e2e8f0; cursor: pointer; user-select: none; position: relative; } th:hover { background-color: #e2e8f0; } th.sorted-asc::after { content: " ▲"; font-size: 0.8em; color: #4b6cb7; } th.sorted-desc::after { content: " ▼"; font-size: 0.8em; color: #4b6cb7; } td { padding: 15px 12px; border-bottom: 1px solid #e2e8f0; vertical-align: top; } tbody tr { transition: background-color 0.2s; } tbody tr:hover { background-color: #f8fafc; } /* Rating color coding */ .rating-10-8 { background-color: #d1fae5 !important; } .rating-7-5 { background-color: #fef3c7 !important; } .rating-5-0 { background-color: #fee2e2 !important; } /* Gray overlay for "not bought" */ .not-bought { position: relative; } .not-bought::after { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(128, 128, 128, 0.5); pointer-events: none; } .rating { font-weight: bold; text-align: center; border-radius: 4px; padding: 3px 8px; display: inline-block; } .link-cell a { display: inline-block; background-color: #4b6cb7; color: white; text-decoration: none; padding: 5px 12px; border-radius: 4px; font-size: 0.9rem; transition: background-color 0.3s; } .link-cell a:hover { background-color: #3a5699; } .yes { color: #10b981; font-weight: 600; } .no { color: #ef4444; font-weight: 600; } .desc-cell { max-width: 300px; white-space: pre-line; } .footer { text-align: center; padding: 20px; color: #64748b; font-size: 0.9rem; border-top: 1px solid #e2e8f0; } .active-filters { display: flex; justify-content: center; gap: 10px; margin: 10px 0; flex-wrap: wrap; } .active-filter-tag { background: #4b6cb7; color: white; padding: 4px 10px; border-radius: 12px; font-size: 0.8rem; display: flex; align-items: center; gap: 5px; } .remove-filter { background: none; border: none; color: white; cursor: pointer; font-size: 1rem; padding: 0; width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; } @media (max-width: 768px) { .container { border-radius: 0; } th, td { padding: 10px 8px; font-size: 0.9rem; } .desc-cell { max-width: 200px; } .filters { flex-direction: column; align-items: center; } .filter-group { width: 100%; max-width: 300px; } } </style> </head> <body> <div class="container"> <header> <h1>Ceas Drawing Tablets</h1> <div class="subtitle">..</div> <div class="filters"> <div class="filter-group"> <div class="filter-label">Purchase Status:</div> <div class="filter-buttons"> <button class="filter-btn" data-filter="all" data-group="purchase">All</button> <button class="filter-btn" data-filter="bought" data-group="purchase">Bought</button> <button class="filter-btn" data-filter="not-bought" data-group="purchase">Not Bought</button> </div> </div> <div class="filter-group"> <div class="filter-label">LineageOS:</div> <div class="filter-buttons"> <button class="filter-btn" data-filter="all" data-group="lineage">All</button> <button class="filter-btn" data-filter="lineage-yes" data-group="lineage">Supported</button> <button class="filter-btn" data-filter="lineage-no" data-group="lineage">Not Supported</button> </div> </div> <div class="filter-group"> <div class="filter-label">Rootable:</div> <div class="filter-buttons"> <button class="filter-btn" data-filter="all" data-group="rootable">All</button> <button class="filter-btn" data-filter="rootable-yes" data-group="rootable">Yes</button> <button class="filter-btn" data-filter="rootable-no" data-group="rootable">No</button> </div> </div> <div class="filter-group"> <div class="filter-label">Rating:</div> <div class="filter-buttons"> <button class="filter-btn" data-filter="all" data-group="rating">All</button> <button class="filter-btn" data-filter="rating-high" data-group="rating">8-10</button> <button class="filter-btn" data-filter="rating-medium" data-group="rating">5-7</button> <button class="filter-btn" data-filter="rating-low" data-group="rating">0-4</button> </div> </div> </div> <div id="activeFilters" class="active-filters"> <!-- Active filters will appear here --> </div> </header> <div class="legend"> <div class="legend-item"> <div class="legend-color" style="background-color: #d1fae5;"></div> <span>Rating: 8-10 (Good)</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #fef3c7;"></div> <span>Rating: 5-7 (Average)</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #fee2e2;"></div> <span>Rating: 0-4 (Poor)</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: rgba(128, 128, 128, 0.5);"></div> <span>Not Bought</span> </div> </div> <div class="table-container"> <table id="devicesTable"> <thead> <tr> <th data-sort="name">Name</th> <th data-sort="date">Date</th> <th data-sort="screen">Screen (inches)</th> <th data-sort="desc">Description</th> <th data-sort="rating">Rating</th> <th data-sort="ram">RAM</th> <th data-sort="gpu">GPU</th> <th data-sort="rootable">Rootable</th> <th data-sort="lineage">LineageOS</th> <th>Link</th> </tr> </thead> <tbody id="tableBody"> <!-- Table rows will be generated by JavaScript --> </tbody> </table> </div> <div class="footer"> <p>Click on any column header to sort. Click filter buttons to combine multiple filters.</p> <p>Data loaded from <code>list.txt</code> or URL query string</p> </div> </div> <script> class DeviceListRenderer { constructor() { this.devices = []; this.filteredDevices = []; this.currentSort = { field: null, direction: 'asc' }; this.activeFilters = { purchase: 'all', lineage: 'all', rootable: 'all', rating: 'all' }; this.init(); } async init() { // Load data first await this.loadData(); // Apply query string filters this.applyQueryStringFilters(); // Apply the filters with query string values this.filteredDevices = [...this.devices]; this.applyFilters(); // Then setup UI this.renderTable(); this.setupEventListeners(); this.updateActiveFiltersDisplay(); } async loadData() { try { // Try to load from query string first const queryData = this.getDataFromQueryString(); if (queryData && queryData.length > 0) { this.devices = queryData; console.log('Loaded data from query string'); } else { // Try to fetch from list.txt const response = await fetch('list.txt'); if (!response.ok) throw new Error('Failed to fetch list.txt'); const text = await response.text(); this.devices = JSON.parse(text); console.log('Loaded data from list.txt'); } } catch (error) { console.error('Error loading data:', error); this.showError('Failed to load data. Please ensure list.txt exists or provide data via query string.'); this.devices = []; } } getDataFromQueryString() { try { const urlParams = new URLSearchParams(window.location.search); const listParam = urlParams.get('list'); if (listParam) { return JSON.parse(decodeURIComponent(listParam)); } } catch (error) { console.error('Error parsing query string data:', error); } return null; } applyQueryStringFilters() { const urlParams = new URLSearchParams(window.location.search); // Check each query string parameter and apply if present ['purchasestatus', 'lineageos', 'rootable', 'rating'].forEach(param => { const value = urlParams.get(param); if (value) { // Normalize the param name (lineageos -> lineage) const filterGroup = param === 'lineageos' ? 'lineage' : param === 'purchasestatus' ? 'purchase' : param; const filterValue = this.mapQueryValueToFilter(filterGroup, value.toLowerCase()); if (filterValue && this.activeFilters.hasOwnProperty(filterGroup)) { this.activeFilters[filterGroup] = filterValue; } } }); } mapQueryValueToFilter(group, value) { const mappings = { purchase: { 'bought': 'bought', 'purchased': 'bought', 'owned': 'bought', 'not-bought': 'not-bought', 'notbought': 'not-bought', 'not': 'not-bought', 'notpurchased': 'not-bought', 'not-owned': 'not-bought' }, lineage: { 'yes': 'lineage-yes', 'no': 'lineage-no', 'supported': 'lineage-yes', 'not-supported': 'lineage-no', 'not': 'lineage-no', 'true': 'lineage-yes', 'false': 'lineage-no', '1': 'lineage-yes', '0': 'lineage-no' }, rootable: { 'yes': 'rootable-yes', 'no': 'rootable-no', 'rootable': 'rootable-yes', 'not-rootable': 'rootable-no', 'not': 'rootable-no', 'true': 'rootable-yes', 'false': 'rootable-no', 'magisk': 'rootable-yes', '1': 'rootable-yes', '0': 'rootable-no' }, rating: { 'high': 'rating-high', 'medium': 'rating-medium', 'low': 'rating-low', '8-10': 'rating-high', '8': 'rating-high', '9': 'rating-high', '10': 'rating-high', '5-7': 'rating-medium', '5': 'rating-medium', '6': 'rating-medium', '7': 'rating-medium', '0-4': 'rating-low', '0': 'rating-low', '1': 'rating-low', '2': 'rating-low', '3': 'rating-low', '4': 'rating-low' } }; return mappings[group]?.[value] || null; } formatDate(dateStr) { // Format YYMMDDHHMM to readable date if (!dateStr || dateStr.length !== 10) return dateStr; const year = '20' + dateStr.substring(0, 2); const month = dateStr.substring(2, 4); const day = dateStr.substring(4, 6); const hour = dateStr.substring(6, 8); const minute = dateStr.substring(8, 10); return `${year}-${month}-${day} ${hour}:${minute}`; } getRatingClass(rating) { const numRating = parseInt(rating); if (numRating >= 8) return 'rating-10-8'; if (numRating >= 5) return 'rating-7-5'; return 'rating-5-0'; } getRatingRange(rating) { const numRating = parseInt(rating); if (numRating >= 8) return 'high'; if (numRating >= 5) return 'medium'; return 'low'; } isNotBought(device) { const desc = device.desc.toLowerCase(); return desc.includes('notbought') || desc.includes('not bought') || desc.includes('(notbought)') || desc.includes('(not bought)'); } applyFilters() { this.filteredDevices = this.devices.filter(device => { // Purchase filter if (this.activeFilters.purchase === 'bought') { if (this.isNotBought(device)) return false; } if (this.activeFilters.purchase === 'not-bought') { if (!this.isNotBought(device)) return false; } // LineageOS filter if (this.activeFilters.lineage === 'lineage-yes' && device.lineage !== 'yes') return false; if (this.activeFilters.lineage === 'lineage-no' && device.lineage === 'yes') return false; // Rootable filter if (this.activeFilters.rootable === 'rootable-yes') { if (!device.rootable || !device.rootable.toLowerCase().includes('magisk')) return false; } if (this.activeFilters.rootable === 'rootable-no') { if (device.rootable && device.rootable.toLowerCase().includes('magisk')) return false; } // Rating filter if (this.activeFilters.rating === 'rating-high' && this.getRatingRange(device.rating) !== 'high') return false; if (this.activeFilters.rating === 'rating-medium' && this.getRatingRange(device.rating) !== 'medium') return false; if (this.activeFilters.rating === 'rating-low' && this.getRatingRange(device.rating) !== 'low') return false; return true; }); // Reapply current sort if any if (this.currentSort.field) { this.sortDevices(this.currentSort.field); } else { this.renderTable(); } } updateFilter(group, filter) { // Reset all buttons in the group document.querySelectorAll(`.filter-btn[data-group="${group}"]`).forEach(btn => { btn.classList.remove('active'); }); // Activate the clicked button const clickedBtn = document.querySelector(`.filter-btn[data-group="${group}"][data-filter="${filter}"]`); if (clickedBtn) { clickedBtn.classList.add('active'); } // Update active filter this.activeFilters[group] = filter; this.applyFilters(); this.updateActiveFiltersDisplay(); } updateActiveFiltersDisplay() { const container = document.getElementById('activeFilters'); container.innerHTML = ''; Object.entries(this.activeFilters).forEach(([group, filter]) => { if (filter !== 'all') { const filterTag = document.createElement('div'); filterTag.className = 'active-filter-tag'; const groupNames = { purchase: 'Purchase', lineage: 'LineageOS', rootable: 'Rootable', rating: 'Rating' }; const filterNames = { 'bought': 'Bought', 'not-bought': 'Not Bought', 'lineage-yes': 'Supported', 'lineage-no': 'Not Supported', 'rootable-yes': 'Rootable', 'rootable-no': 'Not Rootable', 'rating-high': '8-10', 'rating-medium': '5-7', 'rating-low': '0-4' }; filterTag.innerHTML = ` ${groupNames[group]}: ${filterNames[filter] || filter} <button class="remove-filter" data-group="${group}">×</button> `; container.appendChild(filterTag); } }); // Add event listeners to remove buttons document.querySelectorAll('.remove-filter').forEach(btn => { btn.addEventListener('click', (e) => { const group = e.target.dataset.group; this.updateFilter(group, 'all'); }); }); } renderTable() { const tableBody = document.getElementById('tableBody'); tableBody.innerHTML = ''; if (this.filteredDevices.length === 0) { tableBody.innerHTML = ` <tr> <td colspan="10" style="text-align: center; padding: 40px;"> No devices match the current filters. </td> </tr> `; return; } this.filteredDevices.forEach(device => { const row = document.createElement('tr'); // Add not-bought class if applicable if (this.isNotBought(device)) { row.classList.add('not-bought'); } // Add rating-based color class row.classList.add(this.getRatingClass(device.rating)); // Get screen value or default to "-" const screenValue = device.screen || '-'; row.innerHTML = ` <td>${this.escapeHtml(device.name)}</td> <td>${this.formatDate(device.date)}</td> <td>${this.escapeHtml(screenValue)}</td> <td class="desc-cell">${this.escapeHtml(device.desc)}</td> <td class="rating ${this.getRatingClass(device.rating)}">${device.rating}</td> <td>${this.escapeHtml(device.ram)}</td> <td>${this.escapeHtml(device.gpu)}</td> <td class="${device.rootable ? 'yes' : 'no'}">${this.escapeHtml(device.rootable)}</td> <td class="${device.lineage === 'yes' ? 'yes' : 'no'}">${this.escapeHtml(device.lineage)}</td> <td class="link-cell"> <a href="${this.escapeHtml(device.linkUrl)}" target="_blank"> ${this.escapeHtml(device.linkName)} </a> </td> `; tableBody.appendChild(row); }); this.updateSortIndicators(); } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } sortDevices(field) { if (this.currentSort.field === field) { this.currentSort.direction = this.currentSort.direction === 'asc' ? 'desc' : 'asc'; } else { this.currentSort.field = field; this.currentSort.direction = 'asc'; } this.filteredDevices.sort((a, b) => { let aVal = a[field]; let bVal = b[field]; // Convert to numbers if possible for numeric fields if (field === 'rating' || field === 'date' || field === 'screen') { aVal = parseFloat(aVal) || 0; bVal = parseFloat(bVal) || 0; } if (aVal < bVal) return this.currentSort.direction === 'asc' ? -1 : 1; if (aVal > bVal) return this.currentSort.direction === 'asc' ? 1 : -1; return 0; }); this.renderTable(); } updateSortIndicators() { document.querySelectorAll('th[data-sort]').forEach(th => { th.classList.remove('sorted-asc', 'sorted-desc'); if (th.dataset.sort === this.currentSort.field) { th.classList.add(`sorted-${this.currentSort.direction}`); } }); } showError(message) { const tableBody = document.getElementById('tableBody'); tableBody.innerHTML = ` <tr> <td colspan="10" style="text-align: center; padding: 40px; color: #ef4444;"> ${message} </td> </tr> `; } setupEventListeners() { // Sort on header click document.querySelectorAll('th[data-sort]').forEach(th => { th.addEventListener('click', () => { this.sortDevices(th.dataset.sort); }); }); // Filter buttons document.querySelectorAll('.filter-btn').forEach(btn => { btn.addEventListener('click', () => { this.updateFilter(btn.dataset.group, btn.dataset.filter); }); }); // Set initial active states based on applied filters this.updateFilterButtonStates(); } updateFilterButtonStates() { Object.entries(this.activeFilters).forEach(([group, filter]) => { const btn = document.querySelector(`.filter-btn[data-group="${group}"][data-filter="${filter}"]`); if (btn) btn.classList.add('active'); }); } } // Initialize the renderer when page loads document.addEventListener('DOMContentLoaded', () => { new DeviceListRenderer(); }); </script> </body> </html> Fedi TL with CORS Loader ------------ <a href="https://alceawis.de/other/extra/fetchdata/2024-03-07-FediTools/2025-12-11-FediTLFetch/fetchtl.html?url=https://woof.tech" style=color:blue>woof.tech</a> <a href="https://alceawis.de/other/extra/fetchdata/2024-03-07-FediTools/2025-12-11-FediTLFetch/fetchtl.html?url=https://alceawis.com&cors=yes" style=color:blue>acws</a> <a href="https://alceawis.de/other/extra/fetchdata/2024-03-07-FediTools/2025-12-11-FediTLFetch/fetchtl.html?url=https://mastodon.social&cors=yes" style=color:blue>mastodon.social</a> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Mas.to Public Timeline</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; line-height: 1.6; color: #333; background-color: #f5f8fa; max-width: 800px; margin: 0 auto; padding: 20px; } .header { text-align: center; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid #e1e8ed; } .header h1 { color: #2b90d9; font-size: 2.5rem; margin-bottom: 10px; } .header p { color: #657786; font-size: 1.1rem; } .instance-selector { display: flex; justify-content: center; align-items: center; margin: 20px 0; gap: 10px; flex-wrap: wrap; } .instance-selector label { font-weight: 600; } .instance-selector input { padding: 10px 15px; border: 2px solid #e1e8ed; border-radius: 25px; font-size: 1rem; width: 300px; max-width: 100%; transition: border-color 0.3s; } .instance-selector input:focus { outline: none; border-color: #2b90d9; } .instance-selector button { background-color: #2b90d9; color: white; border: none; padding: 10px 20px; border-radius: 25px; font-size: 1rem; cursor: pointer; transition: background-color 0.3s; } .instance-selector button:hover { background-color: #1c6ca5; } .instance-selector button:disabled { background-color: #a0c5e0; cursor: not-allowed; } .config-info { background-color: #f8f9fa; border-radius: 10px; padding: 15px; margin-bottom: 20px; font-size: 0.9rem; color: #657786; border-left: 4px solid #2b90d9; } .config-info h3 { margin-top: 0; margin-bottom: 8px; color: #2b90d9; font-size: 1rem; } .config-info p { margin: 5px 0; } .stats { display: flex; justify-content: space-between; padding: 10px 15px; background-color: #e8f4fc; border-radius: 10px; margin-bottom: 20px; font-size: 0.9rem; color: #657786; } #timeline { margin-bottom: 50px; } .post { background-color: white; border-radius: 15px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); transition: transform 0.2s, box-shadow 0.2s; } .post:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); } .post-header { display: flex; align-items: center; margin-bottom: 15px; } .avatar { width: 50px; height: 50px; border-radius: 50%; object-fit: cover; margin-right: 15px; border: 2px solid #e1e8ed; } .user-info h3 { font-size: 1.1rem; margin-bottom: 5px; } .user-info .username { color: #657786; font-size: 0.9rem; } .post-content { margin-bottom: 15px; font-size: 1.05rem; line-height: 1.7; } .post-content a { color: #2b90d9; text-decoration: none; } .post-content a:hover { text-decoration: underline; } .post-media { margin-top: 15px; border-radius: 10px; overflow: hidden; max-height: 400px; } .post-media img { width: 100%; height: auto; display: block; } .post-footer { display: flex; justify-content: space-between; color: #657786; font-size: 0.9rem; padding-top: 15px; border-top: 1px solid #e1e8ed; margin-top: 15px; } .post-date { display: flex; align-items: center; gap: 5px; } .post-stats { display: flex; gap: 20px; } .post-stat { display: flex; align-items: center; gap: 5px; } .post-actions { display: flex; gap: 15px; margin-top: 10px; padding-top: 10px; border-top: 1px dashed #e1e8ed; } .post-action { display: inline-flex; align-items: center; gap: 5px; padding: 6px 12px; background-color: #f0f8ff; border-radius: 20px; color: #2b90d9; text-decoration: none; font-size: 0.85rem; transition: all 0.2s; border: 1px solid #d1e7ff; } .post-action:hover { background-color: #2b90d9; color: white; transform: translateY(-2px); box-shadow: 0 3px 8px rgba(43, 144, 217, 0.2); text-decoration: none; } .post-action.reply { background-color: #f0fff4; border-color: #c6f6d5; color: #38a169; } .post-action.reply:hover { background-color: #38a169; color: white; box-shadow: 0 3px 8px rgba(56, 161, 105, 0.2); } .post-action i { font-size: 0.8rem; } .loading { text-align: center; padding: 30px; color: #657786; font-size: 1.1rem; } .loading-spinner { display: inline-block; width: 30px; height: 30px; border: 3px solid rgba(43, 144, 217, 0.3); border-radius: 50%; border-top-color: #2b90d9; animation: spin 1s ease-in-out infinite; margin-right: 10px; } @keyframes spin { to { transform: rotate(360deg); } } .error { background-color: #ffeaea; color: #d30000; padding: 20px; border-radius: 10px; text-align: center; margin: 20px 0; } .empty { text-align: center; padding: 40px; color: #657786; font-size: 1.1rem; } .footer { text-align: center; padding: 20px; color: #657786; font-size: 0.9rem; border-top: 1px solid #e1e8ed; margin-top: 30px; } .footer a { color: #2b90d9; text-decoration: none; } .footer a:hover { text-decoration: underline; } @media (max-width: 600px) { body { padding: 10px; } .header h1 { font-size: 2rem; } .instance-selector { flex-direction: column; align-items: stretch; } .instance-selector input { width: 100%; margin-bottom: 10px; } .post { padding: 15px; } .post-stats { flex-wrap: wrap; gap: 10px; } .post-actions { flex-direction: column; gap: 10px; } .post-action { justify-content: center; } } </style> </head> <body> <div class="header"> <h1><i class="fas fa-broadcast-tower"></i> Mastodon Timeline</h1> <p>Public posts from the fediverse with enhanced reply handling</p> </div> <div id="config-display" class="config-info" style="display: none;"> <h3><i class="fas fa-cog"></i> Configuration</h3> <p><strong>CORS Proxy:</strong> <span id="cors-status">Disabled</span></p> <p><strong>Prefilled URL:</strong> From query parameter</p> </div> <div class="instance-selector"> <label for="instance-url">Mastodon Instance:</label> <input type="text" id="instance-url" value="https://mas.to" placeholder="https://your.instance"> <button id="load-timeline">Load Timeline</button> </div> <div class="stats"> <div>Posts loaded: <span id="post-count">0</span></div> <div>Current instance: <span id="current-instance">https://mas.to</span></div> </div> <div id="timeline"> <div class="loading" id="initial-loading"> <div class="loading-spinner"></div> Loading public timeline from mas.to... </div> </div> <div class="footer"> <p>Data from the <a href="https://joinmastodon.org" target="_blank">Mastodon</a> network. This viewer uses the public API and respects instance rate limits.</p> <p>Each post includes direct links and reply handler links that open in new tabs.</p> <p>Use <code>?url=https://your.instance</code> in URL to prefill, and <code>?cors=yes</code> to enable CORS proxy.</p> <p>Made with <i class="fas fa-heart" style="color:#e74c3c"></i> for the fediverse</p> </div> <script> class MastodonTimelineLoader { constructor() { this.timelineContainer = document.getElementById('timeline'); this.instanceInput = document.getElementById('instance-url'); this.loadButton = document.getElementById('load-timeline'); this.postCountElement = document.getElementById('post-count'); this.currentInstanceElement = document.getElementById('current-instance'); this.configDisplay = document.getElementById('config-display'); this.corsStatusElement = document.getElementById('cors-status'); // Reply handler base URL this.replyHandlerBaseUrl = 'https://alceawis.de/other/extra/scripts/fakesocialmedia/replyhandler.html?url='; // Parse query string parameters this.urlParams = new URLSearchParams(window.location.search); this.useCorsProxy = this.urlParams.get('cors') === 'yes'; this.prefilledUrl = this.urlParams.get('url'); // Set initial instance URL from query parameter or default this.instanceUrl = this.prefilledUrl || 'https://mas.to'; this.instanceInput.value = this.instanceUrl; // Update display based on config this.updateConfigDisplay(); // Initialize timeline URL this.updateTimelineUrl(); this.loading = false; this.maxId = null; this.posts = []; this.postCount = 0; this.init(); } updateConfigDisplay() { if (this.useCorsProxy || this.prefilledUrl) { this.configDisplay.style.display = 'block'; this.corsStatusElement.textContent = this.useCorsProxy ? 'Enabled' : 'Disabled'; } } updateTimelineUrl() { if (this.useCorsProxy) { // Use CORS proxy with encoded instance URL const encodedInstanceUrl = encodeURIComponent(this.instanceUrl); this.timelineUrl = `/cors.php?query=${encodedInstanceUrl}/api/v1/timelines/public`; } else { // Direct API call this.timelineUrl = `${this.instanceUrl}/api/v1/timelines/public`; } } getApiUrl(maxId = null) { if (this.useCorsProxy) { // For CORS proxy, we need to include the full URL with parameters let apiUrl = `${this.instanceUrl}/api/v1/timelines/public`; if (maxId) { apiUrl += `?max_id=${maxId}`; } const encodedApiUrl = encodeURIComponent(apiUrl); return `/cors.php?query=${encodedApiUrl}`; } else { // Direct API call let url = `${this.instanceUrl}/api/v1/timelines/public`; if (maxId) { url += `?max_id=${maxId}`; } return url; } } init() { // Update current instance display this.currentInstanceElement.textContent = this.instanceUrl; // Load initial timeline this.loadTimeline(); // Set up event listeners this.loadButton.addEventListener('click', () => this.changeInstance()); this.instanceInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.changeInstance(); } }); // Set up infinite scroll window.addEventListener('scroll', () => { this.handleScroll(); }); } changeInstance() { const newInstance = this.instanceInput.value.trim(); if (!newInstance) { this.showError('Please enter a Mastodon instance URL'); return; } // Basic URL validation try { new URL(newInstance); } catch { this.showError('Please enter a valid URL (include https://)'); return; } this.instanceUrl = newInstance; this.currentInstanceElement.textContent = this.instanceUrl; // Reset state this.maxId = null; this.posts = []; this.postCount = 0; this.postCountElement.textContent = '0'; this.timelineContainer.innerHTML = '<div class="loading"><div class="loading-spinner"></div>Loading public timeline...</div>'; // Load new timeline this.loadTimeline(); } async loadTimeline() { if (this.loading) return; this.loading = true; this.showLoading(); try { const url = this.getApiUrl(this.maxId); const response = await fetch(url, { headers: { 'Accept': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } let newPosts; if (this.useCorsProxy) { // For CORS proxy, we might need to parse the response differently // depending on how the proxy returns data const responseText = await response.text(); try { newPosts = JSON.parse(responseText); } catch (e) { // If direct parse fails, try to extract JSON from possible wrapper console.warn('Direct JSON parse failed, trying alternative parsing'); newPosts = JSON.parse(responseText); } } else { newPosts = await response.json(); } if (!Array.isArray(newPosts)) { throw new Error('Invalid response format from server'); } if (newPosts.length === 0) { if (this.posts.length === 0) { this.showEmptyMessage(); } return; } // Store the max_id for pagination this.maxId = newPosts[newPosts.length - 1].id; // Add new posts to our collection this.posts = [...this.posts, ...newPosts]; this.postCount = this.posts.length; this.postCountElement.textContent = this.postCount; // Render posts this.renderPosts(newPosts); } catch (error) { console.error('Error loading timeline:', error); let errorMessage = `Failed to load timeline: ${error.message}`; if (this.useCorsProxy) { errorMessage += '. The CORS proxy might not be properly configured.'; } else { errorMessage += '. Make sure the instance allows public timeline access and CORS.'; } this.showError(errorMessage); } finally { this.loading = false; this.hideLoading(); } } renderPosts(posts) { // Remove loading indicator if it exists const loadingElement = this.timelineContainer.querySelector('.loading'); if (loadingElement && this.posts.length > posts.length) { loadingElement.remove(); } // Remove empty message if it exists const emptyElement = this.timelineContainer.querySelector('.empty'); if (emptyElement) { emptyElement.remove(); } // Create HTML for each post posts.forEach(post => { const postElement = this.createPostElement(post); this.timelineContainer.appendChild(postElement); }); } createPostElement(post) { const postDiv = document.createElement('div'); postDiv.className = 'post'; postDiv.dataset.id = post.id; // Format date const postDate = new Date(post.created_at); const formattedDate = postDate.toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); // Get display name or username const displayName = post.account.display_name || post.account.username; // Create content HTML let content = post.content; // Handle media attachments let mediaHtml = ''; if (post.media_attachments && post.media_attachments.length > 0) { post.media_attachments.forEach(media => { if (media.type === 'image') { mediaHtml += `<div class="post-media"><img src="${media.url}" alt="${media.description || 'Mastodon image'}"></div>`; } }); } // Get post URL (some instances might not have the url field) let postUrl = post.url; if (!postUrl && post.account && post.account.url) { // Construct a URL if not provided const username = post.account.acct.split('@')[0]; postUrl = `${this.instanceUrl}/@${username}/${post.id}`; } // Create action links if we have a post URL let actionLinks = ''; if (postUrl) { // Encode the post URL for the reply handler const encodedPostUrl = encodeURIComponent(postUrl); const replyHandlerUrl = `${this.replyHandlerBaseUrl}${encodedPostUrl}`; actionLinks = ` <div class="post-actions"> <a href="${postUrl}" class="post-action" target="_blank" title="Open original post in new tab"> <i class="fas fa-external-link-alt"></i> Open Post </a> <a href="${replyHandlerUrl}" class="post-action reply" target="_blank" title="Open with reply handler in new tab"> <i class="fas fa-reply"></i> Reply Handler </a> </div> `; } // Create the post HTML postDiv.innerHTML = ` <div class="post-header"> <img src="${post.account.avatar}" alt="${displayName}'s avatar" class="avatar" onerror="this.src='data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSIyNSIgY3k9IjI1IiByPSIyNSIgZmlsbD0iI2UxZThlZCIvPjx0ZXh0IHg9IjI1IiB5PSIyOCIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE0IiBmaWxsPSIjNjU3Nzg2IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+${displayName.charAt(0).toUpperCase()}</dGV4dD48L3N2Zz4='"> <div class="user-info"> <h3>${this.escapeHtml(displayName)}</h3> <div class="username">@${post.account.acct}</div> </div> </div> <div class="post-content">${content}</div> ${mediaHtml} ${actionLinks} <div class="post-footer"> <div class="post-date"> <i class="far fa-clock"></i> ${formattedDate} </div> <div class="post-stats"> <div class="post-stat"> <i class="far fa-comment"></i> ${post.replies_count} </div> <div class="post-stat"> <i class="fas fa-retweet"></i> ${post.reblogs_count} </div> <div class="post-stat"> <i class="far fa-heart"></i> ${post.favourites_count} </div> </div> </div> `; return postDiv; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } showLoading() { // Only show loading if we're at the bottom if (this.posts.length > 0) { const loadingDiv = document.createElement('div'); loadingDiv.className = 'loading'; loadingDiv.id = 'loading-more'; loadingDiv.innerHTML = '<div class="loading-spinner"></div>Loading more posts...'; this.timelineContainer.appendChild(loadingDiv); } } hideLoading() { const loadingElement = this.timelineContainer.querySelector('#loading-more'); if (loadingElement) { loadingElement.remove(); } } showError(message) { const errorDiv = document.createElement('div'); errorDiv.className = 'error'; errorDiv.innerHTML = `<p><i class="fas fa-exclamation-triangle"></i> ${message}</p>`; // Remove existing error if any const existingError = this.timelineContainer.querySelector('.error'); if (existingError) { existingError.remove(); } // If there are no posts yet, replace the content if (this.posts.length === 0) { this.timelineContainer.innerHTML = ''; this.timelineContainer.appendChild(errorDiv); } else { // Otherwise, add it to the top this.timelineContainer.prepend(errorDiv); } } showEmptyMessage() { this.timelineContainer.innerHTML = ` <div class="empty"> <i class="far fa-comment-dots fa-3x" style="margin-bottom: 15px; color: #bdc3c7;"></i> <h3>No posts found</h3> <p>The public timeline for this instance appears to be empty or not accessible.</p> </div> `; } handleScroll() { // Check if we've scrolled near the bottom of the page const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; const windowHeight = window.innerHeight; const documentHeight = document.documentElement.scrollHeight; // Load more when 80% from the bottom if (scrollTop + windowHeight >= documentHeight * 0.8) { this.loadTimeline(); } } } // Initialize the timeline loader when the page loads document.addEventListener('DOMContentLoaded', () => { new MastodonTimelineLoader(); }); </script> </body> </html> Videoplayer with subtitlesupport ------------------ <a target="_blank" href="view-source:https://alceawis.de/videoplayer?video=https://alcea-wisteria.de/vid/anime/vrns/2025-12-08-vrains-yunthreethings.mp4&time=6:30" style=color:blue>Demo</a><br><hr> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Video Player with Subtitles</title> <style> /* 🎨 GLOBAL STYLES */ body { margin: 0; background: #111; display: flex; justify-content: center; align-items: center; height: 100vh; font-family: sans-serif; user-select: none; } /* 🖼️ VIDEO CONTAINER */ #video-container { position: relative; width: 80%; max-width: 1200px; } /* 🎬 VIDEO ELEMENT */ video { width: 100%; display: block; background: #000; } /* 💬 SUBTITLES (NORMAL MODE) */ #subs { /* Set initial styles for absolute positioning inside the container */ position: absolute; bottom: 5%; left: 50%; transform: translateX(-50%); width: 90%; text-align: center; color: white; font-size: 1.8em; text-shadow: 0 0 10px black, 0 0 10px black; pointer-events: none; line-height: 1.4; word-break: break-word; z-index: 9999; } /* 💬 SUBTITLES (FULLSCREEN MODE) - New Fixed Style */ .subs-fullscreen { /* The style when the element is moved to the document body for fixed positioning */ position: fixed !important; /* Override absolute position */ bottom: 5% !important; /* Anchor to viewport bottom */ width: 90vw !important; /* Scale relative to viewport width */ font-size: 3vw !important; /* Scale font relative to viewport */ /* Ensure it remains centered and visible above all elements */ left: 50%; transform: translateX(-50%); z-index: 99999; } /* ❌ Removed #video-container:fullscreen #subs CSS */ </style> </head> <body> <div id="video-container"> <video id="video" controls></video> <div id="subs"></div> </div> <script> const video = document.getElementById('video'); const subsDiv = document.getElementById('subs'); const videoContainer = document.getElementById('video-container'); // --- URL PARAMETER PARSING --- function getQueryParam(name) { return new URLSearchParams(window.location.search).get(name); } const videoUrl = getQueryParam('video'); if (!videoUrl) { subsDiv.textContent = 'Error: No video specified in URL parameter "?video=..."'; throw new Error('No video specified'); } video.src = videoUrl; const timeParam = getQueryParam('time'); function parseTime(str) { if (!str) return 0; if (str.includes(':')) { const parts = str.split(':').map(Number); if (parts.length === 2) return parts[0]*60 + parts[1]; if (parts.length === 3) return parts[0]*3600 + parts[1]*60 + parts[2]; } return Number(str); } const startTime = parseTime(timeParam); const baseName = videoUrl.replace(/\.[^/.]+$/, ""); const subtitleFiles = [baseName + '.vtt', baseName + '.srt']; let cues = []; // --- SUBTITLE LOADING AND PARSING (Unchanged) --- async function loadSubtitles() { let text = null; let urlUsed = null; for (const url of subtitleFiles) { try { const res = await fetch(url); if (!res.ok) continue; text = await res.text(); urlUsed = url; break; } catch(e) { console.error('Failed to fetch subtitle file:', url, e); continue; } } if (!text) { console.log('No subtitle file found.'); return; } if (urlUsed.endsWith('.srt')) { text = "WEBVTT\n\n" + text .replace(/\r/g, "") .replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, "$1.$2") .replace(/(\d+)\n(\d{2}:\d{2}:\d{2}\.\d{3} -->)/g, "$2"); } cues = []; const entries = text.split(/\n\n+/); for (const entry of entries) { if (entry.toUpperCase().includes('WEBVTT')) continue; const lines = entry.split('\n').map(l => l.trim()).filter(l => l); if (lines.length < 2) continue; let timeLine = ''; let textLines = []; const timeLineIndex = lines.findIndex(l => l.includes('-->')); if (timeLineIndex !== -1) { timeLine = lines[timeLineIndex]; textLines = lines.slice(timeLineIndex + 1); } else { continue; } const [startStr, endStr] = timeLine.split('-->').map(s => s.trim()); function parseTimeStr(str) { const cleanStr = str.replace(',', '.'); const parts = cleanStr.split(':').map(p => parseFloat(p)); if (parts.length === 3) return parts[0]*3600 + parts[1]*60 + parts[2]; if (parts.length === 2) return parts[0]*60 + parts[1]; return parseFloat(cleanStr); } const start = parseTimeStr(startStr); const end = parseTimeStr(endStr); cues.push({start, end, text: textLines.join('\n')}); } } // --- SUBTITLE RENDERING LOOP (Unchanged) --- let cueIndex = 0; function renderSubs() { const t = video.currentTime; if (cueIndex >= cues.length || t < cues[cueIndex].start || t > cues[cueIndex].end) { cueIndex = 0; while (cueIndex < cues.length && cues[cueIndex].end < t) { cueIndex++; } } if (cueIndex < cues.length && t >= cues[cueIndex].start && t <= cues[cueIndex].end) { subsDiv.textContent = cues[cueIndex].text; } else { subsDiv.textContent = ''; } requestAnimationFrame(renderSubs); } // --- FULLSCREEN SUBTITLE POSITION FIX (JAVASCRIPT) --- function updateSubtitlePosition() { // Check if any element is currently in fullscreen mode if (document.fullscreenElement) { // FIX: Move the subtitle div outside of the container and attach it to the body // This ensures it stays on top of the fullscreen layer. document.body.appendChild(subsDiv); subsDiv.classList.add('subs-fullscreen'); } else { // FIX: Move the subtitle div back to its original container videoContainer.appendChild(subsDiv); subsDiv.classList.remove('subs-fullscreen'); } } // Listen for the browser's fullscreen event document.addEventListener('fullscreenchange', updateSubtitlePosition); document.addEventListener('webkitfullscreenchange', updateSubtitlePosition); // For older WebKit browsers (Safari) // --- INITIALIZATION --- loadSubtitles().then(() => { if (video.src) requestAnimationFrame(renderSubs); }); video.addEventListener('loadedmetadata', () => { if (startTime > 0 && startTime < video.duration) { video.currentTime = startTime; } }); // --- KEYBOARD SHORTCUTS --- document.addEventListener('keydown', (e) => { switch(e.key.toLowerCase()) { case ' ': case 'k': e.preventDefault(); video.paused ? video.play() : video.pause(); break; case 'f': e.preventDefault(); // We still request fullscreen on the container to cover the whole box document.fullscreenElement ? document.exitFullscreen() : videoContainer.requestFullscreen(); break; case 'arrowleft': e.preventDefault(); video.currentTime = Math.max(0, video.currentTime - 5); break; case 'arrowright': e.preventDefault(); video.currentTime = Math.min(video.duration, video.currentTime + 5); break; } }); </script> </body> </html> Gif Scroller 2anigif (horizontal/vertical) --------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <script src="https://alceawis.de/other/extra/scripts/libraries/gifjs/gif.js"></script> <style> /* --- STYLES --- */ body { font-family: sans-serif; padding: 20px; background: #f4f4f4; } h1, h2, h3 { color: #333; } #log { white-space: pre-wrap; margin-top: 10px; padding: 10px; border: 1px solid #ccc; background: white; max-height: 150px; overflow-y: auto;} #gif-controls { padding: 20px; background: #fff; border: 1px solid #ddd; border-radius: 8px;} .input-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 10px; } .input-row label, .input-row input, .input-row select { margin-right: 5px; } button { padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } button:disabled { background-color: #ccc; } #statusMessage { margin-top: 15px; font-weight: bold; } #progressFill { height: 10px; width: 0%; background: #4CAF50; margin-top: 5px; border-radius: 5px; transition: width 0.3s;} /* Preview and Output styles */ .preview-container { /* Dimensions will be set dynamically by JS on image load */ overflow: hidden; margin: 10px 0; border: 2px dashed #007bff; position: relative; background: #e0e0e0; } .preview-container img { position: absolute; top: 0; left: 0; will-change: transform; display: none; } #generatedGif { max-width: 100%; border: 1px solid #000; display: none; margin-top: 10px;} #downloadLink { display: none; padding: 8px 15px; background: #28a745; color: white; text-decoration: none; border-radius: 4px; } </style> </head> <body> <h1>GIF Worker Integration Demo</h1> <h2>Service Worker and Logs</h2> <pre id="log">Loading logs...\n</pre> <div id="gif-controls"> <h2>GIF Generation Parameters</h2> <label for="imageUpload">1. Upload Image:</label> <input type="file" id="imageUpload" accept="image/*"><br><br> <div class="input-row"> <label>Visible Width (px):</label><input type="number" id="visiblePortionWidth" value="600" min="100" style="width: 80px;"> <label>Visible Height (px):</label><input type="number" id="visiblePortionHeight" value="300" min="100" style="width: 80px;"> <label>Frames:</label><input type="number" id="gifFrames" value="30" min="5" style="width: 60px;"> <label>Delay (ms):</label><input type="number" id="frameDelay" value="40" min="20" style="width: 60px;"> <label>Direction:</label> <select id="scrollDirection"> <option value="vertical">Vertical</option> <option value="horizontal">Horizontal</option> </select> <label>Custom Loop Length (px):</label><input type="number" id="customLoopLength" placeholder="Optional" style="width: 80px;" title="Set this to force a specific distance to loop over."> </div> <button onclick="generateGif()" id="generateBtn" style="margin-top: 15px;">Generate GIF</button> <div id="statusMessage">Status: Ready</div> <div id="progressFill"></div> </div> <div class="preview-container" id="imageContainer"> <img id="uploadedImage" src="" alt="Uploaded Image" style="max-width: none; max-height: none;"> </div> <div id="gifOutput"> <h3>Generated GIF:</h3> <img id="generatedGif" src=""> <a id="downloadLink" download="scroll_gif.gif">Download GIF</a> </div> <script> // --- GLOBAL ELEMENTS & UTILITIES --- const logEl = document.getElementById('log'); const imageElement = document.getElementById('uploadedImage'); const gifFramesInput = document.getElementById('gifFrames'); const frameDelayInput = document.getElementById('frameDelay'); const scrollDirectionInput = document.getElementById('scrollDirection'); const customLoopLengthInput = document.getElementById('customLoopLength'); const visiblePortionWidthInput = document.getElementById('visiblePortionWidth'); const visiblePortionHeightInput = document.getElementById('visiblePortionHeight'); const generateBtn = document.getElementById('generateBtn'); let isGenerating = false; let imageWidth = 0; let imageHeight = 0; function log(...args) { const msg = '[APP] ' + args.join(' '); console.log(msg); logEl.textContent += msg + '\n'; logEl.scrollTop = logEl.scrollHeight; } function showGifStatus(message, type = 'info') { const statusEl = document.getElementById('statusMessage'); statusEl.textContent = `Status: ${message}`; statusEl.style.color = type === 'error' ? 'red' : type === 'success' ? 'green' : 'black'; } function updateProgress(percent) { document.getElementById('progressFill').style.width = `${percent}%`; } // --- INITIAL CHECKS --- log("DOM Debug: Script start. Checking GIF library status..."); if (typeof GIF === 'function') { log("✅ PATH CHECK: 'gif.js' library loaded successfully (GIF constructor found)."); } else { log("❌ PATH CHECK: 'gif.js' failed to load!", 'error'); } // --- IMAGE LOADING & PREVIEW SETUP --- document.getElementById('imageUpload').addEventListener('change', function(event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { imageElement.src = e.target.result; imageElement.style.display = 'block'; }; reader.readAsDataURL(file); imageElement.onload = function() { imageWidth = imageElement.naturalWidth; imageHeight = imageElement.naturalHeight; log(`DOM Debug: Image loaded. Dimensions: ${imageWidth}x${imageHeight}.`); // Set initial preview size based on user input for visual consistency const w = parseInt(visiblePortionWidthInput.value) || 300; const h = parseInt(visiblePortionHeightInput.value) || 300; document.getElementById('imageContainer').style.width = `${w}px`; document.getElementById('imageContainer').style.height = `${h}px`; // Simple logic to scale image to fit one dimension of the container if (imageWidth / imageHeight > w / h) { // Image is wider than container aspect ratio imageElement.style.width = 'auto'; imageElement.style.height = `${h}px`; } else { // Image is taller than container aspect ratio imageElement.style.width = `${w}px`; imageElement.style.height = 'auto'; } }; } }); // --- GIF GENERATION FUNCTION (With Frame Size and Seamless Loop Fix) --- async function generateGif() { log("DOM Debug: generateGif() called."); if (isGenerating) return; if (typeof GIF !== 'function') { showGifStatus('FATAL ERROR: GIF library is missing!', 'error'); return; } if (!imageElement.src || imageWidth === 0) { showGifStatus('Please upload an image first!', 'error'); return; } isGenerating = true; generateBtn.disabled = true; document.getElementById('generatedGif').style.display = 'none'; document.getElementById('downloadLink').style.display = 'none'; showGifStatus('Loading image and preparing GIF...', 'info'); try { const totalFrames = parseInt(gifFramesInput.value) || 30; const delay = parseInt(frameDelayInput.value) || 40; const isVertical = scrollDirectionInput.value === 'vertical'; // 🔥 Use user-defined visible dimensions for canvas and GIF size (W and H) const w = parseInt(visiblePortionWidthInput.value) || 300; const h = parseInt(visiblePortionHeightInput.value) || 300; // 1. Create Image Object for Drawing const img = new Image(); img.crossOrigin = "anonymous"; await new Promise((resolve) => { img.onload = resolve; img.src = imageElement.src; }); // 2. Calculate Draw Dimensions and Loop Length let drawW, drawH, naturalLoopLength; const customLength = parseInt(customLoopLengthInput.value); // Calculate the scaled dimensions of the source image to fit the new W/H frame if (isVertical) { drawW = w; drawH = (img.height / img.width) * w; naturalLoopLength = drawH; } else { drawH = h; drawW = (img.width / img.height) * h; naturalLoopLength = drawW; } // Determine the final loop length: use custom input if valid, otherwise use natural length. const loopLength = (customLength && customLength > 0) ? customLength : naturalLoopLength; // 🎯 FIX: Calculate the EXACT speed needed for a perfect loop const perfectScrollSpeed = loopLength / totalFrames; log(`DOM Debug: Calculated perfect scroll speed: ${perfectScrollSpeed.toFixed(4)} px/frame. Loop Length Used: ${loopLength.toFixed(2)}px. Output Frame: ${w}x${h}`); const canvas = document.createElement('canvas'); canvas.width = w; // Use new W canvas.height = h; // Use new H const ctx = canvas.getContext('2d'); // 3. Worker path setup const workerPath = 'https://alceawis.de/other/extra/scripts/libraries/gifjs/gif.worker.js'; log(`DOM Debug: Setting GIF worker path to: ${workerPath}.`); showGifStatus('Initializing GIF encoder...', 'info'); updateProgress(5); const gif = new GIF({ workers: 2, quality: 10, width: w, // Use new W height: h, // Use new H workerScript: workerPath }); // 4. FRAME GENERATION LOOP showGifStatus(`Generating ${totalFrames} frames with perfect loop...`, 'info'); for (let i = 0; i < totalFrames; i++) { ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, w, h); // Calculate absolute position based on index (i) and perfect speed let pos = i * perfectScrollSpeed; if (isVertical) { // Draw image 1 (scrolled position) ctx.drawImage(img, 0, -pos, drawW, drawH); // Draw image 2 (wrapping copy: placed exactly one loop-length ahead) ctx.drawImage(img, 0, -pos + loopLength, drawW, drawH); } else { // Horizontal scroll ctx.drawImage(img, -pos, 0, drawW, drawH); ctx.drawImage(img, -pos + loopLength, 0, drawW, drawH); } gif.addFrame(ctx, {copy: true, delay: delay}); const progress = 5 + Math.round((i + 1) / totalFrames * 45); updateProgress(progress); } // 5. RENDERING showGifStatus('Rendering GIF... (0%)', 'info'); updateProgress(50); gif.on('progress', function(p) { const percent = 50 + Math.round(p * 50); updateProgress(percent); showGifStatus(`Rendering GIF... ${Math.round(p * 100)}%`, 'info'); }); gif.on('finished', function(blob) { log("DOM Debug: GIF finished rendering!"); const url = URL.createObjectURL(blob); const gifImg = document.getElementById('generatedGif'); const downloadLink = document.getElementById('downloadLink'); gifImg.src = url; gifImg.style.display = 'block'; downloadLink.href = url; downloadLink.style.display = 'inline-block'; updateProgress(100); showGifStatus(`GIF created successfully!`, 'success'); isGenerating = false; generateBtn.disabled = false; }); gif.on('abort', function(err) { log('Error: GIF generation aborted/failed.', err); showGifStatus('GIF generation failed. (Check console for worker error)', 'error'); isGenerating = false; generateBtn.disabled = false; updateProgress(0); }); gif.render(); log("DOM Debug: Started GIF rendering process."); } catch (err) { log('Error: GIF Generation Error:', err); showGifStatus(`Fatal Error: ${err.message}`, 'error'); updateProgress(0); isGenerating = false; generateBtn.disabled = false; } } </script> </body> </html> FFMPEG EXcludor ----------------- <a target="_blank" href="https://alceawis.de/other/extra/scripts/ffmpeg/ffmpeg-exclude.html?exclude_0=11.70-26.50&exclude_1=49.00-59.30&exclude_2=88.10-115.90" style=color:blue>Demo</a><hr><br> <script src="/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ // Directly use the absolute path to your navbar var navbarUrl = "/other/extra/scripts/ffmpeg/list.html"; $("#ffnavbar").load(navbarUrl) .fail(function() { console.error("Failed to load navbar from: " + navbarUrl); $("#ffnavbar").html("<p>Navigation failed to load. Please check console for errors.</p>"); }); }); </script> <div class="formClass"> <div id="ffnavbar"> <p>Loading navigation...</p> </div> </div> <script> document.addEventListener("DOMContentLoaded", function () { document.getElementById("loadFFmpegBtn").click(); }); </script> <script> document.addEventListener("DOMContentLoaded", function () { document.getElementById("loadFFmpegBtn").click(); }); </script> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Exclusion - ffmpeg.wasm</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: auto; padding: 20px; } input[type=range] { width: 100%; } label { display: block; margin-top: 10px; } video { max-width: 100%; margin-top: 10px; } pre#debugLog { background: #111; color: #0f0; padding: 10px; overflow: auto; max-height: 200px; font-size: 14px; white-space: pre-wrap; word-wrap: break-word; } #progress, #ffmpegProgress { width: 100%; height: 16px; } button { margin-top: 10px; } #message { margin-top: 10px; font-weight: bold; } .error { color: red; } .success { color: green; } .tab { overflow: hidden; border: 1px solid #ccc; background-color: #f1f1f1; border-radius: 4px 4px 0 0; } .tab button { background-color: inherit; float: left; border: none; outline: none; cursor: pointer; padding: 10px 16px; transition: 0.3s; margin-top: 0; } .tab button:hover { background-color: #ddd; } .tab button.active { background-color: #ccc; } .tabcontent { display: none; padding: 15px; border: 1px solid #ccc; border-top: none; border-radius: 0 0 4px 4px; } .tabcontent.active { display: block; } #fileInfo { margin: 10px 0; font-style: italic; } .exclusion { background-color: #f9f9f9; border: 1px solid #ddd; padding: 10px; margin: 10px 0; border-radius: 4px; } .exclusion-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .exclusion-controls { display: flex; gap: 10px; } .exclusion button { margin: 0; } .exclusion-range { display: flex; gap: 10px; align-items: center; } .exclusion-range input { flex: 1; } #addExclusionBtn { background-color: #4CAF50; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; } #addExclusionBtn:hover { background-color: #45a049; } .delete-btn { background-color: #f44336; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; } .delete-btn:hover { background-color: #d32f2f; } .preview-btn { background-color: #2196F3; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; } .preview-btn:hover { background-color: #0b7dda; } .performance-options { background-color: #e7f3ff; border: 1px solid #b3d9ff; padding: 10px; border-radius: 4px; margin: 10px 0; } .performance-note { font-size: 12px; color: #666; margin-top: 5px; } .share-url { background-color: #f0f0f0; border: 1px solid #ccc; padding: 10px; border-radius: 4px; margin: 10px 0; } .share-url textarea { width: 100%; height: 60px; font-family: monospace; font-size: 12px; resize: vertical; } .share-url-buttons { display: flex; gap: 10px; margin-top: 5px; } .copy-btn { background-color: #2196F3; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; } .copy-btn:hover { background-color: #0b7dda; } </style> </head> <body> <h2>Fast MP4 Trimmer with Exclusion</h2> <div class="tab"> <button class="tablinks active" onclick="openTab(event, 'urlTab')">From URL</button> <button class="tablinks" onclick="openTab(event, 'uploadTab')">Upload File</button> </div> <div id="urlTab" class="tabcontent active"> <input type="text" id="videoUrl" placeholder="Enter MP4 URL" size="60" /> <label><input type="checkbox" id="bypassCors" /> Bypass CORS via proxy</label> <button id="loadVideo">Load Video</button> </div> <div id="uploadTab" class="tabcontent"> <input type="file" id="fileUpload" accept="video/mp4" /> <div id="fileInfo"></div> </div> <div class="performance-options"> <h4>Performance Options</h4> <label> <input type="checkbox" id="useStreamCopy" checked /> Use fast stream copy (no re-encoding) - Much faster but may not work with all videos </label> <div class="performance-note"> If stream copy fails, uncheck this to use re-encoding (slower but more compatible) </div> </div> <button id="loadFFmpegBtn">Load ffmpeg.wasm</button> <div id="message"></div> <video id="inputVideo" controls></video> <h3>Exclusion Ranges</h3> <p>Define sections to exclude from the final video:</p> <div id="exclusionsContainer"> <!-- Exclusion ranges will be added here --> </div> <button id="addExclusionBtn">+ Add Exclusion</button> <div class="share-url"> <h4>Shareable URL</h4> <p>This URL contains your exclusion settings and can be shared:</p> <textarea id="shareUrl" readonly></textarea> <div class="share-url-buttons"> <button class="copy-btn" id="copyUrlBtn">Copy URL</button> <button class="copy-btn" id="loadFromUrlBtn">Load from Current URL</button> </div> </div> <button id="trimButton" disabled>Process Video</button> <progress id="progress" value="0" max="100"></progress> <h3>Processed Output</h3> <video id="outputVideo" controls></video> <h3>Debug Log</h3> <pre id="debugLog"></pre> <!-- Updated ffmpeg versions --> <script src="https://unpkg.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script> <script src="https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js"></script> <script> const logBox = document.getElementById('debugLog'); const message = document.getElementById('message'); const inputVideo = document.getElementById('inputVideo'); const outputVideo = document.getElementById('outputVideo'); const progress = document.getElementById('progress'); const loadFFmpegBtn = document.getElementById('loadFFmpegBtn'); const trimButton = document.getElementById('trimButton'); const fileUpload = document.getElementById('fileUpload'); const fileInfo = document.getElementById('fileInfo'); const exclusionsContainer = document.getElementById('exclusionsContainer'); const addExclusionBtn = document.getElementById('addExclusionBtn'); const useStreamCopy = document.getElementById('useStreamCopy'); const shareUrl = document.getElementById('shareUrl'); const copyUrlBtn = document.getElementById('copyUrlBtn'); const loadFromUrlBtn = document.getElementById('loadFromUrlBtn'); // Enhanced console logging ['log', 'error', 'warn', 'info'].forEach(method => { const original = console[method]; console[method] = function(...args) { const msg = args.map(a => { try { return (typeof a === 'object') ? JSON.stringify(a, null, 2) : String(a); } catch { return String(a); } }).join(' '); const time = new Date().toLocaleTimeString(); logBox.textContent += `[${time}] ${method.toUpperCase()}: ${msg}\n`; logBox.scrollTop = logBox.scrollHeight; original.apply(console, args); }; }); const { createFFmpeg, fetchFile } = FFmpeg; const ffmpeg = createFFmpeg({ log: true, corePath: 'https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js' }); let videoBlob = null; let videoDuration = 0; let ffmpegLoaded = false; let exclusions = []; function showMessage(text, isError = false) { message.textContent = text; message.className = isError ? 'error' : 'success'; } function openTab(evt, tabName) { const tabcontent = document.getElementsByClassName("tabcontent"); for (let i = 0; i < tabcontent.length; i++) { tabcontent[i].classList.remove("active"); } const tablinks = document.getElementsByClassName("tablinks"); for (let i = 0; i < tablinks.length; i++) { tablinks[i].classList.remove("active"); } document.getElementById(tabName).classList.add("active"); evt.currentTarget.classList.add("active"); } // Generate query string from exclusions function generateQueryString() { if (exclusions.length === 0) return ''; const params = new URLSearchParams(); exclusions.forEach((exclusion, index) => { params.append(`exclude_${index}`, `${exclusion.start.toFixed(2)}-${exclusion.end.toFixed(2)}`); }); return params.toString(); } // Parse query string to exclusions function parseQueryString(queryString) { const params = new URLSearchParams(queryString); const newExclusions = []; for (let [key, value] of params) { if (key.startsWith('exclude_')) { const [start, end] = value.split('-').map(parseFloat); if (!isNaN(start) && !isNaN(end) && start < end) { newExclusions.push({ start, end }); } } } // Sort by start time return newExclusions.sort((a, b) => a.start - b.start); } // Update share URL function updateShareUrl() { const queryString = generateQueryString(); const currentUrl = window.location.href.split('?')[0]; const newUrl = queryString ? `${currentUrl}?${queryString}` : currentUrl; shareUrl.value = newUrl; } function addExclusion(start = 0, end = 0, skipUrlUpdate = false) { const id = Date.now(); exclusions.push({ id, start, end }); const exclusionDiv = document.createElement('div'); exclusionDiv.className = 'exclusion'; exclusionDiv.id = `exclusion-${id}`; exclusionDiv.innerHTML = ` <div class="exclusion-header"> <h4>Exclusion Range</h4> <div class="exclusion-controls"> <button class="preview-btn" onclick="previewExclusion(${id})">Preview</button> <button class="delete-btn" onclick="removeExclusion(${id})">Delete</button> </div> </div> <div class="exclusion-range"> <label>Start: <span id="startLabel-${id}">${start.toFixed(2)}</span> sec</label> <input type="range" id="startSlider-${id}" min="0" max="${videoDuration.toFixed(2)}" value="${start}" step="0.1" /> </div> <div class="exclusion-range"> <label>End: <span id="endLabel-${id}">${end.toFixed(2)}</span> sec</label> <input type="range" id="endSlider-${id}" min="0" max="${videoDuration.toFixed(2)}" value="${end}" step="0.1" /> </div> `; exclusionsContainer.appendChild(exclusionDiv); // Set up event listeners for the new exclusion const startSlider = document.getElementById(`startSlider-${id}`); const endSlider = document.getElementById(`endSlider-${id}`); const startLabel = document.getElementById(`startLabel-${id}`); const endLabel = document.getElementById(`endLabel-${id}`); startSlider.addEventListener('input', () => { const startValue = parseFloat(startSlider.value); const endValue = parseFloat(endSlider.value); if (startValue > endValue) { startSlider.value = endValue; } startLabel.textContent = parseFloat(startSlider.value).toFixed(2); updateExclusion(id, parseFloat(startSlider.value), parseFloat(endSlider.value)); updateShareUrl(); }); endSlider.addEventListener('input', () => { const startValue = parseFloat(startSlider.value); const endValue = parseFloat(endSlider.value); if (endValue < startValue) { endSlider.value = startValue; } endLabel.textContent = parseFloat(endSlider.value).toFixed(2); updateExclusion(id, parseFloat(startSlider.value), parseFloat(endSlider.value)); updateShareUrl(); }); if (!skipUrlUpdate) { updateShareUrl(); } } function updateExclusion(id, start, end) { const index = exclusions.findIndex(e => e.id === id); if (index !== -1) { exclusions[index].start = start; exclusions[index].end = end; } } function removeExclusion(id) { exclusions = exclusions.filter(e => e.id !== id); const exclusionDiv = document.getElementById(`exclusion-${id}`); if (exclusionDiv) { exclusionDiv.remove(); } updateShareUrl(); } function previewExclusion(id) { const exclusion = exclusions.find(e => e.id === id); if (exclusion) { inputVideo.currentTime = exclusion.start; } } // Load exclusions from URL on page load function loadExclusionsFromUrl() { const queryString = window.location.search.substring(1); if (!queryString) return; const urlExclusions = parseQueryString(queryString); if (urlExclusions.length > 0) { // Clear existing exclusions exclusions = []; exclusionsContainer.innerHTML = ''; // Add exclusions from URL urlExclusions.forEach(exclusion => { addExclusion(exclusion.start, exclusion.end, true); }); showMessage(`Loaded ${urlExclusions.length} exclusion(s) from URL`); updateShareUrl(); } } async function loadFFmpeg() { if (ffmpegLoaded) { showMessage('ffmpeg.wasm already loaded'); return; } showMessage('Loading ffmpeg.wasm...'); loadFFmpegBtn.disabled = true; try { await ffmpeg.load(); ffmpegLoaded = true; showMessage('ffmpeg.wasm loaded successfully'); console.log("ffmpeg loaded:", ffmpeg); trimButton.disabled = !(ffmpegLoaded && videoBlob); } catch (err) { let errorMsg = `Error loading ffmpeg: ${err.message}`; // Add specific hint for COOP/COEP headers if (err.message.includes('SharedArrayBuffer') || err.message.includes('cross-origin isolated')) { errorMsg += `<br><br><strong>Server Configuration Required:</strong><br> For ffmpeg.wasm to work, your server needs these headers:<br> <code> Header set Cross-Origin-Opener-Policy "same-origin"<br> Header set Cross-Origin-Embedder-Policy "require-corp" </code><br><br> If you can't configure the server, try:<br> 1. Serving this page via localhost<br> 2. Using Chrome with <code>--disable-web-security</code> flag (for testing only)`; } showMessage(errorMsg, true); console.error("ffmpeg load error:", err); loadFFmpegBtn.disabled = false; } } function handleVideoFile(file) { if (!file) return; if (!file.type.startsWith('video/') && !file.name.endsWith('.mp4')) { showMessage("Please select an MP4 video file", true); return; } if (file.size > 100 * 1024 * 1024) { // 100MB limit showMessage("File is too large (max 100MB recommended)", true); return; } videoBlob = file; const objectURL = URL.createObjectURL(file); fileInfo.innerHTML = ` <strong>Selected file:</strong> ${file.name}<br> <strong>Size:</strong> ${(file.size / (1024 * 1024)).toFixed(2)}MB<br> <strong>Type:</strong> ${file.type || 'unknown'} `; inputVideo.onloadedmetadata = () => { videoDuration = inputVideo.duration; // Check if we have URL exclusions to load const hasUrlExclusions = window.location.search.includes('exclude_'); if (!hasUrlExclusions) { // Clear any existing exclusions only if no URL exclusions exclusions = []; exclusionsContainer.innerHTML = ''; // Add a default exclusion in the middle of the video const defaultStart = Math.max(0, videoDuration / 4); const defaultEnd = Math.min(videoDuration, videoDuration / 2); addExclusion(defaultStart, defaultEnd); } else { // Update sliders with correct duration for URL-loaded exclusions exclusions.forEach(exclusion => { const startSlider = document.getElementById(`startSlider-${exclusion.id}`); const endSlider = document.getElementById(`endSlider-${exclusion.id}`); if (startSlider && endSlider) { startSlider.max = videoDuration.toFixed(2); endSlider.max = videoDuration.toFixed(2); } }); } showMessage(`Video loaded (${(file.size / (1024 * 1024)).toFixed(2)}MB, ${videoDuration.toFixed(2)}s)`); trimButton.disabled = !(ffmpegLoaded && videoBlob); console.log("Video metadata loaded"); }; inputVideo.onerror = () => { showMessage("Error loading video file", true); videoBlob = null; trimButton.disabled = true; }; inputVideo.src = objectURL; } async function loadVideo() { const url = document.getElementById('videoUrl').value.trim(); const useProxy = document.getElementById('bypassCors').checked; if (!url) { showMessage("Please enter a video URL", true); return; } showMessage('Fetching video...'); try { const proxyUrl = useProxy ? `https://cors-anywhere.herokuapp.com/${url}` : url; console.log(`Fetching from: ${proxyUrl}`); const response = await fetch(proxyUrl, { headers: useProxy ? { 'X-Requested-With': 'XMLHttpRequest' } : {} }); if (!response.ok) { throw new Error(`HTTP ${response.status} - ${response.statusText}`); } const contentLength = response.headers.get('Content-Length'); if (contentLength && parseInt(contentLength) > 50 * 1024 * 1024) { throw new Error("Video is too large (max ~50MB recommended)"); } videoBlob = await response.blob(); // Validate it's actually a video file if (!videoBlob.type.startsWith('video/')) { throw new Error("The URL doesn't point to a valid video file"); } const objectURL = URL.createObjectURL(videoBlob); await new Promise((resolve, reject) => { inputVideo.onloadedmetadata = () => { videoDuration = inputVideo.duration; // Check if we have URL exclusions to load const hasUrlExclusions = window.location.search.includes('exclude_'); if (!hasUrlExclusions) { // Clear any existing exclusions only if no URL exclusions exclusions = []; exclusionsContainer.innerHTML = ''; // Add a default exclusion in the middle of the video const defaultStart = Math.max(0, videoDuration / 4); const defaultEnd = Math.min(videoDuration, videoDuration / 2); addExclusion(defaultStart, defaultEnd); } else { // Update sliders with correct duration for URL-loaded exclusions exclusions.forEach(exclusion => { const startSlider = document.getElementById(`startSlider-${exclusion.id}`); const endSlider = document.getElementById(`endSlider-${exclusion.id}`); if (startSlider && endSlider) { startSlider.max = videoDuration.toFixed(2); endSlider.max = videoDuration.toFixed(2); } }); } showMessage(`Video loaded (${(videoBlob.size / (1024 * 1024)).toFixed(2)}MB, ${videoDuration.toFixed(2)}s)`); trimButton.disabled = !(ffmpegLoaded && videoBlob); console.log("Video metadata loaded"); resolve(); }; inputVideo.onerror = () => { reject(new Error("Video playback error")); }; inputVideo.src = objectURL; }); } catch (err) { showMessage(`Error: ${err.message}`, true); console.error("Video load error:", err); videoBlob = null; trimButton.disabled = true; } } fileUpload.addEventListener('change', (e) => { const file = e.target.files[0]; handleVideoFile(file); }); addExclusionBtn.addEventListener('click', () => { // Add a new exclusion at the end of the video (but not beyond) const lastEnd = exclusions.length > 0 ? Math.max(...exclusions.map(e => e.end)) : 0; const newStart = Math.min(videoDuration, lastEnd + 1); const newEnd = Math.min(videoDuration, newStart + 1); addExclusion(newStart, newEnd); }); copyUrlBtn.addEventListener('click', () => { shareUrl.select(); document.execCommand('copy'); showMessage('URL copied to clipboard!'); }); loadFromUrlBtn.addEventListener('click', () => { loadExclusionsFromUrl(); }); loadFFmpegBtn.onclick = loadFFmpeg; document.getElementById('loadVideo').onclick = loadVideo; document.getElementById('trimButton').onclick = async () => { if (!videoBlob) { showMessage("No video loaded", true); return; } // Sort exclusions by start time const sortedExclusions = [...exclusions].sort((a, b) => a.start - b.start); // Validate exclusions for (let i = 0; i < sortedExclusions.length; i++) { const exclusion = sortedExclusions[i]; if (exclusion.end <= exclusion.start) { showMessage(`Exclusion ${i+1} has invalid range (end must be after start)`, true); return; } if (i > 0 && exclusion.start < sortedExclusions[i-1].end) { showMessage(`Exclusion ${i+1} overlaps with previous exclusion`, true); return; } } try { showMessage('Starting video processing...'); progress.value = 0; console.log(`Writing file to MEMFS`); const fileData = videoBlob instanceof File ? await fetchFile(videoBlob) : new Uint8Array(await videoBlob.arrayBuffer()); await ffmpeg.FS('writeFile', 'input.mp4', fileData); ffmpeg.setProgress(({ ratio }) => { progress.value = Math.round(ratio * 100); showMessage(`Processing... ${progress.value}%`); }); // Calculate the segments to keep (inverse of exclusions) const segments = []; let lastEnd = 0; sortedExclusions.forEach(exclusion => { if (exclusion.start > lastEnd) { segments.push({ start: lastEnd, end: exclusion.start }); } lastEnd = exclusion.end; }); // Add the final segment after the last exclusion if (lastEnd < videoDuration) { segments.push({ start: lastEnd, end: videoDuration }); } console.log(`Processing ${segments.length} segments`); if (segments.length === 0) { showMessage("No content remains after exclusions", true); return; } // Try fast method first (stream copy) if (useStreamCopy.checked) { console.log("Attempting fast stream copy method"); if (segments.length === 1) { // Single segment - simple trim with stream copy await ffmpeg.run( '-i', 'input.mp4', '-ss', segments[0].start.toFixed(2), '-to', segments[0].end.toFixed(2), '-c', 'copy', '-avoid_negative_ts', 'make_zero', 'output.mp4' ); } else { // Multiple segments - use concat demuxer // Create individual segment files const segmentFiles = []; for (let i = 0; i < segments.length; i++) { const segment = segments[i]; const segmentName = `segment${i}.mp4`; await ffmpeg.run( '-i', 'input.mp4', '-ss', segment.start.toFixed(2), '-to', segment.end.toFixed(2), '-c', 'copy', '-avoid_negative_ts', 'make_zero', segmentName ); segmentFiles.push(segmentName); } // Create concat file list let concatContent = ''; segmentFiles.forEach(file => { concatContent += `file '${file}'\n`; }); await ffmpeg.FS('writeFile', 'concat.txt', new TextEncoder().encode(concatContent)); // Concatenate segments await ffmpeg.run( '-f', 'concat', '-safe', '0', '-i', 'concat.txt', '-c', 'copy', 'output.mp4' ); // Clean up segment files segmentFiles.forEach(file => { try { ffmpeg.FS('unlink', file); } catch(e) {} }); try { ffmpeg.FS('unlink', 'concat.txt'); } catch(e) {} } } else { // Use re-encoding method (slower but more compatible) console.log("Using re-encoding method"); // Build the complex filter for excluding sections let filterComplex = ''; let outputCount = 0; // Add segments to keep segments.forEach((segment, index) => { filterComplex += `[0:v]trim=start=${segment.start}:end=${segment.end},setpts=PTS-STARTPTS[v${index}];`; filterComplex += `[0:a]atrim=start=${segment.start}:end=${segment.end},asetpts=PTS-STARTPTS[a${index}];`; outputCount++; }); // Concatenate all segments let concatInputs = ''; for (let i = 0; i < outputCount; i++) { concatInputs += `[v${i}][a${i}]`; } filterComplex += `${concatInputs}concat=n=${outputCount}:v=1:a=1[outv][outa]`; console.log(`Filter complex: ${filterComplex}`); await ffmpeg.run( '-i', 'input.mp4', '-filter_complex', filterComplex, '-map', '[outv]', '-map', '[outa]', '-c:v', 'libx264', '-preset', 'fast', '-crf', '23', '-c:a', 'aac', '-b:a', '128k', '-y', 'output.mp4' ); } console.log(`Reading output file`); const data = ffmpeg.FS('readFile', 'output.mp4'); console.log(`Creating blob (size: ${data.length} bytes)`); const processedBlob = new Blob([data.buffer], { type: 'video/mp4' }); outputVideo.src = URL.createObjectURL(processedBlob); outputVideo.load(); showMessage(`Processing complete! Output: ${(data.length / (1024 * 1024)).toFixed(2)}MB`); console.log("Process completed successfully"); // Clean up files from MEMFS try { ffmpeg.FS('unlink', 'input.mp4'); ffmpeg.FS('unlink', 'output.mp4'); } catch (cleanupErr) { console.warn("Cleanup error:", cleanupErr); } } catch (err) { showMessage(`Error during processing: ${err.message}`, true); console.error("Processing error:", err); // If stream copy failed, suggest trying re-encoding if (useStreamCopy.checked) { showMessage(`Stream copy failed. Try unchecking "Use fast stream copy" for better compatibility.`, true); } } }; // Initialize on page load document.addEventListener('DOMContentLoaded', function() { // Load exclusions from URL if present loadExclusionsFromUrl(); // Initialize share URL updateShareUrl(); }); </script> </body> </html> HTML2SRC --------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Text to HTML Converter with Styling Options</title> <style> *{box-sizing:border-box;font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif}body{margin:0;padding:20px;background-color:#f5f5f5;color:#333;line-height:1.6}.container{max-width:1200px;margin:0 auto;background-color:white;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.1);padding:20px}h1,h2,h3{color:#2c3e50;margin-top:0}h1{text-align:center;margin-bottom:30px;padding-bottom:10px;border-bottom:1px solid #eee}.editor-container,.output-container{margin-bottom:20px}#editor{border:1px solid #ccc;padding:15px;min-height:200px;background-color:#fff;border-radius:4px;margin-bottom:15px;overflow:auto}.controls{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:15px;padding:15px;background-color:#f9f9f9;border-radius:4px}.control-group{display:flex;flex-direction:column;flex:1;min-width:200px}.control-group h4{margin:0 0 10px 0;font-size:14px;color:#555}.checkbox-group{display:flex;flex-wrap:wrap;gap:10px}.checkbox-item{display:flex;align-items:center;gap:5px}button{background-color:#3498db;color:white;border:none;padding:10px 15px;border-radius:4px;cursor:pointer;font-size:16px;transition:background-color 0.3s}button:hover{background-color:#2980b9}#output{width:100%;height:300px;border:1px solid #ccc;padding:15px;background-color:#f9f9f9;white-space:pre-wrap;font-family:monospace;border-radius:4px;overflow:auto}.action-buttons{display:flex;gap:10px;margin-top:15px}.action-buttons button{flex:1}.clear-btn{background-color:#e74c3c}.clear-btn:hover{background-color:#c0392b}.copy-btn{background-color:#2ecc71}.copy-btn:hover{background-color:#27ae60}.info{background-color:#e8f4fd;padding:10px 15px;border-radius:4px;margin-bottom:20px;border-left:4px solid #3498db}.warning{background-color:#fff8e1;padding:10px 15px;border-radius:4px;margin-bottom:20px;border-left:4px solid #ffc107;font-size:14px} </style> </head> <body> <div class="container"> <h1>Text to HTML Converter with Styling Options</h1> <div class="info"> <p>Paste your styled content (like text from Word or Google Docs) into the box below. Use the checkboxes to select which styling elements to keep in the output HTML, then click "Convert".</p> </div> <div class="editor-container"> <h2>Paste Your Styled Content</h2> <div id="editor" contenteditable="true"></div> <div class="controls"> <div class="control-group"> <h4>Keep in Output:</h4> <div class="checkbox-group"> <div class="checkbox-item"> <input type="checkbox" id="keep-bold" checked> <label for="keep-bold">Bold Text</label> </div> <div class="checkbox-item"> <input type="checkbox" id="keep-italic" checked> <label for="keep-italic">Italic Text</label> </div> <div class="checkbox-item"> <input type="checkbox" id="keep-underline" checked> <label for="keep-underline">Underlined Text</label> </div> <div class="checkbox-item"> <input type="checkbox" id="keep-fonts" checked> <label for="keep-fonts">Font Styles</label> </div> <div class="checkbox-item"> <input type="checkbox" id="keep-colors" checked> <label for="keep-colors">Colors</label> </div> <div class="checkbox-item"> <input type="checkbox" id="keep-margins" checked> <label for="keep-margins">Margins & Padding</label> </div> <div class="checkbox-item"> <input type="checkbox" id="keep-links" checked> <label for="keep-links">Links (a href)</label> </div> <div class="checkbox-item"> <input type="checkbox" id="keep-only-selected"> <label for="keep-only-selected">Keep Only Selected Elements</label> </div> </div> </div> <div class="control-group"> <h4>Quick Actions:</h4> <div class="action-buttons"> <button id="select-all">Select All</button> <button id="deselect-all">Deselect All</button> </div> </div> </div> <div class="warning"> <strong>Note:</strong> When "Keep Only Selected Elements" is checked, only the elements you've selected will be kept in the output. All other text and elements will be removed. </div> <button id="convert-btn">Convert to HTML</button> </div> <div class="output-container"> <h2>Generated HTML Code</h2> <textarea id="output" readonly></textarea> <div class="action-buttons"> <button id="copy-btn" class="copy-btn">Copy HTML</button> <button id="clear-btn" class="clear-btn">Clear All</button> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { const editor = document.getElementById('editor'); const output = document.getElementById('output'); const convertBtn = document.getElementById('convert-btn'); const copyBtn = document.getElementById('copy-btn'); const clearBtn = document.getElementById('clear-btn'); const selectAllBtn = document.getElementById('select-all'); const deselectAllBtn = document.getElementById('deselect-all'); const keepBold = document.getElementById('keep-bold'); const keepItalic = document.getElementById('keep-italic'); const keepUnderline = document.getElementById('keep-underline'); const keepFonts = document.getElementById('keep-fonts'); const keepColors = document.getElementById('keep-colors'); const keepMargins = document.getElementById('keep-margins'); const keepLinks = document.getElementById('keep-links'); const keepOnlySelected = document.getElementById('keep-only-selected'); convertBtn.addEventListener('click', function() { let editorContent = editor.innerHTML; // If nothing is checked and "Keep Only Selected Elements" is not checked, keep everything intact const nothingChecked = !keepBold.checked && !keepItalic.checked && !keepUnderline.checked && !keepFonts.checked && !keepColors.checked && !keepMargins.checked && !keepLinks.checked; if (keepOnlySelected.checked) { const tempDiv = document.createElement('div'); tempDiv.innerHTML = editorContent; function removeUnwantedElements(element) { for (let i = element.childNodes.length - 1; i >= 0; i--) { const node = element.childNodes[i]; if (node.nodeType === Node.ELEMENT_NODE) { const shouldKeep = ( (keepBold.checked && (node.tagName === 'B' || node.tagName === 'STRONG')) || (keepItalic.checked && (node.tagName === 'I' || node.tagName === 'EM')) || (keepUnderline.checked && node.tagName === 'U') || (keepLinks.checked && node.tagName === 'A') || (keepFonts.checked && node.style && ( node.style.fontFamily || node.style.fontSize || node.style.fontWeight )) || (keepColors.checked && node.style && ( node.style.color || node.style.backgroundColor )) || (keepMargins.checked && node.style && ( node.style.margin || node.style.padding )) ); if (shouldKeep) { removeUnwantedElements(node); } else { const textContent = node.textContent; const textNode = document.createTextNode(textContent); element.replaceChild(textNode, node); } } } } removeUnwantedElements(tempDiv); editorContent = tempDiv.innerHTML; } else if (nothingChecked) { // If nothing is checked and not in "Keep Only Selected" mode, keep everything as is // No processing needed, just use the original content } else { if (!keepBold.checked) { editorContent = editorContent.replace(/<strong[^>]*>(.*?)<\/strong>/gi, '$1'); editorContent = editorContent.replace(/<b[^>]*>(.*?)<\/b>/gi, '$1'); } if (!keepItalic.checked) { editorContent = editorContent.replace(/<em[^>]*>(.*?)<\/em>/gi, '$1'); editorContent = editorContent.replace(/<i[^>]*>(.*?)<\/i>/gi, '$1'); } if (!keepUnderline.checked) { editorContent = editorContent.replace(/<u[^>]*>(.*?)<\/u>/gi, '$1'); editorContent = editorContent.replace(/style="[^"]*text-decoration:\s*underline[^"]*"/gi, ''); } if (!keepFonts.checked) { editorContent = editorContent.replace(/style="[^"]*font-[^:]*:[^;"]*[;"]/gi, ''); editorContent = editorContent.replace(/<font[^>]*>(.*?)<\/font>/gi, '$1'); } if (!keepColors.checked) { editorContent = editorContent.replace(/style="[^"]*color:[^;"]*[;"]/gi, ''); editorContent = editorContent.replace(/style="[^"]*background-color:[^;"]*[;"]/gi, ''); } if (!keepMargins.checked) { editorContent = editorContent.replace(/style="[^"]*margin[^:]*:[^;"]*[;"]/gi, ''); editorContent = editorContent.replace(/style="[^"]*padding[^:]*:[^;"]*[;"]/gi, ''); } if (!keepLinks.checked) { editorContent = editorContent.replace(/<a\b[^>]*>(.*?)<\/a>/gi, '$1'); } } editorContent = editorContent.replace(/style=""/g, ''); editorContent = editorContent.replace(/<[^>]*style="[^"]*"\s*>/g, function(match) { return match.replace(/style="[^"]*"/, ''); }); output.value = editorContent; }); copyBtn.addEventListener('click', function() { output.select(); document.execCommand('copy'); alert('HTML copied to clipboard!'); }); clearBtn.addEventListener('click', function() { editor.innerHTML = 'Paste styled text here...'; output.value = ''; }); selectAllBtn.addEventListener('click', function() { keepBold.checked = true; keepItalic.checked = true; keepUnderline.checked = true; keepFonts.checked = true; keepColors.checked = true; keepMargins.checked = true; keepLinks.checked = true; keepOnlySelected.checked = false; }); deselectAllBtn.addEventListener('click', function() { keepBold.checked = false; keepItalic.checked = false; keepUnderline.checked = false; keepFonts.checked = false; keepColors.checked = false; keepMargins.checked = false; keepLinks.checked = false; keepOnlySelected.checked = false; }); editor.addEventListener('focus', function() { if (editor.innerHTML === 'Paste styled text here...') { editor.innerHTML = ''; } }); }); </script> </body> </html> MMD Photostudio and Poser (extension to SampleWebMMD) ---------------------------- photostudio.html -- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>PMX + VPD Viewer</title> <script src="./libs/three.js"></script> <script src="./libs/mmdparser.min.js"></script> <script src="./libs/MMDAnimationHelper.js"></script> <script src="./libs/CCDIKSolver.js"></script> <script src="./libs/ammo.min.js"></script> <script src="./libs/TGALoader.js"></script> <script src="./libs/MMDLoader.js"></script> <style> *{margin:0;padding:0;box-sizing:border-box;touch-action:none} body{overflow:hidden;background:#1a1a2e;color:white;font-family:system-ui;height:100vh;width:100vw} #container{position:fixed;top:0;left:0;width:100%;height:100%;z-index:1} #loading{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.7);padding:20px 30px;border-radius:10px;z-index:10;text-align:center;font-size:18px} #info{position:fixed;top:10px;left:10px;background:rgba(0,0,0,0.7);padding:12px;border-radius:10px;z-index:5;max-width:90%;font-size:14px;line-height:1.4} #info h1{font-size:16px;margin-bottom:8px} #controls{position:fixed;bottom:20px;left:0;width:100%;display:flex;justify-content:center;gap:15px;z-index:5;padding:0 20px} .control-btn{background:rgba(30,30,60,0.8);border:2px solid #4a4a8a;color:white;padding:12px 20px;border-radius:50px;font-size:16px;font-weight:600;cursor:pointer;box-shadow:0 4px 8px rgba(0,0,0,0.3);transition:all 0.2s;min-width:140px} .control-btn:active{background:rgba(60,60,100,0.9);transform:scale(0.95)} .control-btn.active{background:rgba(80,80,160,0.9);border-color:#8a8aff} #model-selector{position:fixed;top:80px;right:10px;background:rgba(0,0,0,0.7);padding:10px;border-radius:10px;z-index:5;display:flex;flex-direction:column;gap:8px} .model-btn{background:rgba(40,40,80,0.8);border:1px solid #5a5aaa;color:white;padding:8px 12px;border-radius:6px;font-size:14px;cursor:pointer} .model-btn.active{background:rgba(80,80,160,0.9);border-color:#8a8aff} #help-overlay{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.9);padding:20px;border-radius:10px;z-index:20;max-width:90%;max-height:80%;overflow-y:auto;display:none} #help-overlay h2{margin-bottom:15px;text-align:center} .help-section{margin-bottom:15px} .help-section h3{margin-bottom:8px;color:#8a8aff} .help-key{display:inline-block;background:rgba(255,255,255,0.2);padding:2px 6px;border-radius:4px;margin:0 2px;font-family:monospace} #close-help{display:block;margin:15px auto 0;padding:8px 20px;background:#4a4a8a;border:none;border-radius:5px;color:white;cursor:pointer} #mode-indicator{position:fixed;top:10px;right:60px;background:rgba(0,0,0,0.7);padding:8px 12px;border-radius:6px;z-index:5;font-size:14px} #bone-controls{position:fixed;top:150px;left:10px;background:rgba(0,0,0,0.7);padding:10px;border-radius:10px;z-index:5;display:none;flex-direction:column;gap:8px;max-width:200px} .bone-section{margin-bottom:10px} .bone-section h4{margin-bottom:5px;color:#8a8aff;font-size:12px} .bone-btn{background:rgba(60,60,100,0.8);border:1px solid #6a6aaa;color:white;padding:4px 8px;border-radius:4px;font-size:12px;cursor:pointer;margin:2px;width:calc(50% - 4px);display:inline-block;text-align:center} .bone-btn.active{background:rgba(100,100,200,0.9);border-color:#8a8aff} #selected-bone-info{position:fixed;top:50%;left:10px;background:rgba(0,0,0,0.8);padding:10px;border-radius:10px;z-index:5;font-size:12px;display:none} #bone-transform-controls{position:fixed;bottom:120px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);padding:10px;border-radius:10px;z-index:5;display:none;gap:10px} .transform-btn{background:rgba(60,60,100,0.8);border:1px solid #6a6aaa;color:white;padding:8px 12px;border-radius:6px;font-size:14px;cursor:pointer;min-width:80px} .transform-btn.active{background:rgba(100,100,200,0.9);border-color:#8a8aff} #gesture-help{position:fixed;bottom:180px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);padding:8px 12px;border-radius:6px;z-index:5;font-size:12px;text-align:center;display:none} #stored-pose-controls{position:fixed;top:80px;left:10px;background:rgba(0,0,0,0.7);padding:10px;border-radius:10px;z-index:5;display:flex;flex-direction:column;gap:8px;max-width:200px} .stored-pose-section{margin-bottom:10px} .stored-pose-section h4{margin-bottom:5px;color:#8a8aff;font-size:12px} .stored-pose-btn{background:rgba(60,100,60,0.8);border:1px solid #6aaa6a;color:white;padding:6px 10px;border-radius:4px;font-size:12px;cursor:pointer;margin:2px;width:100%;text-align:center} .stored-pose-btn:active{background:rgba(80,140,80,0.9);transform:scale(0.95)} #state-controls{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);padding:10px;border-radius:10px;z-index:5;display:flex;flex-direction:column;gap:10px;width:90%;max-width:600px} .state-btn{background:rgba(100,60,100,0.8);border:1px solid #8a6a8a;color:white;padding:8px 12px;border-radius:6px;font-size:14px;cursor:pointer;min-width:100px} .state-btn:active{background:rgba(140,80,140,0.9);transform:scale(0.95)} #state-textbox{background:rgba(0,0,0,0.8);border:1px solid #4a4a8a;border-radius:6px;color:white;padding:8px;font-size:12px;font-family:monospace;width:100%;min-height:60px;resize:vertical;display:none} #help-btn{position:fixed;top:10px;right:10px;background:rgba(30,30,60,0.8);border:2px solid #4a4a8a;color:white;padding:8px 12px;border-radius:50%;width:40px;height:40px;cursor:pointer;z-index:5;font-size:18px;display:flex;align-items:center;justify-content:center} .bone-helper{cursor:pointer;transition:all 0.2s} .bone-helper:hover{transform:scale(1.2)} #mobile-gesture-info{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.9);padding:20px;border-radius:10px;z-index:15;text-align:center;display:none;max-width:90%} .gesture-item{margin:10px 0;padding:10px;background:rgba(255,255,255,0.1);border-radius:5px} @media (max-width:480px){#info{font-size:12px;padding:10px}#info h1{font-size:14px}.control-btn{padding:10px 15px;font-size:14px;min-width:120px}#mode-indicator{top:60px;right:10px}#bone-controls{top:120px;max-width:180px}#bone-transform-controls{bottom:100px;flex-wrap:wrap;justify-content:center}.transform-btn{padding:6px 10px;font-size:12px;min-width:70px}#gesture-help{bottom:160px;font-size:11px}#stored-pose-controls{top:60px;max-width:180px}#state-controls{bottom:70px;flex-wrap:wrap;justify-content:center}.state-btn{padding:6px 10px;font-size:12px;min-width:90px}#state-textbox{font-size:11px;min-height:50px}} </style> </head> <body> <div id="container"></div> <div id="loading">Loading models...</div> <div id="info"> <h1>PMX + VPD Viewer</h1> <p>Touch models to move them, empty space to rotate/camera</p> <p id="modelInfo"></p> </div> <div id="mode-indicator">Mode: Model Control</div> <button id="help-btn">?</button> <div id="selected-bone-info">No bone selected</div> <div id="mobile-gesture-info"> <h3>Mobile Gestures Guide</h3> <div class="gesture-item"><strong>Touch Model + Drag:</strong> Move model</div> <div class="gesture-item"><strong>Touch Empty Space + Drag:</strong> Rotate model (Model Mode) / Orbit camera (Camera Mode)</div> <div class="gesture-item"><strong>Two Finger Drag:</strong> Pan camera</div> <div class="gesture-item"><strong>Pinch:</strong> Zoom camera</div> <div class="gesture-item"><strong>Double Tap Bone:</strong> Select bone (Bone Mode)</div> <div class="gesture-item"><strong>Long Press + Drag Bone:</strong> Move bone (Bone Mode)</div> <button id="close-gesture-help" style="margin-top:15px;padding:8px 16px;background:#4a4a8a;color:white;border:none;border-radius:5px;cursor:pointer">Got it!</button> </div> <div id="state-controls"> <button id="generate-state" class="state-btn">Generate State URL</button> <textarea id="state-textbox" placeholder="Click 'Generate State URL' to create a shareable URL with current camera and model positions"></textarea> </div> <div id="stored-pose-controls"> <div class="stored-pose-section"> <h4>Stored Poses</h4> <button id="reload-stor1" class="stored-pose-btn">Reload Pose 1</button> <button id="reload-stor2" class="stored-pose-btn">Reload Pose 2</button> <button id="reload-both" class="stored-pose-btn">Reload Both</button> </div> </div> <div id="bone-controls"> <div class="bone-section"><h4>Head & Neck</h4><button class="bone-btn" data-bone="neck">Neck</button><button class="bone-btn" data-bone="head">Head</button></div> <div class="bone-section"><h4>Arms & Hands</h4><button class="bone-btn" data-bone="shoulder_l">Left Shoulder</button><button class="bone-btn" data-bone="shoulder_r">Right Shoulder</button><button class="bone-btn" data-bone="arm_l">Left Arm</button><button class="bone-btn" data-bone="arm_r">Right Arm</button><button class="bone-btn" data-bone="hand_l">Left Hand</button><button class="bone-btn" data-bone="hand_r">Right Hand</button></div> <div class="bone-section"><h4>Legs & Feet</h4><button class="bone-btn" data-bone="leg_l">Left Leg</button><button class="bone-btn" data-bone="leg_r">Right Leg</button><button class="bone-btn" data-bone="knee_l">Left Knee</button><button class="bone-btn" data-bone="knee_r">Right Knee</button><button class="bone-btn" data-bone="foot_l">Left Foot</button><button class="bone-btn" data-bone="foot_r">Right Foot</button></div> <div class="bone-section"><h4>Spine</h4><button class="bone-btn" data-bone="spine">Spine</button><button class="bone-btn" data-bone="waist">Waist</button></div> <button id="reset-bones" class="control-btn" style="margin-top:10px;min-width:auto">Reset Bones</button> </div> <div id="bone-transform-controls"> <button id="move-bone" class="transform-btn active">Move</button> <button id="rotate-bone" class="transform-btn">Rotate</button> <button id="reset-bone-transform" class="transform-btn">Reset</button> </div> <div id="gesture-help"><div>👆 Touch model to move, empty space to rotate</div></div> <div id="help-overlay"> <h2>Controls Guide</h2> <div class="help-section"><h3>Mobile Touch Controls</h3><p><strong>Touch Model + Drag:</strong> Move model</p><p><strong>Touch Empty Space + Drag:</strong> Rotate model (Model Mode) / Orbit camera (Camera Mode)</p><p><strong>Two Finger Drag:</strong> Pan camera</p><p><strong>Pinch:</strong> Zoom camera</p><p><strong>Double Tap Bone:</strong> Select bone (Bone Mode)</p><p><strong>Long Press + Drag Bone:</strong> Move bone (Bone Mode)</p></div> <div class="help-section"><h3>Control Modes</h3><p><span class="help-key">Model Mode</span> - Move and rotate models</p><p><span class="help-key">Camera Mode</span> - Control camera view</p><p><span class="help-key">Bone Mode</span> - Edit individual bone positions</p></div> <div class="help-section"><h3>Bone Editing (Bone Mode Only)</h3><p><span class="help-key">Tap Bone</span> - Select bone</p><p><span class="help-key">Long Press + Drag</span> - Move selected bone</p><p><span class="help-key">Two Finger Rotate</span> - Rotate selected bone</p></div> <div class="help-section"><h3>Stored Poses</h3><p><span class="help-key">Reload Pose 1</span> - Re-fetch and apply stor1 to Model 1</p><p><span class="help-key">Reload Pose 2</span> - Re-fetch and apply stor2 to Model 2</p><p><span class="help-key">Reload Both</span> - Re-fetch and apply both poses</p></div> <div class="help-section"><h3>State Management</h3><p><span class="help-key">Generate State URL</span> - Create URL with current scene state (camera position, model positions)</p><p><span class="help-key">Text Box</span> - Copy the generated URL manually to share your scene</p></div> <button id="close-help">Close</button> </div> <div id="model-selector"> <button class="model-btn active" data-model="both">Both Models</button> <button class="model-btn" data-model="model1">Model 1</button> <button class="model-btn" data-model="model2">Model 2</button> </div> <div id="controls"> <button id="toggleMode" class="control-btn">Camera Mode</button> <button id="toggleBones" class="control-btn">Bone Mode</button> <button id="resetView" class="control-btn">Reset View</button> <button id="toggleWireframe" class="control-btn">Wireframe</button> </div> <script> // Get URL parameters const urlParams = new URLSearchParams(window.location.search); const pmxPath1 = urlParams.get("pmx"); const pmxPath2 = urlParams.get("pmx2"); const vpdPath1 = urlParams.get("vpd"); const vpdPath2 = urlParams.get("vpd2"); // State parameters - simple format const stateCam = urlParams.get("cam"); const stateM1 = urlParams.get("m1"); const stateM2 = urlParams.get("m2"); let scene, camera, renderer; let model1, model2; let loader; // Input control variables let isRotating = false; let isMovingModel = false; let isPanning = false; let rotateStartX = 0, rotateStartY = 0; let moveStartX = 0, moveStartY = 0; let panStartX = 0, panStartY = 0; let currentModel = 'both'; let controlMode = 'model'; // Keyboard state const keys = {}; // Camera control variables let cameraTarget = new THREE.Vector3(0, 0, 0); let cameraDistance = 25; let cameraPhi = Math.PI / 6; let cameraTheta = 0; // Touch state let touchState = { isTwoFinger: false, initialDistance: 0, initialPan: { x: 0, y: 0 }, lastTapTime: 0, isLongPress: false, longPressTimer: null, isTouchingModel: false }; // Bone editing variables let selectedBone = null; let selectedModel = null; let boneHelpers = []; let isDraggingBone = false; let boneTransformMode = 'move'; let dragPlane = new THREE.Plane(); let dragStartPoint = new THREE.Vector3(); let dragStartBonePosition = new THREE.Vector3(); let dragStartBoneRotation = new THREE.Euler(); const boneMappings = { 'neck': ['首', 'neck', 'kubi'], 'head': ['頭', 'head', 'atama'], 'shoulder_l': ['左肩', 'shoulder_l', 'left shoulder', '左腕'], 'shoulder_r': ['右肩', 'shoulder_r', 'right shoulder', '右腕'], 'arm_l': ['左腕', 'arm_l', 'left arm', '左上腕'], 'arm_r': ['右腕', 'arm_r', 'right arm', '右上腕'], 'hand_l': ['左手', 'hand_l', 'left hand', '左手首'], 'hand_r': ['右手', 'hand_r', 'right hand', '右手首'], 'leg_l': ['左足', 'leg_l', 'left leg', '左大腿'], 'leg_r': ['右足', 'leg_r', 'right leg', '右大腿'], 'knee_l': ['左ひざ', 'knee_l', 'left knee', '左ひざ'], 'knee_r': ['右ひざ', 'knee_r', 'right knee', '右ひざ'], 'foot_l': ['左足首', 'foot_l', 'left foot', '左足先'], 'foot_r': ['右足首', 'foot_r', 'right foot', '右足先'], 'spine': ['上半身', 'spine', 'upper body', '上半身2'], 'waist': ['下半身', 'waist', 'lower body', '腰'] }; // Essential functions that were missing function updateCameraPosition() { const spherical = new THREE.Spherical(cameraDistance, cameraPhi, cameraTheta); const position = new THREE.Vector3(); position.setFromSpherical(spherical); position.add(cameraTarget); camera.position.copy(position); camera.lookAt(cameraTarget); } function setupTouchControls() { const container = document.getElementById('container'); const gestureHelp = document.getElementById('gesture-help'); container.addEventListener('touchstart', function(e) { const now = Date.now(); const isDoubleTap = (now - touchState.lastTapTime) < 300; touchState.lastTapTime = now; gestureHelp.style.display = 'block'; setTimeout(() => gestureHelp.style.display = 'none', 2000); if (e.touches.length === 1) { const touch = e.touches[0]; const mouse = new THREE.Vector2(); mouse.x = (touch.clientX / window.innerWidth) * 2 - 1; mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1; const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const targetModels = getTargetModels(); let hitModel = false; for (const model of targetModels) { if (!model) continue; const intersects = raycaster.intersectObject(model, true); if (intersects.length > 0) { hitModel = true; break; } } if (hitModel && controlMode === 'model') { touchState.isTouchingModel = true; isMovingModel = true; moveStartX = touch.clientX; moveStartY = touch.clientY; } else if (controlMode === 'bone' && isDoubleTap) { handleBoneSelection(e); e.preventDefault(); return; } else { touchState.isTouchingModel = false; isRotating = true; rotateStartX = touch.clientX; rotateStartY = touch.clientY; if (controlMode === 'bone') { touchState.longPressTimer = setTimeout(() => { touchState.isLongPress = true; if (handleBoneDragStart(e, true)) { e.preventDefault(); return; } }, 500); } } } else if (e.touches.length === 2) { touchState.isTwoFinger = true; const touch1 = e.touches[0]; const touch2 = e.touches[1]; touchState.initialDistance = Math.hypot( touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY ); touchState.initialPan.x = (touch1.clientX + touch2.clientX) / 2; touchState.initialPan.y = (touch1.clientY + touch2.clientY) / 2; panStartX = touchState.initialPan.x; panStartY = touchState.initialPan.y; } e.preventDefault(); }); container.addEventListener('touchmove', function(e) { if (controlMode === 'bone' && isDraggingBone) { handleBoneDrag(e, true); return; } if (e.touches.length === 1) { const touch = e.touches[0]; if (touchState.isTouchingModel && controlMode === 'model' && isMovingModel) { const deltaX = touch.clientX - moveStartX; const deltaY = touch.clientY - moveStartY; const moveSpeed = 0.05; const targetModels = getTargetModels(); targetModels.forEach(model => { if (model) { model.position.x += deltaX * moveSpeed; model.position.z -= deltaY * moveSpeed; } }); moveStartX = touch.clientX; moveStartY = touch.clientY; } else if (isRotating) { const deltaX = touch.clientX - rotateStartX; const deltaY = touch.clientY - rotateStartY; if (controlMode === 'model') { const rotationSpeed = 0.01; const targetModels = getTargetModels(); targetModels.forEach(model => { if (model) { model.rotation.y += deltaX * rotationSpeed; model.rotation.x += deltaY * rotationSpeed; } }); } else if (controlMode === 'camera') { const orbitSpeed = 0.01; cameraTheta -= deltaX * orbitSpeed; cameraPhi = Math.max(0.1, Math.min(Math.PI - 0.1, cameraPhi + deltaY * orbitSpeed)); updateCameraPosition(); } rotateStartX = touch.clientX; rotateStartY = touch.clientY; } } else if (e.touches.length === 2 && touchState.isTwoFinger) { const touch1 = e.touches[0]; const touch2 = e.touches[1]; const currentDistance = Math.hypot( touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY ); const zoomDelta = (touchState.initialDistance - currentDistance) * 0.01; cameraDistance = Math.max(5, Math.min(100, cameraDistance + zoomDelta)); touchState.initialDistance = currentDistance; const midX = (touch1.clientX + touch2.clientX) / 2; const midY = (touch1.clientY + touch2.clientY) / 2; const deltaX = midX - panStartX; const deltaY = midY - panStartY; const panSpeed = 0.005; const panVector = new THREE.Vector3(-deltaX, deltaY, 0).multiplyScalar(panSpeed); panVector.applyQuaternion(camera.quaternion); cameraTarget.add(panVector); panStartX = midX; panStartY = midY; updateCameraPosition(); } e.preventDefault(); }); container.addEventListener('touchend', function(e) { if (touchState.longPressTimer) { clearTimeout(touchState.longPressTimer); touchState.longPressTimer = null; } if (controlMode === 'bone') { handleBoneDragEnd(); touchState.isLongPress = false; } isMovingModel = false; isRotating = false; touchState.isTouchingModel = false; if (e.touches.length < 2) { touchState.isTwoFinger = false; } }); } function setupMouseControls() { const container = document.getElementById('container'); let isMouseDown = false; let isRightClick = false; let isMiddleClick = false; let lastMouseX = 0; let lastMouseY = 0; container.addEventListener('mousedown', function(e) { isMouseDown = true; isRightClick = e.button === 2; isMiddleClick = e.button === 1; lastMouseX = e.clientX; lastMouseY = e.clientY; const mouse = new THREE.Vector2(); mouse.x = (e.clientX / window.innerWidth) * 2 - 1; mouse.y = -(e.clientY / window.innerHeight) * 2 + 1; const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const targetModels = getTargetModels(); let hitModel = false; for (const model of targetModels) { if (!model) continue; const intersects = raycaster.intersectObject(model, true); if (intersects.length > 0) { hitModel = true; break; } } if (hitModel && controlMode === 'model' && e.button === 0) { isMovingModel = true; moveStartX = e.clientX; moveStartY = e.clientY; } else { if (controlMode === 'bone') { if (handleBoneDragStart(e, false)) { return; } if (e.button === 0) { const intersects = raycaster.intersectObjects(boneHelpers); if (intersects.length > 0) { selectedBone = intersects[0].object.userData.bone; selectedModel = intersects[0].object.userData.model; updateBoneSelection(); updateBoneButtons(); } } } isRotating = true; rotateStartX = e.clientX; rotateStartY = e.clientY; } e.preventDefault(); }); container.addEventListener('mousemove', function(e) { if (controlMode === 'bone' && isDraggingBone) { handleBoneDrag(e, false); return; } if (!isMouseDown) return; const deltaX = e.clientX - lastMouseX; const deltaY = e.clientY - lastMouseY; if (isMovingModel && controlMode === 'model') { const moveSpeed = 0.02; const targetModels = getTargetModels(); targetModels.forEach(model => { if (model) { model.position.x += deltaX * moveSpeed; model.position.z -= deltaY * moveSpeed; } }); } else if (isRotating) { if (controlMode === 'model') { const targetModels = getTargetModels(); if (isMiddleClick) { const panSpeed = 0.01; const panVector = new THREE.Vector3(-deltaX, deltaY, 0).multiplyScalar(panSpeed); panVector.applyQuaternion(camera.quaternion); cameraTarget.add(panVector); updateCameraPosition(); } else if (isRightClick) { const moveSpeed = 0.02; targetModels.forEach(model => { if (model) { model.position.x += deltaX * moveSpeed; model.position.y -= deltaY * moveSpeed; } }); } else { const rotationSpeed = 0.01; targetModels.forEach(model => { if (model) { model.rotation.y += deltaX * rotationSpeed; model.rotation.x += deltaY * rotationSpeed; } }); } } else if (controlMode === 'camera') { if (isRightClick || isMiddleClick) { const panSpeed = 0.01; const panVector = new THREE.Vector3(-deltaX, deltaY, 0).multiplyScalar(panSpeed); panVector.applyQuaternion(camera.quaternion); cameraTarget.add(panVector); updateCameraPosition(); } else { const orbitSpeed = 0.01; cameraTheta -= deltaX * orbitSpeed; cameraPhi = Math.max(0.1, Math.min(Math.PI - 0.1, cameraPhi + deltaY * orbitSpeed)); updateCameraPosition(); } } } lastMouseX = e.clientX; lastMouseY = e.clientY; }); container.addEventListener('mouseup', function() { isMouseDown = false; isMovingModel = false; isRotating = false; if (controlMode === 'bone') { handleBoneDragEnd(); } }); container.addEventListener('wheel', function(e) { if (controlMode !== 'bone') { const zoomSpeed = 0.5; cameraDistance = Math.max(5, Math.min(100, cameraDistance + e.deltaY * 0.01 * zoomSpeed)); updateCameraPosition(); e.preventDefault(); } }); container.addEventListener('contextmenu', function(e) { e.preventDefault(); }); } function setupKeyboardControls() { document.addEventListener('keydown', function(e) { keys[e.key.toLowerCase()] = true; switch(e.key.toLowerCase()) { case 'r': if (controlMode === 'bone') { resetSelectedBone(); } else { resetView(); } break; case 'f': toggleWireframe(); break; case 'h': document.getElementById('help-overlay').style.display = document.getElementById('help-overlay').style.display === 'block' ? 'none' : 'block'; break; case 'c': if (controlMode !== 'bone') toggleControlMode(); break; case 'b': toggleBoneMode(); break; case '1': selectModel('both'); break; case '2': selectModel('model1'); break; case '3': selectModel('model2'); break; } }); document.addEventListener('keyup', function(e) { keys[e.key.toLowerCase()] = false; }); } function handleKeyboardInput() { if (controlMode === 'bone') { handleBoneEditing(); return; } const moveSpeed = 0.1; const rotationSpeed = 0.03; if (controlMode === 'model') { const targetModels = getTargetModels(); if (keys['w']) { targetModels.forEach(model => { if (model) model.position.z -= moveSpeed; }); } if (keys['s']) { targetModels.forEach(model => { if (model) model.position.z += moveSpeed; }); } if (keys['a']) { targetModels.forEach(model => { if (model) model.position.x -= moveSpeed; }); } if (keys['d']) { targetModels.forEach(model => { if (model) model.position.x += moveSpeed; }); } if (keys['q']) { targetModels.forEach(model => { if (model) model.position.y += moveSpeed; }); } if (keys['e']) { targetModels.forEach(model => { if (model) model.position.y -= moveSpeed; }); } if (keys['arrowup']) { targetModels.forEach(model => { if (model) model.rotation.x -= rotationSpeed; }); } if (keys['arrowdown']) { targetModels.forEach(model => { if (model) model.rotation.x += rotationSpeed; }); } if (keys['arrowleft']) { targetModels.forEach(model => { if (model) model.rotation.y += rotationSpeed; }); } if (keys['arrowright']) { targetModels.forEach(model => { if (model) model.rotation.y -= rotationSpeed; }); } } else if (controlMode === 'camera') { if (keys['w']) { const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion); cameraTarget.add(forward.multiplyScalar(moveSpeed)); updateCameraPosition(); } if (keys['s']) { const backward = new THREE.Vector3(0, 0, 1).applyQuaternion(camera.quaternion); cameraTarget.add(backward.multiplyScalar(moveSpeed)); updateCameraPosition(); } if (keys['a']) { const left = new THREE.Vector3(-1, 0, 0).applyQuaternion(camera.quaternion); cameraTarget.add(left.multiplyScalar(moveSpeed)); updateCameraPosition(); } if (keys['d']) { const right = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion); cameraTarget.add(right.multiplyScalar(moveSpeed)); updateCameraPosition(); } if (keys['q']) { cameraTarget.y += moveSpeed; updateCameraPosition(); } if (keys['e']) { cameraTarget.y -= moveSpeed; updateCameraPosition(); } if (keys['arrowup']) { cameraPhi = Math.max(0.1, cameraPhi - rotationSpeed); updateCameraPosition(); } if (keys['arrowdown']) { cameraPhi = Math.min(Math.PI - 0.1, cameraPhi + rotationSpeed); updateCameraPosition(); } if (keys['arrowleft']) { cameraTheta += rotationSpeed; updateCameraPosition(); } if (keys['arrowright']) { cameraTheta -= rotationSpeed; updateCameraPosition(); } } } // State management functions function generateState() { const params = new URLSearchParams(); if (pmxPath1) params.set('pmx', pmxPath1); if (pmxPath2) params.set('pmx2', pmxPath2); if (vpdPath1) params.set('vpd', vpdPath1); if (vpdPath2) params.set('vpd2', vpdPath2); // Camera state: targetX,targetY,targetZ,distance,phi,theta const camState = `${cameraTarget.x.toFixed(2)},${cameraTarget.y.toFixed(2)},${cameraTarget.z.toFixed(2)},${cameraDistance.toFixed(2)},${cameraPhi.toFixed(4)},${cameraTheta.toFixed(4)}`; params.set('cam', camState); // Model 1 state: posX,posY,posZ,rotX,rotY,rotZ if (model1) { const m1State = `${model1.position.x.toFixed(2)},${model1.position.y.toFixed(2)},${model1.position.z.toFixed(2)},${model1.rotation.x.toFixed(4)},${model1.rotation.y.toFixed(4)},${model1.rotation.z.toFixed(4)}`; params.set('m1', m1State); } // Model 2 state if (model2) { const m2State = `${model2.position.x.toFixed(2)},${model2.position.y.toFixed(2)},${model2.position.z.toFixed(2)},${model2.rotation.x.toFixed(4)},${model2.rotation.y.toFixed(4)},${model2.rotation.z.toFixed(4)}`; params.set('m2', m2State); } // Add stored poses to URL if they exist const storedPose1 = localStorage.getItem('stor1'); const storedPose2 = localStorage.getItem('stor2'); if (storedPose1) { params.set('stor1', encodeURIComponent(storedPose1)); } if (storedPose2) { params.set('stor2', encodeURIComponent(storedPose2)); } const stateURL = `${window.location.origin}${window.location.pathname}?${params.toString()}`; // Show URL in textbox const textbox = document.getElementById('state-textbox'); textbox.value = stateURL; textbox.style.display = 'block'; textbox.select(); return stateURL; } // Also update the loadStateFromURL function to handle stored poses from URL function loadStateFromURL() { if (stateCam) { const parts = stateCam.split(','); if (parts.length === 6) { cameraTarget.set(parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2])); cameraDistance = parseFloat(parts[3]); cameraPhi = parseFloat(parts[4]); cameraTheta = parseFloat(parts[5]); updateCameraPosition(); } } // Load stored poses from URL parameters const urlStor1 = urlParams.get('stor1'); const urlStor2 = urlParams.get('stor2'); if (urlStor1) { try { const decodedStor1 = decodeURIComponent(urlStor1); // Validate it's valid JSON before storing JSON.parse(decodedStor1); localStorage.setItem('stor1', decodedStor1); } catch (error) { console.error('Invalid stor1 data in URL:', error); } } if (urlStor2) { try { const decodedStor2 = decodeURIComponent(urlStor2); // Validate it's valid JSON before storing JSON.parse(decodedStor2); localStorage.setItem('stor2', decodedStor2); } catch (error) { console.error('Invalid stor2 data in URL:', error); } } } // Update the applyModelStates function to also apply stored poses function applyModelStates() { if (stateM1 && model1) { const parts = stateM1.split(','); if (parts.length === 6) { model1.position.set(parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2])); model1.rotation.set(parseFloat(parts[3]), parseFloat(parts[4]), parseFloat(parts[5])); } } if (stateM2 && model2) { const parts = stateM2.split(','); if (parts.length === 6) { model2.position.set(parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2])); model2.rotation.set(parseFloat(parts[3]), parseFloat(parts[4]), parseFloat(parts[5])); } } // Apply stored poses from localStorage (which might have been loaded from URL) setTimeout(() => { applyStoredPosesOnLoad(); }, 100); } function applyModelStates() { if (stateM1 && model1) { const parts = stateM1.split(','); if (parts.length === 6) { model1.position.set(parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2])); model1.rotation.set(parseFloat(parts[3]), parseFloat(parts[4]), parseFloat(parts[5])); } } if (stateM2 && model2) { const parts = stateM2.split(','); if (parts.length === 6) { model2.position.set(parseFloat(parts[0]), parseFloat(parts[1]), parseFloat(parts[2])); model2.rotation.set(parseFloat(parts[3]), parseFloat(parts[4]), parseFloat(parts[5])); } } } // Rest of the essential functions function getTargetModels() { switch(currentModel) { case 'model1': return [model1]; case 'model2': return [model2]; default: return [model1, model2]; } } function resolveAssetPath(assetPath, defaultBase) { if (!assetPath) return null; assetPath = assetPath.trim(); if (/^(https?:|file:|\/)/i.test(assetPath)) return assetPath; if (/^(\.\/|\.\.\/)/.test(assetPath)) return assetPath; if (/^vpd\//i.test(assetPath)) return "./" + assetPath; return (defaultBase || "./pmx/pronama/") + assetPath; } function loadModels() { if (pmxPath1) { const full1 = resolveAssetPath(pmxPath1, "./pmx/pronama/"); loader.load(full1, object => { model1 = object; model1.position.set(-5, 0, 0); scene.add(model1); if (vpdPath1) loadAndApplyVPD(model1, resolveAssetPath(vpdPath1, "./")); applyModelStates(); updateModelInfo(); checkLoadingComplete(); }, null, err => console.error("Error loading PMX1", err)); } if (pmxPath2) { const full2 = resolveAssetPath(pmxPath2, "./pmx/pronama/"); loader.load(full2, object => { model2 = object; model2.position.set(5, 0, 0); scene.add(model2); if (vpdPath2) loadAndApplyVPD(model2, resolveAssetPath(vpdPath2, "./")); applyModelStates(); updateModelInfo(); checkLoadingComplete(); }, null, err => console.error("Error loading PMX2", err)); } if (!pmxPath1 && !pmxPath2) { document.getElementById("loading").textContent = "No PMX models specified."; } setTimeout(loadStateFromURL, 100); } function loadAndApplyVPD(model, vpdPath) { console.log("Loading VPD:", vpdPath); loader.loadVPD(vpdPath, false, function(vpd) { console.log("VPD loaded successfully:", vpd); requestAnimationFrame(() => { try { helper.pose(model, vpd); console.log("Pose applied successfully to model"); } catch (error) { console.error("Error applying pose:", error); } }); }, null, function(error) { console.error("Error loading VPD:", error); }); } function updateModelInfo() { let info = ""; if (model1) info += "Model 1 loaded" + (vpdPath1 ? " + Pose 1" : "") + "<br>"; if (model2) info += "Model 2 loaded" + (vpdPath2 ? " + Pose 2" : "") + "<br>"; document.getElementById("modelInfo").innerHTML = info; } function checkLoadingComplete() { const total = [pmxPath1, pmxPath2].filter(Boolean).length; const loaded = [model1, model2].filter(Boolean).length; if (loaded === total) { document.getElementById("loading").style.display = "none"; // Always try to apply stored poses, regardless of URL parameters setTimeout(() => { applyStoredPosesOnLoad(); }, 1500); } } function applyStoredPosesOnLoad() { const storedPose1 = localStorage.getItem('stor1'); if (storedPose1 && model1) { try { const pose = JSON.parse(storedPose1); applyPoseToModelFromStorage(model1, pose); } catch (error) { console.error('Error applying stored pose 1:', error); } } const storedPose2 = localStorage.getItem('stor2'); if (storedPose2 && model2) { try { const pose = JSON.parse(storedPose2); applyPoseToModelFromStorage(model2, pose); } catch (error) { console.error('Error applying stored pose 2:', error); } } } function applyPoseToModelFromStorage(model, pose) { if (!model || !model.skeleton || !pose) return; model.updateMatrixWorld(true); Object.keys(pose).forEach(japaneseName => { const bone = model.skeleton.bones.find(b => b.name === japaneseName); if (!bone || !pose[japaneseName]) return; const relQuat = new THREE.Quaternion( pose[japaneseName].quaternion.x, pose[japaneseName].quaternion.y, pose[japaneseName].quaternion.z, pose[japaneseName].quaternion.w ); bone.quaternion.copy(relQuat).normalize(); }); model.skeleton.update(); model.updateMatrixWorld(true); } function resetView() { cameraTarget.set(0, 0, 0); cameraDistance = 25; cameraPhi = Math.PI / 6; cameraTheta = 0; updateCameraPosition(); if (model1) { model1.position.set(-5, 0, 0); model1.rotation.set(0, 0, 0); } if (model2) { model2.position.set(5, 0, 0); model2.rotation.set(0, 0, 0); } } function toggleWireframe() { [model1, model2].forEach(model => { if (!model) return; model.traverse(c => { if (c.isMesh) { if (Array.isArray(c.material)) { c.material.forEach(m => m.wireframe = !m.wireframe); } else { c.material.wireframe = !c.material.wireframe; } } }); }); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function toggleControlMode() { if (controlMode === 'bone') return; controlMode = controlMode === 'model' ? 'camera' : 'model'; updateModeUI(); } function toggleBoneMode() { if (controlMode === 'bone') { controlMode = 'model'; document.getElementById('bone-controls').style.display = 'none'; document.getElementById('selected-bone-info').style.display = 'none'; document.getElementById('bone-transform-controls').style.display = 'none'; boneHelpers.forEach(helper => scene.remove(helper)); boneHelpers = []; selectedBone = null; selectedModel = null; } else { controlMode = 'bone'; document.getElementById('bone-controls').style.display = 'flex'; document.getElementById('bone-transform-controls').style.display = 'flex'; const targetModels = getTargetModels(); selectedModel = targetModels[0] || null; setupBoneHelpers(); setBoneTransformMode('move'); } updateModeUI(); } function updateModeUI() { const modeBtn = document.getElementById('toggleMode'); const boneBtn = document.getElementById('toggleBones'); const modeIndicator = document.getElementById('mode-indicator'); if (controlMode === 'camera') { modeBtn.textContent = 'Model Mode'; modeBtn.classList.add('active'); boneBtn.textContent = 'Bone Mode'; boneBtn.classList.remove('active'); modeIndicator.textContent = 'Mode: Camera Control'; } else if (controlMode === 'model') { modeBtn.textContent = 'Camera Mode'; modeBtn.classList.remove('active'); boneBtn.textContent = 'Bone Mode'; boneBtn.classList.remove('active'); modeIndicator.textContent = 'Mode: Model Control'; } else if (controlMode === 'bone') { modeBtn.textContent = 'Camera Mode'; modeBtn.classList.remove('active'); boneBtn.textContent = 'Exit Bones'; boneBtn.classList.add('active'); modeIndicator.textContent = 'Mode: Bone Editing'; } } function selectModel(model) { currentModel = model; document.querySelectorAll('.model-btn').forEach(btn => { btn.classList.remove('active'); if (btn.getAttribute('data-model') === model) { btn.classList.add('active'); } }); updateSelectedModel(); } function updateSelectedModel() { if (controlMode === 'bone') { const targetModels = getTargetModels(); selectedModel = targetModels[0] || null; if (selectedModel && selectedBone) { const boneName = selectedBone.name; const newBone = findBoneInModel(selectedModel, boneName); selectedBone = newBone; updateBoneSelection(); } } } function setupBoneHelpers() { boneHelpers.forEach(helper => scene.remove(helper)); boneHelpers = []; const targetModels = getTargetModels(); targetModels.forEach(model => { if (model && model.skeleton) { model.skeleton.bones.forEach(bone => { const geometry = new THREE.SphereGeometry(0.15, 12, 12); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, transparent: true, opacity: 0.8 }); const helper = new THREE.Mesh(geometry, material); const worldPos = new THREE.Vector3(); bone.getWorldPosition(worldPos); helper.position.copy(worldPos); helper.userData = { bone: bone, model: model }; helper.className = 'bone-helper'; boneHelpers.push(helper); scene.add(helper); }); } }); } function findBoneInModel(model, boneName) { if (!model || !model.skeleton) return null; return model.skeleton.bones.find(bone => bone.name.toLowerCase().includes(boneName.toLowerCase()) ); } function selectBoneByType(boneType) { if (!selectedModel) return; const possibleNames = boneMappings[boneType]; if (!possibleNames) return; for (const name of possibleNames) { const bone = findBoneInModel(selectedModel, name); if (bone) { selectedBone = bone; updateBoneSelection(); document.querySelectorAll('.bone-btn').forEach(btn => { btn.classList.remove('active'); }); document.querySelector(`.bone-btn[data-bone="${boneType}"]`).classList.add('active'); return; } } } function updateBoneSelection() { updateSelectedBoneInfo(); if (selectedBone && selectedModel) { boneHelpers.forEach(helper => { if (helper.userData.bone === selectedBone) { helper.material.color.set(0xff0000); helper.scale.set(1.5, 1.5, 1.5); } else { helper.material.color.set(0x00ff00); helper.scale.set(1, 1, 1); } }); } else { boneHelpers.forEach(helper => { helper.material.color.set(0x00ff00); helper.scale.set(1, 1, 1); }); } } function updateSelectedBoneInfo() { const infoElement = document.getElementById('selected-bone-info'); if (selectedBone && selectedModel) { infoElement.textContent = `Selected: ${selectedBone.name} (${boneTransformMode} mode)`; infoElement.style.display = 'block'; } else { infoElement.textContent = 'No bone selected'; infoElement.style.display = 'block'; } } function setBoneTransformMode(mode) { boneTransformMode = mode; document.getElementById('move-bone').classList.toggle('active', mode === 'move'); document.getElementById('rotate-bone').classList.toggle('active', mode === 'rotate'); updateSelectedBoneInfo(); } function handleBoneDragStart(event, isTouch = false) { if (controlMode !== 'bone' || !selectedBone) return false; const mouse = new THREE.Vector2(); if (isTouch) { mouse.x = (event.touches[0].clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.touches[0].clientY / window.innerHeight) * 2 + 1; } else { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; } const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(boneHelpers); const hitSelectedBone = intersects.some(intersect => intersect.object.userData.bone === selectedBone); if (hitSelectedBone) { isDraggingBone = true; if (boneTransformMode === 'move') { const cameraDirection = new THREE.Vector3(); camera.getWorldDirection(cameraDirection); dragPlane.setFromNormalAndCoplanarPoint(cameraDirection, selectedBone.getWorldPosition(new THREE.Vector3())); raycaster.ray.intersectPlane(dragPlane, dragStartPoint); dragStartBonePosition.copy(selectedBone.position); } else { dragStartBoneRotation.copy(selectedBone.rotation); dragStartPoint.set(mouse.x, mouse.y, 0); } event.preventDefault(); return true; } return false; } function handleBoneDrag(event, isTouch = false) { if (!isDraggingBone || !selectedBone) return; const mouse = new THREE.Vector2(); if (isTouch) { mouse.x = (event.touches[0].clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.touches[0].clientY / window.innerHeight) * 2 + 1; } else { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; } if (boneTransformMode === 'move') { const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const dragPoint = new THREE.Vector3(); if (raycaster.ray.intersectPlane(dragPlane, dragPoint)) { const delta = new THREE.Vector3().subVectors(dragPoint, dragStartPoint); selectedBone.position.copy(dragStartBonePosition).add(delta); } } else { const deltaX = mouse.x - dragStartPoint.x; const deltaY = mouse.y - dragStartPoint.y; const rotationSpeed = 2; selectedBone.rotation.x = dragStartBoneRotation.x + deltaY * rotationSpeed; selectedBone.rotation.y = dragStartBoneRotation.y + deltaX * rotationSpeed; } if (selectedModel) { selectedModel.skeleton.pose(); selectedModel.updateMatrixWorld(true); } updateBoneHelpers(); event.preventDefault(); } function handleBoneDragEnd() { isDraggingBone = false; } function handleBoneSelection(e) { const touch = e.touches[0]; const mouse = new THREE.Vector2(); mouse.x = (touch.clientX / window.innerWidth) * 2 - 1; mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1; const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(boneHelpers); if (intersects.length > 0) { selectedBone = intersects[0].object.userData.bone; selectedModel = intersects[0].object.userData.model; updateBoneSelection(); updateBoneButtons(); } } function handleBoneEditing() { if (!selectedBone || controlMode !== 'bone') return; const moveSpeed = 0.05; const rotationSpeed = 0.03; if (keys['w']) selectedBone.position.z -= moveSpeed; if (keys['s']) selectedBone.position.z += moveSpeed; if (keys['a']) selectedBone.position.x -= moveSpeed; if (keys['d']) selectedBone.position.x += moveSpeed; if (keys['q']) selectedBone.position.y += moveSpeed; if (keys['e']) selectedBone.position.y -= moveSpeed; if (keys['arrowup']) selectedBone.rotation.x -= rotationSpeed; if (keys['arrowdown']) selectedBone.rotation.x += rotationSpeed; if (keys['arrowleft']) selectedBone.rotation.y += rotationSpeed; if (keys['arrowright']) selectedBone.rotation.y -= rotationSpeed; if (selectedModel) { selectedModel.skeleton.pose(); selectedModel.updateMatrixWorld(true); } updateBoneHelpers(); } function updateBoneHelpers() { boneHelpers.forEach(helper => { const bone = helper.userData.bone; const worldPos = new THREE.Vector3(); bone.getWorldPosition(worldPos); helper.position.copy(worldPos); }); } function resetSelectedBone() { if (selectedBone) { selectedBone.position.set(0, 0, 0); selectedBone.rotation.set(0, 0, 0); selectedBone.scale.set(1, 1, 1); if (selectedModel) { selectedModel.skeleton.pose(); selectedModel.updateMatrixWorld(true); } updateBoneHelpers(); } } function updateBoneButtons() { if (!selectedBone) return; const boneName = selectedBone.name.toLowerCase(); document.querySelectorAll('.bone-btn').forEach(btn => { btn.classList.remove('active'); const boneType = btn.getAttribute('data-bone'); const possibleNames = boneMappings[boneType]; if (possibleNames && possibleNames.some(name => boneName.includes(name.toLowerCase()))) { btn.classList.add('active'); } }); } function reloadStoredPose(storageKey, model) { if (!model) return; const storedPose = localStorage.getItem(storageKey); if (storedPose) { try { const pose = JSON.parse(storedPose); applyPoseToModelFromStorage(model, pose); showPoseReloadFeedback(storageKey); } catch (error) { console.error(`Error applying ${storageKey}:`, error); } } } function reloadBothStoredPoses() { if (model1) reloadStoredPose('stor1', model1); if (model2) reloadStoredPose('stor2', model2); showPoseReloadFeedback('both'); } function showPoseReloadFeedback(poseType) { const feedback = document.createElement('div'); feedback.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.8);color:#8a8aff;padding:10px 20px;border-radius:10px;z-index:100;font-size:16px;font-weight:bold'; feedback.textContent = poseType === 'both' ? 'Both Poses Reloaded!' : `Pose ${poseType.slice(-1)} Reloaded!`; document.body.appendChild(feedback); setTimeout(() => document.body.removeChild(feedback), 1000); } function animate() { requestAnimationFrame(animate); handleKeyboardInput(); renderer.render(scene, camera); } // Initialize the application - MOVE THIS TO THE END function init() { helper = new THREE.MMDAnimationHelper(); scene = new THREE.Scene(); scene.background = new THREE.Color(0x1a1a2e); camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); updateCameraPosition(); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById("container").appendChild(renderer.domElement); scene.add(new THREE.AmbientLight(0xffffff, 0.6)); const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); dirLight.position.set(10, 20, 15); scene.add(dirLight); scene.add(new THREE.GridHelper(20, 20)); scene.add(new THREE.AxesHelper(5)); loader = new THREE.MMDLoader(); setupTouchControls(); setupMouseControls(); setupKeyboardControls(); loadModels(); window.addEventListener("resize", onWindowResize); document.getElementById("resetView").addEventListener("click", resetView); document.getElementById("toggleWireframe").addEventListener("click", toggleWireframe); document.getElementById("toggleMode").addEventListener("click", toggleControlMode); document.getElementById("toggleBones").addEventListener("click", toggleBoneMode); document.getElementById("reset-bones").addEventListener("click", resetSelectedBone); document.getElementById("move-bone").addEventListener("click", () => setBoneTransformMode('move')); document.getElementById("rotate-bone").addEventListener("click", () => setBoneTransformMode('rotate')); document.getElementById("reset-bone-transform").addEventListener("click", resetSelectedBone); document.querySelectorAll('.model-btn').forEach(btn => { btn.addEventListener('click', function() { document.querySelectorAll('.model-btn').forEach(b => b.classList.remove('active')); this.classList.add('active'); currentModel = this.getAttribute('data-model'); updateSelectedModel(); }); }); document.querySelectorAll('.bone-btn').forEach(btn => { btn.addEventListener('click', function() { if (controlMode !== 'bone') return; const boneType = this.getAttribute('data-bone'); selectBoneByType(boneType); }); }); document.getElementById('reload-stor1').addEventListener('click', () => reloadStoredPose('stor1', model1)); document.getElementById('reload-stor2').addEventListener('click', () => reloadStoredPose('stor2', model2)); document.getElementById('reload-both').addEventListener('click', reloadBothStoredPoses); document.getElementById('generate-state').addEventListener('click', generateState); document.getElementById('help-btn').addEventListener('click', () => { document.getElementById('help-overlay').style.display = 'block'; }); document.getElementById('close-help').addEventListener('click', () => { document.getElementById('help-overlay').style.display = 'none'; }); document.getElementById('close-gesture-help').addEventListener('click', () => { document.getElementById('mobile-gesture-info').style.display = 'none'; }); if (/Mobi|Android|iPhone|iPad/.test(navigator.userAgent)) { setTimeout(() => { document.getElementById('mobile-gesture-info').style.display = 'block'; }, 1000); } animate(); } // Finally, call init after all functions are defined init(); </script> </body> </html> pose2vpd.html -- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>MMD 3D Pose Editor</title> <style> body { margin: 0; overflow: hidden; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; touch-action: none; -webkit-tap-highlight-color: transparent; } canvas { display: block; cursor: grab; touch-action: none; } canvas.orb-dragging { cursor: grabbing !important; } #ui { position: absolute; top: 10px; left: 10px; background: rgba(25, 25, 35, 0.85); color: white; padding: 20px; border-radius: 12px; width: 280px; z-index: 100; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } #ui h2 { margin-top: 0; color: #6c63ff; text-align: center; font-weight: 600; font-size: 1.4em; margin-bottom: 20px; } button { width: 100%; padding: 12px; margin-bottom: 12px; border-radius: 6px; border: none; background: linear-gradient(135deg, #6c63ff 0%, #4a44b5 100%); color: white; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s; min-height: 44px; } button:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(108, 99, 255, 0.4); } #reset-btn { background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%); } #reset-btn:hover { box-shadow: 0 4px 12px rgba(255, 107, 107, 0.4); } #instructions { font-size: 12px; color: #8888aa; margin-top: 15px; line-height: 1.5; text-align: center; } #loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 18px; z-index: 100; background: rgba(25, 25, 35, 0.9); padding: 20px 30px; border-radius: 10px; text-align: center; } .footer { display: flex; justify-content: space-between; margin-top: 15px; font-size: 11px; color: #666; } .camera-controls { margin-top: 15px; padding: 15px; background: rgba(42, 42, 58, 0.5); border-radius: 8px; } .control-hint { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 11px; } .control-key { background: rgba(108, 99, 255, 0.3); padding: 2px 6px; border-radius: 3px; } #axis-controls { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: none; z-index: 200; } .axis-btn { position: absolute; width: 44px; height: 44px; border-radius: 50%; border: none; color: white; font-size: 18px; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; touch-action: manipulation; } .axis-btn:hover { transform: scale(1.2); } #x-plus { background: #ff4444; top: -60px; left: 0; } #x-minus { background: #ff4444; top: 60px; left: 0; } #y-plus { background: #44ff44; top: 0; left: -60px; } #y-minus { background: #44ff44; top: 0; left: 60px; } #z-plus { background: #4444ff; top: -40px; left: -40px; } #z-minus { background: #4444ff; top: 40px; left: 40px; } .mode-toggle { display: flex; margin-bottom: 15px; border-radius: 6px; overflow: hidden; background: rgba(42, 42, 58, 0.5); } .mode-btn { flex: 1; padding: 10px; background: transparent; border: none; color: #8888aa; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.3s; min-height: auto; } .mode-btn.active { background: #6c63ff; color: white; } #control-mode-indicator { text-align: center; margin-top: 10px; font-size: 12px; color: #a0a0c0; } .storage-buttons { display: flex; gap: 10px; margin-bottom: 12px; } .storage-buttons button { flex: 1; margin-bottom: 0; } #stor1-btn { background: linear-gradient(135deg, #ffa726 0%, #f57c00 100%); } #stor2-btn { background: linear-gradient(135deg, #66bb6a 0%, #388e3c 100%); } #load-buttons { display: flex; gap: 10px; margin-bottom: 12px; } #load-buttons button { flex: 1; margin-bottom: 0; } #load1-btn { background: linear-gradient(135deg, #ffb74d 0%, #ff9800 100%); } #load2-btn { background: linear-gradient(135deg, #81c784 0%, #4caf50 100%); } #export-btn { background: linear-gradient(135deg, #ab47bc 0%, #8e24aa 100%); } #export-btn:hover { box-shadow: 0 4px 12px rgba(171, 71, 188, 0.4); } .notification { position: fixed; top: 20px; right: 20px; background: rgba(25, 25, 35, 0.95); color: white; padding: 15px 20px; border-radius: 8px; border-left: 4px solid #6c63ff; z-index: 1000; transform: translateX(400px); transition: transform 0.3s ease; backdrop-filter: blur(10px); max-width: 300px; } .notification.show { transform: translateX(0); } .mobile-controls { position: absolute; bottom: 20px; right: 20px; display: flex; flex-direction: column; gap: 10px; z-index: 50; } .mobile-control-btn { width: 50px; height: 50px; border-radius: 50%; border: none; background: rgba(108, 99, 255, 0.8); color: white; font-size: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); touch-action: manipulation; } #mobile-ui-toggle { position: absolute; top: 20px; right: 20px; width: 50px; height: 50px; border-radius: 50%; border: none; background: rgba(25, 25, 35, 0.8); color: white; font-size: 20px; z-index: 101; backdrop-filter: blur(10px); display: none; align-items: center; justify-content: center; cursor: pointer; } @media (max-width: 768px) { #ui { width: calc(100% - 40px); max-height: 70vh; overflow-y: auto; } #mobile-ui-toggle { display: flex; } .mobile-controls { display: flex; } } </style> <script src="./libs/three.js"></script> <script src="./libs/mmdparser.min.js"></script> <script src="./libs/ammo.min.js"></script> <script src="./libs/TGALoader.js"></script> <script src="./libs/CCDIKSolver.js"></script> <script src="./libs/MMDLoader.js"></script> <script src="./libs/MMDAnimationHelper.js"></script> </head> <body> <div id="loading"> <div>Loading MMD Model...</div> <div style="margin-top:10px;font-size:14px;" id="loading-progress">0%</div> </div> <button id="mobile-ui-toggle">☰</button> <div id="ui" style="display: none;"> <h2>MMD 3D Pose Editor</h2> <div class="mode-toggle"> <button class="mode-btn active" id="rotation-mode">Rotation</button> <button class="mode-btn" id="position-mode">Position</button> </div> <button id="reset-btn">Reset All Poses</button> <div class="storage-buttons"> <button id="stor1-btn">Store Pose 1</button> <button id="stor2-btn">Store Pose 2</button> </div> <div id="load-buttons"> <button id="load1-btn">Load Pose 1</button> <button id="load2-btn">Load Pose 2</button> </div> <button id="export-btn">Export as VPD</button> <div id="control-mode-indicator">Current Mode: Rotation</div> <div class="camera-controls"> <div style="text-align: center; margin-bottom: 10px; color: #a0a0c0; font-weight: 500;">Camera Controls</div> <div class="control-hint"> <span>Orbit Camera:</span> <span class="control-key">Right Drag</span> </div> <div class="control-hint"> <span>Zoom:</span> <span class="control-key">Scroll Wheel</span> </div> <div class="control-hint"> <span>Pan:</span> <span class="control-key">Middle Drag</span> </div> <div class="control-hint"> <span>Reset Camera:</span> <span class="control-key">Double Click</span> </div> </div> <div id="instructions"> <p>Click and drag the colored orbs to pose the model</p> <p>Red: Rotation • Green: Position • Blue: Special</p> <p><strong>Left Click:</strong> Drag Orbs • <strong>Right Click:</strong> Orbit Camera</p> <p><strong>Click Orb:</strong> Show Axis Controls</p> </div> <div class="footer"> <span>Magic Pose Editor</span> <span>v1.2</span> </div> </div> <div id="axis-controls"> <button class="axis-btn" id="x-plus">X+</button> <button class="axis-btn" id="x-minus">X-</button> <button class="axis-btn" id="y-plus">Y+</button> <button class="axis-btn" id="y-minus">Y-</button> <button class="axis-btn" id="z-plus">Z+</button> <button class="axis-btn" id="z-minus">Z-</button> </div> <div class="mobile-controls"> <button class="mobile-control-btn" id="mobile-orbit">🔄</button> <button class="mobile-control-btn" id="mobile-zoom-in">+</button> <button class="mobile-control-btn" id="mobile-zoom-out">-</button> <button class="mobile-control-btn" id="mobile-reset-cam">📷</button> </div> <div id="notification" class="notification"></div> <script> // Mobile detection and variables let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); let touchStartTime = 0; let lastTouchX = 0; let lastTouchY = 0; let isTouchOrbiting = false; let isTouchDraggingOrb = false; let touchOrbStartTime = 0; let touchOrb = null; let scene, camera, renderer, helper, model; let boneOrbs = []; let raycaster, mouse; let isDraggingOrb = false; let currentOrb = null; let orbGroup; let controls; let axisControls; let selectedOrb = null; let controlMode = 'rotation'; // 'rotation' or 'position' const ESSENTIAL_BONES = { '頭': 'head', '首': 'neck', '左腕': 'left_arm', '右腕': 'right_arm', '左ひじ': 'left_elbow', '右ひじ': 'right_elbow', '左手首': 'left_wrist', '右手首': 'right_wrist', '左足': 'left_leg_upper', '右足': 'right_leg_upper', '左ひざ': 'left_knee', '右ひざ': 'right_knee', '左足首': 'left_ankle', '右足首': 'right_ankle', '左目': 'left_eye', '右目': 'right_eye' }; function radToDeg(radians) { return radians * (180 / Math.PI); } function degToRad(degrees) { return degrees * (Math.PI / 180); } function showNotification(message, duration = 3000) { const notification = document.getElementById('notification'); notification.textContent = message; notification.classList.add('show'); setTimeout(() => { notification.classList.remove('show'); }, duration); } class SimpleOrbitControls { constructor(camera, domElement) { this.camera = camera; this.domElement = domElement; this.enabled = true; this.target = new THREE.Vector3(0, 10, 0); this.minDistance = 5; this.maxDistance = 100; this.minPolarAngle = 0; this.maxPolarAngle = Math.PI; this.spherical = new THREE.Spherical().setFromVector3( new THREE.Vector3().subVectors(camera.position, this.target) ); this.sphericalDelta = new THREE.Spherical(); this.scale = 1; this.panOffset = new THREE.Vector3(); this.zoomChanged = false; this.mouseButtons = { LEFT: 0, MIDDLE: 1, RIGHT: 2 }; this.state = -1; this.domElement.addEventListener('contextmenu', this.onContextMenu.bind(this)); this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this)); this.domElement.addEventListener('wheel', this.onMouseWheel.bind(this)); this.domElement.addEventListener('dblclick', this.onDoubleClick.bind(this)); // Touch events for mobile this.domElement.addEventListener('touchstart', this.onTouchStart.bind(this)); this.domElement.addEventListener('touchmove', this.onTouchMove.bind(this)); this.domElement.addEventListener('touchend', this.onTouchEnd.bind(this)); this.update(); } onContextMenu(event) { event.preventDefault(); } onMouseDown(event) { if (!this.enabled) return; if (event.button !== this.mouseButtons.RIGHT && event.button !== this.mouseButtons.MIDDLE) { return; } event.preventDefault(); switch (event.button) { case this.mouseButtons.RIGHT: this.state = 0; break; case this.mouseButtons.MIDDLE: this.state = 2; break; } if (this.state !== -1) { document.addEventListener('mousemove', this.onMouseMove.bind(this)); document.addEventListener('mouseup', this.onMouseUp.bind(this)); } } onMouseMove(event) { if (!this.enabled) return; event.preventDefault(); const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; if (this.state === 0) { this.sphericalDelta.theta -= movementX * 0.01; this.sphericalDelta.phi -= movementY * 0.01; } else if (this.state === 2) { this.panOffset.x -= movementX * 0.01; this.panOffset.y += movementY * 0.01; } } onMouseUp() { this.state = -1; document.removeEventListener('mousemove', this.onMouseMove); document.removeEventListener('mouseup', this.onMouseUp); } onMouseWheel(event) { if (!this.enabled) return; event.preventDefault(); if (event.deltaY < 0) { this.scale /= 1.1; } else if (event.deltaY > 0) { this.scale *= 1.1; } this.zoomChanged = true; } onDoubleClick() { this.target.set(0, 10, 0); this.spherical.set(1, Math.PI / 2, 0); this.spherical.radius = 45; this.sphericalDelta.set(0, 0, 0); this.panOffset.set(0, 0, 0); this.scale = 1; this.update(); } // Touch event handlers for mobile onTouchStart(event) { if (!this.enabled || event.touches.length !== 1) return; event.preventDefault(); const touch = event.touches[0]; lastTouchX = touch.clientX; lastTouchY = touch.clientY; touchStartTime = Date.now(); // Check if we're touching an orb (will be handled separately) const mouse = new THREE.Vector2( (touch.clientX / window.innerWidth) * 2 - 1, -(touch.clientY / window.innerHeight) * 2 + 1 ); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(boneOrbs); if (intersects.length > 0) { // Orb touch - let the orb handler deal with it return; } // Otherwise, start orbit camera isTouchOrbiting = true; this.state = 0; } onTouchMove(event) { if (!this.enabled || !isTouchOrbiting || event.touches.length !== 1) return; event.preventDefault(); const touch = event.touches[0]; const deltaX = touch.clientX - lastTouchX; const deltaY = touch.clientY - lastTouchY; if (this.state === 0) { this.sphericalDelta.theta -= deltaX * 0.01; this.sphericalDelta.phi -= deltaY * 0.01; } lastTouchX = touch.clientX; lastTouchY = touch.clientY; } onTouchEnd(event) { if (isTouchOrbiting) { isTouchOrbiting = false; this.state = -1; } } update() { const offset = new THREE.Vector3(); this.spherical.theta += this.sphericalDelta.theta; this.spherical.phi += this.sphericalDelta.phi; this.spherical.phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, this.spherical.phi)); if (this.zoomChanged) { this.spherical.radius *= this.scale; this.spherical.radius = Math.max(this.minDistance, Math.min(this.maxDistance, this.spherical.radius)); this.scale = 1; this.zoomChanged = false; } this.target.add(this.panOffset); this.panOffset.set(0, 0, 0); this.target.y = Math.max(0, Math.min(30, this.target.y)); offset.setFromSpherical(this.spherical); this.camera.position.copy(this.target).add(offset); this.camera.lookAt(this.target); this.sphericalDelta.set(0, 0, 0); } } init(); function init() { scene = new THREE.Scene(); scene.background = new THREE.Color(0x1a1a2e); camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000); camera.position.set(0, 15, 45); const ambient = new THREE.AmbientLight(0xffffff, 0.4); scene.add(ambient); const light = new THREE.DirectionalLight(0xffffff, 0.8); light.position.set(0, 20, 10); scene.add(light); const rimLight = new THREE.DirectionalLight(0x4a44b5, 0.3); rimLight.position.set(0, 10, -20); scene.add(rimLight); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const groundGeometry = new THREE.PlaneGeometry(50, 50); const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x2a2a3a, roughness: 0.8, metalness: 0.2 }); const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; ground.position.y = -0.1; scene.add(ground); raycaster = new THREE.Raycaster(); mouse = new THREE.Vector2(); orbGroup = new THREE.Group(); scene.add(orbGroup); controls = new SimpleOrbitControls(camera, renderer.domElement); helper = new THREE.MMDAnimationHelper(); const loader = new THREE.MMDLoader(); function getPMXPath() { const params = new URLSearchParams(window.location.search); const pmxParam = params.get('pmx'); if (pmxParam) { // prepend folder path and decode URI safely return "pmx/pronama/" + decodeURIComponent(pmxParam); } return "pmx/pronama/YusakuFujiki/yusaku.pmx"; // default fallback } const modelPath = getPMXPath(); loader.load( modelPath, function (mesh) { model = mesh; model.position.y = 0; scene.add(model); document.getElementById('loading').style.display = 'none'; document.getElementById('ui').style.display = 'block'; // Show mobile UI toggle on mobile if (isMobile) { document.getElementById('mobile-ui-toggle').style.display = 'flex'; // Hide UI by default on mobile to maximize canvas space document.getElementById('ui').style.display = 'none'; } model.updateMatrixWorld(true); model.skeleton.bones.forEach(bone => { bone.userData.bindQuaternion = bone.quaternion.clone(); }); createAllOrbControls(); setupUI(); setupOrbInteraction(); setupAxisControls(); setupMobileControls(); animate(); }, function (xhr) { if (xhr.lengthComputable) { const percentComplete = (xhr.loaded / xhr.total * 100).toFixed(0); document.getElementById('loading-progress').textContent = `${percentComplete}%`; } }, function (error) { console.error("Error loading model:", error); document.getElementById('loading').innerHTML = ` <div style="color:#ff6b6b">Error loading model</div> <div style="margin-top:10px;font-size:14px;">Check browser console for details</div> <button onclick="location.reload()" style="margin-top:15px;padding:8px 15px;background:#6c63ff;border:none;border-radius:4px;color:white;cursor:pointer;">Retry</button> `; } ); window.addEventListener("resize", onWindowResize); } function setupMobileControls() { const orbitBtn = document.getElementById('mobile-orbit'); const zoomInBtn = document.getElementById('mobile-zoom-in'); const zoomOutBtn = document.getElementById('mobile-zoom-out'); const resetCamBtn = document.getElementById('mobile-reset-cam'); const uiToggle = document.getElementById('mobile-ui-toggle'); orbitBtn.addEventListener('click', function() { showNotification('Drag with one finger to orbit camera'); }); zoomInBtn.addEventListener('click', function() { controls.scale /= 1.2; controls.zoomChanged = true; }); zoomOutBtn.addEventListener('click', function() { controls.scale *= 1.2; controls.zoomChanged = true; }); resetCamBtn.addEventListener('click', function() { controls.onDoubleClick(); showNotification('Camera reset'); }); uiToggle.addEventListener('click', function() { const ui = document.getElementById('ui'); if (ui.style.display === 'none') { ui.style.display = 'block'; } else { ui.style.display = 'none'; } }); } function createAllOrbControls() { orbGroup.children = []; boneOrbs = []; if (!model || !model.skeleton) return; const bones = model.skeleton.bones; Object.keys(ESSENTIAL_BONES).forEach(japaneseName => { const bone = bones.find(b => b.name === japaneseName); if (bone) { createOrbForBone(bone, ESSENTIAL_BONES[japaneseName]); } }); console.log(`Created ${boneOrbs.length} orbs for essential bones`); } function createOrbForBone(bone, englishName) { const boneWorldPos = new THREE.Vector3(); bone.getWorldPosition(boneWorldPos); const orbGeometry = new THREE.SphereGeometry(0.8, 16, 16); let orbColor; if (englishName.includes('eye')) { orbColor = 0x6b6bff; } else if (englishName.includes('wrist') || englishName.includes('ankle')) { orbColor = 0x6bff6b; } else { orbColor = 0xff6b6b; } const orbMaterial = new THREE.MeshBasicMaterial({ color: orbColor, transparent: true, opacity: englishName.includes('eye') ? 0.5 : 0.9 }); const orb = new THREE.Mesh(orbGeometry, orbMaterial); orb.position.copy(boneWorldPos); orb.userData = { type: 'bone_control', bone: bone, boneName: englishName, originalPosition: boneWorldPos.clone() }; orbGroup.add(orb); boneOrbs.push(orb); } function setupOrbInteraction() { renderer.domElement.addEventListener('mousedown', onMouseDown); renderer.domElement.addEventListener('mousemove', onMouseMove); renderer.domElement.addEventListener('mouseup', onMouseUp); renderer.domElement.addEventListener('click', onOrbClick); // Touch events for mobile orb interaction renderer.domElement.addEventListener('touchstart', onTouchStartOrb, { passive: false }); renderer.domElement.addEventListener('touchmove', onTouchMoveOrb, { passive: false }); renderer.domElement.addEventListener('touchend', onTouchEndOrb); } function onTouchStartOrb(event) { if (event.touches.length !== 1) return; event.preventDefault(); const touch = event.touches[0]; mouse.x = (touch.clientX / window.innerWidth) * 2 - 1; mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(boneOrbs); if (intersects.length > 0) { isTouchDraggingOrb = true; touchOrb = intersects[0].object; touchOrbStartTime = Date.now(); lastTouchX = touch.clientX; lastTouchY = touch.clientY; // Show axis controls on long press (for mobile) setTimeout(() => { if (isTouchDraggingOrb && touchOrb && Date.now() - touchOrbStartTime > 500) { showAxisControls(touchOrb); } }, 500); } } function onTouchMoveOrb(event) { if (!isTouchDraggingOrb || !touchOrb || event.touches.length !== 1) return; event.preventDefault(); const touch = event.touches[0]; const deltaX = touch.clientX - lastTouchX; const deltaY = touch.clientY - lastTouchY; const bone = touchOrb.userData.bone; const boneName = touchOrb.userData.boneName; if (controlMode === 'rotation') { // Rotation mode if (boneName.includes('eye')) { bone.rotation.y += deltaX * 0.01; bone.rotation.x += deltaY * 0.01; } else { bone.rotation.y += deltaX * 0.02; bone.rotation.x += deltaY * 0.02; } bone.rotation.x = Math.max(-Math.PI, Math.min(Math.PI, bone.rotation.x)); bone.rotation.y = Math.max(-Math.PI, Math.min(Math.PI, bone.rotation.y)); bone.rotation.z = Math.max(-Math.PI, Math.min(Math.PI, bone.rotation.z)); } else { // Position mode if (boneName.includes('wrist') || boneName.includes('ankle') || boneName.includes('eye')) { bone.position.x += deltaX * 0.05; bone.position.y -= deltaY * 0.05; } } bone.updateMatrixWorld(true); updateOrbPositions(); lastTouchX = touch.clientX; lastTouchY = touch.clientY; } function onTouchEndOrb(event) { if (isTouchDraggingOrb) { // Check if it was a tap (not a drag) to show axis controls if (touchOrb && Date.now() - touchOrbStartTime < 300) { showAxisControls(touchOrb); } isTouchDraggingOrb = false; touchOrb = null; } } function onOrbClick(event) { if (event.button !== 0) return; mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(boneOrbs); if (intersects.length > 0) { const clickedOrb = intersects[0].object; showAxisControls(clickedOrb); event.stopPropagation(); } else { hideAxisControls(); } } function showAxisControls(orb) { selectedOrb = orb; const axisControls = document.getElementById('axis-controls'); axisControls.style.display = 'block'; const screenPosition = new THREE.Vector3(); orb.getWorldPosition(screenPosition); screenPosition.project(camera); const x = (screenPosition.x * 0.5 + 0.5) * window.innerWidth; const y = -(screenPosition.y * 0.5 - 0.5) * window.innerHeight; axisControls.style.left = x + 'px'; axisControls.style.top = y + 'px'; } function hideAxisControls() { selectedOrb = null; document.getElementById('axis-controls').style.display = 'none'; } function setupAxisControls() { document.getElementById('x-plus').addEventListener('click', () => moveBoneOnAxis('x', 1)); document.getElementById('x-minus').addEventListener('click', () => moveBoneOnAxis('x', -1)); document.getElementById('y-plus').addEventListener('click', () => moveBoneOnAxis('y', 1)); document.getElementById('y-minus').addEventListener('click', () => moveBoneOnAxis('y', -1)); document.getElementById('z-plus').addEventListener('click', () => moveBoneOnAxis('z', 1)); document.getElementById('z-minus').addEventListener('click', () => moveBoneOnAxis('z', -1)); // Add touch events for axis controls on mobile const axisButtons = document.querySelectorAll('.axis-btn'); axisButtons.forEach(btn => { btn.addEventListener('touchstart', function(e) { e.preventDefault(); this.click(); }, { passive: false }); }); } function moveBoneOnAxis(axis, direction) { if (!selectedOrb) return; const bone = selectedOrb.userData.bone; const boneName = selectedOrb.userData.boneName; const rotationStep = isMobile ? 0.05 : 0.1; const positionStep = isMobile ? 0.25 : 0.5; if (controlMode === 'rotation') { // Rotation mode if (boneName.includes('eye')) { bone.rotation[axis] += direction * rotationStep * 0.1; } else { bone.rotation[axis] += direction * rotationStep; } bone.rotation.x = Math.max(-Math.PI, Math.min(Math.PI, bone.rotation.x)); bone.rotation.y = Math.max(-Math.PI, Math.min(Math.PI, bone.rotation.y)); bone.rotation.z = Math.max(-Math.PI, Math.min(Math.PI, bone.rotation.z)); } else { // Position mode if (boneName.includes('wrist') || boneName.includes('ankle') || boneName.includes('eye')) { bone.position[axis] += direction * positionStep; } } bone.updateMatrixWorld(true); updateOrbPositions(); } function onMouseDown(event) { if (event.button !== 0) return; mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(boneOrbs); if (intersects.length > 0) { isDraggingOrb = true; currentOrb = intersects[0].object; renderer.domElement.classList.add('orb-dragging'); document.body.style.cursor = 'grabbing'; event.preventDefault(); event.stopPropagation(); } } function onMouseMove(event) { if (!isDraggingOrb || !currentOrb) return; const bone = currentOrb.userData.bone; const boneName = currentOrb.userData.boneName; const deltaX = event.movementX * 0.01; const deltaY = event.movementY * 0.01; if (controlMode === 'rotation') { // Rotation mode if (boneName.includes('eye')) { bone.rotation.y += deltaX * 0.1; bone.rotation.x += deltaY * 0.1; } else { bone.rotation.y += deltaX; bone.rotation.x += deltaY; } bone.rotation.x = Math.max(-Math.PI, Math.min(Math.PI, bone.rotation.x)); bone.rotation.y = Math.max(-Math.PI, Math.min(Math.PI, bone.rotation.y)); bone.rotation.z = Math.max(-Math.PI, Math.min(Math.PI, bone.rotation.z)); } else { // Position mode if (boneName.includes('wrist') || boneName.includes('ankle') || boneName.includes('eye')) { bone.position.x += deltaX * 2; bone.position.y -= deltaY * 2; } } bone.updateMatrixWorld(true); updateOrbPositions(); } function onMouseUp(event) { if (event.button === 0 && isDraggingOrb) { isDraggingOrb = false; currentOrb = null; renderer.domElement.classList.remove('orb-dragging'); document.body.style.cursor = 'default'; } } function updateOrbPositions() { boneOrbs.forEach(orb => { const bone = orb.userData.bone; const boneWorldPos = new THREE.Vector3(); bone.getWorldPosition(boneWorldPos); orb.position.copy(boneWorldPos); }); } function getCurrentPose() { if (!model || !model.skeleton) return {}; const pose = {}; const bones = model.skeleton.bones; model.updateMatrixWorld(true); Object.keys(ESSENTIAL_BONES).forEach(japaneseName => { const bone = bones.find(b => b.name === japaneseName); if (!bone) return; const bindQuat = bone.userData?.bindQuaternion ? bone.userData.bindQuaternion.clone() : new THREE.Quaternion(); // fallback identity // relative rotation: bind^-1 * current const invBind = bindQuat.clone(); if (typeof invBind.invert === "function") invBind.invert(); else invBind.inverse(); const relativeQuat = invBind.multiply(bone.quaternion).normalize(); pose[japaneseName] = { quaternion: { x: relativeQuat.x, y: relativeQuat.y, z: relativeQuat.z, w: relativeQuat.w } }; }); return pose; } // Apply a portable pose (rotation only) function applyPose(pose) { if (!model || !model.skeleton) return; const bones = model.skeleton.bones; model.updateMatrixWorld(true); Object.keys(pose).forEach(japaneseName => { const bone = bones.find(b => b.name === japaneseName); if (!bone || !pose[japaneseName]) return; const relQuat = new THREE.Quaternion( pose[japaneseName].quaternion.x, pose[japaneseName].quaternion.y, pose[japaneseName].quaternion.z, pose[japaneseName].quaternion.w ); const bindQuat = bone.userData?.bindQuaternion ? bone.userData.bindQuaternion.clone() : new THREE.Quaternion(); bone.quaternion.copy(bindQuat.clone().multiply(relQuat).normalize()); bone.updateMatrixWorld(true); }); model.skeleton.update(); updateOrbPositions(); } function exportToVPD() { if (!model || !model.skeleton) return; const bones = model.skeleton.bones; let vpdContent = "Vocaloid Pose Data file\r\n\r\n0;\r\n\r\n"; const exportBones = []; // Update all matrices first model.updateMatrixWorld(true); Object.keys(ESSENTIAL_BONES).forEach(japaneseName => { const bone = bones.find(b => b.name === japaneseName); if (!bone) return; // Get current bone quaternion const currentQuat = bone.quaternion.clone(); // Get bind pose quaternion (store once on load) const bindQuat = bone.userData?.bindQuaternion ? bone.userData.bindQuaternion.clone() : new THREE.Quaternion(); // default identity if missing // Compute relative rotation (Three.js < r150 uses inverse()) const invBind = bindQuat.clone(); if (typeof invBind.invert === "function") { invBind.invert(); } else { invBind.inverse(); } const relativeQuat = invBind.multiply(currentQuat); relativeQuat.normalize(); const rotationThreshold = 0.0001; const hasRotation = Math.abs(relativeQuat.x) > rotationThreshold || Math.abs(relativeQuat.y) > rotationThreshold || Math.abs(relativeQuat.z) > rotationThreshold || Math.abs(relativeQuat.w - 1) > rotationThreshold; if (hasRotation) { exportBones.push({ name: japaneseName, quaternion: relativeQuat, index: exportBones.length }); } }); // Update bone count vpdContent = vpdContent.replace("0;\r\n\r\n", `${exportBones.length};\r\n\r\n`); exportBones.forEach(boneData => { vpdContent += `Bone${boneData.index}{${boneData.name}\r\n`; vpdContent += " 0.000000,0.000000,0.000000;\r\n"; // Convert to MMD quaternion (flip handedness only) const q = boneData.quaternion; const mmdQuat = new THREE.Quaternion(-q.x, q.z, q.y, -q.w); mmdQuat.normalize(); vpdContent += ` ${mmdQuat.x.toFixed(6)},${mmdQuat.y.toFixed(6)},${mmdQuat.z.toFixed(6)},${mmdQuat.w.toFixed(6)};\r\n`; vpdContent += "}\r\n"; }); // Save file const BOM = '\uFEFF'; const blob = new Blob([BOM + vpdContent], { type: 'text/plain; charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'pose.vpd'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showNotification(`VPD exported! ${exportBones.length} modified bones.`); console.log("VPD Content:\n", vpdContent); console.log("Exported relative bone rotations:"); exportBones.forEach(boneData => { console.log(boneData.name, boneData.quaternion); }); } function setupUI() { const resetBtn = document.getElementById('reset-btn'); const exportBtn = document.getElementById('export-btn'); const rotationModeBtn = document.getElementById('rotation-mode'); const positionModeBtn = document.getElementById('position-mode'); const modeIndicator = document.getElementById('control-mode-indicator'); const stor1Btn = document.getElementById('stor1-btn'); const stor2Btn = document.getElementById('stor2-btn'); const load1Btn = document.getElementById('load1-btn'); const load2Btn = document.getElementById('load2-btn'); // Make buttons touch-friendly on mobile if (isMobile) { const buttons = document.querySelectorAll('button'); buttons.forEach(btn => { btn.addEventListener('touchstart', function(e) { e.preventDefault(); this.click(); }, { passive: false }); }); } resetBtn.addEventListener('click', function() { if (model && model.skeleton) { const bones = model.skeleton.bones; bones.forEach(bone => { bone.rotation.set(0, 0, 0); bone.position.set(0, 0, 0); }); model.skeleton.update(); updateOrbPositions(); hideAxisControls(); showNotification('Pose reset to default'); } }); exportBtn.addEventListener('click', exportToVPD); rotationModeBtn.addEventListener('click', function() { controlMode = 'rotation'; rotationModeBtn.classList.add('active'); positionModeBtn.classList.remove('active'); modeIndicator.textContent = 'Current Mode: Rotation'; }); positionModeBtn.addEventListener('click', function() { controlMode = 'position'; positionModeBtn.classList.add('active'); rotationModeBtn.classList.remove('active'); modeIndicator.textContent = 'Current Mode: Position'; }); stor1Btn.addEventListener('click', function() { const pose = getCurrentPose(); localStorage.setItem('stor1', JSON.stringify(pose)); showNotification('Pose saved to Slot 1'); }); stor2Btn.addEventListener('click', function() { const pose = getCurrentPose(); localStorage.setItem('stor2', JSON.stringify(pose)); showNotification('Pose saved to Slot 2'); }); load1Btn.addEventListener('click', function() { const storedPose = localStorage.getItem('stor1'); if (storedPose) { const pose = JSON.parse(storedPose); console.log('Loaded from Slot 1:', pose); applyPose(pose); showNotification('Pose loaded from Slot 1'); } else { console.log('No pose found in Slot 1'); showNotification('No pose found in Slot 1', 2000); } }); load2Btn.addEventListener('click', function() { const storedPose = localStorage.getItem('stor2'); if (storedPose) { const pose = JSON.parse(storedPose); console.log('Loaded from Slot 2:', pose); applyPose(pose); showNotification('Pose loaded from Slot 2'); } else { console.log('No pose found in Slot 2'); showNotification('No pose found in Slot 2', 2000); } }); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animate() { requestAnimationFrame(animate); controls.update(); if (!isDraggingOrb && !isTouchDraggingOrb) { updateOrbPositions(); } renderer.render(scene, camera); } </script> </body> </html> Fedi Pinned Posts ------------ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> body { font-family: Arial, sans-serif; max-width: 95%; margin: 20px auto; } #posts-container.vertical { display: block; } #posts-container.horizontal { display: flex; flex-wrap: wrap; gap: 12px; } .post { border: 1px solid #ccc; padding: 12px; border-radius: 5px; margin-bottom: 12px; flex: 1 1 300px; box-sizing: border-box; } .author { font-weight: bold; margin-bottom: 5px; } .content { margin: 10px 0; white-space: pre-wrap; } .timestamp { font-size: 0.85em; color: #666; } img.inline-media { max-width: 50%; display: block; margin: 8px 0; border-radius: 4px; } iframe.youtube { width: 100%; height: 205px; margin: 8px 0; border: none; } iframe.reply-iframe { width: 100%; border: none; min-height: 150px; margin-top: 8px; border-radius: 4px; } .view-post-link, .comment-load-link { display: inline-block; margin-top: 8px; margin-right: 10px; font-weight: bold; color: #007bff; text-decoration: none; } .view-post-link:hover, .comment-load-link:hover { text-decoration: underline; } #download-html-link { position: fixed; bottom: 10px; right: 10px; background: black; color: white; opacity: 0.45; padding: 6px 10px; border-radius: 4px; text-decoration: none; font-size: 12px; z-index: 9999; } </style> </head> <body> <h1>📌📍<a target="_blank" href="/other/extra/fetchdata/2024-03-07-FediTools/other/2025-08-31-PinnedPosts.html?orient=horizontal" style=color:black>Pinned</a>📍📌</h1> <div id="posts-container">Loading...</div> <a id="download-html-link">Download HTML</a> <script> const STORAGE_KEY = "alcea_pinned_posts"; function renderEmojis(content, emojis) { if (!emojis) return content; emojis.forEach(emoji => { const regex = new RegExp(`:${emoji.shortcode}:`, 'g'); content = content.replace( regex, `<img src="${emoji.url}" alt=":${emoji.shortcode}:" title=":${emoji.shortcode}:" style="height:1.2em;width:1.2em;vertical-align:middle;">` ); }); return content; } function enhanceLinks(html) { const div = document.createElement('div'); div.innerHTML = html; div.querySelectorAll('a').forEach(a => { const href = a.href; a.setAttribute('target', '_blank'); a.setAttribute('rel', 'noopener noreferrer'); if (/https?:\/\/(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)/.test(href)) { const videoId = href.match(/(?:v=|youtu\.be\/)([A-Za-z0-9_-]+)/)?.[1]; if (videoId) { const iframe = document.createElement('iframe'); iframe.className = 'youtube'; iframe.src = `https://www.youtube.com/embed/${videoId}`; iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'; iframe.allowFullscreen = true; a.replaceWith(iframe); } } else if (/\.(gif|jpe?g|png)$/i.test(href)) { const img = document.createElement('img'); img.src = href; img.className = 'inline-media'; a.replaceWith(img); } }); return div.innerHTML; } function renderPosts(posts, account) { const container = document.getElementById('posts-container'); container.innerHTML = ''; posts.forEach(post => { let content = post.content; content = renderEmojis(content, post.emojis); content = enhanceLinks(content); const div = document.createElement('div'); div.className = 'post'; const commentLink = `<a class="comment-load-link" href="#" onclick=" const postContentEl = document.getElementById('post-content-${post.id}'); const value = postContentEl.textContent || postContentEl.innerText || ''; const formattedText = value.replace(/\\n/g,'\\r\\n'); const slicedValue = formattedText.slice(0,22); const commentLoadUrl = 'https://alceawis.de/other/extra/scripts/fakesocialmedia/commentload.html?number=4000&text=' + encodeURIComponent(slicedValue); window.open(commentLoadUrl,'_blank'); return false; ">Load Comment</a>`; div.innerHTML = ` <div class="author">${account.display_name || account.username}</div> <div class="content" id="post-content-${post.id}">${content}</div> <div class="timestamp">${new Date(post.created_at).toLocaleString()}</div> <iframe class="reply-iframe" src="https://alceawis.de/other/extra/scripts/fakesocialmedia/replyhandler.html?url=${encodeURIComponent(post.url)}"> </iframe> <a class="view-post-link" href="${post.url}" target="_blank" rel="noopener noreferrer">View Post</a> ${commentLink} `; container.appendChild(div); const iframe = div.querySelector('.reply-iframe'); iframe.addEventListener('load', () => { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; iframe.style.height = iframeDoc.body.scrollHeight + 20 + 'px'; } catch (e) { console.warn('Could not resize iframe', e); } }); }); } async function fetchAndStorePosts() { const accountRes = await fetch('https://alceawis.com/api/v1/accounts/lookup?acct=alcea'); if (!accountRes.ok) throw new Error('Failed to lookup account'); const account = await accountRes.json(); const pinnedRes = await fetch(`https://alceawis.com/api/v1/accounts/${account.id}/statuses?pinned=true`); if (!pinnedRes.ok) throw new Error('Failed to fetch pinned posts'); const posts = await pinnedRes.json(); const payload = { account, posts, savedAt: Date.now() }; localStorage.setItem(STORAGE_KEY, JSON.stringify(payload)); return payload; } async function loadPinnedPosts() { const container = document.getElementById('posts-container'); const h1 = document.querySelector('h1'); const params = new URLSearchParams(window.location.search); const orient = params.get('orient'); container.className = orient === 'horizontal' ? 'horizontal' : 'vertical'; container.textContent = 'Loading pinned posts...'; let usedCache = false; try { let data = null; const cached = localStorage.getItem(STORAGE_KEY); if (cached) { try { data = JSON.parse(cached); usedCache = true; } catch (e) { console.warn("Invalid cached data, refetching..."); } } if (!data) { data = await fetchAndStorePosts(); usedCache = false; } if (!data.posts || !data.posts.length) { container.textContent = 'No pinned posts found.'; return; } renderPosts(data.posts, data.account); const existingIndicator = h1.querySelector('.cache-indicator'); if (usedCache) { if (!existingIndicator) { const span = document.createElement('span'); span.className = 'cache-indicator'; span.textContent = ' (from cache)'; span.style.color = '#888'; span.style.fontSize = '0.8em'; h1.appendChild(span); } } else { if (existingIndicator) existingIndicator.remove(); } } catch (err) { container.textContent = 'Error loading pinned posts: ' + err.message; console.error(err); } } // Download page as rendered HTML with UTF-8 BOM document.getElementById('download-html-link').addEventListener('click', () => { const htmlEl = document.documentElement.cloneNode(true); const dlLink = htmlEl.querySelector('#download-html-link'); if (dlLink) dlLink.remove(); const head = htmlEl.querySelector('head'); if (!head.querySelector('meta[charset]')) { const meta = document.createElement('meta'); meta.setAttribute('charset', 'UTF-8'); head.insertBefore(meta, head.firstChild); } const htmlString = '<!DOCTYPE html>\n<html>' + htmlEl.innerHTML + '</html>'; // Add UTF-8 BOM for maximum compatibility const contentWithBOM = '\uFEFF' + htmlString; const blob = new Blob([contentWithBOM], { type: 'text/html;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'pinned_posts_rendered.html'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }); document.addEventListener('DOMContentLoaded', loadPinnedPosts); </script> </body> </html> eKF insurance color (eScooter SegWay etc) ---------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>E-Scooter Registration Plate Color</title> <style> body { font-family: Arial, sans-serif; max-width: 450px; margin: 50px auto; padding: 20px; border: 2px solid #ccc; border-radius: 10px; text-align: center; } .color-box { font-size: 1.5em; font-weight: bold; } .Black { color: black; } .Blue { color: blue; } .Green { color: green; } .next small { display: block; margin-top: 5px; font-size: 0.8em; color: #555; } details { margin-top: 20px; text-align: left; } .status { margin-top: 15px; font-weight: bold; } .valid { color: green; } .upcoming { color: blue; } .expired { color: red; } .radio-group { margin-top: 10px; display: flex; gap: 10px; } .radio-group label { display: flex; align-items: center; gap: 5px; cursor: pointer; } </style> </head> <body> <h2>Current Insurance Sticker</h2> <div class="current"> Current Color: <span class="color-box" id="currentColor"></span><br> Valid for: <span id="timeLeft"></span> </div> <details> <summary>Test Sticker Color (Red, Green, Blue)</summary> (Select the color of the sticker) <div class="radio-group"> <label><input type="radio" name="colorTest" value="Red"> Black</label> <label><input type="radio" name="colorTest" value="Green"> Green</label> <label><input type="radio" name="colorTest" value="Blue"> Blue</label> </div> <div id="colorStatus" class="status"></div> <div class="next"> Next Color: <span class="color-box" id="nextColor"></span> <small>from <span id="nextStart"></span></small> </div> </details> <script> const colors = ['Black', 'Blue', 'Green']; const startYear = 2023; const today = new Date(); const currentYear = today.getMonth() >= 2 ? today.getFullYear() : today.getFullYear() - 1; const yearIndex = currentYear - startYear; const currentColor = colors[yearIndex % colors.length]; const nextColor = colors[(yearIndex + 1) % colors.length]; const endDate = new Date(currentYear + 1, 1, 28); if ((currentYear + 1) % 4 === 0 && ((currentYear + 1) % 100 !== 0 || (currentYear + 1) % 400 === 0)) { endDate.setDate(29); } const nextStart = new Date(currentYear + 1, 2, 1); const msLeft = endDate.getTime() - today.getTime(); const daysLeft = Math.floor(msLeft / (1000 * 60 * 60 * 24)); document.getElementById('currentColor').textContent = currentColor; document.getElementById('currentColor').classList.add(currentColor); document.getElementById('nextColor').textContent = nextColor; document.getElementById('nextColor').classList.add(nextColor); document.getElementById('timeLeft').textContent = daysLeft + ' days'; document.getElementById('nextStart').textContent = nextStart.toLocaleDateString('en-GB'); const radios = document.querySelectorAll('input[name="colorTest"]'); radios.forEach(radio => { radio.addEventListener('change', () => { const input = radio.value; const statusEl = document.getElementById('colorStatus'); let message = ''; let cssClass = ''; const normalized = input.charAt(0).toUpperCase() + input.slice(1).toLowerCase(); const mappedColor = normalized === 'Red' ? 'Black' : normalized; let expirationDate; if (mappedColor === 'Black') { expirationDate = endDate; } else if (mappedColor === 'Blue') { expirationDate = new Date(currentYear, 2, 28); } else if (mappedColor === 'Green') { expirationDate = new Date(currentYear, 5, 30); } const expiredMs = today.getTime() - expirationDate.getTime(); const expiredDays = Math.floor(expiredMs / (1000 * 60 * 60 * 24)); const expiredMonths = Math.floor(expiredDays / 30); if (mappedColor === currentColor) { message = 'This color is currently valid.'; cssClass = 'valid'; } else if (mappedColor === nextColor) { message = `This color will be valid soon (from ${nextStart.toLocaleDateString('en-GB')}).`; cssClass = 'upcoming'; } else { message = `This color has expired. It expired ${expiredDays} days (${expiredMonths} months) ago.`; cssClass = 'expired'; } statusEl.textContent = message; statusEl.className = 'status ' + cssClass; }); }); </script> </body> </html> Fedi find latest posts (excluding boosts) ------------ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <details><summary>Profile to post id resolve</summary> Enter a Fediverse account (either @username@instance.com or full URL like https://woof.tech/@dr_muesli)<br> <input type="text" id="accountHandle" placeholder="Enter username or full URL"> <button onclick="getFirstPost()">Find First Post</button> <div id="result"></div></details> <script> function parseHandle(handle) { let username, instance; const urlRegex = /^(https?:\/\/)([^\/]+)\/(.*)$/; const match = handle.match(urlRegex); if (match) { username = match[3].startsWith('@') ? match[3].substring(1) : match[3]; instance = match[2]; } else { const parts = handle.split('@'); if (parts.length === 2) { username = parts[0].substring(1); instance = parts[1]; } } return { username, instance }; } async function getFirstPost() { const handle = document.getElementById('accountHandle').value.trim(); if (!handle) { alert("Please enter a valid Fediverse handle."); return; } const { username, instance } = parseHandle(handle); if (!username || !instance) { alert("Invalid handle format. Please enter a valid Fediverse account or URL."); return; } try { const lookupUrl = `https://${instance}/api/v1/accounts/lookup?acct=${username}`; const lookupResponse = await fetch(lookupUrl); if (!lookupResponse.ok) { const errorText = await lookupResponse.text(); throw new Error(`Error fetching user data: ${lookupResponse.status} - ${errorText}`); } const userData = await lookupResponse.json(); const userId = userData.id; if (!userId) { throw new Error('User ID not found.'); } const statusesUrl = `https://${instance}/api/v1/accounts/${userId}/statuses`; const statusesResponse = await fetch(statusesUrl); if (!statusesResponse.ok) { const errorText = await statusesResponse.text(); throw new Error(`Error fetching posts: ${statusesResponse.status} - ${errorText}`); } const posts = await statusesResponse.json(); if (!Array.isArray(posts) || posts.length === 0) { document.getElementById('result').innerHTML = 'No posts found.'; } else { const firstPost = posts[posts.length - 1]; if (firstPost && firstPost.url) { const firstPostUrl = firstPost.url; document.getElementById('result').innerHTML = ` <p>First post URL:</p> <a href="${firstPostUrl}" target="_blank">${firstPostUrl}</a> `; } else { document.getElementById('result').innerHTML = 'No URL found for the first post.'; } } } catch (error) { document.getElementById('result').innerHTML = `Error: ${error.message}`; } } </script> <title>Fediverse Post Viewer</title> <style> body { font-family: Arial, sans-serif; padding: 20px; } input[type="text"] { width: 100%; padding: 10px; margin-bottom: 10px; } button { padding: 10px 20px; } .post { border: 1px solid #ccc; padding: 10px; margin: 10px 0; } .post-date { color: gray; font-size: 0.9em; } .post-content img { vertical-align: middle; } </style> </head> <body> <h2>Enter a Fediverse Post URL</h2> <input type="text" id="postUrlInput" placeholder="e.g. https://example.social/@user/1234567890"> <button onclick="processUrl()">Fetch Posts</button> <div id="handleDisplay"></div> <div id="errorDisplay" style="color:red;"></div> <div id="posts"></div> <button id="fetchNextBatchButton" style="display:none;" onclick="fetchNextBatch()">Fetch Next Batch</button> <script> let lastPostId = null; let userApiUrl = ''; let domain = ''; let emojis = []; function getQueryParam(name) { const params = new URLSearchParams(window.location.search); return params.get(name); } async function fetchWithFallback(url, options = {}) { try { const res = await fetch(url, options); if (!res.ok) throw new Error(`HTTP error ${res.status}`); return await res.json(); } catch (err) { console.warn('Direct fetch failed, trying proxy...'); const proxiedUrl = `https://alceawis.de/cors.php?query=${encodeURIComponent(url)}`; const proxyRes = await fetch(proxiedUrl, options); if (!proxyRes.ok) throw new Error(`Proxy fetch failed: ${proxyRes.status}`); return await proxyRes.json(); } } async function fetchEmojis(domain) { const emojiUrl = `https://${domain}/api/v1/custom_emojis`; try { return await fetchWithFallback(emojiUrl); } catch (err) { console.error('Error fetching custom emojis:', err); return []; } } function replaceEmojiShortcodes(content, emojis) { emojis.forEach(emoji => { const shortcode = `:${emoji.shortcode}:`; const emojiImg = `<img src="${emoji.url}" alt="${emoji.shortcode}" title="${emoji.shortcode}" style="width: 1.2em; height: 1.2em; vertical-align: middle;" />`; const regex = new RegExp(`(:${emoji.shortcode}:)`, 'g'); content = content.replace(regex, emojiImg); }); return content; } async function fetchPosts() { let url = userApiUrl; if (lastPostId) { url = `${url}&max_id=${lastPostId}`; } try { const posts = await fetchWithFallback(url); const container = document.getElementById('posts'); if (posts.length === 0) { document.getElementById('fetchNextBatchButton').style.display = 'none'; return; } posts.forEach(post => { const div = document.createElement('div'); div.className = 'post'; let postContent = post.content; postContent = replaceEmojiShortcodes(postContent, emojis); div.innerHTML = ` <div class="post-date">${new Date(post.created_at).toLocaleString()}</div> <div class="post-content">${postContent}</div> <a href="${post.url}" target="_blank">View Post</a> `; container.appendChild(div); lastPostId = post.id; // Update the last post ID }); document.getElementById('fetchNextBatchButton').style.display = 'inline-block'; } catch (err) { console.error(err); document.getElementById('errorDisplay').textContent = `Error: ${err.message}`; } } async function processUrl() { const input = document.getElementById('postUrlInput').value.trim(); document.getElementById('errorDisplay').textContent = ''; document.getElementById('handleDisplay').textContent = ''; document.getElementById('posts').innerHTML = ''; document.getElementById('fetchNextBatchButton').style.display = 'none'; if (!input) return; let postUrl; try { postUrl = new URL(input); } catch (e) { document.getElementById('errorDisplay').textContent = 'Invalid URL.'; return; } domain = postUrl.hostname; const pathname = postUrl.pathname; const parts = pathname.split('/'); const postId = parts[parts.length - 1]; const apiUrl = `https://${domain}/api/v1/statuses/${postId}`; try { const postData = await fetchWithFallback(apiUrl); const account = postData.account; const acct = account.acct.includes('@') ? account.acct : `${account.acct}@${domain}`; userApiUrl = `https://${domain}/api/v1/accounts/${account.id}/statuses?limit=30&exclude_reblogs=true`; document.getElementById('handleDisplay').textContent = `User Handle: ${acct}`; emojis = await fetchEmojis(domain); fetchPosts(); } catch (err) { console.error(err); document.getElementById('errorDisplay').textContent = `Error: ${err.message}`; } } function fetchNextBatch() { fetchPosts(); } window.addEventListener('load', () => { const urlParam = getQueryParam('url'); if (urlParam) { document.getElementById('postUrlInput').value = urlParam; processUrl(); } }); </script> </body> </html> Icon Overlay creator --------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Layered Image Editor</title> <style> body { font-family: sans-serif; text-align: center; margin-top: 20px; } canvas { border: 1px solid #ccc; touch-action: none; } input[type="file"] { margin: 5px; } button { margin-top: 10px; padding: 8px 16px; } </style> </head> <body> <div> <label>Top Image: <input type="file" id="topImage" accept="image/*"></label> <label>Bottom Image: <input type="file" id="bottomImage" accept="image/*"></label> <label>Background (optional): <input type="file" id="bgImage" accept="image/*"></label> </div> <button id="downloadBtn">Flatten & Download PNG</button> <br><br> <canvas id="canvas" width="512" height="512"></canvas> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const layers = { bg: { img: null, x: 0, y: 0, scale: 1 }, bottom: { img: null, x: 0, y: 0, scale: 1 }, top: { img: null, x: 0, y: 0, scale: 1 } }; let activeLayer = null; let dragging = false; let dragStart = { x: 0, y: 0 }; let lastTouches = []; function loadImage(input, key) { const file = input.files[0]; if (!file) return; const img = new Image(); img.onload = () => { layers[key].img = img; layers[key].scale = 1; layers[key].x = (canvas.width - img.width) / 2; layers[key].y = (canvas.height - img.height) / 2; draw(); }; img.src = URL.createObjectURL(file); } ['topImage', 'bottomImage', 'bgImage'].forEach(id => { document.getElementById(id).addEventListener('change', e => { const key = id.replace('Image', ''); loadImage(e.target, key); }); }); function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ['bg', 'bottom', 'top'].forEach(key => { const layer = layers[key]; if (layer.img) { const w = layer.img.width * layer.scale; const h = layer.img.height * layer.scale; ctx.drawImage(layer.img, layer.x, layer.y, w, h); } }); } function getTouchCenter(touches) { const x = (touches[0].clientX + touches[1].clientX) / 2; const y = (touches[0].clientY + touches[1].clientY) / 2; const rect = canvas.getBoundingClientRect(); return { x: x - rect.left, y: y - rect.top }; } function distance(t1, t2) { return Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY); } function getLayerAt(x, y) { const keys = ['top', 'bottom', 'bg']; for (let i = keys.length - 1; i >= 0; i--) { const key = keys[i]; const layer = layers[key]; if (!layer.img) continue; const w = layer.img.width * layer.scale; const h = layer.img.height * layer.scale; if (x >= layer.x && x <= layer.x + w && y >= layer.y && y <= layer.y + h) { return key; } } return null; } canvas.addEventListener('mousedown', e => { const x = e.offsetX; const y = e.offsetY; activeLayer = getLayerAt(x, y); if (activeLayer) { dragging = true; dragStart = { x, y }; } }); canvas.addEventListener('mousemove', e => { if (!dragging || !activeLayer) return; const dx = e.offsetX - dragStart.x; const dy = e.offsetY - dragStart.y; dragStart = { x: e.offsetX, y: e.offsetY }; layers[activeLayer].x += dx; layers[activeLayer].y += dy; draw(); }); canvas.addEventListener('mouseup', () => { dragging = false; }); canvas.addEventListener('wheel', e => { const x = e.offsetX; const y = e.offsetY; const key = getLayerAt(x, y); if (!key) return; e.preventDefault(); const layer = layers[key]; const zoomFactor = 1.05; const scaleChange = e.deltaY < 0 ? zoomFactor : 1 / zoomFactor; const oldScale = layer.scale; layer.scale *= scaleChange; const newScale = layer.scale; const dx = x - layer.x; const dy = y - layer.y; layer.x -= dx * (newScale - oldScale) / oldScale; layer.y -= dy * (newScale - oldScale) / oldScale; draw(); }); canvas.addEventListener('touchstart', e => { e.preventDefault(); lastTouches = [...e.touches]; const touch = e.touches[0]; const rect = canvas.getBoundingClientRect(); const x = touch.clientX - rect.left; const y = touch.clientY - rect.top; activeLayer = getLayerAt(x, y); }, { passive: false }); canvas.addEventListener('touchmove', e => { e.preventDefault(); const touches = [...e.touches]; const rect = canvas.getBoundingClientRect(); const layer = layers[activeLayer]; if (!layer) return; if (touches.length === 1 && lastTouches.length === 1) { const dx = touches[0].clientX - lastTouches[0].clientX; const dy = touches[0].clientY - lastTouches[0].clientY; layer.x += dx; layer.y += dy; } else if (touches.length === 2 && lastTouches.length === 2) { const prevDist = distance(lastTouches[0], lastTouches[1]); const newDist = distance(touches[0], touches[1]); const scaleChange = newDist / prevDist; const center = getTouchCenter(touches); const dx = center.x - layer.x; const dy = center.y - layer.y; const oldScale = layer.scale; layer.scale *= scaleChange; layer.x -= dx * (layer.scale - oldScale) / oldScale; layer.y -= dy * (layer.scale - oldScale) / oldScale; } lastTouches = touches; draw(); }, { passive: false }); canvas.addEventListener('touchend', () => { lastTouches = []; }); document.getElementById('downloadBtn').addEventListener('click', () => { const tempCanvas = document.createElement('canvas'); tempCanvas.width = 512; tempCanvas.height = 512; const tempCtx = tempCanvas.getContext('2d'); ['bg', 'bottom', 'top'].forEach(key => { const layer = layers[key]; if (layer.img) { const w = layer.img.width * layer.scale; const h = layer.img.height * layer.scale; tempCtx.drawImage(layer.img, layer.x, layer.y, w, h); } }); const link = document.createElement('a'); link.download = 'flattened.png'; link.href = tempCanvas.toDataURL('image/png'); link.click(); }); </script> </body> </html> Html source url/file find (find facebook mp4 url) -------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>File Link Extractor</title> </head> <body> <h1>1) Upload html file</h1> <!-- File Picker --> <input type="file" id="fileInput" /> <!-- Textbox for extension --> <label for="extInput">Extension (e.g. .mp4): </label> <input type="text" id="extInput" value=".mp4" /> <div id="output"></div> <h2>2.) paste url and fix it</h2> <!-- Textbox for raw input --> <textarea id="rawInput" rows="10" cols="80" placeholder="Paste raw text containing URLs here..."></textarea> <br /> <button onclick="processRawText()">Process Raw Text</button> <h2>Decoded URL:</h2> <textarea id="decodedUrlOutput" rows="3" cols="80" readonly></textarea> <script> // When the file is selected document.getElementById('fileInput').addEventListener('change', handleFileSelect, false); // Handle file selection function handleFileSelect(event) { const file = event.target.files[0]; const ext = document.getElementById('extInput').value.trim(); // Get the user-defined extension if (file) { const reader = new FileReader(); // Read file as text reader.onload = function(e) { const fileContent = e.target.result; extractLinks(fileContent, ext); }; reader.readAsText(file); } else { alert("Please upload a valid file."); } } // Extract links from the content based on extension function extractLinks(fileContent, ext) { const links = []; let startIdx = 0; const extLower = ext.toLowerCase(); // Make sure extension is case-insensitive while ((startIdx = fileContent.indexOf(extLower, startIdx)) !== -1) { // Move backwards to find the start of the URL (https) let httpsIdx = fileContent.lastIndexOf('https', startIdx); if (httpsIdx !== -1) { // Extract from https to the first double quote (") let url = fileContent.slice(httpsIdx, fileContent.indexOf('"', httpsIdx)); links.push(url); } startIdx += extLower.length; // Move past the current extension to search for the next one } // Display the results const output = document.getElementById('output'); output.innerHTML = ''; if (links.length > 0) { output.innerHTML = '<h2>Links Found:</h2>'; const ul = document.createElement('ul'); links.forEach(link => { const li = document.createElement('li'); const linkText = document.createElement('span'); linkText.textContent = link; // Create the copy button const copyButton = document.createElement('button'); copyButton.textContent = 'Copy'; copyButton.onclick = () => copyToClipboard(link); li.appendChild(linkText); li.appendChild(copyButton); ul.appendChild(li); }); output.appendChild(ul); } else { output.innerHTML = '<p>No links found with the specified extension.</p>'; } } // Function to copy the link to the clipboard function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { alert('Link copied to clipboard!'); }).catch(err => { alert('Error copying to clipboard: ' + err); }); } // Process the raw input text and fix the URLs function processRawText() { const rawText = document.getElementById('rawInput').value; const fixedUrl = fixUrl(rawText); const output = document.getElementById('output'); output.innerHTML = ''; if (fixedUrl) { output.innerHTML = `<h2>Fixed URL:</h2> <p><a href="${fixedUrl}" target="_blank">${fixedUrl}</a></p>`; // Display the fixed URL in the decoded URL output area document.getElementById('decodedUrlOutput').value = fixedUrl; } else { output.innerHTML = '<p>No valid URL found in the raw text.</p>'; } } // Fix and decode the URL function fixUrl(rawText) { // Regular expression to find URLs starting with https:// const regex = /https:\/\/[^\s"]+/g; let match = regex.exec(rawText); if (match) { let url = match[0]; // Decode escape sequences like \/ -> / url = url.replace(/\\\//g, '/'); // Decode percent-encoded characters (e.g., \u00253D -> =) url = decodeURIComponent(url); // Check if it's a valid URL and return it try { new URL(url); // Check if the URL is valid return url; } catch (e) { console.error('Invalid URL:', url); return null; } } return null; } </script> </body> </html> Anti PayWall(s) ----------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Smart URL Proxy Router</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; line-height: 1.6; color: #333; } .container { background-color: #f5f5f5; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } input[type="url"] { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; font-size: 16px; } button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; margin-right: 10px; margin-bottom: 10px; } button:hover { background-color: #45a049; } button.secondary { background-color: #2196F3; } button.secondary:hover { background-color: #0b7dda; } select { padding: 8px; border-radius: 4px; border: 1px solid #ddd; font-size: 16px; margin: 10px 0; width: 100%; } .bookmarklet { background-color: #e9f7ef; padding: 15px; border-radius: 4px; margin-top: 10px; word-break: break-all; cursor: pointer; font-family: monospace; } .bookmarklet:hover { background-color: #d4edda; } .instructions { margin-top: 20px; font-size: 14px; color: #555; } .service-info { margin-top: 15px; padding: 10px; background-color: #e7f3fe; border-radius: 4px; display: none; } .active { font-weight: bold; color: #2c7be5; } .section { margin-top: 25px; padding-top: 15px; border-top: 1px solid #eee; } h2 { color: #2c7be5; margin-bottom: 10px; } </style> </head> <body> <div class="container"> <h1>Smart URL Proxy Router</h1> <p>Open any URL through the most appropriate proxy service:</p> <select id="serviceSelector"> <option value="auto">Auto-Select (Smart Proxy)</option> <option value="freedium">Freedium</option> <option value="12ft">12ft.io</option> <option value="archive">Archive.today</option> <option value="libmedium">LibMedium</option> <option value="scribe">Scribe.rip</option> <option value="threadreader">ThreadReaderApp</option> <option value="googlecache">Google Cache</option> </select> <div id="serviceInfo" class="service-info"> <!-- Service information will be displayed here --> </div> <input type="url" id="urlInput" placeholder="https://example.com/article" autofocus> <button onclick="openProxy()">Open via Selected Proxy</button> <button class="secondary" onclick="rotateService()">Rotate to Next Service</button> <div class="section"> <h2>Smart Universal Bookmarklet</h2> <p>This bookmarklet automatically selects the best proxy based on the current website:</p> <div id="smartBookmarklet" class="bookmarklet" onclick="copyBookmarklet('smart')" title="Click to copy"> <!-- Smart bookmarklet code will be inserted here --> </div> <p class="instructions">Drag this link to your bookmarks bar to create a smart one-click proxy button that works everywhere.</p> </div> <div class="section"> <h2>Specific Service Bookmarklet</h2> <p>Bookmarklet for currently selected service:</p> <div id="bookmarkletCode" class="bookmarklet" onclick="copyBookmarklet('current')" title="Click to copy"> <!-- Current service bookmarklet will be inserted here --> </div> <p class="instructions">Drag this link to your bookmarks bar to always use the selected proxy service.</p> </div> </div> <script> // Proxy services configuration const services = [ { id: "auto", name: "Auto-Select", url: "", info: "Smart proxy that automatically selects the best service for the current website." }, { id: "freedium", name: "Freedium", url: "https://freedium.cfd/", info: "Bypasses Medium paywall by showing cached versions of articles.", domains: ["medium.com", "towardsdatascience.com"] }, { id: "12ft", name: "12ft.io", url: "https://12ft.io/", info: "General purpose paywall remover that works on many sites including Medium, Bloomberg, etc.", domains: ["bloomberg.com", "washingtonpost.com", "ft.com"] }, { id: "archive", name: "Archive.today", url: "https://archive.today/?run=1&url=", info: "Creates permanent archived copies of web pages, bypassing paywalls.", domains: ["nytimes.com", "wsj.com", "economist.com"] }, { id: "libmedium", name: "LibMedium", url: "https://libmedium.batsense.net/", info: "Alternative Medium proxy focused on privacy and accessibility.", domains: ["medium.com"] }, { id: "scribe", name: "Scribe.rip", url: "https://scribe.rip/", info: "Another Medium-specific proxy that reformats articles for better readability.", domains: ["medium.com"] }, { id: "threadreader", name: "ThreadReader", url: "https://threadreaderapp.com/thread/", info: "For unrolling Twitter threads (only works with Twitter URLs).", domains: ["twitter.com", "x.com"] }, { id: "googlecache", name: "Google Cache", url: "https://webcache.googleusercontent.com/search?q=cache:", info: "Google's cached version of pages (when available).", domains: ["*"] } ]; let currentServiceIndex = 1; // Start with Freedium as default // Initialize the page function init() { updateServiceInfo(); updateBookmarklet(); updateSmartBookmarklet(); } // Open URL with selected proxy service function openProxy() { const url = document.getElementById('urlInput').value.trim(); if (!url) { alert('Please enter a URL'); return; } try { new URL(url); } catch (e) { alert('Please enter a valid URL (include http:// or https://)'); return; } const serviceId = document.getElementById('serviceSelector').value; let service; if (serviceId === "auto") { service = autoSelectService(url); } else { service = services.find(s => s.id === serviceId); } let proxyUrl; // Special handling for certain services if (service.id === "threadreader") { const tweetId = extractTweetId(url); if (tweetId) { proxyUrl = `${service.url}${tweetId}`; } else { alert("ThreadReader only works with Twitter URLs"); return; } } else { proxyUrl = `${service.url}${url}`; } window.open(proxyUrl, '_blank'); } // Auto-select the best service based on URL domain function autoSelectService(url) { const urlObj = new URL(url); const hostname = urlObj.hostname.replace('www.', ''); // Check for specific domain matches first for (const service of services) { if (service.domains) { for (const domain of service.domains) { if (hostname === domain || (domain === '*' && service.id !== 'auto') || hostname.endsWith('.' + domain)) { return service; } } } } // Default to 12ft.io for general paywalls, then archive.today return services.find(s => s.id === "12ft") || services.find(s => s.id === "archive"); } // Extract tweet ID from Twitter URL function extractTweetId(url) { const twitterRegex = /twitter\.com\/\w+\/status\/(\d+)/; const match = url.match(twitterRegex); return match ? match[1] : null; } // Rotate to next service function rotateService() { currentServiceIndex = (currentServiceIndex + 1) % services.length; if (services[currentServiceIndex].id === "auto") { currentServiceIndex = (currentServiceIndex + 1) % services.length; } document.getElementById('serviceSelector').value = services[currentServiceIndex].id; updateServiceInfo(); updateBookmarklet(); } // Update service info display function updateServiceInfo() { const serviceId = document.getElementById('serviceSelector').value; const service = serviceId === "auto" ? services[0] : services.find(s => s.id === serviceId); const infoDiv = document.getElementById('serviceInfo'); infoDiv.innerHTML = `<strong>${service.name}</strong>: ${service.info}`; infoDiv.style.display = 'block'; } // Update bookmarklet code for current service function updateBookmarklet() { const serviceId = document.getElementById('serviceSelector').value; const service = services.find(s => s.id === serviceId); let bookmarkletCode; if (service.id === "threadreader") { bookmarkletCode = `javascript:(function(){const tweetId=location.href.match(/twitter\\.com\\/\\w+\\/status\\/(\\d+)/);if(tweetId){window.open('${service.url}'+tweetId[1]);}else{alert('This only works on Twitter thread pages');}})();`; } else if (service.id === "auto") { bookmarkletCode = document.getElementById('smartBookmarklet').textContent; } else { bookmarkletCode = `javascript:(function(){window.open('${service.url}'+location.href);})();`; } document.getElementById('bookmarkletCode').textContent = bookmarkletCode; } // Create the smart universal bookmarklet function updateSmartBookmarklet() { const smartBookmarklet = `javascript:(function(){ const url = location.href; const hostname = new URL(url).hostname.replace('www.', ''); // Service matching logic ${services.filter(s => s.id !== 'auto').map(service => { if (!service.domains) return ''; const domainChecks = service.domains.map(domain => { if (domain === '*') return 'true'; return `hostname === '${domain}' || hostname.endsWith('.${domain}')`; }).join(' || '); return `if (${domainChecks}) { window.open('${service.url}' + url); return; }`; }).join('\n')} // Default services window.open('https://12ft.io/' + url); })();`; document.getElementById('smartBookmarklet').textContent = smartBookmarklet; } // Copy bookmarklet to clipboard function copyBookmarklet(type) { const elementId = type === 'smart' ? 'smartBookmarklet' : 'bookmarkletCode'; const bookmarklet = document.getElementById(elementId).textContent; navigator.clipboard.writeText(bookmarklet).then(() => { alert('Bookmarklet copied to clipboard! Drag it to your bookmarks bar.'); }).catch(err => { console.error('Failed to copy: ', err); prompt("Copy this bookmarklet code:", bookmarklet); }); } // Event listeners document.getElementById('serviceSelector').addEventListener('change', function() { updateServiceInfo(); updateBookmarklet(); }); document.getElementById('urlInput').addEventListener('keypress', function(e) { if (e.key === 'Enter') { openProxy(); } }); // Initialize on load window.onload = init; </script> </body> </html> MFM Markdown (Akkoma/Misskey) --------------------------- <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>MFM Builder — Interactive</title> <style> :root{--accent:#7c3aed;--muted:#6b7280;--bg:#0b1220;--card:#071022;--glass:rgba(255,255,255,0.02)} html,body{height:100%;margin:0;font-family:Inter,system-ui,Segoe UI,Roboto,Arial;background:linear-gradient(180deg,#041022,#071224);color:#e6eef8} .wrap{max-width:1100px;margin:20px auto;padding:18px} h1{margin:0 0 8px;font-size:18px;color:var(--accent)} p.lead{margin:0 0 16px;color:var(--muted);font-size:13px} .layout{display:grid;grid-template-columns:1fr 420px;gap:14px} .card{background:linear-gradient(180deg,rgba(255,255,255,0.02),rgba(255,255,255,0.01));border:1px solid rgba(255,255,255,0.03);padding:12px;border-radius:10px;box-shadow:0 6px 20px rgba(2,6,23,0.6)} label.block{display:block;margin-bottom:6px;font-size:13px;color:var(--muted)} textarea#input{text-rendering:optimizeLegibility;width:100%;min-height:120px;border-radius:8px;padding:10px;background:transparent;border:1px solid rgba(255,255,255,0.03);color:#dbeafe;font-family:inherit;resize:vertical} .toolbar{display:flex;flex-direction:column;gap:8px} .group{display:flex;flex-wrap:wrap;gap:8px} button.btn{appearance:none;border:0;padding:8px 10px;border-radius:8px;background:rgba(255,255,255,0.02);color:#dbeafe;cursor:pointer;font-weight:600} button.btn.alt{background:transparent;border:1px solid rgba(255,255,255,0.04);color:var(--muted);font-weight:600} .small{font-size:12px;color:var(--muted)} input[type="text"], input[type="number"], select{padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,0.03);background:transparent;color:#dbeafe} .row{display:flex;gap:8px;align-items:center} .preview-area{display:flex;flex-direction:column;gap:8px} textarea#mfm{height:120px;width:100%;border-radius:8px;padding:10px;background:transparent;border:1px solid rgba(255,255,255,0.03);color:#dbeafe;font-family:ui-monospace,Menlo,monospace} .actions{display:flex;gap:8px;justify-content:space-between;align-items:center} .muted{color:var(--muted);font-size:12px} footer{margin-top:12px;color:var(--muted);font-size:12px} @media (max-width:980px){.layout{grid-template-columns:1fr;}} /* MFM preview specific styles */ .mfm-preview blockquote{border-left:3px solid var(--accent);padding-left:12px;margin:8px 0;opacity:0.8} .mfm-preview pre{background:rgba(255,255,255,0.05);padding:10px;border-radius:6px;overflow-x:auto} .mfm-preview code{font-family:ui-monospace,Menlo,monospace;background:rgba(255,255,255,0.05);padding:2px 4px;border-radius:3px;font-size:0.9em} .mfm-preview .mfm-spin{display:inline-block;animation:mfm-spin 1.5s linear infinite} .mfm-preview .mfm-spin.x{display:inline-block;animation:mfm-spin-x 1.5s linear infinite} .mfm-preview .mfm-spin.y{display:inline-block;animation:mfm-spin-y 1.5s linear infinite} .mfm-preview .mfm-jump{display:inline-block;animation:mfm-jump 0.75s linear infinite} .mfm-preview .mfm-bounce{display:inline-block;animation:mfm-bounce 0.75s linear infinite} .mfm-preview .mfm-shake{display:inline-block;animation:mfm-shake 0.5s ease infinite} .mfm-preview .mfm-twitch{display:inline-block;animation:mfm-twitch 0.5s ease infinite} .mfm-preview .mfm-rainbow{animation:mfm-rainbow 3s linear infinite} .mfm-preview .mfm-sparkle{position:relative;display:inline-block} .mfm-preview .mfm-flip{transform:scale(-1,-1);display:inline-block} .mfm-preview .mfm-flip.h{transform:scale(-1,1);display:inline-block} .mfm-preview .mfm-flip.v{transform:scale(1,-1);display:inline-block} .mfm-preview .mfm-x2{transform:scale(2);display:inline-block;transform-origin:left center} .mfm-preview .mfm-x3{transform:scale(3);display:inline-block;transform-origin:left center} .mfm-preview .mfm-x4{transform:scale(4);display:inline-block;transform-origin:left center} @keyframes mfm-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}} @keyframes mfm-spin-x{0%{transform:rotateX(0deg)}100%{transform:rotateX(360deg)}} @keyframes mfm-spin-y{0%{transform:rotateY(0deg)}100%{transform:rotateY(360deg)}} @keyframes mfm-jump{0%,100%{transform:translateY(0)}50%{transform:translateY(-0.5em)}} @keyframes mfm-bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-0.25em)}} @keyframes mfm-shake{0%{transform:translateX(0)}25%{transform:translateX(-5px)}50%{transform:translateX(0)}75%{transform:translateX(5px)}100%{transform:translateX(0)}} @keyframes mfm-twitch{0%{transform:translateX(0) rotate(0deg)}5%{transform:translateX(0) rotate(0deg)}6%{transform:translateX(0) rotate(-3deg)}10%{transform:translateX(0) rotate(3deg)}15%{transform:translateX(0) rotate(0deg)}} @keyframes mfm-rainbow{0%{color:#ff0000}14%{color:#ff7f00}28%{color:#ffff00}42%{color:#00ff00}56%{color:#0000ff}70%{color:#4b0082}84%{color:#9400d3}100%{color:#ff0000}} </style> </head> <body> <div class="wrap"> <h1>MFM Builder</h1> <p class="lead">Type text, apply modifiers from groups, preview MFM source, then copy to clipboard.</p> <div class="layout"> <div class="card"> <label class="block">Input text (you can select part of text and apply modifiers to selection)</label> <textarea id="input" placeholder="Type here — e.g. MisskeyでFediverse 🍮"></textarea> <div style="height:10px"></div> <div class="toolbar"> <div> <div class="small" style="margin-bottom:6px">Formatting</div> <div class="group" id="format-group"> <button class="btn" data-action="wrap" data-value="**{t}**">Bold</button> <button class="btn" data-action="wrap" data-value="~~{t}~~">Strikethrough</button> <button class="btn" data-action="wrap" data-value="`{t}`">Inline code</button> <button class="btn" data-action="wrap" data-value="> {t}">Quote</button> <button class="btn" data-action="wrap" data-value="<small>{t}</small>">Small</button> <button class="btn" data-action="wrap" data-value="<center>{t}</center>">Center</button> <button class="btn" data-action="wrap" data-value=":$:{t}:$:">CustomEmoji (name)</button> </div> </div> <div> <div class="small" style="margin-bottom:6px">Transforms / Layout</div> <div class="group" id="transform-group"> <button class="btn" data-action="wrap" data-value="$[flip {t}]">Flip</button> <button class="btn" data-action="wrap" data-value="$[flip.h {t}]">Flip H</button> <button class="btn" data-action="wrap" data-value="$[flip.v {t}]">Flip V</button> <button class="btn" data-action="wrap" data-value="$[rotate.deg={deg} {t}]">Rotate</button> <button class="btn" data-action="wrap" data-value="$[position.x={px} {t}]">Shift X</button> <button class="btn" data-action="wrap" data-value="$[position.y={py} {t}]">Shift Y</button> <button class="btn" data-action="wrap" data-value="$[scale.x={sx},y={sy} {t}]">Scale</button> </div> <div class="row" style="margin-top:6px;gap:10px"> <input type="number" id="deg" placeholder="deg" style="width:86px"/> <input type="text" id="px" placeholder="x offset (e.g. 1.5)" style="width:120px"/> <input type="text" id="py" placeholder="y offset" style="width:120px"/> <input type="text" id="sx" placeholder="scale x" style="width:86px"/> <input type="text" id="sy" placeholder="scale y" style="width:86px"/> </div> </div> <div> <div class="small" style="margin-bottom:6px">Animations / Effects</div> <div class="group" id="anim-group"> <button class="btn" data-action="wrap" data-value="$[jelly {t}]">Jelly</button> <button class="btn" data-action="wrap" data-value="$[tada {t}]">Tada</button> <button class="btn" data-action="wrap" data-value="$[jump {t}]">Jump</button> <button class="btn" data-action="wrap" data-value="$[bounce {t}]">Bounce</button> <button class="btn" data-action="wrap" data-value="$[spin {t}]">Spin</button> <button class="btn" data-action="wrap" data-value="$[spin.x {t}]">Spin X</button> <button class="btn" data-action="wrap" data-value="$[spin.y {t}]">Spin Y</button> <button class="btn" data-action="wrap" data-value="$[shake {t}]">Shake</button> <button class="btn" data-action="wrap" data-value="$[twitch {t}]">Twitch</button> <button class="btn" data-action="wrap" data-value="$[sparkle {t}]">Sparkle</button> </div> <div style="margin-top:6px" class="group"> <button class="btn" data-action="wrap" data-value="$[rainbow {t}]">Rainbow</button> <button class="btn" data-action="wrap" data-value="$[x2 {t}]">$[x2]</button> <button class="btn" data-action="wrap" data-value="$[x3 {t}]">$[x3]</button> <button class="btn" data-action="wrap" data-value="$[x4 {t}]">$[x4]</button> </div> </div> <div> <div class="small" style="margin-bottom:6px">Decorations / Border / Color</div> <div class="group"> <button class="btn" data-action="wrap" data-value="$[border.style=solid,width=4 {t}]">Border solid</button> <button class="btn" data-action="wrap" data-value="$[border.color={bcol} {t}]">Border color</button> <button class="btn" data-action="wrap" data-value="$[fg.color={fcol} {t}]">FG color</button> <button class="btn" data-action="wrap" data-value="$[bg.color={bkg} {t}]">BG color</button> <button class="btn" data-action="wrap" data-value="$[blur {t}]">Blur</button> </div> <div class="row" style="margin-top:6px"> <input type="text" id="bcol" placeholder="border color (hex like f00)" style="width:120px"/> <input type="text" id="fcol" placeholder="fg color (hex)" style="width:120px"/> <input type="text" id="bkg" placeholder="bg color (hex)" style="width:120px"/> </div> </div> <div> <div class="small" style="margin-bottom:6px">Advanced / Others</div> <div class="group"> <button class="btn" data-action="wrap" data-value="$[ruby {t} {ruby}]">$[ruby]</button> <button class="btn" data-action="wrap" data-value="` ```{lang}\n{t}\n``` `">Code block</button> <button class="btn" data-action="wrap" data-value="[{text}]({url})">Link</button> <button class="btn" data-action="wrap" data-value="?[{text}]({url})">Hidden preview link</button> </div> <div class="row" style="margin-top:6px"> <input type="text" id="ruby" placeholder="ruby text" style="width:180px"/> <input type="text" id="lang" placeholder="lang id (e.g. ais)" style="width:120px"/> <input type="text" id="url" placeholder="https://..." style="width:180px"/> </div> </div> </div> </div> <div class="card preview-area"> <div> <label class="block">Generated MFM source</label> <textarea id="mfm" readonly placeholder="MFM result will appear here"></textarea> </div> <div class="actions"> <div style="display:flex;gap:8px"> <button id="copy" class="btn">Copy MFM</button> <button id="reset" class="btn alt">Reset</button> <button id="wrap-selection" class="btn alt">Wrap selection with custom</button> </div> <div style="text-align:right"> <div class="muted" id="status">Ready</div> </div> </div> <div style="margin-top:10px"> <label class="block">Preview (best-effort plain preview — actual Misskey client renders MFM differently)</label> <div id="visualPreview" class="mfm-preview" style="min-height:80px;padding:10px;border-radius:8px;background:var(--glass);border:1px solid rgba(255,255,255,0.03);color:#dbeafe;font-size:14px"></div> </div> <div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;align-items:center"> <div class="small">Auto-update MFM:</div> <label class="small"><input type="checkbox" id="auto" checked/> live</label> </div> </div> </div> <footer>Single-file. Works offline. Uses navigator.clipboard with fallback.</footer> </div> <script> /* Full client-side MFM builder logic. - Buttons with data-action="wrap" and data-value use templates where {t} is text, and other placeholders (deg, px, py, sx, sy, bcol, fcol, bkg, ruby, lang, url, text) are replaced with inputs from the toolbar. - If selection exists in #input, wrap selection; otherwise wrap entire input. - Live updates MFM output and a simple visual preview (plain text with some replacements). */ /* helpers */ function el(q, root=document) { return root.querySelector(q); } function els(q, root=document) { return Array.from(root.querySelectorAll(q)); } function setStatus(txt, ok=true) { const s = el('#status'); s.textContent = txt; s.style.color = ok ? '' : '#ff9b9b'; } /* get values */ function getVal(id){ const e = el('#'+id); return e ? e.value : ''; } /* create wrapper with template */ function buildWrapper(template, text) { if (!template) return text; // gather replacements const repl = { t: text, deg: getVal('deg'), px: getVal('px'), py: getVal('py'), sx: getVal('sx') || '2', sy: getVal('sy') || getVal('sx') || '2', bcol: getVal('bcol'), fcol: getVal('fcol'), bkg: getVal('bkg'), ruby: getVal('ruby'), lang: getVal('lang'), url: getVal('url'), text: text }; // For code block template that used backticks wrappers we may have weird spacing; allow {lang} insertion. let out = template; Object.keys(repl).forEach(k => { const v = repl[k] === undefined ? '' : repl[k]; // escape braces in replacement value for safe replacement out = out.split('{' + k + '}').join(v); }); return out; } /* apply wrapper to selection or whole input */ function applyWrapperToInput(template) { const ta = el('#input'); ta.focus(); const start = ta.selectionStart; const end = ta.selectionEnd; const before = ta.value.slice(0, start); const sel = (start !== end) ? ta.value.slice(start, end) : ta.value.slice(0); // if no selection, will wrap whole text (from 0) // If no selection and there is text, wrap entire text; if empty, insert placeholder const textToWrap = sel.length ? sel : 'your text'; const wrapped = buildWrapper(template, textToWrap); if (start !== end) { ta.value = before + wrapped + ta.value.slice(end); // reselect newly wrapped content ta.setSelectionRange(before.length, before.length + wrapped.length); } else { // no selection: replace entire or insert at cursor if (ta.value.length) { // wrap entire value ta.value = wrapped; ta.setSelectionRange(0, wrapped.length); } else { // insert placeholder wrapped at cursor ta.value = wrapped; ta.setSelectionRange(0, wrapped.length); } } updateAll(); } /* take a template that may be a link template like "[{text}]({url})" - support special flow: if template contains "[{text}]" then the tool expects 'text' and 'url' inputs. */ function handleWrapButton(btn) { const template = btn.getAttribute('data-value'); applyWrapperToInput(template); } /* Custom MFM parser for preview */ function parseMFM(text) { if (!text) return ''; // First parse code blocks to prevent interpreting their contents as MFM const codeBlockRegex = /```([a-z]*)\n([\s\S]*?)\n```/g; text = text.replace(codeBlockRegex, (match, lang, code) => { return `<pre><code class="language-${lang}">${escapeHTML(code)}</code></pre>`; }); // Then parse inline code text = text.replace(/`([^`]+)`/g, (match, code) => { return `<code>${escapeHTML(code)}</code>`; }); // Parse MFM functions ($[...]) const mfmFuncRegex = /\$\[([a-z.]+)(?:=([^\]]+))? (.*?)\]/g; text = text.replace(mfmFuncRegex, (match, func, params, content) => { content = parseMFM(content); // Recursively parse nested content switch(func) { case 'flip': return `<span class="mfm-flip">${content}</span>`; case 'flip.h': return `<span class="mfm-flip h">${content}</span>`; case 'flip.v': return `<span class="mfm-flip v">${content}</span>`; case 'spin': return `<span class="mfm-spin">${content}</span>`; case 'spin.x': return `<span class="mfm-spin x">${content}</span>`; case 'spin.y': return `<span class="mfm-spin y">${content}</span>`; case 'jump': return `<span class="mfm-jump">${content}</span>`; case 'bounce': return `<span class="mfm-bounce">${content}</span>`; case 'shake': return `<span class="mfm-shake">${content}</span>`; case 'twitch': return `<span class="mfm-twitch">${content}</span>`; case 'rainbow': return `<span class="mfm-rainbow">${content}</span>`; case 'x2': return `<span class="mfm-x2">${content}</span>`; case 'x3': return `<span class="mfm-x3">${content}</span>`; case 'x4': return `<span class="mfm-x4">${content}</span>`; case 'position.x': case 'position.y': // Position is tricky to preview - just show content return content; case 'rotate.deg': const deg = params ? params.split('=')[1] : '0'; return `<span style="display:inline-block;transform:rotate(${deg}deg)">${content}</span>`; case 'scale.x': case 'scale.y': // Scale params might be x=2,y=3 or just x=2 let scaleX = '1', scaleY = '1'; if (params) { const parts = params.split(','); parts.forEach(part => { const [key, value] = part.split('='); if (key === 'x') scaleX = value; if (key === 'y') scaleY = value; }); } return `<span style="display:inline-block;transform:scale(${scaleX},${scaleY})">${content}</span>`; case 'fg.color': const fgColor = params || 'inherit'; return `<span style="color:#${fgColor}">${content}</span>`; case 'bg.color': const bgColor = params || 'transparent'; return `<span style="background-color:#${bgColor};padding:0 2px;border-radius:3px">${content}</span>`; case 'border.color': const borderColor = params || 'currentColor'; return `<span style="border:1px solid #${borderColor};padding:0 2px;border-radius:3px">${content}</span>`; case 'border.style': // Parse border style params (style=solid,width=4) let borderStyle = 'solid', borderWidth = '1px'; if (params) { const parts = params.split(','); parts.forEach(part => { const [key, value] = part.split('='); if (key === 'style') borderStyle = value; if (key === 'width') borderWidth = value + 'px'; }); } return `<span style="border:${borderWidth} ${borderStyle} currentColor;padding:0 2px;border-radius:3px">${content}</span>`; case 'ruby': // Ruby text is in the content part (content is "base ruby") const parts = content.split(/\s+/); if (parts.length >= 2) { const base = parts.slice(0, -1).join(' '); const ruby = parts[parts.length - 1]; return `<ruby>${base}<rt>${ruby}</rt></ruby>`; } return content; default: return content; } }); // Parse markdown-like syntax text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); text = text.replace(/~~(.*?)~~/g, '<del>$1</del>'); text = text.replace(/> (.*)/g, '<blockquote>$1</blockquote>'); text = text.replace(/<small>(.*?)<\/small>/g, '<small>$1</small>'); text = text.replace(/<center>(.*?)<\/center>/g, '<div style="text-align:center">$1</div>'); // Parse links text = text.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'); text = text.replace(/\?\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" style="text-decoration:none;color:inherit">$1</a>'); // Parse custom emoji text = text.replace(/:(\w+):/g, '<span style="display:inline-block;background:rgba(255,255,255,0.1);border-radius:4px;padding:0 2px">:$1:</span>'); return text; } function escapeHTML(str) { return str.replace(/[&<>'"]/g, tag => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' }[tag])); } /* update MFM textarea from input */ function updateAll() { const input = el('#input').value; // For now the "generated MFM source" is the raw input (modifiers are applied by buttons) el('#mfm').value = input; // Use our custom MFM parser for the preview el('#visualPreview').innerHTML = parseMFM(input); } /* copy to clipboard with fallback */ async function copyText(text) { if (!text) return false; if (navigator.clipboard && navigator.clipboard.writeText) { try { await navigator.clipboard.writeText(text); return true; } catch(e) {} } try { const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.select(); const ok = document.execCommand('copy'); document.body.removeChild(ta); return ok; } catch(e) { return false; } } /* wire buttons */ document.addEventListener('click', (ev) => { const btn = ev.target.closest('button[data-action="wrap"]'); if (btn) { handleWrapButton(btn); setStatus('Wrapped'); return; } }); els('button.btn').forEach(b => { // non-wrap buttons already handled using data-action attribute in event delegation }); /* copy button */ el('#copy').addEventListener('click', async () => { const txt = el('#mfm').value; const ok = await copyText(txt); setStatus(ok ? 'Copied to clipboard' : 'Copy failed', ok); }); /* reset */ el('#reset').addEventListener('click', () => { el('#input').value = ''; el('#mfm').value = ''; el('#visualPreview').innerHTML = ''; setStatus('Reset'); }); /* wrap-selection: prompt for a custom template and wrap selection or entire text */ el('#wrap-selection').addEventListener('click', () => { const tpl = prompt('Enter wrapper template with {t} as placeholder (e.g. $[spin {t}])','$[spin {t}]'); if (!tpl) return; applyWrapperToInput(tpl); setStatus('Applied custom wrapper'); }); /* live updates */ el('#input').addEventListener('input', () => { if (el('#auto').checked) updateAll(); }); els('input').forEach(i=>i.addEventListener('change', () => { if (el('#auto').checked) updateAll(); })); /* initialize */ updateAll(); setStatus('Ready'); /* Accessibility: keyboard shortcuts */ document.addEventListener('keydown', (ev) => { if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === 'b') { // ctrl/cmd+B toggle bold on selection ev.preventDefault(); applyWrapperToInput('**{t}**'); setStatus('Bold applied'); } }); </script> </body> </html> FFMPEG Video compressor / resizer ---------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Video Compressor with ffmpeg.wasm</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: auto; padding: 20px; } input[type=range] { width: 100%; } label { display: block; margin-top: 10px; } video { max-width: 100%; margin-top: 10px; } pre#debugLog { background: #111; color: #0f0; padding: 10px; overflow: auto; max-height: 200px; font-size: 14px; white-space: pre-wrap; word-wrap: break-word; } #progress, #ffmpegProgress { width: 100%; height: 16px; } button { margin-top: 10px; } #message { margin-top: 10px; font-weight: bold; } .error { color: red; } .success { color: green; } .tab { overflow: hidden; border: 1px solid #ccc; background-color: #f1f1f1; border-radius: 4px 4px 0 0; } .tab button { background-color: inherit; float: left; border: none; outline: none; cursor: pointer; padding: 10px 16px; transition: 0.3s; margin-top: 0; } .tab button:hover { background-color: #ddd; } .tab button.active { background-color: #ccc; } .tabcontent { display: none; padding: 15px; border: 1px solid #ccc; border-top: none; border-radius: 0 0 4px 4px; } .tabcontent.active { display: block; } #fileInfo { margin: 10px 0; font-style: italic; } .mode-selector { margin: 15px 0; } .mode-options { padding: 10px; border: 1px solid #ddd; border-radius: 4px; margin-top: 10px; } .hidden { display: none; } .comparison { display: flex; justify-content: space-between; margin: 20px 0; } .video-container { width: 48%; text-align: center; } .audio-options { margin-top: 15px; padding: 10px; border: 1px solid #eee; border-radius: 4px; background: #f8f8f8; } .two-column { display: flex; justify-content: space-between; } .column { width: 48%; } </style> </head> <body> <h2>Video Compressor with ffmpeg.wasm</h2> <div class="tab"> <button class="tablinks active" onclick="openTab(event, 'urlTab')">From URL</button> <button class="tablinks" onclick="openTab(event, 'uploadTab')">Upload File</button> </div> <div id="urlTab" class="tabcontent active"> <input type="text" id="videoUrl" placeholder="Enter video URL" size="60" /> <label><input type="checkbox" id="bypassCors" /> Bypass CORS via proxy</label> <button id="loadVideo">Load Video</button> </div> <div id="uploadTab" class="tabcontent"> <input type="file" id="fileUpload" accept="video/*" /> <div id="fileInfo"></div> </div> <button id="loadFFmpegBtn">Load ffmpeg.wasm</button> <div id="message"></div> <div class="comparison"> <div class="video-container"> <h3>Original Video</h3> <video id="inputVideo" controls></video> <div id="originalInfo"></div> </div> <div class="video-container"> <h3>Compressed Video</h3> <video id="outputVideo" controls></video> <div id="compressedInfo"></div> </div> </div> <div class="mode-selector"> <label><strong>Compression Mode:</strong></label><br> <label><input type="radio" name="mode" value="resolution" checked onclick="toggleMode()" /> Resolution (Width x Height)</label><br> <label><input type="radio" name="mode" value="filesize" onclick="toggleMode()" /> Target Filesize</label> </div> <div class="two-column"> <div class="column"> <div id="resolutionOptions" class="mode-options"> <label>Width: <input type="number" id="widthInput" min="64" step="1" /> px</label> <label>Height: <input type="number" id="heightInput" min="64" step="1" /> px</label> <label> Maintain Aspect Ratio: <input type="checkbox" id="keepAspect" checked /> <span id="aspectRatio"></span> </label> <label> Video Quality (CRF): <input type="range" id="qualitySlider" min="0" max="51" value="28" /> <span id="qualityValue">28</span> (Lower = better quality, 23-28 is recommended) </label> </div> <div id="filesizeOptions" class="mode-options hidden"> <label>Target Filesize: <input type="number" id="targetSize" min="0.1" step="0.1" value="5" /> MB</label> <label> Video Quality Preset: <select id="filesizePreset"> <option value="ultrafast">Ultrafast (fastest, larger files)</option> <option value="superfast">Superfast</option> <option value="veryfast">Veryfast</option> <option value="faster">Faster</option> <option value="fast">Fast</option> <option value="medium" selected>Medium (good balance)</option> <option value="slow">Slow</option> <option value="slower">Slower</option> <option value="veryslow">Veryslow (slowest, smallest files)</option> </select> </label> <label> Video/Audio Allocation: <select id="bitrateAllocation"> <option value="90/10">90% Video / 10% Audio</option> <option value="80/20">80% Video / 20% Audio</option> <option value="70/30">70% Video / 30% Audio</option> <option value="60/40">60% Video / 40% Audio</option> </select> </label> </div> </div> <div class="column"> <div class="audio-options"> <h4>Audio Settings</h4> <label> Audio Codec: <select id="audioCodec"> <option value="aac">AAC (recommended)</option> <option value="libmp3lame">MP3</option> <option value="copy">Keep Original</option> <option value="none">No Audio</option> </select> </label> <label> Audio Bitrate: <select id="audioBitrate"> <option value="320k">320 kbps (high quality)</option> <option value="256k">256 kbps</option> <option value="192k">192 kbps</option> <option value="160k">160 kbps</option> <option value="128k" selected>128 kbps (good balance)</option> <option value="96k">96 kbps</option> <option value="64k">64 kbps (low quality)</option> <option value="32k">32 kbps (very low quality)</option> </select> </label> <label> Audio Channels: <select id="audioChannels"> <option value="2">Stereo (2 channels)</option> <option value="1">Mono (1 channel)</option> <option value="copy">Keep Original</option> </select> </label> <label> Sample Rate: <select id="audioSampleRate"> <option value="44100">44.1 kHz (CD quality)</option> <option value="48000">48 kHz</option> <option value="22050">22.05 kHz</option> <option value="copy">Keep Original</option> </select> </label> </div> </div> </div> <button id="compressButton" disabled>Compress Video</button> <progress id="progress" value="0" max="100"></progress> <h3>Debug Log</h3> <pre id="debugLog"></pre> <script src="https://unpkg.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script> <script src="https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js"></script> <script> const logBox = document.getElementById('debugLog'); const message = document.getElementById('message'); const inputVideo = document.getElementById('inputVideo'); const outputVideo = document.getElementById('outputVideo'); const progress = document.getElementById('progress'); const loadFFmpegBtn = document.getElementById('loadFFmpegBtn'); const compressButton = document.getElementById('compressButton'); const fileUpload = document.getElementById('fileUpload'); const fileInfo = document.getElementById('fileInfo'); const originalInfo = document.getElementById('originalInfo'); const compressedInfo = document.getElementById('compressedInfo'); const qualitySlider = document.getElementById('qualitySlider'); const qualityValue = document.getElementById('qualityValue'); const widthInput = document.getElementById('widthInput'); const heightInput = document.getElementById('heightInput'); const keepAspect = document.getElementById('keepAspect'); const aspectRatio = document.getElementById('aspectRatio'); const targetSize = document.getElementById('targetSize'); const filesizePreset = document.getElementById('filesizePreset'); const bitrateAllocation = document.getElementById('bitrateAllocation'); const audioCodec = document.getElementById('audioCodec'); const audioBitrate = document.getElementById('audioBitrate'); const audioChannels = document.getElementById('audioChannels'); const audioSampleRate = document.getElementById('audioSampleRate'); // Enhanced console logging ['log', 'error', 'warn', 'info'].forEach(method => { const original = console[method]; console[method] = function(...args) { const msg = args.map(a => { try { return (typeof a === 'object') ? JSON.stringify(a, null, 2) : String(a); } catch { return String(a); } }).join(' '); const time = new Date().toLocaleTimeString(); logBox.textContent += `[${time}] ${method.toUpperCase()}: ${msg}\n`; logBox.scrollTop = logBox.scrollHeight; original.apply(console, args); }; }); const { createFFmpeg, fetchFile } = FFmpeg; const ffmpeg = createFFmpeg({ log: true, corePath: 'https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js' }); let videoBlob = null; let videoDuration = 0; let originalWidth = 0; let originalHeight = 0; let originalSize = 0; let ffmpegLoaded = false; function showMessage(text, isError = false) { message.textContent = text; message.className = isError ? 'error' : 'success'; } function toggleMode() { const mode = document.querySelector('input[name="mode"]:checked').value; document.getElementById('resolutionOptions').classList.toggle('hidden', mode !== 'resolution'); document.getElementById('filesizeOptions').classList.toggle('hidden', mode !== 'filesize'); } function openTab(evt, tabName) { const tabcontent = document.getElementsByClassName("tabcontent"); for (let i = 0; i < tabcontent.length; i++) { tabcontent[i].classList.remove("active"); } const tablinks = document.getElementsByClassName("tablinks"); for (let i = 0; i < tablinks.length; i++) { tablinks[i].classList.remove("active"); } document.getElementById(tabName).classList.add("active"); evt.currentTarget.classList.add("active"); } async function loadFFmpeg() { if (ffmpegLoaded) { showMessage('ffmpeg.wasm already loaded'); return; } showMessage('Loading ffmpeg.wasm...'); loadFFmpegBtn.disabled = true; try { await ffmpeg.load(); ffmpegLoaded = true; showMessage('ffmpeg.wasm loaded successfully'); console.log("ffmpeg loaded:", ffmpeg); compressButton.disabled = !(ffmpegLoaded && videoBlob); } catch (err) { let errorMsg = `Error loading ffmpeg: ${err.message}`; if (err.message.includes('SharedArrayBuffer') || err.message.includes('cross-origin isolated')) { errorMsg += `<br><br><strong>Server Configuration Required:</strong><br> For ffmpeg.wasm to work, your server needs these headers:<br> <code> Header set Cross-Origin-Opener-Policy "same-origin"<br> Header set Cross-Origin-Embedder-Policy "require-corp" </code><br><br> If you can't configure the server, try:<br> 1. Serving this page via localhost<br> 2. Using Chrome with <code>--disable-web-security</code> flag (for testing only)`; } showMessage(errorMsg, true); console.error("ffmpeg load error:", err); loadFFmpegBtn.disabled = false; } } function handleVideoFile(file) { if (!file) return; if (!file.type.startsWith('video/')) { showMessage("Please select a video file", true); return; } if (file.size > 100 * 1024 * 1024) { // 100MB limit showMessage("File is too large (max 100MB recommended)", true); return; } videoBlob = file; originalSize = file.size; const objectURL = URL.createObjectURL(file); fileInfo.innerHTML = ` <strong>Selected file:</strong> ${file.name}<br> <strong>Size:</strong> ${(file.size / (1024 * 1024)).toFixed(2)}MB<br> <strong>Type:</strong> ${file.type || 'unknown'} `; inputVideo.onloadedmetadata = () => { videoDuration = inputVideo.duration; originalWidth = inputVideo.videoWidth; originalHeight = inputVideo.videoHeight; // Set default width/height inputs widthInput.value = originalWidth; heightInput.value = originalHeight; aspectRatio.textContent = `${originalWidth}:${originalHeight}`; originalInfo.innerHTML = ` <strong>Dimensions:</strong> ${originalWidth}×${originalHeight}<br> <strong>Duration:</strong> ${videoDuration.toFixed(2)}s<br> <strong>Size:</strong> ${(file.size / (1024 * 1024)).toFixed(2)}MB `; showMessage(`Video loaded (${(file.size / (1024 * 1024)).toFixed(2)}MB, ${originalWidth}×${originalHeight}, ${videoDuration.toFixed(2)}s)`); compressButton.disabled = !(ffmpegLoaded && videoBlob); console.log("Video metadata loaded"); }; inputVideo.onerror = () => { showMessage("Error loading video file", true); videoBlob = null; compressButton.disabled = true; }; inputVideo.src = objectURL; } // Update height when width changes (if aspect ratio is locked) widthInput.addEventListener('input', () => { if (keepAspect.checked && originalWidth > 0) { const ratio = originalHeight / originalWidth; heightInput.value = Math.round(widthInput.value * ratio); } }); // Update width when height changes (if aspect ratio is locked) heightInput.addEventListener('input', () => { if (keepAspect.checked && originalHeight > 0) { const ratio = originalWidth / originalHeight; widthInput.value = Math.round(heightInput.value * ratio); } }); // Update quality value display qualitySlider.addEventListener('input', () => { qualityValue.textContent = qualitySlider.value; }); async function loadVideo() { const url = document.getElementById('videoUrl').value.trim(); const useProxy = document.getElementById('bypassCors').checked; if (!url) { showMessage("Please enter a video URL", true); return; } showMessage('Fetching video...'); try { const proxyUrl = useProxy ? `https://cors-anywhere.herokuapp.com/${url}` : url; console.log(`Fetching from: ${proxyUrl}`); const response = await fetch(proxyUrl, { headers: useProxy ? { 'X-Requested-With': 'XMLHttpRequest' } : {} }); if (!response.ok) { throw new Error(`HTTP ${response.status} - ${response.statusText}`); } const contentLength = response.headers.get('Content-Length'); if (contentLength && parseInt(contentLength) > 50 * 1024 * 1024) { throw new Error("Video is too large (max ~50MB recommended)"); } videoBlob = await response.blob(); originalSize = videoBlob.size; if (!videoBlob.type.startsWith('video/')) { throw new Error("The URL doesn't point to a valid video file"); } const objectURL = URL.createObjectURL(videoBlob); await new Promise((resolve, reject) => { inputVideo.onloadedmetadata = () => { videoDuration = inputVideo.duration; originalWidth = inputVideo.videoWidth; originalHeight = inputVideo.videoHeight; // Set default width/height inputs widthInput.value = originalWidth; heightInput.value = originalHeight; aspectRatio.textContent = `${originalWidth}:${originalHeight}`; originalInfo.innerHTML = ` <strong>Dimensions:</strong> ${originalWidth}×${originalHeight}<br> <strong>Duration:</strong> ${videoDuration.toFixed(2)}s<br> <strong>Size:</strong> ${(videoBlob.size / (1024 * 1024)).toFixed(2)}MB `; showMessage(`Video loaded (${(videoBlob.size / (1024 * 1024)).toFixed(2)}MB, ${originalWidth}×${originalHeight}, ${videoDuration.toFixed(2)}s)`); compressButton.disabled = !(ffmpegLoaded && videoBlob); console.log("Video metadata loaded"); resolve(); }; inputVideo.onerror = () => { reject(new Error("Video playback error")); }; inputVideo.src = objectURL; }); } catch (err) { showMessage(`Error: ${err.message}`, true); console.error("Video load error:", err); videoBlob = null; compressButton.disabled = true; } } fileUpload.addEventListener('change', (e) => { const file = e.target.files[0]; handleVideoFile(file); }); loadFFmpegBtn.onclick = loadFFmpeg; document.getElementById('loadVideo').onclick = loadVideo; document.getElementById('compressButton').onclick = async () => { if (!videoBlob) { showMessage("No video loaded", true); return; } try { showMessage('Starting compression process...'); progress.value = 0; console.log(`Writing file to MEMFS`); const fileData = videoBlob instanceof File ? await fetchFile(videoBlob) : new Uint8Array(await videoBlob.arrayBuffer()); await ffmpeg.FS('writeFile', 'input.mp4', fileData); ffmpeg.setProgress(({ ratio }) => { progress.value = Math.round(ratio * 100); showMessage(`Processing... ${progress.value}%`); }); const mode = document.querySelector('input[name="mode"]:checked').value; const audioSettings = getAudioSettings(); let command; if (mode === 'resolution') { // Resolution-based compression const width = parseInt(widthInput.value) || originalWidth; const height = parseInt(heightInput.value) || originalHeight; const crf = qualitySlider.value; command = [ '-i', 'input.mp4', '-vf', `scale=${width}:${height}`, '-c:v', 'libx264', '-crf', crf, '-preset', 'medium', ...audioSettings, 'output.mp4' ]; console.log(`Executing ffmpeg command for resolution mode: ${width}x${height}, CRF ${crf}`); } else { // Filesize-based compression const targetMB = parseFloat(targetSize.value) || 5; const allocation = bitrateAllocation.value.split('/'); const videoPercent = parseInt(allocation[0]) / 100; const audioPercent = parseInt(allocation[1]) / 100; const targetBits = Math.round(targetMB * 1024 * 1024 * 8 / videoDuration); const videoBits = Math.round(targetBits * videoPercent); const audioBits = Math.round(targetBits * audioPercent); const preset = filesizePreset.value; command = [ '-i', 'input.mp4', '-c:v', 'libx264', '-b:v', `${videoBits}`, '-maxrate', `${videoBits}`, '-bufsize', `${videoBits * 2}`, '-preset', preset, ...audioSettings.filter(opt => !opt.startsWith('-b:a:')), // Remove any existing audio bitrate '-b:a', `${audioBits}`, 'output.mp4' ]; console.log(`Executing ffmpeg command for filesize mode: ${targetMB}MB, ${preset} preset`); } await ffmpeg.run(...command); console.log(`Reading output file`); const data = ffmpeg.FS('readFile', 'output.mp4'); console.log(`Creating blob (size: ${data.length} bytes)`); const compressedBlob = new Blob([data.buffer], { type: 'video/mp4' }); outputVideo.src = URL.createObjectURL(compressedBlob); outputVideo.load(); // Show compression results const originalMB = (originalSize / (1024 * 1024)).toFixed(2); const compressedMB = (data.length / (1024 * 1024)).toFixed(2); const reduction = ((1 - (data.length / originalSize)) * 100).toFixed(1); compressedInfo.innerHTML = ` <strong>New Size:</strong> ${compressedMB}MB (${reduction}% reduction)<br> <strong>Compression Ratio:</strong> ${originalMB}MB → ${compressedMB}MB `; showMessage(`Compression complete! Reduced from ${originalMB}MB to ${compressedMB}MB (${reduction}% smaller)`); console.log("Compression process completed successfully"); // Clean up files from MEMFS try { ffmpeg.FS('unlink', 'input.mp4'); ffmpeg.FS('unlink', 'output.mp4'); } catch (cleanupErr) { console.warn("Cleanup error:", cleanupErr); } } catch (err) { showMessage(`Error during compression: ${err.message}`, true); console.error("Compression error:", err); if (err.message.includes('memory')) { showMessage("Video might be too large for available memory", true); } else if (err.message.includes('codec') || err.message.includes('format')) { showMessage("Video codec might not be supported", true); } } }; function getAudioSettings() { const codec = audioCodec.value; const bitrate = audioBitrate.value; const channels = audioChannels.value; const sampleRate = audioSampleRate.value; if (codec === 'none') { return ['-an']; // No audio } if (codec === 'copy') { return ['-c:a', 'copy']; // Copy original audio } const settings = [ '-c:a', codec, '-b:a', bitrate ]; if (channels !== 'copy') { settings.push('-ac', channels); } if (sampleRate !== 'copy') { settings.push('-ar', sampleRate); } return settings; } </script> </body> </html> Fediverse instance user info ---------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: Arial, sans-serif; } .user-list { margin-top: 20px; } .user-link { display: block; padding: 8px; margin: 5px 0; text-decoration: none; color: #007BFF; font-weight: bold; } .user-link:hover { text-decoration: underline; } table { width: 100%; border-collapse: collapse; margin-top: 10px; } th, td { padding: 8px; border: 1px solid #ddd; text-align: left; } th { background-color: #f4f4f4; } details { margin-top: 20px; } details summary { font-weight: bold; cursor: pointer; } </style> </head> <body> <form id="endpoint-form"> <label for="domain">Enter Domain:</label> <input type="text" id="domain" name="domain" placeholder="e.g., mastodon.social" required> <button type="submit">Fetch Users</button> </form> <div id="user-count"></div> <div class="user-list" id="user-list"> <!-- Users will be listed here --> </div> <!-- Instance Data in a details section --> <details id="instance-details"> <summary>Click to View Instance Information</summary> <table id="instance-table"> <!-- Instance data will be displayed here --> </table> </details> <script> // Function to get query string value for 'instance' function getQueryStringParameter(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name); } // Automatically trigger fetch if 'instance' query parameter is provided const instanceFromQuery = getQueryStringParameter('instance'); if (instanceFromQuery) { document.getElementById('domain').value = instanceFromQuery; fetchData(instanceFromQuery); // Auto trigger fetch for the instance } document.getElementById('endpoint-form').addEventListener('submit', async (e) => { e.preventDefault(); const domain = document.getElementById('domain').value.trim(); if (!domain) { alert('Please enter a valid domain'); return; } fetchData(domain); }); async function fetchData(domain) { const endpointDirectory = `https://${domain}/api/v1/directory?local=true`; const endpointInstance = `https://${domain}/api/v1/instance`; const userListContainer = document.getElementById('user-list'); const userCountContainer = document.getElementById('user-count'); const instanceTable = document.getElementById('instance-table'); const instanceDetails = document.getElementById('instance-details'); userListContainer.innerHTML = 'Loading...'; userCountContainer.innerHTML = ''; // Clear any previous count try { // Fetch the instance data to get the total user count and other details const instanceResponse = await fetch(endpointInstance); if (!instanceResponse.ok) { throw new Error('Failed to fetch instance data.'); } const instanceData = await instanceResponse.json(); const totalUsers = instanceData.stats ? instanceData.stats.user_count : 'Unknown'; // Display total user count userCountContainer.innerHTML = `Total Users: ${totalUsers}`; // Fetch the users from the directory endpoint const directoryResponse = await fetch(endpointDirectory); if (!directoryResponse.ok) { throw new Error('Failed to fetch users from the directory.'); } const data = await directoryResponse.json(); if (data && Array.isArray(data)) { // Sort users by `created_at` (registration date) const sortedUsers = data.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); userListContainer.innerHTML = ''; // Clear loading message sortedUsers.forEach(user => { // Format user data const userName = user.display_name || 'Unknown User'; const userUsername = user.username || 'Unknown Username'; const registrationDate = new Date(user.created_at).toLocaleDateString() || 'Invalid Date'; const userLink = document.createElement('a'); userLink.href = `https://${domain}/users/${userUsername}`; // Link to the user's profile userLink.classList.add('user-link'); userLink.textContent = `${userName} (@${userUsername}) - Registered: ${registrationDate}`; // Open link in a new tab userLink.target = "_blank"; userListContainer.appendChild(userLink); }); } else { userListContainer.innerHTML = 'No users found or invalid response format.'; } // Display instance data in a table const instanceDetailsHTML = ` <tr><th>Instance Title</th><td>${instanceData.title || 'N/A'}</td></tr> <tr><th>Description</th><td>${instanceData.description || 'N/A'}</td></tr> <tr><th>Email</th><td><a href="mailto:${instanceData.email || 'N/A'}">${instanceData.email || 'N/A'}</a></td></tr> <tr><th>Version</th><td>${instanceData.version || 'N/A'}</td></tr> <tr><th>Registrations</th><td>${instanceData.registrations ? 'Enabled' : 'Disabled'}</td></tr> <tr><th>Approval Required</th><td>${instanceData.approval_required ? 'Yes' : 'No'}</td></tr> <tr><th>Invites Enabled</th><td>${instanceData.invites_enabled ? 'Yes' : 'No'}</td></tr> <tr><th>Total Users</th><td>${instanceData.stats.user_count || 'N/A'}</td></tr> <tr><th>Total Statuses</th><td>${instanceData.stats.status_count || 'N/A'}</td></tr> <tr><th>Total Domains</th><td>${instanceData.stats.domain_count || 'N/A'}</td></tr> `; instanceTable.innerHTML = instanceDetailsHTML; // Enable the details section instanceDetails.open = false; } catch (error) { console.error(error); userListContainer.innerHTML = 'Error fetching data. Please try again.'; } } </script> </body> </html> PmxFromZip Loader (via Service worker, with vmd anim) -------------------------------- zipfsmmd-sw.js -- // zipfsmmd-sw.js importScripts('https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js'); let ZIP_PATH = null; function swLog(msg) { console.log('[PMX-ZIP SW]', msg); self.clients.matchAll().then(clients => { clients.forEach(client => { client.postMessage({ type: 'SW_LOG', text: msg }); }); }); } self.addEventListener('install', event => { swLog('Install event fired'); self.skipWaiting(); }); self.addEventListener('activate', event => { swLog('Activate event fired'); event.waitUntil(self.clients.claim()); }); self.addEventListener('message', event => { if (event.data && event.data.type === 'SET_ZIP_PATH') { ZIP_PATH = event.data.zipPath; swLog(`ZIP path set to: ${ZIP_PATH}`); zipCache.clear(); } }); const zipCache = new Map(); self.addEventListener('fetch', event => { if (!ZIP_PATH) return; // ZIP path not set, fallback to normal fetch const url = new URL(event.request.url); if (url.origin !== self.location.origin) return; const relPath = url.pathname; if (zipCache.has(relPath)) { swLog(`Serving from cache: ${relPath}`); event.respondWith(zipCache.get(relPath).clone()); return; } event.respondWith( (async () => { try { if (zipCache.size === 0) { swLog(`Fetching ZIP from ${ZIP_PATH}`); const res = await fetch(ZIP_PATH); if (!res.ok) throw new Error(`Failed to fetch ZIP: ${res.status}`); const arrayBuffer = await res.arrayBuffer(); const zip = await JSZip.loadAsync(arrayBuffer); for (const filename of Object.keys(zip.files)) { const file = zip.files[filename]; if (file.dir) continue; const fileData = await file.async('arraybuffer'); zipCache.set('/' + filename, new Response(fileData)); swLog(`Extracted: /${filename} (${fileData.byteLength} bytes)`); } swLog('Unzip complete'); } if (zipCache.has(relPath)) { return zipCache.get(relPath).clone(); } return fetch(event.request); } catch (e) { swLog(`ZIP fetch/unzip error: ${e}`); return fetch(event.request); } })() ); }); pmxziploader.html -- <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>PMX Loader from ZIP</title> <style> body { margin: 0; overflow: hidden; background: #000; font-family: monospace; } #loading { position: absolute; top: 10px; left: 10px; color: #0f0; background: #000; padding: 4px 10px; font-size: 14px; z-index: 10; } #log { position: absolute; bottom: 0; left: 0; width: 100%; height: 150px; overflow-y: auto; background: rgba(0,0,0,.8); color: #0f0; font-size: 12px; padding: 5px; box-sizing: border-box; white-space: pre-wrap; } #controls { position: absolute; top: 10px; right: 10px; z-index: 10; display: flex; flex-direction: column; } .control-btn { font-size: 20px; padding: 10px; margin: 5px; background: #333; color: #fff; border: none; border-radius: 6px; cursor: pointer; user-select: none; } .error { color: #f00; } .warning { color: #ff0; } .info { color: #0f0; } #wireframeBtn { font-size: 16px; } </style> </head> <body> <div id="loading">Initializing PMX loader...</div> <div id="log"></div> <div id="controls"> <button class="control-btn" id="zoomIn">+</button> <button class="control-btn" id="zoomOut">−</button> <button class="control-btn" id="wireframeBtn">Wireframe OFF</button> </div> <script src="./libs/three.js"></script> <script src="./libs/mmdparser-obsolete.min.js"></script> <script src="./libs/ammo.min.js"></script> <script src="./libs/TGALoader.js"></script> <script src="./libs/MMDLoader.js"></script> <script src="./libs/MMDAnimationHelper.js"></script> <script src="./libs/CCDIKSolver.js"></script> <script src="./libs/MMDPhysics.js"></script> <script> // Override console methods to capture all output const originalConsole = { log: console.log, warn: console.warn, error: console.error }; function logToDiv(type, ...args) { const msg = args.join(' '); const logEl = document.getElementById('log'); const className = type === 'error' ? 'error' : type === 'warn' ? 'warning' : 'info'; logEl.innerHTML += `<span class="${className}">${msg}</span>\n`; logEl.scrollTop = logEl.scrollHeight; } function log(...args) { const msg = args.join(' '); originalConsole.log(msg); logToDiv('info', msg); } function warn(...args) { const msg = args.join(' '); originalConsole.warn(msg); logToDiv('warn', msg); } function error(...args) { const msg = args.join(' '); originalConsole.error(msg); logToDiv('error', msg); } // Override console methods console.log = log; console.warn = warn; console.error = error; // Capture unhandled errors window.addEventListener('error', function(e) { error(`Unhandled error: ${e.message} (${e.filename}:${e.lineno}:${e.colno})`); if (e.error && e.error.stack) { error(e.error.stack); } }); // Capture unhandled promise rejections window.addEventListener('unhandledrejection', function(e) { error('Unhandled promise rejection:', e.reason); if (e.reason && e.reason.stack) { error(e.reason.stack); } }); // Track texture loading errors let missingTextures = 0; // Patch THREE.TextureLoader to catch texture loading errors const originalLoad = THREE.TextureLoader.prototype.load; THREE.TextureLoader.prototype.load = function(url, onLoad, onProgress, onError) { const patchedOnError = (err) => { missingTextures++; error(`Failed to load texture: ${url}`); if (onError) onError(err); }; return originalLoad.call(this, url, onLoad, onProgress, patchedOnError); }; // Patch ImageLoader to catch image loading errors const originalImageLoad = THREE.ImageLoader.prototype.load; THREE.ImageLoader.prototype.load = function(url, onLoad, onProgress, onError) { const patchedOnError = (err) => { missingTextures++; error(`Failed to load image: ${url}`); if (onError) onError(err); }; return originalImageLoad.call(this, url, onLoad, onProgress, patchedOnError); }; const currentDir = new URL('.', window.location.href).href; const swPath = new URL('zipfsmmd-sw.js', currentDir).href; const FALLBACK_ZIP = new URL('models/AoiZaizen.zip', currentDir).href; function getZipUrl(){ const params = new URLSearchParams(window.location.search); const zip = params.get('pmx'); if(zip) return new URL(zip, window.location.href).href; log('No ?pmx= query parameter found; falling back to default ZIP'); return FALLBACK_ZIP; } function getPmxFileName(){ const params = new URLSearchParams(window.location.search); let modelFile = params.get('model'); if(modelFile){ if(!modelFile.startsWith('/')) modelFile = '/' + modelFile; return modelFile; } const zipUrl = getZipUrl(); const zipBase = zipUrl.split('/').pop().replace(/\.zip$/i, ''); return '/' + zipBase + '.pmx'; } const zipPath = getZipUrl(); const pmxFileName = getPmxFileName(); const vmdName = 'bts-bestofme'; if('serviceWorker' in navigator){ navigator.serviceWorker.register(swPath).then(reg=>{ log(`Service Worker registered at: ${swPath}`); if(!navigator.serviceWorker.controller) setTimeout(()=>location.reload(),500); else{ navigator.serviceWorker.controller.postMessage({type:'SET_ZIP_PATH',zipPath}); initLoader(); } }).catch(err=>{ error('SW registration failed:', err); initLoader(); }); navigator.serviceWorker.addEventListener('message',event=>{ if(event.data && event.data.type==='SW_LOG') log('[SW]', event.data.text); }); }else{ warn('Service Workers not supported'); initLoader(); } async function initLoader(){ log('Initializing PMX loader...'); log(`ZIP path: ${zipPath}`); log(`PMX model path inside ZIP: ${pmxFileName}`); log(`VMD name: ${vmdName}`); let scene, renderer, camera, mesh, helper; let ready = false; const clock = new THREE.Clock(); let theta = Math.PI / 4, phi = Math.PI / 4, radius = 50; let isMouseDown = false, previousMousePosition = {x: 0, y: 0}; function initScene(){ scene = new THREE.Scene(); scene.add(new THREE.AmbientLight(0xeeeeee)); renderer = new THREE.WebGLRenderer({alpha:true,antialias:true}); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(40, window.innerWidth/window.innerHeight, 1, 1000); updateCameraPosition(); document.addEventListener('mousedown', onMouseDown); document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); window.addEventListener('resize', onWindowResize); // Touch controls document.addEventListener('touchstart', onTouchStart); document.addEventListener('touchmove', onTouchMove); document.addEventListener('touchend', onTouchEnd); document.getElementById('zoomIn').addEventListener('click', ()=>zoomCamera(-5)); document.getElementById('zoomOut').addEventListener('click', ()=>zoomCamera(5)); // Wireframe toggle button document.getElementById('wireframeBtn').addEventListener('click', toggleWireframe); } function toggleWireframe() { if (!mesh) return; let isWireframe = false; mesh.traverse(function(child) { if (child.isMesh) { if (Array.isArray(child.material)) { child.material.forEach(mat => { mat.wireframe = !mat.wireframe; isWireframe = mat.wireframe || isWireframe; // If textures are missing, set wireframe color to light green if (mat.wireframe && missingTextures > 0) { mat.color.set(0x90EE90); // Light green mat.emissive.set(0x90EE90); mat.needsUpdate = true; } else if (!mat.wireframe) { // Reset to default colors when wireframe is off mat.color.set(0xFFFFFF); mat.emissive.set(0x000000); mat.needsUpdate = true; } }); } else { child.material.wireframe = !child.material.wireframe; isWireframe = child.material.wireframe || isWireframe; // If textures are missing, set wireframe color to light green if (child.material.wireframe && missingTextures > 0) { child.material.color.set(0x90EE90); // Light green child.material.emissive.set(0x90EE90); child.material.needsUpdate = true; } else if (!child.material.wireframe) { // Reset to default colors when wireframe is off child.material.color.set(0xFFFFFF); child.material.emissive.set(0x000000); child.material.needsUpdate = true; } } } }); document.getElementById('wireframeBtn').textContent = isWireframe ? 'Wireframe ON' : 'Wireframe OFF'; log(`Wireframe mode ${isWireframe ? 'enabled' : 'disabled'}`); if (isWireframe && missingTextures > 0) { log(`Using light green wireframe (${missingTextures} missing textures)`); } } function onMouseDown(e){e.preventDefault(); isMouseDown=true; previousMousePosition={x:e.clientX,y:e.clientY};} function onMouseMove(e){ if(!isMouseDown) return; e.preventDefault(); const dx = e.clientX - previousMousePosition.x; const dy = e.clientY - previousMousePosition.y; theta -= dx * 0.005; phi -= dy * 0.005; phi = Math.min(Math.max(phi,0.01), Math.PI-0.01); updateCameraPosition(); previousMousePosition = {x:e.clientX,y:e.clientY}; } function onMouseUp(e){e.preventDefault(); isMouseDown=false;} // Touch controls let touchStartX = 0, touchStartY = 0; function onTouchStart(e) { e.preventDefault(); touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; } function onTouchMove(e) { e.preventDefault(); const dx = e.touches[0].clientX - touchStartX; const dy = e.touches[0].clientY - touchStartY; theta -= dx * 0.005; phi -= dy * 0.005; phi = Math.min(Math.max(phi, 0.01), Math.PI - 0.01); updateCameraPosition(); touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; } function onTouchEnd(e) { } function zoomCamera(delta){ radius += delta; radius = Math.min(Math.max(radius,10),200); updateCameraPosition(); } function updateCameraPosition(){ camera.position.x = radius * Math.sin(phi) * Math.cos(theta); camera.position.y = radius * Math.cos(phi); camera.position.z = radius * Math.sin(phi) * Math.sin(theta); camera.lookAt(scene.position); } function onWindowResize(){ camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } async function loadModel(){ document.getElementById('loading').textContent = 'Loading model from ZIP...'; return new Promise((resolve, reject) => { const loader = new THREE.MMDLoader(); // Patch MMDLoader's texture loading const originalLoadTexture = loader._loadTexture; loader._loadTexture = function(path, modelPath, onLoad, onProgress, onError) { const patchedOnError = (err) => { missingTextures++; error(`Failed to load MMD texture: ${path}`); if (onError) onError(err); }; return originalLoadTexture.call(this, path, modelPath, onLoad, onProgress, patchedOnError); }; loader.load(pmxFileName, object => { mesh = object; mesh.position.y = -10; scene.add(mesh); document.getElementById('loading').textContent = 'Loading animation...'; log('PMX model loaded successfully from ZIP'); if (missingTextures > 0) { warn(`${missingTextures} textures failed to load`); } resolve(); }, xhr => { if(xhr.lengthComputable){ const percent = (xhr.loaded / xhr.total) * 100; document.getElementById('loading').textContent = `Loading model... ${percent.toFixed(1)}%`; } }, err => { error('Model load error:', err); document.getElementById('loading').textContent = 'Model load failed!'; reject(err); }); }); } async function loadAnimation(){ return new Promise((resolve, reject) => { const loader = new THREE.MMDLoader(); const vmdPath = new URL(`vmd/${vmdName}.vmd`, currentDir).href; log(`Loading VMD from: ${vmdPath}`); loader.loadAnimation(vmdPath, mesh, vmdClip => { vmdClip.name = vmdName; setupAnimation(vmdClip); document.getElementById('loading').style.display = 'none'; log('Animation loaded successfully'); resolve(); }, xhr => { if(xhr.lengthComputable){ const percent = (xhr.loaded / xhr.total) * 100; document.getElementById('loading').textContent = `Loading animation... ${percent.toFixed(1)}%`; } }, err => { error('Animation load error:', err); document.getElementById('loading').textContent = 'Animation load failed!'; reject(err); }); }); } function setupAnimation(vmdClip){ try { ready = false; helper = new THREE.MMDAnimationHelper({afterglow: 2, resetPhysicsOnLoop: true}); helper.add(mesh, { animation: vmdClip, physics: true }); const mixer = helper.objects.get(mesh).mixer; mixer.addEventListener('finished', () => { log('Animation finished. Restarting...'); setupAnimation(vmdClip); }); ready = true; } catch (e) { error('Error setting up animation:', e); throw e; } } function animate(){ try { requestAnimationFrame(animate); if(ready && helper) helper.update(clock.getDelta()); renderer.render(scene, camera); } catch (e) { error('Animation loop error:', e); } } initScene(); try { await loadModel(); await loadAnimation(); animate(); log('PMX viewer ready'); } catch(e) { error('Initialization failed:', e); } } </script> </body> </html> <!--- ammo.min.js 2b9e0d61ee837ab1f8d2c189d3182b65 CCDIKSolver.js 14a67e16f1620e86a1bf47add2cf8dc2 MMDAnimationHelper.js ada4ff3d55c28f87b9ba301bb82fac7e MMDLoader.js 12d36dd5a55d70736214c0fc316af320 MMDLoader.js.orig f82d61afe34c7de18b0adc3d1f59348b mmd-loader.min.js c3041dcf588c2cc3f8a9866497ee787a mmdparser.min.js 543372130dab2337cc8158b904068a28 mmdparser-obsolete.min.js 543372130dab2337cc8158b904068a28 MMDPhysics.js 3d6ea9133522d293b69d8db6eaff00dd OrbitControls.js e1aab2c938f25bbac4b6d327e551fac0 OutlineEffect.js 14a675124f646040d5a34cea3f3c854c Stats.min.js b134a94301558097e6880e870dd0c166 TGALoader.js a98fce8285c995df26eb9a00f915ec52 three.js.noboneerr 828532a6c967fa58a79ef10ad49d9f24 three.min.js eb8549863a97355411c3259a3f93b8e1 three.module.js 9274bccc27ba09a42f61eb971687f78d three.module.min.js 8477f142c1d8e670968cb678788ce041 Vmd.js 5b6f1505c7b6b0e32e5bb26ec33c54c4 VmdFileParser.js a4854fd3bc0aa2b86e1d8adc9425149e ---> Ffmpeg trimmer ------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>MP4 Trimmer with ffmpeg.wasm</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: auto; padding: 20px; } input[type=range] { width: 100%; } label { display: block; margin-top: 10px; } video { max-width: 100%; margin-top: 10px; } pre#debugLog { background: #111; color: #0f0; padding: 10px; overflow: auto; max-height: 200px; font-size: 14px; white-space: pre-wrap; word-wrap: break-word; } #progress, #ffmpegProgress { width: 100%; height: 16px; } button { margin-top: 10px; } #message { margin-top: 10px; font-weight: bold; } .error { color: red; } .success { color: green; } .tab { overflow: hidden; border: 1px solid #ccc; background-color: #f1f1f1; border-radius: 4px 4px 0 0; } .tab button { background-color: inherit; float: left; border: none; outline: none; cursor: pointer; padding: 10px 16px; transition: 0.3s; margin-top: 0; } .tab button:hover { background-color: #ddd; } .tab button.active { background-color: #ccc; } .tabcontent { display: none; padding: 15px; border: 1px solid #ccc; border-top: none; border-radius: 0 0 4px 4px; } .tabcontent.active { display: block; } #fileInfo { margin: 10px 0; font-style: italic; } </style> </head> <body> <h2>Trim MP4 Video</h2> <div class="tab"> <button class="tablinks active" onclick="openTab(event, 'urlTab')">From URL</button> <button class="tablinks" onclick="openTab(event, 'uploadTab')">Upload File</button> </div> <div id="urlTab" class="tabcontent active"> <input type="text" id="videoUrl" placeholder="Enter MP4 URL" size="60" /> <label><input type="checkbox" id="bypassCors" /> Bypass CORS via proxy</label> <button id="loadVideo">Load Video</button> </div> <div id="uploadTab" class="tabcontent"> <input type="file" id="fileUpload" accept="video/mp4" /> <div id="fileInfo"></div> </div> <button id="loadFFmpegBtn">Load ffmpeg.wasm</button> <div id="message"></div> <video id="inputVideo" controls></video> <label>Start Time: <span id="startLabel">0</span> sec</label> <input type="range" id="startSlider" min="0" max="100" value="0" step="0.1" /> <label>End Time: <span id="endLabel">0</span> sec</label> <input type="range" id="endSlider" min="0" max="100" value="100" step="0.1" /> <button id="trimButton" disabled>Trim Video</button> <progress id="progress" value="0" max="100"></progress> <h3>Trimmed Output</h3> <video id="outputVideo" controls></video> <h3>Debug Log</h3> <pre id="debugLog"></pre> <!-- Updated ffmpeg versions --> <script src="https://unpkg.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script> <script src="https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js"></script> <script> const logBox = document.getElementById('debugLog'); const message = document.getElementById('message'); const inputVideo = document.getElementById('inputVideo'); const outputVideo = document.getElementById('outputVideo'); const startSlider = document.getElementById('startSlider'); const endSlider = document.getElementById('endSlider'); const startLabel = document.getElementById('startLabel'); const endLabel = document.getElementById('endLabel'); const progress = document.getElementById('progress'); const loadFFmpegBtn = document.getElementById('loadFFmpegBtn'); const trimButton = document.getElementById('trimButton'); const fileUpload = document.getElementById('fileUpload'); const fileInfo = document.getElementById('fileInfo'); // Enhanced console logging ['log', 'error', 'warn', 'info'].forEach(method => { const original = console[method]; console[method] = function(...args) { const msg = args.map(a => { try { return (typeof a === 'object') ? JSON.stringify(a, null, 2) : String(a); } catch { return String(a); } }).join(' '); const time = new Date().toLocaleTimeString(); logBox.textContent += `[${time}] ${method.toUpperCase()}: ${msg}\n`; logBox.scrollTop = logBox.scrollHeight; original.apply(console, args); }; }); const { createFFmpeg, fetchFile } = FFmpeg; const ffmpeg = createFFmpeg({ log: true, corePath: 'https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js' }); let videoBlob = null; let videoDuration = 0; let ffmpegLoaded = false; function showMessage(text, isError = false) { message.textContent = text; message.className = isError ? 'error' : 'success'; } function openTab(evt, tabName) { const tabcontent = document.getElementsByClassName("tabcontent"); for (let i = 0; i < tabcontent.length; i++) { tabcontent[i].classList.remove("active"); } const tablinks = document.getElementsByClassName("tablinks"); for (let i = 0; i < tablinks.length; i++) { tablinks[i].classList.remove("active"); } document.getElementById(tabName).classList.add("active"); evt.currentTarget.classList.add("active"); } async function loadFFmpeg() { if (ffmpegLoaded) { showMessage('ffmpeg.wasm already loaded'); return; } showMessage('Loading ffmpeg.wasm...'); loadFFmpegBtn.disabled = true; try { await ffmpeg.load(); ffmpegLoaded = true; showMessage('ffmpeg.wasm loaded successfully'); console.log("ffmpeg loaded:", ffmpeg); trimButton.disabled = !(ffmpegLoaded && videoBlob); } catch (err) { let errorMsg = `Error loading ffmpeg: ${err.message}`; // Add specific hint for COOP/COEP headers if (err.message.includes('SharedArrayBuffer') || err.message.includes('cross-origin isolated')) { errorMsg += `<br><br><strong>Server Configuration Required:</strong><br> For ffmpeg.wasm to work, your server needs these headers:<br> <code> Header set Cross-Origin-Opener-Policy "same-origin"<br> Header set Cross-Origin-Embedder-Policy "require-corp" </code><br><br> If you can't configure the server, try:<br> 1. Serving this page via localhost<br> 2. Using Chrome with <code>--disable-web-security</code> flag (for testing only)`; } showMessage(errorMsg, true); console.error("ffmpeg load error:", err); loadFFmpegBtn.disabled = false; } } function handleVideoFile(file) { if (!file) return; if (!file.type.startsWith('video/') && !file.name.endsWith('.mp4')) { showMessage("Please select an MP4 video file", true); return; } if (file.size > 100 * 1024 * 1024) { // 100MB limit showMessage("File is too large (max 100MB recommended)", true); return; } videoBlob = file; const objectURL = URL.createObjectURL(file); fileInfo.innerHTML = ` <strong>Selected file:</strong> ${file.name}<br> <strong>Size:</strong> ${(file.size / (1024 * 1024)).toFixed(2)}MB<br> <strong>Type:</strong> ${file.type || 'unknown'} `; inputVideo.onloadedmetadata = () => { videoDuration = inputVideo.duration; startSlider.max = endSlider.max = videoDuration.toFixed(2); startSlider.value = 0; endSlider.value = videoDuration.toFixed(2); startLabel.textContent = "0"; endLabel.textContent = videoDuration.toFixed(2); showMessage(`Video loaded (${(file.size / (1024 * 1024)).toFixed(2)}MB, ${videoDuration.toFixed(2)}s)`); trimButton.disabled = !(ffmpegLoaded && videoBlob); console.log("Video metadata loaded"); }; inputVideo.onerror = () => { showMessage("Error loading video file", true); videoBlob = null; trimButton.disabled = true; }; inputVideo.src = objectURL; } async function loadVideo() { const url = document.getElementById('videoUrl').value.trim(); const useProxy = document.getElementById('bypassCors').checked; if (!url) { showMessage("Please enter a video URL", true); return; } showMessage('Fetching video...'); try { const proxyUrl = useProxy ? `https://cors-anywhere.herokuapp.com/${url}` : url; console.log(`Fetching from: ${proxyUrl}`); const response = await fetch(proxyUrl, { headers: useProxy ? { 'X-Requested-With': 'XMLHttpRequest' } : {} }); if (!response.ok) { throw new Error(`HTTP ${response.status} - ${response.statusText}`); } const contentLength = response.headers.get('Content-Length'); if (contentLength && parseInt(contentLength) > 50 * 1024 * 1024) { throw new Error("Video is too large (max ~50MB recommended)"); } videoBlob = await response.blob(); // Validate it's actually a video file if (!videoBlob.type.startsWith('video/')) { throw new Error("The URL doesn't point to a valid video file"); } const objectURL = URL.createObjectURL(videoBlob); await new Promise((resolve, reject) => { inputVideo.onloadedmetadata = () => { videoDuration = inputVideo.duration; startSlider.max = endSlider.max = videoDuration.toFixed(2); startSlider.value = 0; endSlider.value = videoDuration.toFixed(2); startLabel.textContent = "0"; endLabel.textContent = videoDuration.toFixed(2); showMessage(`Video loaded (${(videoBlob.size / (1024 * 1024)).toFixed(2)}MB, ${videoDuration.toFixed(2)}s)`); trimButton.disabled = !(ffmpegLoaded && videoBlob); console.log("Video metadata loaded"); resolve(); }; inputVideo.onerror = () => { reject(new Error("Video playback error")); }; inputVideo.src = objectURL; }); } catch (err) { showMessage(`Error: ${err.message}`, true); console.error("Video load error:", err); videoBlob = null; trimButton.disabled = true; } } startSlider.oninput = () => { const startValue = parseFloat(startSlider.value); const endValue = parseFloat(endSlider.value); if (startValue > endValue) { startSlider.value = endValue; } startLabel.textContent = parseFloat(startSlider.value).toFixed(2); inputVideo.currentTime = parseFloat(startSlider.value); }; endSlider.oninput = () => { const startValue = parseFloat(startSlider.value); const endValue = parseFloat(endSlider.value); if (endValue < startValue) { endSlider.value = startValue; } endLabel.textContent = parseFloat(endSlider.value).toFixed(2); inputVideo.currentTime = parseFloat(endSlider.value); }; fileUpload.addEventListener('change', (e) => { const file = e.target.files[0]; handleVideoFile(file); }); loadFFmpegBtn.onclick = loadFFmpeg; document.getElementById('loadVideo').onclick = loadVideo; document.getElementById('trimButton').onclick = async () => { if (!videoBlob) { showMessage("No video loaded", true); return; } const start = parseFloat(startSlider.value); const end = parseFloat(endSlider.value); const duration = end - start; if (duration <= 0) { showMessage("End time must be after start time", true); return; } try { showMessage('Starting trim process...'); progress.value = 0; console.log(`Writing file to MEMFS`); const fileData = videoBlob instanceof File ? await fetchFile(videoBlob) : new Uint8Array(await videoBlob.arrayBuffer()); await ffmpeg.FS('writeFile', 'input.mp4', fileData); ffmpeg.setProgress(({ ratio }) => { progress.value = Math.round(ratio * 100); showMessage(`Processing... ${progress.value}%`); }); console.log(`Executing ffmpeg command`); await ffmpeg.run( '-i', 'input.mp4', '-ss', `${start}`, '-t', `${duration}`, '-c:v', 'copy', // Use stream copy for faster processing '-c:a', 'copy', '-avoid_negative_ts', '1', 'output.mp4' ); console.log(`Reading output file`); const data = ffmpeg.FS('readFile', 'output.mp4'); console.log(`Creating blob (size: ${data.length} bytes)`); const trimmedBlob = new Blob([data.buffer], { type: 'video/mp4' }); outputVideo.src = URL.createObjectURL(trimmedBlob); outputVideo.load(); showMessage(`Trim complete! Output: ${(data.length / (1024 * 1024)).toFixed(2)}MB`); console.log("Trim process completed successfully"); // Clean up files from MEMFS try { ffmpeg.FS('unlink', 'input.mp4'); ffmpeg.FS('unlink', 'output.mp4'); } catch (cleanupErr) { console.warn("Cleanup error:", cleanupErr); } } catch (err) { showMessage(`Error during trimming: ${err.message}`, true); console.error("Trim error:", err); // Additional error details for common issues if (err.message.includes('memory')) { showMessage("Video might be too large for available memory", true); } else if (err.message.includes('codec') || err.message.includes('format')) { showMessage("Video codec might not be supported", true); } } }; </script> </body> </html> Mastodon Reply handler (+ Akkoma) ---------------------- <!DOCTYPE html> <base href="https://" target="_blank" rel="noopener noreferrer"> <html lang="en"> <head> <meta charset="UTF-8"> <title>Akkoma Post Viewer</title> <style> body { font-family: sans-serif; //background-color: #f5f5f5; //padding: 2rem; } .toot { background: white; padding: 1rem; border-radius: 0px; max-width: 600px; box-shadow: 0 0 20px rgba(0,0,0,0.1); display: flex; gap: 1rem; margin-bottom: 1rem; flex-direction: column; } .avatar { width: 48px; height: 48px; border-radius: 50%; flex-shrink: 0; } .content { flex: 1; } .user { font-weight: bold; } .handle { color: gray; font-size: 0.9em; margin-left: 0.3em; } .post-content { margin: 0.5em 0; } .counts { color: gray; font-size: 0.9em; display: flex; gap: 1em; } .view-post { margin-top: 0em; font-size: 0.9em; color: #0077cc; } .view-post a { text-decoration: none; color: inherit; } .reply { margin-left: 2rem; /* Indent replies */ } .parent-stats { font-size: 1rem; color: gray; margin-bottom: 1rem; } </style> </head> <body> <div id="results"></div> <script> async function fetchEmojis(instance) { const url = `https://${instance}/api/v1/custom_emojis`; try { const response = await fetch(url); if (!response.ok) throw new Error("Failed to fetch emojis"); return await response.json(); } catch (error) { console.error("Error fetching emojis: ", error); return []; } } function replaceEmojis(content, emojis) { emojis.forEach(emoji => { const regex = new RegExp(`:${emoji.shortcode}:`, 'g'); const imgTag = `<img src="${emoji.url}" alt=":${emoji.shortcode}:" class="emoji" style="width:20px; height:20px;">`; content = content.replace(regex, imgTag); }); return content; } async function fetchPostStats(postUrl) { const { instance, statusId } = extractInstanceAndStatus(postUrl); try { const response = await fetch(`https://${instance}/api/v1/statuses/${statusId}`); if (!response.ok) throw new Error("Failed to fetch post"); const data = await response.json(); const acct = data.account; const emojis = await fetchEmojis(instance); let content = replaceEmojis(data.content, emojis); const fullPostUrl = data.url; const postHtml = ` <div class="toot"> <div class="header"> <div class="content"> <div class="parent-stats"> </div> <div class="counts"> ❤️ ${data.favourites_count} 🔁 ${data.reblogs_count} 💬 ${data.replies_count} </div> <div class="view-post"> <!--<a href="${fullPostUrl}" target="_blank">View This Post</a>--> </div> </div> </div> <div class="replies" id="replies-${statusId}"></div> </div> `; const repliesContainer = document.createElement('div'); repliesContainer.innerHTML = postHtml; document.getElementById('results').appendChild(repliesContainer); if (data.replies_count > 0) { await fetchReplies(statusId, instance); } } catch (error) { document.getElementById('results').innerHTML = `<p>Error: ${error.message}</p>`; } } async function fetchReplies(statusId, instance) { const url = `https://${instance}/api/v1/statuses/${statusId}/context`; try { const response = await fetch(url); if (!response.ok) throw new Error("Failed to fetch replies"); const data = await response.json(); const replies = data.descendants; const repliesContainer = document.getElementById(`replies-${statusId}`); for (const reply of replies) { const replyHtml = await renderPost(reply, instance); repliesContainer.innerHTML += replyHtml; if (reply.replies_count > 0) { await fetchReplies(reply.id, instance); } } } catch (error) { console.error("Error fetching replies: ", error); } } async function renderPost(post, instance) { const acct = post.account; const postUrl = post.url; // Use correct canonical post URL const emojis = await fetchEmojis(instance); let content = replaceEmojis(post.content, emojis); return ` <div class="toot reply"> <div class="header"> <img src="${acct.avatar_static}" alt="avatar" class="avatar"> <div class="content"> <div class="user"> ${acct.display_name || acct.username} <span class="handle">@${acct.acct}</span> </div> <div class="post-content">${content}</div> <div class="counts"> ❤️ ${post.favourites_count} 🔁 ${post.reblogs_count} 💬 ${post.replies_count} </div> <div class="view-post"> <a href="${postUrl}" target="_blank">View This Reply</a> </div> </div> </div> </div> `; } function extractInstanceAndStatus(postUrl) { const urlParts = postUrl.split('/'); const instance = urlParts[2]; const statusId = urlParts[urlParts.length - 1]; return { instance, statusId }; } const urlParams = new URLSearchParams(window.location.search); const postUrl = urlParams.get('url'); if (postUrl) { fetchPostStats(postUrl); } else { fetchPostStats('https://mas.to/@alcea/114893352699203026'); } </script> Fediverse Remote Account Suspension Checker ------------------------------ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Mastodon Suspension Check</title> <style> body { font-family: Arial, sans-serif; padding: 1rem; max-width: 700px; margin: auto; } label { display: block; margin-top: 1rem; font-weight: bold; } input[type="text"] { width: 100%; padding: 0.4rem; font-size: 1rem; } button { margin-top: 1rem; padding: 0.5rem 1rem; font-size: 1rem; cursor: pointer; } ul { list-style: none; padding-left: 0; margin-top: 1rem; } li { margin-bottom: 0.5rem; } .loading { color: gray; } .suspended { color: red; font-weight: bold; } .active { color: green; font-weight: bold; } .error { color: orange; font-weight: bold; } .lookup-domain { margin-left: 0.5rem; font-size: 0.9em; color: #555; font-family: monospace; text-decoration: none; } .lookup-domain:hover { text-decoration: underline; } .checkbox-label { font-weight: normal; margin-top: 0.5rem; display: flex; align-items: center; gap: 0.4rem; } </style> </head> <body> <h1>Am I suspended ? </h1> <ul id="results"> <!-- results go here --> </ul> <form id="checkForm"> <label for="acctInput">User handle to check (e.g. alceawis@alceawis.com):</label> <input type="text" id="acctInput" value="alceawis@alceawis.com" required /> <label for="followersUrlInput">Followers JSON endpoint URL:</label> <input type="text" id="followersUrlInput" value="https://alceawis.com/alceawis/followers" required /> <label class="checkbox-label"> <input type="checkbox" id="formatToggle" /> Use <code>normal Mastodon followers</code> JSON format </label> <button type="submit">Check Suspension Status</button> </form> <script> async function runCheck() { const acctToCheck = document.getElementById('acctInput').value.trim(); const userFollowersUrl = document.getElementById('followersUrlInput').value.trim(); const useNormalFormat = document.getElementById('formatToggle').checked; const resultsEl = document.getElementById('results'); resultsEl.innerHTML = '<li class="loading">Loading followers...</li>'; if (!acctToCheck || !userFollowersUrl) { resultsEl.innerHTML = '<li class="error">Please fill in both fields.</li>'; return; } resultsEl.innerHTML = ''; const extractDomain = url => { try { return new URL(url).hostname; } catch { return null; } }; const suspensionCache = new Map(); try { const resp = await fetch(userFollowersUrl); if (!resp.ok) throw new Error(`Failed to fetch followers: ${resp.status}`); const data = await resp.json(); let followersUrls = []; if (useNormalFormat) { // Normal Mastodon followers format: array of follower objects if (!Array.isArray(data)) { resultsEl.innerHTML = '<li class="error">Expected an array of follower objects in normal format.</li>'; return; } followersUrls = data.map(follower => follower.url).filter(Boolean); } else { // orderedItems format: object with orderedItems array of follower URLs followersUrls = data.orderedItems || []; } if (followersUrls.length === 0) { resultsEl.innerHTML = '<li>No followers found.</li>'; return; } for (const followerUrl of followersUrls) { const domain = extractDomain(followerUrl); if (!domain) { const li = document.createElement('li'); li.textContent = `Invalid follower URL: ${followerUrl}`; li.classList.add('error'); resultsEl.appendChild(li); continue; } if (!suspensionCache.has(domain)) { suspensionCache.set(domain, checkSuspended(domain, acctToCheck)); } const suspended = await suspensionCache.get(domain); const li = document.createElement('li'); const textNode = document.createTextNode(`${domain} — Suspended: ${suspended === null ? 'Error' : suspended ? 'YES' : 'NO'}`); li.appendChild(textNode); const lookupLink = document.createElement('a'); lookupLink.className = 'lookup-domain'; lookupLink.textContent = `[>> ${domain}]`; lookupLink.href = `https://${domain}/api/v1/accounts/lookup?acct=${encodeURIComponent(acctToCheck)}`; lookupLink.target = '_blank'; lookupLink.rel = 'noopener noreferrer'; li.appendChild(lookupLink); li.classList.add(suspended === null ? 'error' : suspended ? 'suspended' : 'active'); resultsEl.appendChild(li); } } catch (error) { resultsEl.innerHTML = `<li class="error">Error: ${error.message}</li>`; } async function checkSuspended(instance, acct) { const apiUrl = `https://${instance}/api/v1/accounts/lookup?acct=${encodeURIComponent(acct)}`; try { const resp = await fetch(apiUrl); if (resp.status === 404) { return true; } if (!resp.ok) { console.warn(`API error from ${instance}: ${resp.status}`); return null; } const json = await resp.json(); if ('suspended' in json) { return Boolean(json.suspended); } return false; } catch (e) { console.warn(`Error fetching from ${instance}:`, e); return null; } } } document.getElementById('checkForm').addEventListener('submit', event => { event.preventDefault(); runCheck(); }); window.addEventListener('load', () => { runCheck(); }); </script> </body> </html> Fedi Check Discoverability ------------------- <form id="lookup-form"><input type="text" id="domain" name="domain" placeholder="Enter domain" value="mas.to"><input type="text" id="user" name="user" placeholder="Enter username" value="@alceawis@alceawis.com"><button type="button" onclick="openLookup()">Submit</button></form><script>function openLookup() {const domain = document.getElementById('domain').value.trim();const user = document.getElementById('user').value.trim();if (domain && user) {const url = `https://${domain}/api/v1/accounts/lookup?acct=${user}`;window.open(url, '_blank');} else {alert("Please fill out both fields.");}}</script> Logger (Network & Console) ------------------------- <meta charset="UTF-8"> <style> #statusLine { //background: #333; padding: 5px 10px; //border: 1px solid #555; display: flex; justify-content: space-between; align-items: center; margin-top: -20px; cursor: pointer; opacity: 0.8; } #showLogs { font-size: 0.9em; text-decoration: underline; cursor: pointer; color: #66cc66; } #logModal { display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); } #modalContent { background: #333; margin: 5% auto; padding: 20px; border: 1px solid #444; width: 80%; max-height: 70%; overflow-y: auto; white-space: pre-wrap; font-size: 0.9em; color: #ddd; line-height: 1.5; opacity: 1; } #closeModal { float: right; cursor: pointer; font-weight: bold; color: #f8f8f8; } .log-section { margin-bottom: 1em; } a { color: #66ccff; text-decoration: underline; } .log-entry { white-space: pre-wrap; word-wrap: break-word; } #latestLog { font-size: 1.1em; color: #66cc66; } </style> </head> <body> <div id="statusLine"> <span id="latestLog">Waiting for events...</span> </div> <div id="logModal"> <div id="modalContent"> <span id="closeModal">[close]</span> <div class="log-section"><h3>Network Logs</h3><div id="networkLogs"></div></div> <div class="log-section"><h3>Console Logs</h3><div id="consoleLogs"></div></div> </div> </div> <script> const latest = document.getElementById('latestLog'), netDiv = document.getElementById('networkLogs'), conDiv = document.getElementById('consoleLogs'), modal = document.getElementById('logModal'), statusLine = document.getElementById('statusLine'), closeModal = document.getElementById('closeModal'), netHist = [], conHist = [], seen = new Set(); function update(msg) { latest.textContent = msg; } function formatLogWithLinks(text) { const urlRegex = /(https?:\/\/[^\s]+)/g; return text.replace(urlRegex, url => { return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`; }); } console.log = ((log) => (...a) => { const msg = "> " + a.map(x => typeof x === "object" ? JSON.stringify(x) : x).join(" "); conHist.push(msg); update(msg); log.apply(console, a); })(console.log); new PerformanceObserver(list => list.getEntries().forEach(async e => { if (seen.has(e.name)) return; seen.add(e.name); let pct = "100%"; let statusCode = " "; if (e.transferSize && e.encodedBodySize) { let val = Math.min(Math.round((e.transferSize / e.encodedBodySize) * 100), 100); if (!isFinite(val)) val = 100; pct = val + "%"; } try { const response = await fetch(e.name, { method: "HEAD" }); // Use HEAD request to only get headers statusCode = response.status; } catch (error) { console.error('Failed to fetch status code:', error); statusCode = "N/A"; } const msg = "> " + e.name + " - " + pct + " loaded (Status: " + statusCode + ")"; netHist.push(msg); update(msg); })).observe({ type: "resource", buffered: true }); statusLine.onclick = () => { netDiv.innerHTML = netHist.length ? netHist.map(formatLogWithLinks).join('<br>') : '(no network logs yet)'; conDiv.innerHTML = conHist.length ? conHist.map(formatLogWithLinks).join('<br>') : '(no console logs yet)'; modal.style.display = 'block'; }; closeModal.onclick = () => modal.style.display = 'none'; window.onclick = (e) => { if (e.target == modal) modal.style.display = 'none'; }; </script> <br> Repo(s) Compare Tool (GH/CB) ------------------------ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Repo Compare Tool</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } table { border-collapse: collapse; width: 100%; margin-top: 20px; } th, td { border: 1px solid #ccc; padding: 8px 12px; text-align: left; vertical-align: top; } th { background-color: #f9f9f9; } a { color: #0066cc; text-decoration: none; } a:hover { text-decoration: underline; } /* Style for forked repos when checkbox is checked */ .forked a { color: gray !important; font-style: italic; } #log { margin-top: 30px; padding: 10px; background: #f0f0f0; border: 1px solid #ccc; font-size: 14px; max-height: 200px; overflow-y: auto; } #log .log-entry { margin-bottom: 5px; } input { margin-right: 10px; } form label { margin-right: 15px; } </style> </head> <body> <h2>Compare Public Repos</h2> <label><input type="checkbox" id="markForked"> Mark forked repos (grey)</label> <label><input type="checkbox" id="fetchAll"> All repos</label> <form id="repoForm" style="margin-top: 10px;"> <label>URL 1: <input type="text" id="user1" value="https://codeberg.org/alceawisteria" required></label> <label>URL 2: <input type="text" id="user2" value="https://github.com/Ry3yr" required></label> <button type="submit">Compare</button> </form> <h3 id="sharedHeader">Shared Repositories</h3> <table id="sharedReposTable" aria-label="Shared repositories table"> <thead> <tr> <th>Repository Name</th> <!-- This will be replaced dynamically --> </tr> </thead> <tbody> <!-- Shared repos go here --> </tbody> </table> <h3 id="differentHeader">Different Repositories</h3> <table id="differentReposTable" aria-label="Different repositories table"> <thead> <tr> <th id="user1Header">User 1 Only</th> <th id="user2Header">User 2 Only</th> </tr> </thead> <tbody> <!-- Different repos go here --> </tbody> </table> <h3>Debug Log</h3> <div id="log"></div> <script> const logDiv = document.getElementById("log"); function log(msg) { const entry = document.createElement("div"); entry.className = "log-entry"; entry.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; logDiv.appendChild(entry); logDiv.scrollTop = logDiv.scrollHeight; } function detectPlatform(input) { if (input.includes("codeberg.org")) return "codeberg"; if (input.includes("github.com")) return "github"; return "github"; // default guess } function extractUsername(input) { input = input.trim(); try { if (input.startsWith("http")) { const url = new URL(input); const pathParts = url.pathname.split("/").filter(Boolean); return pathParts[0] || null; } else { return input; } } catch { return input; } } // Fetch all pages if fetchAll is true, otherwise just one page async function fetchRepos(platform, username, fetchAll = false) { let repos = []; let page = 1; const per_page = 100; // max per page for GitHub and probably Codeberg async function fetchPage(page) { let url; if (platform === "github") { url = `https://api.github.com/users/${username}/repos?per_page=${per_page}&page=${page}`; } else if (platform === "codeberg") { url = `https://codeberg.org/api/v1/users/${username}/repos?limit=${per_page}&page=${page}`; } else { log(`Unknown platform for user ${username}`); return []; } log(`Fetching from ${platform}: ${url}`); try { const res = await fetch(url); if (!res.ok) { log(`Failed to fetch from ${platform}: ${res.status} ${res.statusText}`); return []; } const data = await res.json(); log(`Fetched ${data.length} repos from ${platform} for ${username}, page ${page}`); return data.map(repo => ({ name: repo.name, fork: repo.fork })); } catch (err) { log(`Error fetching from ${platform}: ${err.message}`); return []; } } if (!fetchAll) { // Just fetch the first page only repos = await fetchPage(1); } else { // Fetch all pages until empty result while (true) { const pageRepos = await fetchPage(page); if (pageRepos.length === 0) break; repos = repos.concat(pageRepos); if (pageRepos.length < per_page) break; // Last page reached page++; } } return repos; } function makeRepoLink(platform, username, repo, markForked) { let base = platform === "github" ? "https://github.com" : "https://codeberg.org"; const cssClass = markForked && repo.fork ? 'forked' : ''; return `<span class="${cssClass}"><a href="${base}/${username}/${repo.name}" target="_blank" rel="noopener noreferrer">${repo.name}</a></span>`; } document.getElementById("repoForm").addEventListener("submit", async (e) => { e.preventDefault(); logDiv.innerHTML = ''; // Clear log const input1 = document.getElementById("user1").value; const input2 = document.getElementById("user2").value; const markForked = document.getElementById("markForked").checked; const fetchAll = document.getElementById("fetchAll").checked; const platform1 = detectPlatform(input1); const platform2 = detectPlatform(input2); const username1 = extractUsername(input1); const username2 = extractUsername(input2); log(`Starting comparison between "${username1}" (${platform1}) and "${username2}" (${platform2})`); if (fetchAll) log("Fetching ALL repos for each user"); // Update table headers with usernames document.getElementById("sharedHeader").textContent = `Repos shared by ${username1} and ${username2}`; document.getElementById("user1Header").textContent = `Repos only on ${username1}`; document.getElementById("user2Header").textContent = `Repos only on ${username2}`; // Update shared repos table header to two columns for both users document.querySelector("#sharedReposTable thead tr").innerHTML = ` <th>${username1}</th><th>${username2}</th> `; const [repos1, repos2] = await Promise.all([ fetchRepos(platform1, username1, fetchAll), fetchRepos(platform2, username2, fetchAll) ]); // Convert to map for easy lookup by name const map1 = new Map(repos1.map(r => [r.name, r])); const map2 = new Map(repos2.map(r => [r.name, r])); const sameNames = [...map1.keys()].filter(name => map2.has(name)); const only1Names = [...map1.keys()].filter(name => !map2.has(name)); const only2Names = [...map2.keys()].filter(name => !map1.has(name)); // Populate shared repos table (two columns) const sharedTbody = document.querySelector("#sharedReposTable tbody"); if (sameNames.length === 0) { sharedTbody.innerHTML = `<tr><td colspan="2"><em>No shared repositories.</em></td></tr>`; } else { sharedTbody.innerHTML = sameNames .map(name => { const repo1 = map1.get(name); const repo2 = map2.get(name); const link1 = repo1 ? makeRepoLink(platform1, username1, repo1, markForked) : ''; const link2 = repo2 ? makeRepoLink(platform2, username2, repo2, markForked) : ''; return `<tr><td>${link1}</td><td>${link2}</td></tr>`; }) .join(''); } // Populate different repos table with aligned rows const differentTbody = document.querySelector("#differentReposTable tbody"); const maxRows = Math.max(only1Names.length, only2Names.length); let rowsHTML = ''; for (let i = 0; i < maxRows; i++) { const repo1 = only1Names[i] ? makeRepoLink(platform1, username1, map1.get(only1Names[i]), markForked) : ''; const repo2 = only2Names[i] ? makeRepoLink(platform2, username2, map2.get(only2Names[i]), markForked) : ''; rowsHTML += `<tr><td>${repo1}</td><td>${repo2}</td></tr>`; } if (maxRows === 0) { rowsHTML = `<tr><td><em>No unique repositories on either user.</em></td><td></td></tr>`; } differentTbody.innerHTML = rowsHTML; log(`Comparison complete. Shared: ${sameNames.length}, ${username1} only: ${only1Names.length}, ${username2} only: ${only2Names.length}`); }); </script> </body> </html> Moving Button bar with LocStorage position memory -------------------------------------------------------------------------- <style> .marquee-container { width: 100vw; overflow: hidden; box-sizing: border-box; } .marquee { white-space: nowrap; position: relative; will-change: transform; } .marquee a { display: inline-block; margin-right: 0.25rem; } .marquee img { vertical-align: middle; width: 88px; height: 31px; } </style> <div class="marquee-container"> <div class="marquee" id="marquee"> <a href="https://yusaao.org" target="_blank"><img src="https://alceawis.de/other/images/buttons/yusaao.png" alt="yusaao"></a> <a href="https://m.youtube.com/watch?v=Kt4ojxXBIck#https://www.nintendolife.com/news/2025/06/arc-system-works-announces-new-interactive-adventure-for-switch-2" target="_blank"><img src="https://alceawis.de/other/images/buttons/derpy.png" alt="Derpy"></a> <a href="https://web.archive.org/web/20250629060949/https://phoster.zone/buttons/88/piracy.gif" target="_blank" rel="noopener noreferrer"><img src="https://i.ibb.co/27g10Wnn/piracy.gif" alt="Piracy"></a> <a href="https://example.com/browser-support" target="_blank"><img src="https://i.ibb.co/hRR0jL79/anybrowser.gif" alt="Any Browser"></a> <a href="https://alceawis.de/mmdvrmresources.html#https://alcea-wisteria.de/blog/2024/08/mmmd-vrm-vroid-mikumikudance-resources" target="_blank"><img src="https://alceawis.de/other/images/buttons/mikumikuvroid.png" alt="mikumiku"></a> <a href="#" id="reset-marquee">Reset</a> </div> </div> <!--<button id="reset-marquee">Reset</button>--> <script> let position = 0; (function () { const tryInit = () => { const marquee = document.getElementById('marquee'); if (!marquee) { requestAnimationFrame(tryInit); return; } const container = marquee.parentElement; position = parseFloat(localStorage.getItem('marqueePos')) || container.offsetWidth; function animateMarquee() { position -= 0.5; const marqueeWidth = marquee.scrollWidth; if (position < -marqueeWidth) { position = container.offsetWidth; } marquee.style.transform = `translateX(${position}px)`; requestAnimationFrame(animateMarquee); } animateMarquee(); window.addEventListener('beforeunload', () => { localStorage.setItem('marqueePos', position); }); document.getElementById('reset-marquee').addEventListener('click', () => { position = container.offsetWidth; localStorage.setItem('marqueePos', position); marquee.style.transform = `translateX(${position}px)`; }); }; tryInit(); })(); </script> Button Maker ------------ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Upload Image Inside Zone</title> <!-- html2canvas --> <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script> <!-- gif.js --> <script src="https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.worker.min.js"></script> <style> body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; margin-top: 0; touch-action: none; } .button-container { position: relative; width: 300px; height: 81px; background-image: url('https://i.ibb.co/Zp4WJmZX/emptybutton.png'); background-size: 100% 100%; background-repeat: no-repeat; border: 0; margin-bottom: 0; padding: 0; overflow: hidden; } .upload-zone { position: absolute; top: 5%; left: 2%; width: 95%; height: 87%; border: 0px dashed #555; box-sizing: border-box; overflow: hidden; cursor: grab; touch-action: none; background-color: transparent; } .image-wrapper { width: 100%; height: 100%; transform-origin: center center; transition: transform 0.05s ease; will-change: transform; } .image-wrapper img { width: 100%; height: 100%; object-fit: fit; pointer-events: none; user-select: none; touch-action: none; } input[type="file"] { display: none; } #output img { border: 0px solid #ccc; margin-top: 0px; max-width: 100%; } .controls { display: flex; flex-direction: column; align-items: center; gap: 5px; margin-bottom: 10px; } input[type="text"] { padding: 4px; width: 200px; box-sizing: border-box; } button { padding: 6px 12px; } </style> </head> <body> <div class="button-container" id="screenshotTarget"> <label class="upload-zone" title="Click to upload image"> <input type="file" accept="image/*" id="fileInput" /> <div class="image-wrapper" id="imageWrapper"> <img id="uploadedImage" src="" alt="" /> </div> </label> </div> <div class="controls"> <input type="text" id="filenameInput" placeholder="Enter base filename" /> <button id="screenshotBtn">PNG Screenshot</button> <!--<button id="gifBtn">GIF Screenshot</button>--> </div> <div id="output"></div> <script> const fileInput = document.getElementById('fileInput'); const uploadedImage = document.getElementById('uploadedImage'); const imageWrapper = document.getElementById('imageWrapper'); const uploadZone = document.querySelector('.upload-zone'); const screenshotBtn = document.getElementById('screenshotBtn'); const gifBtn = document.getElementById('gifBtn'); const output = document.getElementById('output'); const filenameInput = document.getElementById('filenameInput'); let scale = 1; let lastScale = 1; let startX = 0; let startY = 0; let translateX = 0; let translateY = 0; let initialDistance = null; let isDragging = false; function setTransform() { imageWrapper.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`; } fileInput.addEventListener('change', () => { const file = fileInput.files[0]; if (file && file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = e => { uploadedImage.src = e.target.result; scale = 1; lastScale = 1; translateX = 0; translateY = 0; setTransform(); }; reader.readAsDataURL(file); } else { uploadedImage.src = ''; alert('Please upload a valid image file.'); } }); function getDistance(touches) { const dx = touches[0].clientX - touches[1].clientX; const dy = touches[0].clientY - touches[1].clientY; return Math.sqrt(dx * dx + dy * dy); } uploadZone.addEventListener('touchstart', e => { if (e.touches.length === 2) { initialDistance = getDistance(e.touches); } else if (e.touches.length === 1) { isDragging = true; startX = e.touches[0].clientX - translateX; startY = e.touches[0].clientY - translateY; } }); uploadZone.addEventListener('touchmove', e => { e.preventDefault(); if (e.touches.length === 2) { const currentDistance = getDistance(e.touches); const delta = currentDistance / initialDistance; scale = Math.max(1, Math.min(lastScale * delta, 4)); setTransform(); } else if (e.touches.length === 1 && isDragging) { translateX = e.touches[0].clientX - startX; translateY = e.touches[0].clientY - startY; setTransform(); } }); uploadZone.addEventListener('touchend', e => { if (e.touches.length < 2) { lastScale = scale; initialDistance = null; } if (e.touches.length === 0) { isDragging = false; } }); uploadZone.addEventListener('mousedown', e => { isDragging = true; startX = e.clientX - translateX; startY = e.clientY - translateY; uploadZone.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', e => { if (isDragging) { translateX = e.clientX - startX; translateY = e.clientY - startY; setTransform(); } }); document.addEventListener('mouseup', () => { isDragging = false; uploadZone.style.cursor = 'grab'; }); function getFilename(ext) { const base = filenameInput.value.trim() || 'screenshot'; return base + '.' + ext; } screenshotBtn.addEventListener('click', () => { html2canvas(document.getElementById('screenshotTarget'), { useCORS: true, backgroundColor: null, scale: 2 }).then(canvas => { const link = document.createElement('a'); link.download = getFilename('png'); link.href = canvas.toDataURL('image/png'); link.click(); const img = new Image(); img.src = canvas.toDataURL('image/png'); output.innerHTML = ''; output.appendChild(img); }); }); gifBtn.addEventListener('click', () => { html2canvas(document.getElementById('screenshotTarget'), { useCORS: true, backgroundColor: null, scale: 2 }).then(canvas => { const gif = new GIF({ workers: 2, quality: 10, width: canvas.width, height: canvas.height }); gif.addFrame(canvas, { delay: 500 }); gif.on('finished', blob => { const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.download = getFilename('gif'); link.href = url; link.click(); const img = new Image(); img.src = url; output.innerHTML = ''; output.appendChild(img); }); gif.render(); }); }); </script> </body> </html> Youtube Channel Video Lister (OAuth) ------------------------------------ <!DOCTYPE html> <html> <head>   <title>YouTube Videos with OAuth Token Input</title>   <script src="https://apis.google.com/js/api.js"></script>   <style>     table { border-collapse: collapse; width: 100%; }     th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }     #tokenInputArea { margin-bottom: 1em; }     #downloadButtons { margin-top: 1em; }     #downloadButtons button { margin-right: 10px; }   </style> </head> <body>   <h1>YouTube Channel Videos</h1>   <div id="tokenInputArea">     <label for="oauthToken">Paste OAuth Access Token:</label><br>     <input type="text" id="oauthToken" size="80" placeholder="Paste your OAuth Access Token here"/>     <button id="submitToken">Submit Token</button>     <button id="clearToken">Clear Token</button>   </div>   <button id="authorize_button" style="display:none;">Authorize with Google</button>   <button id="signout_button" style="display:none;">Sign Out</button>   <div id="content"></div>   <div id="downloadButtons" style="display:none;">     <button id="downloadHtmlBtn">Download Table as HTML</button>     <button id="downloadJsonBtn">Download as JSON</button>   </div>   <script>     const API_KEY = 'YOUR_API_KEY'; // Replace with your API key     const DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest"];     const authorizeButton = document.getElementById('authorize_button');     const signoutButton = document.getElementById('signout_button');     const contentDiv = document.getElementById('content');     const tokenInput = document.getElementById('oauthToken');     const submitTokenBtn = document.getElementById('submitToken');     const clearTokenBtn = document.getElementById('clearToken');     const downloadButtonsDiv = document.getElementById('downloadButtons');     const downloadHtmlBtn = document.getElementById('downloadHtmlBtn');     const downloadJsonBtn = document.getElementById('downloadJsonBtn');     let accessToken = localStorage.getItem('yt_access_token') || null;     let lastVideoDetails = [];     let channelUsername = 'user'; // fallback username     function clearUI() {       contentDiv.innerHTML = '';       authorizeButton.style.display = 'none';       signoutButton.style.display = 'none';       downloadButtonsDiv.style.display = 'none';     }     async function callYouTubeApiWithToken(token) {       contentDiv.innerHTML = 'Loading videos...';       try {         // Get channel info         const channelResponse = await fetch('https://www.googleapis.com/youtube/v3/channels?part=contentDetails,snippet&mine=true', {           headers: {             'Authorization': 'Bearer ' + token           }         });         if (!channelResponse.ok) {           throw new Error('Failed to get channel info. Status: ' + channelResponse.status);         }         const channelData = await channelResponse.json();         if (!channelData.items || channelData.items.length === 0) {           contentDiv.innerHTML = 'No channel found or token has no permission.';           return;         }         // Extract username from snippet title or fallback to 'user'         if (channelData.items[0].snippet && channelData.items[0].snippet.title) {           channelUsername = channelData.items[0].snippet.title.replace(/\s+/g, '_').toLowerCase();         }         const uploadsPlaylistId = channelData.items[0].contentDetails.relatedPlaylists.uploads;         let videos = [];         let nextPageToken = '';         do {           const playlistResponse = await fetch(`https://www.googleapis.com/youtube/v3/playlistItems?part=snippet,contentDetails&playlistId=${uploadsPlaylistId}&maxResults=50&pageToken=${nextPageToken}`, {             headers: {               'Authorization': 'Bearer ' + token             }           });           if (!playlistResponse.ok) {             throw new Error('Failed to get playlist items. Status: ' + playlistResponse.status);           }           const playlistData = await playlistResponse.json();           videos = videos.concat(playlistData.items);           nextPageToken = playlistData.nextPageToken || '';         } while (nextPageToken);         const videoIds = videos.map(v => v.contentDetails.videoId);         let allVideoDetails = [];         for (let i = 0; i < videoIds.length; i += 50) {           const batchIds = videoIds.slice(i, i + 50).join(',');           const videosResponse = await fetch(`https://www.googleapis.com/youtube/v3/videos?part=snippet,statistics&id=${batchIds}`, {             headers: {               'Authorization': 'Bearer ' + token             }           });           if (!videosResponse.ok) {             throw new Error('Failed to get video details. Status: ' + videosResponse.status);           }           const videosData = await videosResponse.json();           allVideoDetails = allVideoDetails.concat(videosData.items);         }         lastVideoDetails = allVideoDetails;         renderVideos(allVideoDetails);         document.getElementById('tokenInputArea').style.display = 'none';         signoutButton.style.display = 'inline-block';         downloadButtonsDiv.style.display = 'block';       } catch (error) {         contentDiv.innerHTML = 'Error: ' + error.message;         console.error(error);       }     }     function renderVideos(videoDetails) {       if (!videoDetails.length) {         contentDiv.innerHTML = 'No videos found.';         return;       }       let html = '<table id="videosTable"><thead><tr>';       html += '<th>Title</th><th>Views</th><th>Likes</th><th>Comments</th><th>URL</th><th>Upload Date</th>';       html += '</tr></thead><tbody>';       videoDetails.forEach(video => {         const stats = video.statistics || {};         const snippet = video.snippet || {};         const url = `https://www.youtube.com/watch?v=${video.id}`;         html += '<tr>';         html += `<td>${snippet.title}</td>`;         html += `<td>${stats.viewCount || '0'}</td>`;         html += `<td>${stats.likeCount || '0'}</td>`;         html += `<td>${stats.commentCount || '0'}</td>`;         html += `<td><a href="${url}" target="_blank">Watch</a></td>`;         html += `<td>${new Date(snippet.publishedAt).toLocaleDateString()}</td>`;         html += '</tr>';       });       html += '</tbody></table>';       contentDiv.innerHTML = html;     }     function download(filename, content, type) {       const blob = new Blob([content], { type });       const url = URL.createObjectURL(blob);       const a = document.createElement('a');       a.href = url;       a.download = filename;       document.body.appendChild(a);       a.click();       document.body.removeChild(a);       URL.revokeObjectURL(url);     }     downloadHtmlBtn.addEventListener('click', () => {       const table = document.getElementById('videosTable');       if (!table) {         alert('No table to download.');         return;       }       // Wrap table in minimal HTML so it can be opened standalone       const htmlContent = `<!DOCTYPE html> <html> <head><meta charset="UTF-8"><title>YouTube Videos</title></head> <body> ${table.outerHTML} </body> </html>`;       download('youtube_videos.html', htmlContent, 'text/html');     });     downloadJsonBtn.addEventListener('click', () => {       if (!lastVideoDetails.length) {         alert('No video data to download.');         return;       }       const dateStr = new Date().toISOString().slice(0,10); // yyyy-mm-dd       const filename = `${dateStr}-${channelUsername}.json`;       download(filename, JSON.stringify(lastVideoDetails, null, 2), 'application/json');     });     // On submit token     submitTokenBtn.addEventListener('click', () => {       const token = tokenInput.value.trim();       if (!token) {         alert('Please paste an OAuth access token.');         return;       }       localStorage.setItem('yt_access_token', token);       accessToken = token;       callYouTubeApiWithToken(token);     });     // Clear token     clearTokenBtn.addEventListener('click', () => {       localStorage.removeItem('yt_access_token');       accessToken = null;       contentDiv.innerHTML = '';       document.getElementById('tokenInputArea').style.display = 'block';       signoutButton.style.display = 'none';       downloadButtonsDiv.style.display = 'none';       tokenInput.value = '';     });     // On page load, check for token     if (accessToken) {       tokenInput.value = accessToken;       callYouTubeApiWithToken(accessToken);     } else {       contentDiv.innerHTML = 'Please paste your OAuth access token above and submit.';       document.getElementById('tokenInputArea').style.display = 'block';     }   </script> </body> </html> Basic Reddit client ----------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Reddit Client</title> <style> body { font-family: Arial, sans-serif; max-width: 900px; margin: 30px auto; color: #222; } input, button { font-size: 1rem; padding: 0.4em; } .post { margin-bottom: 1.5em; } .post img { max-width: 400px; display: block; margin: 0.5em 0; } .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; } .modal-content { background: #fff; padding: 1em; max-width: 800px; width: 90%; max-height: 90%; overflow-y: auto; border-radius: 4px; position: relative; } .close-btn { position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 1.2rem; background: none; border: none; } .comment { border-left: 2px solid #ccc; padding-left: 10px; margin-top: 10px; } </style> </head> <body> <h1>Reddit Client</h1> <form id="subreddit-form"> <label>Subreddit: <input id="subreddit-input" value="deutschland"></label> <button type="submit">Load</button> </form> <div id="posts"></div> <button id="load-more" style="display:none">Load More</button> <div id="modal" class="modal"> <div class="modal-content"> <button class="close-btn">✖</button> <div id="modal-body"></div> </div> </div> <script> const form = document.getElementById('subreddit-form'); const input = document.getElementById('subreddit-input'); const postsDiv = document.getElementById('posts'); const loadMoreBtn = document.getElementById('load-more'); let after = ''; let currentSub = 'deutschland'; form.addEventListener('submit', e => { e.preventDefault(); currentSub = input.value.trim() || 'deutschland'; after = ''; postsDiv.innerHTML = ''; loadMoreBtn.style.display = 'none'; loadPosts(); }); loadMoreBtn.addEventListener('click', loadPosts); async function loadPosts() { const url = `https://www.reddit.com/r/${encodeURIComponent(currentSub)}.json?limit=10&after=${after}`; try { const res = await fetch(url); const json = await res.json(); const children = json.data.children; after = json.data.after || ''; children.forEach(child => renderPost(child.data)); loadMoreBtn.style.display = after ? 'block' : 'none'; } catch (err) { console.error(err); postsDiv.innerHTML += `<p>Error loading subreddit data.</p>`; } } function renderPost(post) { const div = document.createElement('div'); div.className = 'post'; const title = document.createElement('h3'); title.innerHTML = `<a href="#" data-permalink="${post.permalink}">${escapeHtml(post.title)}</a>`; title.querySelector('a').addEventListener('click', openModal); div.appendChild(title); let imgUrl = ''; if (post.preview && post.preview.images?.length) { imgUrl = post.preview.images[0].source.url.replace(/&amp;/g, '&'); } else if (/\.(jpe?g|png|gif)$/i.test(post.url)) { imgUrl = post.url; } if (imgUrl) { const img = document.createElement('img'); img.src = imgUrl; div.appendChild(img); } const snippet = document.createElement('p'); snippet.textContent = post.selftext ? post.selftext.substring(0, 150) + '...' : ''; div.appendChild(snippet); postsDiv.appendChild(div); } const modal = document.getElementById('modal'); const modalBody = document.getElementById('modal-body'); modal.querySelector('.close-btn').addEventListener('click', () => modal.style.display = 'none'); window.addEventListener('click', e => { if (e.target === modal) modal.style.display = 'none'; }); async function openModal(e) { e.preventDefault(); const permalink = e.target.dataset.permalink; const url = `https://www.reddit.com${permalink}.json`; try { const res = await fetch(url); const json = await res.json(); const post = json[0].data.children[0].data; const comments = json[1].data.children; modalBody.innerHTML = ` <h2><a href="https://www.reddit.com${post.permalink}" target="_blank">${escapeHtml(post.title)}</a></h2> <p>u/${post.author} • ${new Date(post.created_utc*1000).toLocaleString()} • ${post.score} points • ${post.num_comments} comments</p> <div>${decodeHtml(post.selftext_html || '')}</div> ${renderImages(post)} <h3>Comments</h3> `; comments.forEach(c => renderComment(c, 0, permalink)); modal.style.display = 'flex'; } catch (err) { console.error(err); } } function renderImages(post) { const imgs = []; if (/\.(jpe?g|png|gif)$/i.test(post.url)) imgs.push(post.url); if (post.preview?.images) { post.preview.images.forEach(img => { const src = img.source.url.replace(/&amp;/g, '&'); if (!imgs.includes(src)) imgs.push(src); }); } if (post.gallery_data?.items && post.media_metadata) { post.gallery_data.items.forEach(item => { const m = post.media_metadata[item.media_id]?.s?.u; if (m && !imgs.includes(m)) imgs.push(m.replace(/&amp;/g, '&')); }); } return imgs.map(u => `<img src="${u}" style="max-width:100%; margin:10px 0">`).join(''); } function renderComment(node, depth, postPermalink) { if (node.kind !== 't1') return; const c = node.data; const div = document.createElement('div'); div.className = 'comment'; div.style.marginLeft = depth * 20 + 'px'; div.innerHTML = ` <p><strong><a href="https://www.reddit.com/user/${c.author}" target="_blank">u/${c.author}</a></strong> (${c.score} pts) <a href="https://www.reddit.com${postPermalink}${c.id}" target="_blank" style="font-size:0.85em">[link]</a> </p> <div>${decodeHtml(c.body_html)}</div> `; modalBody.appendChild(div); if (c.replies?.data?.children) { c.replies.data.children.forEach(child => renderComment(child, depth + 1, postPermalink)); } } function escapeHtml(s) { return s.replace(/[&<>"']/g, m => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[m]); } function decodeHtml(html) { const txt = document.createElement('textarea'); txt.innerHTML = html; return txt.value; } // Initial load loadPosts(); </script> </body> </html> Youtube Comments Fetcher ------------------------------------------ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>YouTube Comments Loader</title> <style> .comment { margin-bottom: 1em; border-bottom: 1px solid #ccc; padding: 0.5em; } .reply { margin-left: 2em; color: #555; } button.reply-btn { margin-top: 0.5em; } </style> </head> <body> <h2>YouTube Comments Fetcher</h2> <label> API Key: <input type="text" id="apiKeyInput" placeholder="Enter YouTube API Key"> </label><br><br> <label> Video URL: <input type="text" id="videoUrl" placeholder="Paste YouTube video URL here"> </label><br><br> <button onclick="loadComments()">Load Comments</button> <div id="stats"></div> <div id="comments"></div> <script> function getQueryParam(name) { const params = new URLSearchParams(window.location.search); return params.get(name); } function extractVideoId(url) { const regex = /(?:v=|\.be\/|embed\/)([a-zA-Z0-9_-]{11})/; const match = url.match(regex); return match ? match[1] : null; } async function fetchTotalCommentCount(videoId, apiKey) { const statsUrl = `https://www.googleapis.com/youtube/v3/videos?part=statistics&id=${videoId}&key=${apiKey}`; const res = await fetch(statsUrl); if (!res.ok) throw new Error("Failed to fetch video stats"); const data = await res.json(); return parseInt(data.items[0].statistics.commentCount || "0", 10); } async function loadComments() { const apiKey = document.getElementById('apiKeyInput').value.trim(); const url = document.getElementById('videoUrl').value.trim(); const videoId = extractVideoId(url); if (!apiKey) { alert('API key is required'); return; } if (!videoId) { alert('Invalid YouTube URL'); return; } const statsDiv = document.getElementById('stats'); const commentsDiv = document.getElementById('comments'); statsDiv.innerHTML = ''; commentsDiv.innerHTML = '<p>Loading comments...</p>'; try { const totalComments = await fetchTotalCommentCount(videoId, apiKey); statsDiv.innerHTML = `<h3>Total Comments on Video: ${totalComments}</h3>`; } catch (e) { statsDiv.innerHTML = `<p style="color:red;">Error fetching total comment count</p>`; } let commentsHTML = ''; let pageToken = ''; let totalLoaded = 0; commentsDiv.innerHTML = ''; while (true) { const apiUrl = `https://www.googleapis.com/youtube/v3/commentThreads?part=snippet,replies&videoId=${videoId}&key=${apiKey}&maxResults=100&pageToken=${pageToken}`; const response = await fetch(apiUrl); if (!response.ok) { commentsDiv.innerHTML = `<p>Error: ${response.statusText}</p>`; return; } const data = await response.json(); data.items.forEach(item => { const topComment = item.snippet.topLevelComment.snippet; const commentId = item.snippet.topLevelComment.id; const replyCount = item.snippet.totalReplyCount; const commentHTML = ` <div class="comment" id="comment-${commentId}"> <p><strong>${topComment.authorDisplayName}</strong>:</p> <p>${topComment.textDisplay}</p> ${replyCount > 0 ? `<button class="reply-btn" onclick="loadReplies('${commentId}', '${apiKey}')">Load ${replyCount} Replies</button>` : ''} <div class="replies" id="replies-${commentId}"></div> </div> `; commentsDiv.insertAdjacentHTML('beforeend', commentHTML); totalLoaded++; }); if (!data.nextPageToken) break; pageToken = data.nextPageToken; await new Promise(resolve => setTimeout(resolve, 100)); } commentsDiv.insertAdjacentHTML('afterbegin', `<h4>Top-Level Comments Loaded: ${totalLoaded}</h4>`); } async function loadReplies(parentId, apiKey) { const repliesContainer = document.getElementById(`replies-${parentId}`); const button = document.querySelector(`#comment-${parentId} .reply-btn`); if (button) button.disabled = true; let pageToken = ''; let repliesHTML = ''; while (true) { const url = `https://www.googleapis.com/youtube/v3/comments?part=snippet&parentId=${parentId}&key=${apiKey}&maxResults=100&pageToken=${pageToken}`; const response = await fetch(url); if (!response.ok) { repliesContainer.innerHTML = `<p>Error loading replies: ${response.statusText}</p>`; return; } const data = await response.json(); data.items.forEach(reply => { const r = reply.snippet; repliesHTML += ` <div class="reply"> <p><strong>${r.authorDisplayName}</strong>: ${r.textDisplay}</p> </div> `; }); if (!data.nextPageToken) break; pageToken = data.nextPageToken; await new Promise(resolve => setTimeout(resolve, 100)); } repliesContainer.innerHTML = repliesHTML; } window.onload = () => { const apikey = getQueryParam('apikey'); if (apikey) { document.getElementById('apiKeyInput').value = apikey; } }; </script> </body> </html> File to code (box) --------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Multi File Reader</title> </head> <body> <input type="file" id="fileInput" multiple><br><br> <textarea id="output" rows="30" cols="100" readonly></textarea> <script> document.getElementById('fileInput').addEventListener('change', function(event) { const files = event.target.files; const output = document.getElementById('output'); const promises = []; for (let i = 0; i < files.length; i++) { const file = files[i]; const reader = new FileReader(); const promise = new Promise((resolve) => { reader.onload = function(e) { const content = e.target.result; const formatted = `${file.name}\n--\n${content}`; resolve(formatted); }; reader.readAsText(file); }); promises.push(promise); } Promise.all(promises).then(results => { output.value = results.join('\n\n'); }); }); </script> </body> </html> Monthly cost calc --------------------------- <!DOCTYPE html> <html> <head> <title>Monthly Cost Calculator</title> <style> body { font-family: Arial; padding: 20px; } .item { margin-bottom: 10px; } input[type="text"], input[type="number"] { padding: 5px; margin-right: 10px; } button { padding: 5px 10px; margin-top: 10px; } </style> </head> <body> <h2>Monthly Cost Calculator</h2> <div id="items"></div> <button onclick="addItem()">+ Add Item</button> <hr> <h3>Total: €<span id="total">0.00</span></h3> <button onclick="exportJSON()">Export as JSON</button> <script> function addItem(description = '', cost = 0) { const container = document.getElementById('items'); const div = document.createElement('div'); div.className = 'item'; div.innerHTML = ` <input type="text" placeholder="Description" value="${description}"> <input type="number" placeholder="Cost" value="${cost}" oninput="calculateTotal()" step="0.01" min="0"> `; container.appendChild(div); calculateTotal(); } function calculateTotal() { const inputs = document.querySelectorAll('#items input[type="number"]'); let total = 0; inputs.forEach(input => { total += parseFloat(input.value) || 0; }); document.getElementById('total').textContent = total.toFixed(2); } function exportJSON() { const items = []; const rows = document.querySelectorAll('#items .item'); rows.forEach(row => { const desc = row.querySelector('input[type="text"]').value; const cost = parseFloat(row.querySelector('input[type="number"]').value) || 0; items.push({ description: desc, cost: cost }); }); const dataStr = JSON.stringify(items, null, 2); const blob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'monthly_costs.json'; a.click(); URL.revokeObjectURL(url); } function getURLParameter(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name); } async function loadJSONFromURL() { const jsonUrl = getURLParameter('url'); if (jsonUrl) { try { const response = await fetch(jsonUrl); const data = await response.json(); data.forEach(item => addItem(item.description, item.cost)); } catch (error) { console.error('Failed to load JSON:', error); } } else { addItem(); } } window.onload = loadJSONFromURL; </script> </body> </html> Codeberg _ fetch earliest/lastet commit + patch (email) -------------------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Codeberg Commit Finder</title> <style> body { font-family: sans-serif; margin: 2rem; } input, button { padding: 0.5rem; } input[type="text"] { width: 360px; border: 1px solid #ccc; border-radius: 4px; } button { margin-left: 0.5rem; cursor: pointer; } #result { margin-top: 1.5rem; line-height: 1.5; white-space: pre-wrap; } a { color: #0366d6; margin-right: 1rem; } </style> </head> <body> <h1>Codeberg Commit Finder</h1> <label> Codeberg repo URL or user profile: <input id="repoInput" placeholder="e.g. https://codeberg.org/alceawisteria or /user/repo"> <button id="searchBtn">Find commit</button> </label> <label> <input type="checkbox" id="firstCommitCheck"> Find first-ever commit </label> <div id="result"></div> <script> async function detectRepoFromInput(input) { input = input.trim().replace(/\/+$/, ''); const matchRepo = input.match(/codeberg\.org\/([^\/]+)\/([^\/]+)/i); if (matchRepo) return `${matchRepo[1]}/${matchRepo[2]}`; const matchUser = input.match(/codeberg\.org\/([^\/]+)/i); if (matchUser) { const user = matchUser[1]; const r = await fetch(`https://codeberg.org/api/v1/users/${user}/repos`); if (!r.ok) throw new Error(`Cannot fetch repos for user ${user}`); const repos = await r.json(); if (!repos.length) throw new Error(`User ${user} has no public repos`); return `${user}/${repos[0].name}`; } const fallbackMatch = input.match(/^([\w-]+)\/([\w.-]+)$/); if (fallbackMatch) return input; throw new Error("Please enter a valid Codeberg repo or user profile URL"); } async function fetchCommits(repo, first = false) { const base = `https://codeberg.org/api/v1/repos/${repo}/commits`; let url = `${base}?limit=1&page=${first ? 9999 : 1}`; const res = await fetch(url); if (!res.ok) throw new Error(`Failed to get commits from Codeberg (${res.status})`); const commits = await res.json(); if (!commits.length) throw new Error("No commits found."); return commits[first ? commits.length - 1 : 0]; } document.getElementById('searchBtn').addEventListener('click', async () => { const raw = document.getElementById('repoInput').value; const first = document.getElementById('firstCommitCheck').checked; const result = document.getElementById('result'); result.textContent = 'Searching…'; try { const repo = await detectRepoFromInput(raw); const commit = await fetchCommits(repo, first); const url = `https://codeberg.org/${repo}/commit/${commit.sha}`; const patch = `${url}.patch`; result.innerHTML = ` <strong>Repository:</strong> <a href="https://codeberg.org/${repo}" target="_blank">${repo}</a><br> <strong>Commit SHA:</strong> <a href="${url}" target="_blank">${commit.sha.slice(0, 7)}</a> <a href="${patch}" target="_blank">[.patch]</a><br> <strong>Message:</strong> ${commit.commit.message}<br> <strong>Date:</strong> ${new Date(commit.commit.author.date).toLocaleString()} `; } catch (err) { result.textContent = `⚠️ ${err.message}`; } }); </script> </body> </html> Github _ fetch earliest/lastet commit + patch (email) -------------------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>GitHub Commit Finder</title> <style> body { font-family: system-ui, sans-serif; margin: 2rem; } label { display: block; margin-bottom: 0.75rem; } input, button { padding: 0.5rem; } input[type="text"] { width: 360px; border: 1px solid #ccc; border-radius: 4px; } button { margin-left: 0.5rem; cursor: pointer; } #result { margin-top: 1.5rem; line-height: 1.5; white-space: pre-wrap; } a { color: #0366d6; margin-right: 1rem; } </style> </head> <body> <h1>GitHub Commit Finder</h1> <label> GitHub username, profile URL, or repo URL/full-name: <input id="userInput" placeholder="e.g. Ry3yr or https://github.com/Ry3yr/OSTR"> <button id="searchBtn">Find commit</button> </label> <label> <input type="checkbox" id="firstCommitCheck"> Find first-ever commit </label> <div id="result"></div> <script> function extractRepoOrUser(raw) { raw = raw.trim().replace(/\/+$/, ''); // Match full repo URL const repoMatch = raw.match(/github\.com\/([^\/]+)\/([^\/]+)/i); if (repoMatch) return { type: "repo", repo: `${repoMatch[1]}/${repoMatch[2]}` }; // Match full repo format like "Ry3yr/OSTR" const simpleRepoMatch = raw.match(/^([\w-]+)\/([\w.-]+)$/); if (simpleRepoMatch) return { type: "repo", repo: raw }; // Match username or profile URL const userMatch = raw.match(/github\.com\/([^\/]+)/i); return { type: "user", user: userMatch ? userMatch[1] : raw }; } async function fetchLatestPushRepo(user) { const r = await fetch(`https://api.github.com/users/${user}/events/public?per_page=30`); if (!r.ok) throw new Error(`GitHub API returned ${r.status}`); const events = await r.json(); for (const ev of events) { if (ev.type === 'PushEvent') return ev.repo.name; } throw new Error('No recent repositories found.'); } async function fetchLatestCommit(repo) { const r = await fetch(`https://api.github.com/repos/${repo}/commits?per_page=1`); if (!r.ok) throw new Error(`Failed to get latest commit for ${repo}`); const [c] = await r.json(); return formatCommit(c, repo); } async function fetchFirstCommit(repo) { const r1 = await fetch(`https://api.github.com/repos/${repo}/commits?per_page=1`); if (!r1.ok) throw new Error(`Failed to get commit history for ${repo}`); const linkHeader = r1.headers.get("Link"); if (!linkHeader || !linkHeader.includes('rel="last"')) { const [only] = await r1.json(); return formatCommit(only, repo); } const lastPageMatch = linkHeader.match(/<([^>]+)>;\s*rel="last"/); const lastPageUrl = lastPageMatch?.[1]; const rLast = await fetch(lastPageUrl); const commits = await rLast.json(); return formatCommit(commits.at(-1), repo); } function formatCommit(c, repo) { return { repo, sha: c.sha, msg: c.commit.message, date: c.commit.author.date, url: `https://github.com/${repo}/commit/${c.sha}`, patch: `https://github.com/${repo}/commit/${c.sha}.patch` }; } document.getElementById('searchBtn').addEventListener('click', async () => { const input = document.getElementById('userInput').value; const first = document.getElementById('firstCommitCheck').checked; const resultBox = document.getElementById('result'); resultBox.textContent = 'Searching…'; try { const parsed = extractRepoOrUser(input); let repo; if (parsed.type === 'repo') { repo = parsed.repo; } else if (parsed.type === 'user') { repo = await fetchLatestPushRepo(parsed.user); } else { throw new Error('Invalid input.'); } const commit = first ? await fetchFirstCommit(repo) : await fetchLatestCommit(repo); resultBox.innerHTML = ` <strong>Repository:</strong> <a href="https://github.com/${commit.repo}" target="_blank">${commit.repo}</a><br> <strong>Commit SHA:</strong> <a href="${commit.url}" target="_blank">${commit.sha.slice(0, 7)}</a> EMAIL: <a href="${commit.patch}" target="_blank">[.patch]</a><br> <strong>Message:</strong> ${commit.msg}<br> <strong>Date:</strong> ${new Date(commit.date).toLocaleString()} `; } catch (err) { resultBox.textContent = `⚠️ ${err.message}`; } }); </script> </body> </html> Load Website from zip (VirtEnv + Zip FS + ServiceWorker) (complex9) -------------------------------------------------------- V4 (which works off Palapa (offline) Litespeed, but not GH Pages (use V3 there): https://web.archive.org/web/*/https://ry3yr.github.io/zipfs-sw-worker.zip ==zipfs-sw-worker.html== <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>ZIP-FS Demo</title> <style> body { font-family: monospace; white-space: pre-wrap; background: #222; color: #eee; padding: 1rem; } #log { max-height: 30vh; overflow-y: auto; border: 1px solid #555; padding: 1rem; background: #111; } iframe { width: 100%; height: 60vh; border: 2px solid #555; margin-top: 1rem; background: white; color: black; } a.link-root { display: inline-block; margin-top: 1rem; color: #6cf; text-decoration: underline; cursor: pointer; } </style> </head> <body> <h1>ZIP-FS Demo Logs</h1> <pre id="log">Loading logs...\n</pre> <!-- Friendly URL link --> <a href="/root" target="zipfs-iframe" class="link-root">Open Root (/root)</a> <!-- Leave iframe src blank initially --> <iframe id="zipfs-iframe" title="ZIPFS root" name="zipfs-iframe"></iframe> <script> const logEl = document.getElementById('log'); const iframe = document.getElementById('zipfs-iframe'); function log(...args) { const msg = '[ZIPFS] ' + args.join(' '); console.log(msg); logEl.textContent += msg + '\n'; logEl.scrollTop = logEl.scrollHeight; } function loadIframe() { log('Setting iframe src to /root'); iframe.src = '/root'; } if ('serviceWorker' in navigator) { log('Registering Service Worker...'); navigator.serviceWorker.register('/zipfs-sw.js').then(reg => { log('Service Worker registration succeeded:', reg); log('Service Worker state:', reg.active?.state || reg.installing?.state || 'unknown'); if (!navigator.serviceWorker.controller) { log('Page not yet controlled. Reloading in 500ms...'); setTimeout(() => location.reload(), 500); } else { // SW already controlling → safe to load iframe loadIframe(); } navigator.serviceWorker.oncontrollerchange = () => { log('Controller changed. Reloading page...'); location.reload(); }; }).catch(err => { log('Service Worker registration failed:', err); }); navigator.serviceWorker.addEventListener('message', event => { if (event.data?.type === 'SW_LOG') { log(event.data.text); } }); } else { log('Service Workers not supported in this browser.'); } </script> </body> </html> ==zipfs-sw.js== /* zipfs-sw.js */ importScripts('https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js'); const ZIP_URL = 'https://ry3yr.github.io/alceawis.de.zip'; const files = new Map(); /* Promise that resolves when the ZIP has finished loading */ let zipReadyResolve; const zipReadyPromise = new Promise(res => { zipReadyResolve = res; }); function swLog(...args) { const msg = '[ZIPFS SW] ' + args.join(' '); console.log(msg); self.clients.matchAll().then(clients => clients.forEach(c => c.postMessage({ type: 'SW_LOG', text: msg })) ); } self.addEventListener('install', () => { swLog('Installing…'); self.skipWaiting(); }); self.addEventListener('activate', event => { swLog('Activating → downloading ZIP…'); event.waitUntil( (async () => { const r = await fetch(ZIP_URL, { mode: 'cors' }); swLog('ZIP fetch status:', r.status); if (!r.ok) throw new Error('ZIP fetch failed'); const buf = await r.arrayBuffer(); swLog('Unzipping…'); const zip = await JSZip.loadAsync(buf); let count = 0; for (const entry of Object.values(zip.files)) { if (entry.dir) continue; const path = '/' + entry.name.split('/').slice(1).join('/'); const content = await entry.async('uint8array'); files.set(path, content); swLog('→', path, `(${content.length} bytes)`); count++; } swLog('Unzip done –', count, 'files'); zipReadyResolve(); await self.clients.claim(); })().catch(err => swLog('ZIP load failed:', err)) ); }); self.addEventListener('fetch', event => { const url = new URL(event.request.url); /* 1️⃣ Let cross‑origin requests go to the network untouched */ if (url.origin !== self.location.origin) return; /* 2️⃣ Friendly URLs: “/”, “/root”, “/home” → “/index.html” */ let path = url.pathname; if (path === '/' || path === '/root' || path === '/home') path = '/index.html'; event.respondWith( (async () => { /* Wait until the ZIP is ready */ await zipReadyPromise; const file = files.get(path); if (file) { swLog('Serving', path, 'from ZIP'); return new Response(file, { headers: { 'Content-Type': guessType(path) } }); } /* 3️⃣ Same‑origin file not in ZIP → fall back to network */ swLog('Not in ZIP, fetching from network:', path); try { return await fetch(event.request); } catch (err) { swLog('Network fetch failed:', err); return new Response('<h1>Offline & not cached</h1>', { headers: { 'Content-Type': 'text/html' }, status : 503 }); } })() ); }); function guessType(p) { return p.endsWith('.html') ? 'text/html' : p.endsWith('.js') ? 'application/javascript': p.endsWith('.css') ? 'text/css' : p.endsWith('.json') ? 'application/json' : p.endsWith('.png') ? 'image/png' : p.endsWith('.jpg')||p.endsWith('.jpeg') ? 'image/jpeg' : p.endsWith('.svg') ? 'image/svg+xml' : 'application/octet-stream'; } /* Optional error logging */ self.addEventListener('error', e => swLog('Error:', e.message)); self.addEventListener('unhandledrejection',e => swLog('Unhandled rejection:', e.reason)); Wage Calc / Lohnrechner --------------------------------------- <!DOCTYPE html> <html lang="de"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: Arial, sans-serif; padding: 20px; background-color: #f4f4f4; } .container { max-width: 450px; margin: auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); } label { display: block; margin-top: 15px; } input { width: 100%; padding: 8px; margin-top: 5px; box-sizing: border-box; } #bruttoMonth, #nettoMonth { font-weight: bold; } .result { color: green; } </style> </head> <body> <div class="container"> <h2>WageCalc</h2> <label for="hourlyWage">Brutto-Stundenlohn (€):</label> <input type="number" id="hourlyWage" placeholder="z. B. 22" step="0.01"> <label for="hoursPerDay">Stunden pro Tag:</label> <input type="number" id="hoursPerDay" value="8" placeholder="z. B. 8"> <label for="daysPerWeek">Tage pro Woche:</label> <input type="number" id="daysPerWeek" value="5" placeholder="z. B. 5"> <label for="weeksPerMonth">Wochen pro Monat (Standard: 4.33):</label> <input type="number" id="weeksPerMonth" value="4" placeholder="z. B. 4.33" step="0.01"> <label for="taxPercent">Steuerabzug in % (Standard: 25):</label> <input type="number" id="taxPercent" placeholder="z. B. 25" step="0.1" value="25"> <label for="bruttoMonth">Brutto-Monatslohn (€):</label> <input type="number" id="bruttoMonth" placeholder="Wird automatisch berechnet" step="0.01"> <label for="nettoMonth">Netto-Monatslohn (€):</label> <input type="text" id="nettoMonth" readonly class="result"> </div> <script> const hourlyWageEl = document.getElementById("hourlyWage"); const hoursPerDayEl = document.getElementById("hoursPerDay"); const daysPerWeekEl = document.getElementById("daysPerWeek"); const weeksPerMonthEl = document.getElementById("weeksPerMonth"); const taxPercentEl = document.getElementById("taxPercent"); const bruttoMonthEl = document.getElementById("bruttoMonth"); const nettoMonthEl = document.getElementById("nettoMonth"); let isEditingBrutto = false; function getValue(el, defaultValue = 0) { const val = parseFloat(el.value); return isNaN(val) ? defaultValue : val; } function updateNetto(brutto) { const taxPercent = getValue(taxPercentEl, 25); const netto = brutto * (1 - (taxPercent / 100)); nettoMonthEl.value = netto.toFixed(2) + " €"; } function calculateBrutto() { if (isEditingBrutto) return; const wage = getValue(hourlyWageEl); const hours = getValue(hoursPerDayEl); const days = getValue(daysPerWeekEl); const weeks = getValue(weeksPerMonthEl, 4.33); const brutto = wage * hours * days * weeks; bruttoMonthEl.value = brutto.toFixed(2); updateNetto(brutto); } function reverseCalculateHourlyWage() { const brutto = getValue(bruttoMonthEl); const hours = getValue(hoursPerDayEl); const days = getValue(daysPerWeekEl); const weeks = getValue(weeksPerMonthEl, 4.33); const totalHours = hours * days * weeks; if (totalHours > 0) { const newWage = brutto / totalHours; hourlyWageEl.value = newWage.toFixed(2); } updateNetto(brutto); } [hourlyWageEl, hoursPerDayEl, daysPerWeekEl, weeksPerMonthEl, taxPercentEl].forEach(input => { input.addEventListener("input", () => { isEditingBrutto = false; calculateBrutto(); }); }); bruttoMonthEl.addEventListener("input", () => { isEditingBrutto = true; reverseCalculateHourlyWage(); }); </script> </body> </html> ____________________ Rate Pay Calc ---------------------------- <style> body { font-family: Arial, sans-serif; text-align: center; padding: 40px; } .top-section { display: flex; justify-content: center; align-items: center; gap: 10px; margin-bottom: 30px; } .input-group { display: flex; flex-direction: column; align-items: flex-start; margin: 0 10px; } label { font-weight: bold; margin-bottom: 5px; } input[type="number"], input[type="text"] { padding: 8px; width: 180px; font-size: 16px; } input[type="range"] { transform: rotate(-90deg); width: 100px; } .bottom-section { display: flex; justify-content: center; align-items: center; gap: 10px; } .slider-vertical { display: flex; flex-direction: column; align-items: center; } </style> </head> <body> <h2>Payment Duration Calculator</h2> <div class="top-section"> <div class="slider-vertical"> <input type="range" id="rateSlider" min="50" max="1000" step="10" value="200"> <span>Rate</span> </div> <div class="input-group"> <label for="rate">Rate per Month ($):</label> <input type="number" id="rate" value="200"> </div> <div class="input-group"> <label for="total">Full Payment ($):</label> <input type="number" id="total" placeholder="e.g., 4800"> </div> </div> <div class="bottom-section"> <div class="input-group"> <label for="duration">Duration (Years & Months, e.g. 2y 6m):</label> <input type="text" id="duration" placeholder="e.g., 2y 6m"> </div> <div class="slider-vertical"> <input type="range" id="durationSlider" min="1" max="240" step="1" value="24"> <span>Months</span> </div> </div> <script> const rateInput = document.getElementById("rate"); const totalInput = document.getElementById("total"); const durationInput = document.getElementById("duration"); const rateSlider = document.getElementById("rateSlider"); const durationSlider = document.getElementById("durationSlider"); let updating = false; function updateDurationFromValues() { if (updating) return; const rate = parseFloat(rateInput.value); const total = parseFloat(totalInput.value); if (rate > 0 && total > 0) { updating = true; const months = Math.floor(total / rate); const years = Math.floor(months / 12); const remMonths = months % 12; durationInput.value = `${years}y ${remMonths}m`; durationSlider.value = months; updating = false; } } function updateRateFromDurationText() { if (updating) return; const total = parseFloat(totalInput.value); const match = durationInput.value.match(/(\d+)\s*y\s*(\d*)\s*m?/i); if (total > 0 && match) { updating = true; const years = parseInt(match[1]) || 0; const months = parseInt(match[2]) || 0; const totalMonths = years * 12 + months; if (totalMonths > 0) { const newRate = total / totalMonths; rateInput.value = newRate.toFixed(2); rateSlider.value = Math.round(newRate); durationSlider.value = totalMonths; } updating = false; } } function updateDurationFromSlider() { if (updating) return; const months = parseInt(durationSlider.value); const years = Math.floor(months / 12); const remMonths = months % 12; durationInput.value = `${years}y ${remMonths}m`; const total = parseFloat(totalInput.value); if (total > 0 && months > 0) { const rate = total / months; updating = true; rateInput.value = rate.toFixed(2); rateSlider.value = Math.round(rate); updating = false; } } function updateRateFromSlider() { if (updating) return; const rate = parseInt(rateSlider.value); rateInput.value = rate; updateDurationFromValues(); } rateInput.addEventListener("input", () => { rateSlider.value = rateInput.value; updateDurationFromValues(); }); totalInput.addEventListener("input", () => { updateDurationFromValues(); updateRateFromDurationText(); }); durationInput.addEventListener("input", updateRateFromDurationText); rateSlider.addEventListener("input", updateRateFromSlider); durationSlider.addEventListener("input", updateDurationFromSlider); </script> </body> </html> _________________________________ Dehumidifier Calculator ------------------------------------ <h2>Dehumidifier Time Calculator</h2> <label>Start RH (%): <input type="number" id="startRH" value="55"></label><br> <label>Target RH (%): <input type="number" id="targetRH" value="45"></label><br> <label>Room Area (m²): <input type="number" id="area" value="18"></label><br> <label>Ceiling Height (m): <input type="number" id="height" value="2.5"></label><br> <label>Rated Capacity (L/day at ideal conditions): <input type="number" id="capacity" value="10"></label><br> <label>Power (W): <input type="number" id="power" value="165"></label><br> <label>Airflow (m³/h): <input type="number" id="airflow" value="265"></label><br> <label>People in Room: <input type="number" id="people" value="1"></label><br><br> <button onclick="calculateRealisticTime()">Calculate Time</button> <h3 id="result"></h3> <script> function simulateTime(startRH, targetRH, volume, maxVapor, baseRateLPerHour, humanLPerHour) { let totalTimeHours = 0; let currentRH = startRH; while (currentRH > targetRH && totalTimeHours < 48) { const absCurrent = maxVapor * (currentRH / 100); const absNext = maxVapor * ((currentRH - 1) / 100); const deltaL = ((absCurrent - absNext) * volume) / 1000; const rhEfficiency = Math.min(1, Math.max(0.3, (currentRH - 35) / 25)); const effectiveRate = baseRateLPerHour * rhEfficiency - humanLPerHour; if (effectiveRate <= 0) return Infinity; const timeStep = deltaL / effectiveRate; totalTimeHours += timeStep; currentRH -= 1; } return totalTimeHours; } function calculateRealisticTime() { const startRH = parseFloat(document.getElementById('startRH').value); const targetRH = parseFloat(document.getElementById('targetRH').value); const area = parseFloat(document.getElementById('area').value); const height = parseFloat(document.getElementById('height').value); const capacityLDay = parseFloat(document.getElementById('capacity').value); const powerW = parseFloat(document.getElementById('power').value); const airflow = parseFloat(document.getElementById('airflow').value); const people = parseInt(document.getElementById('people').value); const volume = area * height; const maxVapor = 17.3; const idealRate = capacityLDay / 24; const rhFactor = (startRH - 30) / (80 - 30); const tempFactor = 20 / 30; const airflowFactor = Math.min(1, airflow / 300); const realWorldEfficiency = Math.max(0.3, rhFactor * tempFactor * airflowFactor); const baseRateLPerHour = idealRate * realWorldEfficiency; const humanMoisturePerHour = people * 0.05; const timeWithHumans = simulateTime(startRH, targetRH, volume, maxVapor, baseRateLPerHour, humanMoisturePerHour); const timeWithoutHumans = simulateTime(startRH, targetRH, volume, maxVapor, baseRateLPerHour, 0); if (!isFinite(timeWithHumans)) { document.getElementById('result').innerText = `Remove external humidity sources.Reaching humidity levels with current gear is impossible`; return; } const totalMinutes = timeWithHumans * 60; const extraTime = timeWithHumans - timeWithoutHumans; const perPersonTime = people > 0 ? extraTime / people : 0; let message = `Estimated Time: ${timeWithHumans.toFixed(2)} hours (${totalMinutes.toFixed(0)} minutes)`; if (people > 0) { message += `<br>ExtraSources +${extraTime.toFixed(2)} hours total`; message += `<br>Each person adds approx. ${perPersonTime.toFixed(2)} hours`; } document.getElementById('result').innerHTML = message; } </script> _____________________________ LinkMaker (HTML & BBCode) -------------------------------------------- <label>Title: <input type="text" id="titleInput"></label><br><br> <label>Link: <input type="text" id="linkInput"></label><br><br> <button onclick="generateLinks()">Generate</button> <h3>HTML Link:</h3> <textarea id="htmlOutput" rows="2" cols="60" readonly></textarea> <h3>BBCode Link:</h3> <textarea id="bbcodeOutput" rows="2" cols="60" readonly></textarea> <script> function generateLinks() { const title = document.getElementById("titleInput").value; const link = document.getElementById("linkInput").value; const htmlLink = `<a href="${link}" target="_blank">${title}</a>`; const bbcodeLink = `[url=${link}]${title}[/url]`; document.getElementById("htmlOutput").value = htmlLink; document.getElementById("bbcodeOutput").value = bbcodeLink; } </script> </body> </html> ______________ iOS 6 icon maker ---------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> body { font-family: sans-serif; background: linear-gradient(135deg, #cfd9df 0%, #e2ebf0 100%); height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; } .upload-container { text-align: center; } canvas { width: 200px; height: 200px; border-radius: 30px; box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); margin-top: 20px; } button { margin-top: 15px; padding: 8px 16px; border: none; border-radius: 8px; background-color: #3498db; color: white; cursor: pointer; } button:hover { background-color: #2980b9; } .options { margin-top: 10px; } </style> </head> <body> <div class="upload-container"> <h2>Upload Icon & Export with Glass Effect</h2> <input type="file" accept="image/*" onchange="handleFile(event)"> <div class="options"> <label> <input type="checkbox" id="glassCheckbox" checked> Include glass effect </label> </div> <canvas id="iconCanvas" width="400" height="400"></canvas> <br> <button onclick="downloadPng()">Download PNG</button> </div> <script> let originalFileName = "icon"; function handleFile(event) { const file = event.target.files[0]; if (!file) return; originalFileName = file.name.split(".").slice(0, -1).join(".") || "icon"; const reader = new FileReader(); reader.onload = function(e) { const img = new Image(); img.onload = function() { const canvas = document.getElementById("iconCanvas"); const ctx = canvas.getContext("2d"); const w = canvas.width; const h = canvas.height; const radius = 60; ctx.clearRect(0, 0, w, h); ctx.save(); ctx.beginPath(); ctx.moveTo(radius, 0); ctx.lineTo(w - radius, 0); ctx.quadraticCurveTo(w, 0, w, radius); ctx.lineTo(w, h - radius); ctx.quadraticCurveTo(w, h, w - radius, h); ctx.lineTo(radius, h); ctx.quadraticCurveTo(0, h, 0, h - radius); ctx.lineTo(0, radius); ctx.quadraticCurveTo(0, 0, radius, 0); ctx.closePath(); ctx.clip(); ctx.drawImage(img, 0, 0, w, h); const includeGlass = document.getElementById("glassCheckbox").checked; if (includeGlass) { const gradient = ctx.createLinearGradient(0, 0, 0, h * 0.4); gradient.addColorStop(0, "rgba(255, 255, 255, 0.6)"); gradient.addColorStop(1, "rgba(255, 255, 255, 0)"); ctx.fillStyle = gradient; ctx.fillRect(0, 0, w, h); ctx.beginPath(); ctx.ellipse(w / 2, h * 0.2, w * 0.65, h * 0.35, 0, 0, Math.PI * 2); ctx.fillStyle = "rgba(255, 255, 255, 0.29)"; ctx.fill(); } ctx.restore(); }; img.src = e.target.result; }; reader.readAsDataURL(file); } function downloadPng() { const canvas = document.getElementById("iconCanvas"); const link = document.createElement("a"); link.download = `${originalFileName}-glass.png`; link.href = canvas.toDataURL("image/png"); link.click(); } </script> </body> </html> __________________________ Sinwave Text anim ----------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> @property --progress { initial-value: 0; inherits: false; syntax: '<number>'; } @keyframes progress { from { --progress: 0; } to { --progress: 1; } } @font-face { font-family: 'ChaosLanguage'; src: url('/other/extra/font/chaoslanguage.ttf') format('truetype'); } .shape-container { position: fixed; top: 100px; left: 0; width: 100%; display: flex; justify-content: center; } .shape { position: relative; display: flex; justify-content: center; align-items: center; word-spacing: 0.5em; /* Increased space between words */ } .letter { display: inline-block; font-size: 4rem; /* Adjust font size to be 2x larger */ color: black; font-family: 'ChaosLanguage', Arial, sans-serif; /* Use ChaosLanguage font */ position: relative; animation: move-letter 3s ease-in-out infinite; letter-spacing: 0.1em; /* Increased space between letters */ } @keyframes move-letter { 0% { transform: translateY(0); } 50% { transform: translateY(-50px); /* Adjust the wave amplitude */ } 100% { transform: translateY(0); } } </style> </head> <body> <div class="shape-container"> <div class="shape" id="text-shape"> <!-- The letters will be dynamically added here --> </div> </div> <script> function generateWaveAnimation(text) { const container = document.getElementById("text-shape"); const lettersWithDelays = [...text].map((char, i) => { if (char === ' ') { return '&nbsp;'; // Add space between words } return `<span class="letter" style="animation-delay:${i * 0.1}s">${char}</span>`; }).join(''); container.innerHTML = lettersWithDelays; } const text = "There is nothing to see here"; generateWaveAnimation(text); </script> </body> </html> Character Portrait List (anigif) -------------------------------- <!DOCTYPE html> <html lang="en"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> .gallery { display: grid; grid-template-columns: repeat(8, 1fr); gap: 0; padding: 0; //width: 50vw; /* Full width */ //height: 50vh; /* Full height */ } .gif-container { position: relative; width: 400px; height: 600px; } .gif-container img { width: 400px; height: 600px; display: block; } .overlay { position: absolute; top: 0; left: 0; width: 400px; height: 600px; border: 1px solid black; pointer-events: none; } </style> </head> <body> <div class="gallery" id="gallery"></div> <script> async function fetchGIFs() { const baseURL = window.location.origin + window.location.pathname; // Get current directory const directoryURL = new URL("characters/", baseURL).href; // Navigate to 'characters/' folder const gallery = document.getElementById("gallery"); try { const response = await fetch(directoryURL); const text = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, "text/html"); const gifLinks = [...doc.querySelectorAll("a")] .map(a => a.href) .filter(href => href.endsWith(".gif")); gifLinks.forEach(gifURL => { const container = document.createElement("div"); container.className = "gif-container"; const img = document.createElement("img"); img.src = gifURL; img.alt = "Character GIF"; const overlay = document.createElement("div"); overlay.className = "overlay"; container.appendChild(img); container.appendChild(overlay); gallery.appendChild(container); }); } catch (error) { console.error("Error fetching GIFs:", error); } } fetchGIFs(); </script> </body> CD Music Wall ------------------ <!DOCTYPE html> <html lang="en"> <br><br> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: Arial, sans-serif; text-align: center; } .cd-container { display: grid; justify-content: center; gap: 1px; } .cd { width: 48px; height: 48px; position: relative; cursor: pointer; display: flex; align-items: center; transition: transform 0.3s ease-in-out; } .cd-case { width: 48px; height: 48px; position: relative; transform-origin: left; transition: transform 0.6s ease-in-out; background-size: cover; } .cd-disc { width: 48px; height: 48px; position: absolute; right: 0; opacity: 0; transition: opacity 0.6s ease-in-out; background-size: cover; } .cd.open .cd-case { transform: rotateY(-120deg); } .cd.open .cd-disc { opacity: 1; } .cd.zoomed-in { transform: scale(3); z-index: 1000; } </style> </head> <body> <script> function checkAndModifyLayout() { const urlParams = new URLSearchParams(window.location.search); if (!urlParams.has('layout')) { // If 'layout' doesn't exist, add it with value '4x8' urlParams.set('layout', '4x8'); window.location.search = urlParams.toString(); } } checkAndModifyLayout(); </script> <!---CDCase-Start---> <div class="cd-container" id="cd-container"></div> <script> function getQueryParam(name) { const params = new URLSearchParams(window.location.search); return params.get(name); } function applyLayout() { const layout = getQueryParam('layout'); const container = document.getElementById('cd-container'); if (layout) { const [rows, cols] = layout.split('x').map(Number); if (!isNaN(rows) && !isNaN(cols)) { container.style.gridTemplateColumns = `repeat(${cols}, 48px)`; container.style.gridTemplateRows = `repeat(${rows}, 48px)`; } } } async function loadCDs() { const response = await fetch('cds.txt'); const text = await response.text(); const container = document.getElementById('cd-container'); text.trim().split('\n').forEach(line => { const [album, img1, img2] = line.split(' '); const cd = document.createElement('div'); cd.className = 'cd'; cd.innerHTML = ` <div class="cd-case" style="background-image: url('${img1}');"></div> <div class="cd-disc" style="background-image: url('${img2}');"></div> `; const cdCase = cd.querySelector('.cd-case'); cd.onclick = (event) => { event.stopPropagation(); // Prevent body click from triggering immediately document.querySelectorAll('.cd').forEach(el => { if (el !== cd) el.classList.remove('open', 'zoomed-in'); }); cd.classList.toggle('open'); cd.classList.toggle('zoomed-in'); }; cdCase.onclick = (event) => { event.stopPropagation(); if (cd.classList.contains('open')) { window.open(`https://thispage.com?cd=${album}`, '_blank'); } }; container.appendChild(cd); }); applyLayout(); document.body.addEventListener('click', () => { document.querySelectorAll('.cd').forEach(cd => { cd.classList.remove('open', 'zoomed-in'); }); }); } loadCDs(); </script> </body> </html> [2 CD] image replace ver ------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Color Detection and Replacement Tool</title> <style> body { font-family: Arial, sans-serif; text-align: center; } canvas { border: 1px solid #000; margin-top: 20px; width: 50%; /* Makes the canvas 50% of its original width */ height: auto; /* Maintains aspect ratio */ } .upload-container { margin-top: 20px; } #output { margin-top: 20px; } </style> </head> <body> <h1>Color Detection and Replacement Tool</h1> <p>This tool highlights areas of the image where the color <code>#D7D7D7</code> appears and lets you replace them with your own images.</p> <canvas id="canvas"></canvas> <div class="upload-container"> <p>Upload two images to replace the highlighted areas:</p> <label>First Image: <input type="file" id="upload1" accept="image/*"></label> <label>Second Image: <input type="file" id="upload2" accept="image/*"></label> <button id="replaceBtn">Replace Areas</button> <br><br> <button id="toggleBtn">Toggle Red Markings</button> <br><br> <button id="downloadBtn">Download as PNG</button> </div> <div id="output"></div> <script> const imageUrl = "https://ry3yr.github.io/2CDTemplate.png"; const targetColor = { r: 215, g: 215, b: 215 }; // #D7D7D7 const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const output = document.getElementById("output"); const upload1 = document.getElementById("upload1"); const upload2 = document.getElementById("upload2"); const replaceBtn = document.getElementById("replaceBtn"); const toggleBtn = document.getElementById("toggleBtn"); const downloadBtn = document.getElementById("downloadBtn"); const img = new Image(); img.crossOrigin = "anonymous"; // To handle cross-origin images img.src = imageUrl; let redMarkingsVisible = true; // Tracks the visibility of the red markings let originalImageData = null; // Stores the original image data let redMarkingOverlay = null; // Stores the semi-transparent red markings let replacementImagesData = null; // Stores the replacement image data img.onload = function () { canvas.width = img.width; canvas.height = img.height; // Draw the original image on the canvas ctx.drawImage(img, 0, 0); // Highlight areas of #D7D7D7 highlightTargetColor(); }; img.onerror = function () { output.innerHTML = "<strong>Failed to load image. Check the URL and try again.</strong>"; }; function highlightTargetColor() { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; let found = false; for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Check if the pixel matches the target color if (r === targetColor.r && g === targetColor.g && b === targetColor.b) { // Highlight the pixel with red data[i] = 255; // Red data[i + 1] = 0; // Green data[i + 2] = 0; // Blue found = true; } } ctx.putImageData(imageData, 0, 0); originalImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); if (found) { createRedMarkingOverlay(imageData); // Create the semi-transparent overlay output.innerHTML = "<strong>Highlighted areas with #D7D7D7 in red.</strong>"; } else { output.innerHTML = "<strong>No areas with #D7D7D7 found.</strong>"; } } function createRedMarkingOverlay(imageData) { const overlay = ctx.createImageData(imageData.width, imageData.height); const overlayData = overlay.data; for (let i = 0; i < imageData.data.length; i += 4) { if (imageData.data[i] === 255 && imageData.data[i + 1] === 0 && imageData.data[i + 2] === 0) { // If the pixel is red, make it semi-transparent overlayData[i] = 255; // Red overlayData[i + 1] = 0; // Green overlayData[i + 2] = 0; // Blue overlayData[i + 3] = 128; // 50% opacity } else { // Leave other pixels transparent overlayData[i + 3] = 0; } } redMarkingOverlay = overlay; } toggleBtn.addEventListener("click", function () { if (redMarkingsVisible) { // Hide the red markings (overlay), but keep the replacement images visible ctx.putImageData(originalImageData, 0, 0); // Re-draw the base image if (replacementImagesData) { ctx.putImageData(replacementImagesData, 0, 0); // Re-draw the replacement images } redMarkingsVisible = false; } else { // Show the red markings (overlay) on top of the already replaced image ctx.putImageData(originalImageData, 0, 0); if (replacementImagesData) { ctx.putImageData(replacementImagesData, 0, 0); } if (redMarkingOverlay) { ctx.putImageData(redMarkingOverlay, 0, 0); // Add the red markings on top } redMarkingsVisible = true; } }); replaceBtn.addEventListener("click", function () { if (!upload1.files[0] || !upload2.files[0]) { output.innerHTML = "<strong>Please upload two images before replacing the areas.</strong>"; return; } const uploadedImg1 = new Image(); const uploadedImg2 = new Image(); uploadedImg1.src = URL.createObjectURL(upload1.files[0]); uploadedImg2.src = URL.createObjectURL(upload2.files[0]); uploadedImg1.onload = function () { uploadedImg2.onload = function () { // Store the uploaded images for replacement replacementImagesData = replaceAreasWithImages(uploadedImg1, uploadedImg2); }; }; }); function replaceAreasWithImages(img1, img2) { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; const visited = new Set(); const directions = [ [-1, 0], [1, 0], [0, -1], [0, 1] ]; // Neighboring pixels (left, right, up, down) let shapeIndex = 0; function floodFill(x, y) { const stack = [[x, y]]; const shapePixels = []; while (stack.length) { const [cx, cy] = stack.pop(); const index = (cy * canvas.width + cx) * 4; if ( cx >= 0 && cx < canvas.width && cy >= 0 && cy < canvas.height && !visited.has(index) && data[index] === 255 && data[index + 1] === 0 && data[index + 2] === 0 ) { visited.add(index); shapePixels.push([cx, cy]); for (const [dx, dy] of directions) { stack.push([cx + dx, cy + dy]); } } } return shapePixels; } function getBoundingBox(pixels) { let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; for (const [x, y] of pixels) { if (x < minX) minX = x; if (y < minY) minY = y; if (x > maxX) maxX = x; if (y > maxY) maxY = y; } return { minX, minY, maxX, maxY }; } for (let y = 0; y < canvas.height; y++) { for (let x = 0; x < canvas.width; x++) { const index = (y * canvas.width + x) * 4; if (data[index] === 255 && data[index + 1] === 0 && data[index + 2] === 0 && !visited.has(index)) { const shapePixels = floodFill(x, y); const { minX, minY, maxX, maxY } = getBoundingBox(shapePixels); const width = maxX - minX + 1; const height = maxY - minY + 1; const replacementImg = shapeIndex % 2 === 0 ? img1 : img2; shapeIndex++; ctx.drawImage(replacementImg, minX, minY, width, height); } } } return ctx.getImageData(0, 0, canvas.width, canvas.height); // Return the modified image data with replacements } downloadBtn.addEventListener("click", function () { const link = document.createElement("a"); link.download = "canvas-image.png"; link.href = canvas.toDataURL("image/png"); link.click(); }); </script> </body> </html> [2 CD] Label printer (xy pixel ver) ------------------------------ <a target="_blank" href="https://www.ebay.de/itm/126333019567#https://www.topstick-labels.com/fileadmin/Buero-Zuhause/Content/TopStick/productimages/8696_EBD_1.jpg" style=color:blue>UseWith</a> *<a target="_blank" href="https://www.herma.co.uk/office-home/free-software/label-assistant-online-standalone/label-assistant-online-standalone/?tx_hermaeao_pi1%5barticle_id%5d=8696" style=color:blue>Online Assistant</a><hr><br> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Label Template Maker</title> <style> body { font-family: Arial, sans-serif; text-align: center; } .container { display: flex; flex-direction: column; align-items: center; margin-top: 20px; } .upload-buttons { margin: 20px; } #canvas-container { position: relative; width: 210mm; height: 297mm; margin-top: 20px; border: 1px solid #000; } .label { position: absolute; width: 117mm; height: 117mm; border: 1px solid #ccc; overflow: hidden; border-radius: 50%; } .label1 { top: 21.5mm; left: 46.5mm; } .label2 { top: 158.5mm; left: 46.5mm; } img { width: 100%; height: 100%; object-fit: cover; } .button-container { margin-top: 20px; } canvas { display: none; } </style> </head> <body> <h1>Label Template Maker</h1> <div class="container"> <div class="upload-buttons"> <input type="file" id="image1" accept="image/*"> <input type="file" id="image2" accept="image/*"> </div> <div id="canvas-container"> <div id="label1" class="label label1"> <img id="img1" src="" alt="Label 1"> </div> <div id="label2" class="label label2"> <img id="img2" src="" alt="Label 2"> </div> </div> <div class="button-container"> <button id="download">Download as Image</button> </div> <canvas id="finalCanvas" width="793" height="1122"></canvas> </div> <script> const imageInput1 = document.getElementById('image1'); const imageInput2 = document.getElementById('image2'); const img1 = document.getElementById('img1'); const img2 = document.getElementById('img2'); const downloadButton = document.getElementById('download'); const finalCanvas = document.getElementById('finalCanvas'); const ctx = finalCanvas.getContext('2d'); function updateImage(input, imgElement) { const file = input.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(event) { imgElement.src = event.target.result; }; reader.readAsDataURL(file); } } imageInput1.addEventListener('change', () => updateImage(imageInput1, img1)); imageInput2.addEventListener('change', () => updateImage(imageInput2, img2)); function drawCanvas() { ctx.fillStyle = "#FFFFFF"; ctx.fillRect(0, 0, finalCanvas.width, finalCanvas.height); const mmToPx = 3.779527559; const labelWidth = 117 * mmToPx; const labelHeight = 117 * mmToPx; const img1Element = document.getElementById('img1'); const label1X = 46.5 * mmToPx; const label1Y = 21.5 * mmToPx; if (img1Element.src) { ctx.save(); ctx.beginPath(); ctx.arc( label1X + labelWidth / 2, label1Y + labelHeight / 2, labelWidth / 2, 0, Math.PI * 2 ); ctx.clip(); ctx.drawImage(img1Element, label1X, label1Y, labelWidth, labelHeight); ctx.restore(); } const img2Element = document.getElementById('img2'); const label2X = 46.5 * mmToPx; const label2Y = 158.5 * mmToPx; if (img2Element.src) { ctx.save(); ctx.beginPath(); ctx.arc( label2X + labelWidth / 2, label2Y + labelHeight / 2, labelWidth / 2, 0, Math.PI * 2 ); ctx.clip(); ctx.drawImage(img2Element, label2X, label2Y, labelWidth, labelHeight); ctx.restore(); } } downloadButton.addEventListener('click', () => { drawCanvas(); const link = document.createElement('a'); link.download = 'label-template.png'; link.href = finalCanvas.toDataURL(); link.click(); }); </script> </body> </html> CD Jewelcase Booklet Printer -------------------------------- <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: Arial, sans-serif; margin: 20px; } .preview { border: 1px solid #ccc; width: 210mm; height: 297mm; margin: 10px auto; position: relative; overflow: hidden; display: flex; justify-content: center; align-items: flex-start; flex-direction: column; } canvas { display: none; } .image-info { margin: 10px 0; font-size: 14px; } .image-info span { font-weight: bold; } </style> </head> <body> <h1>CD Jewelcase printer (2pcs)</h1> <input type="file" id="imageUpload" accept="image/*" multiple> <label><input type="checkbox" id="keepAspectRatio"> Keep Aspect Ratio</label> <div id="imageInfoContainer"></div> <div class="preview" id="preview"> <p>Preview will appear here.</p> </div> <button id="rotateBtn" style="display: none;">Rotate Image</button> <button id="downloadBtn" style="display: none;">Download PNG</button> <canvas id="canvas" width="2480" height="3508"></canvas> <!-- A4 at 300 DPI --> <script> const imageUpload = document.getElementById("imageUpload"); const previewDiv = document.getElementById("preview"); const downloadBtn = document.getElementById("downloadBtn"); const rotateBtn = document.getElementById("rotateBtn"); const keepAspectRatioCheckbox = document.getElementById("keepAspectRatio"); const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const imageInfoContainer = document.getElementById("imageInfoContainer"); let loadedImages = []; let currentRotation = 0; imageUpload.addEventListener("change", async (event) => { const files = event.target.files; if (files.length === 0) return; loadedImages = []; imageInfoContainer.innerHTML = ''; for (const file of files) { const img = await loadImage(URL.createObjectURL(file)); loadedImages.push({ img, file }); const scaleX = 1456 / img.width; const scaleY = 1456 / img.height; const imgInfo = document.createElement('div'); imgInfo.classList.add('image-info'); imgInfo.innerHTML = ` <span>Filename:</span> ${file.name} <br> <span>Resolution:</span> ${img.width} x ${img.height} px <br> <span>Scale X:</span> ${scaleX.toFixed(2)} <br> <span>Scale Y:</span> ${scaleY.toFixed(2)} <br> `; imageInfoContainer.appendChild(imgInfo); } processImages(); }); function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = reject; img.src = src; }); } function processImages() { const A4_WIDTH = 2480; const A4_HEIGHT = 3508; const IMAGE_SIZE = 1456; ctx.clearRect(0, 0, canvas.width, canvas.height); const offsets = calculateOffsets(loadedImages.length, A4_WIDTH, A4_HEIGHT, IMAGE_SIZE); drawImages(offsets); updatePreview(); downloadBtn.style.display = "inline-block"; rotateBtn.style.display = "inline-block"; } function calculateOffsets(count, canvasWidth, canvasHeight, imageSize) { const offsets = []; const spacing = 20; const totalHeight = count * imageSize + (count - 1) * spacing; const startX = (canvasWidth - imageSize) / 2; const startY = (canvasHeight - totalHeight) / 2; for (let i = 0; i < count; i++) { offsets.push({ x: startX, y: startY + i * (imageSize + spacing) }); } return offsets; } function drawImages(offsets) { const IMAGE_SIZE = 1456; loadedImages.forEach((data, index) => { const { img, file } = data; ctx.save(); ctx.translate(offsets[index].x + IMAGE_SIZE / 2, offsets[index].y + IMAGE_SIZE / 2); ctx.rotate(currentRotation); if (keepAspectRatioCheckbox.checked) { const scaleX = IMAGE_SIZE / img.width; const scaleY = IMAGE_SIZE / img.height; const scale = Math.max(scaleX, scaleY); const width = img.width * scale; const height = img.height * scale; ctx.drawImage(img, -width / 2, -height / 2, width, height); } else { ctx.drawImage(img, -IMAGE_SIZE / 2, -IMAGE_SIZE / 2, IMAGE_SIZE, IMAGE_SIZE); } ctx.restore(); }); } function updatePreview() { const dataUrl = canvas.toDataURL("image/png"); previewDiv.innerHTML = `<img src="${dataUrl}" alt="Preview" style="max-width:100%; max-height:100%;">`; } downloadBtn.addEventListener("click", () => { const link = document.createElement("a"); link.download = "print-ready.png"; link.href = canvas.toDataURL("image/png"); link.click(); }); rotateBtn.addEventListener("click", () => { currentRotation += Math.PI / 2; const offsets = calculateOffsets(loadedImages.length, 2480, 3508, 1456); drawImages(offsets); updatePreview(); }); </script> </body> </html> Mp3 Albumart & id3 tag extract ------------------------- <input type="file" id="fileInput" accept="audio/mp3" /> <div id="albumArtContainer"> <img id="albumArt" src="" alt="Album Art" style="max-widh: 300px;" /> <p id="imageFileName"></p> <!-- Display the file name --> </div> <br> <!-- Details/ Spoiler Section for ID3 Tags --> <details id="id3Details"> <summary>Click to view ID3 tags</summary> <div id="id3TagContent"></div> </details> <script src="https://cdnjs.cloudflare.com/ajax/libs/jsmediatags/3.9.5/jsmediatags.js"></script> <script type="text/javascript"> var jsmediatags = window.jsmediatags; document.getElementById('fileInput').addEventListener('change', function(event) { var file = event.target.files[0]; if (file) { document.getElementById('imageFileName').textContent = "File name: " + file.name; jsmediatags.read(file, { onSuccess: function(tag) { console.log(tag); if (tag.tags && tag.tags.picture) { var imageData = tag.tags.picture; var base64String = ""; for (var i = 0; i < imageData.data.length; i++) { base64String += String.fromCharCode(imageData.data[i]); } var base64Image = "data:" + imageData.format + ";base64," + window.btoa(base64String); document.getElementById("albumArt").src = base64Image; } var id3TagContent = document.getElementById("id3TagContent"); id3TagContent.innerHTML = ` <strong>Title:</strong> ${tag.tags.title || 'N/A'}<br> <strong>Artist:</strong> ${tag.tags.artist || 'N/A'}<br> <strong>Album:</strong> ${tag.tags.album || 'N/A'}<br> <strong>Year:</strong> ${tag.tags.year || 'N/A'}<br> <strong>Genre:</strong> ${tag.tags.genre || 'N/A'}<br> <strong>Track:</strong> ${tag.tags.track || 'N/A'}<br> <strong>Composer:</strong> ${tag.tags.composer || 'N/A'}<br> <strong>Publisher:</strong> ${tag.tags.publisher || 'N/A'}<br> `; }, onError: function(error) { console.log(error); } }); } }); </script> Invisible(simple) details spoiler ------------------- <details><summary style="color: transparent; text-decoration: none;">title</summary> </details> PMX Portfolio Showcase (mikumikidance 3d model) ----------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Display Model URLs</title> <style> body { font-family: Arial, sans-serif; margin: 20px; padding: 20px; } #output { white-space: pre-wrap; font-family: monospace; background-color: #f4f4f4; padding: 10px; border: 1px solid #ccc; } .iframe-container { display: flex; align-items: center; justify-content: center; margin-top: 20px; } iframe { width: 1200px; height: 700px; border: none; } button { padding: 10px; font-size: 16px; cursor: pointer; } #modelName { text-align: center; font-size: 18px; margin-top: 10px; } .unavailable { color: red; } .available { color: green; } </style> </head> <body> <details><summary>Model URLs</summary> <div id="output">Loading...</div> </details> <div class="iframe-container"> <button id="prevButton">Previous</button> <div id="iframe-container"></div> <button id="nextButton">Next</button> </div> <div class="iframe-container"> <button id="nextAvailableButton">Next Available</button> </div> <div id="modelName"></div> <script> let modelUrls = []; let checkUrls = []; let modelNames = []; let urlStatuses = []; // To store the availability of each URL let currentIndex = 0; // Function to check if a URL is reachable async function checkUrl(url) { try { const response = await fetch(url, { method: 'HEAD' }); return response.ok; } catch (error) { return false; } } // Function to check all URLs and store their statuses async function checkAllUrls() { const checks = checkUrls.map(url => checkUrl(url)); urlStatuses = await Promise.all(checks); } // Function to update the iframe and model name display function updateIframe() { if (modelUrls.length > 0) { const iframeContainer = document.getElementById('iframe-container'); iframeContainer.innerHTML = ''; // Clear previous iframe const modelNameDisplay = document.getElementById('modelName'); const url = modelUrls[currentIndex]; const name = modelNames[currentIndex]; // Update the model name display if (urlStatuses[currentIndex]) { const iframe = document.createElement('iframe'); iframe.src = url; iframeContainer.appendChild(iframe); modelNameDisplay.textContent = name; modelNameDisplay.className = 'available'; // Green for available } else { modelNameDisplay.innerHTML = `${name} - <span class="unavailable">not available</span>`; modelNameDisplay.className = 'unavailable'; // Red for unavailable } } } // Fetch the model list and initialize everything fetch('https://yusaao.org/bowlroll/list.txt') .then(response => response.text()) .then(async text => { const lines = text.split('\n'); modelNames = lines.map(line => { const match = line.match(/^\S+/); return match ? match[0] : null; }).filter(name => name !== null); // Generate the iframe and availability check URLs modelUrls = modelNames.map(name => `https://alcea-wisteria.de/PHP/SampleWebMMD-master/0loader.html?pmx=pmx/pronama/${name}/${name}.pmx` ); checkUrls = modelNames.map(name => `https://alcea-wisteria.de/PHP/SampleWebMMD-master/pmx/pronama/${name}/${name}.pmx` ); // Check all URLs and store their statuses await checkAllUrls(); // Update the output to show URLs and statuses const outputText = modelUrls.map((url, index) => { const status = urlStatuses[index] ? 'Available' : 'Not Available'; return `${url} - ${status}`; }).join('\n'); document.getElementById('output').textContent = outputText; if (modelUrls.length > 0) { updateIframe(); } }) .catch(error => { console.error('Error fetching the file:', error); document.getElementById('output').textContent = 'Error loading the file.'; }); // Event listener for "Previous" button document.getElementById('prevButton').addEventListener('click', () => { if (currentIndex > 0) { currentIndex--; updateIframe(); } }); // Event listener for "Next" button document.getElementById('nextButton').addEventListener('click', () => { if (currentIndex < modelUrls.length - 1) { currentIndex++; updateIframe(); } }); // Event listener for "Next Available" button document.getElementById('nextAvailableButton').addEventListener('click', () => { // Look for the next available model let nextAvailableIndex = currentIndex + 1; while (nextAvailableIndex < modelUrls.length && !urlStatuses[nextAvailableIndex]) { nextAvailableIndex++; } // If a valid next available model is found, update the iframe if (nextAvailableIndex < modelUrls.length) { currentIndex = nextAvailableIndex; updateIframe(); } }); </script> </body> </html> <details><summary>Src "0loader"</summary><plaintext> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> </head> <body> <button id="zoomInButton" style="position: absolute; top: 10px; right: 20px; font-size: 24px; padding: 20px; background-color: #f44336; color: white; border: none; border-radius: 10px;">-</button> <button id="zoomOutButton" style="position: absolute; top: 50px; right: 20px; font-size: 24px; padding: 20px; background-color: #008CBA; color: white; border: none; border-radius: 10px;">+</button> <!--<div class="pose"><input type="button" value="pose2" onclick="PoseClickEvent(this.value)"></div>--> <script src="./libs/three.js"></script> <!--<script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/libs/mmdparser.min.js"></script>--> <script src="./libs/mmdparser-obsolete.min.js"></script> <script src="./libs/ammo.min.js"></script> <script src="./libs/TGALoader.js"></script> <script src="./libs/MMDLoader.js"></script> <script src="./libs/MMDAnimationHelper.js"></script> <script src="./libs/CCDIKSolver.js"></script> <script src="./libs/MMDPhysics.js"></script> <script> let scene, renderer, camera, mesh, helper; let ready = false; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const clock = new THREE.Clock(); const urlParams = new URLSearchParams(window.location.search); const modelPath = urlParams.get('pmx') || './pmx/pronama/AoiZaizen/AoiZaizen.pmx'; // Default model path const vmdPath = urlParams.get('vmd') || 'bts-bestofme'; // Default VMD path (without extension) let isMouseDown = false; let previousMousePosition = { x: 0, y: 0 }; // Parameters for camera orbit let theta = Math.PI / 4; // Rotation angle (around Y axis) let phi = Math.PI / 4; // Rotation angle (around X axis) let radius = 50; // Camera distance from the origin (model) // Variables to store touch positions let isTouching = false; let touchStart = { x: 0, y: 0 }; // Variables for pinch-to-zoom let initialPinchDistance = null; window.onload = () => { Init(); LoadModeler(); Render(); // Set up zoom buttons document.getElementById("zoomInButton").addEventListener("click", () => { zoomCamera(1); // Zoom in }); document.getElementById("zoomOutButton").addEventListener("click", () => { zoomCamera(-1); // Zoom out }); }; Init = () => { scene = new THREE.Scene(); const ambient = new THREE.AmbientLight(0xeeeeee); scene.add(ambient); renderer = new THREE.WebGLRenderer({ alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0xcccccc, 0); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(40, windowWidth / windowHeight, 1, 1000); // Initial camera position updateCameraPosition(); // Mouse event listeners for rotation document.addEventListener('mousedown', onMouseDown, false); document.addEventListener('mousemove', onMouseMove, false); document.addEventListener('mouseup', onMouseUp, false); // Touch event listeners for rotation and pinch-to-zoom document.addEventListener('touchstart', onTouchStart, false); document.addEventListener('touchmove', onTouchMove, false); document.addEventListener('touchend', onTouchEnd, false); }; // Function to zoom the camera in or out zoomCamera = (direction) => { radius += direction * 5; // Adjust the zoom speed (5 units per button press) // Prevent zooming too close or too far radius = Math.max(10, Math.min(radius, 200)); // Adjust min/max zoom distance // Update camera position based on the new radius updateCameraPosition(); }; // Mouse interaction functions onMouseDown = (event) => { isMouseDown = true; previousMousePosition = { x: event.clientX, y: event.clientY }; }; onMouseMove = (event) => { if (!isMouseDown) return; const deltaX = event.clientX - previousMousePosition.x; const deltaY = event.clientY - previousMousePosition.y; // Update the angles based on mouse movement theta -= deltaX * 0.005; // Rotate around Y-axis phi -= deltaY * 0.005; // Invert rotation around X-axis (subtract instead of add) // Prevent the camera from flipping upside down phi = Math.max(Math.min(phi, Math.PI / 2), -Math.PI / 2); // Update camera position based on new angles updateCameraPosition(); // Update previous mouse position previousMousePosition = { x: event.clientX, y: event.clientY }; }; onMouseUp = () => { isMouseDown = false; }; // Touch interaction functions onTouchStart = (event) => { if (event.touches.length === 1) { // Start touch rotation isTouching = true; touchStart = { x: event.touches[0].clientX, y: event.touches[0].clientY }; } else if (event.touches.length === 2) { // Start pinch-to-zoom initialPinchDistance = getPinchDistance(event); } }; onTouchMove = (event) => { if (event.touches.length === 1 && isTouching) { // Rotate based on touch movement const deltaX = event.touches[0].clientX - touchStart.x; const deltaY = event.touches[0].clientY - touchStart.y; // Update the angles based on touch movement theta -= deltaX * 0.005; // Rotate around Y-axis phi -= deltaY * 0.005; // Invert rotation around X-axis (subtract instead of add) // Prevent the camera from flipping upside down phi = Math.max(Math.min(phi, Math.PI / 2), -Math.PI / 2); // Update camera position based on new angles updateCameraPosition(); // Update touch start position touchStart = { x: event.touches[0].clientX, y: event.touches[0].clientY }; } else if (event.touches.length === 2 && initialPinchDistance !== null) { // Pinch-to-zoom: Calculate new pinch distance const newPinchDistance = getPinchDistance(event); const distanceChange = newPinchDistance - initialPinchDistance; // Zoom in or out based on pinch distance change radius += distanceChange * 0.1; // Adjust zoom speed // Prevent zooming too close or too far radius = Math.max(10, Math.min(radius, 200)); // Update camera position based on new zoom (radius) updateCameraPosition(); // Update initial pinch distance for next move initialPinchDistance = newPinchDistance; } }; onTouchEnd = () => { isTouching = false; initialPinchDistance = null; }; // Helper function to calculate the distance between two touch points getPinchDistance = (event) => { const touch1 = event.touches[0]; const touch2 = event.touches[1]; const dx = touch2.clientX - touch1.clientX; const dy = touch2.clientY - touch1.clientY; return Math.sqrt(dx * dx + dy * dy); }; updateCameraPosition = () => { // Update camera position using spherical coordinates (radius, theta, phi) camera.position.x = radius * Math.sin(phi) * Math.cos(theta); camera.position.y = radius * Math.cos(phi); camera.position.z = radius * Math.sin(phi) * Math.sin(theta); // Make sure the camera always looks at the center of the scene (the origin) camera.lookAt(scene.position); }; LoadModeler = async () => { const loader = new THREE.MMDLoader(); LoadPMX = () => { return new Promise(resolve => { loader.load(modelPath, (object) => { mesh = object; // Move the model down along the Y-axis by a specific value (e.g., -10) mesh.position.y = -10; // Adjust this value to move the model down or up scene.add(mesh); resolve(true); }, onProgress, onError); }); } LoadVMD = (vmd) => { return new Promise(resolve => { const path = `./vmd/${vmd}.vmd`; // Correct VMD path format loader.loadAnimation(path, mesh, (vmdClip) => { vmdClip.name = vmd; VmdControl(vmd, vmdClip); resolve(true); }, onProgress, onError); }); } await LoadPMX(); await LoadVMD(vmdPath); } VmdControl = (id, vmdClip, loop = true) => { ready = false; helper = new THREE.MMDAnimationHelper({ afterglow: 2.0, resetPhysicsOnLoop: true }); helper.add(mesh, { animation: vmdClip, physics: false }); if (vmdClip) { const mixer = helper.objects.get(mesh).mixer; if (!loop) { mixer.existingAction(vmdClip).setLoop(THREE.LoopOnce); } mixer.addEventListener("loop", () => { console.log("loop"); }); mixer.addEventListener("finished", () => { console.log("finished"); VmdControl(id, vmdClip, true); }); } ready = true; } onProgress = (xhr) => { if (xhr.lengthComputable) { const percentComplete = (xhr.loaded / xhr.total) * 100; console.log(Math.round(percentComplete, 2) + '% downloaded'); } } onError = (xhr) => { console.log("ERROR"); } Render = () => { requestAnimationFrame(Render); renderer.clear(); renderer.render(scene, camera); if (ready) { helper.update(clock.getDelta()); } } </script> ______________ Nintendo eShop Purchase Parse: ----------------------------- <!DOCTYPE html> <html lang="en"> <style> .steps-container { display: flex; justify-content: space-between; padding: 10px; } .step { background-color: #f2f2f2; padding: 15px; border-radius: 8px; width: 22%; text-align: center; transition: background-color 0.3s; } .step a, .step button { color: #007BFF; text-decoration: none; font-weight: bold; margin-top: 10px; display: block; } .step button { background-color: #007BFF; color: white; padding: 10px; border: none; cursor: pointer; border-radius: 5px; } .step.completed { background-color: #28a745; } .step-number { font-size: 18px; font-weight: bold; color: #333; } </style> </head> <body> <div class="steps-container"> <div class="step" id="step-1"><div class="step-number">1</div><a href="https://accounts.nintendo.com/" target="_blank" onclick="markAsCompleted(event, 'step-1')">Login</a></div> <div class="step" id="step-2"><div class="step-number">2</div><a href="https://ec.nintendo.com/my/transactions/1" target="_blank" onclick="markAsCompleted(event, 'step-2')">View Transactions</a></div> <div class="step" id="step-3"><div class="step-number">3</div><a href="https://ec.nintendo.com/api/my/transactions?limit=100&offset=0" target="_blank" onclick="markAsCompleted(event, 'step-3')">Fetch Data</a></div> <div class="step" id="step-4"><div class="step-number">4</div><button onclick="fillFromClipboard()">Paste</button></div> </div> <script> function markAsCompleted(e, id) { e.preventDefault(); document.getElementById(id).classList.add('completed'); setTimeout(() => window.open(e.target.href, '_blank'), 300); } function fillFromClipboard() { alert("Paste functionality triggered."); } </script> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: Arial, sans-serif; margin: 20px; } textarea { width: 100%; height: 150px; margin-bottom: 20px; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f4f4f4; } button { margin-right: 10px; padding: 10px 15px; cursor: pointer; } </style> </head> <body> <h1>eShop Purchases</h1> <textarea id="jsonInput" placeholder="Paste your JSON here"></textarea> <button onclick="formatTable()">Format Table</button> <button onclick="removeZeroAmounts()">Remove Entries with 0,00 Amount</button> <!--<button onclick="fillFromClipboard()">Fill from Clipboard</button>--> <table id="outputTable"> <thead> <tr> <th>Name</th> <th>Amount</th> <th>Type</th> <th>Date</th> </tr> </thead> <tbody> </tbody> </table> <script> let currentData = []; function formatTable() { const input = document.getElementById("jsonInput").value; const tableBody = document.querySelector("#outputTable tbody"); tableBody.innerHTML = ""; try { const jsonData = JSON.parse(input); const transactions = jsonData.transactions; currentData = transactions; transactions.forEach(transaction => { const row = document.createElement("tr"); const nameCell = document.createElement("td"); nameCell.textContent = transaction.title || "N/A"; row.appendChild(nameCell); const amountCell = document.createElement("td"); amountCell.textContent = transaction.amount ? transaction.amount.formatted_value : "N/A"; row.appendChild(amountCell); const typeCell = document.createElement("td"); typeCell.textContent = transaction.transaction_type || "N/A"; row.appendChild(typeCell); const dateCell = document.createElement("td"); dateCell.textContent = transaction.date || "N/A"; row.appendChild(dateCell); tableBody.appendChild(row); }); } catch (error) { alert("Invalid JSON format. Please check your input."); } } function removeZeroAmounts() { const tableBody = document.querySelector("#outputTable tbody"); tableBody.innerHTML = ""; // Filter out entries where the formatted value is "0,00 €" or equivalent const filteredData = currentData.filter(transaction => { const amount = transaction.amount ? transaction.amount.formatted_value : null; return amount && !amount.trim().startsWith("0,00"); }); filteredData.forEach(transaction => { const row = document.createElement("tr"); const nameCell = document.createElement("td"); nameCell.textContent = transaction.title || "N/A"; row.appendChild(nameCell); const amountCell = document.createElement("td"); amountCell.textContent = transaction.amount ? transaction.amount.formatted_value : "N/A"; row.appendChild(amountCell); const typeCell = document.createElement("td"); typeCell.textContent = transaction.transaction_type || "N/A"; row.appendChild(typeCell); const dateCell = document.createElement("td"); dateCell.textContent = transaction.date || "N/A"; row.appendChild(dateCell); tableBody.appendChild(row); }); currentData = filteredData; } async function fillFromClipboard() { try { const clipboardText = await navigator.clipboard.readText(); document.getElementById("jsonInput").value = clipboardText; } catch (err) { alert("Failed to read clipboard content. Please ensure the clipboard contains valid JSON."); console.error("Clipboard error: ", err); } } </script> </body> </html> _____________________ Youtube embed generator ---------------------------------------- <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>YouTube Embed Generator</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } label { display: block; margin-top: 10px; } input, textarea { width: 100%; margin-top: 5px; padding: 10px; box-sizing: border-box; } button { margin-top: 15px; padding: 10px 20px; background-color: #007BFF; color: #fff; border: none; cursor: pointer; } button:hover { background-color: #0056b3; } iframe { margin-top: 20px; } textarea { height: 100px; } .copy-btn { margin-top: 10px; background-color: #28a745; } .copy-btn:hover { background-color: #218838; } </style> </head> <body> <h1>YouTube Embed Generator</h1> <label for="apiKey">API Key:</label> <input type="text" id="apiKey" placeholder="Enter your YouTube API Key"> <label for="youtubeUrl">YouTube URL:</label> <input type="text" id="youtubeUrl" placeholder="Enter YouTube URL"> <button id="generateEmbed">Generate Embed</button> <h2>Video Details</h2> <p><strong>Name:</strong> <span id="videoName">N/A</span></p> <p><strong>Video Link:</strong> <a id="videoLink" href="#" target="_blank">-</a></p> <h2>Embed Preview</h2> <iframe id="videoEmbed" width="560" height="315" frameborder="0" allowfullscreen></iframe> <h2>Embed Code</h2> <textarea id="embedCode" readonly></textarea> <button class="copy-btn" id="copyEmbed">Copy to Clipboard</button> <script> document.getElementById('generateEmbed').addEventListener('click', function() { const apiKey = document.getElementById('apiKey').value.trim(); const youtubeUrl = document.getElementById('youtubeUrl').value.trim(); const videoIdMatch = youtubeUrl.match(/(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/) || youtubeUrl.match(/(?:https?:\/\/)?youtu\.be\/([a-zA-Z0-9_-]+)/); if (!apiKey) { alert("Please enter your YouTube API Key."); return; } if (!videoIdMatch || !videoIdMatch[1]) { alert("Invalid YouTube URL."); return; } const videoId = videoIdMatch[1]; const apiUrl = `https://www.googleapis.com/youtube/v3/videos?part=snippet&id=${videoId}&key=${apiKey}`; fetch(apiUrl) .then(response => { if (!response.ok) { throw new Error("Error fetching video details. Check your API key or video ID."); } return response.json(); }) .then(data => { const video = data.items[0]; if (!video) { alert("No video found for the provided URL."); return; } const videoName = video.snippet.title; const videoLink = `https://www.youtube.com/watch?v=${videoId}`; const embedCode = `<b>${videoName}</b>\n<iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe>`; // Update the page with video details document.getElementById('videoName').textContent = videoName; document.getElementById('videoLink').textContent = videoLink; document.getElementById('videoLink').href = videoLink; document.getElementById('videoEmbed').src = `https://www.youtube.com/embed/${videoId}`; document.getElementById('embedCode').textContent = embedCode; }) .catch(error => { alert(error.message); }); }); document.getElementById('copyEmbed').addEventListener('click', function() { const embedCode = document.getElementById('embedCode'); embedCode.select(); embedCode.setSelectionRange(0, 99999); document.execCommand('copy'); alert("Embed code copied to clipboard!"); }); </script> __________________ Model download (bowlroll esque) ----------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Model Downloads</title> <h1> Model Downloads </h1><hr> <details><summary>Tools</summary> <a target="_blank" href="https://ry3yr.github.io/bowlroll.zip#https://codeberg.org/alceawisteria/pages/src/branch/main/modeldld" style=color:gray>src</a><br><a target="_blank" href="https://ry3yr.github.io/create_pmx_icon.zip#yusaao.org/bowlroll/models/create_pmx_icon.zip">Create_PMX_Icon</a></details><br> Since <a target="_blank" href="https://bowlroll.net/user/890347/files?sort=download&order=down&date=none&auth=none" style=color:blue>bowlroll</a> decided to delete all the 30 models I uploaded to its service on 2024/11/24 (many of em entirely made by me, some painstakingly retextured and rigged):<br> I'll upload my stuff here now<br> Previews are available on <a href=https://hub.vroid.com/en/users/75406576>my vroid hub</a> (probably ? unless they go maverick aswell)<br> -alcea<br> <hr><br> <style> /* Styles for the popup */ #popup { display: none; position: absolute; border: 2px solid #333; background-color: #fff; padding: 5px; z-index: 1000; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); } #popup img { max-width: 400px; /* Adjust max width for the popup */ height: auto; } </style> </head> <body> <div id="list-container"></div> <div id="popup"><img src="" alt="Full size preview"></div> <script> async function loadList() { try { const response = await fetch('https://yusaao.org/bowlroll/list.txt'); //const response = await fetch('https://alceawisteria.codeberg.page/modeldld/list.txt'); const text = await response.text(); const lines = text.split('\n').map(line => line.trim()).filter(line => line); const container = document.getElementById('list-container'); const popup = document.getElementById('popup'); const popupImg = popup.querySelector('img'); lines.forEach(line => { let [name, desc] = line.split('*').map(part => part.trim()); // Check for "*" and split const link = document.createElement('a'); link.href = `https://yusaao.org/bowlroll/models/${name}.zip`; link.target = '_blank'; const img = document.createElement('img'); img.src = `https://yusaao.org/bowlroll/imgs/${name}.jpg`; img.alt = name; img.style.width = '100px'; // adjust width as needed img.style.height = 'auto'; // maintain aspect ratio img.addEventListener('mouseenter', (e) => { popupImg.src = img.src; popup.style.display = 'block'; }); img.addEventListener('mousemove', (e) => { popup.style.top = `${e.pageY + 10}px`; popup.style.left = `${e.pageX + 10}px`; }); img.addEventListener('mouseleave', () => { popup.style.display = 'none'; popupImg.src = ''; }); link.appendChild(img); const label = document.createElement('p'); label.innerHTML = name + (desc ? ` - ${desc}` : ''); // Append desc if it exists const itemContainer = document.createElement('div'); itemContainer.appendChild(link); itemContainer.appendChild(label); container.appendChild(itemContainer); }); } catch (error) { console.error('Error loading list.txt:', error); } } loadList(); </script> </body> </html> __________ Twitter Embed relay ---------------- <details><summary>Bookmarklet</summary><textarea id="tweeturl" rows="8" cols="80"></textarea><br> <button onclick="openBookmarkDialogtweeturl()">Add to Bookmark</button> <button onclick="copyToClipboardtweeturl()">Copy to Clipboard</button> <script> var decodedStringtweeturl = atob('amF2YXNjcmlwdDooZnVuY3Rpb24oKSB7CiAgICB2YXIgY3VycmVudFVybCA9IGVuY29kZVVSSUNvbXBvbmVudChsb2NhdGlvbi5ocmVmKTsKICAgIHZhciBuZXdVcmwgPSAnaHR0cHM6Ly9hbGNlYXdpcy5kZS90d2l0dGVycmVsYXkuaHRtbD90d2VldHVybD0nICsgY3VycmVudFVybDsKICAgIHdpbmRvdy5vcGVuKG5ld1VybCwgJ19ibGFuaycpOwp9KSgpOwo='); function openBookmarkDialogtweeturl() { var bookmarkName = prompt("Enter bookmark name:"); if (bookmarkName) { var bookmarkUrl = decodedStringtweeturl; if (window.navigator.userAgent.indexOf("Chrome") !== -1 && chrome && chrome.bookmarks) { chrome.bookmarks.create({ title: bookmarkName, url: bookmarkUrl }, function() { alert("Bookmark added: " + bookmarkName); }); } else if (window.navigator.userAgent.indexOf("Firefox") !== -1 && browser && browser.bookmarks) { browser.bookmarks.create({ title: bookmarkName, url: bookmarkUrl }).then(function() { alert("Bookmark added: " + bookmarkName); }); } else { alert("Sorry, your browser does not support the bookmarking feature.");}}} function copyToClipboardtweeturl() { var tweeturl = document.getElementById("tweeturl").value; var tempInput = document.createElement("input"); tempInput.value = tweeturl; document.body.appendChild(tempInput); tempInput.select(); tempInput.setSelectionRange(0, 99999); document.execCommand("copy"); document.body.removeChild(tempInput); alert("Text copied to clipboard: " + tweeturl);} document.getElementById("tweeturl").value = decodedStringtweeturl; </script></details><br> <!--EmbedGenerator--> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Twitter Embed Generator</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } #embed { margin-top: 20px; text-align: center; } #debug { margin-top: 20px; color: red; } #htmlCode { margin-top: 20px; padding: 10px; background-color: #f9f9f9; border: 1px solid #ddd; font-family: monospace; white-space: pre-wrap; word-wrap: break-word; } </style> </head> <body> <h1>Twitter Embed Generator</h1> <input type="text" id="twitterUrl" placeholder="Enter Twitter URL" size="50"> <button id="embedButton">Embed Tweet</button> <div id="embed"></div> <div id="debug"></div> <div id="htmlCode"></div> <script> // Correct Base64 encoded HTML template const base64EmbedTemplate = ` PGRpdiBpZD0iZW1iZWQiPjwvZGl2PjxibG9ja3F1b3RlIGNsYXNzPSJ0d2l0dGVyLXR3ZWV0IiBkYXRh LWRudD0idHJ1ZSIgYWxpZ249ImNlbnRlciI+PHAgbGFuZz0iZW4iIGRpcj0idHJyIj5JdCYjMzc7cyBw cmV0dHkgYXdlc29tZSBob3cgZGFuY2luZyBtYWtlcyByb2JvdHMgbGVzcyBpbnRpbWlkYXRpbmcuIExv b2tpbmcgZm9yd2FyZCB0byBzZWVpbmcgbW9yZSBub250cml2aWFsIE1hY2hpbmUgTGVhcm5pbmcgb24g dGhlc2Ugcm9ib3RzLiBDcmVkaXQ6IEJvc3RvbiBEeW5hbWljcy4gPGEgaHJlZj0iaHR0cHM6Ly90d2l0 dGVyLmNvbS9SZXphX1phZGVoL3N0YXR1cy8ke3R3ZWV0SWR9P3JlZl9zcmM9dHdzcmMlNUR0Znc+PC9h PiZuYnNwOyBSZXphIFphZGVoIEBSZXphX1phZGVoPC9ibG9ja3F1b3RlPjxzY3JpcHQgYXN5bmMgc3Jj PSJodHRwczovL3BsYXRmb3JtLnR3aXR0ZXIuY29tL3dpZGdldHMuanMiIGNoYXJzZXQ9InV0Zi04Ij48 L3NjcmlwdD4=`; // Decode Base64 function decodeBase64(base64) { return decodeURIComponent(atob(base64).split('').map(function(c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } document.getElementById('embedButton').addEventListener('click', function() { const url = document.getElementById('twitterUrl').value; const tweetId = extractTweetId(url); const debugDiv = document.getElementById('debug'); const htmlCodeDiv = document.getElementById('htmlCode'); debugDiv.innerHTML = ""; // Clear previous debug info document.getElementById('embed').innerHTML = ""; // Clear previous embed htmlCodeDiv.innerHTML = ""; // Clear previous HTML code try { if (tweetId) { // Decode the Base64 template and insert the tweet ID let embedHtml = decodeBase64(base64EmbedTemplate); embedHtml = embedHtml.replace('${tweetId}', tweetId); // Replace placeholder with actual ID document.getElementById('embed').innerHTML = embedHtml; htmlCodeDiv.innerText = embedHtml.trim(); if (!document.getElementById('twitter-wjs')) { const script = document.createElement('script'); script.id = 'twitter-wjs'; script.src = 'https://platform.twitter.com/widgets.js'; script.async = true; script.charset = 'utf-8'; document.body.appendChild(script); } else { twttr.widgets.load(); } } else { throw new Error('Invalid Twitter URL format.'); } } catch (error) { debugDiv.innerHTML = `Error: ${error.message}`; } }); function extractTweetId(url) { const regex = /\/status\/(\d+)/; const match = url.match(regex); return match ? match[1] : null; } function prefillTweetUrl() { const urlParams = new URLSearchParams(window.location.search); const tweetUrl = urlParams.get('tweeturl'); if (tweetUrl) { document.getElementById('twitterUrl').value = tweetUrl; // Trigger the button click to embed the tweet automatically document.getElementById('embedButton').click(); } } window.onload = prefillTweetUrl; </script> </body> Amazon Search ---------------- <!DOCTYPE html> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script> function generateUrl() { const protocol = document.getElementById('protocol').value; const domain = document.getElementById('domain').value; const path = document.getElementById('path').value; const k = document.getElementById('k').value; const s = document.getElementById('s').value; const page = document.getElementById('page').value; const lowPrice = document.getElementById('lowPrice').value; const highPrice = document.getElementById('highPrice').value; const url = `${protocol}://${domain}${path}?k=${encodeURIComponent(k)}&s=${encodeURIComponent(s)}&page=${page}&low-price=${lowPrice}&high-price=${highPrice}`; document.getElementById('result').innerHTML = `<a href="${url}" target="_blank">${url}</a>`; } </script> </head> <body> <h1>AmazonSearch</h1> <label for="k">Search Query (k):</label> <input type="text" id="k" value=""><br><br><br> <label for="protocol">Protocol:</label> <input type="text" id="protocol" value="https"><br> <label for="domain">Domain:</label> <input type="text" id="domain" value="www.amazon.de"><br> <label for="path">Path:</label> <input type="text" id="path" value="/s"><br> <label for="s">Sort (s):</label> <input type="text" id="s" value="price-asc-rank"><br> <label for="page">Page Number:</label> <input type="number" id="page" value="1"><br> <label for="lowPrice">Low Price:</label> <input type="number" id="lowPrice" value=""><br> <label for="highPrice">High Price:</label> <input type="number" id="highPrice" value=""><br> <button onclick="generateUrl()">Generate URL</button> <div id="result"></div> </body> </html> ______________________ Youtube Load Channel stats (videos per week) -------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <button onclick="fillInput('UCmIpOnd5BVx5Si2hp0WNKZw')">repeekyraid</button> <button onclick="fillInput('UCrltGih11A_Nayz6hG5XtIw')">dkpr</button> <button onclick="fillInput('UC-5ooQ1hSQ-uM8Mu4dLko4Q')">cea</button> <script>function fillInput(channelId) {document.getElementById('channelId').value = channelId;}</script><br><br> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>YouTube Channel Video Stats</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head> <body> <label for="apiKey">API Key:</label> <input type="text" id="apiKey" placeholder="Enter YouTube API Key"> <br> <label for="channelId">Channel ID:</label> <input type="text" id="channelId" placeholder="Enter Channel ID"> <br> <button id="fetchData">Fetch Video Data</button> <canvas id="uploadChart" width="400" height="200"></canvas> <div id="videoLinks"></div> <script> const fetchDataButton = document.getElementById('fetchData'); const ctx = document.getElementById('uploadChart').getContext('2d'); const videoLinksDiv = document.getElementById('videoLinks'); let chartInstance; let weeklyVideosMap = {}; fetchDataButton.addEventListener('click', async () => { const apiKey = document.getElementById('apiKey').value; const channelId = document.getElementById('channelId').value; if (!apiKey || !channelId) { alert('Please provide both API key and Channel ID.'); return; } const videoData = await fetchAllVideos(apiKey, channelId); const weeklyData = groupVideosByWeek(videoData); displayLineChart(weeklyData); }); async function fetchAllVideos(apiKey, channelId) { let videoData = []; let nextPageToken = ''; const maxResults = 50; do { const response = await fetch(`https://www.googleapis.com/youtube/v3/search?key=${apiKey}&channelId=${channelId}&part=snippet&order=date&type=video&maxResults=${maxResults}&pageToken=${nextPageToken}`); const data = await response.json(); if (data.items) { data.items.forEach(item => { videoData.push({ title: item.snippet.title, url: `https://www.youtube.com/watch?v=${item.id.videoId}`, publishedAt: new Date(item.snippet.publishedAt) }); }); } nextPageToken = data.nextPageToken; } while (nextPageToken); return videoData; } function groupVideosByWeek(videoData) { const weeks = {}; videoData.forEach(video => { const startOfWeek = new Date(video.publishedAt.setDate(video.publishedAt.getDate() - video.publishedAt.getDay())); // Start of week (Sunday) const weekKey = startOfWeek.toISOString().split('T')[0]; if (!weeks[weekKey]) { weeks[weekKey] = []; } weeks[weekKey].push(video); }); weeklyVideosMap = weeks; // Store this map for reference when a point is clicked return Object.keys(weeks).sort().map(weekKey => ({ week: weekKey, count: weeks[weekKey].length })); } function displayLineChart(weeklyData) { const labels = weeklyData.map(data => data.week); const dataCounts = weeklyData.map(data => data.count); if (chartInstance) { chartInstance.destroy(); } chartInstance = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: 'Videos Uploaded per Week', data: dataCounts, backgroundColor: 'rgba(75, 192, 192, 0.2)', borderColor: 'rgba(75, 192, 192, 1)', borderWidth: 2, fill: true, tension: 0.3 // Smoothens the line }] }, options: { scales: { x: { title: { display: true, text: 'Weeks' } }, y: { beginAtZero: true, title: { display: true, text: 'Videos Uploaded' } } }, onClick: (e) => { const points = chartInstance.getElementsAtEventForMode(e, 'nearest', { intersect: true }, true); if (points.length) { const pointIndex = points[0].index; const week = chartInstance.data.labels[pointIndex]; displayVideosForWeek(week); } } } }); } function displayVideosForWeek(week) { const videos = weeklyVideosMap[week]; if (!videos) { videoLinksDiv.innerHTML = '<p>No videos for this week.</p>'; return; } const videoLinksHtml = videos.map(video => `<a href="${video.url}" target="_blank">${video.title}</a><br>`).join(''); videoLinksDiv.innerHTML = videoLinksHtml; } </script> </body> </html> ___________________________________________________ Akkoma Post resolver ------------------ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>API Key Form</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head> <body> <!-- HTML form to input the API key, pbUrl, and URL --> <form id="apiForm"> <label for="apikey">API Key:</label> <input type="text" id="apikey" name="apikey" required> <br> <label for="pbUrl">pbUrl:</label> <input type="text" id="pbUrl" name="pbUrl" required> <br> <label for="url">URL:</label> <input type="text" id="url" name="url" pattern="https://.*" required> <input type="submit" value="Submit"> <input type="button" value="Clear" id="clearButton"> </form> <!-- Result container --> <div id="result"></div> <script> $(document).ready(function() { // Function to get query string parameter value by name function getQueryStringParam(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name); } // Function to populate textboxes from query string values function populateTextboxesFromQueryString() { const apiKeyParam = getQueryStringParam('apikey'); const pbUrlParam = getQueryStringParam('pbUrl'); const urlParam = getQueryStringParam('search'); $('#apikey').val(apiKeyParam); $('#pbUrl').val(pbUrlParam); $('#url').val(urlParam); } // Call the function to populate textboxes on page load populateTextboxesFromQueryString(); // Check if ampersand is present in the URL bar const urlBarValue = window.location.href; if (urlBarValue.includes('&')) { // Retrieve form values const apiKey = $('#apikey').val(); const pbUrl = $('#pbUrl').val(); const search = $('#url').val(); // Perform AJAX request performAjaxRequest(apiKey, pbUrl, search); } // Perform AJAX request function performAjaxRequest(apiKey, pbUrl, search) { const url = pbUrl + "/api/v2/search/?q=" + encodeURIComponent(search) + "&limit=1&resolve=true"; // Disable form elements $("#apikey").prop("disabled", true); $("#pbUrl").prop("disabled", true); $("#url").prop("disabled", true); $("#submit").prop("disabled", true); // Perform AJAX request $.ajax({ url: url, headers: { "Authorization": "Bearer " + apiKey }, success: function(response) { if (response.statuses && response.statuses.length > 0 && response.statuses[0].id) { const id = response.statuses[0].id; // Construct the new Akkoma URL const newUrl = `${pbUrl}/notice/${id}`; // Redirect to the new URL in the same tab window.location.href = newUrl; } else { $("#result").html("Please enter a valid URL.<br>cURL Result: " + JSON.stringify(response) + "<br>" + url); } }, error: function(xhr, status, error) { $("#result").html("Error: " + error); }, complete: function() { // Re-enable form elements $("#apikey").prop("disabled", false); $("#pbUrl").prop("disabled", false); $("#url").prop("disabled", false); $("#submit").prop("disabled", false); } }); } // Submit form event handler $("#apiForm").submit(function(event) { event.preventDefault(); // Prevent default form submission // Retrieve form values const apiKey = $("#apikey").val(); const pbUrl = $("#pbUrl").val(); const search = $("#url").val(); // Perform AJAX request performAjaxRequest(apiKey, pbUrl, search); }); // Clear button event handler $("#clearButton").click(function() { // Clear input values $("#apikey").val(""); $("#pbUrl").val(""); $("#url").val(""); // Clear result container $("#result").html(""); }); }); </script> </body> </html> _______________________ Akkoma API Key Generator ------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Akkoma API Key Generator</title> </head> <body> <h1>Akkoma API Key Generator</h1> <form id="apiKeyForm"> <label for="instanceUrl">Instance URL (include http/https):</label> <input type="text" id="instanceUrl" name="instanceUrl" placeholder="https://your-instance-url" required><br><br> <label for="appName">Application Name:</label> <input type="text" id="appName" name="appName" required><br><br> <label for="scopes">Scopes (comma-separated):</label> <input type="text" id="scopes" name="scopes" value="read,write,follow" required><br><br> <label for="username">Username:</label> <input type="text" id="username" name="username" required><br><br> <label for="password">Password:</label> <input type="password" id="password" name="password" required><br><br> <button type="submit">Generate API Key</button> </form> <h2>Generated API Key</h2> <pre id="apiKeyResult"></pre> <script> document.getElementById('apiKeyForm').addEventListener('submit', function(event) { event.preventDefault(); const instanceUrl = document.getElementById('instanceUrl').value; const appName = document.getElementById('appName').value; const scopes = document.getElementById('scopes').value.replace(/\s+/g, ''); const username = document.getElementById('username').value; const password = document.getElementById('password').value; fetch(`${instanceUrl}/api/v1/apps`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ client_name: appName, redirect_uris: 'urn:ietf:wg:oauth:2.0:oob', scopes: scopes }) }) .then(response => response.json()) .then(data => { if (data.client_id && data.client_secret) { // Now use the client_id and client_secret to get an access token return fetch(`${instanceUrl}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ client_id: data.client_id, client_secret: data.client_secret, grant_type: 'password', username: username, password: password, scope: scopes }) }); } else { throw new Error('Failed to get client_id and client_secret'); } }) .then(response => response.json()) .then(tokenData => { if (tokenData.access_token) { document.getElementById('apiKeyResult').textContent = `Access Token (API Key): ${tokenData.access_token}`; } else { document.getElementById('apiKeyResult').textContent = 'Error generating access token: ' + JSON.stringify(tokenData); } }) .catch(error => { document.getElementById('apiKeyResult').textContent = 'Fetch error: ' + error; }); }); </script> </body> </html> Parceltrack ----------------- <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Parceltrack</title> <style> .input-group { margin-bottom: 10px; } input { margin-right: 5px; } .output { margin-top: 20px; } iframe { width: 100%; height: 300px; border: none; margin-bottom: 10px; } .link { display: block; margin-bottom: 5px; } </style> </head> <h1> Parcel Track</h1> <body> <div id="container"> </div> <button id="addButton">Add New</button> <button id="saveButton">Save</button> <button id="loadButton">WhereIsMyPackage ?!</button> <div id="output" class="output"> </div> <script> document.addEventListener('DOMContentLoaded', function() { const container = document.getElementById('container'); const addButton = document.getElementById('addButton'); const saveButton = document.getElementById('saveButton'); const loadButton = document.getElementById('loadButton'); const output = document.getElementById('output'); function createInputGroup(number = '', service = '') { const div = document.createElement('div'); div.classList.add('input-group'); const numberInput = document.createElement('input'); numberInput.type = 'text'; numberInput.placeholder = 'Number'; numberInput.value = number; const serviceInput = document.createElement('input'); serviceInput.type = 'text'; serviceInput.placeholder = 'dhl hermes dpd'; serviceInput.value = service; div.appendChild(numberInput); div.appendChild(serviceInput); container.appendChild(div); } function loadFromLocalStorage() { const savedData = JSON.parse(localStorage.getItem('inputGroups')); if (savedData) { savedData.forEach(group => { createInputGroup(group.number, group.service); }); } else { createInputGroup(); } } function saveToLocalStorage() { const inputGroups = []; document.querySelectorAll('.input-group').forEach(group => { const number = group.children[0].value; const service = group.children[1].value; inputGroups.push({ number, service }); }); localStorage.setItem('inputGroups', JSON.stringify(inputGroups)); //alert('Data saved to local storage!'); } // Load and render URLs or iframes based on the parcel provider function renderURLs() { output.innerHTML = ''; const savedData = JSON.parse(localStorage.getItem('inputGroups')); if (savedData) { savedData.forEach(group => { const { number, service } = group; let iframeUrl = ''; let url = ''; if (service.toLowerCase() === 'hermes') { iframeUrl = `https://parcelsapp.com/de/tracking/${number}`; url = `https://www.myhermes.de/empfangen/sendungsverfolgung/sendungsinformation#${number}`; } else if (service.toLowerCase() === 'dpd') { iframeUrl = `https://parcelsapp.com/de/tracking/${number}`; url = `https://dpd.com/track?number=${number}`; } else if (service.toLowerCase() === 'dhl') { iframeUrl = `https://parcelsapp.com/de/tracking/${number}`; url = `https://www.dhl.de/de/privatkunden/kundenkonto/willkommen.html?piececode=${number}`; } else { url = `https://example.com/?number=${number}`; } const link = document.createElement('a'); link.href = url; link.textContent = `${service}: ${url}`; link.target = '_blank'; link.classList.add('link'); output.appendChild(link); // If it's one of the three services, display the iframe below the link if (iframeUrl) { const iframe = document.createElement('iframe'); iframe.src = iframeUrl; output.appendChild(iframe); } }); } } // Add event listeners addButton.addEventListener('click', () => createInputGroup()); saveButton.addEventListener('click', saveToLocalStorage); loadButton.addEventListener('click', renderURLs); // Load data when the page loads loadFromLocalStorage(); }); </script> ________________ Song Duration changer (Nightcore-ify) ft.ffmpeg ------------------------------------------------ drag and drop song, enter new desired duration, press button, bam (oh and a lil chipmunk button, incase you wanna go full nightcore)<br><br> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Audio Speed Adjuster</title> <style> body { font-family: Arial, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; } #drop-zone { border: 2px dashed #ccc; border-radius: 10px; padding: 20px; width: 300px; text-align: center; } #file-info { margin-top: 10px; } #ffmpeg-command { margin-top: 20px; padding: 10px; background-color: #f0f0f0; border-radius: 5px; word-break: break-all; } </style> </head> <body> <div id="drop-zone">Drop your audio file here</div> <div id="file-info"></div> <input type="text" id="new-length" placeholder="New length (mm:ss) or Speed Factor" /> <label><input type="checkbox" id="speed-mode" /> Speed Mode (2x means twice as fast playback)</label> <button onclick="generateFFmpegCommand()">Generate FFmpeg Command</button> <button onclick="addAserateFilter()">Chip</button> <div id="ffmpeg-command"></div> <script> let audioFile; let originalDuration; let command = ''; document.getElementById('drop-zone').addEventListener('dragover', function(event) { event.preventDefault(); event.stopPropagation(); event.dataTransfer.dropEffect = 'copy'; }); document.getElementById('drop-zone').addEventListener('drop', function(event) { event.preventDefault(); event.stopPropagation(); audioFile = event.dataTransfer.files[0]; if (audioFile) { document.getElementById('file-info').innerText = `File: ${audioFile.name}`; const audio = new Audio(URL.createObjectURL(audioFile)); audio.addEventListener('loadedmetadata', function() { originalDuration = audio.duration; const minutes = Math.floor(originalDuration / 60); const seconds = Math.floor(originalDuration % 60); document.getElementById('file-info').innerText += `\nOriginal Duration: ${minutes}:${seconds.toString().padStart(2, '0')} (${originalDuration.toFixed(2)} seconds)`; }); } }); function generateFFmpegCommand() { const newLength = document.getElementById('new-length').value; const speedMode = document.getElementById('speed-mode').checked; if (!audioFile || !newLength || !originalDuration) { alert('Please drop an audio file and enter the new length or speed factor.'); return; } let baseCommand; if (speedMode) { const speedFactor = parseFloat(newLength); if (isNaN(speedFactor) || speedFactor <= 0) { alert('Please enter a valid speed factor.'); return; } if (speedFactor > 2 || speedFactor < 0.5) { const sqrtFactor = Math.sqrt(speedFactor); baseCommand = `ffmpeg -i "${audioFile.name}" -filter:a "atempo=${sqrtFactor},atempo=${sqrtFactor}"`; } else { baseCommand = `ffmpeg -i "${audioFile.name}" -filter:a "atempo=${speedFactor}"`; } } else { const [newMinutes, newSeconds] = newLength.split(':').map(Number); const newDuration = (newMinutes * 60) + newSeconds; const speedFactor = originalDuration / newDuration; if (speedFactor > 2 || speedFactor < 0.5) { const sqrtFactor = Math.sqrt(speedFactor); baseCommand = `ffmpeg -i "${audioFile.name}" -filter:a "atempo=${sqrtFactor},atempo=${sqrtFactor}"`; } else { baseCommand = `ffmpeg -i "${audioFile.name}" -filter:a "atempo=${speedFactor}"`; } } command = baseCommand + ' output.mp3'; document.getElementById('ffmpeg-command').innerText = command; } function addAserateFilter() { if (!command) { alert('Please generate the FFmpeg command first.'); return; } const aserateFilter = 'asetrate=44100'; command = command.replace(' output.mp3', `,${aserateFilter} output.mp3`); document.getElementById('ffmpeg-command').innerText = command; } </script> </body> </html> __________________________________ Line Compare Tool (For File Safety Check) -------------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Line Checker</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } textarea { width: 100%; height: 200px; margin-bottom: 20px; } button { padding: 10px 20px; font-size: 16px; } </style> </head> <body> <h1>Line Cleaner</h1> <textarea id="inputText1" placeholder="Enter your text here..."></textarea> <textarea id="inputText2" placeholder="Enter your text here..."></textarea> <button onclick="cleanLines()">Clean</button> <script> function cleanLines() { const textAreas = [document.getElementById('inputText1'), document.getElementById('inputText2')]; textAreas.forEach(textArea => { const lines = textArea.value.split('\n'); const cleanedLines = lines.map(line => line.replace(/^\d+\.\s*/, '')); textArea.value = cleanedLines.join('\n'); }); } </script> <!---Check Function---> <button onclick="compareTexts()">Compare</button> <div id="results"></div> <script> function compareTexts() { const text1 = document.getElementById('inputText1').value.split('\n'); const text2 = document.getElementById('inputText2').value.split('\n'); const totalLines1 = text1.length; const totalLines2 = text2.length; let matchingLinesCount = 0; let differingLines = []; text1.forEach((line1, index1) => { let foundMatch = false; text2.forEach((line2) => { if (line1 === line2) { foundMatch = true; } }); if (foundMatch) { matchingLinesCount++; } else { differingLines.push(`Line ${index1 + 1}: "${line1}"`); } }); const resultsDiv = document.getElementById('results'); resultsDiv.innerHTML = ` <p>Total lines in Box 1: ${totalLines1}</p> <p>Total lines in Box 2: ${totalLines2}</p> <p>Number of matching lines: ${matchingLinesCount}</p> <p>Lines that differ:</p> <ul>${differingLines.map(line => `<li>${line}</li>`).join('')}</ul> `; } </script> </body> </html> __________________________ mtd toot display ------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Mastodon Toot Display</title> <style> #api-url-container, #toot-container { border: 1px solid #ccc; padding: 20px; max-width: 600px; margin: 20px auto; } #api-url-container { margin-bottom: 10px; } </style> </head> <body> <div id="api-url-container">API URL: Loading...</div> <div id="toot-container">Loading toot...</div> <script> // Function to get query string parameters from the URL function getQueryParams() { const params = new URLSearchParams(window.location.search); return { tootUrl: params.get('url'), accessToken: params.get('apikey') }; } // Function to extract toot ID and domain from the toot URL function extractTootIdAndDomain(tootUrl) { const url = new URL(tootUrl); const pathParts = url.pathname.split('/'); const tootId = pathParts[pathParts.length - 1]; return { domain: url.origin, tootId: tootId }; } // Function to fetch toot data from Mastodon API async function fetchToot(tootUrl, accessToken) { const { domain, tootId } = extractTootIdAndDomain(tootUrl); const apiUrl = `${domain}/api/v1/statuses/${tootId}`; // Display API URL in the DOM document.getElementById('api-url-container').innerText = `API URL: ${apiUrl}`; try { const response = await fetch(apiUrl, { headers: { 'Authorization': `Bearer ${accessToken}` } }); if (!response.ok) { throw new Error('Network response was not ok'); } const tootData = await response.json(); return tootData; } catch (error) { console.error('Fetch error:', error); return null; } } // Function to display toot data function displayToot(tootData) { const container = document.getElementById('toot-container'); if (tootData) { container.innerHTML = ` <h3>${tootData.account.display_name} (@${tootData.account.username})</h3> <p>${tootData.content}</p> <small>${new Date(tootData.created_at).toLocaleString()}</small> `; } else { container.innerHTML = 'Failed to load toot.'; } } // Main function to execute the script async function main() { const { tootUrl, accessToken } = getQueryParams(); if (tootUrl && accessToken) { const tootData = await fetchToot(tootUrl, accessToken); displayToot(tootData); } else { document.getElementById('toot-container').innerHTML = 'Invalid toot URL or access token.'; } } // Run the main function on page load main(); </script> </body> </html> __________________ Mastodon API Poster (with emoji support and url gen on post) ---------------------------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Mastodon Post Privacy with Emoji Picker, Search, and URL Display</title> <style> body { font-family: Arial, sans-serif; margin: 20px; display: flex; } .container { max-width: 600px; margin-left: 20px; } label { display: block; margin-top: 10px; } input[type="text"], textarea, input[type="button"], select { padding: 10px; font-size: 16px; margin-bottom: 10px; display: block; width: calc(100% - 22px); /* Full width minus padding and border */ } textarea { height: 100px; resize: vertical; } input[type="button"] { background-color: #007bff; color: white; border: none; cursor: pointer; } input[type="button"]:hover { background-color: #0056b3; } .status { margin-top: 20px; font-weight: bold; } .emoji-picker { width: 200px; margin-right: 20px; display: flex; flex-direction: column; } .emoji-picker input { margin-bottom: 10px; padding: 5px; } .emoji-picker button { background: none; border: none; font-size: 24px; cursor: pointer; padding: 5px; margin: 2px; } .emoji-picker button:hover { background-color: #f0f0f0; } .emoji-item { display: inline-block; } .post-url { margin-top: 20px; } </style> </head> <body> <div class="emoji-picker" id="emoji-picker"> <h3>Emoji Picker</h3> <input type="text" id="emoji-search" placeholder="Search emojis..." oninput="filterEmojis()" /> <!-- Emoji buttons will be inserted here dynamically --> </div> <div class="container"> <h1>Create Mastodon Post</h1> <p>Enter your Mastodon instance URL and access token, then write your post and select the visibility.</p> <label for="instance-url">Mastodon Instance URL:</label> <input type="text" id="instance-url" placeholder="https://mastodon.instance" /> <label for="access-token">Access Token:</label> <input type="text" id="access-token" placeholder="Your Access Token" /> <label for="post-content">Post Content:</label> <textarea id="post-content" placeholder="Write your post here"></textarea> <label for="post-visibility">Post Visibility:</label> <select id="post-visibility"> <option value="public">Public</option> <option value="unlisted">Unlisted</option> <option value="private">Private</option> <option value="direct">Direct</option> </select> <input type="button" value="Create Post" onclick="createPost()" /> <div class="status" id="status"></div> <div class="post-url" id="post-url"></div> </div> <script> let emojis = []; // Function to parse query parameters function getQueryParams() { const params = new URLSearchParams(window.location.search); return { url: params.get('url') || '', apikey: params.get('apikey') || '' }; } // Pre-fill fields based on query parameters function prefillFields() { const queryParams = getQueryParams(); document.getElementById('instance-url').value = queryParams.url; document.getElementById('access-token').value = queryParams.apikey; if (queryParams.url && queryParams.apikey) { loadEmojis(queryParams.url, queryParams.apikey); } } // Function to load emojis from Mastodon instance async function loadEmojis(instanceUrl, accessToken) { try { const response = await fetch(`${instanceUrl}/api/v1/custom_emojis`, { headers: { 'Authorization': `Bearer ${accessToken}` } }); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status} - ${response.statusText}`); } emojis = await response.json(); displayEmojis(emojis); } catch (error) { console.error('Error loading emojis:', error); document.getElementById('status').textContent = 'Error loading emojis. Check console for details.'; } } // Function to display emojis function displayEmojis(emojiList) { const emojiPicker = document.getElementById('emoji-picker'); const emojiContainer = document.getElementById('emoji-container') || document.createElement('div'); emojiContainer.id = 'emoji-container'; emojiContainer.innerHTML = ''; // Clear previous emojis emojiList.forEach(emoji => { const button = document.createElement('button'); button.innerHTML = emoji.shortcode ? emoji.shortcode.replace(/:/g, '') : emoji.url; // Display shortcode or URL button.title = emoji.shortcode || 'Emoji'; // Tooltip button.className = 'emoji-item'; button.onclick = () => insertEmoji(emoji.shortcode); emojiContainer.appendChild(button); }); if (!document.getElementById('emoji-container')) { emojiPicker.appendChild(emojiContainer); } } // Function to filter emojis based on search input function filterEmojis() { const searchQuery = document.getElementById('emoji-search').value.toLowerCase(); const filteredEmojis = emojis.filter(emoji => emoji.shortcode.toLowerCase().includes(searchQuery)); displayEmojis(filteredEmojis); } // Function to insert emoji into the post content function insertEmoji(emoji) { const postContentArea = document.getElementById('post-content'); postContentArea.value += `:${emoji}:`; // Format emoji shortcode for Mastodon postContentArea.focus(); // Optional: Focus the textarea after inserting } // Function to create a post async function createPost() { const mastodonInstance = document.getElementById('instance-url').value.trim(); const accessToken = document.getElementById('access-token').value.trim(); const postContent = document.getElementById('post-content').value.trim(); const postVisibility = document.getElementById('post-visibility').value; const apiEndpoint = `${mastodonInstance}/api/v1/statuses`; const data = { status: postContent, visibility: postVisibility }; // Basic validation if (!mastodonInstance || !accessToken || !postContent) { document.getElementById('status').textContent = 'Please provide all required fields: Mastodon instance URL, access token, and post content.'; return; } try { const response = await fetch(apiEndpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); // Log response for debugging console.log('Response status:', response.status); console.log('Response headers:', response.headers); const result = await response.json(); console.log('Response data:', result); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status} - ${response.statusText}`); } // Display post URL const postUrl = result.url; // Assuming `url` is returned in the response document.getElementById('status').textContent = 'Post created successfully!'; document.getElementById('post-url').innerHTML = `View your post <a href=${postUrl} target=_blank>${postUrl}</a> <a href=https://alceawis.de/mtdtootdisplay.html?url=${postUrl}&apikey=${accessToken} target=_blank>»</a>`; } catch (error) { document.getElementById('status').textContent = 'Error creating post. Check console for details.'; console.error('Error:', error); } } // Call prefillFields on page load window.onload = prefillFields; </script> </body> </html> _______________________________ Img-to-base64-html-embed ----------------------------- <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Image Upload and Download as HTML</title> </head> <body> <h1>Upload an Image and embed into *.html</h1> <input type="file" id="imageInput" accept="image/*"> <br><br> <label for="linkInput">Link (optional):</label> <input type="url" id="linkInput" placeholder="https://example.com"> <br><br> <button id="downloadBtn">Download HTML</button> <script> document.getElementById('downloadBtn').addEventListener('click', function () { const fileInput = document.getElementById('imageInput'); const linkInput = document.getElementById('linkInput'); const file = fileInput.files[0]; const link = linkInput.value; if (!file) { alert('Please upload an image first.'); return; } const reader = new FileReader(); reader.onloadend = function () { const base64String = reader.result; let htmlContent = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Image</title> </head> <body> `; if (link) { htmlContent += `<a href="${link}" target="_blank">`; } htmlContent += `<img src="${base64String}" style="width: 100%;">`; if (link) { htmlContent += `</a>`; } htmlContent += ` </body> </html> `; const blob = new Blob([htmlContent], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'image.html'; a.click(); URL.revokeObjectURL(url); }; reader.readAsDataURL(file); }); </script> </body> </html> _____________________ All images drawn chart (requires same server or cors) ----------------------------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Date Chart</title> <style> #chart-container { position: relative; width: 80%; margin: auto; height: 500px; /* Adjust as needed */ } #myChart { position: absolute; top: 0; left: 0; width: 100%; height: 100%; /* Ensures the canvas covers the entire container */ } #imageBackground { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-size: contain; /* Ensure the image covers the container */ background-position: center; /* Center the image */ background-repeat: no-repeat; /* Avoid repeating the image */ opacity: 0.3; pointer-events: none; /* Ensure clicks go through to the canvas */ } </style> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head> <body> <button onclick="switchView('week')">Weekly View</button> <button onclick="switchView('month')">Monthly View</button> <div id="chart-container"> <div id="imageBackground"></div> <canvas id="myChart"></canvas> <div id="imageLink">...</div> </div> <script> let urlMap = new Map(); // Map to store URL mappings let dates = []; // Array to store dates for chart async function fetchData() { const response = await fetch('https://alcea-wisteria.de/artbackup/'); const html = await response.text(); return html; } function parseDOM(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const rows = doc.querySelectorAll('tr'); dates = []; urlMap.clear(); rows.forEach(row => { const dateCell = row.querySelector('td[data-sort]'); const link = row.querySelector('a[href]'); if (dateCell && link) { const dateText = dateCell.getAttribute('data-sort'); const url = link.getAttribute('href'); const dateMatch = /(\d{4}-\d{2}-\d{2})/.exec(dateText); if (dateMatch) { const date = dateMatch[1]; dates.push(date); const fullUrl = `https://alcea-wisteria.de${url}`; if (!urlMap.has(date)) { urlMap.set(date, []); } urlMap.get(date).push(fullUrl); console.log('Extracted Date:', date); console.log('Extracted URL:', fullUrl); } } }); } function getWeekYear(date) { const d = new Date(date); const year = d.getFullYear(); const start = new Date(year, 0, 1); const diff = d - start + ((start.getTimezoneOffset() - d.getTimezoneOffset()) * 60000); const oneDay = 86400000; const day = Math.floor(diff / oneDay); const week = Math.ceil((day + 1) / 7); return `${year}-W${week < 10 ? '0' : ''}${week}`; } function mapWeekToMonth(weekNumber) { if (weekNumber >= 1 && weekNumber <= 4) return '01'; // Month 01 if (weekNumber >= 5 && weekNumber <= 6) return '02'; // Month 02 if (weekNumber >= 7 && weekNumber <= 10) return '03'; // Month 03 if (weekNumber >= 11 && weekNumber <= 14) return '04'; // Month 04 if (weekNumber >= 15 && weekNumber <= 18) return '05'; // Month 05 if (weekNumber >= 19 && weekNumber <= 22) return '06'; // Month 06 if (weekNumber >= 23 && weekNumber <= 26) return '07'; // Month 07 if (weekNumber >= 27 && weekNumber <= 30) return '08'; // Month 08 if (weekNumber >= 31 && weekNumber <= 34) return '09'; // Month 09 if (weekNumber >= 35 && weekNumber <= 38) return '10'; // Month 10 if (weekNumber >= 39 && weekNumber <= 42) return '11'; // Month 11 if (weekNumber >= 43 && weekNumber <= 52) return '12'; // Month 12 return ''; // Default case if weekNumber is out of expected range } function getCounts(dates, type) { const counts = {}; dates.forEach(date => { let key; if (type === 'week') { key = getWeekYear(date); } else if (type === 'month') { key = date.slice(0, 7); // Extract YYYY-MM } counts[key] = (counts[key] || 0) + 1; }); const sortedKeys = Object.keys(counts).sort(); const labels = sortedKeys; const data = sortedKeys.map(key => counts[key]); return { labels, data }; } function findImageUrlForWeek(weekLabel) { const [year, week] = weekLabel.split('-W'); const weekNumber = parseInt(week, 10); const month = mapWeekToMonth(weekNumber); // Use mapWeekToMonth function const monthLabel = `${year}-${month}`; console.log('Calculated Month from Week:', monthLabel); return findImageUrlForMonth(monthLabel); } function findImageUrlForMonth(monthLabel) { const urls = []; dates.forEach(date => { if (date.startsWith(monthLabel)) { const dateUrls = getImageUrlsForDate(date); if (dateUrls.length > 0) { urls.push(...dateUrls); } } }); return urls.length > 0 ? urls[Math.floor(Math.random() * urls.length)] : ''; } function getImageUrlsForDate(date) { return urlMap.get(date) || []; } async function init() { const html = await fetchData(); parseDOM(html); const weekCounts = getCounts(dates, 'week'); const monthCounts = getCounts(dates, 'month'); const newestDate = dates.reduce((max, date) => (date > max ? date : max), dates[0]); const ctx = document.getElementById('myChart').getContext('2d'); const myChart = new Chart(ctx, { type: 'line', data: { labels: weekCounts.labels, datasets: [{ label: `Counts Per Week (Latest: ${newestDate})`, data: weekCounts.data, backgroundColor: 'rgba(75, 192, 192, 0.2)', borderColor: 'rgba(75, 192, 192, 1)', borderWidth: 1 }] }, options: { scales: { y: { beginAtZero: true, stepSize: 1 } }, plugins: { tooltip: { callbacks: { label: function (context) { const label = context.dataset.label || ''; const value = context.parsed.y; return `Value: ${value}`; } } } } } }); window.switchView = function (view) { if (view === 'week') { myChart.data.labels = weekCounts.labels; myChart.data.datasets[0].data = weekCounts.data; myChart.data.datasets[0].label = `Counts Per Week (Latest: ${newestDate})`; } else if (view === 'month') { myChart.data.labels = monthCounts.labels; myChart.data.datasets[0].data = monthCounts.data; myChart.data.datasets[0].label = `Counts Per Month (Latest: ${newestDate})`; } myChart.update(); }; myChart.canvas.addEventListener('click', function(event) { const points = myChart.getElementsAtEventForMode(event, 'nearest', { intersect: true }, true); if (points.length) { const firstPoint = points[0]; const label = myChart.data.labels[firstPoint.index]; let imageUrl = ''; if (label.includes('-W')) { imageUrl = findImageUrlForWeek(label); } else if (label.includes('-')) { if (label.length === 7) { // Monthly view (YYYY-MM) const urls = findImageUrlForMonth(label); imageUrl = findImageUrlForMonth(label); } else { imageUrl = getImageUrlsForDate(label)[0]; } } console.log('Clicked Date:', label); console.log('Image URL:', imageUrl); if (imageUrl) { document.getElementById('imageBackground').style.backgroundImage = `url(${imageUrl})`; document.getElementById('imageLink').outerHTML = `<a id="imageLink" href="${imageUrl}" target="_blank">${imageUrl.substring(imageUrl.lastIndexOf('/') + 1)}</a>`; } else { document.getElementById('imageBackground').style.backgroundImage = ''; } } }); } init(); </script> </body> </html> <details><summary>php version</summary><plaintext> <?php $url = "https://alcea-wisteria.de/artbackup/"; $html = file_get_contents($url); //$pattern = '/<tr>(.*?)<\/tr>/s'; //timestamp based $pattern = '/<td data-sort="([^"]*)"/'; //filename based preg_match_all($pattern, $html, $matches); $dates = array(); $urls = array(); foreach ($matches[0] as $match) { preg_match('/\d{4}-\d{2}-\d{2}/', $match, $date); if (!empty($date)) { $dates[] = $date[0]; $urls[] = $date[0]; // Append the date to the URL } } $newestIndex = array_search(max($dates), $dates); $newestUrl = $urls[$newestIndex]; // Weekly counts $weekCounts = array(); foreach ($dates as $date) { $weekYear = date('o-\WW', strtotime($date)); // Extract the year and week number if (isset($weekCounts[$weekYear])) { $weekCounts[$weekYear]++; } else { $weekCounts[$weekYear] = 1; } } $startDate = min(array_keys($weekCounts)); $endDate = date('o-\WW'); // Get the current year-week $currentDate = $startDate; while ($currentDate <= $endDate) { if (!isset($weekCounts[$currentDate])) { $weekCounts[$currentDate] = 0; } $currentDate = date('o-\WW', strtotime($currentDate . ' +1 week')); } ksort($weekCounts); $weekLabels = array_keys($weekCounts); $weekData = array_values($weekCounts); // Monthly counts $monthCounts = array(); foreach ($dates as $date) { $monthYear = date('Y-m', strtotime($date)); // Extract the year and month if (isset($monthCounts[$monthYear])) { $monthCounts[$monthYear]++; } else { $monthCounts[$monthYear] = 1; } } $startDate = min(array_keys($monthCounts)); $endDate = date('Y-m'); // Get the current year-month $currentDate = $startDate; while ($currentDate <= $endDate) { if (!isset($monthCounts[$currentDate])) { $monthCounts[$currentDate] = 0; } $currentDate = date('Y-m', strtotime($currentDate . ' +1 month')); } ksort($monthCounts); $monthLabels = array_keys($monthCounts); $monthData = array_values($monthCounts); ?> <!DOCTYPE html> <html> <head> <title>Date Chart</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head> <body> <button onclick="switchView('week')">Weekly View</button> <button onclick="switchView('month')">Monthly View</button> <canvas id="myChart"></canvas> <script> var weekLabels = <?php echo json_encode($weekLabels); ?>; var weekData = <?php echo json_encode($weekData); ?>; var monthLabels = <?php echo json_encode($monthLabels); ?>; var monthData = <?php echo json_encode($monthData); ?>; var ctx = document.getElementById('myChart').getContext('2d'); var myChart = new Chart(ctx, { type: 'line', data: { labels: weekLabels, datasets: [{ label: 'Counts Per Week (Latest: <?php echo $newestUrl; ?>)', data: weekData, backgroundColor: 'rgba(75, 192, 192, 0.2)', borderColor: 'rgba(75, 192, 192, 1)', borderWidth: 1 }] }, options: { scales: { y: { beginAtZero: true, stepSize: 1 } }, plugins: { tooltip: { callbacks: { label: function(context) { var label = context.dataset.label || ''; var value = context.parsed.y; var tooltipLabel = 'Value: ' + value; // Customize the tooltip label here return tooltipLabel; } } } } } }); function switchView(view) { if (view === 'week') { myChart.data.labels = weekLabels; myChart.data.datasets[0].data = weekData; myChart.data.datasets[0].label = 'Counts Per Week (Latest: <?php echo $newestUrl; ?>)'; } else if (view === 'month') { myChart.data.labels = monthLabels; myChart.data.datasets[0].data = monthData; myChart.data.datasets[0].label = 'Counts Per Month (Latest: <?php echo $newestUrl; ?>)'; } myChart.update(); } </script> </body> </html> ______________________________ Unit 8 Array convert (from javascript) -------------------------------------- To convert unit8 and arrays of such nature from javascript console back into human readable form:<br> <!DOCTYPE html> <html> <head> <title>Unit8 Array Converter</title> <style> body { font-family: Arial, sans-serif; text-align: center; padding: 20px; } textarea { padding: 10px; font-size: 16px; width: 400px; height: 150px; } button { padding: 10px 20px; font-size: 16px; background-color: #4CAF50; color: white; border: none; cursor: pointer; } #output { margin-top: 20px; font-size: 18px; white-space: pre-wrap; } </style<!DOCTYPE html> <html> <head> <title>Unit8 Array Converter</title> <style> /* Same CSS as before */ </style> </head> <body> <h1>Unit8 Array Converter</h1> <textarea id="input-text" placeholder="Enter a unit8 array in one of the following formats: 1. Array of numbers: [60, 33, 68, 79, ...] 2. Typed Array: new Uint8Array([60, 33, 68, 79, ...]) 3. ArrayBuffer: new ArrayBuffer(16) with individual values set 4. DataView: new DataView(new ArrayBuffer(16)) with individual values set 5. JavaScript object with numeric keys: { '0': 60, '1': 33, '2': 68, '3': 79, ... }"></textarea> <button onclick="convertArray()">Convert</button> <div id="output"></div> <script> function convertArray() { const inputText = document.getElementById("input-text").value.trim(); try { let unit8Array; // Try parsing as a regular array of numbers try { unit8Array = JSON.parse(inputText); if (Array.isArray(unit8Array) && unit8Array.every(Number.isInteger) && unit8Array.every(value => value >= 0 && value <= 255)) { unit8Array = new Uint8Array(unit8Array); } else { unit8Array = null; } } catch (error) { unit8Array = null; } // Try parsing as a Uint8Array if (!unit8Array) { try { if (inputText.startsWith('new Uint8Array')) { unit8Array = eval(inputText); } else { unit8Array = null; } } catch (error) { unit8Array = null; } } // Try parsing as an ArrayBuffer if (!unit8Array) { try { if (inputText.startsWith('new ArrayBuffer')) { const buffer = eval(inputText); if (buffer instanceof ArrayBuffer) { unit8Array = new Uint8Array(buffer); } else { unit8Array = null; } } else { unit8Array = null; } } catch (error) { unit8Array = null; } } // Try parsing as a DataView if (!unit8Array) { try { if (inputText.startsWith('new DataView')) { const view = eval(inputText); if (view instanceof DataView) { const buffer = view.buffer; unit8Array = new Uint8Array(buffer); } else { unit8Array = null; } } else { unit8Array = null; } } catch (error) { unit8Array = null; } } // Try parsing as a JavaScript object with numeric keys if (!unit8Array) { try { const obj = JSON.parse(inputText); if (typeof obj === 'object' && obj !== null && Object.keys(obj).every(key => /^\d+$/.test(key) && obj[key] >= 0 && obj[key] <= 255)) { const values = Object.values(obj); unit8Array = new Uint8Array(values); } else { unit8Array = null; } } catch (error) { unit8Array = null; } } if (unit8Array) { const readableText = new TextDecoder().decode(unit8Array); document.getElementById("output").textContent = readableText; } else { document.getElementById("output").textContent = "Error: Invalid input. Please enter a valid unit8 array."; } } catch (error) { document.getElementById("output").textContent = "Error: Invalid input. Please enter a valid unit8 array."; } } </script> </body> </html> ________________________________________ Load website from Zip (Service woker approach) - https://codepen.io/ryedai1/pen/MWMWpNr (complex8) ----------------------------------------------- ==index.html== <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Intercept All Requests</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <script> async function loadZipAndCreateFileMap() { const response = await fetch('https://alcea-wisteria.de/z_files/alceawis.de.zip'); const blob = await response.blob(); const zip = await JSZip.loadAsync(blob); const fileMap = {}; const filePromises = []; zip.forEach((relativePath, zipEntry) => { const filePromise = zipEntry.async('blob').then(blob => { fileMap['/' + relativePath] = blob; }); filePromises.push(filePromise); }); await Promise.all(filePromises); return fileMap; } if ('serviceWorker' in navigator) { loadZipAndCreateFileMap().then(fileMap => { navigator.serviceWorker.register('/service-worker.js').then(registration => { console.log('Service Worker registered with scope:', registration.scope); navigator.serviceWorker.ready.then(swRegistration => { const channel = new MessageChannel(); channel.port1.onmessage = event => { if (event.data === 'SW_READY') { swRegistration.active.postMessage({ type: 'FILE_MAP', fileMap: fileMap }); fetch('index.html').then(response => response.text()).then(html => { document.body.innerHTML = html; }); } }; swRegistration.active.postMessage('INIT', [channel.port2]); }); }).catch(error => { console.log('Service Worker registration failed:', error); }); }); } </script> </head> <body> <h1>Loading...</h1> </body> </html> === service-worker.js== let fileMap = {}; self.addEventListener('message', event => { if (event.data === 'INIT') { event.ports[0].postMessage('SW_READY'); } else if (event.data.type === 'FILE_MAP') { fileMap = event.data.fileMap; } }); self.addEventListener('fetch', event => { const url = new URL(event.request.url); console.log('Intercepted request:', event.request); if (fileMap[url.pathname]) { event.respondWith( Promise.resolve(fileMap[url.pathname]).then(blob => new Response(blob)) ); } else { event.respondWith(fetch(event.request)); } }); Load website from Zip (direct) - https://codepen.io/ryedai1/pen/jOjNjVR ----------------------------------------------- <a target="_blank" href="https://codepen.io/ryedai1/pen/abMraqm" style=color:blue>Take1</a><br> https://github.com/gkjohnson/client-side-zip-server https://github.com/Touffy/client-zip <!---https://stackoverflow.com/questions/27304992/how-to-extract-a-zip-file-and-render-the-contents-client-side--> <!DOCTYPE html> <html> <head> <title>JSZip Example</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <script> // Check if JSZip is available, otherwise load local copy if (typeof JSZip === 'undefined') { document.write('<script src="jszip.min.js"><\/script>'); } document.addEventListener("DOMContentLoaded", async function () { const url = "https://alcea-wisteria.de/z_files/alceawis.de.zip"; const fileListElement = document.getElementById("file-list"); function sanitizeFileName(fileName) { return fileName.replace(/[^a-zA-Z0-9.-]/g, '_'); } async function getFileType(fileName) { const extension = fileName.split('.').pop().toLowerCase(); switch (extension) { case 'html': case 'htm': return 'text/html'; case 'css': return 'text/css'; case 'js': return 'application/javascript'; case 'json': return 'application/json'; case 'png': return 'image/png'; case 'jpg': case 'jpeg': return 'image/jpeg'; case 'gif': return 'image/gif'; case 'svg': return 'image/svg+xml'; default: return 'application/octet-stream'; } } async function processScriptContent(content, fileMap) { fileMap.forEach(({ relativePath, absolutePath }) => { const regex = new RegExp(relativePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'); content = content.replace(regex, absolutePath); }); content = content.replace(/fetch\(['"]([^'"]+)['"]\)/g, (match, p1) => { const file = fileMap.find(entry => entry.relativePath === p1); if (file) { return `fetch('${file.absolutePath}')`; } else { return match; } }); content = content.replace(/['"]([^'"]+\.(json|xml|csv|txt|png|jpg|jpeg|gif|svg))['"]/g, (match, p1) => { const file = fileMap.find(entry => entry.relativePath === p1); if (file) { return `'${file.absolutePath}'`; } else { return match; } }); return content; } async function loadHtmlContent(content, fileMap) { let modifiedContent = content; fileMap.forEach(({ relativePath, absolutePath }) => { const regex = new RegExp(relativePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'); modifiedContent = modifiedContent.replace(regex, absolutePath); }); const tempElement = document.createElement('div'); tempElement.innerHTML = modifiedContent; tempElement.querySelectorAll('iframe').forEach(iframe => { const src = iframe.getAttribute('src'); const file = fileMap.find(entry => entry.relativePath === src); if (file) { iframe.setAttribute('src', file.absolutePath); } }); tempElement.querySelectorAll('a').forEach(anchor => { const href = anchor.getAttribute('href'); if (href && !href.startsWith('http')) { const file = fileMap.find(entry => entry.relativePath === href); if (file) { anchor.setAttribute('href', file.absolutePath); anchor.setAttribute('target', '_self'); } } }); const scriptPromises = []; tempElement.querySelectorAll('script').forEach(script => { const src = script.getAttribute('src'); if (src) { const file = fileMap.find(entry => entry.relativePath === src); if (file) { const newScript = document.createElement('script'); newScript.src = file.absolutePath; newScript.type = 'application/javascript'; script.replaceWith(newScript); } } else { scriptPromises.push(new Promise(async (resolve) => { const scriptContent = await processScriptContent(script.textContent, fileMap); const newScript = document.createElement('script'); newScript.textContent = scriptContent; script.replaceWith(newScript); resolve(); })); } }); await Promise.all(scriptPromises); document.open(); document.write(tempElement.innerHTML); document.close(); } try { const response = await fetch(url); if (!response.ok) throw new Error("Failed to fetch the zip file."); const blob = await response.blob(); const zip = await JSZip.loadAsync(blob); const fileMap = []; for (const [relativePath, file] of Object.entries(zip.files)) { if (!file.dir) { const fileContent = await file.async("blob"); const fileType = await getFileType(relativePath); const objectUrl = URL.createObjectURL(new Blob([fileContent], { type: fileType })); fileMap.push({ relativePath, absolutePath: objectUrl }); } } fileListElement.textContent = fileMap.map(entry => entry.relativePath).join("\n"); const indexPath = "index.html"; const indexFile = fileMap.find(entry => entry.relativePath === indexPath); if (indexFile) { try { const response = await fetch(indexFile.absolutePath); const indexContent = await response.text(); await loadHtmlContent(indexContent, fileMap); } catch (error) { console.error(`Error loading ${indexPath} from object URL. Trying to load from zip blob.`); console.error(error); // Log individual filename refetched console.log(`Refetching ${indexFile.relativePath} from zip blob.`); const indexContent = await indexFile.blob(); const textContent = await new Response(indexContent).text(); await loadHtmlContent(textContent, fileMap); } } else { throw new Error(`${indexPath} not found in the zip file.`); } } catch (error) { fileListElement.textContent = "Error: " + error.message; } }); </script> </head> <body> <h1>JSZip Example</h1> <pre id="file-list"></pre> </body> </html> Console Log Visualizer: ------------------------- <!--https://github.com/gh-canon/stack-snippet-console-https://stackoverflow.com/questions/62422123/display-browser-console-in-html-page--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Console Output Display</title> <style> .console-output { position: fixed; bottom: 0; left: 0; width: 100%; max-height: 55%; overflow-y: auto; background-color: #333; color: #fff; padding: 10px; font-family: monospace; font-size: 14px; white-space: pre-wrap; /* Allows text to wrap and show entire log entry */ } .log { color: #fff; } .error { color: #f44336; } .warn { color: #ff9800; } .info { color: #2196f3; } .debug { color: #9e9e9e; } </style> </head> <body> <script> function dumpConsoleToDOM() { const consoleMethods = ['log', 'error', 'warn', 'info', 'debug']; consoleMethods.forEach(method => { const originalMethod = console[method]; console[method] = function(...args) { originalMethod.apply(console, args); const consoleOutput = Array.from(args || []) .map(item => { if (typeof item === 'object') { return `<div class="${method}">${serializeObject(item)}</div>`; } else { return `<div class="${method}">${item}</div>`; } }) .join(''); const consoleDiv = document.createElement('div'); consoleDiv.classList.add('console-output'); consoleDiv.innerHTML = consoleOutput; document.body.appendChild(consoleDiv); }; }); } function serializeObject(obj) { if (obj instanceof Error) { return `${obj.name}: ${obj.message}\n${obj.stack}`; } else if (obj instanceof ProgressEvent) { return `ProgressEvent: {\n isTrusted: ${obj.isTrusted},\n lengthComputable: ${obj.lengthComputable},\n loaded: ${obj.loaded},\n total: ${obj.total},\n type: '${obj.type}',\n target: ${obj.target ? obj.target.constructor.name : 'null'}\n}`; } else { return JSON.stringify(obj, null, 2); } } dumpConsoleToDOM(); </script> </body> </html> _____________________________________ Fetch mastodon hashtag counts ------------------------------ <meta charset="UTF-8"> <form id="searchForm"> <label for="apiUrl">Mastodon API URL:</label> <input type="text" id="apiUrl" name="apiUrl"> <br> <label for="tag1">Hashtag 1:</label> <input type="text" id="tag1" name="tag1"> <label for="tag2">Hashtag 2:</label> <input type="text" id="tag2" name="tag2"> <label for="tag3">Hashtag 3:</label> <input type="text" id="tag3" name="tag3"> <details> <label for="tag4">Hashtag 4:</label> <input type="text" id="tag4" name="tag4"> <label for="tag5">Hashtag 5:</label> <input type="text" id="tag5" name="tag5"> <label for="tag6">Hashtag 6:</label> <input type="text" id="tag6" name="tag6"> <label for="tag7">Hashtag 7:</label> <input type="text" id="tag7" name="tag7"> <label for="tag8">Hashtag 8:</label> <input type="text" id="tag8" name="tag8"> <label for="tag9">Hashtag 9:</label> <input type="text" id="tag9" name="tag9"> <label for="tag10">Hashtag 10:</label> <input type="text" id="tag10" name="tag10"> </details> <button type="submit">Count Statuses</button> </form> <div id="statusCounts"></div> <div id="errorMessage" style="color: red;"></div> <script> // Get the current URL const url = new URL(window.location.href); // Get the query parameters const apiUrl = url.searchParams.get('instance') || 'https://pb.todon.de/api/v1/timelines/tag/'; const tag1 = url.searchParams.get('hashtag01') || 'CurrListeningAlcea'; const tag2 = url.searchParams.get('hashtag02') || 'CodeAlcea'; const tag3 = url.searchParams.get('hashtag03') || 'DoodlesAlcea'; const tag4 = url.searchParams.get('hashtag04') || ''; const tag5 = url.searchParams.get('hashtag05') || ''; const tag6 = url.searchParams.get('hashtag06') || ''; const tag7 = url.searchParams.get('hashtag07') || ''; const tag8 = url.searchParams.get('hashtag08') || ''; const tag9 = url.searchParams.get('hashtag09') || ''; const tag10 = url.searchParams.get('hashtag10') || ''; // Prefill the form fields document.getElementById('apiUrl').value = apiUrl; document.getElementById('tag1').value = tag1; document.getElementById('tag2').value = tag2; document.getElementById('tag3').value = tag3; document.getElementById('tag4').value = tag4; document.getElementById('tag5').value = tag5; document.getElementById('tag6').value = tag6; document.getElementById('tag7').value = tag7; document.getElementById('tag8').value = tag8; document.getElementById('tag9').value = tag9; document.getElementById('tag10').value = tag10; const searchForm = document.getElementById('searchForm'); const statusCountsElement = document.getElementById('statusCounts'); const errorMessageElement = document.getElementById('errorMessage'); searchForm.addEventListener('submit', (event) => { event.preventDefault(); statusCountsElement.innerHTML = ''; errorMessageElement.textContent = ''; // Get the values of the filled-in hashtag fields const hashtags = [ document.getElementById('tag1').value, document.getElementById('tag2').value, document.getElementById('tag3').value, document.getElementById('tag4').value, document.getElementById('tag5').value, document.getElementById('tag6').value, document.getElementById('tag7').value, document.getElementById('tag8').value, document.getElementById('tag9').value, document.getElementById('tag10').value ].filter(tag => tag.trim() !== ''); // Process the filled-in hashtags hashtags.forEach(tag => countStatuses(apiUrl, tag, tag)); }); function countStatuses(apiUrl, tag, tagName) { const fullUrl = `${apiUrl}${tag}`; let totalCount = 0; let nextLink = null; function extractLinkUrl(linkHeader, rel) { if (!linkHeader) return null; let links = linkHeader.split(','); for (let i = 0; i < links.length; i++) { let link = links[i].trim(); if (link.indexOf(`rel="${rel}"`) !== -1) { let url = link.substring(link.indexOf('<') + 1, link.indexOf('>')); return url; } } return null; } function fetchEntities() { let url = nextLink || fullUrl; return fetch(url, { headers: { 'Authorization': 'Bearer token' } }) .then(response => { if (response.ok) { return response.json().then(data => { totalCount += data.length; let statusCountElement = document.getElementById(`status-count-${tagName}`); if (!statusCountElement) { statusCountElement = document.createElement('div'); statusCountElement.id = `status-count-${tagName}`; statusCountsElement.appendChild(statusCountElement); } statusCountElement.textContent = `*${tagName}: ${totalCount}`; let linkHeader = response.headers.get('Link'); nextLink = extractLinkUrl(linkHeader, 'next'); if (nextLink) { return fetchEntities(); } else { console.log(`Total status count for ${tag}: ${totalCount}`); } }); } else { console.error(`Error fetching data: ${response.status}`); errorMessageElement.textContent = `Error fetching data for hashtag "${tag}": ${response.status}`; throw new Error(`Error fetching data: ${response.status}`); } }) .catch(error => { console.error('Error fetching data:', error); errorMessageElement.textContent = `Error fetching data for hashtag "${tag}": ${error.message}`; }); } fetchEntities(); } </script> </body> </html> _______________________ WeatherInfo ------------------- <meta charset="utf-8"> <div id="weather-info">Loading weather information...</div> <div id="error-message" style="color: red;"></div> <script> const urlParams = new URLSearchParams(window.location.search); const latitude = urlParams.has('lat') ? parseFloat(urlParams.get('lat')) : 48.3667; const longitude = urlParams.has('long') ? parseFloat(urlParams.get('long')) : 10.9; const country = urlParams.has('country') ? urlParams.get('country') : 'Germany'; const weatherDescriptionLookup = [ 'Clear sky', 'Partly cloudy', 'Partly cloudy', 'Partly cloudy', 'Fog', 'Fog', 'Fog', 'Drizzle', 'Rain', 'Rain', 'Thunderstorm', 'Thunderstorm', 'Snow', 'Snow', 'Snow', 'Snow', 'Snow', 'Sleet', 'Dust', 'Dust', 'Dust', 'Dust', 'Strong wind', 'Strong wind', 'Unknown' ]; const openMeteoUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true&timezone=Europe%2FBerlin`; const geoapifyUrl = `https://api.geoapify.com/v1/geocode/reverse?lat=${latitude}&lon=${longitude}&format=json&limit=1`; Promise.all([ fetch(geoapifyUrl).then(response => response.json()), fetch(openMeteoUrl).then(response => response.json()) ]) .then(([geoapifyData, openMeteoData]) => { const cityName = geoapifyData.results?.[0]?.city || 'Unknown'; const weatherCode = openMeteoData.current_weather.weathercode; const weatherDescriptionText = weatherDescriptionLookup[weatherCode] || 'Unknown'; const weatherInfo = document.getElementById('weather-info'); weatherInfo.textContent = `${country}: • ${openMeteoData.current_weather.temperature}°C •${weatherDescriptionText}`; }) .catch(error => { console.error('Error:', error); document.getElementById('error-message').textContent = 'Error retrieving weather information: ' + error.message; }); </script> __________________________ Mastodon-userpost-percentage-instance ------------------------------------ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Mastodon Account Statuses</title> </head> <body> <h1>Mastodon Account Statuses</h1> <div class="info-line"> <div id="userName"></div> <div id="userStatuses"></div> </div> <div id="instanceStatuses"></div> <div id="userPercentage"></div> <script> // Get the query parameters const urlParams = new URLSearchParams(window.location.search); const instanceUrl = urlParams.get('instanceUrl') || 'https://mastodon.animexx.de'; const userId = urlParams.get('userId') || '111676830721936824'; // Fetch user account information fetch(`${instanceUrl}/api/v1/accounts/${userId}`) .then(response => response.json()) .then(userData => { const {acct} = userData; const userNameDiv = document.getElementById('userName'); userNameDiv.textContent = `Username: @${acct}`; // Display the user's status count const userStatusesDiv = document.getElementById('userStatuses'); userStatusesDiv.textContent = `The user has ${userData.statuses_count} statuses.`; // Fetch instance information return fetch(`${instanceUrl}/api/v1/instance`); }) .then(response => response.json()) .then(instanceData => { const {status_count: instanceStatusesCount} = instanceData.stats; const instanceStatusesDiv = document.getElementById('instanceStatuses'); instanceStatusesDiv.textContent = `${instanceUrl} has ${instanceStatusesCount} total statuses.`; // Calculate and display the user's percentage of instance posts const userStatusesCount = parseInt(document.getElementById('userStatuses').textContent.match(/\d+/)[0]); const userPercentage = (userStatusesCount / instanceStatusesCount * 100).toFixed(2); const userPercentageDiv = document.getElementById('userPercentage'); userPercentageDiv.textContent = `${document.getElementById('userName').textContent.split(': ')[1]} is responsible for ${userPercentage}% of the posts on this instance.`; }) .catch(error => console.error(error)); </script> </body> </html> <!------> <br><br> <form id="inputForm"> <label for="userId">User ID:</label> <input type="text" id="userId" name="userId" required> <label for="instanceUrl">Instance URL:</label> <input type="text" id="instanceUrl" name="instanceUrl" required> <button type="submit">Submit</button> </form> <script> document.getElementById('inputForm').addEventListener('submit', (event) => { event.preventDefault(); const newInstanceUrl = document.getElementById('instanceUrl').value; const newUserId = document.getElementById('userId').value; const newUrl = `?instanceUrl=${newInstanceUrl}&userId=${newUserId}`; window.location.href = window.location.pathname + newUrl; }); </script> Extract final word from json field: ------------------------------------------------- <body> <script> fetch('https://alceawis.de/other/extra/scripts/fakesocialmedia/data_alcea.json') .then(response => response.json()) .then(data => { const finalElements = data.slice(0, 15).map(obj => { const values = Object.values(obj); const lastValue = values[values.length - 1].value; return lastValue.trim().split(/\s+/).pop(); }); const container = document.createElement('div'); finalElements.forEach(element => { const elementDiv = document.createElement('div'); elementDiv.textContent = element; container.appendChild(elementDiv); }); document.body.appendChild(container); }) .catch(error => console.error(error)); </script> </body> </html> ________________________________ WebDAV Webserver Access urlgen ---------------------------- <!--https://stackoverflow.com/questions/26545126/ithit-ajax-file-browser-active-directory-webdav-auto-login--> <h1>WebDAV Server Access</h1> <form> <label for="webDavServerPath">WebDAV Server Path:</label> <input type="text" id="webDavServerPath" value="webdav.hidrive.strato.com"><br> <label for="username">Username:</label> <input type="text" id="username"><br> <label for="password">Password:</label> <input type="password" id="password"><br> <button type="button" onclick="openWebDavLogin()">Access WebDAV Server</button> </form> <script> function openWebDavLogin() { var webDavServerPath = document.getElementById("webDavServerPath").value; var username = document.getElementById("username").value; var password = document.getElementById("password").value; // Construct the login URL with the username and password //var loginUrl = `${webDavServerPath}?mode=login&username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`; var loginUrl = `${encodeURIComponent(username)}:${encodeURIComponent(password)}@${webDavServerPath}`; // Open the WebDAV login in a new tab window.open(loginUrl, '_blank'); } </script> _________ Bluesky TL render (via feednami) --------------------- <script src="https://storage.googleapis.com/feednami-static/js/feednami-client-v1.0.1.js"></script> <script> function fetchRssFeed(feedUrl) { feednami.load(feedUrl, function(result) { if (result.error) { console.error('Error fetching RSS feed:', result.error); } else { const entries = result.feed.entries; displayBlueSkyEmbeds(entries); } }); } // Function to display BlueSkya embeds function displayBlueSkyEmbeds(feedItems) { feedItems.forEach(item => { const link = item.link; const lastSlashPos = link.lastIndexOf('/'); const postId = link.substring(lastSlashPos + 1); let guidValue = item.guid; let didPrefix = 'did:plc:'; if (!guidValue.startsWith('http')) { const startPos = guidValue.indexOf('plc:') + 4; const endPos = guidValue.indexOf('/app.bsky'); const guidPart = guidValue.substring(startPos, endPos); guidValue = `at://${didPrefix}${guidPart}/app.bsky.feed.post/${postId}`; } const embed = document.createElement('blockquote'); embed.classList.add('bluesky-embed'); embed.setAttribute('data-bluesky-uri', guidValue); embed.setAttribute('data-bluesky-cid', ''); document.body.appendChild(embed); }); // Load the BlueSkya embed script const embedScript = document.createElement('script'); embedScript.async = true; embedScript.src = 'https://embed.bsky.app/static/embed.js'; embedScript.charset = 'utf-8'; document.body.appendChild(embedScript); } // Example usage const feedUrl = new URL(window.location.href).searchParams.get('bskyurl') || 'https://bsky.app/profile/did:plc:nc45m3kcywjhyr4tgsbmea64/rss'; fetchRssFeed(feedUrl); </script> _____________________ Hiragana & Katakana to Romaji ----------------------------- <a target="_blank" href="https://j-talk.com/convert" style=color:blue>alt</a><br> <meta charset="utf-8"> <!DOCTYPE html> <html> <head> <title>Hiragana and Katakana to Romaji </title> </head> <body> <h1>Hiragana and Katakana to Romaji Converter</h1> Enter Hiraga or Kataka and it'll output romaji !<br> <input type="text" id="inputText" placeholder="Enter Hiragana or Katakana" oninput="convertToRomaji()"> <p>Romaji: <span id="romaji"></span></p> <script> function hiraganaKatakanaToRomaji(text) { const hiraganaToRomaji = { 'あ': 'a', 'い': 'i', 'う': 'u', 'え': 'e', 'お': 'o', 'か': 'ka', 'き': 'ki', 'く': 'ku', 'け': 'ke', 'こ': 'ko', 'さ': 'sa', 'し': 'shi', 'す': 'su', 'せ': 'se', 'そ': 'so', 'た': 'ta', 'ち': 'chi', 'つ': 'tsu', 'て': 'te', 'と': 'to', 'な': 'na', 'に': 'ni', 'ぬ': 'nu', 'ね': 'ne', 'の': 'no', 'は': 'ha', 'ひ': 'hi', 'ふ': 'fu', 'へ': 'he', 'ほ': 'ho', 'ま': 'ma', 'み': 'mi', 'む': 'mu', 'め': 'me', 'も': 'mo', 'や': 'ya', 'ゆ': 'yu', 'よ': 'yo', 'ら': 'ra', 'り': 'ri', 'る': 'ru', 'れ': 're', 'ろ': 'ro', 'わ': 'wa', 'を': 'wo', 'ん': 'n' }; const katakanaToRomaji = { 'ア': 'a', 'イ': 'i', 'ウ': 'u', 'エ': 'e', 'オ': 'o', 'カ': 'ka', 'キ': 'ki', 'ク': 'ku', 'ケ': 'ke', 'コ': 'ko', 'サ': 'sa', 'シ': 'shi', 'ス': 'su', 'セ': 'se', 'ソ': 'so', 'タ': 'ta', 'チ': 'chi', 'ツ': 'tsu', 'テ': 'te', 'ト': 'to', 'ナ': 'na', 'ニ': 'ni', 'ヌ': 'nu', 'ネ': 'ne', 'ノ': 'no', 'ハ': 'ha', 'ヒ': 'hi', 'フ': 'fu', 'ヘ': 'he', 'ホ': 'ho', 'マ': 'ma', 'ミ': 'mi', 'ム': 'mu', 'メ': 'me', 'モ': 'mo', 'ヤ': 'ya', 'ユ': 'yu', 'ヨ': 'yo', 'ラ': 'ra', 'リ': 'ri', 'ル': 'ru', 'レ': 're', 'ロ': 'ro', 'ワ': 'wa', 'ヲ': 'wo', 'ン': 'n' }; let romaji = ''; for (let i = 0; i < text.length; i++) { const char = text[i]; romaji += hiraganaToRomaji[char] || katakanaToRomaji[char] || char; } return romaji; } function convertToRomaji() { const inputText = document.getElementById('inputText').value; const romaji = hiraganaKatakanaToRomaji(inputText); document.getElementById('romaji').textContent = romaji; } </script> </body> </html> _______________________ Volumina in Box Calculator ------------------------- Bin Space: 74.400.000 mm³<br><hr> <script> // Function to calculate the volume of a bin function calculateBinVolume(length, width, height) { return length * width * height; } // Function to calculate the volume of an item function calculateItemVolume(length, width, height) { return length * width * height; } // Function to check if the items fit in the bin function checkItemsFitInBin() { // Get the bin dimensions const binLength = parseInt(document.getElementById('bin-length').value); const binWidth = parseInt(document.getElementById('bin-width').value); const binHeight = parseInt(document.getElementById('bin-height').value); // Get the item dimensions const itemLength = parseInt(document.getElementById('item-length').value); const itemWidth = parseInt(document.getElementById('item-width').value); const itemHeight = parseInt(document.getElementById('item-height').value); // Get the number of items const itemCount = parseInt(document.getElementById('item-count').value); // Check if the item dimensions exceed the bin height //if (itemHeight > binHeight) { if (itemHeight > binHeight || itemLength > binLength || itemWidth > binWidth) { // Display error message const resultElement = document.getElementById('result'); resultElement.innerHTML = "Item height exceeds bin height. Items do not fit in bin."; return; } // Calculate the volume of the bin const binVolume = calculateBinVolume(binLength, binWidth, binHeight); // Calculate the total volume of all items const totalItemVolume = calculateItemVolume(itemLength, itemWidth, itemHeight) * itemCount; // Calculate the remaining space in the bin const remainingSpace = binVolume - totalItemVolume; // Check if the items fit in the bin if (remainingSpace >= 0) { // Calculate the percentage of space used by the items const spaceUsedPercentage = (totalItemVolume / binVolume) * 100; // Display the result const resultElement = document.getElementById('result'); resultElement.innerHTML = "Items fit in bin.<br>" + "Total space occupied by items: " + totalItemVolume + "<br>" + "Remaining space in the bin: " + remainingSpace + "<br>" + "Space used by items in percentage: " + spaceUsedPercentage + "%"; } else { // Display the result const resultElement = document.getElementById('result'); resultElement.innerHTML = "Items do not fit in bin."; } } </script> </head> <body> <label for="bin-length">Bin Length:</label> <input type="number" id="bin-length" value="600"> <br> <label for="bin-width">Bin Width:</label> <input type="number" id="bin-width" value="400"> <br> <label for="bin-height">Bin Height:</label> <input type="number" id="bin-height" value="310"> <br> <label for="item-length">Item Length:</label> <input type="number" id="item-length"> <br> <label for="item-width">Item Width:</label> <input type="number" id="item-width"> <br> <label for="item-height">Item Height:</label> <input type="number" id="item-height"> <br> <label for="item-count">Number of Items:</label> <input type="number" id="item-count"> <br> <button onclick="checkItemsFitInBin()">Check</button> <p id="result"></p> </body> </html> __________________ Mail obfuscation (base64img) ------------------------- <script> function generateEmailImages() { var email = 'alceawisteria@proton.mailll'; var element = document.getElementById('email'); for (var i = 0; i < email.length; i++) { var letter = email.charAt(i); var imageElement = document.createElement('img'); imageElement.src = generateLetterImageBase64(letter); element.appendChild(imageElement);}} function generateLetterImageBase64(letter) { var canvas = document.createElement('canvas'); canvas.width = 24; canvas.height = 50; var ctx = canvas.getContext('2d'); ctx.fillStyle = '#000'; ctx.font = 'bold 15px Arial'; ctx.fillText(letter, 10, 30); return canvas.toDataURL(); } </script> <body onload="generateEmailImages()"> <p><span id="email"></span></p> </body ____________________ Wheel of Fortune (What do I draw today) -------------------- <style> #wheelOfFortune { display: inline-flex; position: relative; overflow: hidden; } #wheel {display: block;} #spin { font: 1.0rem/0 sans-serif; user-select: none; cursor: pointer; display: flex; justify-content: center; align-items: center; position: absolute; top: 50%; left: 50%; width: 30%; height: 30%; margin: -15%; background: #000; color: #fff; box-shadow: 0 0 0 8px currentColor, 0 0px 15px 5px rgba(0, 0, 0, 0.6); border-radius: 50%; transition: 0.8s;} #spin::after { content: ""; position: absolute; top: -17px; border: 10px solid transparent; border-bottom-color: currentColor; border-top: none; } </style> </head> <body> <div id="wheelOfFortune"> <canvas id="wheel" width="300" height="300"></canvas> <div id="spin">SPIN</div> </div> <script> const sectors = [ { color: "https://i.ibb.co/bzJzvBY/ishiseto.jpg", label: "イシズ・イシュタール" }, { color: "https://i.ibb.co/YpSBKpX/yunaoi.jpg", label: "ゆさあお" }, { color: "https://i.ibb.co/yB0jFVr/subnmis.jpg", label: "流星のロックマン" }, { color: "https://i.ibb.co/DfGxJGP/Screenshot-2024-04-14-16-01-48.jpg", label: "シオン(キングダムハーツ)" }, { color: "https://i.ibb.co/Yc3v4KS/Instruments.jpg", label: "instruments" } ]; const rand = (m, M) => Math.random() * (M - m) + m; const tot = sectors.length; const elSpin = document.querySelector("#spin"); const ctx = document.querySelector("#wheel").getContext("2d"); const dia = ctx.canvas.width; const rad = dia / 2; const PI = Math.PI; const TAU = 2 * PI; const arc = TAU / tot; const friction = 0.991; const angVelMin = 0.002; let angVelMax = 0; let angVel = 0; let ang = 0; let isSpinning = false; let isAccelerating = false; let animFrame = null; const getIndex = () => Math.floor(tot - ang / TAU * tot) % tot; const drawSector = (sector, i) => { const angle = arc * i; const img = new Image(); img.onload = function() { ctx.save(); ctx.beginPath(); ctx.moveTo(rad, rad); ctx.arc(rad, rad, rad, angle, angle + arc); ctx.closePath(); ctx.clip(); ctx.translate(rad, rad); ctx.rotate(angle + arc / 2); const scaleFactor = Math.min(dia / img.width, dia / img.height); const scaledWidth = img.width * scaleFactor; const scaledHeight = img.height * scaleFactor; ctx.drawImage(img, -scaledWidth / 5, -scaledHeight / 5, scaledWidth, scaledHeight); ctx.restore(); ctx.textAlign = "right"; ctx.fillStyle = "#000"; ctx.font = "bold 30px sans-serif"; ctx.fillText(sector.label, 0, rad - 10); }; img.src = sector.color; }; const rotate = () => { const sector = sectors[getIndex()]; ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`; //elSpin.textContent = !angVel ? "SPIN" : sector.label; elSpin.textContent = sector.label; }; const frame = () => { if (!isSpinning) return; if (angVel >= angVelMax) isAccelerating = false; if (isAccelerating) { angVel ||= angVelMin; angVel *= 1.06; } else { isAccelerating = false; angVel *= friction; if (angVel < angVelMin) { isSpinning = false; angVel = 0; cancelAnimationFrame(animFrame); } } ang += angVel; ang %= TAU; rotate(); }; const engine = () => { frame(); animFrame = requestAnimationFrame(engine); }; elSpin.addEventListener("click", () => { if (isSpinning) return; isSpinning = true; isAccelerating = true; angVelMax = rand(0.25, 0.40); engine(); }); sectors.forEach(drawSector); rotate(); </script> </body> <!--SelfUpdatingLink--> <a id="link" href="#" target="_blank">Link</a> <script> const spinDiv = document.getElementById("spin"); const linkDiv = document.getElementById("link"); // Create a new MutationObserver instance const observer = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { if (mutation.type === "childList" && mutation.target === spinDiv) { const spinText = spinDiv.textContent; const linkURL = `https://www.pixiv.net/en/users/75406576/artworks/${spinText}`; linkDiv.href = linkURL; } } }); observer.observe(spinDiv, { childList: true }); setTimeout(() => { spinDiv.textContent = "New Spin Text"; }, 3000); </script> __________________________________________________________________ Megaman Zero Esque CSS: (with audio for buttons) ------------------------ <!----Audio---> <audio id="audio"><source src="https://a.tumblr.com/tumblr_ojrn7aGBii1w2e2oyo1.mp3" type="audio/mpeg"></audio> <script> var html5_audiotypes={"mp3":"audio/mpeg","mp4":"audio/mp4","ogg":"audio/ogg","wav":"audio/wav"};function createsoundbite(e){var t=document.createElement("audio");if(t.canPlayType)for(var n=0;n<arguments.length;n++){var r=document.createElement("source");r.setAttribute("src",arguments[n]),arguments[n].match(/\.(\w+)$/i)&&r.setAttribute("type",html5_audiotypes[RegExp.$1]),t.appendChild(r)}return t.load(),t.playclip=function(){t.pause(),t.currentTime=0,t.play()},t}var mouseoversound=createsoundbite("/other/music/sfx/megaman/mmz4_option.mp3");var clicksound=createsoundbite("/other/music/sfx/megaman/mmz4_ok.mp3"); document.getElementById("img").addEventListener("click", function(){ document.getElementById("audio").play(); }); </script> <!----MMZ_CSS--> <style> body { background-color: #010245; /* Add this line to set the background color to blue */ background-image: url(https://alceawis.de/other/music/sfx/megaman/mmz_t-n-b.gif), url(https://alceawis.de/other/music/sfx/megaman/mmz_l-n-r.gif); background-repeat: repeat-x, repeat-y; background-position: center top, right top; background-attachment: fixed; } body::before { content: ""; position: fixed; bottom: 0; left: 0; width: 100%; height: 100%; background-image: url(https://alceawis.de/other/music/sfx/megaman/mmz_l-n-r.gif); background-repeat: repeat-y; background-position: right bottom; transform: scaleX(-1); pointer-events: none; /* Add this line to allow interaction with elements behind */ } body::after { content: ""; position: fixed; bottom: 0; left: 0px; right: 0px; height: 100%; background-image: url(https://alceawis.de/other/music/sfx/megaman/mmz_t-n-b.gif); background-repeat: repeat-x; background-position: center top; transform: scaleY(-1); pointer-events: none; /* Add this line to allow interaction with elements behind */ } button { background-color: #18FFF7; opacity: 0.5; color: white !important; } * { color: white !important; } </style> _______________________________________ Add Border around ALL elements on page: ------------------------------------------------------------------ <style> * { border: 1px solid #18FFF7; } </style> ___________________ Link from base64 and array generator (musicshare / hidrive) --------------------------- <select id="rollPicker"> </select> <button onclick="generateLink()">Generate Link</button> <br> <div id="linkOutput"></div> <script> var encodedList = "MDAwIFs2NzUgTWJdIFRoZSBNZW50IFZlcnkgQmVzdAowIE5hbmEgT1NUCjAgUGFyYXN5dGUgT1NUCjBbblprXShIaXJveXVraVNhd2FubykKMEFwZXhfTGVnZW5kc19PU1QKMEFyY2FuYSBGYW1pZ2xpYSBPU1QKMEJsb29kc3RhaW5lZCBPU1QKMENhc3RsZXZhbmlhCjBDZWxlc3RlIE9TVAowQ2hhcmxvdHRlIE9TVAowQ2hpY29yeSBPU1QKMENocm9ubyBPU1RzCjBDaW5nCjBEYW5jZSB3aXRoIHRoZSBEZWFkCjBEQyBCZXN0IG9mIFsgTUJdCjBEZWFkbHkgUHJlbW9uaXRpb24gT1NUCjBEZWF0aCBOb3RlIFszNjVNQl0KMEUuWC4gVHJvb3BlcnMgT1NUCjBFc2NhcGUgdGhlIEZhdGUgW0Jlc3Qgb2ZdCjBGYWxsaW5nIGluIFJldmVyc2UKMEZGLUJlc3Qtb2YKMEZsb3dlciBTdW4gJiBSYWluIEJlc3RvZmYKMEZ1bGxfTW9vbl9Xb19TYWdhc2hpdGUKMEhhdmVuIE9yaWdpbmFsIFNvdW5kdHJhY2sKMEhpYmlrZSBFdXBob25pdW0gT3JpZ2luYWwgU291bmR0cmFjawowSG9zaGlOb1NhbWlkYXJlIE9TVAowSG9zaGlOb1NhbWlkYXJlT1NUCjBJbmF6dW1hIEVsZXZlbiBPU1QgQmVzdCBvZgowSm9zZWVfdG8gdG9yYV90b19zYWthbmFfdGFjaGkKMEthZ3V5YS1zYW1hIHdhIEtva3VyYXNldGFpIE9TVAowS2FpdG91IEpva2VyIE9TVAowS2FtaWthemUgS2FpdG91IEplYW5uZSBPU1QKMEthemUgZ2EgVHN1eW9rdSBGdWl0ZWlydQowS2lsbCBMYSBLaWxsIE9TVAowS2l6bmFpdmVyIE9TVAowS29lIG5vIEthdGFjaGkgT1NUCjBLdXJ6Z2VzYWd0CjBLdXp1IG5vIEhvbmthaSBPU1QKMExhemVyaGF3awowTGlua2luX1BhcmsKME1hY2hpbmUtRG9sbCB3YSBLaXp1dHN1a2FuYWkgT1NUCjBNYWRlIGluIEFieXNzIE9TVAowTWFnaWMgS2FpdG8gMTQxMiBPU1QKME1haG91a2EgS291a291IG5vIFJldHRvdXNlaSBPU1QKME1lZ2FtYW5fQmVzdF9PZmZzX1sxLjUgR0JdCjBNaXJhaSBOaWtraSBCZXN0IE9mIFs0MjNNQl0KME1pc2F0byBGdWt1ZW4KME11cmRlciBieSBOdW1iZXJzIE9TVAowTmFnaSBubyBBc3VrYXJhIE9TVAowTmFydXRvIE9TVAowTm9yYWdhbWlfT1NUCjBOb3JuOSBPU1QKMFJvY2ttYW5fRVhFCjBSeXUgZ2EgR290b2t1IE9TVAowUnl1dXNlaSBubyBSb2NrbWFuIE9TVAowU2FuZ2F0c3Ugbm8gTGlvbiBPU1QKMFNhdHN1cmlrdSBubyBUZW5zaGkgT3JpZ2luYWwgU291bmR0cmFjawowU2hpa2lfT1NUCjBTb21hIEJyaW5nZXIgT1NUCjBTcGVlZCBHcmFwaGVyIE9TVAowU3ViYXJhc2hpa2kgS29ubyBTZWthaQowU3dpdGNoZWQgT3JpZ2luYWwgU291bmR0cmFjawowVGV0c3V3YW4gQmlyZHkgT3JpZ2luYWwgU291bmR0cmFjawowVExvWgowVG9reW8gRVNQIE9yaWdpbmFsIFNvdW5kdHJhY2sKMFRvbW9rbyBLYXdhc2UKMFRyYWNlIC0gS2Fzb3VrZW4gbm8gT3Rva28gT1NUCjBUcmlja3N0ZXIgRWRvZ2F3YSBSYW5wbyBPU1QKMFVuZGVydGFsZSBPU1QKMFVzb3RzdWtpIEhpbWUgdG8gTW91bW9rdSBPdWppIE9TVAowVXRhZGEgSGlrYXJ1CjBWaW9sZXQgRXZlcmdhcmRlbiBPU1QKMFhlbm9fT1NUCjBab25lIG9mIHRoZSBFbmRlcnMgT1NUCkFqaW4gT1NUCkFydEF0dGFja19PU1QKQXp1cmUgU3RyaWtlciBHdW52b2x0Ckdob3N0IFRyaWNrIE9yaWdpbmFsIFNvdW5kdHJhY2sKR09EIEVBVEVSIE9TVApHeWFrdXRlbgpNaXNhdG8gRnVrdWVuIC0gQmVzdCBvZgpUb2t5byBHaG91bCBPU1QKWUdPX0Jlc3RfT2ZfWzE1MTBNQl0="; function generateLink() { var rollPicker = document.getElementById("rollPicker"); var selectedOption = rollPicker.options[rollPicker.selectedIndex].value; var link = "https://my.link.de/share/folder#$/"; var finalLink = link + selectedOption; var finalLink = '<a href="' + finalLink + '" target="_blank">' + finalLink + '</a>'; document.getElementById("linkOutput").innerHTML = finalLink;} var decodedList = atob(encodedList); var options = decodedList.split('\n'); var rollPicker = document.getElementById("rollPicker"); for (var i = 0; i < options.length; i++) { var option = document.createElement("option"); option.value = options[i]; option.text = options[i]; rollPicker.appendChild(option);} </script> ___________________________ HTML Class extractor (here: hidrive names) ------------------------------ <input type="text" id="classInput" placeholder="Enter class name" value="label-text display_name"><br> <textarea id="pasteInput" rows="5" placeholder="Paste HTML content here"></textarea> <button id="extractButton" onclick="extractText()">Extract</button> <div id="output"></div> <script> function extractText() { var classInput = document.getElementById('classInput'); var className = classInput.value.trim(); var pasteInput = document.getElementById('pasteInput'); var htmlContent = pasteInput.value; var parser = new DOMParser(); var doc = parser.parseFromString(htmlContent, "text/html"); var elements = doc.getElementsByClassName(className); var extractedText = ''; for (var i = 0; i < elements.length; i++) { extractedText += elements[i].textContent + '<br>'; } var outputDiv = document.getElementById('output'); outputDiv.innerHTML = extractedText; } </script> </body> </html> ____________________________ MMD Bone Renamer (MikuMikuDance) ---------------------------------- <details><summary>How2</summary> --PMXEditor v2.0--<br> 1) Edit -> BatchNameEditor<br> => Copy names to Clipboard<br> <a class="u-button-style u-nav-link u-white" href="https://i.ibb.co/745fFQt/Screenshot-2024-07-05-10-07-23.jpg#https://ibb.co/m8wj705" target="_blank">(click)</a><br> <br> then copy japanese back after conversion </details> <!DOCTYPE html> <html> <head> <style> table { border-collapse: collapse; width: 100%; } td, th { border: 1px solid #000; padding: 8px; text-align: left; } .copy-button { margin-top: 10px; } </style> </head> <body> <h1>MMD Bone Renamer Tool</h1> <form id="parse-form"> <label for="csv-url">CSV URL:</label> <input type="text" id="csv-url" value="https://ry3yr.github.io/mmdbonenames.csv"> <br><br> <label for="input-text">Input Text:</label> <br> <textarea id="input-text" placeholder="Paste non-Japanese bone names here"></textarea> <br> <input type="submit" value="Submit"> </form> <div class="fixed-div"> <button class="copy-button" onclick="copyColumnToClipboard(0)">Copy Old Names</button> <button class="copy-button" onclick="copyColumnToClipboard(1)">Copy New Names</button> </div> <div id="output"></div> <script> function parseCSV(csvData) { var lines = csvData.split("\n"); var result = []; for (var i = 0; i < lines.length; i++) { var currentLine = lines[i].split(","); if (currentLine.length >= 2) { var key = currentLine[0].replace(/"/g, ""); var value = currentLine[1].replace(/"/g, ""); result.push({ key: key, value: value }); } } return result; } document.getElementById("parse-form").addEventListener("submit", function(event) { event.preventDefault(); var csvUrl = document.getElementById("csv-url").value.trim(); // Fetch CSV file from the provided URL fetch(csvUrl) .then(function(response) { return response.text(); }) .then(function(data) { var inputText = document.getElementById("input-text").value.trim(); var lines = inputText.split("\n"); // Parse the CSV data var parsedData = parseCSV(data); // Replace each line if a corresponding value exists var tableContent = "<table><tr><th>Old Name</th><th>New Name</th></tr>"; for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); var matchingValue = parsedData.find(function(item) { return item.key === line; }); var oldName = line; var newName = matchingValue ? matchingValue.value : ""; tableContent += "<tr><td>" + oldName + "</td><td>" + newName + "</td></tr>"; } tableContent += "</table>"; document.getElementById("output").innerHTML = tableContent; //$(".copy-button").remove(); //remove copy button before adding new one //var copyButton = '<button class="copy-button" onclick="copyColumnToClipboard(0)">Copy Old Names</button>'; //copyButton += '<button class="copy-button" onclick="copyColumnToClipboard(1)">Copy New Names</button>'; //document.getElementById("output").insertAdjacentHTML("beforebegin", copyButton); }); }); // Function to copy table column content to clipboard function copyColumnToClipboard(columnIndex) { var table = document.querySelector("table"); var columnData = []; // Get the data from the specified column var rows = table.getElementsByTagName("tr"); for (var i = 0; i < rows.length; i++) { var cells = rows[i].getElementsByTagName("td"); if (cells.length > columnIndex) { columnData.push(cells[columnIndex].innerText); } } // Copy the column data to the clipboard var textarea = document.createElement("textarea"); textarea.textContent = columnData.join("\n"); document.body.appendChild(textarea); textarea.select(); document.execCommand("copy"); document.body.removeChild(textarea); //alert("Content copied to clipboard!"); } </script> </body> </html> <!---v1--jquery--CjxzY3JpcHQgc3JjPSJodHRwczovL2NvZGUuanF1ZXJ5LmNvbS9qcXVlcnktMy42LjAubWluLmpzIj48L3NjcmlwdD4KPHN0eWxlPgp0YWJsZXtib3JkZXItY29sbGFwc2U6Y29sbGFwc2U7d2lkdGg6MTAwJX10ZCx0aHtib3JkZXI6MXB4IHNvbGlkICMwMDA7cGFkZGluZzo4cHg7dGV4dC1hbGlnbjpsZWZ0fS5jb3B5LWJ1dHRvbnttYXJnaW4tdG9wOjEwcHh9CiAgPC9zdHlsZT4KICA8c2NyaXB0PgogICAgJChkb2N1bWVudCkucmVhZHkoZnVuY3Rpb24oKSB7CiAgICAgIGZ1bmN0aW9uIHBhcnNlQ1NWKGNzdkRhdGEpIHsKICAgICAgICB2YXIgbGluZXMgPSBjc3ZEYXRhLnNwbGl0KCJcbiIpOwogICAgICAgIHZhciByZXN1bHQgPSBbXTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxpbmVzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICB2YXIgY3VycmVudExpbmUgPSBsaW5lc1tpXS5zcGxpdCgiLCIpOwogICAgICAgICAgaWYgKGN1cnJlbnRMaW5lLmxlbmd0aCA+PSAyKSB7CiAgICAgICAgICAgIHZhciBrZXkgPSBjdXJyZW50TGluZVswXS5yZXBsYWNlKC8iL2csICIiKTsKICAgICAgICAgICAgdmFyIHZhbHVlID0gY3VycmVudExpbmVbMV0ucmVwbGFjZSgvIi9nLCAiIik7CiAgICAgICAgICAgIHJlc3VsdC5wdXNoKHsga2V5OiBrZXksIHZhbHVlOiB2YWx1ZSB9KTsKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAKICAgICAgICByZXR1cm4gcmVzdWx0OwogICAgICB9CiAgICAgICQoIiNwYXJzZS1mb3JtIikuc3VibWl0KGZ1bmN0aW9uKGV2ZW50KSB7CiAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTsKICAgICAgICAKICAgICAgICB2YXIgY3N2VXJsID0gJCgiI2Nzdi11cmwiKS52YWwoKS50cmltKCk7CiAgICAgICAgCiAgICAgICAgLy8gRmV0Y2ggQ1NWIGZpbGUgZnJvbSB0aGUgcHJvdmlkZWQgVVJMCiAgICAgICAgJC5nZXQoY3N2VXJsLCBmdW5jdGlvbihkYXRhKSB7CiAgICAgICAgICB2YXIgaW5wdXRUZXh0ID0gJCgiI2lucHV0LXRleHQiKS52YWwoKS50cmltKCk7CiAgICAgICAgICB2YXIgbGluZXMgPSBpbnB1dFRleHQuc3BsaXQoIlxuIik7CiAgICAgICAgICAKICAgICAgICAgIC8vIFBhcnNlIHRoZSBDU1YgZGF0YQogICAgICAgICAgdmFyIHBhcnNlZERhdGEgPSBwYXJzZUNTVihkYXRhKTsKICAgICAgICAgIAogICAgICAgICAgLy8gUmVwbGFjZSBlYWNoIGxpbmUgaWYgYSBjb3JyZXNwb25kaW5nIHZhbHVlIGV4aXN0cwogICAgICAgICAgdmFyIHRhYmxlQ29udGVudCA9ICI8dGFibGU+PHRyPjx0aD5PbGQgTmFtZTwvdGg+PHRoPk5ldyBOYW1lPC90aD48L3RyPiI7CiAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxpbmVzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICAgIHZhciBsaW5lID0gbGluZXNbaV0udHJpbSgpOwogICAgICAgICAgICB2YXIgbWF0Y2hpbmdWYWx1ZSA9IHBhcnNlZERhdGEuZmluZChmdW5jdGlvbihpdGVtKSB7CiAgICAgICAgICAgICAgcmV0dXJuIGl0ZW0ua2V5ID09PSBsaW5lOwogICAgICAgICAgICB9KTsKICAgICAgICAgICAgCiAgICAgICAgICAgIHZhciBvbGROYW1lID0gbGluZTsKICAgICAgICAgICAgdmFyIG5ld05hbWUgPSBtYXRjaGluZ1ZhbHVlID8gbWF0Y2hpbmdWYWx1ZS52YWx1ZSA6ICIiOwogICAgICAgICAgICAKICAgICAgICAgICAgdGFibGVDb250ZW50ICs9ICI8dHI+PHRkPiIgKyBvbGROYW1lICsgIjwvdGQ+PHRkPiIgKyBuZXdOYW1lICsgIjwvdGQ+PC90cj4iOwogICAgICAgICAgfQogICAgICAgICAgdGFibGVDb250ZW50ICs9ICI8L3RhYmxlPiI7CiAgICAgICAgICAKICAgICAgICAgICQoIiNvdXRwdXQiKS5odG1sKHRhYmxlQ29udGVudCk7CiAgICAgICAgICAkKCIuY29weS1idXR0b24iKS5yZW1vdmUoKTsgLy9yZW1vdmUgY29weSBidXR0b24gYmVmb3JlIGFkZGluZyBuZXcgb25lCiAgICAgICAgICAvLyBBZGQgY29weSBidXR0b25zCiAgICAgICAgICB2YXIgY29weUJ1dHRvbiA9ICc8YnV0dG9uIGNsYXNzPSJjb3B5LWJ1dHRvbiIgb25jbGljaz0iY29weUNvbHVtblRvQ2xpcGJvYXJkKDApIj5Db3B5IE9sZCBOYW1lczwvYnV0dG9uPic7CiAgICAgICAgICBjb3B5QnV0dG9uICs9ICc8YnV0dG9uIGNsYXNzPSJjb3B5LWJ1dHRvbiIgb25jbGljaz0iY29weUNvbHVtblRvQ2xpcGJvYXJkKDEpIj5Db3B5IE5ldyBOYW1lczwvYnV0dG9uPic7CiAgICAgICAgICAKICAgICAgICAgICQoIiNvdXRwdXQiKS5iZWZvcmUoY29weUJ1dHRvbik7CiAgICAgICAgfSk7CiAgICAgIH0pOwogICAgfSk7CiAgICAKICAgIC8vIEZ1bmN0aW9uIHRvIGNvcHkgdGFibGUgY29sdW1uIGNvbnRlbnQgdG8gY2xpcGJvYXJkCiAgICBmdW5jdGlvbiBjb3B5Q29sdW1uVG9DbGlwYm9hcmQoY29sdW1uSW5kZXgpIHsKICAgICAgdmFyIHRhYmxlID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcigidGFibGUiKTsKICAgICAgdmFyIGNvbHVtbkRhdGEgPSBbXTsKICAgICAgCiAgICAgIC8vIEdldCB0aGUgZGF0YSBmcm9tIHRoZSBzcGVjaWZpZWQgY29sdW1uCiAgICAgIHZhciByb3dzID0gdGFibGUuZ2V0RWxlbWVudHNCeVRhZ05hbWUoInRyIik7CiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgcm93cy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBjZWxscyA9IHJvd3NbaV0uZ2V0RWxlbWVudHNCeVRhZ05hbWUoInRkIik7CiAgICAgICAgaWYgKGNlbGxzLmxlbmd0aCA+IGNvbHVtbkluZGV4KSB7CiAgICAgICAgICBjb2x1bW5EYXRhLnB1c2goY2VsbHNbY29sdW1uSW5kZXhdLmlubmVyVGV4dCk7CiAgICAgICAgfQogICAgICB9CiAgICAgIAogICAgICAvLyBDb3B5IHRoZSBjb2x1bW4gZGF0YSB0byB0aGUgY2xpcGJvYXJkCiAgICAgIHZhciB0ZXh0YXJlYSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInRleHRhcmVhIik7CiAgICAgIHRleHRhcmVhLnRleHRDb250ZW50ID0gY29sdW1uRGF0YS5qb2luKCJcbiIpOwogICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHRleHRhcmVhKTsKICAgICAgdGV4dGFyZWEuc2VsZWN0KCk7CiAgICAgIGRvY3VtZW50LmV4ZWNDb21tYW5kKCJjb3B5Iik7CiAgICAgIGRvY3VtZW50LmJvZHkucmVtb3ZlQ2hpbGQodGV4dGFyZWEpOwogICAgICAKICAgICAgYWxlcnQoIkNvbnRlbnQgY29waWVkIHRvIGNsaXBib2FyZCEiKTsKICAgIH0KICA8L3NjcmlwdD4KPC9oZWFkPgo8Ym9keT4KICA8aDE+TU1EIEJvbmUgUmVuYW1lciBUb29sPC9oMT4KICAKICA8Zm9ybSBpZD0icGFyc2UtZm9ybSI+CiAgICA8bGFiZWwgZm9yPSJjc3YtdXJsIj5DU1YgVVJMOjwvbGFiZWw+CiAgICA8aW5wdXQgdHlwZT0idGV4dCIgaWQ9ImNzdi11cmwiIHZhbHVlPSJodHRwczovL3J5M3lyLmdpdGh1Yi5pby9tbWRib25lbmFtZXMuY3N2Ij4KICAgIDxicj48YnI+CiAgICA8bGFiZWwgZm9yPSJpbnB1dC10ZXh0Ij5JbnB1dCBUZXh0OjwvbGFiZWw+CiAgICA8YnI+CiAgICA8dGV4dGFyZWEgaWQ9ImlucHV0LXRleHQiIHBsYWNlaG9sZGVyPSJQYXN0ZSBub24gamFwYW5lc2UgYm9uZSBuYW1lcyBoZXJlIj48L3RleHRhcmVhPgogICAgPGJyPgogICAgPGlucHV0IHR5cGU9InN1Ym1pdCIgdmFsdWU9IlN1Ym1pdCI+CiAgPC9mb3JtPgogIAogIDxkaXYgaWQ9Im91dHB1dCI+PC9kaXY+CjwvYm9keT4KPC9odG1sPg== --> _________________________ Bank Saldo Fetch ---------------- <script> function fetchValues() { const inputText = document.getElementById('inputText').value; const euroRegex = /-(\d+(?:,\d+)?)\s*EUR/g; const dateRegex = /^(?!.*Valuta).*\b\d{1,2}\.\s.*$/gm; const euroMatches = Array.from(inputText.matchAll(euroRegex), match => match[1]); const dateMatches = Array.from(inputText.matchAll(dateRegex), match => match[0]); let result = '<table>'; result += '<tr><th>€</th><th>Date</th><th>Text</th></tr>'; for (let i = 1; i < euroMatches.length; i++) { const startIndex = inputText.indexOf(euroMatches[i - 1]); const endIndex = inputText.indexOf(euroMatches[i], startIndex + euroMatches[i - 1].length); let textBetweenMatches = inputText.substring(startIndex + euroMatches[i - 1].length + 1, endIndex).trim(); const truncatedText = textBetweenMatches.substring(0, 45); result += `<tr><td>${euroMatches[i]}€</td><td>${dateMatches[i] ? dateMatches[i].replace(/[•Valuata]/g, '') : ''}</td><td>${truncatedText}</td></tr>`; } result += '</table>'; document.getElementById('result').innerHTML = result; } </script> </head> <body> <textarea id="inputText" rows="10" cols="50" placeholder="Bank Saldo Fetch"></textarea><br> <button onclick="fetchValues()">Fetch Values</button><br> <div id="result"></div> <div id="output"></div> <button onclick="calculateTotals()">Calculate Subtotal</button> <script> function calculateTotals() { const divContent = document.getElementById('result').textContent; const lines = divContent.split('\n'); let totalAll = 0; let totalNettoVMarkt = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const euroValueMatch = line.match(/[0-9,]+\u20AC/); const vendorMatch = line.match(/Netto|V-MARKT/); if (euroValueMatch) { const euroValue = parseFloat(euroValueMatch[0]); totalAll += euroValue; } if (euroValueMatch && vendorMatch) { const euroValue = parseFloat(euroValueMatch[0]); totalNettoVMarkt += euroValue; } } console.log('Total All:', totalAll.toFixed(2) + '€'); console.log('Total Netto + V-MARKT:', totalNettoVMarkt.toFixed(2) + '€'); var outputElement = document.getElementById('output'); var totalAllParagraph = document.createElement('p'); totalAllParagraph.textContent = 'Total All: ' + totalAll.toFixed(2) + '€'; outputElement.appendChild(totalAllParagraph); var totalNettoVMarktParagraph = document.createElement('p'); totalNettoVMarktParagraph.textContent = 'Total Netto + V-MARKT: ' + totalNettoVMarkt.toFixed(2) + '€'; outputElement.appendChild(totalNettoVMarktParagraph); } </script> ______________ Imagejoiner (ImageMagick replacement) ------------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Join Images Tool</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.2/min/dropzone.min.css"> <style> body { font-family: Arial, sans-serif; padding: 20px; max-width: 1200px; margin: 0 auto; } canvas { max-width: 100%; height: auto; margin-top: 20px; border: 1px solid #ccc; display: block; } .options { margin-top: 15px; padding: 15px; background-color: #f9f9f9; border-radius: 5px; } .options label { margin-right: 10px; } .grid-options { display: none; margin-top: 10px; margin-left: 20px; } .grid-options input { width: 40px; margin: 0 5px; } .file-size-limit { margin-top: 10px; margin-bottom: 15px; } .file-size-limit input { width: 100px; margin: 0 5px; } .file-size-limit select { margin-left: 5px; } .info-text { color: #666; font-size: 0.9em; margin-top: 5px; } .dropzone { border: 2px dashed #0087F7; border-radius: 5px; background: white; padding: 20px; margin-bottom: 20px; } button { padding: 8px 15px; margin-right: 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } button:disabled { background-color: #cccccc; cursor: not-allowed; } button:hover:not(:disabled) { background-color: #45a049; } #downloadButton { background-color: #008CBA; } #downloadButton:hover:not(:disabled) { background-color: #007B9A; } .status { margin-top: 10px; padding: 10px; border-radius: 4px; } .status.success { background-color: #dff0d8; color: #3c763d; } .status.error { background-color: #f2dede; color: #a94442; } </style> </head> <body> <h1>Image Joining Tool</h1> <h2>Upload Images</h2> <div id="myDropzone" class="dropzone"></div> <div class="options"> <h3>Layout Options</h3> <input type="radio" name="orientation" id="radioHorizontal" value="horizontal" checked> <label for="radioHorizontal">Horizontal</label> <input type="radio" name="orientation" id="radioVertical" value="vertical"> <label for="radioVertical">Vertical</label> <input type="radio" name="orientation" id="radioGrid" value="grid"> <label for="radioGrid">Grid</label> <div id="gridOptions" class="grid-options"> <span>Grid layout: </span> <input type="number" id="gridCols" min="1" value="2"> × <input type="number" id="gridRows" min="1" value="2"> </div> <h3>Processing Options</h3> <input type="checkbox" id="resizeCheckbox"> <label for="resizeCheckbox">Resize smaller images to largest</label> <br> <input type="checkbox" id="orderCheckbox"> <label for="orderCheckbox">Order images by filename</label> <br><br> <h3>File Size Limit</h3> <div class="file-size-limit"> <label for="fileSizeLimit">Maximum file size:</label> <input type="number" id="fileSizeLimit" min="0.1" step="0.1" value="2"> <select id="fileSizeUnit"> <option value="KB">KB</option> <option value="MB" selected>MB</option> </select> <div class="info-text">Set to 0 for no limit</div> </div> <button id="joinButton">Join Images</button> <button id="downloadButton" disabled>Download Joined Image</button> <div id="statusMessage" class="status"></div> </div> <div id="imageInfo"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.2/min/dropzone.min.js"></script> <script> Dropzone.autoDiscover = false; var imageFiles = []; var currentCanvas = null; var myDropzone = new Dropzone("#myDropzone", { url: "#", autoProcessQueue: false, acceptedFiles: "image/*", addRemoveLinks: true, maxFiles: 20, init: function() { this.on("addedfile", function(file) { imageFiles.push(file); }); this.on("removedfile", function(file) { imageFiles = imageFiles.filter(f => f !== file); }); } }); // Show/hide grid options when grid radio is selected document.querySelectorAll('input[name="orientation"]').forEach(radio => { radio.addEventListener('change', function() { document.getElementById('gridOptions').style.display = this.id === 'radioGrid' ? 'block' : 'none'; }); }); document.getElementById("joinButton").addEventListener("click", function() { if (imageFiles.length === 0) { showStatus("Please upload at least one image.", "error"); return; } var orientation = document.querySelector('input[name="orientation"]:checked').value; var resizeImages = document.getElementById("resizeCheckbox").checked; var orderByFilename = document.getElementById("orderCheckbox").checked; var filesToJoin = [...imageFiles]; if (orderByFilename) { filesToJoin.sort((a, b) => a.name.localeCompare(b.name)); } if (orientation === 'grid') { var cols = parseInt(document.getElementById('gridCols').value) || 1; var rows = parseInt(document.getElementById('gridRows').value) || 1; joinImages(orientation, resizeImages, filesToJoin, cols, rows); } else { joinImages(orientation, resizeImages, filesToJoin); } }); function joinImages(orientation, resizeImages, files, cols, rows) { var loadedImages = []; var loadCount = 0; files.forEach(function(file, index) { var img = new Image(); img.onload = function() { loadedImages[index] = img; loadCount++; if (loadCount === files.length) { mergeImages(loadedImages, orientation, resizeImages, cols, rows); } }; img.src = URL.createObjectURL(file); }); } function mergeImages(images, orientation, resizeImages, cols, rows) { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); let totalWidth = 0; let totalHeight = 0; let maxWidth = 0; let maxHeight = 0; images.forEach(function(img) { totalWidth += img.width; totalHeight += img.height; if (img.width > maxWidth) maxWidth = img.width; if (img.height > maxHeight) maxHeight = img.height; }); if (orientation === "horizontal") { canvas.width = totalWidth; canvas.height = maxHeight; } else if (orientation === "vertical") { canvas.width = maxWidth; canvas.height = totalHeight; } else if (orientation === "grid") { // For grid, calculate dimensions based on cols and rows let cellWidth = maxWidth; let cellHeight = maxHeight; if (resizeImages) { // Find max dimensions for all images images.forEach(img => { if (img.width > cellWidth) cellWidth = img.width; if (img.height > cellHeight) cellHeight = img.height; }); } canvas.width = cellWidth * cols; canvas.height = cellHeight * rows; } let offsetX = 0; let offsetY = 0; if (orientation === "grid") { let cellWidth = canvas.width / cols; let cellHeight = canvas.height / rows; for (let i = 0; i < images.length; i++) { if (i >= cols * rows) break; // Don't draw more images than grid cells let row = Math.floor(i / cols); let col = i % cols; let img = images[i]; let drawWidth = cellWidth; let drawHeight = cellHeight; if (resizeImages) { ctx.drawImage(img, col * cellWidth, row * cellHeight, cellWidth, cellHeight); } else { // Center the image in the cell if not resizing let x = col * cellWidth + (cellWidth - img.width) / 2; let y = row * cellHeight + (cellHeight - img.height) / 2; ctx.drawImage(img, x, y); } } } else { // Original horizontal/vertical code images.forEach(function(img) { if (resizeImages) { if (orientation === "horizontal" && img.height !== maxHeight) { let scale = maxHeight / img.height; let newWidth = img.width * scale; ctx.drawImage(img, offsetX, 0, newWidth, maxHeight); offsetX += newWidth; } else if (orientation === "vertical" && img.width !== maxWidth) { let scale = maxWidth / img.width; let newHeight = img.height * scale; ctx.drawImage(img, 0, offsetY, maxWidth, newHeight); offsetY += newHeight; } else { if (orientation === "horizontal") { ctx.drawImage(img, offsetX, 0); offsetX += img.width; } else { ctx.drawImage(img, 0, offsetY); offsetY += img.height; } } } else { if (orientation === "horizontal") { ctx.drawImage(img, offsetX, 0); offsetX += img.width; } else { ctx.drawImage(img, 0, offsetY); offsetY += img.height; } } }); } var container = document.getElementById("imageInfo"); container.innerHTML = ""; container.appendChild(canvas); currentCanvas = canvas; // Apply file size limit if specified applyFileSizeLimit(); } function applyFileSizeLimit() { if (!currentCanvas) return; const sizeLimit = parseFloat(document.getElementById("fileSizeLimit").value); const unit = document.getElementById("fileSizeUnit").value; // Convert to bytes let maxBytes; if (unit === "KB") { maxBytes = sizeLimit * 1024; } else if (unit === "MB") { maxBytes = sizeLimit * 1024 * 1024; } // If no limit or limit is 0, enable download immediately if (sizeLimit <= 0) { document.getElementById("downloadButton").disabled = false; showStatus("Image ready for download.", "success"); return; } // Try different quality levels to meet the size limit let quality = 0.9; let dataURL = currentCanvas.toDataURL("image/jpeg", quality); // Function to check size and adjust quality if needed function checkAndAdjustQuality() { const base64String = dataURL.split(',')[1]; const fileSize = atob(base64String).length; if (fileSize > maxBytes && quality > 0.1) { // Reduce quality and try again quality -= 0.1; dataURL = currentCanvas.toDataURL("image/jpeg", quality); setTimeout(checkAndAdjustQuality, 0); // Use timeout to prevent blocking } else { // We're either under the limit or at minimum quality currentCanvas.dataset.dataURL = dataURL; document.getElementById("downloadButton").disabled = false; if (fileSize > maxBytes) { showStatus(`Image size (${formatFileSize(fileSize)}) exceeds limit (${formatFileSize(maxBytes)}). Using minimum quality.`, "error"); } else { showStatus(`Image ready for download. Final size: ${formatFileSize(fileSize)} (quality: ${Math.round(quality * 100)}%)`, "success"); } } } checkAndAdjustQuality(); } function formatFileSize(bytes) { if (bytes < 1024) return bytes + " bytes"; else if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " KB"; else return (bytes / 1048576).toFixed(2) + " MB"; } function showStatus(message, type) { const statusElement = document.getElementById("statusMessage"); statusElement.textContent = message; statusElement.className = "status " + type; } document.getElementById("downloadButton").addEventListener("click", function() { if (!currentCanvas) { showStatus("No joined image available!", "error"); return; } var link = document.createElement("a"); // Use the stored data URL if we applied size limits if (currentCanvas.dataset.dataURL) { link.href = currentCanvas.dataset.dataURL; link.download = "joined-image.jpg"; } else { link.href = currentCanvas.toDataURL("image/png"); link.download = "joined-image.png"; } link.click(); }); </script> </body> </html> _____________________________________ Link to Sitepart ----------------------- <a target="_blank" href="https://alceawis.de/sitepartdisplay.html?url=https%3A%2F%2Falceawis.de%2FHobbies&pattern1=Hobbies&pattern2=Tools" style=color:blue>Example</a><br><br> <details><summary>Create Link</summary> <!DOCTYPE html> <html> <head> <title>Link to Sitepart</title> <script> function generateUrl() { // Get the values from the textboxes const url = document.getElementById("url").value; const pattern1 = document.getElementById("pattern1").value; const pattern2 = document.getElementById("pattern2").value; const generatedUrl = `sitepartdisplay.html?url=${encodeURIComponent(url)}&pattern1=${(pattern1)}&pattern2=${(pattern2)}`; const generatedUrlFin = `<a target="_blank" href="${generatedUrl}" style="color: blue">${generatedUrl}</a>`; document.getElementById("generatedUrl").innerHTML = generatedUrlFin; // Redirect to the generated URL //window.open(generatedUrl, "_blank"); } </script> </head> <body> <h1>LinkToSitepart</h1> <p>Enter the details below to generate the URL:</p> <label for="url">URL:</label> <input type="text" id="url" name="url"> <label for="pattern1">Pattern 1:</label> <input type="text" id="pattern1" name="pattern1"> <label for="pattern2">Pattern 2:</label> <input type="text" id="pattern2" name="pattern2"> <p>Generated URL: <span id="generatedUrl"></span></p> <button onclick="generateUrl()">Generate URL</button> </body> </html> </details> <div id="content"></div> <script> const urlParams = new URLSearchParams(window.location.search); const url = urlParams.get("url"); const pattern1 = urlParams.get("pattern1"); const pattern2 = urlParams.get("pattern2"); fetch(url) .then(response => response.text()) .then(data => { const start = data.indexOf(pattern1) + pattern1.length; const end = data.indexOf(pattern2); const content = data.substring(start, end); const tempElement = document.createElement("div"); tempElement.innerHTML = content; const relativeUrls = tempElement.querySelectorAll("a[href], img[src]"); relativeUrls.forEach(url => { const originalUrl = url.getAttribute("href") || url.getAttribute("src"); const absoluteUrl = new URL(originalUrl, window.location.href).href; url.setAttribute("href", absoluteUrl); url.setAttribute("src", absoluteUrl);}); document.getElementById("content").appendChild(tempElement);}); </script> </body> </html> ____________________ Youtube Timestamp extractor (+FFMPEG command generator) for music album dlds: -------------------------------- <!DOCTYPE html> <html> <head> <title>Extract Timestamps</title> </head> <body> Extract Timestamps from YT Videodescriptions<br> <br> <label for="apiKey">API Key:</label> <input type="password" id="apiKey"> <label for="videoUrl">YouTube Video URL:</label> <input type="text" id="videoUrl" placeholder="yturl"> <input type="text" id="videolength" placeholder="videolength" style="width: 60px;"> <button onclick="extractTimestamps()">Extract Timestamps</button> <br><br><textarea id="timestamps" rows="10" cols="50"></textarea> <script> function extractTimestamps() { var apiKey = document.getElementById("apiKey").value; var videoUrl = document.getElementById("videoUrl").value; if (!apiKey || !videoUrl) { alert("Please enter the API key and video URL."); return; } var videoId = extractVideoId(videoUrl); var apiUrl = `https://www.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=${videoId}&key=${apiKey}`; fetch(apiUrl) .then(response => response.json()) .then(data => { var description = data.items[0].snippet.description; var timestampsAndTitles = extractTimestampsAndTitles(description); var timestampsTextArea = document.getElementById("timestamps"); timestampsTextArea.value = timestampsAndTitles .map(item => `${item.timestamp} ${item.title}`) .join("\n"); var videoDuration = data.items[0].contentDetails.duration; var videoLength = convertISO8601Duration(videoDuration); var videoLengthInput = document.getElementById("videolength"); videoLengthInput.value = videoLength; }) .catch(error => { console.error("Error fetching video details:", error); alert("Error fetching video details. Please check the API key and video URL."); }); } function extractTimestampsAndTitles(description) { var regex = /(\d{1,2}:\d{2}(?::\d{2})?)\s+(.+)/g; var results = []; var match; while ((match = regex.exec(description)) !== null) { var timestamp = match[1]; var title = match[2]; results.push({ timestamp: timestamp, title: title }); } return results; } function extractVideoId(url) { var videoId = ""; var regexPatterns = [ /youtu\.be\/([\w-]+)/, /youtube\.com\/watch\?v=([\w-]+)/, /youtube\.com\/embed\/([\w-]+)/, /youtube\.com\/v\/([\w-]+)/, /youtube\.com\/watch\?.*v=([\w-]+)/ ]; for (var i = 0; i < regexPatterns.length; i++) { var match = url.match(regexPatterns[i]); if (match && match[1]) { videoId = match[1]; break; } } return videoId; } function convertISO8601Duration(duration) { var match = duration.match(/PT(\d+H)?(\d+M)?(\d+S)?/); var hours = match[1] ? parseInt(match[1]) : 0; var minutes = match[2] ? parseInt(match[2]) : 0; var seconds = match[3] ? parseInt(match[3]) : 0; // Format the length as xx:xx var formattedLength = ""; if (hours > 0) { formattedLength += hours.toString().padStart(2, "0") + ":"; } formattedLength += minutes.toString().padStart(2, "0") + ":"; formattedLength += seconds.toString().padStart(2, "0"); return formattedLength; } </script> </body> </html> <hr> <details><summary>Generate FFMPEG</summary> FFMPEG GEnerator <h1>FFmpeg Command Generator</h1> <!--<label for="timestamps">Timestamps:</label><br> <textarea id="timestamps" rows="10" cols="50"></textarea><br><br>--> <label for="sourcefilename">Source Filename:</label><br> <input type="text" id="sourcefilename"><br><br> <button id="submit">Generate Commands</button> <button onclick="replaceText()">Replace Text</button></details><br><br> <pre id="commands"></pre> <script> var timestampsTextarea = document.getElementById('timestamps'); var sourceFilenameTextbox = document.getElementById('sourcefilename'); var submitButton = document.getElementById('submit'); submitButton.addEventListener('click', function() { var timestampsValue = timestampsTextarea.value; var lines = timestampsValue.split('\n'); var sourceFilename = sourceFilenameTextbox.value; var ffmpegCommands = []; for (var i = 0; i < lines.length; i++) { var line = lines[i]; var parts = line.split(' '); var timestamp = parts[0]; var name = parts.slice(1).join(' '); // Generate the FFmpeg command var ffmpegCommand = 'ffmpeg -i "' + sourceFilename + '" -ss ' + timestamp + ' -to ' + (i < lines.length - 1 ? lines[i + 1].split(' ')[0] : '') + ' -c copy "' + name + '.mp3"'; ffmpegCommands.push(ffmpegCommand);} var commandsElement = document.getElementById('commands'); commandsElement.textContent = ffmpegCommands.join('\n'); }); </script> <script> function replaceText() { var commandsElement = document.getElementById("commands"); var videolength = document.getElementById("videolength").value; commandsElement.innerHTML = commandsElement.innerHTML.replace(/-to -c/g, "-to " + videolength + " -c");} </script> _________________________ Fill textbox with ?card= querystring, submit, then stop ------------------------------------------------- <form onsubmit="submitForm()"> <input type="text" name="url" placeholder="Enter URL"> <input id="submit" type="submit" value="Submit"> </form> <!--Testfunction--> <script>function submitForm() {alert('Submitted!');}</script> <script> var urlParams = new URLSearchParams(window.location.search); var card = urlParams.get('card'); if (card !== null && card !== '') { var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname; //findquerystrings //window.history.replaceState({}, document.title, newUrl); //removequerystrings (all) document.getElementsByName('url')[0].value = card; const submitButton = document.querySelector('input[type="submit"][value="Submit"]'); submitButton.click();} </script> <!--special-idijot-solution--for-kids-who-hate-ampersand-and-only-need-one-querystring- <script> var url = window.location.href; var card = ''; if (url.includes('?card=')) {card = url.split('?card=')[1].replace(/#38;/g, '');} if (card !== null && card !== '') { var newUrl = window.location.protocol + "//" + window.location.host + window.location.pathname; window.history.replaceState({}, document.title, newUrl); document.getElementsByName('url')[0].value = card; document.getElementById('url').value = card; document.getElementById('submit').click(); } </script>--> ____________ Scroll href links right to left (with stop on hover) ----------------------------------------------------- <div id="linksContainer"></div> <script> fetch('pixivreel') .then(response => response.text()) .then(data => { const linksContainer = document.getElementById('linksContainer'); linksContainer.style.display = 'flex'; linksContainer.style.overflow = 'hidden'; const parser = new DOMParser(); const doc = parser.parseFromString(data, 'text/html'); const linkElements = Array.from(doc.querySelectorAll('a')); linkElements.forEach(link => { linksContainer.appendChild(link.cloneNode(true));}); const containerWidth = linksContainer.offsetWidth; let scrollPosition = containerWidth; let isHovered = false; function scrollLinks() { if (!isHovered) { scrollPosition--; if (scrollPosition <= -containerWidth) { scrollPosition = containerWidth;} linksContainer.style.transform = `translateX(${scrollPosition}px)`;} requestAnimationFrame(scrollLinks);} scrollLinks(); linksContainer.addEventListener('mouseenter', () => { isHovered = true;}); linksContainer.addEventListener('mouseleave', () => { isHovered = false;});}) .catch(error => { console.log('Error:', error); }); </script> ______________________ PMX Capable MMD Web Renderer (with stage + multimodelsupport + move stage + vmd2 support)(2024/07/22)(complex8) --------------------------------------------------- =====index.html========== <!--GeneratePmx2Links--> <script> document.addEventListener('DOMContentLoaded', function() { const pmx2linksContainer = document.getElementById('pmx2links'); const links = document.querySelectorAll('a[href*="?pmx="]'); links.forEach(link => { let originalHref = link.getAttribute('href'); let newHref = originalHref.replace('?pmx=', '?pmx2='); const newLink = document.createElement('a'); newLink.textContent = link.textContent; // Copy the text content newLink.setAttribute('href', newHref); // Set the modified href newLink.addEventListener('click', function(event) { event.preventDefault(); // Prevent the default action (navigation) let currentUrl = window.location.href; let newUrl; if (currentUrl.includes('?')) { newUrl = currentUrl + '&' + newHref.split('?')[1]; // Append new query string } else { newUrl = currentUrl + '?' + newHref.split('?')[1]; // Append new query string } window.location.href = newUrl; }); pmx2linksContainer.appendChild(newLink); }); }); </script> <div style="display: flex; flex-direction: row;"> <div id="readystate">...If vmd will cause keychar err, use https://www.mediafire.com/file/9olqch9pazq3fzd/AnimationSmoother.rar...</div> <div id="vmdplay">...</div> <div id="xyzvalue">...</div> </div> <button onclick="togglePhysicsAndReload()">Toggle Physics</button> <script> function togglePhysicsAndReload() { var physicsAreOn = localStorage.getItem("physicsareon"); if (physicsAreOn) { localStorage.removeItem("physicsareon"); } else { localStorage.setItem("physicsareon", "true");} location.reload();} var physicsAreOn = localStorage.getItem("physicsareon"); if (physicsAreOn) { document.querySelector("button").textContent = "Physics On"; } else { document.querySelector("button").textContent = "Physics Off";} </script> <!--<button onclick="clonePMX()">Clone PMX</button>--> <div class="keyboard"> <button id="." onclick="togglePhysicsAndReload()" style="background-color: transparent; border: none;color: transparent;">Move L3.__</button> <button id="moveUpButton">Move Up</button><br> <button id="moveLeftButton">Move Left</button> <button id="moveDownButton">Move Down</button> <button id="moveRightButton">Move Right</button> <button id="." style="background-color: transparent; border: none;color: transparent;">.__</button> <button id="rotateleftButton">RotateLeft</button> <button id="rotaterightButton">RotateRight</button> <a target="_blank" href="https://www.youtube.com/playlist?list=PLBva3abEZvyT-_ajETBGeOCGBA_AFBT5Z#https://www.reddit.com/r/mikumikudance" style=color:blue>r/MMD</a> <a target="_blank" href="https://codepen.io/ryedai1/pens/tags/?selected_tag=mikumikudance" style=color:gray>(tools)</a> (<a target="_blank" href="https://ry3yr.github.io/SampleWebMMD-master.zip" style=color:blue>src-DLD</a>) <!--<a target="_blank" href="http://10.10.10.254/data/UsbDisk1/Volume1/SampleWebMMD-master/index.html?pmx=YusakuFujiki/yusaku.pmx&stage=/livestageclub/livestageclubanimated.pmx&vmd=bts-dna" style=color:blue>Offline-TripMate</a>(<a target="_blank" href="https://alceawis.de/qrgen.html?qr=http://10.10.10.254/data/UsbDisk1/Volume1/SampleWebMMD-master/index.html?pmx=YusakuFujiki/yusaku.pmx&stage=/livestageclub/livestageclubanimated.pmx&vmd=bts-dna" style=color:blue>qr</a>)--> </div> <details> <input type="text" id="buttonvmdInput" value="001" style="width: 100px;"> <button type="button" onclick="copyToClipboard()">Copy</button> <button type="button" onclick="updateQueryStringParameter('vmd', document.getElementById('buttonvmdInput').value);checkfile();">cstm</button> <button type="button" onclick="localStorage.setItem('camid', document.getElementById('buttonvmdInput').value); PoseClickEvent(document.getElementById('buttonvmdInput').value);">cstm+cam</button> <input type="checkbox" id="checkboxSendToPHP"> <label for="checkboxSendToPHP">Send to PHP</label> <!--CopytoClipboard---Code-----> <script> function copyToClipboard() { var inputText = document.getElementById("buttonvmdInput").value; var outputText = '{ id: "' + inputText + '", pose: "' + inputText + '", VmdClip: null, AudioClip: false },'; if (document.getElementById("checkboxSendToPHP").checked) { // Send the output text to mainvmdwrite.php var url = "mainvmdwrite.php"; var xhr = new XMLHttpRequest(); xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { alert("Text sent to mainvmdwrite.php!"); }}; xhr.send("outputText=" + encodeURIComponent(outputText)); } else { navigator.clipboard.writeText(outputText) .then(function() { alert("Text copied to clipboard!");}) .catch(function(error) { console.error("Error copying text: ", error); });}} </script> </details> <script> document.addEventListener('DOMContentLoaded', function() { if (window.self !== window.top) { var copyButton = document.querySelector('button[onclick="copyToClipboard()"]'); copyButton.disabled = true; copyButton.addEventListener('mouseover', function() { var tooltip = document.createElement('span'); tooltip.innerText = 'Disabled by CORS Policy';tooltip.style.position = 'absolute';tooltip.style.backgroundColor = '#000';tooltip.style.color = '#fff';tooltip.style.padding = '5px'; tooltip.style.borderRadius = '4px';tooltip.style.fontSize = '12px';tooltip.style.visibility = 'visible';tooltip.style.top = copyButton.offsetTop + copyButton.offsetHeight + 'px'; tooltip.style.left = copyButton.offsetLeft + 'px';document.body.appendChild(tooltip);}); copyButton.addEventListener('mouseout', function() { var tooltip = document.querySelector('span'); tooltip.parentNode.removeChild(tooltip);});}}); </script> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> </head> <div class="pose"> <!--<button type="button" value="001" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);">vm1</button>--> <!--<button type="button" value="001" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);">vm1</button>--> <button type="button" value="001" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">vm1</button> <button type="button" value="002" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">vm2</button> <button type="button" value="003" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">vm3(camoff)</button> <button type="button" value="004walknthink" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">vm4</button> <button type="button" value="005hard-carry" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">tst(audio)</button> <button type="button" value="bts-bestofme" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">best(audio)</button> </div> <details><summary>All Motions</summary><div class="file-buttons" id="file-buttons"></div></details> <script> function updateQueryStringParameter(key, value) { var url = new URL(window.location.href); var params = new URLSearchParams(url.search); params.set(key, value); url.search = params.toString(); history.replaceState(null, '', url.toString());} </script> <!--<button id="loadButton" value="./vmd/002.vmd">Load VMD File</button>--> <a href="?pmx=YusakuFujiki/yusaku.pmx">Yu</a> <a href="?pmx=AoiZaizen/AoiZaizen.pmx">Aoi</a> <a href="?pmx=Xion/xion.pmx">Xion</a> <a href="?pmx=9s/9s.pmx">9s</a> <a href="?pmx=2b/na_2b_0418n.pmx">2B</a> <a href="?pmx=TLoZ_Zelda_(Scholar)/zelda_scholar.pmx">BOTW Zelda</a> <a href="?pmx=TLoZ_Zelda_(Skyward_Sword)/Zelda_(Skyward_Sword).pmx">SW Zelda</a> <a href="?pmx=TLoZ_Link_BOTW)/linkbotw.pmx">BOTW Link</a> <a href="?pmx=kh_roxas/kh_roxas.pmx">Roxas</a> <a href="?pmx=off/off.pmx">-[OFF]-</a> <button class="off-button" onclick="window.location.href='?pmx=YusakuFujiki/yusaku.pmx&pmx2=AoiZaizen/AoiZaizen.pmx';" style="background-color: #ffcccc; border: #ff9999; border-radius: 8px; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; transition-duration: 0.4s;">yusaao</button> <button class="off-button" onclick="window.location.href='?pmx=Xion%2Fxion.pmx&pmx2=Roxas%2Froxas.pmx';" style="background-color: #ffcccc; border: #ff9999; border-radius: 8px; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; transition-duration: 0.4s;">Xion&Rox</button> <button onclick="window.location.href='?pmx=YusakuFujiki%2Fyusaku.pmx&pmx2=AoiZaizen%2FAoiZaizen.pmx&stage=%2Flivestageclub%2Flivestageclubanimated.pmx&vmd=30sechug&vmd2=30sechug_2';">yusao-hug</button> </div> <details><summary>2ndChara</summary><div id="pmx2links"></div></details> <div style="display: flex; flex-direction: row;"> <button id="play" onclick="renderer.domElement.requestFullscreen()" style="background-color: pink; background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.25) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.25) 50%, rgba(255, 255, 255, 0.25) 75%, transparent 75%, transparent); background-size: 20px 20px; border: none; color: white; padding: 10px 20px; font-size: 16px; font-weight: bold; border-radius: 5px; box-shadow: 0 0 10px rgba(255, 105, 180, 0.5); cursor: pointer; transition: transform 0.3s;" title="Spacebar">Play Animation and Audio</button> <button id="fullscreenButton" title="F10" style="opacity: 0.2; transition: opacity 0.3s; background-color: #007BFF; border: none; padding: 10px 20px; border-radius: 5px;">Fullscreen</button> <label class="switch"><input type="checkbox" id="toggleSwitch" title="extras yes/no"><span class="slider"></span></label>Face/Lips/Extras: <span id="statusValue">Unknown</span> </div> <!---Toggle-extraz--localstorage-value-between-yes-and-no--> <style> .switch{position:relative;display:inline-block;width:60px;height:34px}.slider,.slider:before{position:absolute;transition:.4s}.switch input{opacity:0;width:0;height:0}.slider{cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ccc;border-radius:34px}.slider:before{content:"";height:26px;width:26px;border-radius:50%;left:4px;bottom:4px;background-color:#fff}input:checked+.slider{background-color:#2196f3}input:checked+.slider:before{transform:translateX(26px)} </style> <script> function updateStatus(isChecked) { const statusValue = isChecked ? 'yes' : 'no'; localStorage.setItem('extraz', statusValue); document.getElementById('statusValue').textContent = statusValue;} function loadStatus() { const storedValue = localStorage.getItem('extraz'); const isChecked = storedValue === 'yes'; document.getElementById('toggleSwitch').checked = isChecked; updateStatus(isChecked);} document.getElementById('toggleSwitch').addEventListener('change', (event) => { updateStatus(event.target.checked); }); // Ensure the function is called after the DOM is fully loaded document.addEventListener('DOMContentLoaded', loadStatus); </script> <!----Check VMD File--Existance---> <!--<button id="checkFileButton" onclick="checkfile()">Check File</button>--> <script> function getQueryStringParameter(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name);} async function checkFileExistence(filePath) { try { const response = await fetch(filePath, { method: 'HEAD' }); if (response.ok) { console.log(`File exists: ${filePath}`); document.getElementById('readystate').textContent = `File exists: ${filePath}`; location.reload(); } else { console.log(`File does not exist (404): ${filePath}`); document.getElementById('readystate').textContent = `File doesn't exist: ${filePath}`;} } catch (error) { console.error(`Error fetching file: ${error}`); document.getElementById('readystate').textContent = `Error fetching file: ${error}`;}} function checkfile() { const vmd = getQueryStringParameter('vmd'); if (vmd) { const filePath = `./vmd/${vmd}.vmd`; checkFileExistence(filePath); } else { console.log('No vmd parameter found in the query string.'); document.getElementById('readystate').textContent = 'No vmd parameter found in the query string.';}} const domContentLoadedHandler = () => { checkfile(); }; </script> <!--AudioPlayback--ThreeJS-ver--> <script> async function playAnimationAndAudio() { const urlParams = new URLSearchParams(window.location.search); const vmdValue = urlParams.get('vmd'); if (!vmdValue) { console.log('No vmd parameter found in the URL'); return false; } console.log('vmdValue from URL:', vmdValue); const audioPath = `audio/${vmdValue}.mp3`; const audioListener = new THREE.AudioListener(); const audio = new THREE.Audio(audioListener); const audioLoader = new THREE.AudioLoader(); // Load audio file try { const audioBuffer = await new Promise((resolve, reject) => { audioLoader.load(audioPath, resolve, onAudioLoadProgress, reject); }); audio.setBuffer(audioBuffer); audio.setLoop(true); // Set to true if audio should loop audio.setVolume(1.0); // Adjust volume as needed audio.play(); console.log('Audio loaded and playing:', audioPath); } catch (error) { console.error('Error loading audio:', error); return false; } // Function to handle audio load progress (optional) function onAudioLoadProgress(xhr) { if (xhr.lengthComputable) { const percentComplete = (xhr.loaded / xhr.total) * 100; console.log('Audio load progress:', percentComplete.toFixed(2) + '%'); } } const clock = new THREE.Clock(); function update() { const delta = clock.getDelta(); } update(); return true; } </script> <!--AudioPlayback--non-ThreeJS-ver--> <!-- <script> function playAnimationAndAudio() { var urlParams = new URLSearchParams(window.location.search); var vmdValue = urlParams.get('vmd'); if (vmdValue) { var audioPath = 'audio/' + vmdValue + '.mp3'; if (window.audioElement && !window.audioElement.paused) { window.audioElement.currentTime = 0; } else { setTimeout(500); window.audioElement = new Audio(audioPath); audioElement.loop = true; } window.audioElement.play(); console.log('Animation triggered'); } else { console.log('No vmd parameter found in the URL');}} </script> --> <div class="stage"> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/herogarden/herogarden.pmx';window.location.href = updatedUrl;})()">herogarden</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/parkavenue/parkavenue.pmx';window.location.href = updatedUrl;})()">parkavenue</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/livestageclub/livestageclubanimated.pmx';window.location.href = updatedUrl;})()">LiveStage</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/h2cu09/h2cu09.pmx';window.location.href = updatedUrl;})()">h2cu09</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/SciFi_Cube/SciFi_Cube.pmx';window.location.href = updatedUrl;})()">SciFi_Cube</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/kh_twilighttown/kh_station_plaza/Station_Plaza.pmx';window.location.href = updatedUrl;})()">TwilightTownStation</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/([?&])stage=[^&]*/g, '$1').replace(/(&|\?)$/, '');var newStageParam = encodeURIComponent('/kh_twtnw/Memory\'s_Skyscraper/Memory\'s_Skyscraper.pmx');updatedUrl += (updatedUrl.indexOf('?') > -1 ? '&' : '?') + 'stage=' + newStageParam;window.location.href = updatedUrl;})()">TWTNW Skyscraper</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/palette_stage/palette_stage.pmx';window.location.href = updatedUrl;})()">palette_stage</a> <a href target="_blank" ="#" onclick="redirectToTargetURL();"><b>[TestStageCoordinates]</b></a> </div> <!--redirect--coordinates--for--stage--> <script> function redirectToTargetURL() { var currentURL = window.location.href; var targetURL = "stageloader.html"; var queryParams = currentURL.split("?")[1]; var finalURL = targetURL + "?" + queryParams; window.open(finalURL, "_blank"); } </script> <script src="./libs/three.js"></script> <script src="./libs/mmdparser.min.js"></script> <script src="./libs/ammo.min.js"></script> <script src="./libs/TGALoader.js"></script> <script src="./libs/MMDLoader.js"></script> <script src="./libs/MMDAnimationHelper.js"></script> <script src="./libs/mmd-loader.min.js"></script> <script src="./libs/CCDIKSolver.js"></script> <script src="./libs/MMDPhysics.js"></script> <!--<script src="./libs/Vmd.js"></script> <script src="./libs/VmdFileParser.js"></script>--> <!--<script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/build/three.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/libs/mmdparser.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/ammo.js@0.0.10/ammo.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/loaders/TGALoader.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/loaders/MMDLoader.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/animation/MMDAnimationHelper.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/animation/CCDIKSolver.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/animation/MMDPhysics.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/jsm/controls/OrbitControls.js"></script>--> <script src="./js/main.js"></script> </body> </html> <!----Mark--used-button--green--> <script> window.addEventListener('load', function() { const urlParams = new URLSearchParams(window.location.search); var vmdValue = urlParams.get('vmd'); var buttons = document.querySelectorAll("button"); for (var i = 0; i < buttons.length; i++) {if (buttons[i].value === vmdValue) {buttons[i].style.backgroundColor = "lightgreen";}} }); </script> <!--AllMotions-show---onlineCompOnly---> <script> const fileDir = './vmd'; function listFiles() { const fileButtonsContainer = document.getElementById('file-buttons'); fetch(fileDir) .then(response => response.text()) .then(data => { const doc = new DOMParser().parseFromString(data, 'text/html'); const files = [...doc.getElementsByTagName('a')]; files .map(file => file.textContent.trim()) .filter(fileName => fileName.endsWith('.vmd')) .forEach(fileName => { const button = document.createElement('button'); button.type = 'button'; button.value = fileName; button.textContent = fileName.replace('.vmd', ''); button.onclick = () => { updateQueryStringParameter('vmd', fileName.replace('.vmd', '')); location.reload();}; fileButtonsContainer.appendChild(button);});}) .catch(error => { console.error('Error listing files:', error); });} function updateQueryStringParameter(key, value) { const url = new URL(window.location.href); url.searchParams.set(key, value); window.history.replaceState({}, '', url.toString());} window.addEventListener('load', listFiles); </script> <!--keyboard-shortcuts--f10--spacebar--> <script> document.addEventListener('DOMContentLoaded', function() { document.addEventListener('keydown', function(event) { if (event.key === 'F10') { event.preventDefault(); // Prevent default action (fullscreen mode) renderer.domElement.requestFullscreen()}}); document.addEventListener('keydown', function(event) { if (event.key === ' ') { // Spacebar key event.preventDefault(); // Prevent default action (scrolling the page) document.getElementById('play').click();}});}); </script> =====main.js========== let scene, renderer, camera, mesh, mesh2; let hasLoaded = false; let mixer1, mixer2, clock, cameraAnimation; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; function getQueryStringValue(key) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(key); } const pmx = getQueryStringValue('pmx'); const vmdpath = getQueryStringValue('vmd') || "bts-bestofme"; const pmx2 = getQueryStringValue('pmx2') || "AoiZaizen/AoiZaizen.pmx"; const cameraId = getQueryStringValue('camera'); const stage = getQueryStringValue('stage') || "/sorclair/sorclair.pmx"; let Pmx; let Pmx2; //define new camposition globally let xyzglobal = []; let positionXYZ = localStorage.getItem('xyz') || "0, 0, 0"; if (positionXYZ) {xyzglobal = positionXYZ.split(',').map(parseFloat);} if (pmx) { Pmx = `./pmx/pronama/${pmx.trim()}`; console.log(`PMX: ${pmx.trim()}`); } else { console.log("No PMX selected."); } if (pmx2) { Pmx2 = `./pmx/pronama/${pmx2.trim()}`; console.log(`PMX2: ${pmx2.trim()}`); } else { console.log("No PMX2 selected."); } let StagePath = stage ? `./stages/${stage.trim()}` : './stages/sorclair/sorclair.pmx'; if (pmx) { Pmx = `./pmx/pronama/${pmx.trim()}`;console.log(`PMX: ${pmx.trim()}`);} else {console.log("No PMX selected.");} if (pmx2) {Pmx2 = `./pmx/pronama/${pmx2.trim()}`;console.log(`PMX2: ${pmx2.trim()}`);} else {console.log("No PMX2 selected.");} if (StagePath) {StagePath = `./stages${stage.trim()}`;} else {StagePath = './stages/sorclair/sorclair.pmx';} console.log('StagePath:', StagePath); if (StagePath) { const loader = new THREE.MMDLoader(); const lastIndex = StagePath.lastIndexOf("/"); const basePath = StagePath.substring(0, lastIndex); const vmd1Path = `${basePath}/001.vmd`; const vmd2Path = `${basePath}/002.vmd`; loader.load(StagePath, (stageObject) => { var ambientLight = new THREE.AmbientLight(0xffffff, 1.0); //hardcoded scene.add(ambientLight); scene.add(stageObject); //set stage pos. let positionXYZ = localStorage.getItem('xyz') || "0, 0, 0"; let xyzArray = []; if (positionXYZ) {xyzArray = positionXYZ.split(',').map(parseFloat);} let x, y, z; if (xyzArray.length === 3 && xyzArray.every(coord => !isNaN(coord))) {[x, y, z] = xyzArray.map((coord, index) => coord + (index === 0 ? -0 : -0));} x = -x; //flip x because we move over 0coordinate //y = 0; //y must always be 0 so we do not fall under the stage z= -z; //flip x because we move over 0coordinate stageObject.position.set(x, y, z); const mixer = new THREE.AnimationMixer(stageObject); loader.loadAnimation(vmd1Path, stageObject, (vmd1Clip) => { vmd1Clip.name = "001"; console.log(`Loaded VMD: ${vmd1Path}`); const motionObject1 = MotionObjects.find(obj => obj.id === "001"); if (motionObject1) { motionObject1.VmdClip = vmd1Clip; const action1 = mixer.clipAction(vmd1Clip); action1.play(); } else { console.warn(`Motion object with id "001" not found.`); } }, onProgress, onError); loader.loadAnimation(vmd2Path, stageObject, (vmd2Clip) => { vmd2Clip.name = "002"; console.log(`Loaded VMD: ${vmd2Path}`); const motionObject2 = MotionObjects.find(obj => obj.id === "002"); if (motionObject2) { motionObject2.VmdClip = vmd2Clip; const action2 = mixer.clipAction(vmd2Clip); action2.play(); } else { console.warn(`Motion object with id "002" not found.`); } }, onProgress, onError); const clock = new THREE.Clock(); const animate = () => { requestAnimationFrame(animate); const delta = clock.getDelta(); mixer.update(delta); renderer.render(scene, camera); }; animate(); }, onProgress, onError); } else {console.warn('No valid stage path found.');} //if (!Stage) {Stage = stage ? `stages${stage}` : '/sorclair/sorclair.pmx';} if (!Pmx) {Pmx = `./pmx/pronama/AoiZaizen/AoiZaizen.pmx`;} console.log('StagePath:', StagePath); const MotionObjects = [ { id: "001", pose: "001", VmdClip: null, AudioClip: false }, { id: "002", pose: "002", VmdClip: null, AudioClip: false }, ]; window.onload = () => { Init(); LoadStage().then(() => { LoadModels().then(() => { }); }); }; function Init() { document.getElementById("moveLeftButton").addEventListener("click", () => { camera.position.x -= 1; }); document.getElementById("moveRightButton").addEventListener("click", () => { camera.position.x += 1; }); document.getElementById("moveUpButton").addEventListener("click", () => { camera.position.y += 1; }); document.getElementById("moveDownButton").addEventListener("click", () => { camera.position.y -= 1; }); document.getElementById("rotaterightButton").addEventListener("click", () => { mesh.rotateY(Math.PI / 4); }); document.getElementById("rotateleftButton").addEventListener("click", () => { mesh.rotateY(-Math.PI / 4); }); const storedValue = localStorage.getItem('xyz'); const displayDiv = document.getElementById('xyzvalue'); displayDiv.innerHTML = "<b>&nbsp;XYZ</b>" + storedValue; scene = new THREE.Scene(); renderer = new THREE.WebGLRenderer({ alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(100, windowWidth / windowHeight, 1, 1000); // Initialize camera position //let positionXYZ = localStorage.getItem('xyz'); //let xyzArray = []; //if (positionXYZ) {xyzArray = positionXYZ.split(',').map(parseFloat);} //if (xyzArray.length === 3) { //const [x, y, z] = xyzArray; //camera.position.set(x, y, z);} else { //camera.position.set(0, 19, 20); //} let positionXYZ = localStorage.getItem('xyz'); let xyzArray = []; if (positionXYZ) {xyzArray = positionXYZ.split(',').map(parseFloat);} let x, y, z; if (xyzArray.length === 3 && xyzArray.every(coord => !isNaN(coord))) {[x, y, z] = xyzArray.map((coord, index) => coord + (index === 0 ? 15 : 15)); // Add 15 to x and y } else { x = 0;y = 19;z = 20;} camera.position.set(x, y, z); clock = new THREE.Clock(); } function LoadStage() { return new Promise((resolve, reject) => { const loader = new THREE.MMDLoader(); loader.load(StagePath, (stageObject) => { resolve(); }, onProgress, reject); }); } let animate; function startAnimation() { document.getElementById('readystate').innerHTML = 'Camera(localstorage): ready - ' + localStorage.getItem('vmd') + ' <a href="javascript:location.reload(true)">Reload</a>'; if (!animate) { console.error('Animation function not initialized.'); return; } animate(); // Start the animation loop } document.getElementById('play').addEventListener('click', async () => { const urlParams = new URLSearchParams(window.location.search); const vmdValue = urlParams.get('vmd') || "bts-bestofme"; if (!vmdValue) { console.log('No vmd parameter found in the URL'); return false;} console.log('vmdValue from URL:', vmdValue); const audioPath = `audio/${vmdValue}.mp3`; const audioListener = new THREE.AudioListener(); const audio = new THREE.Audio(audioListener); const audioLoader = new THREE.AudioLoader(); try { const audioBuffer = await new Promise((resolve, reject) => { audioLoader.load(audioPath, resolve, onAudioLoadProgress, reject); }); const loopDuration = parseFloat(localStorage.getItem('vmdplay')); audio.setBuffer(audioBuffer); audio.setLoop(true); // Set to true if audio should loop audio.setVolume(1.0); // Adjust volume as needed audio.play(); console.log('Audio loaded and playing:', audioPath);} catch (error) { console.error('Error loading audio:', error); document.getElementById('readystate').textContent = "Error loading Audio"; return false;} function onAudioLoadProgress(xhr) { if (xhr.lengthComputable) { const percentComplete = (xhr.loaded / xhr.total) * 100; console.log('Audio load progress:', percentComplete.toFixed(2) + '%');}} try { startAnimation(); } catch (error) { console.error('Error loading models:', error);}}); async function LoadModels() { const loader = new THREE.MMDLoader(); function LoadPMX(path) { return new Promise((resolve, reject) => { loader.load(path, (object) => { resolve(object); }, onProgress, reject); });} async function LoadVMDAnimation(mesh, isMesh2 = false) { function getQueryStringParameter(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name);} const vmdId = getQueryStringParameter('vmd') || 'bts-bestofme'; const vmdId2 = getQueryStringParameter('vmd2') || vmdId; const vmdPaths = isMesh2 ? [ `./vmd/${vmdId2}.vmd`, `./vmd/${vmdId2}_lips.vmd`, `./vmd/${vmdId2}_facials.vmd` ] : [ `./vmd/${vmdId}.vmd`, `./vmd/${vmdId}_lips.vmd`, `./vmd/${vmdId}_facials.vmd` ]; localStorage.setItem('vmd', isMesh2 ? vmdId2 : vmdId); async function fileExists(url) { return new Promise((resolve) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { resolve(xhr.status === 200); } }; xhr.onerror = () => resolve(false); xhr.send(); }); } // Check if 'extraz' is set to 'yes' and adjust paths accordingly const checkExtraz = localStorage.getItem('extraz') === 'yes'; const pathsToCheck = [vmdPaths[0]]; // Always check the base VMD file if (checkExtraz) { pathsToCheck.push(vmdPaths[1], vmdPaths[2]);} const animations = []; for (const path of pathsToCheck) { if (await fileExists(path)) { const vmdClip = await new Promise((resolve, reject) => { loader.loadAnimation(path, mesh, (clip) => { clip.name = path; resolve(clip); }, onProgress, reject);}); animations.push(vmdClip); if (path.includes('_lips') || path.includes('_facials')) { console.log(`Loaded additional VMD: ${path}`);} } else { console.log(`File not found: ${path}`);}} if (animations.length > 0) { const mainAnimation = animations.find(clip => !clip.name.includes('_lips') && !clip.name.includes('_facials')); if (mainAnimation) { const vmdPlayTime = mainAnimation.duration.toFixed(2); // Get the VMD animation duration localStorage.setItem('vmdplay', vmdPlayTime); // Save the VMD play time to localStorage const vmdPlayDiv = document.getElementById('vmdplay'); if (vmdPlayDiv) { vmdPlayDiv.innerHTML = `<b>VMD Playtime:</b> ${vmdPlayTime} seconds`;}} return animations; } else {console.log('No VMD files loaded.'); const readystateDiv = document.getElementById('readystate'); if (readystateDiv) { readystateDiv.textContent = 'MotionError: No valid VMD files found.';} return [];}} async function LoadModel1() { let positionXYZ = localStorage.getItem('xyz') || "0, 0, 0"; let position = new THREE.Vector3(0, 0, 0); if (positionXYZ) { const xyzArray = positionXYZ.split(',').map(parseFloat); if (xyzArray.length === 3) { const [x, y, z] = xyzArray; //camera.position.set(x, y, z); //position.set(x, y, z); } else {console.error('Stored xyz coordinates in localStorage are not in the expected format.');} } else {console.error('No xyz coordinates found in localStorage.');} const mesh = await LoadPMX(Pmx); mesh.position.copy(position); scene.add(mesh); const vmdClip = await LoadVMDAnimation(mesh, false); // Pass false for mesh const helper = new THREE.MMDAnimationHelper({ afterglow: 1.0 }); const mmd = { mesh: mesh, animation: vmdClip }; helper.add(mmd.mesh, { animation: mmd.animation, physics: true }); return { mesh: mesh, helper: helper };} async function LoadModel2() { try {if (!Pmx2) { throw new Error('Pmx2 is not defined.');} let positionXYZ = localStorage.getItem('xyz') || "0, 0, 0"; let position = new THREE.Vector3(0, 0, 0); if (positionXYZ) { const xyzArray = positionXYZ.split(',').map(parseFloat); if (xyzArray.length === 3) { const [x, y, z] = xyzArray; position.set(x, y, z); } else {throw new Error('Stored xyz coordinates in localStorage are not in the expected format.');} } else {throw new Error('No xyz coordinates found in localStorage.');} const mesh2 = await LoadPMX(Pmx2); //mesh2.position.copy(position); //mesh2.position.x += 15; //!new URLSearchParams(window.location.search).has('vmd2') && (mesh2.position.x += 15); mesh2.position.x = parseInt(new URLSearchParams(window.location.search).get('distance')) || (new URLSearchParams(window.location.search).has('vmd2') ? 0 : 15); scene.add(mesh2); const vmdClip = await LoadVMDAnimation(mesh2, true); // Pass true for mesh2 const helper = new THREE.MMDAnimationHelper({ afterglow: 1.0 }); helper.add(mesh2, { animation: vmdClip, physics: true }); return { mesh: mesh2, helper }; } catch (error) { console.error('Error loading model 2:', error); return null;}} async function LoadCameraAnimation(camera) { let camid; if (new URLSearchParams(window.location.search).has('camera')) { camid = new URLSearchParams(window.location.search).get('camera'); } else if (new URLSearchParams(window.location.search).has('vmd')) { camid = new URLSearchParams(window.location.search).get('vmd'); } else { camid = localStorage.getItem('camid'); if (!camid) { camid = 'bts-bestofme';}} const cameraVmdPath = "./camera/" + camid + ".vmd"; try { let positionXYZ = localStorage.getItem('xyz'); let xyzArray = []; if (positionXYZ) {xyzArray = positionXYZ.split(',').map(parseFloat);} let x, y, z; if (xyzArray.length === 3 && xyzArray.every(coord => !isNaN(coord))) {[x, y, z] = xyzArray.map((coord, index) => coord + (index === 0 ? 15 : 15));} //camera.position.set(x, y, z); const vmdClip = await new Promise((resolve, reject) => { loader.loadAnimation(cameraVmdPath, camera, (vmdClip) => { vmdClip.name = camid; resolve(vmdClip);}, onProgress, reject);}); return vmdClip;} catch (error) {console.error('Error loading camera animation:', error); throw error;}} const { mesh: mesh1, helper: helper1 } = await LoadModel1(); const { mesh: mesh2, helper: helper2 } = await LoadModel2(); const fov = 45; // Define the field of view const aspect = window.innerWidth / window.innerHeight; // Define the aspect ratio const near = 1; // Define the near clipping plane const far = 1000; // Define the far clipping plane const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); const cameraVmdClip = await LoadCameraAnimation(camera); const cameraHelper = new THREE.MMDAnimationHelper(); cameraHelper.add(camera, { animation: cameraVmdClip }); const clock = new THREE.Clock(); animate = () => { requestAnimationFrame(animate); const delta = clock.getDelta(); helper1.update(delta); if (helper2) helper2.update(delta); cameraHelper.update(delta); // Update camera animation renderer.render(scene, camera);};} function onProgress(xhr) { if (xhr.lengthComputable) { const percentComplete = xhr.loaded / xhr.total * 100; console.log(Math.round(percentComplete) + '% downloaded'); document.getElementById('readystate').innerHTML = Math.round(percentComplete) + '% downloaded (if stuck, click <a href="audio" target="_blank">here</a>) ' + 'Camera: ready - ' + localStorage.getItem('camid');}} function onError(xhr) { console.error("Error loading resource:", xhr); document.getElementById('readystate').textContent = "Error loading resource: " + xhr.statusText;} fullscreenButton.addEventListener('click', () => { if (!document.fullscreenElement) { //document.body.requestFullscreen(); renderer.domElement.requestFullscreen(); } else { if (document.exitFullscreen) { document.exitFullscreen(); } } }); _______________________________ PMX Capable MMD Web Renderer (with stage + multimodelsupport)(2024/07/13-complete rewrite standalone from prev ver) ----------------------------- Download: https://ry3yr.github.io/SampleWebMMD-master.zip Src: https://codeberg.org/alceawisteria/MMD_Web_Renderer ====main.js=== let scene, renderer, camera, mesh, mesh2; let hasLoaded = false; let mixer1, mixer2, clock, cameraAnimation; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; function getQueryStringValue(key) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(key); } const pmx = getQueryStringValue('pmx'); const vmdpath = getQueryStringValue('vmd') || "bts-bestofme"; const pmx2 = getQueryStringValue('pmx2') || "AoiZaizen/AoiZaizen.pmx"; const cameraId = getQueryStringValue('camera'); const stage = getQueryStringValue('stage') || "/sorclair/sorclair.pmx"; let Pmx; let Pmx2; if (pmx) { Pmx = `./pmx/pronama/${pmx.trim()}`; console.log(`PMX: ${pmx.trim()}`); } else { console.log("No PMX selected."); } if (pmx2) { Pmx2 = `./pmx/pronama/${pmx2.trim()}`; console.log(`PMX2: ${pmx2.trim()}`); } else { console.log("No PMX2 selected."); } let StagePath = stage ? `./stages/${stage.trim()}` : './stages/sorclair/sorclair.pmx'; if (pmx) { Pmx = `./pmx/pronama/${pmx.trim()}`;console.log(`PMX: ${pmx.trim()}`);} else {console.log("No PMX selected.");} if (pmx2) {Pmx2 = `./pmx/pronama/${pmx2.trim()}`;console.log(`PMX2: ${pmx2.trim()}`);} else {console.log("No PMX2 selected.");} if (StagePath) {StagePath = `./stages${stage.trim()}`;} else {StagePath = './stages/sorclair/sorclair.pmx';} console.log('StagePath:', StagePath); if (StagePath) { const loader = new THREE.MMDLoader(); const lastIndex = StagePath.lastIndexOf("/"); const basePath = StagePath.substring(0, lastIndex); const vmd1Path = `${basePath}/001.vmd`; const vmd2Path = `${basePath}/002.vmd`; loader.load(StagePath, (stageObject) => { var ambientLight = new THREE.AmbientLight(0xffffff, 1.0); //hardcoded scene.add(ambientLight); scene.add(stageObject); const mixer = new THREE.AnimationMixer(stageObject); loader.loadAnimation(vmd1Path, stageObject, (vmd1Clip) => { vmd1Clip.name = "001"; console.log(`Loaded VMD: ${vmd1Path}`); const motionObject1 = MotionObjects.find(obj => obj.id === "001"); if (motionObject1) { motionObject1.VmdClip = vmd1Clip; const action1 = mixer.clipAction(vmd1Clip); action1.play(); } else { console.warn(`Motion object with id "001" not found.`); } }, onProgress, onError); loader.loadAnimation(vmd2Path, stageObject, (vmd2Clip) => { vmd2Clip.name = "002"; console.log(`Loaded VMD: ${vmd2Path}`); const motionObject2 = MotionObjects.find(obj => obj.id === "002"); if (motionObject2) { motionObject2.VmdClip = vmd2Clip; const action2 = mixer.clipAction(vmd2Clip); action2.play(); } else { console.warn(`Motion object with id "002" not found.`); } }, onProgress, onError); const clock = new THREE.Clock(); const animate = () => { requestAnimationFrame(animate); const delta = clock.getDelta(); mixer.update(delta); renderer.render(scene, camera); }; animate(); }, onProgress, onError); } else {console.warn('No valid stage path found.');} //if (!Stage) {Stage = stage ? `stages${stage}` : '/sorclair/sorclair.pmx';} if (!Pmx) {Pmx = `./pmx/pronama/AoiZaizen/AoiZaizen.pmx`;} console.log('StagePath:', StagePath); const MotionObjects = [ { id: "001", pose: "001", VmdClip: null, AudioClip: false }, ]; window.onload = () => { Init(); LoadStage().then(() => { LoadModels().then(() => { }); }); }; function Init() { document.getElementById("moveLeftButton").addEventListener("click", () => { camera.position.x -= 1; }); document.getElementById("moveRightButton").addEventListener("click", () => { camera.position.x += 1; }); document.getElementById("moveUpButton").addEventListener("click", () => { camera.position.y += 1; }); document.getElementById("moveDownButton").addEventListener("click", () => { camera.position.y -= 1; }); document.getElementById("rotaterightButton").addEventListener("click", () => { mesh.rotateY(Math.PI / 4); }); document.getElementById("rotateleftButton").addEventListener("click", () => { mesh.rotateY(-Math.PI / 4); }); scene = new THREE.Scene(); renderer = new THREE.WebGLRenderer({ alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(100, windowWidth / windowHeight, 1, 1000); camera.position.set(0, 19, 20); clock = new THREE.Clock(); } function LoadStage() { return new Promise((resolve, reject) => { const loader = new THREE.MMDLoader(); loader.load(StagePath, (stageObject) => { resolve(); }, onProgress, reject); }); } let animate; function startAnimation() { document.getElementById('readystate').innerHTML = 'Camera(localstorage): ready - ' + localStorage.getItem('vmd') + ' <a href="javascript:location.reload(true)">Reload</a>'; if (!animate) { console.error('Animation function not initialized.'); return; } animate(); // Start the animation loop } document.getElementById('play').addEventListener('click', async () => { const urlParams = new URLSearchParams(window.location.search); const vmdValue = urlParams.get('vmd') || "bts-bestofme"; if (!vmdValue) { console.log('No vmd parameter found in the URL'); return false;} console.log('vmdValue from URL:', vmdValue); const audioPath = `audio/${vmdValue}.mp3`; const audioListener = new THREE.AudioListener(); const audio = new THREE.Audio(audioListener); const audioLoader = new THREE.AudioLoader(); try { const audioBuffer = await new Promise((resolve, reject) => { audioLoader.load(audioPath, resolve, onAudioLoadProgress, reject); }); audio.setBuffer(audioBuffer); audio.setLoop(true); // Set to true if audio should loop audio.setVolume(1.0); // Adjust volume as needed audio.play(); console.log('Audio loaded and playing:', audioPath); } catch (error) { console.error('Error loading audio:', error); document.getElementById('readystate').textContent = "Error loading Audio"; return false; } function onAudioLoadProgress(xhr) { if (xhr.lengthComputable) { const percentComplete = (xhr.loaded / xhr.total) * 100; console.log('Audio load progress:', percentComplete.toFixed(2) + '%'); }} try { startAnimation(); } catch (error) { console.error('Error loading models:', error); } }); async function LoadModels() { const loader = new THREE.MMDLoader(); function LoadPMX(path) { return new Promise(resolve => { loader.load(path, (object) => { resolve(object); }, onProgress, onError); }); } async function LoadVMDAnimation(mesh, id) { function getQueryStringParameter(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name); } const vmdId = getQueryStringParameter('vmd') || 'bts-bestofme'; const vmdPath = `./vmd/${vmdId}.vmd`; localStorage.setItem('vmd', vmdId); return new Promise((resolve, reject) => { loader.loadAnimation(vmdPath, mesh, (vmdClip) => { vmdClip.name = vmdId; resolve(vmdClip); }, onProgress, reject); }); } async function LoadCameraAnimation(camera) { let camid; if (new URLSearchParams(window.location.search).has('camera')) { camid = new URLSearchParams(window.location.search).get('camera'); } else if (new URLSearchParams(window.location.search).has('vmd')) { camid = new URLSearchParams(window.location.search).get('vmd'); } else { camid = localStorage.getItem('camid'); if (!camid) { camid = 'bts-bestofme'; } } const cameraVmdPath = "./camera/" + camid + ".vmd"; try { const vmdClip = await new Promise((resolve, reject) => { loader.loadAnimation(cameraVmdPath, camera, (vmdClip) => { vmdClip.name = camid; // Set the name to the loaded camid resolve(vmdClip); }, onProgress, reject); }); return vmdClip; } catch (error) { console.error('Error loading camera animation:', error); throw error; // Re-throw the error to propagate it } } async function LoadModel1() { const mesh = await LoadPMX(Pmx); scene.add(mesh); const vmdClip = await LoadVMDAnimation(mesh, "001"); const helper = new THREE.MMDAnimationHelper({ afterglow: 1.0 }); const mmd = { mesh: mesh, animation: vmdClip }; helper.add(mmd.mesh, { animation: mmd.animation, physics: true }); return { mesh: mesh, helper: helper }; } async function LoadModel2() { if (Pmx2) { const mesh2 = await LoadPMX(Pmx2); mesh2.position.x += 15; scene.add(mesh2); const vmdClip = await LoadVMDAnimation(mesh2, "002"); const helper = new THREE.MMDAnimationHelper({ afterglow: 1.0 }); const mmd = { mesh: mesh2, animation: vmdClip }; helper.add(mmd.mesh, { animation: mmd.animation, physics: true }); return { mesh: mesh2, helper: helper }; } } const { mesh: mesh1, helper: helper1 } = await LoadModel1(); const { mesh: mesh2, helper: helper2 } = await LoadModel2(); const fov = 45; // Define the field of view const aspect = window.innerWidth / window.innerHeight; // Define the aspect ratio const near = 1; // Define the near clipping plane const far = 1000; // Define the far clipping plane const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); const cameraVmdClip = await LoadCameraAnimation(camera); const cameraHelper = new THREE.MMDAnimationHelper(); cameraHelper.add(camera, { animation: cameraVmdClip }); const clock = new THREE.Clock(); animate = () => { requestAnimationFrame(animate); const delta = clock.getDelta(); helper1.update(delta); if (helper2) helper2.update(delta); cameraHelper.update(delta); // Update camera animation renderer.render(scene, camera); }; } function onProgress(xhr) { if (xhr.lengthComputable) { const percentComplete = xhr.loaded / xhr.total * 100; console.log(Math.round(percentComplete) + '% downloaded'); document.getElementById('readystate').innerHTML = Math.round(percentComplete) + '% downloaded (if stuck, click <a href="audio" target="_blank">here</a>) ' + 'Camera: ready - ' + localStorage.getItem('camid'); } } function onError(xhr) { console.error("Error loading resource:", xhr); document.getElementById('readystate').textContent = "Error loading resource: " + xhr.statusText; } fullscreenButton.addEventListener('click', () => { if (!document.fullscreenElement) { //document.body.requestFullscreen(); renderer.domElement.requestFullscreen(); } else { if (document.exitFullscreen) { document.exitFullscreen(); } } }); ===========index.htm.====== <!--GeneratePmx2Links--> <script> document.addEventListener('DOMContentLoaded', function() { const pmx2linksContainer = document.getElementById('pmx2links'); const links = document.querySelectorAll('a[href*="?pmx="]'); links.forEach(link => { let originalHref = link.getAttribute('href'); let newHref = originalHref.replace('?pmx=', '?pmx2='); const newLink = document.createElement('a'); newLink.textContent = link.textContent; // Copy the text content newLink.setAttribute('href', newHref); // Set the modified href newLink.addEventListener('click', function(event) { event.preventDefault(); // Prevent the default action (navigation) let currentUrl = window.location.href; let newUrl; if (currentUrl.includes('?')) { newUrl = currentUrl + '&' + newHref.split('?')[1]; // Append new query string } else { newUrl = currentUrl + '?' + newHref.split('?')[1]; // Append new query string } window.location.href = newUrl; }); pmx2linksContainer.appendChild(newLink); }); }); </script> <div id="readystate">...If vmd will cause keychar err, use https://www.mediafire.com/file/9olqch9pazq3fzd/AnimationSmoother.rar...</div> <button onclick="togglePhysicsAndReload()">Toggle Physics</button> <script> function togglePhysicsAndReload() { var physicsAreOn = localStorage.getItem("physicsareon"); if (physicsAreOn) { localStorage.removeItem("physicsareon"); } else { localStorage.setItem("physicsareon", "true");} location.reload();} var physicsAreOn = localStorage.getItem("physicsareon"); if (physicsAreOn) { document.querySelector("button").textContent = "Physics On"; } else { document.querySelector("button").textContent = "Physics Off";} </script> <!--<button onclick="clonePMX()">Clone PMX</button>--> <div class="keyboard"> <button id="." onclick="togglePhysicsAndReload()" style="background-color: transparent; border: none;color: transparent;">Move L3.__</button> <button id="moveUpButton">Move Up</button><br> <button id="moveLeftButton">Move Left</button> <button id="moveDownButton">Move Down</button> <button id="moveRightButton">Move Right</button> <button id="." style="background-color: transparent; border: none;color: transparent;">.__</button> <button id="rotateleftButton">RotateLeft</button> <button id="rotaterightButton">RotateRight</button> <a target="_blank" href="https://www.youtube.com/playlist?list=PLBva3abEZvyT-_ajETBGeOCGBA_AFBT5Z#https://www.reddit.com/r/mikumikudance" style=color:blue>r/MMD</a> <a target="_blank" href="https://codepen.io/ryedai1/pens/tags/?selected_tag=mikumikudance" style=color:gray>(tools)</a> (<a target="_blank" href="https://ry3yr.github.io/SampleWebMMD-master.zip" style=color:blue>src-DLD</a>) <!--<a target="_blank" href="http://10.10.10.254/data/UsbDisk1/Volume1/SampleWebMMD-master/index.html?pmx=YusakuFujiki/yusaku.pmx&stage=/livestageclub/livestageclubanimated.pmx&vmd=bts-dna" style=color:blue>Offline-TripMate</a>(<a target="_blank" href="https://alceawis.de/qrgen.html?qr=http://10.10.10.254/data/UsbDisk1/Volume1/SampleWebMMD-master/index.html?pmx=YusakuFujiki/yusaku.pmx&stage=/livestageclub/livestageclubanimated.pmx&vmd=bts-dna" style=color:blue>qr</a>)--> </div> <details> <input type="text" id="buttonvmdInput" value="001" style="width: 100px;"> <button type="button" onclick="copyToClipboard()">Copy</button> <button type="button" onclick="updateQueryStringParameter('vmd', document.getElementById('buttonvmdInput').value);checkfile();">cstm</button> <button type="button" onclick="localStorage.setItem('camid', document.getElementById('buttonvmdInput').value); PoseClickEvent(document.getElementById('buttonvmdInput').value);">cstm+cam</button> <input type="checkbox" id="checkboxSendToPHP"> <label for="checkboxSendToPHP">Send to PHP</label> <!--CopytoClipboard---Code-----> <script> function copyToClipboard() { var inputText = document.getElementById("buttonvmdInput").value; var outputText = '{ id: "' + inputText + '", pose: "' + inputText + '", VmdClip: null, AudioClip: false },'; if (document.getElementById("checkboxSendToPHP").checked) { // Send the output text to mainvmdwrite.php var url = "mainvmdwrite.php"; var xhr = new XMLHttpRequest(); xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { alert("Text sent to mainvmdwrite.php!"); }}; xhr.send("outputText=" + encodeURIComponent(outputText)); } else { navigator.clipboard.writeText(outputText) .then(function() { alert("Text copied to clipboard!");}) .catch(function(error) { console.error("Error copying text: ", error); });}} </script> </details> <script> document.addEventListener('DOMContentLoaded', function() { if (window.self !== window.top) { var copyButton = document.querySelector('button[onclick="copyToClipboard()"]'); copyButton.disabled = true; copyButton.addEventListener('mouseover', function() { var tooltip = document.createElement('span'); tooltip.innerText = 'Disabled by CORS Policy';tooltip.style.position = 'absolute';tooltip.style.backgroundColor = '#000';tooltip.style.color = '#fff';tooltip.style.padding = '5px'; tooltip.style.borderRadius = '4px';tooltip.style.fontSize = '12px';tooltip.style.visibility = 'visible';tooltip.style.top = copyButton.offsetTop + copyButton.offsetHeight + 'px'; tooltip.style.left = copyButton.offsetLeft + 'px';document.body.appendChild(tooltip);}); copyButton.addEventListener('mouseout', function() { var tooltip = document.querySelector('span'); tooltip.parentNode.removeChild(tooltip);});}}); </script> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> </head> <div class="pose"> <!--<button type="button" value="001" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);">vm1</button>--> <!--<button type="button" value="001" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);">vm1</button>--> <button type="button" value="001" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">vm1</button> <button type="button" value="002" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">vm2</button> <button type="button" value="003" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">vm3(camoff)</button> <button type="button" value="004walknthink" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">vm4</button> <button type="button" value="005hard-carry" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">tst(audio)</button> <button type="button" value="bts-bestofme" onclick="updateQueryStringParameter('vmd', this.value); localStorage.setItem('camid', this.value);location.reload();">best(audio)</button> </div> <details><summary>All Motions</summary><div class="file-buttons" id="file-buttons"></div></details> <script> function updateQueryStringParameter(key, value) { var url = new URL(window.location.href); var params = new URLSearchParams(url.search); params.set(key, value); url.search = params.toString(); history.replaceState(null, '', url.toString());} </script> <!--<button id="loadButton" value="./vmd/002.vmd">Load VMD File</button>--> <a href="?pmx=YusakuFujiki/yusaku.pmx">Yu</a> <a href="?pmx=AoiZaizen/AoiZaizen.pmx">Aoi</a> <a href="?pmx=Xion/xion.pmx">Xion</a> <a href="?pmx=9s/9s.pmx">9s</a> <a href="?pmx=2b/na_2b_0418n.pmx">2B</a> <a href="?pmx=TLoZ_Zelda_(Scholar)/zelda_scholar.pmx">BOTW Zelda</a> <a href="?pmx=TLoZ_Zelda_(Skyward_Sword)/Zelda_(Skyward_Sword).pmx">SW Zelda</a> <a href="?pmx=TLoZ_Link_BOTW)/linkbotw.pmx">BOTW Link</a> <a href="?pmx=kh_roxas/kh_roxas.pmx">Roxas</a> <a href="?pmx=off/off.pmx">-[OFF]-</a> <button class="off-button" onclick="window.location.href='?pmx=YusakuFujiki/yusaku.pmx&pmx2=AoiZaizen/AoiZaizen.pmx';" style="background-color: #ffcccc; border: #ff9999; border-radius: 8px; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; transition-duration: 0.4s;">yusaao</button> <button class="off-button" onclick="window.location.href='?pmx=Xion%2Fxion.pmx&pmx2=Roxas%2Froxas.pmx';" style="background-color: #ffcccc; border: #ff9999; border-radius: 8px; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; transition-duration: 0.4s;">Xion&Rox</button> </div> <details><summary>2ndChara</summary><div id="pmx2links"></div></details> <button id="play" onclick="" style="background-color: pink; background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.25) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.25) 50%, rgba(255, 255, 255, 0.25) 75%, transparent 75%, transparent); background-size: 20px 20px; border: none; color: white; padding: 10px 20px; font-size: 16px; font-weight: bold; border-radius: 5px; box-shadow: 0 0 10px rgba(255, 105, 180, 0.5); cursor: pointer; transition: transform 0.3s;" title="Spacebar">Play Animation and Audio</button> <button id="fullscreenButton" title="F10">Fullscreen</button> <!----Check VMD File--Existance---> <!--<button id="checkFileButton" onclick="checkfile()">Check File</button>--> <script> function getQueryStringParameter(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name);} async function checkFileExistence(filePath) { try { const response = await fetch(filePath, { method: 'HEAD' }); if (response.ok) { console.log(`File exists: ${filePath}`); document.getElementById('readystate').textContent = `File exists: ${filePath}`; location.reload(); } else { console.log(`File does not exist (404): ${filePath}`); document.getElementById('readystate').textContent = `File doesn't exist: ${filePath}`;} } catch (error) { console.error(`Error fetching file: ${error}`); document.getElementById('readystate').textContent = `Error fetching file: ${error}`;}} function checkfile() { const vmd = getQueryStringParameter('vmd'); if (vmd) { const filePath = `./vmd/${vmd}.vmd`; checkFileExistence(filePath); } else { console.log('No vmd parameter found in the query string.'); document.getElementById('readystate').textContent = 'No vmd parameter found in the query string.';}} const domContentLoadedHandler = () => { checkfile(); }; </script> <!--AudioPlayback--ThreeJS-ver--> <script> async function playAnimationAndAudio() { const urlParams = new URLSearchParams(window.location.search); const vmdValue = urlParams.get('vmd'); if (!vmdValue) { console.log('No vmd parameter found in the URL'); return false; } console.log('vmdValue from URL:', vmdValue); const audioPath = `audio/${vmdValue}.mp3`; const audioListener = new THREE.AudioListener(); const audio = new THREE.Audio(audioListener); const audioLoader = new THREE.AudioLoader(); // Load audio file try { const audioBuffer = await new Promise((resolve, reject) => { audioLoader.load(audioPath, resolve, onAudioLoadProgress, reject); }); audio.setBuffer(audioBuffer); audio.setLoop(true); // Set to true if audio should loop audio.setVolume(1.0); // Adjust volume as needed audio.play(); console.log('Audio loaded and playing:', audioPath); } catch (error) { console.error('Error loading audio:', error); return false; } // Function to handle audio load progress (optional) function onAudioLoadProgress(xhr) { if (xhr.lengthComputable) { const percentComplete = (xhr.loaded / xhr.total) * 100; console.log('Audio load progress:', percentComplete.toFixed(2) + '%'); } } const clock = new THREE.Clock(); function update() { const delta = clock.getDelta(); } update(); return true; } </script> <!--AudioPlayback--non-ThreeJS-ver--> <!-- <script> function playAnimationAndAudio() { var urlParams = new URLSearchParams(window.location.search); var vmdValue = urlParams.get('vmd'); if (vmdValue) { var audioPath = 'audio/' + vmdValue + '.mp3'; if (window.audioElement && !window.audioElement.paused) { window.audioElement.currentTime = 0; } else { setTimeout(500); window.audioElement = new Audio(audioPath); audioElement.loop = true; } window.audioElement.play(); console.log('Animation triggered'); } else { console.log('No vmd parameter found in the URL');}} </script> --> <div class="stage"> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/herogarden/herogarden.pmx';window.location.href = updatedUrl;})()">herogarden</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/parkavenue/parkavenue.pmx';window.location.href = updatedUrl;})()">parkavenue</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/livestageclub/livestageclubanimated.pmx';window.location.href = updatedUrl;})()">LiveStage</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/h2cu09/h2cu09.pmx';window.location.href = updatedUrl;})()">h2cu09</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/SciFi_Cube/SciFi_Cube.pmx';window.location.href = updatedUrl;})()">SciFi_Cube</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/kh_twilighttown/kh_station_plaza/Station_Plaza.pmx';window.location.href = updatedUrl;})()">TwilightTownStation</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/([?&])stage=[^&]*/g, '$1').replace(/(&|\?)$/, '');var newStageParam = encodeURIComponent('/kh_twtnw/Memory\'s_Skyscraper/Memory\'s_Skyscraper.pmx');updatedUrl += (updatedUrl.indexOf('?') > -1 ? '&' : '?') + 'stage=' + newStageParam;window.location.href = updatedUrl;})()">TWTNW Skyscraper</a> </div> <script src="./libs/three.js"></script> <script src="./libs/mmdparser.min.js"></script> <script src="./libs/ammo.min.js"></script> <script src="./libs/TGALoader.js"></script> <script src="./libs/MMDLoader.js"></script> <script src="./libs/MMDAnimationHelper.js"></script> <script src="./libs/mmd-loader.min.js"></script> <script src="./libs/CCDIKSolver.js"></script> <script src="./libs/MMDPhysics.js"></script> <!--<script src="./libs/Vmd.js"></script> <script src="./libs/VmdFileParser.js"></script>--> <!--<script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/build/three.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/libs/mmdparser.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/ammo.js@0.0.10/ammo.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/loaders/TGALoader.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/loaders/MMDLoader.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/animation/MMDAnimationHelper.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/animation/CCDIKSolver.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/animation/MMDPhysics.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/jsm/controls/OrbitControls.js"></script>--> <script src="./js/main.js"></script> </body> </html> <!----Mark--used-button--green--> <script> window.addEventListener('load', function() { const urlParams = new URLSearchParams(window.location.search); var vmdValue = urlParams.get('vmd'); var buttons = document.querySelectorAll("button"); for (var i = 0; i < buttons.length; i++) {if (buttons[i].value === vmdValue) {buttons[i].style.backgroundColor = "lightgreen";}} }); </script> <!--AllMotions-show---onlineCompOnly---> <script> const fileDir = './vmd'; function listFiles() { const fileButtonsContainer = document.getElementById('file-buttons'); fetch(fileDir) .then(response => response.text()) .then(data => { const doc = new DOMParser().parseFromString(data, 'text/html'); const files = [...doc.getElementsByTagName('a')]; files .map(file => file.textContent.trim()) .filter(fileName => fileName.endsWith('.vmd')) .forEach(fileName => { const button = document.createElement('button'); button.type = 'button'; button.value = fileName; button.textContent = fileName.replace('.vmd', ''); button.onclick = () => { updateQueryStringParameter('vmd', fileName.replace('.vmd', '')); location.reload();}; fileButtonsContainer.appendChild(button);});}) .catch(error => { console.error('Error listing files:', error); });} function updateQueryStringParameter(key, value) { const url = new URL(window.location.href); url.searchParams.set(key, value); window.history.replaceState({}, '', url.toString());} window.addEventListener('load', listFiles); </script> <!--keyboard-shortcuts--f10--spacebar--> <script> document.addEventListener('DOMContentLoaded', function() { document.addEventListener('keydown', function(event) { if (event.key === 'F10') { event.preventDefault(); // Prevent default action (fullscreen mode) renderer.domElement.requestFullscreen()}}); document.addEventListener('keydown', function(event) { if (event.key === ' ') { // Spacebar key event.preventDefault(); // Prevent default action (scrolling the page) document.getElementById('play').click();}});}); </script> ___________________________________ PMX Capable MMD Web Renderer (with stage support) -------------------------------- Download: https://ry3yr.github.io/SampleWebMMD-master.zip Src: https://github.com/Momijinn/SampleWebMMD ===/js/main.js ===== let scene, renderer, camera, mesh, helper, cameraAnimation; let ready = false; const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const clock = new THREE.Clock(); function getQueryStringValue(key) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(key); } const pmx = getQueryStringValue('pmx'); const stage = getQueryStringValue('stage'); let Pmx; let Stage; if (pmx) { Pmx = `./pmx/pronama/${pmx.trim()}`; } else { console.log("No PMX selected."); } if (stage) { Stage = `./stages${stage.trim()}`; } else { console.log("No stage selected."); } if (!Pmx) { Pmx = `./pmx/pronama/AoiZaizen/AoiZaizen.pmx`; } if (!Stage) { Stage = "./stages/sorclair/sorclair.pmx"; } const MotionObjects = [ { id: "001", pose: "001", VmdClip: null, AudioClip: false }, { id: "002", pose: "002", VmdClip: null, AudioClip: false }, { id: "003", pose: "003", VmdClip: null, AudioClip: false }, { id: "bts-bestofme", pose: "bts-bestofme", VmdClip: null, AudioClip: true }, ]; window.onload = () => { Init(); LoadModeler(); Render(); }; function Init() { document.getElementById("moveLeftButton").addEventListener("click", moveCameraLeft); document.getElementById("moveRightButton").addEventListener("click", moveCameraRight); document.getElementById("moveUpButton").addEventListener("click", moveCameraUp); document.getElementById("moveDownButton").addEventListener("click", moveCameraDown); document.getElementById("rotaterightButton").addEventListener("click", rotateCameraRight); document.getElementById("rotateleftButton").addEventListener("click", rotateCameraLeft); function moveCameraLeft() { camera.position.x -= 1; } function moveCameraRight() { camera.position.x += 1; } function moveCameraUp() { camera.position.y += 1; } function moveCameraDown() { camera.position.y -= 1; } function rotateCameraRight() { mesh.rotateY(Math.PI / 4); } function rotateCameraLeft() { mesh.rotateY(-Math.PI / 4); } scene = new THREE.Scene(); const ambient = new THREE.AmbientLight(0xeeeeee); scene.add(ambient); renderer = new THREE.WebGLRenderer({ alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0xcccccc, 0); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(100, windowWidth / windowHeight, 1, 1000); camera.position.set(0, 19, 20); } async function LoadModeler() { const loader = new THREE.MMDLoader(); function LoadPMX() { return new Promise(resolve => { loader.load(Pmx, object => { mesh = object; scene.add(mesh); resolve(true); },);});} function LoadVMD(id) { return new Promise(resolve => { const path = "./vmd/" + id + ".vmd"; const val = MotionObjects.findIndex(MotionObject => MotionObject.id == id); loader.loadAnimation(path, mesh, vmd => { vmd.name = id; MotionObjects[val].VmdClip = vmd; //newid = id; //console.log(newid); document.getElementById('readystate').innerHTML = "Checking motion vmd - " + id + ' (if stuck, click <a href="js/main.js" target="_blank">here</a>)'; resolve(true); },);});} function LoadCamera(id) { let camid; if (new URLSearchParams(window.location.search).has('camera')) { camid = new URLSearchParams(window.location.search).get('camera'); } else { camid = localStorage.getItem('camid'); if (!camid) { camid = 'bts-bestofme'; } } const path = "./camera/" + camid + ".vmd"; return new Promise((resolve, reject) => { loader.loadAnimation(path, camera, cameraAnimation => { cameraAnimation.name = path; resolve(cameraAnimation); console.log(path); }, onProgress, (xhr) => reject(new Error(`Failed to load Camera VMD file: ${path}`))); }); } function LoadAudio(id) { return new Promise(resolve => { const path = "./audio/" + id + ".mp3"; const val = MotionObjects.findIndex(MotionObject => MotionObject.id == id); if (MotionObjects[val].AudioClip) { new THREE.AudioLoader().load(path, buffer => { const listener = new THREE.AudioListener(); const audio = new THREE.Audio(listener).setBuffer(buffer); MotionObjects[val].AudioClip = audio; document.getElementById('readystate').innerHTML = "Checking audio - " + id + ' (if stuck, click <a href="audio" target="_blank">here</a>)'; resolve(true); },); } else { resolve(false);}});} const loadAdditionalPMX = () => { return new Promise(resolve => { loader.load(Stage, object => { const additionalMesh = object; scene.add(additionalMesh); resolve(true); const loadAdditionalPMX = () => { return new Promise(resolve => { loader.load(Stage, object => { const additionalMesh = object; scene.add(additionalMesh); resolve(true); }); }); }; let path; //preperations for 001.vmd and 002.vmd try {path = `./stages${stage.trim()}`;} catch (e) { path = '/default/default';} const lastIndex = path.lastIndexOf("/"); const basePath = path.substring(0, lastIndex); const vmd1 = `${basePath}/001.vmd`; const vmd2 = `${basePath}/002.vmd`; console.log(vmd1); console.log(vmd2); let mixer; const url = window.location.href; const isAnimated = url.indexOf('animated') !== -1; if (isAnimated) { function createMixer() { mixer = new THREE.AnimationMixer(object); } function playAnimation(animationClip) { const action = mixer.clipAction(animationClip); action.play(); } loader.loadAnimation(vmd1, object, (animationClip) => { createMixer(); playAnimation(animationClip); }); loader.loadAnimation(vmd2, object, (animationClip) => { playAnimation(animationClip); }); function animate() { requestAnimationFrame(animate); const deltaTime = clock.getDelta(); if (mixer) { mixer.update(deltaTime); } renderer.render(scene, camera); } animate(); } },);});}; await Promise.all([loadAdditionalPMX()]); await LoadPMX(); await Promise.all(MotionObjects.map(async (MotionObject) => {return await LoadVMD(MotionObject.id);})); await Promise.all(MotionObjects.map(async (MotionObject) => {return await LoadAudio(MotionObject.id);})); cameraAnimation = await LoadCamera("camera-animation-id"); // Ensure to load camera animation VmdControl("loop", true);} function VmdControl(id, loop) { const index = MotionObjects.findIndex(MotionObject => MotionObject.id == id); if (index === -1) { console.log("not Found ID"); //document.getElementById('readystate').textContent = "Default Camera: ready"; document.getElementById('readystate').textContent = "Camera: ready - " + localStorage.getItem('camid'); return; } ready = false; helper = new THREE.MMDAnimationHelper({ afterglow: 2.0, resetPhysicsOnLoop: true }); const enablePhysics = localStorage.getItem("physicsareon") === "true"; helper.add(mesh, { animation: MotionObjects[index].VmdClip, physics: enablePhysics }); if (MotionObjects[index].AudioClip) { MotionObjects[index].AudioClip.play(); } if (cameraAnimation) { helper.add(camera, {animation: cameraAnimation}); } const mixer = helper.objects.get(mesh).mixer; if (!loop) { mixer.existingAction(MotionObjects[index].VmdClip).setLoop(THREE.LoopOnce); } mixer.addEventListener("loop", (event) => { console.log("loop"); }); mixer.addEventListener("finished", (event) => { console.log("finished"); VmdControl("loop", true); }); ready = true; } function onProgress(xhr) { if (xhr.lengthComputable) { const percentComplete = xhr.loaded / xhr.total * 100; console.log(Math.round(percentComplete, 2) + '% downloaded'); } } function onError(xhr) { console.log("ERROR", xhr); } function Render() { requestAnimationFrame(Render); renderer.clear(); renderer.render(scene, camera); if (ready) { helper.update(clock.getDelta()); } } const generatePoseClickEvent = (motionObjects) => { const functionBody = motionObjects.map(function (motion) { return "case '" + motion.pose + "':\n" + " VmdControl('" + motion.id + "', " + (motion.pose === "pose1") + ");\n" + " break;\n"; }).join(""); const poseClickEventFunction = new Function("id", "switch (id) { " + functionBody + " default: VmdControl('001', true); break; }"); return poseClickEventFunction; }; const PoseClickEvent = generatePoseClickEvent(MotionObjects); console.log(PoseClickEvent); ===/index.html== <div id="readystate">...</div> <button onclick="togglePhysicsAndReload()">Toggle Physics</button> <script> function togglePhysicsAndReload() { var physicsAreOn = localStorage.getItem("physicsareon"); if (physicsAreOn) { localStorage.removeItem("physicsareon"); } else { localStorage.setItem("physicsareon", "true");} location.reload();} var physicsAreOn = localStorage.getItem("physicsareon"); if (physicsAreOn) { document.querySelector("button").textContent = "Physics On"; } else { document.querySelector("button").textContent = "Physics Off";} </script> <div class="keyboard"> <button id="." onclick="togglePhysicsAndReload()" style="background-color: transparent; border: none;color: transparent;">Move L3.__</button> <button id="moveUpButton">Move Up</button><br> <button id="moveLeftButton">Move Left</button> <button id="moveDownButton">Move Down</button> <button id="moveRightButton">Move Right</button> <button id="." style="background-color: transparent; border: none;color: transparent;">.__</button> <button id="rotateleftButton">RotateLeft</button> <button id="rotaterightButton">RotateRight</button> <a target="_blank" href="https://www.youtube.com/playlist?list=PLBva3abEZvyT-_ajETBGeOCGBA_AFBT5Z#https://www.reddit.com/r/mikumikudance" style=color:blue>r/MMD</a> <a target="_blank" href="https://codepen.io/ryedai1/pens/tags/?selected_tag=mikumikudance" style=color:gray>(tools)</a> </div> <details> <input type="text" id="buttonvmdInput" value="001" style="width: 100px;"> <button type="button" onclick="copyToClipboard()">Copy</button> <button type="button" onclick="PoseClickEvent(document.getElementById('buttonvmdInput').value)">cstm</button> <button type="button" onclick="localStorage.setItem('camid', document.getElementById('buttonvmdInput').value); PoseClickEvent(document.getElementById('buttonvmdInput').value);">cstm+cam</button> <input type="checkbox" id="checkboxSendToPHP"> <label for="checkboxSendToPHP">Send to PHP</label> <!--CopytoClipboard---Code-----> <script> function copyToClipboard() { var inputText = document.getElementById("buttonvmdInput").value; var outputText = '{ id: "' + inputText + '", pose: "' + inputText + '", VmdClip: null, AudioClip: false },'; if (document.getElementById("checkboxSendToPHP").checked) { // Send the output text to mainvmdwrite.php var url = "mainvmdwrite.php"; var xhr = new XMLHttpRequest(); xhr.open("POST", url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { alert("Text sent to mainvmdwrite.php!"); }}; xhr.send("outputText=" + encodeURIComponent(outputText)); } else { navigator.clipboard.writeText(outputText) .then(function() { alert("Text copied to clipboard!");}) .catch(function(error) { console.error("Error copying text: ", error); });}} </script> </details> <script> document.addEventListener('DOMContentLoaded', function() { if (window.self !== window.top) { var copyButton = document.querySelector('button[onclick="copyToClipboard()"]'); copyButton.disabled = true; copyButton.addEventListener('mouseover', function() { var tooltip = document.createElement('span'); tooltip.innerText = 'Disabled by CORS Policy';tooltip.style.position = 'absolute';tooltip.style.backgroundColor = '#000';tooltip.style.color = '#fff';tooltip.style.padding = '5px'; tooltip.style.borderRadius = '4px';tooltip.style.fontSize = '12px';tooltip.style.visibility = 'visible';tooltip.style.top = copyButton.offsetTop + copyButton.offsetHeight + 'px'; tooltip.style.left = copyButton.offsetLeft + 'px';document.body.appendChild(tooltip);}); copyButton.addEventListener('mouseout', function() { var tooltip = document.querySelector('span'); tooltip.parentNode.removeChild(tooltip);});}}); </script> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> </head> <div class="pose"> <button type="button" value="001" onclick="localStorage.setItem('camid', this.value);PoseClickEvent(this.value);">vm1</button> <button type="button" value="002" onclick="localStorage.setItem('camid', this.value);PoseClickEvent(this.value);">vm2</button> <button type="button" value="003" onclick="localStorage.setItem('camid', this.value);PoseClickEvent(this.value);">vm3(camoff)</button> <button type="button" value="004walknthink" onclick="localStorage.setItem('camid', this.value);PoseClickEvent(this.value);">vm4</button> <button type="button" value="005hard-carry" onclick="localStorage.setItem('camid', this.value);PoseClickEvent(this.value);">tst(audio)</button> <!--set audio to "true" in main.js and have a 005hard-carry.mp3 in audio folder"--> <button type="button" value="bts-bestofme" onclick="localStorage.setItem('camid', this.value);PoseClickEvent(this.value);">best(audio)</button> <!--<button id="loadButton" value="./vmd/002.vmd">Load VMD File</button>--> <a href="?pmx=YusakuFujiki/yusaku.pmx">Yu</a> <a href="?pmx=AoiZaizen/AoiZaizen.pmx">Aoi</a> <a href="?pmx=Xion/xion.pmx">Xion</a> <a href="?pmx=2b/na_2b_0418n.pmx">2B</a> <a href="?pmx=TLoZ_Zelda_(Scholar)/zelda_scholar.pmx">BOTW Zelda</a> <a href="?pmx=TLoZ_Link_BOTW)/linkbotw.pmx">BOTW Link</a> <a href="?pmx=kh_roxas/kh_roxas.pmx">Roxas</a> <a href="?pmx=kh_terra/Casual/Terra%20[Casual].pmx">Terra(KHBBS)</a> <a href="?pmx=kh_aqua/Casual%20Aqua/Aqua%20[Casual%20Skirt].pmx">Aqua(KHBBS)</a> </div> <div class="stage"> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/herogarden/herogarden.pmx';window.location.href = updatedUrl;})()">herogarden</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/parkavenue/parkavenue.pmx';window.location.href = updatedUrl;})()">parkavenue</a> <a href="javascript:(function() {var currentUrl = window.location.href;var updatedUrl = currentUrl.replace(/(\?|&)stage=([^&]*)/g, '') + (currentUrl.indexOf('?') > -1 ? '&' : '?') +'stage=/livestageclub/livestageclubanimated.pmx';window.location.href = updatedUrl;})()">LiveStage</a> </div> <script src="./libs/three.js"></script> <script src="./libs/mmdparser.min.js"></script> <script src="./libs/ammo.min.js"></script> <script src="./libs/TGALoader.js"></script> <script src="./libs/MMDLoader.js"></script> <script src="./libs/MMDAnimationHelper.js"></script> <script src="./libs/mmd-loader.min.js"></script> <script src="./libs/CCDIKSolver.js"></script> <script src="./libs/MMDPhysics.js"></script> <!--<script src="./libs/Vmd.js"></script> <script src="./libs/VmdFileParser.js"></script>--> <!--<script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/build/three.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/libs/mmdparser.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/ammo.js@0.0.10/ammo.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/loaders/TGALoader.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/loaders/MMDLoader.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/animation/MMDAnimationHelper.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/animation/CCDIKSolver.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r106/examples/js/animation/MMDPhysics.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/jsm/controls/OrbitControls.js"></script> --> <script src="./js/main.js"></script> </body> </html> __________________________ MMD PMD texture fixer: -------------------- <!DOCTYPE html> <html> <head> <title>Image Drag and Drop</title> <style> #drop-zone { width: 400px; height: 400px; border: 2px dashed gray; margin: 20px auto; text-align: center; line-height: 400px; font-size: 20px; } #image-element { max-width: 100%; max-height: 400px; margin-top: 20px; } #download-button { display: none; margin-top: 10px; } </style> </head> <body> <div id="drop-zone">Drag and drop an image here</div> <img id="image-element" src="" alt="Processed Image"> <a id="download-button" href="#" download="">Download</a> <script> // Get the HTML elements const dropZone = document.getElementById('drop-zone'); const imageElement = document.getElementById('image-element'); const downloadButton = document.getElementById('download-button'); // Add event listeners for drag and drop events dropZone.addEventListener('dragover', handleDragOver); dropZone.addEventListener('drop', handleFileDrop); // Handle the dragover event to allow the drop function handleDragOver(event) { event.preventDefault(); } // Handle the drop event function handleFileDrop(event) { event.preventDefault(); // Get the dropped file const file = event.dataTransfer.files[0]; // Create a FileReader object to read the file const reader = new FileReader(); // Set up the onload event handler reader.onload = function(e) { // Create a new image element const newImage = new Image(); // Set the onload event handler to handle image loading newImage.onload = function() { // Perform the image processing steps const processedImage = processImage(newImage); // Display the processed image imageElement.src = processedImage.src; // Set the download link properties downloadButton.href = processedImage.src; downloadButton.setAttribute('download', file.name); // Show the download button downloadButton.style.display = 'block'; }; // Set the source of the new image to the dropped file newImage.src = e.target.result; }; // Read the dropped file as a data URL reader.readAsDataURL(file); } // Function to process the image and fit it into the criteria function processImage(image) { // Create a canvas element const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // Ensure the canvas dimensions are power-of-two (POT) const canvasWidth = Math.pow(2, Math.floor(Math.log2(image.width))); const canvasHeight = Math.pow(2, Math.floor(Math.log2(image.height))); canvas.width = canvasWidth; canvas.height = canvasHeight; // Draw the image on the canvas ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight); // Convert the image to RGBA format if necessary if (ctx.getImageData(0, 0, 1, 1).data.length !== 4) { const imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight); const rgbaImageData = convertToRGBA(imageData); ctx.putImageData(rgbaImageData, 0, 0); } // Create a new image element with the processed image data const processedImage = new Image(); processedImage.src = canvas.toDataURL(); return processedImage; } // Function to convert the image data to RGBA format function convertToRGBA(imageData) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = imageData.width; canvas.height = imageData.height; // Create a new ImageData object with RGBA format const rgbaImageData = ctx.createImageData(imageData.width, imageData.height); // Convert the pixel data to RGBA format for (let i = 0; i < imageData.data.length; i += 4) { rgbaImageData.data[i] = imageData.data[i]; rgbaImageData.data[i + 1] = imageData.data[i + 1]; rgbaImageData.data[i + 2] = imageData.data[i + 2]; rgbaImageData.data[i + 3] = 255; // Set alpha channel to opaque } return rgbaImageData; } </script> </body> </html> ____________________ Image attribute analyzer: ------------------------- <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.2/min/dropzone.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.2/min/dropzone.min.js"></script> <div id="myDropzone" class="dropzone"></div> <div id="imageInfo"></div> <script> Dropzone.autoDiscover = false; var myDropzone = new Dropzone("#myDropzone", { url: "#", autoProcessQueue: false, maxFiles: 5, acceptedFiles: "image/*", init: function () { var submitButton = document.querySelector("#submit"); this.on("addedfile", function (file) { processImage(file);}); submitButton.addEventListener("click", function () { myDropzone.processQueue();});}}); function processImage(file) { var reader = new FileReader(); reader.onload = function (event) { var img = new Image(); img.onload = function () { var canvas = document.createElement("canvas"); var context = canvas.getContext("2d"); context.drawImage(img, 0, 0); var codec = img.src.split(";")[0].split(":")[1]; var width = img.width; var height = img.height; var fileSize = file.size; var colorSpace = extractColorSpace(context); var bitDepth = extractBitDepth(context); var fileFormat = extractFileFormat(file); displayImageInfo(file, codec, width, height, fileSize, colorSpace, bitDepth, fileFormat);}; img.src = event.target.result;}; reader.readAsDataURL(file);} function extractColorSpace(context) { return context.getImageData(0, 0, 1, 1).colorSpace;} function extractBitDepth(context) { return context.getImageData(0, 0, 1, 1).data.length * 8;} function extractFileFormat(file) { var fileNameParts = file.name.split("."); return fileNameParts[fileNameParts.length - 1];} function displayImageInfo(file, codec, width, height, fileSize, colorSpace, bitDepth, fileFormat) { var infoDiv = document.getElementById("imageInfo"); var imageDiv = document.createElement("div"); var imgName = document.createElement("p"); var imgInfo = document.createElement("p"); imgName.innerHTML = "<hr><br>Image Name: " + file.name; var thumbnail = document.createElement("img"); thumbnail.src = URL.createObjectURL(file); thumbnail.width = 45; thumbnail.height = 45; imgInfo.innerHTML = "Image Info: Codec: " + codec + "<br>"; if (height % 2 !== 0) { imgInfo.innerHTML += "<span style='color: red;'>Height: " + height + "px</span><br>"; } else { imgInfo.innerHTML += "Height: " + height + "px<br>"; } if (width % 2 !== 0) { imgInfo.innerHTML += "<span style='color: red;'>Width: " + width + "px</span><br>"; } else { imgInfo.innerHTML += "Width: " + width + "px<br>"; } imgInfo.innerHTML += "File Size: " + fileSize + " bytes<br>Color Space: " + colorSpace + "<br>Bit Depth: " + bitDepth + " bits<br>File Format: " + fileFormat + "<br>"; imgInfo.appendChild(thumbnail); imageDiv.appendChild(imgName); imageDiv.appendChild(imgInfo); infoDiv.appendChild(imageDiv); } </script> </body> </html> _______________________ Dynamically change link behaviour with toggle button ------------------------------------------- <button id="toggleButton">Download</button> <script> function toggleDownloadParam() { var links = document.querySelectorAll('a[id="dld"]'); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.href.indexOf('?') === -1) { link.href += '?download=yes'; } else { link.href += '&download=yes'; } } } var toggleButton = document.getElementById('toggleButton'); toggleButton.addEventListener('click', function() { if (toggleButton.classList.contains('on')) { toggleButton.classList.remove('on'); toggleButton.textContent = 'Toggle'; toggleDownloadParam(); } else { toggleButton.classList.add('on'); toggleButton.textContent = 'Toggle (On)'; toggleDownloadParam(); } }); </script> <a href="example.html" id="dld">Download Link 1</a><br> _________________________________________ Nitter Instance Selector ------------------------ <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <label for="nitterUrl"><a href=https://status.d420.de/#https://github.com/zedeus/nitter/issues/986 target=_blank>Nitter</a> URL:</label> <select id="nitterUrl"> <option value="https://nitter.privacydev.net/">https://nitter.privacydev.net/</option> <option value="https://nitter.no-logs.com/">https://nitter.no-logs.com/</option> <option value="https://nitter.poast.org/">https://nitter.poast.org/</option> <option value="https://nitter.kavin.rocks/">https://nitter.kavin.rocks/ (noRSS)</option> </select> <input type="text" id="username" value="alceawisteria"> <input type="checkbox" id="renderRss" checked> Render RSS <input type="checkbox" id="openNewTab"> Open in new tab automatically <br> <button onclick="generateLink()">Generate Link</button> <br><br> <div id="linkOutput"></div> <div id="rssOutput"></div> <br> <br><br> <script> function generateLink() { var nitterUrl = document.getElementById("nitterUrl").value; var username = document.getElementById("username").value; var link = nitterUrl + username; var linkOutput = document.getElementById("linkOutput"); var rsslink = nitterUrl + username + "/rss"; var rssOutput = $("#rssOutput"); var renderRss = document.getElementById("renderRss").checked; var openNewTab = document.getElementById("openNewTab").checked; if (openNewTab) { linkOutput.innerHTML = "<a href='" + link + "' target=_blank>" + link + "</a>"; linkOutput.getElementsByTagName("a")[0].click(); } else { linkOutput.innerHTML = "<a href='" + link + "' target=_blank>" + link + "</a>"; } if (renderRss) { var rssUrl = link + "/rss"; var rss2JsonUrl = "https://api.rss2json.com/v1/api.json?rss_url=" + encodeURIComponent(rssUrl); $.ajax(rss2JsonUrl, { accepts: { json: "application/json" }, dataType: "json", success: function (data) { var items = data.items.slice(0, 10); var rssHtml = "<ul>"; items.forEach(function (item) { var title = item.title; var description = item.description; var pubDate = new Date(item.pubDate).toLocaleDateString(); var author = item.author; var link = item.link; rssHtml += "<li>"; rssHtml += "<h3><a href='" + link + "' target='_blank'>" + title + "</a></h3>"; rssHtml += "<p>" + description + "</p>"; rssHtml += "<p><strong>Date:</strong> " + pubDate + "</p>"; rssHtml += "<p><strong>Author:</strong> " + author + "</p>"; rssHtml += "</li>"; }); rssHtml += "</ul>"; rssOutput.html(rssHtml); }, error: function () { rssOutput.html("Error loading RSS feed."); } }); } else { rssOutput.html(rsslink); } } </script> ___________________________________________ Fetch 100 tag entry mastodon ---------------------------- <!DOCTYPE html> <html> <head> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head> <body> <div> <label for="instance-url">Instance URL:</label> <input type="text" id="instance-url" placeholder="Enter instance URL" value="https://"> </div> <div> <label for="tag">Tag:</label> <input type="text" id="tag" placeholder="Enter tag"> </div> <button onclick="fetchEntries()">Fetch Entries</button> <hr> <div id="entries"></div> <script> var initialFetchURL = 'https://mastodon.cloud/api/v1/timelines/tag/MastodonArt?limit=100'; fetchEntries(); function fetchEntries() { var instanceURL = document.getElementById('instance-url').value; var tag = document.getElementById('tag').value; var apiUrl = initialFetchURL; if (instanceURL && tag) { apiUrl = instanceURL + '/api/v1/timelines/tag/' + tag + '?limit=100'; } $.ajax({ url: apiUrl, dataType: 'json', success: function(data) { displayEntries(data); }, error: function(error) { console.log('Error:', error); } }); } function displayEntries(entries) { var entriesDiv = document.getElementById('entries'); entriesDiv.innerHTML = ''; entries.forEach(function(entry) { var entryHTML = '<div>'; entryHTML += '<p>' + entry.content + '</p>'; entryHTML += '<p>Author: ' + entry.account.display_name + '</p>'; entryHTML += '<p><a href="' + entry.url + '" target="_blank">View Toot</a></p>'; entryHTML += '<hr>'; entryHTML += '</div>'; entriesDiv.innerHTML += entryHTML; }); } </script> </body> </html> ____________________________ Favorite Clothes with table generator ------------------------------------- <table> <tr> <th>Image</th><th>Description</th><th>Source</th><th>Purchasable</th> </tr> <tr><td><a href="https://www.pixiv.net/en/artworks/116877725" target="_blank"><img src="https://medium-media.vgm.io/albums/84/55848/55848-1477525776.jpg" alt="Image" width="100" height="100"></a></td> <td>Pretty dress worn by the GodEater Girl<br> on the <a href="https://vgmdb.net/album/55848" target="_blank">Cover of the 5th Anniv CD</a></td> <td><a href="https://vgmdb.net/album/55848" target="_blank">SRC</a></td> <td>No</td> </tr> <!-- Add more rows as needed --> <tr><td><a href="https://www.amazon.com/Cosplay-Automata-Costume-Kimono-Halloween/dp/B0BNVZJ6FL" target="_blank"><img src="https://m.media-amazon.com/images/I/61maeTQesuL._AC_SX569_.jpg" alt="Image" width="100" height="100"></a></td><td>2Bs Kimono (Switch DLC) <a href="https://vgmdb.net/album/55848" target="_blank">eBay</a></td> <td><a href="https://www.nintendo.de/DLC/NieR-Automata-6C2P4A118680823-2233414.html" target="_blank">SRC</a></td> <td>Yes</td> </tr> </table> <!---New_Entry_Generator--> <details><summary>(+)</summary> <style> .entry{margin-bottom:10px}.entry input[type=text]{width:200px;margin-right:10px}.preview{border:1px solid #ccc;padding:10px;width:300px;height:100px}#outputTable{margin-top:20px;border-collapse:collapse}#outputTable td,#outputTable th{padding:5px;border:1px solid #ccc} </style> <form> <div class="entry"> <input type="text" id="imageURL" placeholder="Image URL" oninput="updatePreview()"> <input type="text" id="imageLink" placeholder="Image Link URL" oninput="updatePreview()"> <input type="text" id="description" placeholder="Description" oninput="updatePreview()"> <input type="text" id="sourceLink" placeholder="Source Link URL" oninput="updatePreview()"> <select id="purchasable" onchange="updatePreview()"> <option value="Yes">Yes</option> <option value="No">No</option> </select></div></form> <table id="outputTable"></table> <textarea id="outputText" rows="10" cols="50" readonly></textarea> <script> function updatePreview() { var imageURL = document.getElementById("imageURL").value; var imageLink = document.getElementById("imageLink").value; var description = document.getElementById("description").value; var sourceLink = document.getElementById("sourceLink").value; var purchasable = document.getElementById("purchasable").value; var outputTable = document.getElementById("outputTable"); var outputText = document.getElementById("outputText"); var row = '<tr>' + '<td><a href="' + imageLink + '" target="_blank"><img src="' + imageURL + '" alt="Image" width="50" height="50"></a></td>' + '<td>' + description + '</td>' + '<td><a href="' + sourceLink + '" target="_blank">SRC</a></td>' + '<td>' + purchasable + '</td>' + '</tr>'; outputTable.innerHTML = row; var outputCode = '<tr>' + '<td><a href="' + imageLink + '" target="_blank"><img src="' + imageURL + '" alt="Image" width="100" height="100"></a></td>' + '<td>' + description + '</td>\n' + '<td><a href="' + sourceLink + '" target="_blank">SRC</a></td>\n' + '<td>' + purchasable + '</td>\n' + '</tr>'; outputText.value = outputCode; } </script> ______________________________________ Fetch full Table from site --------------------------- <script> function fetchTable() { fetch('https://alceawis.de/favclothes.html') .then(response => response.text()) .then(html => { const tempElement = document.createElement('div'); tempElement.innerHTML = html; const table = tempElement.querySelector('table'); const div = document.getElementById('tableContainer'); div.appendChild(table); }) } //document.addEventListener('DOMContentLoaded', fetchTable); </script> <button onclick="fetchTable()">Fetch Table</button> <div id="tableContainer"></div> ____________________ Bookmarklets (with copy function) ---------------------------------- <!--Mastodon--Fediverse--Bookmarklet--> <a href="https://codepen.io/ryedai1/pen/NWJoeRW" target="_blank"><img src="https://donestech.net/files/noticies/xarxa-fediverse-mastodon.png" style="width:50px" alt="feditext">Reroute any mastodon/fedi link to your home instance</a><br> <textarea id="feditext" rows="8" cols="80"></textarea><br> <button onclick="openBookmarkDialogfeditext()">Add to Bookmark</button> <button onclick="copyToClipboardfeditext()">Copy to Clipboard</button> <script> var decodedStringfeditext = atob('amF2YXNjcmlwdDooZnVuY3Rpb24oKSB7CiAgdmFyIGVuY29kZWRVcmwgPSBlbmNvZGVVUklDb21wb25lbnQod2luZG93LmxvY2F0aW9uLmhyZWYpOwogIHZhciByZWRpcmVjdFVybCA9ICJodHRwczovL2FsY2VhLXdpc3RlcmlhLmRlL1BIUC8wZGVtby8yMDIzLTEwLTIyLU1hc3RvZG9uVG9vdFNlYXJjaC9zZWFyY2htYXN0b3VybC5waHA/c2VhcmNoPSIgKyBlbmNvZGVkVXJsICsgIiZwYlVybD1odHRwcyUzQSUyRiUyRnBiLnRvZG9uLmRlJmFwaWtleT1hcGlrZXloZXJlIjsKICB3aW5kb3cubG9jYXRpb24uaHJlZiA9IHJlZGlyZWN0VXJsOw=='); function openBookmarkDialogfeditext() { var bookmarkName = prompt("Enter bookmark name:"); if (bookmarkName) { var bookmarkUrl = decodedStringfeditext; if (window.navigator.userAgent.indexOf("Chrome") !== -1 && chrome && chrome.bookmarks) { chrome.bookmarks.create({ title: bookmarkName, url: bookmarkUrl }, function() { alert("Bookmark added: " + bookmarkName); }); } else if (window.navigator.userAgent.indexOf("Firefox") !== -1 && browser && browser.bookmarks) { browser.bookmarks.create({ title: bookmarkName, url: bookmarkUrl }).then(function() { alert("Bookmark added: " + bookmarkName); }); } else { alert("Sorry, your browser does not support the bookmarking feature.");}}} function copyToClipboardfeditext() { var feditext = document.getElementById("feditext").value; var tempInput = document.createElement("input"); tempInput.value = feditext; document.body.appendChild(tempInput); tempInput.select(); tempInput.setSelectionRange(0, 99999); document.execCommand("copy"); document.body.removeChild(tempInput); alert("Text copied to clipboard: " + feditext);} document.getElementById("feditext").value = decodedStringfeditext; </script> <hr> <!--Youtube--Super8--Bookmarklet--> <a href="https://alceawis.de/super8" target="_blank"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/Invidious-logo.svg/120px-Invidious-logo.svg.png" style="width:50px" alt="super8text">Reroute any youtube links to super8/invidious</a><br> <textarea id="super8text" rows="8" cols="80"></textarea><br> <button onclick="openBookmarkDialogsuper8text()">Add to Bookmark</button> <button onclick="copyToClipboardsuper8text()">Copy to Clipboard</button> <script> var decodedStringsuper8text = atob('amF2YXNjcmlwdDooZnVuY3Rpb24oKSB7CiAgdmFyIGN1cnJlbnRVcmwgPSB3aW5kb3cubG9jYXRpb24uaHJlZjsKICB2YXIgZGVza3RvcFJlZ2V4ID0gL3lvdXR1YmVcLmNvbVwvd2F0Y2hcP2FwcD1kZXNrdG9wLzsKICB2YXIgbW9iaWxlUmVnZXggPSAvbVwueW91dHViZVwuY29tXC8vOwogIHZhciB5b3V0dVJlZ2V4ID0gL3lvdXR1XC5iZVwvKFtcdy1dKykvOwogIHZhciByZXBsYWNlbWVudFVybCA9ICJodHRwczovL3N1cGVyOC5hYnN0dXJ6dGF1LmJlL3dhdGNoP3Y9IjsKICBpZiAoZGVza3RvcFJlZ2V4LnRlc3QoY3VycmVudFVybCkgfHwgbW9iaWxlUmVnZXgudGVzdChjdXJyZW50VXJsKSB8fCB5b3V0dVJlZ2V4LnRlc3QoY3VycmVudFVybCkpIHsKICAgIHZhciB2aWRlb0lkID0gY3VycmVudFVybC5tYXRjaCgvWz8mXXY9KFtcdy1dKykvKVsxXTsKICAgIHZhciBuZXdVcmwgPSByZXBsYWNlbWVudFVybCArIHZpZGVvSWQ7CiAgICB3aW5kb3cubG9jYXRpb24uaHJlZiA9IG5ld1VybDsKICB9Cn0pKCk7'); function openBookmarkDialogsuper8text() { var bookmarkName = prompt("Enter bookmark name:"); if (bookmarkName) { var bookmarkUrl = decodedStringsuper8text; if (window.navigator.userAgent.indexOf("Chrome") !== -1 && chrome && chrome.bookmarks) { chrome.bookmarks.create({ title: bookmarkName, url: bookmarkUrl }, function() { alert("Bookmark added: " + bookmarkName); }); } else if (window.navigator.userAgent.indexOf("Firefox") !== -1 && browser && browser.bookmarks) { browser.bookmarks.create({ title: bookmarkName, url: bookmarkUrl }).then(function() { alert("Bookmark added: " + bookmarkName); }); } else { alert("Sorry, your browser does not support the bookmarking feature.");}}} function copyToClipboardsuper8text() { var super8text = document.getElementById("super8text").value; var tempInput = document.createElement("input"); tempInput.value = super8text; document.body.appendChild(tempInput); tempInput.select(); tempInput.setSelectionRange(0, 99999); document.execCommand("copy"); document.body.removeChild(tempInput); alert("Text copied to clipboard: " + super8text);} document.getElementById("super8text").value = decodedStringsuper8text; </script> <hr> <!--Pinterest--Bookmarklet--> <a href="https://codepen.io/ryedai1/pen/QWPNBBr" target="_blank"><img src="https://images.vexels.com/media/users/3/137399/isolated/preview/47c9900ae893cbed1e1599ab9c8bcb18-pinterest-symbol-logo.png?w=738&amp;fmt=webp" style="width:50px" alt="pinteresttext">Extract the pinterest main image from the current page</a> (<a target="_blank" href="https://alcea-wisteria.de/PHP/0demo/2024-03-11-ImgExtract4Websites/Pinterest/xtrctpin.php?pinurl=https://www.pinterest.de/pin/804877764644009233/">Demo</a>)<br> <textarea id="pinteresttext" rows="8" cols="80"></textarea><br> <button onclick="openBookmarkDialogpinteresttext()">Add to Bookmark</button> <button onclick="copyToClipboardpinteresttext()">Copy to Clipboard</button> <script> var decodedStringpinteresttext = atob('amF2YXNjcmlwdDooZnVuY3Rpb24oKSB7IHZhciBjdXJyZW50VXJsID0gZW5jb2RlVVJJQ29tcG9uZW50KHdpbmRvdy5sb2NhdGlvbi5ocmVmKTsgdmFyIGFwaVVybCA9ICdodHRwczovL2FsY2VhLXdpc3RlcmlhLmRlL1BIUC8wZGVtby8yMDI0LTAzLTExLUltZ0V4dHJhY3Q0V2Vic2l0ZXMvUGludGVyZXN0L3h0cmN0cGluLnBocD9waW51cmw9JzsgdmFyIHRhcmdldFVybCA9IGFwaVVybCArIGN1cnJlbnRVcmw7IHdpbmRvdy5vcGVuKHRhcmdldFVybCk7IH0pKCk7Cg'); function openBookmarkDialogpinteresttext() { var bookmarkName = prompt("Enter bookmark name:"); if (bookmarkName) { var bookmarkUrl = decodedStringpinteresttext; if (window.navigator.userAgent.indexOf("Chrome") !== -1 && chrome && chrome.bookmarks) { chrome.bookmarks.create({ title: bookmarkName, url: bookmarkUrl }, function() { alert("Bookmark added: " + bookmarkName); }); } else if (window.navigator.userAgent.indexOf("Firefox") !== -1 && browser && browser.bookmarks) { browser.bookmarks.create({ title: bookmarkName, url: bookmarkUrl }).then(function() { alert("Bookmark added: " + bookmarkName); }); } else { alert("Sorry, your browser does not support the bookmarking feature.");}}} function copyToClipboardpinteresttext() { var pinteresttext = document.getElementById("pinteresttext").value; var tempInput = document.createElement("input"); tempInput.value = pinteresttext; document.body.appendChild(tempInput); tempInput.select(); tempInput.setSelectionRange(0, 99999); document.execCommand("copy"); document.body.removeChild(tempInput); alert("Text copied to clipboard: " + pinteresttext);} document.getElementById("pinteresttext").value = decodedStringpinteresttext; </script> <hr> _________________________________________ Screenshoot current page --------------------------------------- <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script> </head> <body> <button onclick="captureScreenshot()">*click*</button> <script> function captureScreenshot() { // Set the dimensions of the screenshot to the entire document, including content beyond the scrollbar var width = document.documentElement.scrollWidth; var height = document.documentElement.scrollHeight; html2canvas(document.body, { width: width, height: height, scrollX: 0, scrollY: 0, windowWidth: document.documentElement.clientWidth, windowHeight: document.documentElement.clientHeight }).then(function(canvas) { var screenshotImage = canvas.toDataURL(); var pageTitle = document.title; var filename = pageTitle.replace(/[^\w\s]/gi, ''); var timestamp = new Date().getTime(); filename += '_' + timestamp; //var link = document.createElement('a'); //link.href = screenshotImage; //link.download = filename + '.png'; //link.click(); var newTab = window.open(); newTab.document.write('<img src="' + screenshotImage + '"/>'); }); } </script> ___________________ Create image snapshot of element: --------------------------------- <p id="customFont" class="customfont" contenteditable="true">Hello world!</p>´ <button id="captureButton">*Cheese!!*</button> <div id="imageContainer"></div> <script> const captureButton = document.getElementById('captureButton'); const imageContainer = document.getElementById('imageContainer'); captureButton.addEventListener('click', function() { const paragraph = document.getElementById('customFont'); let content = paragraph.innerHTML; content = content.replace(/<\/div>/g, ' ').replace(/<div>/g, ' ').replace(/&amp;/g, '&'); const paragraphComputedStyle = getComputedStyle(paragraph); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.width = parseFloat(paragraphComputedStyle.width); canvas.height = parseFloat(paragraphComputedStyle.height) + 50; const computedStyle = getComputedStyle(paragraph); context.font = computedStyle.font; context.fillStyle = computedStyle.color; const textMetrics = context.measureText(content); const textHeight = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent; const yCoordinate = (canvas.height - textHeight) / 2 + textMetrics.actualBoundingBoxAscent; context.fillText(content, 0, yCoordinate); canvas.toBlob(function(blob) { while (imageContainer.firstChild) { imageContainer.removeChild(imageContainer.firstChild); } const image = document.createElement('img'); const reader = new FileReader(); reader.onloadend = function() { image.src = reader.result; }; reader.readAsDataURL(blob); imageContainer.appendChild(image); }, 'image/png'); }); </script> __________________________ Font test: ---------------------------------------------------- <style type="text/css"> @font-face { font-family: "My Custom Font"; src: url(https://alceawis.de/other/extra/font/GreatVibes-Regular.otf) format("truetype"); } p.customfont { font-family: "My Custom Font", Verdana, Tahoma; } .drag-drop-area { border: 2px dashed #ccc; padding: 20px; text-align: center; font-size: 18px; cursor: pointer; } </style> </head> <body> <p id="customFont" class="customfont" contenteditable="true">Hello world!</p> <input type="range" id="fontSizeSlider" min="10" max="50" value="16" step="2" onchange="changeFontSize()"> <style id="customFontFace"></style> <script> function changeFontSource(fontUrl) { var fontFace = document.getElementById("customFontFace"); fontFace.innerHTML = "@font-face { font-family: 'My Custom Font'; src: url(" + fontUrl + ") format('truetype'); }"; } function changeFontSize() { var fontSize = document.getElementById("fontSizeSlider").value; var customFont = document.getElementById("customFont"); customFont.style.fontSize = fontSize + "px"; } function handleDragOver(event) { event.preventDefault(); event.stopPropagation(); event.dataTransfer.dropEffect = 'copy'; } function handleDrop(event) { event.preventDefault(); event.stopPropagation(); var file = event.dataTransfer.files[0]; var fontUrl = URL.createObjectURL(file); changeFontSource(fontUrl); } function handleUrlInput() { var fontUrl = document.getElementById("fontUrlInput").value; changeFontSource(fontUrl); } function handleFileBrowse(event) { var file = event.target.files[0]; var fontUrl = URL.createObjectURL(file); changeFontSource(fontUrl); } // Function to handle the font selection from the dropdown function handleFontSelection() { var dropdown = document.getElementById("fontDropdown"); var selectedOption = dropdown.options[dropdown.selectedIndex]; var fontUrl = selectedOption.value; var pretext = selectedOption.getAttribute("pretext"); var softkeyboardUrl = selectedOption.getAttribute("softkeyboard"); if (pretext) { var customFont = document.getElementById("customFont"); customFont.innerText = pretext; } if (softkeyboardUrl) { fetchSoftkeyboardContent(softkeyboardUrl); } changeFontSource(fontUrl); } function fetchSoftkeyboardContent(url) { fetch(url) .then(function (response) { return response.text(); }) .then(function (data) { var softkeyboardDiv = document.getElementById("softkeyboardDiv"); softkeyboardDiv.innerHTML = data; }) .catch(function (error) { console.log('Error fetching softkeyboard content:', error); }); } </script> <select id="fontDropdown" onchange="handleFontSelection()"> <option value="other/extra/font/GreatVibes-Regular.otf" pretext="Great Vibes !">Great Vibes</option> <option value="other/extra/font/learning_curve_regular_ot_ps.otf" pretext="Learning Curves">Learning Curve</option> <option value="other/extra/font/learning_curve_regular_ot_ps.otf" pretext="">--other--</option> <option value="other/extra/font/others/heo.ttf" pretext="󱴻󱳍󱳳󱴰" softkeyboard="other/extra/font/0softkbs/heo.html">Heo</option> </select> <hr> <div class="drag-drop-area" ondragover="handleDragOver(event)" ondrop="handleDrop(event)"> <p>Drag and drop a font file here</p> </div> <label for="fontUrlInput">or enter a URL to a font:</label> <input type="text" id="fontUrlInput" onchange="handleUrlInput()"> <!-- Add file browse input element --> <input type="file" id="fileBrowseInput" style="display: none;" onchange="handleFileBrowse(event)"> <br>or pick a local file: <button onclick="document.getElementById('fileBrowseInput').click()">Browse</button> <div id="softkeyboardDiv"></div> <!---Save_to image ---> <button id="captureButton">*Cheese!!*</button> <div id="imageContainer"></div> <script> const captureButton = document.getElementById('captureButton'); const imageContainer = document.getElementById('imageContainer'); captureButton.addEventListener('click', function() { const paragraph = document.getElementById('customFont'); let content = paragraph.innerHTML; content = content.replace(/<\/div>/g, ' ').replace(/<div>/g, ' '); const paragraphComputedStyle = getComputedStyle(paragraph); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.width = parseFloat(paragraphComputedStyle.width); canvas.height = parseFloat(paragraphComputedStyle.height) + 50; const computedStyle = getComputedStyle(paragraph); context.font = computedStyle.font; context.fillStyle = computedStyle.color; const textMetrics = context.measureText(content); const textHeight = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent; const yCoordinate = (canvas.height - textHeight) / 2 + textMetrics.actualBoundingBoxAscent; context.fillText(content, 0, yCoordinate); canvas.toBlob(function(blob) { while (imageContainer.firstChild) { imageContainer.removeChild(imageContainer.firstChild); } const image = document.createElement('img'); const reader = new FileReader(); reader.onloadend = function() { image.src = reader.result; }; reader.readAsDataURL(blob); imageContainer.appendChild(image); }, 'image/png'); }); </script> ________________________________________ Save current page to html -------------------------- <unique> <button onclick="savePageAsHTML()">Save Page as HTML</button> <script> function savePageAsHTML() { var placeholder = document.querySelector('unique'); placeholder.parentNode.removeChild(placeholder); var html = document.documentElement.outerHTML; var filename = 'dyingfedi.html'; var element = document.createElement('a'); element.setAttribute('href', 'data:text/html;charset=utf-8,' + encodeURIComponent(html)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); document.body.appendChild(placeholder); } </script> </unique> ______________ FolderLister (spoilertag) with download as html function ----------------------------- <!DOCTYPE html> <html> <head> <style> .folder { margin-left: 20px; } .empty-folder { color: gray; } </style> </head> <body> <h1>Drag and Drop Folder Lister</h1> <div> <label for="baseUrl">Base URL: </label> <input type="text" id="baseUrl" placeholder="Enter base URL" value="http://10.10.10.254/data/UsbDisk1/Volume1"> </div> <div> <label for="excludeText">Exclude Text: </label> <input type="text" id="excludeText" placeholder="Enter text to exclude" value="D_drive/"> </div> <div id="folderList" ondragover="allowDrop(event)" ondrop="drop(event)"> <p>Drop a folder here to list its contents:</p> </div> <script> function allowDrop(event) { event.preventDefault(); } function drop(event) { event.preventDefault(); const folder = event.dataTransfer.items[0].webkitGetAsEntry(); const baseUrl = document.getElementById("baseUrl").value; const excludeText = document.getElementById("excludeText").value; listFolderContents(folder, document.getElementById("folderList"), baseUrl, excludeText); } function listFolderContents(folder, parentElement, baseUrl, excludeText) { if (folder.isDirectory) { const details = document.createElement("details"); const summary = document.createElement("summary"); summary.textContent = folder.name; details.appendChild(summary); const subFolderList = document.createElement("ul"); details.appendChild(subFolderList); parentElement.appendChild(details); const directoryReader = folder.createReader(); function readEntries() { directoryReader.readEntries(function (entries) { if (entries.length > 0) { entries.forEach(function (entry) { listFolderContents(entry, subFolderList, baseUrl, excludeText); }); readEntries(); // Continue reading entries in chunks } else if (subFolderList.childElementCount === 0) { details.classList.add("empty-folder"); // Apply the CSS class to the empty folder } }); } readEntries(); } else { const listItem = document.createElement("li"); const link = document.createElement("a"); link.textContent = folder.name; link.target = "_blank"; link.href = baseUrl ? baseUrl + folder.fullPath : "file:///123/" + folder.fullPath; if (excludeText && link.href.includes(excludeText)) { link.href = link.href.replace(excludeText, ""); } listItem.appendChild(link); parentElement.appendChild(listItem); } } </script> <!--Download--> <script> function downloadHTML() { const folderList = document.getElementById("folderList").cloneNode(true); const baseUrl = document.getElementById("baseUrl").value; updateHyperlinks(folderList, baseUrl); const doctype = document.implementation.createDocumentType('html', '', ''); const html = document.implementation.createDocument('', 'html', doctype); html.documentElement.appendChild(folderList); const serializer = new XMLSerializer(); const htmlString = serializer.serializeToString(html); const blob = new Blob([htmlString], { type: 'text/html' }); const url = URL.createObjectURL(blob); const downloadLink = document.createElement('a'); downloadLink.href = url; downloadLink.download = 'folder_list.html'; downloadLink.click(); } function updateHyperlinks(element, baseUrl) { const links = element.getElementsByTagName('a'); for (let i = 0; i < links.length; i++) { const link = links[i]; const href = link.getAttribute('href'); if (baseUrl) { link.href = href; } else { link.href = href; } } } </script> <button onclick="downloadHTML()">Download HTML</button> ____________________________ Github commits per day chart ------------------------------------ <!DOCTYPE html> <html> <head> <title>Commit Chart</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head> <body> <div> <label for="apiUrl">API URL:</label> <input type="text" id="apiUrl" value="https://api.github.com/repos/Ry3yr/ry3yr.github.io/commits?per_page=50"> <button id="loadButton" onclick="loadChart()">Load Chart</button> </div> <div id="repoName"></div> <canvas id="commitChart"></canvas> <script> let chart; function loadChart() { if (chart) { chart.destroy(); // Clear the existing chart } const apiUrl = document.getElementById('apiUrl').value; fetch(apiUrl) .then(response => response.json()) .then(data => { // Extract commit dates const commitDates = data.map(commit => new Date(commit.commit.author.date).toLocaleDateString()); // Count commits per day const commitCounts = {}; commitDates.forEach(date => { commitCounts[date] = (commitCounts[date] || 0) + 1; }); // Prepare data for the chart const labels = Object.keys(commitCounts); const values = Object.values(commitCounts); // Get the repository name from the API URL const repoName = getRepoName(apiUrl); // Display the repository name document.getElementById('repoName').textContent = `Repository: ${repoName}`; // Create the chart const ctx = document.getElementById('commitChart').getContext('2d'); chart = new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: 'Commits per Day', data: values, backgroundColor: 'rgba(54, 162, 235, 0.6)' }] }, options: { scales: { x: { display: true, reverse: true, // Invert the x-axis title: { display: true, text: 'Date' } }, y: { beginAtZero: true, stepSize: 1 } } } }); }); } // Extract the repository name from the API URL function getRepoName(apiUrl) { const parts = apiUrl.split('/'); return parts[parts.length - 3] + '/' + parts[parts.length - 2]; } // Load chart with default API URL loadChart(); </script> ______________________ URL-online-check-and-fallback-redirect ------------------------------------------------------------ <a href="#" id="checkURLLink">Check URL</a> <div id="result"></div> <script> document.getElementById('checkURLLink').addEventListener('click', function() { var url = "https://alcea-wisteria.de/PHP/0demo/2023-08-15-JSFiddle-Clone/htmls/0ld/link.html"; //var url = "https://alcea-wisteria.de/PHP/0demo/2023-08-15-JSFiddle-Clone/jsfiddle-user.ph?mode=lowbandwidth" var xhr = new XMLHttpRequest(); xhr.open('HEAD', url); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { const corsStatus = xhr.getResponseHeader('Access-Control-Allow-Origin'); const headers = xhr.getAllResponseHeaders(); const result = document.getElementById('result'); if (corsStatus || headers.includes('content-length') || headers.includes('content-type') || headers.includes('last-modified')) { result.innerHTML = '<strong>Accessing original URL</strong><br>'; result.innerHTML += 'Access-Control-Allow-Origin: ' + corsStatus + '<br>'; window.open(url, "_blank"); } else { result.innerHTML = '<strong>Accessing static backup</strong><br>'; result.innerHTML += 'Response Headers:<br>' + headers + '<br>'; var alternativeUrl = "https://ry3yr.github.io/jsfiddleclone/jsfiddle-user.html?mode=lowbandwidth"; window.open(alternativeUrl, "_blank"); } } }; xhr.send(); }); </script> ________________________________ Set favico --------- <script> var link = document.createElement('link'); link.rel = 'icon'; link.type = 'image/gif'; link.href = 'https://alceawis.de/other/images/bg/chocoomnomnom32.gif'; document.head.appendChild(link); </script> _____________________ Txt and Folder lister with Reorder capability (can generate cmd too) --------------------------------------------------------------------- <!DOCTYPE html> <html> <head> <title>Draggable Grid Example</title> <style> .grid-container { display: grid; grid-template-columns: repeat(2, 1fr); grid-gap: 10px; } .grid-item { padding: 10px; border: 1px solid #ccc; background-color: #f9f9f9; cursor: grab; } .grid-item.dragging { opacity: .5; cursor: grabbing; } </style> </head> <body> <div class="grid-container"> <div id="folder-grid" class="grid-item"> <h3>Folder Files</h3> </div> <div id="text-grid" class="grid-item"> <h3>Text Lines</h3> </div> </div> <script> let folderGrid = null; let textGrid = null; let clickedItem = null; function createGridItem(content, draggable = true) { const gridItem = document.createElement("div"); gridItem.className = "grid-item"; gridItem.textContent = content; if (draggable) { gridItem.draggable = true; gridItem.addEventListener("dragstart", function (event) { event.dataTransfer.setData("text/plain", content); gridItem.classList.add("dragging"); }); gridItem.addEventListener("dragend", function () { gridItem.classList.remove("dragging"); }); } gridItem.addEventListener("click", function () { if (clickedItem === null) { clickedItem = gridItem; gridItem.classList.add("dragging"); } else { swapItems(clickedItem, gridItem); clickedItem = null; gridItem.classList.remove("dragging"); } }); return gridItem; } function addFileToList(file) { if (file.isDirectory) { return; // Exclude directories from the list } const fileName = file.name; const isHeaderPresent = folderGrid.querySelector("h3"); const gridItem = createGridItem(fileName); if (isHeaderPresent) { folderGrid.appendChild(gridItem); } else { const header = document.createElement("h3"); header.textContent = "Folder Files"; folderGrid.appendChild(header); folderGrid.appendChild(gridItem); } } function addLineToList(line) { const gridItem = createGridItem(line.trim(), false); textGrid.appendChild(gridItem); } function readTextFile(file) { const reader = new FileReader(); reader.onload = function (event) { const lines = event.target.result.split("\n"); lines.forEach((line) => { addLineToList(line); }); }; reader.readAsText(file); } function traverseDirectory(directory) { const directoryReader = directory.createReader(); directoryReader.readEntries(function (entries) { entries.forEach((entry) => { if (entry.isFile) { entry.file(function (file) { if (file.type === "text/plain") { readTextFile(file); } else { addFileToList(file); } }); } else if (entry.isDirectory) { addFileToList(entry); traverseDirectory(entry); } }); }); } function swapItems(item1, item2) { const tempContent = item1.textContent; item1.textContent = item2.textContent; item2.textContent = tempContent; } window.addEventListener("DOMContentLoaded", function () { folderGrid = document.getElementById("folder-grid"); textGrid = document.getElementById("text-grid"); folderGrid.addEventListener("dragover", function (event) { event.preventDefault(); }); folderGrid.addEventListener("drop", function (event) { event.preventDefault(); const isHeaderPresent = folderGrid.querySelector("h3"); if (!isHeaderPresent) { const header = document.createElement("h3"); header.textContent = "Folder Files"; folderGrid.appendChild(header); } const items = event.dataTransfer.items; for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.kind === "file") { const entry = item.webkitGetAsEntry(); if (entry) { if (entry.isDirectory) { addFileToList(entry); traverseDirectory(entry); } else if (entry.isFile) { entry.file(function (file) { if (file.type === "text/plain") { readTextFile(file); } else { addFileToList(file); } }); } } } } }); textGrid.addEventListener("dragover", function (event) { event.preventDefault(); }); textGrid.addEventListener("drop", function (event) { event.preventDefault(); const items = event.dataTransfer.items; for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.kind === "file") { const entry = item.webkitGetAsEntry(); if (entry && entry.isFile) { entry.file(function (file) { if (file.type === "text/plain") { readTextFile(file); } }); } } } }); }); </script> </body> <label for="insert-text">Insert Text:</label> <input type="text" id="insert-text" placeholder="Insert Text" /> <br /> <label for="placeholder-text">Placeholder Text:</label> <input type="text" id="placeholder-text" placeholder="Placeholder Text" /> <br /> <label for="after-text">After Text:</label> <input type="text" id="after-text" placeholder="After Text" /> <br /> <button id="copy-button">Copy to Clipboard</button> <script> document.getElementById("copy-button").addEventListener("click", function () { const folderGridLines = Array.from(document.getElementById("folder-grid").querySelectorAll("div")).map(line => line.innerText); const textGridLines = Array.from(document.getElementById("text-grid").querySelectorAll("div")).map(line => line.innerText); const insertText = document.getElementById("insert-text").value || "Insert text"; const placeholderText = document.getElementById("placeholder-text").value || "placeholder"; const afterText = document.getElementById("after-text").value; const combinedContent = folderGridLines.map((line, index) => `${insertText} "${line}" ${placeholderText} "${textGridLines[index]}"${afterText ? " " + afterText : ""}`).join('\n'); const tempTextarea = document.createElement("textarea"); tempTextarea.value = combinedContent; document.body.appendChild(tempTextarea); tempTextarea.select(); document.execCommand("copy"); document.body.removeChild(tempTextarea); //alert("Content copied to clipboard!"); }); </script> </body> </html> ________________ Line-Keeper--Remover-add-leading-0s ----------------------------------- <body> <textarea id="linesToFilter" rows="10" cols="50"></textarea> <br> <input type="text" id="keyword" placeholder="Enter keyword" /> <button id="filterButton">Filter Lines</button> <br> <input type="text" id="eraseTextbox" placeholder="Enter phrase to erase" /> <button id="eraseButton">Erase</button> <br> <button id="leadingNumbersButton">Leading Numbers</button> <input type="checkbox" id="leadingZeroCheckbox" /> <label for="leadingZeroCheckbox">Leading Zero</label> <script> const linesToFilterInput = document.getElementById('linesToFilter'); const keywordInput = document.getElementById('keyword'); const filterButton = document.getElementById('filterButton'); const mainTextbox = document.getElementById('linesToFilter'); const eraseTextbox = document.getElementById('eraseTextbox'); const eraseButton = document.getElementById('eraseButton'); const leadingNumbersButton = document.getElementById('leadingNumbersButton'); const leadingZeroCheckbox = document.getElementById('leadingZeroCheckbox'); filterButton.addEventListener('click', function() { const linesToFilter = linesToFilterInput.value.split('\n'); const keyword = keywordInput.value; const filteredLines = linesToFilter.filter(line => line.includes(keyword)); const newContent = filteredLines.join('\n'); linesToFilterInput.value = newContent; }); eraseButton.addEventListener('click', function() { const phraseToErase = eraseTextbox.value; const content = mainTextbox.value; const newContent = content.replace(new RegExp(phraseToErase, 'g'), ''); mainTextbox.value = newContent; }); leadingNumbersButton.addEventListener('click', function() { const linesToNumber = mainTextbox.value.split('\n'); const useLeadingZero = leadingZeroCheckbox.checked; let numberedLines = ''; for (let i = 0; i < linesToNumber.length; i++) { let lineNumber = i + 1; if (useLeadingZero) { lineNumber = lineNumber.toString().padStart(linesToNumber.length.toString().length, '0'); } numberedLines += `${lineNumber}. ${linesToNumber[i]}\n`; } mainTextbox.value = numberedLines; }); </script> </body> </html> _______________________________ Fetch Youtube Playlist + duration (&reorder cap) -------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>YouTube Playlist Videos</title> <style> table {border-collapse: collapse; width: 100%} td, th {text-align: left; padding: 8px; border-bottom: 1px solid #ddd} th {background-color: #f2f2f2} </style> </head> <body> <form id="playlistForm"> <label for="playlistInput">YouTube Playlist URL:</label> <input type="text" id="playlistInput" required><br> <label for="apiKeyInput">YouTube API Key:</label> <input type="text" id="apiKeyInput" required><br> <input type="checkbox" id="sortCheckbox"> <label for="sortCheckbox">Sort by playlist order</label><br> <input type="submit" value="Get Video List"> </form> <table id="videoTable"> <thead> <tr> <th>Video Name</th> <th>Playtime</th> <th>Date</th> <th>Exact Time of Upload</th> </tr> </thead> <tbody id="videoList"></tbody> </table> <script> function convertDuration(duration) { var match = duration.match(/PT(\d+H)?(\d+M)?(\d+S)?/); var hours = (parseInt(match[1]) || 0); var minutes = (parseInt(match[2]) || 0); var seconds = (parseInt(match[3]) || 0); return hours + "h " + minutes + "m " + seconds + "s"; } function convertDate(date) { var isoDate = new Date(date); return isoDate.toDateString(); } function convertExactTime(date) { var isoDate = new Date(date); var year = isoDate.getFullYear().toString().padStart(4, '0'); var month = (isoDate.getMonth() + 1).toString().padStart(2, '0'); var day = isoDate.getDate().toString().padStart(2, '0'); var hours = isoDate.getHours().toString().padStart(2, '0'); var minutes = isoDate.getMinutes().toString().padStart(2, '0'); return year + month + day + '' + hours + '' + minutes; } document.getElementById("playlistForm").addEventListener("submit", function (event) { event.preventDefault(); var playlistUrl = document.getElementById("playlistInput").value; var apiKey = document.getElementById("apiKeyInput").value; var playlistId = playlistUrl.match(/(?:list=)([\w-]+)/)[1]; var apiUrl = "https://www.googleapis.com/youtube/v3/playlistItems"; var params = { part: "snippet,contentDetails", maxResults: 150, playlistId: playlistId, key: apiKey, }; var url = apiUrl + "?" + Object.keys(params) .map(function (key) { return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); }) .join("&"); var videoList = document.getElementById("videoList"); videoList.innerHTML = ""; var videoItems = []; var totalDuration = 0; // Variable to store the total playtime var fetchPromises = []; // Array to store fetch promises var pageToken = ""; // Pagination token function fetchVideos(pageToken) { var url = "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet%2CcontentDetails&playlistId=" + playlistId + "&maxResults=50&key=" + apiKey + "&pageToken=" + pageToken; fetch(url) .then((response) => response.json()) .then((data) => { var nextPageToken = data.nextPageToken; // Get pagination token for the next page data.items.forEach(function (item, index) { var videoId = item.contentDetails.videoId; var videoTitle = item.snippet.title; var vId = item.contentDetails.videoId; var durUrl = "https://www.googleapis.com/youtube/v3/videos?part=contentDetails&id=" + vId + "&key=" + apiKey; var fetchPromise = fetch(durUrl) .then((response) => response.json()) .then((durationData) => { var duration = durationData.items[0].contentDetails.duration; var videoDuration = convertDuration(duration); var videoDate = item.snippet.publishedAt; var videoDateFormatted = convertDate(videoDate); var videoExactTime = convertExactTime(videoDate); videoItems.push({ videoId: videoId, videoTitle: videoTitle, videoDuration: videoDuration, videoDate: videoDateFormatted, videoExactTime: videoExactTime, playlistIndex: index, }); totalDuration += getDurationInSeconds(duration); }) .catch((error) => { console.log(error); // Handle the error, e.g., display an error message for this video }); fetchPromises.push(fetchPromise); }); if (nextPageToken) { // Fetch the next page if there is a pagination token fetchVideos(nextPageToken); } else { // All pages have been fetched, proceed with processing the data Promise.all(fetchPromises) .then(() => { if (document.getElementById("sortCheckbox").checked) { // Sort by playlist order videoItems.sort((a, b) => a.playlistIndex - b.playlistIndex); } else { // Sort by date videoItems.sort( (a, b) => new Date(a.videoDate) - new Date(b.videoDate) ); } videoItems.forEach(function (video) { var videoRow = document.createElement("tr"); var videoNameCell = document.createElement("td"); var videoNameLink = document.createElement("a"); videoNameLink.href = "https://www.youtube.com/watch?v=" + video.videoId; videoNameLink.target = "_blank"; videoNameLink.textContent = video.videoTitle; videoNameCell.appendChild(videoNameLink); videoRow.appendChild(videoNameCell); var playtimeCell = document.createElement("td"); playtimeCell.textContent = video.videoDuration; videoRow.appendChild(playtimeCell); var dateCell = document.createElement("td"); dateCell.textContent = video.videoDate; videoRow.appendChild(dateCell); var exactTimeCell = document.createElement("td"); exactTimeCell.textContent = video.videoExactTime; videoRow.appendChild(exactTimeCell); videoList.appendChild(videoRow); }); var totalDurationRow = document.createElement("tr"); var totalDurationCell = document.createElement("td"); totalDurationCell.setAttribute("colspan", "4"); totalDurationCell.innerHTML = "Total Playtime: " + convertSecondsToDuration(totalDuration) + " <a href='" + playlistUrl + "' target='_blank'>" + playlistUrl + "</a>"; totalDurationRow.appendChild(totalDurationCell); videoList.appendChild(totalDurationRow); }) .catch((error) => { console.log(error); var videoTable = document.getElementById("videoTable"); videoTable.innerHTML = "An error occurred while fetching the video list."; }); } }) .catch((error) => { console.log(error); var videoTable = document.getElementById("videoTable"); videoTable.innerHTML = "An error occurred while fetching the video list."; }); } fetchVideos(pageToken); function getDurationInSeconds(duration) { var match = duration.match(/PT(\d+H)?(\d+M)?(\d+S)?/); var hours = parseInt(match[1]) || 0; var minutes = parseInt(match[2]) || 0; var seconds = parseInt(match[3]) || 0; return hours * 3600 + minutes * 60 + seconds; } function convertSecondsToDuration(seconds) { var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds % 3600) / 60); var secs = seconds % 60; return ( hours.toString().padStart(2, "0") + "h " + minutes.toString().padStart(2, "0") + "m " + secs.toString().padStart(2, "0") + "s" ); } }); function copyTableToClipboard() { var table = document.getElementById("videoTable"); var tableHtml = table.outerHTML; var tempElement = document.createElement("textarea"); tempElement.value = tableHtml; document.body.appendChild(tempElement); tempElement.select(); document.execCommand("copy"); document.body.removeChild(tempElement); alert("Table copied to clipboard as HTML!"); } var copyButton = document.createElement("button"); copyButton.textContent = "Copy Table"; copyButton.addEventListener("click", copyTableToClipboard); document.body.appendChild(copyButton); function reorderRowsByDate() { var table = document.getElementById("videoTable"); var tbody = table.querySelector("#videoList"); var rows = Array.from(tbody.getElementsByTagName("tr")); var tableData = rows.map(function (row) { return Array.from(row.getElementsByTagName("td")).map(function (cell) { if (cell.hasChildNodes() && cell.firstChild.nodeName === "A") { return cell.innerHTML; // Preserve link structure } else { return cell.textContent; } }); }); tableData.sort(function (a, b) { var dateA = new Date(a[2]); var dateB = new Date(b[2]); return dateA - dateB; }); for (var i = 0; i < rows.length; i++) { var cells = rows[i].getElementsByTagName("td"); for (var j = 0; j < cells.length; j++) { // Restore link structure if necessary if (tableData[i][j].includes("<a href=")) { cells[j].innerHTML = tableData[i][j]; } else { cells[j].textContent = tableData[i][j]; } } } } var reorderButton = document.createElement("button"); reorderButton.textContent = "Reorder by Date"; reorderButton.addEventListener("click", reorderRowsByDate); document.body.appendChild(reorderButton); </script> <button onclick="reorderRowsByVideoName()">Reorder by Video Name</button> <script> function reorderRowsByVideoName() { var table = document.getElementById("videoTable"); var tbody = table.querySelector("#videoList"); var rows = Array.from(tbody.getElementsByTagName("tr")); var tableData = rows.map(function(row) { return Array.from(row.getElementsByTagName("td")).map(function(cell) { if (cell.hasChildNodes() && cell.firstChild.nodeName === "A") { return cell.innerHTML; } else { return cell.textContent; } }); }); tableData.sort(function(a, b) { var nameA = a[0].toLowerCase(); var nameB = b[0].toLowerCase(); if (nameA < nameB) return -1; if (nameA > nameB) return 1; return 0; }); for (var i = 0; i < rows.length; i++) { var cells = rows[i].getElementsByTagName("td"); for (var j = 0; j < cells.length; j++) { if (tableData[i][j].includes("<a href=")) { cells[j].innerHTML = tableData[i][j]; } else { cells[j].textContent = tableData[i][j]; } } } } </script> <button onclick="reorderRowsByUploadTime()">Reorder by Exact Time of Upload</button> <script> function reorderRowsByUploadTime() { var table = document.getElementById("videoTable"); var tbody = table.querySelector("#videoList"); var rows = Array.from(tbody.getElementsByTagName("tr")); // Extract the rows and their corresponding timestamps var tableData = rows.map(function(row) { // Assuming the timestamp is in the last cell of each row var cells = row.getElementsByTagName("td"); var timestamp = cells[cells.length - 1].textContent.trim(); return { row: row, time: parseInt(timestamp) }; }); // Sort the rows by the timestamp tableData.sort(function(a, b) { return a.time - b.time; }); // Reorder the rows in the table body tableData.forEach(function(data) { tbody.appendChild(data.row); }); } </script> <!---https://stackoverflow.com/questions/15596753/how-do-i-get-video-durations-with-youtube-api-version-3---> _______________________________ Open details tag (id) if keyword exists in url ------------------------------------------------------------------- <script> window.addEventListener("DOMContentLoaded", function() { var url = window.location.href; var keyword = "instance"; if (url.indexOf(keyword) !== -1) { var detailsElement = document.getElementById("mtd"); detailsElement.setAttribute("open", "true"); } }); </script> <details id="mtd"></details> ____________________________________________ Limit link to header <h1> --------------------- <span style="display: block; text-align: center;"><a href="Hobbies_old.html" style="color: #454138;"><h1 style="display: inline;">Hobbies</h1></a></span> ______________________ Sneaky Random youtube playlistvideo autoplay er --------------------------------------------------------------- <div id="videoTitle"></div> <script> const urlParams = new URLSearchParams(window.location.search); const apiKey = urlParams.get('apikey') || 'YOUR_DEFAULT_API_KEY'; const playlistId = urlParams.get('playlistid') || 'PLdR7m7PFLzQ7RqOVfxk2Fr2F-a7iWzovn'; function fetchRandomVideo() { fetch(`https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=${playlistId}&key=${apiKey}`) .then(response => response.json()) .then(data => { const randomIndex = Math.floor(Math.random() * data.items.length); const video = data.items[randomIndex].snippet; const videoId = video.resourceId.videoId; const iframe = document.createElement('iframe'); iframe.width = '100'; iframe.height = '20'; iframe.src = `https://super8.absturztau.be/embed/${videoId}?si=playlist=${playlistId}&autoplay=1`; iframe.title = 'YouTube video player'; iframe.frameBorder = '0'; iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'; iframe.allowFullscreen = true; document.body.appendChild(iframe); const videoTitle = video.title; const titleElement = document.getElementById('videoTitle'); titleElement.textContent = videoTitle; }) .catch(error => console.log('Error:', error)); } fetchRandomVideo(); </script> </body> ______________________________ Redirect current url/page querystrings to other url -------------------------------------------------- <a href="#" onclick="redirectToTargetURL();">Click here to redirect querystrings</a> <script> function redirectToTargetURL() { var currentURL = window.location.href; var targetURL = "https://alceawis.de/other/extra/scripts/z_teststuff/mtdnotifications.php"; var queryParams = currentURL.split("?")[1]; var finalURL = targetURL + "?" + queryParams; window.location.href = finalURL; } </script> ___________________________________ Display multiuser mtd tl in tableview ----------------------------------------- <style> table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid black; padding: 2px; text-align: left; } th { background-color: #f2f2f2; } </style> </head> <body> <table> <thead> <tr> <th>User ID</th> <th>Posts</th> </tr> </thead> <tbody id="results"></tbody> </table> <script> // Fetch the contents of the mtdusers.txt file fetch('https://ry3yr.github.io/mtduserspublic.txt') .then(response => response.text()) .then(data => { // Split the data into lines const lines = data.split('\n'); // Iterate over each line lines.forEach(line => { // Extract the user ID and instance URL const [userid, instanceurl] = line.split(' '); // Construct the API URL const apiUrl = `${instanceurl}/api/v1/accounts/${userid}/statuses`; // Check if the URL contains the 'limit' parameter const urlParams = new URLSearchParams(window.location.search); const limitParam = urlParams.get('limit'); const limit = limitParam ? parseInt(limitParam) : 1; // Fetch posts using the Mastodon API fetch(`${apiUrl}?limit=${limit}`) .then(response => response.json()) .then(data => { // Check if there are any posts if (data.length > 0) { // Create a new row in the table for the user const tableRow = document.createElement('tr'); // User ID cell const userIdCell = document.createElement('td'); fetch(`${instanceurl}/api/v1/accounts/${userid}`) .then(response => response.json()) .then(data => { const username = data.username; const userpfp = data.avatar_static; const userLink = document.createElement('a'); userLink.href = `${instanceurl}/@${username}`; userLink.target = '_blank'; userLink.innerHTML = `<img src="${userpfp}" alt="${username}" width="128px"> ${username}`; userIdCell.appendChild(userLink); }) .catch(error => { console.log('Error fetching user data for User ID:', userid); console.error(error); }); tableRow.appendChild(userIdCell); // Posts cell const postsCell = document.createElement('td'); // Iterate over each post data.forEach(async post => { const postContent = await replaceEmojis(post.content, post.emojis); const linkUrl = `${instanceurl}/web/statuses/${post.id}`; const timestamp = new Date(post.created_at).toLocaleString(); // Create a separate cell for each post const postCell = document.createElement('td'); postCell.innerHTML = `${postContent}<br><a href="${linkUrl}" target="_blank">View</a> ${timestamp}`; postsCell.appendChild(postCell); }); // Add the posts cell to the table row tableRow.appendChild(postsCell); // Append the row to the table body const tableBody = document.getElementById('results'); tableBody.appendChild(tableRow); } else { console.log('No posts found for User ID:', userid); } }) .catch(error => { console.log('Error fetching posts for User ID:', userid); console.error(error); }); }); }) .catch(error => { console.log('Error:', error); }); // Function to replace emoji codes with images async function replaceEmojis(content, emojis) { for (const emoji of emojis) { const shortcode = emoji.shortcode; const url = emoji.url; const shortcodePattern = new RegExp(':' + shortcode + ':', 'g'); const emojiTag = `<img src="${url}" alt="${shortcode}" width="20px">`; content = content.replace(shortcodePattern, emojiTag); } return content; } </script> </body> </html> ____________________________ Navbar with hamburger menu toggle ----------------------------------------- <style> .navbar{list-style-type:none;margin:0;padding:0;background-color:#fff;float:right;margin-right:120px}.navbar li{display:inline-block;position:relative}.navbar a{display:block;color:#000;text-decoration:none;padding:8px}.navbar a:hover{background-color:#f5f}.submenu{display:none;position:absolute;background-color:#fff;left:-45px;top:100%;width:200px}.navbar li:hover .submenu{display:block}.submenu li{display:block;color:#000}.navbar .active .submenu{top:0} </style> <script> function toggleNavbar() { var navbar = document.getElementById('navbar'); navbar.style.display = (navbar.style.display === 'none' || navbar.style.display === '') ? 'block' : 'none'; } </script> <script> function toggleLayout() { var extraLink = document.getElementById('extraLink'); var navbar = document.querySelector('.navbar'); var submenus = document.querySelectorAll('.submenu'); var checkbox = document.getElementById('layoutCheckbox'); if (checkbox.checked) { extraLink.style.display = 'inline'; navbar.style.position = 'absolute'; navbar.style.left = '0'; navbar.style.top = '0'; navbar.style.margin = '0'; navbar.style.width = '120px'; navbar.style.height = '100vh'; navbar.style.display = 'flex'; navbar.style.flexDirection = 'column'; submenus.forEach(function(submenu) {submenu.style.left = '100%'; submenu.style.top = '0';}); } else { //extraLink.style.display = 'none'; navbar.style.float = 'right'; navbar.style.marginRight = '120px'; navbar.style.marginLeft = '0';}} </script> <a href="#" id="extraLink" style="position: fixed; right: 130px; top: 0;display:none;" onclick="toggleNavbar(event)">[-]</a> <nav> <ul class="navbar" id="navbar"> <li class="u-nav-item"><a class="u-button-style u-nav-link u-text-active-palette-1-base u-text-hover-palette-2-base" href="index.html">Homepage</a></li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-text-active-palette-1-base u-text-hover-palette-2-base" href="About.html?user=alcea">About</a></li> <li class="u-nav-item"> <a class="u-button-style u-nav-link u-text-active-palette-1-base u-text-hover-palette-2-base" href="Contact.html">Contact</a> <div class="u-nav-popup submenu"> <ul class="u-h-spacing-5 u-nav u-unstyled u-v-spacing-5 u-nav-2"> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="https://www.abcsubmit.com/view/id_1f8339emu_jn1?utm=abcsubmit" target="_blank">Mail [📬]</a></li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="verify.html#mastodon" target="_blank">Mastodon [📬]</a></li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="verify.html#twitter" target="_blank">Twitter [📬]</a></li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="https://reddit.com/user/repeekyraid#https://www.reddit.com/user/-Rye-/" target="_blank">Reddit [📬]</a></li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="Comments.html">Comments</a></li> </ul></div></li><li class="u-nav-item"> <a class="u-button-style u-nav-link u-text-active-palette-1-base u-text-hover-palette-2-base" href="Hobbies.html">Hobbies</a> <div class="u-nav-popup submenu"> <ul class="u-h-spacing-5 u-nav u-unstyled u-v-spacing-5 u-nav-3"> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="All_Project_%28spoiler_button%29.html">OSTRip </a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="pastebinlinks.html?note=FAQ" target=_blank>FAQ [🡕]</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="Art.html">Art</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="code.html">Code</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="Games.html">GamingStuff</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="Blog.html">Blog</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="favs.html">Favorites</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="https://ry3yr.github.io/OSTR/" target=_blank>OldSite [🡕]</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="UnderratedContent.html" target=_blank>Underrated [🡕]</a> </li> </ul></div></li><li class="u-nav-item"> <a class="u-button-style u-nav-link u-text-active-palette-1-base u-text-hover-palette-2-base" href="Music.html">Music</a> <div class="u-nav-popup submenu"><ul class=u-"nav-4"> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="Releases.html">Releases</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="Startseiteplayer.html">Musicplayer</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="midisheetmusic.html">Sheet Music (Midi)</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="Unresoundtracker.html">UnreSoundtracker</a> </li> <li class="u-nav-item"><a class="u-button-style u-nav-link u-white" href="radio.html">Radio/Shoutcast</a> </li> <li class="u-nav-item"><input type="checkbox" id="layoutCheckbox" onchange="toggleLayout()"></li> </ul></div></li></ul></nav> ________________________________________ Fetch pixiv link and display from random -------------------------------------- <!--for use with --https://codepen.io/ryedai1/pen/ZEPGdOx--> <button id="extractButton">Random pixiv !</button> <div id="artworkContainer"></div> <script> document.getElementById('extractButton').addEventListener('click', function() { // Fetch the HTML file fetch('https://ry3yr.github.io/pixiv-artworks-archive.html') .then(response => response.text()) .then(html => { // Create a temporary DOM element to parse the HTML const tempElement = document.createElement('div'); tempElement.innerHTML = html; const artworkLinks = tempElement.querySelectorAll('a[target=_blank]'); const randomIndex = Math.floor(Math.random() * artworkLinks.length); const randomArtworkLink = artworkLinks[randomIndex]; const linkHref = randomArtworkLink.href; const linkText = randomArtworkLink.textContent; const artworkId = linkHref.match(/\/(\d+)$/)[1]; const embedCode = `<div><img src="https://embed.pixiv.net/decorate.php?illust_id=${artworkId}&mode=sns-automator" width="50%"></div><br>`; const link = `<a href="${linkHref}" target="_blank">${linkText}</a>`; const artworkContainer = document.getElementById('artworkContainer'); artworkContainer.innerHTML = embedCode + link; }) .catch(error => { console.error('Error:', error); }); }); </script> _________________________________ FetchMastodon Notifications ----------------------- <b>?userid=111958546062297646&apikey=APIKEYHERE&instanceurl=https://mastodon.social</b> <br> <head> <title>Mastodon Notifications</title> <style> .notification { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; } .notification .account { font-weight: bold; } .notification .status { margin-top: 5px; } .notification .date { font-size: 0.8em; color: #888; cursor: pointer; text-decoration: underline; } .notification.reply { background-color: lightblue; } .notification.fav { background-color: pink; } </style> </head> <body> <div id="notifications"></div> <div id="error"></div> <script> // Get the query parameters from the URL const urlParams = new URLSearchParams(window.location.search); const apiKey = urlParams.get('apikey'); const userId = urlParams.get('userid'); const instanceUrl = urlParams.get('instanceurl'); // Create a new XMLHttpRequest object const xhr = new XMLHttpRequest(); // Define the API endpoint URL const apiUrl = `${instanceUrl}/api/v1/notifications`; // Set up the request xhr.open('GET', apiUrl); xhr.setRequestHeader('Authorization', `Bearer ${apiKey}`); // Define the onload callback function xhr.onload = function() { if (xhr.status === 200) { const notifications = JSON.parse(xhr.responseText); // Display the notifications const notificationsDiv = document.getElementById('notifications'); notifications.forEach(notification => { const notificationElement = createNotificationElement(notification); notificationsDiv.appendChild(notificationElement); }); } else { const errorDiv = document.getElementById('error'); errorDiv.textContent = 'Request failed. Status: ' + xhr.status; console.error('Request failed. Status:', xhr.status); } }; // Define the onerror callback function xhr.onerror = function() { const errorDiv = document.getElementById('error'); errorDiv.textContent = 'Request failed. Please check your network connection.'; console.error('Request failed. Please check your network connection.'); }; // Send the request xhr.send(); // Helper function to create a notification element function createNotificationElement(notification) { const notificationElement = document.createElement('div'); notificationElement.classList.add('notification'); const accountElement = document.createElement('div'); accountElement.classList.add('account'); accountElement.textContent = notification.account.display_name || notification.account.username; const statusElement = document.createElement('div'); statusElement.classList.add('status'); statusElement.innerHTML = notification.status.content; const dateElement = document.createElement('div'); dateElement.classList.add('date'); const date = new Date(notification.created_at); dateElement.textContent = date.toLocaleString(); dateElement.addEventListener('click', function() { window.open(notification.status.url, '_blank'); }); // Find all links in the status and set target="_blank" const links = statusElement.getElementsByTagName('a'); for (let i = 0; i < links.length; i++) { links[i].setAttribute('target', '_blank'); } if (notification.type === 'mention') { notificationElement.classList.add('reply'); } else if (notification.type === 'favourite') { notificationElement.classList.add('fav'); } notificationElement.appendChild(accountElement); notificationElement.appendChild(statusElement); notificationElement.appendChild(dateElement); return notificationElement; } </script> </body> </html> _______________________________ Replace Text in nearby Textfield: ------------------------------------------------- <button id="replaceButton">Replace</button> <script> function replaceText() { var textField = document.getElementById("outputTextarea"); var newText = textField.value.replace(/Alcea/gi, "arusea"); textField.value = newText; } document.getElementById("replaceButton").addEventListener("click", replaceText); </script> _______________________________ Escape characters inside var string (for form safe submittal) ---------------------------------------------------------------------- const value = document.createTextNode(textarea.value).nodeValue; //no emoji support //const valueatob = btoa(value); //convert to base64 for sending via querystring (optional for other things) (source: https://alcea-wisteria.de/PHP/0demo/2024-02-18-Fake-SocialMedia/post2mtd.html#https://alceawis.de/other/extra/scripts/fakesocialmedia/post2mtd.html) ________________________________________ Mastodon querystring poster -------------------------------------------- <div id="postInfo"></div> <script> const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const instanceurl = urlParams.get('instanceurl'); const user = urlParams.get('user'); const apikey = urlParams.get('apikey'); const encodedValue = urlParams.get('value'); const encoding = urlParams.get('encoding') || 'base64'; // Retrieve the encoding value or use 'base64' as default let decodedValue; if (encoding === 'base64') { decodedValue = atob(encodedValue); // Use base64 decoding if encoding is 'base64' } else { decodedValue = decodeURIComponent(encodedValue); // Use URI decoding for other encoding schemes } const apiUrl = `${instanceurl}`; //const apiUrl = `${instanceurl}/api/v1/statuses`; const userId = user; const apiKey = apikey; const postData = { status: decodedValue, }; const postInfoDiv = document.getElementById('postInfo'); fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, }, body: JSON.stringify(postData), }) .then(response => { if (!response.ok) { throw new Error('Error creating post: ' + response.status); } return response.json(); }) .then(data => { console.log('Post created successfully:', data); postInfoDiv.innerText = JSON.stringify(data); }) .catch(error => { console.error('Error creating post:', error); postInfoDiv.innerText = 'Error creating post: ' + error; }); </script> Old-Base64-only-ver: CiAgPGRpdiBpZD0icG9zdEluZm8iPjwvZGl2PgoKICA8c2NyaXB0PgogICAgY29uc3QgcXVlcnlTdHJpbmcgPSB3aW5kb3cubG9jYXRpb24uc2VhcmNoOwogICAgY29uc3QgdXJsUGFyYW1zID0gbmV3IFVSTFNlYXJjaFBhcmFtcyhxdWVyeVN0cmluZyk7CiAgICBjb25zdCBpbnN0YW5jZXVybCA9IHVybFBhcmFtcy5nZXQoJ2luc3RhbmNldXJsJyk7CiAgICBjb25zdCB1c2VyID0gdXJsUGFyYW1zLmdldCgndXNlcicpOwogICAgY29uc3QgYXBpa2V5ID0gdXJsUGFyYW1zLmdldCgnYXBpa2V5Jyk7CiAgICBjb25zdCBlbmNvZGVkVmFsdWUgPSB1cmxQYXJhbXMuZ2V0KCd2YWx1ZScpOwogICAgY29uc3QgZGVjb2RlZFZhbHVlID0gYXRvYihlbmNvZGVkVmFsdWUpOwogICAgY29uc3QgYXBpVXJsID0gYCR7aW5zdGFuY2V1cmx9YDsKICAgIC8vY29uc3QgYXBpVXJsID0gYCR7aW5zdGFuY2V1cmx9L2FwaS92MS9zdGF0dXNlc2A7CiAgICBjb25zdCB1c2VySWQgPSB1c2VyOwogICAgY29uc3QgYXBpS2V5ID0gYXBpa2V5OwogICAgY29uc3QgcG9zdERhdGEgPSB7CiAgICAgIHN0YXR1czogZGVjb2RlZFZhbHVlLAogICAgfTsKCiAgICBmZXRjaChhcGlVcmwsIHsKICAgICAgbWV0aG9kOiAnUE9TVCcsCiAgICAgIGhlYWRlcnM6IHsKICAgICAgICAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nLAogICAgICAgICdBdXRob3JpemF0aW9uJzogYEJlYXJlciAke2FwaUtleX1gLAogICAgICB9LAogICAgICBib2R5OiBKU09OLnN0cmluZ2lmeShwb3N0RGF0YSksCiAgICB9KQogICAgICAudGhlbihyZXNwb25zZSA9PiByZXNwb25zZS5qc29uKCkpCiAgICAgIC50aGVuKGRhdGEgPT4gewogICAgICAgIGNvbnNvbGUubG9nKCdQb3N0IGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5OicsIGRhdGEpOwogICAgICAgIGNvbnN0IHBvc3RJbmZvRGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3Bvc3RJbmZvJyk7CiAgICAgICAgcG9zdEluZm9EaXYuaW5uZXJUZXh0ID0gSlNPTi5zdHJpbmdpZnkoZGF0YSk7CiAgICAgIH0pCiAgICAgIC5jYXRjaChlcnJvciA9PiB7CiAgICAgICAgY29uc29sZS5lcnJvcignRXJyb3IgY3JlYXRpbmcgcG9zdDonLCBlcnJvcik7CiAgICAgIH0pOwogIDwvc2NyaXB0Pg== ____________________________ Hide img based on texinput keyword: -------------------------- <style>.hidden-image {display: none;}</style> <input type="text" id="imageKeyword" placeholder="Enter image keyword"> <script> document.addEventListener("DOMContentLoaded", function() { // Get all the images on the page var images = document.getElementsByTagName("img"); function handleKeywordChange() { var keyword = document.getElementById('imageKeyword').value.toLowerCase(); for (var i = 0; i < images.length; i++) { var img = images[i]; var imgUrl = img.getAttribute("src"); if (imgUrl && imgUrl.toLowerCase().includes(keyword)) { img.classList.remove("hidden-image"); } else { img.classList.add("hidden-image"); } } } document.getElementById('imageKeyword').addEventListener('input', handleKeywordChange); handleKeywordChange(); }); </script> _________________________ Empty fake img placeholder (var size) image ------------------------------------------- <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+Ph4DwADCAH6LH6x7QAAAABJRU5ErkJggg==" alt="Empty PNG" width=45px> ___________________________ Invisible details spoiler triggered with href ------------------------------------- <a href="javascript:var spoiler = document.getElementById('pastplaying'); spoiler.open = !spoiler.open;">LastPlaying</a> <details id="pastplaying"> <summary style="display: none;"></summary> <div class="custom-summary" onclick="toggleDetails()"></div> <div class="dropdown-content"> •<a target="_blank" href="https://codepen.io/ryedai1/full/ZEPMqZB" style=color:blue>AntenneBayern</a> •<a target="_blank" href="https://codepen.io/ryedai1/full/ZEPMqZB?baseurl=https://myonlineradio.de/bigfm/playlist?date=" style=color:blue>BigFM</a> </details><br> ______________________________________________________ Enable disabledElement with unobstr \ hidden from plainsight link ---------------------- Add ALLOW SOURCE VIEW <a href="javascript:void(0);" style="text-decoration: none;color: black;" onclick="document.getElementById('Save').disabled = false;">snippet</a> <br> <input type="submit" id=Save value="Save" disabled> ______________________________________________________ AES Crypt (HTMLCRYPT) light: ------------ <title>Encryption Tool</title> <script> function stringToArrayBuffer(str) { var encoder = new TextEncoder(); return encoder.encode(str); } function arrayBufferToString(buffer) { var decoder = new TextDecoder(); return decoder.decode(buffer); } async function generateAESKey() { var key = await crypto.subtle.generateKey( { name: "AES-CBC", length: 256 }, true, ["encrypt", "decrypt"] ); return key; } async function encryptAES(data, key) { var iv = crypto.getRandomValues(new Uint8Array(16)); var encrypted = await crypto.subtle.encrypt( { name: "AES-CBC", iv: iv }, key, data ); return { iv: iv, ciphertext: new Uint8Array(encrypted) }; } async function decryptAES(ciphertext, key, iv) { var decrypted = await crypto.subtle.decrypt( { name: "AES-CBC", iv: iv }, key, ciphertext ); return new Uint8Array(decrypted); } async function handleSubmit(event) { event.preventDefault(); var password = document.getElementById("password").value; var htmlText = document.getElementById("htmlText").value; var passwordBuffer = stringToArrayBuffer(password); var keyMaterial = await crypto.subtle.importKey( "raw", passwordBuffer, { name: "PBKDF2" }, false, ["deriveKey"] ); var aesKey = await crypto.subtle.deriveKey( { name: "PBKDF2", salt: new Uint8Array(16), // Use a random salt iterations: 100000, hash: "SHA-256" }, keyMaterial, { name: "AES-CBC", length: 256 }, true, ["encrypt", "decrypt"] ); var htmlBuffer = stringToArrayBuffer(htmlText); var encryptedData = await encryptAES(htmlBuffer, aesKey); var ivHex = Array.prototype.map .call(encryptedData.iv, (x) => ("00" + x.toString(16)).slice(-2)) .join(""); var ciphertextHex = Array.prototype.map .call(encryptedData.ciphertext, (x) => ("00" + x.toString(16)).slice(-2)) .join(""); var encryptedOutput = ivHex + ciphertextHex; document.getElementById("encryptedOutput").textContent = encryptedOutput; // Calculate SHA-256 hash of the encrypted output var encoder = new TextEncoder(); var data = encoder.encode(encryptedOutput); var hashBuffer = await crypto.subtle.digest("SHA-256", data); var hashArray = Array.from(new Uint8Array(hashBuffer)); var hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join(""); document.getElementById("sha256Output").textContent = hashHex; } async function handleDecrypt(event) { event.preventDefault(); var password = document.getElementById("password").value; var encryptedHex = document.getElementById("encryptedOutput").textContent; var passwordBuffer = stringToArrayBuffer(password); var keyMaterial = await crypto.subtle.importKey( "raw", passwordBuffer, { name: "PBKDF2" }, false, ["deriveKey"] ); var aesKey = await crypto.subtle.deriveKey( { name: "PBKDF2", salt: new Uint8Array(16), // Use a random salt for real-world scenarios iterations: 100000, hash: "SHA-256" }, keyMaterial, { name: "AES-CBC", length: 256 }, true, ["encrypt", "decrypt"] ); var ivHex = encryptedHex.substr(0, 32); var ciphertextHex = encryptedHex.substr(32); var ivBytes = new Uint8Array(ivHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); var ciphertextBytes = new Uint8Array(ciphertextHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); var decryptedBytes = await decryptAES(ciphertextBytes, aesKey, ivBytes); var decryptedHTML = arrayBufferToString(decryptedBytes); document.getElementById("decryptedOutput").innerHTML = decryptedHTML; } </script> </head> <body> <h1>Encryption Tool</h1> <form onsubmit="handleSubmit(event)"> <label for="password">Password:</label> <input type="password" id="password" required> <br> <label for="htmlText">HTML Text:</label> <br> <textarea id="htmlText" rows="10" cols="50"></textarea> <br> <input type="submit" value="Encrypt"> </form> <div> <h3>Encrypted Output:</h3> <textarea id="encryptedOutput" rows="10" cols="50"></textarea> </div> <div> <details><summary>Sha</summary><h3>SHA-256 Output:</h3> <textarea id="sha256Output"></textarea></details> </div> <form onsubmit="handleDecrypt(event)"> <!--<h3>Decryptiona</h3> <input type="submit" value="Decrypt"> </form> <div> <h3>Decrypted Output:</h3> <textarea id="decryptedOutput"></textarea> </div>--> <hr> <!DOCTYPE html> <html> <head> <title>Decryption Tool</title> You need to trigger encrypt first <script> function stringToArrayBuffer(str) { var encoder = new TextEncoder(); return encoder.encode(str); } function arrayBufferToString(buffer) { var decoder = new TextDecoder(); return decoder.decode(buffer); } async function decryptAES(ciphertext, key, iv) { var decrypted = await crypto.subtle.decrypt( { name: "AES-CBC", iv: iv }, key, ciphertext ); return new Uint8Array(decrypted); } async function handleDecrypt(event) { event.preventDefault(); var password = document.getElementById("password").value; var encryptedHex = document.getElementById("encryptedOutput").value; var passwordBuffer = stringToArrayBuffer(password); var keyMaterial = await crypto.subtle.importKey( "raw", passwordBuffer, { name: "PBKDF2" }, false, ["deriveKey"] ); var aesKey = await crypto.subtle.deriveKey( { name: "PBKDF2", salt: new Uint8Array(16), // Use a random salt for real-world scenarios iterations: 100000, hash: "SHA-256" }, keyMaterial, { name: "AES-CBC", length: 256 }, true, ["encrypt", "decrypt"] ); var ivHex = encryptedHex.substr(0, 32); var ciphertextHex = encryptedHex.substr(32); var ivBytes = new Uint8Array(ivHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); var ciphertextBytes = new Uint8Array(ciphertextHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); var decryptedBytes = await decryptAES(ciphertextBytes, aesKey, ivBytes); var decryptedHTML = arrayBufferToString(decryptedBytes); document.getElementById("decryptedOutput").innerText = decryptedHTML; } </script> <h1>Decrypt</h1> <script> function stringToArrayBuffer2(str) { var encoder = new TextEncoder(); return encoder.encode(str); } function arrayBufferToString2(buffer) { var decoder = new TextDecoder(); return decoder.decode(buffer); } async function decryptAES2(ciphertext, key, iv) { var decrypted = await crypto.subtle.decrypt( { name: "AES-CBC", iv: iv, }, key, ciphertext ); return new Uint8Array(decrypted); } async function handleDecrypt2(event) { event.preventDefault(); var password = document.getElementById("password").value; var encryptedHex = document.getElementById("encryptedOutput").value; var passwordBuffer = stringToArrayBuffer2(password); var keyMaterial = await crypto.subtle.importKey( "raw", passwordBuffer, { name: "PBKDF2" }, false, ["deriveKey"] ); var aesKey = await crypto.subtle.deriveKey( { name: "PBKDF2", salt: new Uint8Array(16), // Use a random salt for real-world scenarios iterations: 100000, hash: "SHA-256", }, keyMaterial, { name: "AES-CBC", length: 256 }, true, ["encrypt", "decrypt"] ); var ivHex = encryptedHex.substr(0, 32); var ciphertextHex = encryptedHex.substr(32); var ivBytes = new Uint8Array( ivHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)) ); var ciphertextBytes = new Uint8Array( ciphertextHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)) ); var decryptedBytes = await decryptAES2(ciphertextBytes, aesKey, ivBytes); var decryptedHTML = arrayBufferToString2(decryptedBytes); document.getElementById("decryptedOutput").innerHTML = decryptedHTML; } </script> <form onsubmit="handleDecrypt2(event)"> <input type="password" id="password" required> <input type="submit" value="Decrypt"> </form> <div> <div id="decryptedOutput"></div> </div> <hr><hr><br> DEcryptionBoilerplate<br> <plaintext> <script> function stringToArrayBuffer(str) { var encoder = new TextEncoder(); return encoder.encode(str); } function arrayBufferToString(buffer) { var decoder = new TextDecoder(); return decoder.decode(buffer); } async function decryptAES(ciphertext, key, iv) { var decrypted = await crypto.subtle.decrypt( { name: "AES-CBC", iv: iv }, key, ciphertext ); return new Uint8Array(decrypted); } async function handleDecrypt(event) { event.preventDefault(); var password = document.getElementById("password").value; var encryptedHex = document.getElementById("encryptedOutput").value; var passwordBuffer = stringToArrayBuffer(password); var keyMaterial = await crypto.subtle.importKey( "raw", passwordBuffer, { name: "PBKDF2" }, false, ["deriveKey"] ); var aesKey = await crypto.subtle.deriveKey( { name: "PBKDF2", salt: new Uint8Array(16), // Use a random salt for real-world scenarios iterations: 100000, hash: "SHA-256" }, keyMaterial, { name: "AES-CBC", length: 256 }, true, ["encrypt", "decrypt"] ); var ivHex = encryptedHex.substr(0, 32); var ciphertextHex = encryptedHex.substr(32); var ivBytes = new Uint8Array(ivHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); var ciphertextBytes = new Uint8Array(ciphertextHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); var decryptedBytes = await decryptAES(ciphertextBytes, aesKey, ivBytes); var decryptedHTML = arrayBufferToString(decryptedBytes); document.getElementById("decryptedOutput").innerHTML = decryptedHTML; } </script> <form onsubmit="handleDecrypt(event)"> <input type="password" id="password" required> <textarea id="encryptedOutput" rows="10" cols="50" style="display: none;">3a89c921bc7d3a731fa1b499823724084e388e770e8f2099ad179ef843c99cabd27e52077aa5b7875f790e6a3af6496e4149d436ac6424c10ccc2b8b78383e78 </textarea> <input type="submit" value="Decrypt"> </form><div><div id="decryptedOutput"></div></div> _______________ Iframe with cachebusting reload button: -------------------------------------- <button onclick="addCacheBustingParameter()" style="background:gray; border:transparent;">ReMeet</button><br><br> <iframe id="friends" src="https://alceawis.de/other/extra/personal/friends/friends.html " style="border:0px #ffffff none;" name="statusit" scrolling="no" frameborder="0" marginheight="0px" marginwidth="0px" height=100% width="900" allowfullscreen></iframe> <script> function addCacheBustingParameter() { var iframe = document.getElementById('friends'); var iframeSrc = iframe.src; iframe.src = iframeSrc + '?cache=' + Date.now(); } </script> _________________________________ Open mastodon url in home instance: ------------------------------ <a href="?search=https://sunny.garden/@Iva852/109293246960188756&pbUrl=https://pb.todon.de&apikey=apikey">test</a><br> <!DOCTYPE html> <html> <head> <title>API Key Form</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head> <body> <!-- HTML form to input the API key, pbUrl, and URL --> <form id="apiForm"> <label for="apikey">API Key:</label> <input type="text" id="apikey" name="apikey" required> <br> <label for="pbUrl">pbUrl:</label> <input type="text" id="pbUrl" name="pbUrl" required> <br> <label for="url">URL:</label> <input type="text" id="url" name="url" pattern="https://.*" required> <input type="submit" value="Submit"> <input type="button" value="Clear" id="clearButton"> </form> <!-- Result container --> <div id="result"></div> <script> $(document).ready(function() { // Function to get query string parameter value by name function getQueryStringParam(name) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(name); } // Function to populate textboxes from query string values function populateTextboxesFromQueryString() { const apiKeyParam = getQueryStringParam('apikey'); const pbUrlParam = getQueryStringParam('pbUrl'); const urlParam = getQueryStringParam('search'); $('#apikey').val(apiKeyParam); $('#pbUrl').val(pbUrlParam); $('#url').val(urlParam); } // Call the function to populate textboxes on page load populateTextboxesFromQueryString(); // Check if ampersand is present in the URL bar const urlBarValue = window.location.href; if (urlBarValue.includes('&')) { // Retrieve form values const apiKey = $('#apikey').val(); const pbUrl = $('#pbUrl').val(); const search = $('#url').val(); // Perform AJAX request performAjaxRequest(apiKey, pbUrl, search); } // Perform AJAX request function performAjaxRequest(apiKey, pbUrl, search) { var url = pbUrl + "/api/v2/search/?q=" + encodeURIComponent(search) + "&limit=1&resolve=true"; // Disable form elements $("#apikey").prop("disabled", true); $("#pbUrl").prop("disabled", true); $("#url").prop("disabled", true); $("#submit").prop("disabled", true); // Perform AJAX request $.ajax({ url: url, headers: { "Authorization": "Bearer " + apiKey }, success: function(response) { if (response.statuses && response.statuses.length > 0 && response.statuses[0].id) { var id = response.statuses[0].id; // Extract username and domain from the URL var urlParts = parseURL(search); var pathParts = urlParts.pathname.split("/").filter(function(part) { return part !== ""; }); var username = pathParts[0]; var domain = urlParts.hostname; // Construct the new URL var newUrl = pbUrl + "/" + username + "@" + domain + "/" + id; // Output the new URL $("#result").html("New URL: <a id='newUrlLink' href='" + newUrl + "'>" + newUrl + "</a>"); // Automatically open the new URL in a new tab $("#newUrlLink")[0].click(); } else { $("#result").html("Please enter a URL<br>cURL Result: " + JSON.stringify(response) + "<br>" + url + "<br><a target='_blank' href='https://codepen.io/ryedai1/full/WNYZBya'>Lookup</a>"); } }, error: function(xhr, status, error) { $("#result").html("Error: " + error); }, complete: function() { // Re-enable form elements $("#apikey").prop("disabled", false); $("#pbUrl").prop("disabled", false); $("#url").prop("disabled", false); $("#submit").prop("disabled", false); } }); } // Helper function to parse URL function parseURL(url) { var parser = document.createElement("a"); parser.href = url; return parser; } // Submit form event handler $("#apiForm").submit(function(event) { event.preventDefault(); // Prevent default form submission // Retrieve form values var apiKey = $("#apikey").val(); var pbUrl = $("#pbUrl").val(); var search = $("#url").val(); // Perform AJAX request performAjaxRequest(apiKey, pbUrl, search); }); // Clear button event handler $("#clearButton").click(function() { // Clear input values $("#apikey").val(""); $("#pbUrl").val(""); $("#url").val(""); // Clear result container $("#result").html(""); }); }); </script> </body> </html> _____________________________ Fetch popular mastodon posts -------------------------- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <style> .container { display: flex; } .post-box { border: 1px solid #ccc; padding: 10px; margin-right: 10px; display: flex; align-items: center; width: 500px; height: 250px; } .post-avatar { width: 80px; height: 80px; margin-right: 10px; border-radius: 50%; object-fit: cover; } .post-content { font-size: 14px; margin-bottom: 5px; overflow: hidden; text-overflow: ellipsis; max-height: 200px; max-width: 100%; white-space: normal; } .post-link { font-size: 12px; color: blue; } </style> <div class="container"></div> <script> $(document).ready(function() { var instances = ['kopimi.space', 'phpc.social', 'urusai.social', 'mastodon.social']; var totalPostsPerInstance = 10; var container = $('.container'); instances.forEach(function(instance) { var apiUrl = 'https://' + instance + '/api/v1/timelines/public'; var params = { 'local': true, 'only_media': false, 'limit': totalPostsPerInstance, 'max_id': null }; var sortedPosts = []; while (sortedPosts.length < totalPostsPerInstance) { var queryString = $.param(params); var requestUrl = apiUrl + '?' + queryString; $.ajax({ url: requestUrl, async: false, dataType: 'json', success: function(response) { var posts = response; if (posts) { posts.forEach(function(post) { var content = post.content; var favouritesCount = post.favourites_count; var boostsCount = post.reblogs_count; var url = post.url; if (!post.account.bot) { if (favouritesCount >= 2 || boostsCount >= 2) { var replyCount = post.replies_count; sortedPosts.push({ 'content': content, 'favouritesCount': favouritesCount, 'boostsCount': boostsCount, 'replyCount': replyCount, 'url': url, 'avatar': post.account.avatar_static }); } } if (sortedPosts.length >= totalPostsPerInstance) { return false; } }); } else { console.error('Error retrieving posts from ' + instance + '.'); return false; } var lastPost = posts[posts.length - 1]; params.max_id = lastPost.id; }, error: function() { console.error('Error retrieving posts from ' + instance + '.'); return false; } }); } sortedPosts.sort(function(a, b) { var aCount = Math.max(a.favouritesCount, a.boostsCount); var bCount = Math.max(b.favouritesCount, b.boostsCount); return bCount - aCount; }); sortedPosts.forEach(function(post) { var content = post.content; var favouritesCount = post.favouritesCount; var boostsCount = post.boostsCount; var replyCount = post.replyCount; var postUrl = post.url; var avatarUrl = post.avatar; var postBox = $('<div class="post-box"></div>'); var postAvatar = $('<img class="post-avatar" src="' + avatarUrl + '" alt="User Avatar"><br>'); var postContent = $('<div class="post-content">' + content + '</div>'); var counts = $('<div class="counts"></div>'); var favouritesSpan = $('<span>F: ' + favouritesCount + '</span><br>'); var boostsSpan = $('<span>B: ' + boostsCount + '</span><br>'); var repliesSpan = $('<span>R: ' + replyCount + '</span><br>'); var postLink = $('<div class="post-link"><a target="_blank" href="' + postUrl + '">View Post</a></div>'); counts.append(favouritesSpan, boostsSpan, repliesSpan); postBox.append(postAvatar, postContent, counts, postLink); container.append(postBox); }); }); }); </script> __________________________________________ Mastodon Instance Cycle: -------------------------------------------- <input type="checkbox" id="cycleCheckbox" onchange="toggleCycle()" /> <script> let canFire = true; let intervalId; function toggleCycle() { const checkbox = document.getElementById('cycleCheckbox'); if (checkbox.checked) { // Start the cycle intervalId = setInterval(() => { if (!canFire) return; const elements = [...document.querySelectorAll('#mtdlink')]; if (elements.length > 0) { const randomIndex = Math.floor(Math.random() * elements.length); elements[randomIndex].click(); } canFire = false; setTimeout(() => { canFire = true; }, 20000); }, 1000); } else { // Stop the cycle clearInterval(intervalId); } } </script> <!--renderer--> •<a href="#" onclick="changeEndpoint('https://mastodon.social')" id="mtdlink">mastodon.social</a> •<a href="#" onclick="changeEndpoint('https://urusai.social')" id="mtdlink">urusai.social</a> •<a href="#" onclick="changeEndpoint('https://phpc.social')" id="mtdlink">phpc.social</a> •<a href="#" onclick="changeEndpoint('https://pb.todon.de')"id="mtdlink">pb.todon.de</a> •<a href="#" onclick="changeEndpoint('https://kopimi.space')" id="mtdlink">kopimi.space</a> <div id="container"><div id="instance-data" class="example"></div></div> <style> #container { background-size: cover; background-position: center; background-repeat: no-repeat; } #instance-data { color: black; text-shadow: 2px 2px 8px #ffffff; //} @keyframes colorCycle { 0% { color: #7f00ff; text-shadow: 2px 2px 4px #7f00ff; } 50% { color: transparent; } 100% { color: #7f00ff; text-shadow: 2px 2px 4px #7f00ff; } } .example { animation: colorCycle 18s infinite; } </style> </head> <body onload="fetchInstanceData('https://urusai.social')"> <script> function fetchInstanceData(endpoint) { const url = `${endpoint}/api/v1/instance`; const instanceDataElement = document.getElementById('instance-data'); const containerElement = document.getElementById('container'); fetch(url) .then(response => response.json()) .then(instanceData => { // Extract specific fields const uri = instanceData.uri; const title = instanceData.title; const shortDescription = instanceData.short_description; const description = instanceData.description; const userCount = instanceData.stats.user_count; const statusCount = instanceData.stats.status_count; const domainCount = instanceData.stats.domain_count; const streamingApiUrl = instanceData.urls.streaming_api; const thumbnail = instanceData.thumbnail; const email = instanceData.email; const registrations = instanceData.registrations; instanceDataElement.innerHTML = ` <b><a target="_blank" href="https://${uri}" style="color: pink">${uri}</a></b><br> ${title} (${email})<br> •${shortDescription}<br> <hr>${description}<hr> <b>UserCount:</b> ${userCount} Statuses: ${statusCount} <b>Domains:</b> ${domainCount} <b>Registration possible:</b> <u>${registrations}</u><br>- `; containerElement.style.backgroundImage = `url(${thumbnail})`; }) } function changeEndpoint(endpoint) { fetchInstanceData(endpoint); } </script> </body> </html> ____________________________ Display code text file with -&_ delims orderly: ------------------------------------------------- <script> window.addEventListener("DOMContentLoaded", () => { const url = document.getElementById("dropdownMenu").value; changeDropdownSelection(url); }); </script> <head> <style> .extracted{margin-bottom:10px}.title{background-color:#f1f1f1;padding:10px;cursor:pointer;font-weight:700}.content{display:none;padding:10px;background-color:#fff;position:relative}.content.show{display:block}.copy-button{position:absolute;top:5px;right:5px;padding:5px 10px;border:none;background-color:#ddd;cursor:pointer} </style> </head> <body> <div class="dropdown"> <select id="dropdownMenu" onchange="changeDropdownSelection()"> <option value="/other/Computerstuff/Commands/Autohotkey/Commands.txt">Autohotkey</option> <option value="/other/Computerstuff/Commands/Android-Keyevents.txt">AndroidKeyevents</option> <option value="/other/Computerstuff/Commands/DOS/Commands.txt">DOS</option> <option value="/other/Computerstuff/Commands/FFMPEG_Commands.txt">FFMPEG</option> <option value="/other/Computerstuff/Commands/Commands_ImageMagick.txt">Imagemagick</option> <option value="/other/Computerstuff/Commands/Sox_CMDS.txt">Sox</option> <option value="/other/Computerstuff/Commands/HTML_Codes.txt">HTML</option> <option value="/other/Computerstuff/Commands/PHP.txt">PHP</option> <option value="/other/Computerstuff/Commands/Python.txt">Python</option> </select> </div> <div id="output"></div> <script> function toggleContent(titleElement) { const contentElement = titleElement.nextElementSibling; contentElement.classList.toggle("show"); } function copyContent(contentElement) { const text = contentElement.innerText.trim(); navigator.clipboard.writeText(text) .then(() => { console.log('Content copied to clipboard:', text); }) .catch((error) => { console.error('Failed to copy content to clipboard:', error); }); } function changeDropdownSelection() { const dropdown = document.getElementById('dropdownMenu'); const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const fileTextParam = urlParams.get('filetext'); if (fileTextParam !== null) { dropdown.value = fileTextParam; } const selectedValue = dropdown.value; fetch(selectedValue) .then((response) => response.text()) .then((data) => { const container = document.getElementById('output'); container.innerHTML = ''; const lines = data.split('\n'); const filteredLines = []; for (let i = 0; i < lines.length; i++) { if (lines[i].includes('----- ----')) { //remove space to make work if (i > 0) { filteredLines.push(lines[i - 1]); } } } const contentLines = data.split(/------ ---+/); //remove space to make work console.log("Change data.split(/------ ---+/ to (/----"); contentLines.shift(); filteredLines.forEach((line, index) => { const extractedElement = document.createElement("div"); extractedElement.className = "extracted"; const titleElement = document.createElement("div"); titleElement.className = "title"; titleElement.innerText = line; titleElement.addEventListener("click", () => toggleContent(titleElement)); extractedElement.appendChild(titleElement); const contentElement = document.createElement("div"); contentElement.className = "content"; contentElement.innerText = contentLines[index].trim(); extractedElement.appendChild(contentElement); const copyButton = document.createElement("button"); copyButton.innerText = "Copy"; copyButton.className = "copy-button"; copyButton.addEventListener("click", () => copyContent(contentElement)); contentElement.appendChild(copyButton); container.appendChild(extractedElement); }); }) .catch((error) => { console.error(error); }); } </script> ====CodeDisplayVer=== PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KICA8c3R5bGU+CiAgICAuZXh0cmFjdGVkLWNvbnRlbnQgewogICAgICBtYXJnaW4tYm90dG9tOiAxMHB4OwogICAgICBwYWRkaW5nOiA1cHg7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkICNjY2M7CiAgICAgIGJhY2tncm91bmQtY29sb3I6ICNmOWY5Zjk7CiAgICB9CgogICAgLnRpdGxlIHsKICAgICAgZm9udC13ZWlnaHQ6IDcwMDsKICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2NjYzsKICAgICAgcGFkZGluZzogNXB4OwogICAgICBtYXJnaW4tYm90dG9tOiA1cHg7CiAgICAgIGN1cnNvcjogcG9pbnRlcjsKICAgIH0KCiAgICAuY29udGVudCB7CiAgICAgIGRpc3BsYXk6IG5vbmU7CiAgICAgIHBhZGRpbmc6IDVweDsKICAgIH0KCiAgICAuY29udGVudC5zaG93IHsKICAgICAgZGlzcGxheTogYmxvY2s7CiAgICB9CgogICAgLmRyb3Bkb3duIHsKICAgICAgcG9zaXRpb246IHJlbGF0aXZlOwogICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7CiAgICB9CgogICAgLmRyb3Bkb3duLWNvbnRlbnQgewogICAgICBkaXNwbGF5OiBub25lOwogICAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICAgIGJhY2tncm91bmQtY29sb3I6ICNmOWY5Zjk7CiAgICAgIG1pbi13aWR0aDogMTYwcHg7CiAgICAgIGJveC1zaGFkb3c6IDBweCA4cHggMTZweCAwcHggcmdiYSgwLCAwLCAwLCAwLjIpOwogICAgICB6LWluZGV4OiAxOwogICAgfQoKICAgIC5kcm9wZG93bjpob3ZlciAuZHJvcGRvd24tY29udGVudCB7CiAgICAgIGRpc3BsYXk6IGJsb2NrOwogICAgfQogIDwvc3R5bGU+CiAgPHNjcmlwdD4KICAgIGZ1bmN0aW9uIHRvZ2dsZUNvbnRlbnQoZWxlbWVudCkgewogICAgICBjb25zdCBjb250ZW50RWxlbWVudCA9IGVsZW1lbnQubmV4dEVsZW1lbnRTaWJsaW5nOwogICAgICBjb250ZW50RWxlbWVudC5jbGFzc0xpc3QudG9nZ2xlKCJzaG93Iik7CiAgICB9CgogICAgZnVuY3Rpb24gcGFyc2VDb250ZW50KGRhdGEsIHRpdGxlcykgewogICAgICBjb25zdCBwYXR0ZXJuID0gLy0tLS0tLSguKj8pX19fX18vZ3M7CiAgICAgIGNvbnN0IG1hdGNoZXMgPSBbLi4uZGF0YS5tYXRjaEFsbChwYXR0ZXJuKV07CiAgICAgIGNvbnN0IG91dHB1dENvbnRhaW5lciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJvdXRwdXQiKTsKICAgICAgb3V0cHV0Q29udGFpbmVyLmlubmVySFRNTCA9ICIiOwogICAgICBmb3IgKGxldCBpID0gMDsgaSA8IG1hdGNoZXMubGVuZ3RoOyBpKyspIHsKICAgICAgICBjb25zdCBtYXRjaCA9IG1hdGNoZXNbaV07CiAgICAgICAgY29uc3QgZXh0cmFjdGVkQ29udGVudCA9IG1hdGNoWzFdLnRyaW0oKTsgLy8gVHJpbSBhbnkgbGVhZGluZy90cmFpbGluZyB3aGl0ZXNwYWNlCiAgICAgICAgY29uc3QgZXh0cmFjdGVkRWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImRpdiIpOwogICAgICAgIGV4dHJhY3RlZEVsZW1lbnQuY2xhc3NOYW1lID0gImV4dHJhY3RlZC1jb250ZW50IjsKICAgICAgICBjb25zdCB0aXRsZUVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJkaXYiKTsKICAgICAgICB0aXRsZUVsZW1lbnQuY2xhc3NOYW1lID0gInRpdGxlIjsKICAgICAgICB0aXRsZUVsZW1lbnQuaW5uZXJUZXh0ID0gdGl0bGVzW2ldOwogICAgICAgIHRpdGxlRWxlbWVudC5hZGRFdmVudExpc3RlbmVyKCJjbGljayIsICgpID0+IHRvZ2dsZUNvbnRlbnQodGl0bGVFbGVtZW50KSk7CiAgICAgICAgZXh0cmFjdGVkRWxlbWVudC5hcHBlbmRDaGlsZCh0aXRsZUVsZW1lbnQpOwogICAgICAgIGNvbnN0IGNvbnRlbnRFbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7CiAgICAgICAgY29udGVudEVsZW1lbnQuY2xhc3NOYW1lID0gImNvbnRlbnQiOwogICAgICAgIGNvbnRlbnRFbGVtZW50LmlubmVyVGV4dCA9IGV4dHJhY3RlZENvbnRlbnQ7CiAgICAgICAgZXh0cmFjdGVkRWxlbWVudC5hcHBlbmRDaGlsZChjb250ZW50RWxlbWVudCk7CiAgICAgICAgb3V0cHV0Q29udGFpbmVyLmFwcGVuZENoaWxkKGV4dHJhY3RlZEVsZW1lbnQpOwogICAgICB9CiAgICB9CgogICAgYXN5bmMgZnVuY3Rpb24gZmV0Y2hBbmRQYXJzZSh1cmwpIHsKICAgICAgdHJ5IHsKICAgICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHVybCk7CiAgICAgICAgaWYgKCFyZXNwb25zZS5vaykgewogICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCJOZXR3b3JrIHJlc3BvbnNlIHdhcyBub3Qgb2siKTsKICAgICAgICB9CiAgICAgICAgY29uc3QgZGF0YSA9IGF3YWl0IHJlc3BvbnNlLnRleHQoKTsKICAgICAgICBjb25zdCBsaW5lcyA9IGRhdGEuc3BsaXQoIlxuIik7CiAgICAgICAgY29uc3QgZmlsdGVyZWRMaW5lcyA9IFtdOwogICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbGluZXMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgIGlmIChsaW5lc1tpXS5pbmNsdWRlcygiLS0tLS0tLS0tIikpIHsKICAgICAgICAgICAgaWYgKGkgPiAwKSB7CiAgICAgICAgICAgICAgZmlsdGVyZWRMaW5lcy5wdXNoKGxpbmVzW2kgLSAxXSk7CiAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcGFyc2VDb250ZW50KGRhdGEsIGZpbHRlcmVkTGluZXMpOwogICAgICB9IGNhdGNoIChlcnJvcikgewogICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyb3IpOwogICAgICB9CiAgICB9CgogICAgZnVuY3Rpb24gY2hhbmdlRHJvcGRvd25TZWxlY3Rpb24oKSB7CiAgICAgIGNvbnN0IGRyb3Bkb3duID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImRyb3Bkb3duTWVudSIpOwogICAgICBjb25zdCBzZWxlY3RlZE9wdGlvbiA9IGRyb3Bkb3duLm9wdGlvbnNbZHJvcGRvd24uc2VsZWN0ZWRJbmRleF07CiAgICAgIGNvbnN0IHVybCA9IHNlbGVjdGVkT3B0aW9uLnZhbHVlOwogICAgICBmZXRjaEFuZFBhcnNlKHVybCk7CiAgICB9CiAgPC9zY3JpcHQ+CjwvaGVhZD4KPGJvZHk+CiAgPGRpdiBjbGFzcz0iZHJvcGRvd24iPgogICAgPHNlbGVjdCBpZD0iZHJvcGRvd25NZW51IiBvbmNoYW5nZT0iY2hhbmdlRHJvcGRvd25TZWxlY3Rpb24oKSI+CiAgICAgIDxvcHRpb24gdmFsdWU9Im90aGVyL0NvbXB1dGVyc3R1ZmYvQ29tbWFuZHMvQXV0b2hvdGtleS9Db21tYW5kcy50eHQiPkF1dG9ob3RrZXk8L29wdGlvbj4KICAgICAgPG9wdGlvbiB2YWx1ZT0ib3RoZXIvQ29tcHV0ZXJzdHVmZi9Db21tYW5kcy9ET1MvQ29tbWFuZHMudHh0Ij5ET1M8L29wdGlvbj4KICAgICAgPG9wdGlvbiB2YWx1ZT0ib3RoZXIvQ29tcHV0ZXJzdHVmZi9Db21tYW5kcy9GRk1QRUclMjBDb21tYW5kcy50eHQiPkZGTVBFRzwvb3B0aW9uPgogICAgICA8b3B0aW9uIHZhbHVlPSJvdGhlci9Db21wdXRlcnN0dWZmL0NvbW1hbmRzL0NvbW1hbmRzX0ltYWdlTWFnaWNrLnR4dCI+SW1hZ2VtYWdpY2s8L29wdGlvbj4KICAgICAgPG9wdGlvbiB2YWx1ZT0ib3RoZXIvQ29tcHV0ZXJzdHVmZi9Db21tYW5kcy9Tb3hfQ01EUy50eHQiPlNveDwvb3B0aW9uPgogICAgICA8b3B0aW9uIHZhbHVlPSJvdGhlci9Db21wdXRlcnN0dWZmL0NvbW1hbmRzL0hUTUxfQ29kZXMudHh0Ij5IVE1MPC9vcHRpb24+CiAgICAgIDxvcHRpb24gdmFsdWU9Im90aGVyL0NvbXB1dGVyc3R1ZmYvQ29tbWFuZHMvUEhQLnR4dCI+UEhQPC9vcHRpb24+CiAgICAgIDxvcHRpb24gdmFsdWU9Im90aGVyL0NvbXB1dGVyc3R1ZmYvQ29tbWFuZHMvUHl0aG9uLnR4dCI+UHl0aG9uPC9vcHRpb24+CiAgICAgIDxvcHRpb24gdmFsdWU9Im90aGVyL0NvbXB1dGVyc3R1ZmYvQ29tbWFuZHMvQW5kcm9pZC1LZXlldmVudHMudHh0Ij5BbmRyb2lkIEtleWV2ZW50czwvb3B0aW9uPgogICAgPC9zZWxlY3Q+CiAgPC9kaXY+CiAgPGRpdiBpZD0ib3V0cHV0Ij48L2Rpdj4KPC9ib2R5Pgo8L2h0bWw+ _______________________________ Table fetch specific entries: --------------------------------------- <div class="table-container"></div> <style> .table-container { display: inline-block; } table { border-collapse: collapse; } tr { border-bottom: 2px solid #000; border-top: 2px solid #000; } td { border-right: 2px solid #000; border-left: 2px solid #000; } </style> <script> function fetchAndPopulateDataTD() { fetch('https://alceawis.de/other/extra/personal/personality/0apersonality.html') .then(response => response.text()) .then(data => { const tempDiv = document.createElement('div'); tempDiv.innerHTML = data; const table = tempDiv.querySelector('table'); const rows = table.querySelectorAll('tr'); const filteredRows = Array.from(rows).filter(row => { const cells = row.querySelectorAll('td'); return Array.from(cells).some(cell => cell.innerHTML.includes('Favorite')); }); const displayDiv = document.querySelector('.table-container'); displayDiv.innerHTML = ''; const filteredTable = document.createElement('table'); filteredRows.forEach(row => { const clonedRow = row.cloneNode(true); filteredTable.appendChild(clonedRow); }); displayDiv.appendChild(filteredTable); }) .catch(error => { console.error('Error:', error); }); } fetchAndPopulateDataTD(); </script> ________________________ Mastodon Keyword over time: -------------------------------------- <form onsubmit="openInNewTab(event)"> <label for="dropdown"></label> <select id="dropdown" name="endpoint"> <option value="https://urusai.social/api/v1/accounts/111417582756052979/statuses">@alcea@urusai.social</option> <option value="https://pb.todon.de/api/v1/accounts/109629985010224381/statuses">@alcea@pb.todon</option> <option value="https://mastodon.social/api/v1/accounts/109977878421486714/statuses">@ryedai@mastodon.social</option> <option value="https://mstdn.animexx.de/api/v1/accounts/111676830721936824/statuses">@alcea@animexx</option> </select> <label for="textfield">Enter a keyword:</label> <input type="text" id="textfield" name="keyword"> <input type="submit" value="Submit"> </form> <script> function openInNewTab(event) { event.preventDefault(); var endpoint = document.getElementById("dropdown").value; var keyword = document.getElementById("textfield").value; var url = "" + "?endpoint=" + encodeURIComponent(endpoint) + "&keyword=" + encodeURIComponent(keyword); window.open(url, "_blank"); // Open the URL in a new tab or window } </script><hr> <a target="_blank" href="?endpoint=https://urusai.social/api/v1/accounts/111417582756052979/statuses&keyword=koyu" style=color:blue>title</a><br> <!DOCTYPE html> <html> <head> <title>Line Chart Example</title> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head> <body> <!--<div id="entities"></div>--> <canvas id="lineChart"></canvas> <script> $(document).ready(function() { var endpoint = 'https://pb.todon.de/api/v1/accounts/109629985010224381/statuses'; // Get the value of the "endpoint" and "keyword" query string parameters var urlParams = new URLSearchParams(window.location.search); var customEndpoint = urlParams.get('endpoint'); if (customEndpoint) { endpoint = customEndpoint; } var keyword = urlParams.get('keyword'); var entities = []; fetchEntities(); function fetchEntities() { var url = endpoint; if (entities.length > 0) { var linkHeader = '<' + endpoint + '?max_id=' + entities[entities.length - 1].id + '>; rel="next"'; $.ajaxSetup({ headers: { 'Link': linkHeader } }); } $.ajax({ url: url, type: 'GET', dataType: 'json', headers: { 'Authorization': 'Bearer token' }, success: function(response, textStatus, xhr) { var newEntities = response; for (var i = 0; i < newEntities.length; i++) { var entity = newEntities[i]; entities.push(entity); } var linkHeader = xhr.getResponseHeader('Link'); var nextLink = extractLinkUrl(linkHeader, 'next'); if (entities.length >= 200) { // Render the fetched entities renderEntities(); // Create the line chart createLineChart(); } else if (nextLink) { // Fetch the next set of entities endpoint = nextLink; fetchEntities(); } else { console.log('Finished fetching 200 entities'); // Render the fetched entities renderEntities(); // Create the line chart createLineChart(); } }, error: function(xhr, status, error) { console.log('Error: ' + error); } }); } function extractLinkUrl(linkHeader, rel) { var links = linkHeader.split(','); for (var i = 0; i < links.length; i++) { var link = links[i].trim(); var regex = /<([^>]+)>;\s*rel="([^"]+)"/g; var match = regex.exec(link); if (match && match[2] === rel) { return match[1]; } } return null; } function renderEntities() { for (var i = 0; i < entities.length; i++) { var entity = entities[i]; // Render the URL and date on the page var entityHtml = '<p>'; entityHtml += 'URL: <a target=_blank href="' + entity.url + '">' + entity.url + '</a><br>'; entityHtml += 'Date: ' + entity.created_at + '<br>'; entityHtml += 'Toot: ' + entity.content + '<br>'; entityHtml += '</p>'; $('#entities').append(entityHtml); } } function createLineChart() { var labels = []; var dataPoints = []; for (var i = 0; i < entities.length; i++) { var entity = entities[i]; labels.push(entity.created_at); var keywordCount = countKeywordOccurrences(entity.content, keyword); dataPoints.push(keywordCount); } var ctx = document.getElementById('lineChart').getContext('2d'); var lineChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: 'Keyword "' + keyword + '" Occurrences - ' + endpoint, data: dataPoints, borderColor: 'blue', fill: false }] }, options: { responsive: true, scales: { x: { display: true, reverse: true, // Invert the x-axis title: { display: true, text: 'Date' } }, y: { display: true, title: { display: true, text: 'Keyword "' + keyword + '" Occurrences' } } } } }); } function countKeywordOccurrences(text, keyword) { var regex = new RegExp(keyword, 'gi'); var matches = text.match(regex); if (matches) { return matches.length; } return 0; } }); </script> </body> </html> _________________________ JS inbrowser zip extractor: ----------------------------------- <!DOCTYPE html> <html> <head> <title>Zip Renderer</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js"></script> <script> function renderZipFile() { var zipFileName = document.getElementById("zipFileName").value; fetch(zipFileName) .then(function(response) { return response.arrayBuffer(); }) .then(function(buffer) { return JSZip.loadAsync(buffer); }) .then(function(zip) { var folders = []; var files = []; var linksWithSecondSlash = []; zip.forEach(function(relativePath, zipEntry) { if (zipEntry.dir) { folders.push(relativePath); } else { files.push({ relativePath: relativePath, zipEntry: zipEntry }); } }); folders.sort(); files.sort(function(a, b) { return a.relativePath.localeCompare(b.relativePath); }); files.forEach(function(file) { var link = file.relativePath; if (link.split('/').length > 2) { linksWithSecondSlash.push(link); } else { renderFile(file.zipEntry); } }); linksWithSecondSlash.forEach(function(link) { var zipEntry = zip.file(link); if (zipEntry) { renderFile(zipEntry); } }); folders.forEach(function(folder) { renderFolder(folder); }); }) .catch(function(error) { console.error('Error rendering zip file:', error); }); } function renderFolder(folder) { var folderElement = document.createElement('p'); folderElement.textContent = folder; document.body.appendChild(folderElement); } function renderFile(zipEntry) { zipEntry.async('blob').then(function(blob) { var url = URL.createObjectURL(blob); var fileElement = document.createElement('a'); fileElement.href = url; fileElement.download = zipEntry.name; fileElement.textContent = zipEntry.name; fileElement.target = "_blank"; document.body.appendChild(fileElement); document.body.appendChild(document.createElement('br')); }); } </script> </head> <body> <label for="zipFileName">Zip File Name:</label> <input type="text" id="zipFileName" name="zipFileName" value="https://ry3yr.github.io/alceawis.de.zip"> <button onclick="renderZipFile()">Render Zip</button> </body> </html> _______________________ Change all of a certain link on page: ------------------------------------------------ <script> document.addEventListener('DOMContentLoaded', function() { var links = document.querySelectorAll('a[href^="https://m.youtube.com"]'); for (var i = 0; i < links.length; i++) { var link = links[i]; link.href = link.href.replace('https://m.youtube.com', 'https://super8.absturztau.be'); } }); </script> <a href="https://m.youtube.com/watch?v=01bwEdQg6EY">EternalSnow</a> _____________________________ htaccess enable caching: ------------------------------- # Enable the mod_expires module ExpiresActive On # Cache resources with specified file extensions ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType application/javascript "access plus 1 month" ExpiresByType text/css "access plus 1 month" # Set default expiration for other resources ExpiresDefault "access plus 1 week" ____________________ Currently binged Album: --------------------------------- <div id="output"></div><div id="old"></div> <script> window.addEventListener('DOMContentLoaded', (event) => { fetch('https://ry3yr.github.io/Dayalbum.js') .then(response => response.text()) .then(fileContent => { var lines = fileContent.split("\n"); var topLine = lines[0]; document.getElementById("output").innerHTML = "<b>CurrBingedAlbum</>: " + topLine; var lines = fileContent.split("\n"); var randomIndex = Math.floor(Math.random() * lines.length); var randomLine = lines[randomIndex]; document.getElementById("old").innerHTML = "<span style='color: gray; font-size: 12px;'>(Old: " + randomLine + "</span>" + ")"; }); }); </script> _____________________ Keep specific imgs despite low bandwith querystrings: ---------------------------------------- <script> if (window.location.href.includes("lowbandwidth")) { var style = document.createElement("style"); style.innerHTML = `object, img:not(#important) {display: none !important;}`; document.head.appendChild(style); } </script> Images display/not depending on "?mode=<a target="_blank" href="?mode=lowbandwidth" style=color:blue>lowbandwidth</a>" querystring:<br> keep (id="important"): [<img src="https://alcea-wisteria.de/PHP/0demo/2023-07-09-FetchMastodonAva/alcea/alcea_2024-02-08.png" id="important" style="height: 35px;">]<br><br> hide (id="null"): [<img src="https://alcea-wisteria.de/PHP/0demo/2023-07-09-FetchMastodonAva/alcea/alcea_2024-02-08.png" id="null" style="height: 35px;">] _______________________ Shoutcast Radio Stream: ----------------------------------------- <a target="_blank" href="http://fmstream.org/index.php?c=D" style=color:blue>Kartoffelstations</a> <br><hr><br> <style> body { font-family: Arial, sans-serif; margin: 20px; } label { font-weight: bold; } input[type="text"], input[type="file"], button { margin-bottom: 10px; } #player { margin-top: 20px; background-color: #f5f5f5; padding: 20px; border-radius: 8px; } audio { width: 100%; } #trackInfo { margin-top: 20px; font-weight: bold; } </style> </head> <body> <button onclick="queryURL()">[NowPlaying]</button> <div id="response"></div> <br> <br> [ <a href="#" onclick="document.getElementById('urlInput').value ='https://stream.antenne.de/antenne/stream/mp3'; return false;">Antenne</a> <a href="#" onclick="document.getElementById('urlInput').value = 'https://stream.bigfm.de/bw/mp3-128'; return false;">BigFM</a> <a href="#" onclick="document.getElementById('urlInput').value = 'https://liveradio.swr.de/sw890cl/swr3/play.aac'; return false;">SWR3</a> ]<br> <input type="text" id="urlInput" value="https://stream.antenne.de/antenne/stream/mp3"> <br> <button onclick="renderShoutcast()">[▷Play]</button> <br> <div id="player"></div> <div id="trackInfo"></div> <script> var audioElement = null; function renderShoutcast() { var urlInput = document.getElementById("urlInput").value; var playerDiv = document.getElementById("player"); // Clear existing player while (playerDiv.firstChild) { playerDiv.removeChild(playerDiv.firstChild); } audioElement = document.createElement("audio"); audioElement.src = urlInput; audioElement.controls = true; playerDiv.appendChild(audioElement); // Add event listener for the 'canplay' event audioElement.addEventListener('canplay', function() { audioElement.play(); var button = document.querySelector("[onclick='queryURL()']"); button.click(); //click queryURLButton }); } // Handle file upload var fileInput = document.getElementById('fileInput'); fileInput.addEventListener('change', function(event) { var file = event.target.files[0]; var reader = new FileReader(); reader.onload = function(event) { var url = event.target.result; document.getElementById('urlInput').value = url; renderShoutcast(); }; reader.readAsDataURL(file); }); </script> <!---Get Nowplaying---> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> function queryURL() { var url = $('#urlInput').val(); var requestURL = 'https://alcea-wisteria.de/PHP/0demo/2024-02-09-ShoutcaseMetadataextract/querystringextract.php?streamurl=' + url; fetch(requestURL) .then(function(response) { if (!response.ok) { throw new Error('Network response was not ok'); } return response.text(); }) .then(function(data) { $('#response').text(data); }) .catch(function(error) { $('#response').text('Error occurred while fetching data: ' + error); }); } </script> <details><summary>file</summary><input type="file" id="fileInput"></details> <details><summary>PHPTitlefetch</summary> <plaintext> <?php function getStreamMetadata() { if (isset($_GET['streamurl'])) { $streamUrl = $_GET['streamurl']; $needle = 'StreamTitle='; $ua = 'Dailymate Radio/1.0'; $opts = ['http' => ['method' => 'GET', 'header' => 'Icy-MetaData: 1', 'user_agent' => $ua] ]; $context = stream_context_create($opts); $icyMetaIntFound = false; $icyInterval = -1; $offset = 0; if(($headers = get_headers($streamUrl, 0, $context))) { foreach($headers as $h) { if(!(strpos(strtolower($h), 'icy-metaint:') === false)) { $icyMetaIntFound = true; $icyInterval = explode(':', $h)[1]; break; } } } if(!$icyMetaIntFound) { echo "icy-metaint header not exists!"; return; } if($stream = fopen($streamUrl, 'r', false, $context)) { while($buffer = stream_get_contents($stream, $icyInterval, $offset)) { if(strpos($buffer, $needle) !== false) { fclose($stream); $title = explode($needle, $buffer)[1]; return substr($title, 1, strpos($title, ';') - 2); } $offset += $icyInterval; } } } else { // The 'streamurl' parameter is not set } } echo getStreamMetadata(); ?> __________________________ Radio Song Time Url constructor: -------------------------------------------- <a target="_blank" href="?baseurl=https://www.antenne.de/pramm/song-suche?date=" style=color:blue>Changestation</a><br> <script> function openSongSearchResults() { var parameterUrl = new URL(window.location.href); var baseurl = parameterUrl.searchParams.get("baseurl"); if (baseurl == null) { baseurl = "https://www.antenne.de/programm/song-suche"; } var year = document.getElementById("yearInput").value; var month = document.getElementById("monthInput").value; var day = document.getElementById("dayInput").value; var hours = document.getElementById("hoursInput").value; var minutes = document.getElementById("minutesInput").value; var date = year + '-' + month + '-' + day; var time = hours + '' + minutes; var url = baseurl + "?date=" + date + "&channel=live&time=" + time; window.open(url, "_blank"); } function getCurrentDateTime() { var currentDate = new Date(); var year = currentDate.getFullYear(); var month = String(currentDate.getMonth() + 1).padStart(2, '0'); var day = String(currentDate.getDate()).padStart(2, '0'); var hours = String(currentDate.getHours()).padStart(2, '0'); var minutes = String(currentDate.getMinutes()).padStart(2, '0'); document.getElementById("yearInput").value = year; document.getElementById("monthInput").value = month; document.getElementById("dayInput").value = day; document.getElementById("hoursInput").value = hours; //document.getElementById("minutesInput").value = minutes; } </script> <body onload="getCurrentDateTime()"> <h1>RadioSongSearch</h1> <label for="yearInput">Year:</label> <input type="text" id="yearInput"> <label for="monthInput">Month:</label> <input type="text" id="monthInput"> <label for="dayInput">Day:</label> <input type="text" id="dayInput"> <label for="hoursInput">Time:</label> <input type="text" id="hoursInput"> <label for="minutesInput">Minutes:</label> <input type="text" id="minutesInput"> <br> <button onclick="openSongSearchResults()">Search</button> </body> ___________________________ Random game (dekudeals & steam json) picker: ----------------------------------------- <div id="collection-links"></div> <script> function fetchData() { fetch('https://ry3yr.github.io/collection.json') .then(response => response.json()) .then(data1 => { const randomIndex1 = Math.floor(Math.random() * data1.items.length); const randomEntry1 = data1.items[randomIndex1]; fetch('https://ry3yr.github.io/steam_library.json') .then(response => response.json()) .then(data2 => { const games = data2.response.games; const randomIndex2 = Math.floor(Math.random() * games.length); const randomGame = games[randomIndex2]; const randomEntry = Math.random() < 0.5 ? randomEntry1.name + ' (NintendoSwitch)' : randomGame.name + ' (Steam)'; document.getElementById('collection-links').innerHTML = `<p>${randomEntry} <a href="javascript:fetchData()">[Rerun]</a></p>`; }); }); } fetchData(); </script> _______________________ MAL / MyAnimeList Table parser (xml export: https://myanimelist.net/panel.php?go=export) ------------------------------- <script> fetch("https://ry3yr.github.io/mal.xml") .then(response => response.text()) .then(xmlString => { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlString, "text/xml"); const table = document.createElement("table"); const animeList = xmlDoc.getElementsByTagName("anime"); const animeData = []; for (let i = 0; i < animeList.length; i++) { const anime = animeList[i]; const seriesTitle = anime.getElementsByTagName("series_title")[0].textContent; const seriesType = anime.getElementsByTagName("series_type")[0].textContent; const seriesEpisodes = parseInt(anime.getElementsByTagName("series_episodes")[0].textContent); const myScore = parseFloat(anime.getElementsByTagName("my_score")[0].textContent); const myStatus = anime.getElementsByTagName("my_status")[0].textContent; animeData.push({ seriesTitle, seriesType, seriesEpisodes, myScore, myStatus }); } const createTable = () => { table.innerHTML = ""; const headerRow = document.createElement("tr"); const headers = [ { label: "Title", key: "seriesTitle" }, { label: "Type", key: "seriesType" }, { label: "Episodes", key: "seriesEpisodes" }, { label: "Score", key: "myScore" }, { label: "Status", key: "myStatus" } ]; headers.forEach(header => { const headerCell = document.createElement("th"); headerCell.textContent = header.label; headerCell.addEventListener("click", () => sortTable(header.key)); headerRow.appendChild(headerCell); }); table.appendChild(headerRow); animeData.forEach(data => { const row = document.createElement("tr"); headers.forEach(header => { const cell = document.createElement("td"); if (header.key === "seriesTitle") { const link = document.createElement("a"); link.href = `https://myanimelist.net/search/all?q=${encodeURIComponent(data[header.key])}`; link.target = "_blank"; link.textContent = data[header.key]; cell.appendChild(link); } else { cell.textContent = data[header.key]; } row.appendChild(cell); }); table.appendChild(row); }); document.body.appendChild(table); }; let sortKey = ""; let sortAscending = true; const sortTable = key => { if (sortKey === key) { sortAscending = !sortAscending; } else { sortKey = key; sortAscending = true; } animeData.sort((a, b) => { const valueA = a[key]; const valueB = b[key]; let comparison = 0; if (valueA > valueB) { comparison = 1; } else if (valueA < valueB) { comparison = -1; } return sortAscending ? comparison : -comparison; }); createTable(); }; createTable(); }) .catch(error => { console.error("Error loading mal.xml:", error); }); </script> ______________________________ Random mtd tag fetch (jukebox): ----------------------------------------- <a href="?tag=https://urusai.social/api/v1/timelines/tag/RamblingsAlcea">xmpl<a><br> <div id="entryContainer"></div> <script> async function displayRandomEntry() { const urlParams = new URLSearchParams(window.location.search); const tag = urlParams.get("tag"); let randomInstanceUrl; if (tag) { randomInstanceUrl = `${tag}`; } else { const instances = [ "https://urusai.social/api/v1/timelines/tag/CurrListeningAlcea", "https://mastodon.social/api/v1/timelines/tag/CurrListeningRyeDai", "https://pb.todon.de/api/v1/timelines/tag/CurrListeningAlcea" ]; randomInstanceUrl = instances[Math.floor(Math.random() * instances.length)]; } try { const response = await fetch(randomInstanceUrl); const data = await response.json(); const randomEntry = data[Math.floor(Math.random() * data.length)]; //let entryContent = randomEntry.content.replace(/<br(\s*\/)?>/g, "").replace(/<\/?p>/g, ""); let entryContent = randomEntry.content; const entryContainer = document.getElementById("entryContainer"); if (entryContainer) { const entryLink = document.createElement("a"); entryLink.href = randomEntry.url; entryLink.target = "_blank"; entryLink.textContent = "->"; entryContainer.innerHTML = entryContent; entryContainer.appendChild(entryLink); } else { console.log("Entry container not found."); } } catch (error) { console.error("Error fetching the entry:", error); } } displayRandomEntry(); </script> __________________________ Youtube Link relay: -------------------------- <a target="_blank" href="?v=0SQ-TJKPPIg&t=1m02s" style="color:blue">Xmple</a><br><br> <script> const urlParams = new URLSearchParams(window.location.search); const videoId = urlParams.get('v'); const timestamp = urlParams.get('t') || '0m0s'; if (videoId) { const youtubeLink = `https://www.youtube.com/watch?v=${videoId}&t=${timestamp}`; // window.open(youtubeLink); window.location.href = youtubeLink; } else { // Handle the case when the video ID is empty console.log("Video ID is empty. Redirecting stopped."); } </script> <details> <u>Parameters:</u><br> <b>v</b>: VideoId<br> <b>t</b>: 00m00s <br> <br>Example:<br> ?v=0SQ-TJKPPIg&t=1m02s </details> <hr> <script> function generateLink() { var url = document.getElementById("youtube-url").value; var videoId = extractVideoId(url); var link = window.location.origin + "/yt?v=" + videoId; var minutes = document.getElementById("minutes").value; var seconds = document.getElementById("seconds").value; if (minutes && seconds) { var timeQuery = "&t=" + minutes + "m" + seconds + "s"; link += timeQuery; var ytlink = "https://m.youtube.com/watch?v=" + videoId + timeQuery; } document.getElementById("generated-link").textContent = link; document.getElementById("generated-ytlink").textContent = ytlink; } function extractVideoId(url) { var videoId = ""; var regex = /[?&]v=([^&#]*)/i; var match = regex.exec(url); if (match && match[1]) { videoId = match[1]; } return videoId; } </script> </head> <body> <label for="youtube-url">Enter YouTube URL:</label> <input type="text" id="youtube-url" placeholder="https://www.youtube.com/watch?v=..."> <br><br> <label for="minutes">Minutes:</label> <input type="text" id="minutes"> <label for="seconds">Seconds:</label> <input type="text" id="seconds"> <br><br> <button onclick="generateLink()">Generate Link</button> <p id="generated-link"></p> <p>YouTube Link: <span id="generated-ytlink"></span></p> ________________ Link that only shows while (not iframed) -------------------------- <style>.hide-iframed { display: none; }</style> <script> function isInIframe() { try { return window.self !== window.top; } catch (e) { return true; } } window.onload = () => { if (!isInIframe()) { document.querySelector('.hide-iframed').style.display = 'inline'; } }; </script> <a target="_blank" href="https://mega.nz/folder/wcgUXCDA#JtOYw-xbBRy4FtBJ2Cnf0A" class="hide-iframed">Mega</a> ____________________________ Fetch 15 newest Channel Youtube Videos (api) ------------------------------------------- <script> const API_KEY = 'APIKey'; const CHANNEL_ID = 'UCmIpOnd5BVx5Si2hp0WNKZw'; const MAX_RESULTS = 15; function fetchVideos() { fetch( `https://www.googleapis.com/youtube/v3/search?key=${API_KEY}&channelId=${CHANNEL_ID}&part=snippet,id&order=date&maxResults=${MAX_RESULTS}` ) .then((response) => response.json()) .then((data) => { data.items.forEach((item) => { const videoId = item.id.videoId; const title = item.snippet.title; const link = document.createElement('a'); link.href = `https://www.youtube.com/watch?v=${videoId}`; link.textContent = title; link.target = '_blank'; // Add target="_blank" to open the link in a new tab document.body.appendChild(link); const lineBreak = document.createElement('br'); document.body.appendChild(lineBreak); const additionalLineBreak = document.createElement('br'); document.body.appendChild(additionalLineBreak); }); }) .catch((error) => { console.error('Error fetching videos:', error); }); } fetchVideos(); </script> ___________________________ Fetch and display random table entry: -------------------------------- <div class="random-row-container"></div><button onclick="fetchAndPopulateData()">me</button> <style>.random-row-container {display: inline-block;} button {display: inline-block;vertical-align: top;}</style> <script> function fetchAndPopulateData() { fetch('https://alceawis.de/other/extra/personal/personality/0apersonality.html') .then(response => response.text()) .then(data => { const tempDiv = document.createElement('div'); tempDiv.innerHTML = data; const table = tempDiv.querySelector('table'); const rows = table.querySelectorAll('tr'); const randomRowIndex = Math.floor(Math.random() * rows.length); const randomRow = rows[randomRowIndex]; const displayDiv = document.querySelector('.random-row-container'); displayDiv.innerHTML = ''; const tableElement = document.createElement('table'); const newRow = tableElement.insertRow(); randomRow.querySelectorAll('td').forEach((cell, index) => { const newCell = newRow.insertCell(); newCell.innerHTML = cell.innerHTML; newCell.style.border = '1px solid #000'; newCell.style.padding = '5px 10px'; newCell.style.margin = '2px'; newCell.style.whiteSpace = 'nowrap'; }); displayDiv.appendChild(tableElement); }) .catch(error => { //fetchAndPopulateData(); console.error('Error:', error); }); } // Fetch data on page load //fetchAndPopulateData(); </script> ____________________ Table Bio Info Chart / Card: ------------------------------- <style> body { font-family: Arial, sans-serif; background-color: #f7f7f7; margin: 0; padding: 20px; } h1 { text-align: center; color: #333; } table { border-collapse: collapse; width: 50%; margin: 0 auto; background-color: #fff; border: 1px solid #ddd; } th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; } tr:nth-child(even) { background-color: #f2f2f2; } </style> <script> function calculateAge() { var birthDate = new Date('1991-01-18'); var today = new Date(); var age = today.getFullYear() - birthDate.getFullYear(); var monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { age--; } if (age > 100) { age = "100+"; } var birthDateString = birthDate.toLocaleDateString(); var ageString = '(' + age + ')'; var combinedString = birthDateString + ' ' + ageString; document.getElementById('birthdate-age').textContent = combinedString; } </script> </head> <body onload="calculateAge()"> <h1>Personal Information</h1> <table> <tr> <th>Info</th> <th></th> </tr> <tr> <td>Birthday &amp; Age</td> <td id="birthdate-age"></td> </tr> <tr> <td>Blood Type</td> <td>C</td> </tr> <tr> <td>Favorite Colors</td> <td>Red, Blue</td> </tr> <tr> <td>...</td> <td>.</td> </tr> </table> </body> </html> ____________________ Chart query (from txt & value at end) querystring ver -------------------------------------------------- <details> <a target="_blank" href="?lineslice=6&fetchTarget=mbti.txt&targeturl=https://jung" style=color:blue>mbti</a> <a target="_blank" href="?lineslice=6&fetchTarget=bigfive.txt&targeturl=https://bigfive" style=color:blue>biugfive</a> </details> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <body> <h1 id="highestPercentage"></h1> <h2 id="entryCount"></h2> <h3 id="mostFrequentValue"></h3> <canvas id="chart"></canvas> <script> const urlParams = new URLSearchParams(window.location.search); const lineSliceParam = urlParams.get('lineslice') || '6'; const targetUrl = urlParams.get('targeturl') || 'https://www.web.de'; const fetchTarget = urlParams.get('fetchTarget') || 'mbti.txt'; fetch(fetchTarget) .then(response => response.text()) .then(data => { const lines = data.split('\n'); const frequencies = {}; lines.forEach(line => { const lastSixCharacters = line.slice(-lineSliceParam); if (frequencies[lastSixCharacters]) { frequencies[lastSixCharacters]++; } else { frequencies[lastSixCharacters] = 1; } }); const sortedFrequencies = Object.entries(frequencies).sort((a, b) => b[1] - a[1]); const labels = sortedFrequencies.map(entry => entry[0]); const values = sortedFrequencies.map(entry => entry[1]); const highestFrequency = values[0]; const totalFrequencies = values.reduce((acc, val) => acc + val, 0); const highestPercentage = ((highestFrequency / totalFrequencies) * 100).toFixed(2); const highestPercentageElement = document.getElementById('highestPercentage'); highestPercentageElement.innerHTML = `<a target="_blank" href="${targetUrl}/${labels[0].toLowerCase()}.html">${labels[0]} %${highestPercentage}</a>`.replace(/[()]/g, ''); const entryCountElement = document.getElementById('entryCount'); entryCountElement.textContent = `${highestFrequency} entries vs ${lines.length} total lines`; const mostFrequentValueElement = document.getElementById('mostFrequentValue'); const ctx = document.getElementById('chart').getContext('2d'); new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: 'Frequency', data: values, backgroundColor: 'rgba(75, 192, 192, 0.6)', borderColor: 'rgba(75, 192, 192, 1)', borderWidth: 1 }] }, options: { responsive: true, scales: { y: { beginAtZero: true, precision: 0 } } } }); }) .catch(error => console.log(error)); </script> </body> ____________________ MBTI Probability crunch: -------------- <!DOCTYPE html> <html> <head> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> </head> <body> <h1 id="highestPercentage"></h1> <h2 id="entryCount"></h2> <h3 id="mostFrequentValue"></h3> <canvas id="chart"></canvas> <script> fetch('/other/extra/personal/personality/mbti.txt') .then(response => response.text()) .then(data => { const lines = data.split('\n'); const frequencies = {}; lines.forEach(line => { const lastSixCharacters = line.slice(-6); if (frequencies[lastSixCharacters]) { frequencies[lastSixCharacters]++; } else { frequencies[lastSixCharacters] = 1; } }); const sortedFrequencies = Object.entries(frequencies).sort((a, b) => b[1] - a[1]); const labels = sortedFrequencies.map(entry => entry[0]); const values = sortedFrequencies.map(entry => entry[1]); const highestFrequency = values[0]; const totalFrequencies = values.reduce((acc, val) => acc + val, 0); const highestPercentage = ((highestFrequency / totalFrequencies) * 100).toFixed(2); const highestPercentageElement = document.getElementById('highestPercentage'); highestPercentageElement.textContent = `${labels[0]} %${highestPercentage}`; const entryCountElement = document.getElementById('entryCount'); entryCountElement.textContent = `${highestFrequency} entries vs ${lines.length} total lines`; const mostFrequentValueElement = document.getElementById('mostFrequentValue'); //mostFrequentValueElement.textContent = `Most frequent of {labels[0]}`; const ctx = document.getElementById('chart').getContext('2d'); new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: 'Frequency', data: values, backgroundColor: 'rgba(75, 192, 192, 0.6)', borderColor: 'rgba(75, 192, 192, 1)', borderWidth: 1 }] }, options: { responsive: true, scales: { y: { beginAtZero: true, precision: 0 } } } }); }) .catch(error => console.log(error)); </script> ______________________ Load HTML Content on button press: ----------------------------------------------- <script> function loadBase64HTML() { var base64HTML = "PGh0bWw+PGJvZHk+VGhpcyBpcyBhIGJhc2U2NC1lbmNvZGVkIEhUTUwgY29kZSBpbiBhIG51bWJlciBvZiBhIGRpdmlkZSBkYXRhPC9ib2R5PjwvaHRtbD4="; var decodedHTML = atob(base64HTML); var contentContainer = document.getElementById('contentContainer'); contentContainer.innerHTML = decodedHTML; } </script> <button onclick="loadBase64HTML()">Load HTML</button> <div id="contentContainer"></div> __________________________ Right sideopening drawer ------------------------------ <!----Android--> <style>.container{position:relative;display:inline-block}.hidden-div{display:none;position:absolute;top:0;left:100%;padding:0px;background-color:transparent}</style> <div class="container"><img src="https://www.freepnglogos.com/uploads/android-logo-png/android-logo-0.png" alt="Android" onclick="!function(){var e=document.getElementById('hiddenDiv');e.style.display='none'===e.style.display?'block':'none'}()" width="45" height="45"><div id="hiddenDiv" class="hidden-div"> <div style="display: flex;"> <a target="_blank" href="jp.ne.ibis.ibispaintx.app"><img src="https://img.icons8.com/color/452/ibis-paint-x.png" width="45" height="45"></a> <a target="_blank" href="org.ffmpeg.gui"><img src="https://cdn.icon-icons.com/icons2/3053/PNG/512/ffmpeg_macos_bigsur_icon_190192.png" width="45" height="45"></a> <a target="_blank" href="nextapp.fx"><img src="https://www.svgrepo.com/show/504367/fx-file-explorer.svg" width="45" height="45"></a> <a target="_blank" href="com.simplemobiletools.gallery.pro"><img src="https://i.ibb.co/2ShMS3m/simplegallery.png" width="45" height="45"></a> <a target="_blank" href="com.bokhary.lazyboard"><img src="https://i.ibb.co/M8TQFwF/Lazyboard.png" width="45" height="45"></a> </div></div></div> _______________________________ Birthday reminder: ----------------------------- <div id="outputbday"></div> <script> fetchBirthday(); function fetchBirthday() { fetch('https://ry3yr.github.io/bdays.txt') .then(response => response.text()) .then(data => { const lines = data.split('\n'); const randomIndex = Math.floor(Math.random() * lines.length); const randomLine = lines[randomIndex]; const [date, name] = randomLine.split('*').map(entry => entry.trim()); const daysTillBday = daysUntilBirthday(date); const age = calculateAge(date); displayBirthdayInfo(name, daysTillBday, age); }) .catch(error => { console.error('Error:', error); }); } function daysUntilBirthday(birthday) { const today = new Date(); const nextBirthday = new Date(today.getFullYear(), new Date(birthday).getMonth(), new Date(birthday).getDate()); if (nextBirthday < today) { nextBirthday.setFullYear(today.getFullYear() + 1); } const timeDiff = nextBirthday.getTime() - today.getTime(); const daysUntil = Math.ceil(timeDiff / (1000 * 3600 * 24)); const isLeapYear = (year) => (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)); const year = nextBirthday.getFullYear(); const daysInYear = isLeapYear(year) ? 366 : 365; return daysUntil; } function calculateAge(birthday) { const today = new Date(); const birthDate = new Date(birthday); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); const dayDiff = today.getDate() - birthDate.getDate(); if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) { age--; } return age; } function displayBirthdayInfo(name, days, age) { const outputDiv = document.getElementById('outputbday'); if (Number.isNaN(days)) { fetchBirthday(); // Retry fetching a valid birthday } else { const currentDate = new Date(); const year = currentDate.getFullYear(); const isLeapYear = (year) => (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)); const daysInYear = isLeapYear(year) ? 366 : 365; if (days === 0) { outputDiv.innerHTML = `Today is <b>${name}</b>'s Birthday! (Age: ${age}) <a target="_blank" href="https://alceawis.de/bdays.txt" style="color:gray">(s)</a>`; } else if (days === daysInYear) { outputDiv.innerHTML = `Today is <b>${name}</b>'s Birthday! (Age: ${age}) <a target="_blank" href="https://alceawis.de/bdays.txt" style="color:gray">(s)</a>`; } else { outputDiv.innerHTML = `[${name}] <b>${days}</b> day(s) till birthday. (Age: ${age}) <a target="_blank" href="https://alceawis.de/bdays.txt" style="color:gray">(s)</a>`; } } } </script> ________________________ Color Links on buttonpress if querystring exists _______________________ ?user=user<br><br> <button id="showLinksButton" style="border: none;">Show Links</button> <script> document.getElementById('showLinksButton').addEventListener('click', function() { var urlParams = new URLSearchParams(window.location.search); if (urlParams.get('user') === 'user') { var links = document.querySelectorAll('a#transp'); for (var i = 0; i < links.length; i++) { links[i].style.color = 'red'; } } else { alert('Sorry, you do not have permission.'); } }); </script><br> <a id="transp" target="_blank" href="https://alcea-wisteria.de/PHP/0demo/2024-01-14-Lastplyed/lastplayed.php?gamename=${item.name}" style="color: transparent;">LinkyLink</a> ________________________ Render textfile in style: ------------------------- name https://link1 name2 https://link2 <script> fetch('https://alcea-wisteria.de/PHP/xnotes/xnotes.txt') .then(response => response.text()) .then(data => { const lines = data.trim().split('\n'); lines.forEach(line => { const [title, url] = line.split(' https://'); const fullUrl = 'https://' + url; const link = document.createElement('a'); link.href = fullUrl; link.target = '_blank'; link.textContent = title; document.body.appendChild(link); document.body.appendChild(document.createElement('br')); }); }) .catch(error => { console.log('Error fetching data:', error); }); </script> ____________________________ Hide onpage links based on querystring ------------------------------------------------- "note" querystring value determines which links stay<br> "?note=FAQ" keeps all links with FAQ in them and hides the rest<br><br> <!-----FilterLinks-with-note-querystring--> <style> .hidden { display: none; } </style> <script> // Get the value of the "note" query string parameter from the URL const urlParams = new URLSearchParams(window.location.search); const noteValue = urlParams.get("note"); // Get all the anchor tags (URLs) on the page const links = document.getElementsByTagName("a"); // Loop through each link and hide the ones that don't have the note value in their title for (let i = 0; i < links.length; i++) { const link = links[i]; const linkTitle = link.textContent; if (noteValue && linkTitle && !linkTitle.toLowerCase().includes(noteValue.toLowerCase())) { link.classList.add("hidden"); } } </script> (source: https://codepen.io/ryedai1/pen/Yzgqwaq ) ______________________________ Audio on link hover/click --------------------------------------- <!----view-source:https://molars.neocities.org/--> <script> // Mouseover/ Click sound effect- by JavaScript Kit (www.javascriptkit.com) // Visit JavaScript Kit at http://www.javascriptkit.com/ for full source code //** Usage: Instantiate script by calling: var uniquevar=createsoundbite("soundfile1", "fallbackfile2", "fallebacksound3", etc) //** Call: uniquevar.playclip() to play sound var html5_audiotypes={ //define list of audio file extensions and their associated audio types. Add to it if your specified audio file isn't on this list: "mp3": "audio/mpeg", "mp4": "audio/mp4", "ogg": "audio/ogg", "wav": "audio/wav"} function createsoundbite(sound){ var html5audio=document.createElement('audio') if (html5audio.canPlayType){ //check support for HTML5 audio for (var i=0; i<arguments.length; i++){ var sourceel=document.createElement('source') sourceel.setAttribute('src', arguments[i]) if (arguments[i].match(/\.(\w+)$/i)) sourceel.setAttribute('type', html5_audiotypes[RegExp.$1]) html5audio.appendChild(sourceel)} html5audio.load() html5audio.playclip=function(){ html5audio.pause() html5audio.currentTime=0 html5audio.play()} return html5audio} else{ return {playclip:function(){throw new Error("Your browser doesn't support HTML5 audio unfortunately")}}}} //Initialize two sound clips with 1 fallback file each: var mouseoversound=createsoundbite("https://a.tumblr.com/tumblr_ojrn7aGBii1w2e2oyo1.mp3") var clicksound=createsoundbite("https://a.tumblr.com/tumblr_ojrmy55yUN1w2e2oyo1.mp3") </script> <script> document.getElementById("img").addEventListener("click", function(){ document.getElementById("audio").play(); }); </script> <div> <div class="sidebar"> <img src=""> <a href="#" onmouseover="mouseoversound.playclip()" onclick="clicksound.playclip()">about us</a><br> <img src=""><a href="#" onmouseover="mouseoversound.playclip()" onclick="clicksound.playclip()"> resources</a><br> <img src=""><a href="#" onmouseover="mouseoversound.playclip()" onclick="clicksound.playclip()"> graphics</a><br> <img src=""> <a href="#" onmouseover="mouseoversound.playclip()" onclick="clicksound.playclip()">interests</a><br> <img src=""> <a href="#" onmouseover="mouseoversound.playclip()" onclick="clicksound.playclip()">socials</a><br> _________________________ 45degree / SouthEast animate (section) backgroundscroll: --------------------------------------------------- <style>.tile-sectionsteam {background-image: url('https://alceawis.de/other/images/bg/steambg.gif');background-repeat: repeat;background-size: 34px;animation: scroll45deg 2s linear infinite;}@keyframes scroll45deg {0% {background-position: 0 0;}100% {background-position: 34px 34px;}}</style> <div class="tile-sectionsteam"> Text<br><br><br><br><br><br><br><br><br><br><br><br> </div> ________________________________ html code listview writer: --------------------------- <style> .smart-hover:hover::after { content: attr(title); } </style> <script> function generateHTML() { // Get input values const url = document.getElementById('urlInput').value; const name = document.getElementById('nameInput').value; const imageLink = document.getElementById('imageInput').value; const description = document.getElementById('descriptionInput').value; // Create the link element const link = document.createElement('a'); link.href = url; link.target = '_blank'; // Create the image element const image = document.createElement('img'); image.src = imageLink; image.style.width = '45px'; const span = document.createElement('span'); span.classList.add('smart-hover'); span.title = ' •' + description; const nameElement = document.createElement('u'); nameElement.innerText = name; span.appendChild(nameElement); link.appendChild(image); link.appendChild(span); // Get the generated HTML code const generatedHTML = '<a target="_blank" href="' + url + '"><img src="' + imageLink + '" style="width:45px;"></img></a><span class="smart-hover" title=" •' + description + '"> <u>' + name + '</u></span><style>.smart-hover:hover::after{content:attr(title);}</style><br>'; const outputHTML = document.getElementById('outputHTML'); outputHTML.innerHTML = generatedHTML; const outputText = document.getElementById('outputText'); outputText.value = generatedHTML; } </script> </head> <body> <form> <label for="urlInput">URL:</label> <input type="text" id="urlInput" oninput="generateHTML()"><br> <label for="nameInput">Name:</label> <input type="text" id="nameInput" oninput="generateHTML()"><br> <label for="imageInput">Image Link:</label> <input type="text" id="imageInput" oninput="generateHTML()"><br> <label for="descriptionInput">Description:</label> <input type="text" id="descriptionInput" oninput="generateHTML()"><br> </form> <div id="outputHTML"></div> <textarea id="outputText" rows="10" cols="50" readonly></textarea </body> </html> __________________ HalfSize <hr> (50%<hr>) ---------------------- <hr style="width:50%;float:left;"> ___________________________ ZeroWidth Joiner between inputs (for mastodon emojis ): ------------------------------------------- <div id="textboxes"> <input type="text" id="textbox1" placeholder="Enter text 1"> <button onclick="removeTextbox(1)">-</button> <br> <input type="text" id="textbox2" placeholder="Enter text 2"> <button onclick="removeTextbox(2)">-</button> <br> </div> <button onclick="addTextbox()">+</button> <br> <button onclick="copyText()">Copy</button> <script> var textboxCount = 2; function addTextbox() { textboxCount++; var newTextbox = document.createElement("input"); newTextbox.setAttribute("type", "text"); newTextbox.setAttribute("id", "textbox" + textboxCount); newTextbox.setAttribute("placeholder", "Enter text " + textboxCount); var removeButton = document.createElement("button"); removeButton.innerHTML = "-"; removeButton.onclick = function() { removeTextbox(textboxCount); }; var container = document.getElementById("textboxes"); container.appendChild(newTextbox); container.appendChild(removeButton); container.appendChild(document.createElement("br")); } function removeTextbox(index) { var textbox = document.getElementById("textbox" + index); var removeButton = textbox.nextElementSibling; var lineBreak = removeButton.nextElementSibling; textbox.remove(); removeButton.remove(); lineBreak.remove(); } function copyText() { var joinedText = ""; for (var i = 1; i <= textboxCount; i++) { var textbox = document.getElementById("textbox" + i); joinedText += textbox.value + "\u200D"; } var tempInput = document.createElement("input"); tempInput.setAttribute("type", "text"); tempInput.setAttribute("value", joinedText); document.body.appendChild(tempInput); tempInput.select(); document.execCommand("copy"); document.body.removeChild(tempInput); alert("Text copied to clipboard: " + joinedText); } </script> <!--<input type="text" id="textbox1" placeholder="Enter text 1">+ <input type="text" id="textbox2" placeholder="Enter text 2"> <button onclick="copyText()">Copy</button> <script> function copyText() { var text1 = document.getElementById("textbox1").value; var text2 = document.getElementById("textbox2").value; var joinedText = text1 + "\u200D" + text2; var tempInput = document.createElement("input"); tempInput.setAttribute("type", "text"); tempInput.setAttribute("value", joinedText); document.body.appendChild(tempInput); tempInput.select(); document.execCommand("copy"); document.body.removeChild(tempInput); alert("Text copied to clipboard: " + joinedText); } </script>--> ___________________________ Simple detail spoiler: ---------------------------------- <details><summary>name</summary> text </details> ______________________ HTML Midi Sheetmusic render: -------------------------------------------------- <!DOCTYPE html> <html> <head> <title>Staff View</title> <script src="https://cdn.jsdelivr.net/npm/tone@14.7.58"></script> <script src="https://cdn.jsdelivr.net/npm/@magenta/music@1.23.1/es6/core.js"></script> <script src="https://cdn.jsdelivr.net/npm/focus-visible@5"></script> <script src="https://cdn.jsdelivr.net/npm/html-midi-player@1.5.0"></script> </head> <body> <div> <label for="midiUrl">Enter MIDI URL:</label> <input type="text" id="midiUrl" /> <button onclick="loadMidi()">Load MIDI</button> </div> <midi-player id="player" src="https://alceawis.de/other/music/midi/piano/ygovrains_-_Sad_Reminiscence.mid" sound-font visualizer="#myStaffVisualizer"> </midi-player> <midi-visualizer type="staff" id="myStaffVisualizer" src="https://alceawis.de/other/music/midi/piano/ygovrains_-_Sad_Reminiscence.mid"> </midi-visualizer> <script> function loadMidi() { const midiUrlInput = document.getElementById('midiUrl'); const midiUrl = midiUrlInput.value; const player = document.getElementById('player'); const visualizer = document.getElementById('myStaffVisualizer'); player.setAttribute('src', midiUrl); visualizer.setAttribute('src', midiUrl); } document.addEventListener('DOMContentLoaded', function () { loadMidi(); }); </script> </body> </html> <!--<!DOCTYPE html> <html> <head> <title>Staff View</title> <script src="https://cdn.jsdelivr.net/npm/tone@14.7.58"></script> <script src="https://cdn.jsdelivr.net/npm/@magenta/music@1.23.1/es6/core.js"></script> <script src="https://cdn.jsdelivr.net/npm/focus-visible@5"></script> <script src="https://cdn.jsdelivr.net/npm/html-midi-player@1.5.0"></script> </head> <body> <midi-player id="player" src="https://alceawis.de/other/music/midi/piano/ygovrains_-_Sad_Reminiscence.mid" sound-font visualizer="#myStaffVisualizer"> </midi-player> <midi-visualizer type="staff" id="myStaffVisualizer" src="https://alceawis.de/other/music/midi/piano/ygovrains_-_Sad_Reminiscence.mid"> </midi-visualizer> </body> </html>--> ___________________________ YearWeekDayHour Progress bar: ----------------------------------- <style> .progress-container { display: flex; align-items: center; margin-bottom: -23px; margin-right: 0px; } .progress-bar { width: 234px; height: 16px; border: 1px solid #ccc; background-color: #fff; border-radius: 43px; overflow: hidden; position: relative; } .progress-bar-fill { height: 100%; background-color: #4caf50; } .progress-label { position: absolute; top: 0; left: 50%; transform: translateX(-50%); color: #000; //font-weight: italic; } .progress-text { margin-left: 10px; } .year-progress-bar, .week-progress-bar, .day-progress-bar, .day2-progress-bar { background-color: #fff; } .year-progress-label, .week-progress-label, .day-progress-label, .day2-progress-label { color: #000; } </style> <body> <div class="progress-container"> <div class="progress-bar" id="year-progress-bar"> <div class="progress-bar-fill" id="year-progress-bar-fill"></div> <div class="progress-label" id="year-progress-label"></div> </div> <p class="progress-text" id="year-progress-text"></p> </div> <div class="progress-container"> <div class="progress-bar" id="week-progress-bar"> <div class="progress-bar-fill" id="week-progress-bar-fill"></div> <div class="progress-label" id="week-progress-label"></div> </div> <p class="progress-text" id="week-progress-text"></p> </div> <div class="progress-container"> <div class="progress-bar" id="day-progress-bar"> <div class="progress-bar-fill" id="day-progress-bar-fill"></div> <div class="progress-label" id="day-progress-label"></div> </div> <p class="progress-text" id="day-progress-text"></p> </div> <div class="progress-container"> <div class="progress-bar" id="hour-progress-bar"> <div class="progress-bar-fill" id="hour-progress-bar-fill"></div> <div class="progress-label" id="hour-progress-label"></div> </div> <p class="progress-text" id="hour-progress-text"></p> </div> <script> function updateProgressBar(progressBarId, fillId, labelId, textId, percentage, text) { document.getElementById(fillId).style.width = percentage + "%"; document.getElementById(labelId).innerHTML = Math.round(percentage) + "%"; document.getElementById(textId).innerHTML = text; } var currentDate = new Date(); var currentYear = currentDate.getFullYear(); var startDate = new Date(currentYear, 0, 1); var endDate = new Date(currentYear + 1, 0, 1); var daysPassed = Math.floor((currentDate - startDate) / (1000 * 60 * 60 * 24)); var daysPassed = daysPassed +1; //fix the current day not being taken into account !! var totalDays = Math.floor((endDate - startDate) / (1000 * 60 * 60 * 24)); var yearPercentage = (daysPassed / totalDays) * 100; var currentDayOfWeek = currentDate.getDay(); if (currentDayOfWeek === 0) { currentDayOfWeek = 7; } var totalDaysOfWeek = 7; var weekPercentage = (currentDayOfWeek / totalDaysOfWeek) * 100; var currentDayOfMonth = currentDate.getDate(); var totalDaysOfMonth = new Date(currentYear, currentDate.getMonth() + 1, 0).getDate(); var dayPercentage = (currentDayOfMonth / totalDaysOfMonth) * 100; var currentHourOfDay = currentDate.getHours(); var totalHoursOfDay = 24; var hourPercentage = (currentHourOfDay / totalHoursOfDay) * 100; updateProgressBar("year-progress-bar", "year-progress-bar-fill", "year-progress-label", "year-progress-text", yearPercentage, "Days in (" + currentYear + "): " + daysPassed + "/" + totalDays); updateProgressBar("week-progress-bar", "week-progress-bar-fill", "week-progress-label", "week-progress-text", weekPercentage, "Days of the week: " + currentDayOfWeek + "/" + totalDaysOfWeek); updateProgressBar("day-progress-bar", "day-progress-bar-fill", "day-progress-label", "day-progress-text", dayPercentage, "Days of the month: " + currentDayOfMonth + "/" + totalDaysOfMonth); updateProgressBar("hour-progress-bar", "hour-progress-bar-fill", "hour-progress-label", "hour-progress-text", hourPercentage, "Hours/day: " + currentHourOfDay + "/" + totalHoursOfDay); </script> </body> _________________________________________ Arbeitsagentur Jobcenter jobsearch: ---------------------------------------------------------- <script> function searchJobs() { var params = {}; var angebotsart = document.getElementById('angebotsart').value; if (angebotsart) { params.angebotsart = angebotsart; } var wo = document.getElementById('wo').value; if (wo) { params.wo = wo; } var umkreis = document.getElementById('umkreis').value; if (umkreis) { params.umkreis = umkreis; } var was = document.getElementById('was').value; if (was) { params.was = was; } var zeitarbeitCheckbox = document.getElementById('zeitarbeit'); if (!zeitarbeitCheckbox.checked) { params.zeitarbeit = true; } var arbeitszeit = document.getElementById('arbeitszeit').value; if (arbeitszeit) { params.arbeitszeit = arbeitszeit; } var queryString = Object.keys(params).map(function(key) { return key + '=' + encodeURIComponent(params[key]); }).join('&'); var url = "https://www.arbeitsagentur.de/jobsuche/suche?" + queryString; var decodedUrl = decodeURIComponent(url); document.getElementById('url').innerHTML = '<a href="' + url + '" target="_blank">' + decodedUrl + '</a>'; window.open(url, "_blank"); } </script> </head> <body> <label for="was">Was:</label> <input type="text" id="was" placeholder="Jobtitel" value=""><br> <label for="angebotsart">Art:</label> <input type="text" id="angebotsart" placeholder="???"><br> <label for="wo">Wo:</label> <input type="text" id="wo" placeholder=""><br> <label for="umkreis">Umkreis:</label> <input type="text" id="umkreis" placeholder="25km"><br> <label for="arbeitszeit">Arbeitszeit:</label> <input type="text" id="arbeitszeit" placeholder="vz;tz;snw;ho;mj"><br> <label for="zeitarbeit">ZA:</label> <input type="checkbox" id="zeitarbeit"><br> <button onclick="searchJobs()">Search</button> <br> <br> <p>URL: <span id="url"></span></p> </body> </html> _____________________________________ DisplayYoutube channel playlists: ----------------------- <ul id="playlists"></ul> <script> // YouTube API Key var api_key = 'apikey'; // Replace with your own YouTube Data API key // YouTube Channel ID var channel_id = new URLSearchParams(window.location.search).get('channelid') || 'UCmIpOnd5BVx5Si2hp0WNKZw'; // Replace with the desired channel ID or use the default channel ID if not provided in the query string // Search term var search_term = new URLSearchParams(window.location.search).get('keyword') || ''; // Get the keyword from the query string or set it as an empty string if not provided // Retrieve playlists using YouTube Data API var playlistsContainer = document.getElementById('playlists'); function fetchPlaylists(pageToken) { var url = `https://www.googleapis.com/youtube/v3/playlists?part=snippet&channelId=${channel_id}&maxResults=50&key=${api_key}`; // Add pageToken if provided if (pageToken) { url += `&pageToken=${pageToken}`; } // Make a GET request using Fetch API fetch(url) .then(response => response.json()) .then(data => { var playlists = data.items; if (playlists.length > 0) { playlists.forEach(playlist => { var playlistTitle = playlist.snippet.title; var playlistId = playlist.id; // Filter playlists based on the search term if (search_term === '' || playlistTitle.toLowerCase().includes(search_term.toLowerCase())) { var playlistLink = document.createElement('a'); playlistLink.href = `https://www.youtube.com/playlist?list=${playlistId}`; playlistLink.target = '_blank'; playlistLink.textContent = playlistTitle; var listItem = document.createElement('li'); listItem.appendChild(playlistLink); playlistsContainer.appendChild(listItem); } }); // Check if there are more playlists to fetch if (data.nextPageToken) { fetchPlaylists(data.nextPageToken); } } else { if (playlistsContainer.childElementCount === 0) { playlistsContainer.innerHTML = "<p>No playlists found for the channel with the provided criteria.</p>"; } } }) .catch(error => { console.error(error); }); } // Start fetching the playlists fetchPlaylists(); </script> </body> </html> ________________________________________ Querystring redirect protection: ------------------------------ <script> var time = 3 * 1; setInterval(function() { var seconds = time; var minutes = 0; if (seconds.toString().length == 1) { seconds = "0" + seconds; } if (minutes.toString().length == 1) { minutes = "0" + minutes; } document.getElementById("time").innerHTML = minutes + ":" + seconds; time--; if (time == 0) { var queryString = window.location.search; var params = new URLSearchParams(queryString); var encodedSgs = "c2F2ZWdhbWVz"; var decodedSgs = atob(encodedSgs); if (params.get(decodedSgs) !== null) { var encodedUrl = "aHR0cHM6Ly9teS5oaWRyaXZlLmNvbS9zaGFyZS8xZDNmazFhcW9i"; var decodedUrl = atob(encodedUrl); window.location.href = decodedUrl; } } }, 1000); </script> <div class="timer" onload="timer(1800)"> <div class="time"><strong>Redirecting in <span id="time">x seconds</span></strong></div> </div> __________________________________ Preview_cards extractor --------------------------------------- <body> <div id="previewCardImages"></div> <script> function extractPreviewCardLinks(responseText) { const pattern = /\/cache\/preview_cards\/images\/\d{3}\/\d{3}\/\d{3}\/original\/[a-f0-9]+\.(?:jpg|jpeg|png|gif)/gi; const matches = responseText.match(pattern); return matches || []; } function generateImageTags(imageUrls, domain) { let html = ''; imageUrls.forEach(imageUrl => { html += '<img src="' + domain + imageUrl + '" alt="Preview Card Image"><br>'; }); return html; } const url = 'https://mastodon.social/api/v1/accounts/109977878421486714/statuses'; fetch(url) .then(response => response.text()) .then(responseText => { const previewCards = extractPreviewCardLinks(responseText); const domain = 'https://files.mastodon.social'; const html = generateImageTags(previewCards, domain); document.getElementById('previewCardImages').innerHTML = html; }) .catch(error => { console.error('Error: Unable to fetch data.', error); }); </script> ___________________________ VanillaRSS -------------------------------- <script src="https://code.jquery.com/jquery-3.7.1.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.4/moment.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-rss/4.3.0/jquery.rss.min.js"></script> <script> jQuery(function($) { $("#rss-feeds").rss("http://www.ost-center.com/derniers-osts-rss", { limit: 10, entryTemplate: '<div><a href="{url}" target="_blank">{title}</a>{shortBodyPlain}</div>' }); }); </script> </head> <body> <div id="rss-feeds"></div> _________________________ CORS Tester: --------------------- <!DOCTYPE html> <html> <head> <style> iframe { width: 500px; height: 300px; border: none; } </style> <script> function checkCORSStatus() { const url = document.getElementById('urlInput').value; const xhr = new XMLHttpRequest(); xhr.open('HEAD', url); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { const corsStatus = xhr.getResponseHeader('Access-Control-Allow-Origin'); const headers = xhr.getAllResponseHeaders(); const result = document.getElementById('result'); if (corsStatus) { result.innerHTML = '<strong>CORS is enabled for the URL.</strong><br>'; result.innerHTML += 'Access-Control-Allow-Origin: ' + corsStatus + '<br>'; } else { result.innerHTML = '<strong>CORS is not enabled for the URL.</strong><br>'; } result.innerHTML += '<br><strong>Response Headers:</strong><br>' + headers; } }; xhr.send(); const iframe = document.getElementById('iframe'); iframe.src = url; } </script> </head> <body> <div> <label for="urlInput">Enter URL:</label> <input type="text" id="urlInput" placeholder="https://example.com" /> <button onclick="checkCORSStatus()">Check CORS Status</button> </div> <div id="result"></div> <iframe id="iframe"></iframe> </body> </html> ______________________________ Render Steam Library: ------------------------------------ <a target="_blank" href="https://alcea-wisteria.de/PHP/0demo/2023-11-26-SteamLibraryFetch/steamfetch.php">Fetch</a><br><br> <br> <a target="_blank" href="https://steamcommunity.com/id/alceawisteria#https://steamcommunity.com/profiles/76561198119673186/games/?tab=all">[Alcea's Steam Library]</a> <style> .game { margin-bottom: 5px; display: flex; flex-direction: column; align-items: center; } .game h2 { margin-bottom: 0px; text-align: center; } .game img { width: 100%; height: 100%; object-fit: cover; } table { border-collapse: collapse; } table td, table th { padding: 0; border: none; } </style> </head> <body> <div id="game-container"></div> <script> function getQueryStringParameter(parameter) { var urlParams = new URLSearchParams(window.location.search); return urlParams.has(parameter); } fetch('https://ry3yr.github.io/steam_library.json') .then(function(response) { if (!response.ok) { throw new Error('Error loading JSON file: ' + response.status); } return response.json(); }) .then(function(data) { var games = data.response.games; // Sort games alphabetically if "alphabetical" query string parameter exists if (getQueryStringParameter('sort')) { games.sort(function(a, b) { return a.name.localeCompare(b.name); }); } var table = document.createElement('table'); var tableHead = document.createElement('thead'); var tableBody = document.createElement('tbody'); var headerRow = document.createElement('tr'); var nameHeader = document.createElement('th'); nameHeader.textContent = 'Game Name'; var appidHeader = document.createElement('th'); appidHeader.textContent = 'App ID'; var achievementsHeader = document.createElement('th'); achievementsHeader.textContent = 'Achievements'; // New header for achievements URL var playtimeHeader = document.createElement('th'); playtimeHeader.textContent = 'Playtime'; headerRow.appendChild(nameHeader); headerRow.appendChild(appidHeader); headerRow.appendChild(achievementsHeader); // Append the new header to the header row headerRow.appendChild(playtimeHeader); // Append the playtime header to the header row tableHead.appendChild(headerRow); table.appendChild(tableHead); games.forEach(function(game) { var row = document.createElement('tr'); var nameCell = document.createElement('td'); var appidCell = document.createElement('td'); var achievementsCell = document.createElement('td'); // New cell for achievements URL var playtimeCell = document.createElement('td'); var gameLink = document.createElement('a'); gameLink.href = 'https://store.steampowered.com/app/' + game.appid; gameLink.textContent = game.name; gameLink.target = '_blank'; // Open link in a new tab or window //nameCell.appendChild(gameLink); appidCell.textContent = game.appid; achievementsCell.innerHTML = `<a target="_blank" href="https://steamcommunity.com/id/alceawisteria/stats/${game.appid}/achievements/">Achievements</a> <a id="transp" target="_blank" href="https://alcea-wisteria.de/PHP/0demo/2024-01-14-Lastplayed/lastplayed.php?gamename=${game.name}" style="color: transparent;">→</a>`; playtimeCell.textContent = game.playtime_forever; var image = document.createElement('img'); image.src = `https://cdn.cloudflare.steamstatic.com/steam/apps/${game.appid}/header.jpg`; image.style.width = '200px'; image.style.height = '35px'; image.style.objectFit = 'cover'; var imageLink = document.createElement('a'); imageLink.href = gameLink.href; imageLink.target = '_blank'; imageLink.appendChild(image); var container = document.createElement('div'); container.classList.add('game'); container.appendChild(imageLink); container.appendChild(nameCell); row.appendChild(container); row.appendChild(appidCell); row.appendChild(achievementsCell); row.appendChild(playtimeCell); tableBody.appendChild(row); }); table.appendChild(tableBody); document.getElementById('game-container').appendChild(table); }) .catch(function(error) { console.error('Error loading/parsing JSON file:', error); }); </script> ______________________________ Print txt file to div (ascii-art): ---------------------- <style> #content { width: 2000px; height: 2000px; overflow: hidden; transform-origin: top left; } #content pre { white-space: pre-wrap; word-wrap: break-word; transform: scale(1); transform-origin: top left; } </style> </head> <body> <div id="content"></div> <script> const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const txtUrl = urlParams.get('txt'); let fetchUrl = 'https://ry3yr.github.io/alceaasciipfp.txt'; if (txtUrl) { fetchUrl = txtUrl; } fetch(fetchUrl) .then(response => response.text()) .then(data => { console.log(data); const contentDiv = document.getElementById('content'); const lines = data.split('\n'); const redrawContent = (lineIndex) => { if (lineIndex >= lines.length) { return; } const line = lines[lineIndex]; const preElement = document.createElement('pre'); preElement.textContent = line; let rainbowColor; if (window.location.search.includes("rainbow")) { const lineIndex = 5; rainbowColor = `hsl(${lineIndex * 10}, 100%, 50%)`; } else { rainbowColor = "default-color"; } preElement.style.color = rainbowColor; contentDiv.appendChild(preElement); const scale = Math.min(contentDiv.offsetWidth / preElement.offsetWidth, contentDiv.offsetHeight / preElement.offsetHeight); preElement.style.transform = `scale(${scale})`; setTimeout(() => { redrawContent(lineIndex + 1); }, 200); }; redrawContent(0); }) .catch(error => { console.error('Error:', error); }); </script> </body> </html> __________________________________________________________ Mastodon Thread as comment system: ----------------------------------------------------------- <script type="module"> const styles = ` :root { --font-color: #5d686f; --font-size: 1.0rem; --block-border-width: 1px; --block-border-radius: 3px; --block-border-color: #ededf0; --block-background-color: #f7f8f8; --comment-indent: 40px; } #mastodon-comments-list { margin: 0 auto; } .mastodon-comment { background-color: var(--block-background-color); border-radius: var(--block-border-radius); border: var(--block-border-width) var(--block-border-color) solid; padding: 20px; margin-bottom: 1.5rem; display: flex; flex-direction: column; color: var(--font-color); font-size: var(--font-size); } .mastodon-comment p { margin-bottom: 0px; } .mastodon-comment .author { padding-top:0; display:flex; } .mastodon-comment .author a { text-decoration: none; } .mastodon-comment .author .avatar img { margin-right:1rem; min-width:60px; border-radius: 5px; } .mastodon-comment .author .details { display: flex; flex-direction: column; } .mastodon-comment .author .details .name { font-weight: bold; } .mastodon-comment .author .details .user { color: #5d686f; font-size: medium; } .mastodon-comment .author .date { margin-left: auto; font-size: small; } .mastodon-comment .content { margin: 15px 20px; } .mastodon-comment .attachments { margin: 0px 10px; } .mastodon-comment .attachments > * { margin: 0px 10px; } .mastodon-comment .attachments img { max-width: 100%; } .mastodon-comment .content p:first-child { margin-top:0; margin-bottom:0; } .mastodon-comment .status > div { display: inline-block; margin-right: 15px; } .mastodon-comment .status a { color: #5d686f; text-decoration: none; } .mastodon-comment .status .replies.active a { color: #003eaa; } .mastodon-comment .status .reblogs.active a { color: #8c8dff; } .mastodon-comment .status .favourites.active a { color: #ca8f04; } `; class MastodonComments extends HTMLElement { constructor() { super(); this.host = this.getAttribute("host"); this.user = this.getAttribute("user"); this.tootId = this.getAttribute("tootId"); this.commentsLoaded = false; const styleElem = document.createElement("style"); styleElem.innerHTML = styles; document.head.appendChild(styleElem); } connectedCallback() { this.innerHTML = ` <h2>Comments</h2> <noscript> <div id="error"> Please enable JavaScript to view the comments powered by the Fediverse. </div> </noscript> <p>You can use your Fediverse (i.e. Mastodon, among many others) account to reply to this <a class="link" href="https://${this.host}/@${this.user}/${this.tootId}">post</a>. </p> <p id="mastodon-comments-list"></p> `; const comments = document.getElementById("mastodon-comments-list"); const rootStyle = this.getAttribute("style"); if (rootStyle) { comments.setAttribute("style", rootStyle); } this.respondToVisibility(comments, this.loadComments.bind(this)); } escapeHtml(unsafe) { return (unsafe || "") .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); } toot_active(toot, what) { var count = toot[what + "_count"]; return count > 0 ? "active" : ""; } toot_count(toot, what) { var count = toot[what + "_count"]; return count > 0 ? count : ""; } user_account(account) { var result = `@${account.acct}`; if (account.acct.indexOf("@") === -1) { var domain = new URL(account.url); result += `@${domain.hostname}`; } return result; } render_toots(toots, in_reply_to, depth) { var tootsToRender = toots .filter((toot) => toot.in_reply_to_id === in_reply_to) .sort((a, b) => a.created_at.localeCompare(b.created_at)); tootsToRender.forEach((toot) => this.render_toot(toots, toot, depth)); } render_toot(toots, toot, depth) { toot.account.display_name = this.escapeHtml(toot.account.display_name); toot.account.emojis.forEach((emoji) => { toot.account.display_name = toot.account.display_name.replace( `:${emoji.shortcode}:`, `<img src="${this.escapeHtml(emoji.static_url)}" alt="Emoji ${ emoji.shortcode }" height="20" width="20" />`, ); }); const mastodonComment = `<div class="mastodon-comment" style="margin-left: calc(var(--comment-indent) * ${depth})"> <div class="author"> <div class="avatar"> <img src="${this.escapeHtml( toot.account.avatar_static, )}" height=60 width=60 alt=""> </div> <div class="details"> <a class="name" href="${toot.account.url}" rel="nofollow">${ toot.account.display_name }</a> <a class="user" href="${ toot.account.url }" rel="nofollow">${this.user_account(toot.account)}</a> </div> <a class="date" href="${ toot.url }" rel="nofollow">${toot.created_at.substr( 0, 10, )} ${toot.created_at.substr(11, 8)}</a> </div> <div class="content">${toot.content}</div> <div class="attachments"> ${toot.media_attachments .map((attachment) => { if (attachment.type === "image") { return `<a href="${attachment.url}" rel="nofollow"><img src="${ attachment.preview_url }" alt="${this.escapeHtml(attachment.description)}" /></a>`; } else if (attachment.type === "video") { return `<video controls><source src="${attachment.url}" type="${attachment.mime_type}"></video>`; } else if (attachment.type === "gifv") { return `<video autoplay loop muted playsinline><source src="${attachment.url}" type="${attachment.mime_type}"></video>`; } else if (attachment.type === "audio") { return `<audio controls><source src="${attachment.url}" type="${attachment.mime_type}"></audio>`; } else { return `<a href="${attachment.url}" rel="nofollow">${attachment.type}</a>`; } }) .join("")} </div> <div class="status"> <div class="replies ${this.toot_active(toot, "replies")}"> <a href="${ toot.url }" rel="nofollow"><i class="fa fa-reply fa-fw"></i>${this.toot_count( toot, "replies", )}</a> </div> <div class="reblogs ${this.toot_active(toot, "reblogs")}"> <a href="${ toot.url }" rel="nofollow"><i class="fa fa-retweet fa-fw"></i>${this.toot_count( toot, "reblogs", )}</a> </div> <div class="favourites ${this.toot_active(toot, "favourites")}"> <a href="${ toot.url }" rel="nofollow"><i class="fa fa-star fa-fw"></i>${this.toot_count( toot, "favourites", )}</a> </div> </div> </div>`; var div = document.createElement("div"); div.innerHTML = typeof DOMPurify !== "undefined" ? DOMPurify.sanitize(mastodonComment.trim()) : mastodonComment.trim(); document .getElementById("mastodon-comments-list") .appendChild(div.firstChild); this.render_toots(toots, toot.id, depth + 1); } loadComments() { if (this.commentsLoaded) return; document.getElementById("mastodon-comments-list").innerHTML = "Loading comments from the Fediverse..."; let _this = this; fetch( "https://" + this.host + "/api/v1/statuses/" + this.tootId + "/context", ) .then((response) => response.json()) .then((data) => { if ( data["descendants"] && Array.isArray(data["descendants"]) && data["descendants"].length > 0 ) { document.getElementById("mastodon-comments-list").innerHTML = ""; _this.render_toots(data["descendants"], _this.tootId, 0); } else { document.getElementById("mastodon-comments-list").innerHTML = "<p>Not comments found</p>"; } _this.commentsLoaded = true; }); } respondToVisibility(element, callback) { var options = { root: null, }; var observer = new IntersectionObserver((entries, observer) => { entries.forEach((entry) => { if (entry.intersectionRatio > 0) { callback(); } }); }, options); observer.observe(element); } } customElements.define("mastodon-comments", MastodonComments); </script> <div id="comments_thread"></div> <mastodon-comments host="pb.todon.de" user="alcea" tootId="111359759683825226"></mastodon-comments> ____________________________________ Load external html into dom / cross: -------------------------------------------- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ var baseUrl = "https://ry3yr.github.io/mtdalceacomment"; $("#mtdcomm").load(baseUrl + ""); }); </script> <div class="formClass"> <div id="mtdcomm"> </div> </div> _____________________________ AdblockDetect: ------------------------ <!DOCTYPE html> <html> <head> <style> .message { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 20px; background-color: #fff; border: 1px solid #ccc; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); z-index: 9999; } .message.show { display: block; } </style> </head> <body> <p class="message">..</p> <script> fetch('https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js').then(() => { document.getElementsByClassName('message')[0].innerHTML = 'Adblock disabled.. Consider installing one !'; document.getElementsByClassName('message')[0].classList.add('no-ab'); document.getElementsByClassName('message')[0].classList.add('show'); }).catch(() => { document.getElementsByClassName('message')[0].innerHTML = 'Adblock detected! Good Job !<br>Party time !'; document.getElementsByClassName('message')[0].classList.add('ab'); document.getElementsByClassName('message')[0].classList.add('show'); }); document.addEventListener('click', function(event) { var message = document.getElementsByClassName('message')[0]; if (event.target !== message && !message.contains(event.target)) { message.classList.remove('show'); } }); </script> </body> </html> ______________________________________________ Advanced Mastodon & Akkoma Timeline / TL render (with querystring support): (complex6) ------------------------------------------------------------------------------------------------------------ <a href="?instance=mastodon.social&userid=109977878421486714">Ryedai</a> <a href="?instance=awau.social&userid=111347725791284150">AlceaAwau</a> <a href="?instance=pb.todon.de&userid=109629985010224381">Alcea</a> <a href="?instance=sapphic.engineer&userid=peng">[Akkoma]</a> <body> <div id="feed"></div> <script> function getQueryStringParams(url) { const params = {}; const urlParams = new URLSearchParams(url); for (const [key, value] of urlParams) { params[key] = value; } return params; } // Get the query parameters from the URL const queryParams = getQueryStringParams(window.location.search); const instance = queryParams.instance || "pb.todon.de"; const userid = queryParams.userid || "109629985010224381"; // Function to replace emoji shortcodes with images function replaceEmojis(content, customEmojis) { customEmojis.forEach(customEmoji => { const shortcode = customEmoji.shortcode; const url = customEmoji.url; const shortcodePattern = new RegExp(':' + shortcode + ':', 'g'); const emojiTag = '<img src="' + url + '" alt="' + shortcode + '" width="20px">'; content = content.replace(shortcodePattern, emojiTag); }); return content; } // Function to fetch data from the selected API URL function fetchData(apiUrl) { const accessToken = 'your-access-token'; fetch(apiUrl, { headers: { 'Authorization': 'Bearer ' + accessToken } }) .then(response => response.json()) .then(data => { // Clear the feed before appending new content document.getElementById('feed').innerHTML = ''; data.forEach(status => { let content = replaceEmojis(status.content, customEmojis); let media = ''; let avatar = ''; let tootLink = ''; // Check for YouTube video link const youtubeRegex = /https?:\/\/(www\.)?(m\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/; const invidiousRegex = /https?:\/\/(www\.)?(m\.)?super8\.absturztau\.be\/watch\?v=([-a-zA-Z0-9_]{11})/; const youtubeMatch = status.content.match(youtubeRegex) || status.content.match(invidiousRegex); if (youtubeMatch) { // Extract video ID from YouTube link const videoId = youtubeMatch[3]; // Create embedded player for YouTube video media = `<div><iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe></div>`; } else { // Check for Pixiv artwork link const pixivRegex = /https?:\/\/(?:www\.)?pixiv\.net\/(?:en\/)?artworks\/(\d+)/; const pixivMatch = status.content.match(pixivRegex); if (pixivMatch) { // Extract artwork ID from Pixiv link const artworkId = pixivMatch[1]; // Create image preview for Pixiv artwork media = `<div><img src="https://embed.pixiv.net/decorate.php?illust_id=${artworkId}&mode=sns-automator" width="50%"></div>`; } else { const tenorRegex = /https?:\/\/(?:www\.)?tenor\.com\/view\/[a-zA-Z0-9-]+-(\d+)/; const tenorMatch = status.content.match(tenorRegex); if (tenorMatch) { // Extract Tenor.com video ID const videoId = tenorMatch[1]; // Create embedded player for Tenor.com video media = `<div><iframe src="https://tenor.com/embed/${videoId}" frameborder="0" allowfullscreen="true" width="100%" height="400px"></iframe></div>`; } else { // Check for Dailymotion video link const dailymotionRegex = /https?:\/\/(www\.)?dailymotion\.com\/video\/([a-zA-Z0-9_-]+)/; const dailymotionMatch = status.content.match(dailymotionRegex); if (dailymotionMatch) { const videoId = dailymotionMatch[2]; media = `<div><iframe frameborder="0" width="480" height="270" src="https://www.dailymotion.com/embed/video/${videoId}?autoplay=0" allowfullscreen allow="autoplay"></iframe></div>`; }else{ // Check for Vidlii video link const vidliiRegex = /https?:\/\/(www\.)?(m\.)?vidlii\.com\/watch\?v=([a-zA-Z0-9_-]+)/; const vidliiMatch = status.content.match(vidliiRegex); if (vidliiMatch) { const videoId = vidliiMatch[3]; media = `<iframe allowfullscreen src="https://www.vidlii.com/embed?v=${videoId}" frameborder="0" width="640" height="360"></iframe><br>`; }else{ // Check Soundcloud Link const soundcloudRegex = /https?:\/\/(m\.)?soundcloud\.com\/([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_-]+)/; const soundcloudMatch = status.content.match(soundcloudRegex); if (soundcloudMatch) { const soundcloudUrl = `https://w.soundcloud.com/player/?url=${encodeURIComponent(soundcloudMatch[0] )}&color=0066cc&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true`; //media = `<iframe width="100%" height="300" scrolling="no" frameborder="no" allow="autoplay" src="${soundcloudUrl}"></iframe>`; media = `<iframe allowfullscreen src="${status.url}/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="600" height=400"></iframe><script src="embed.js" async><\/\script><br>`; } else { // Check for imgbb image URLs in the status content const imageRegex = /(https?:\/\/(?:www\.)?i\.ibb\.co\/[^ ]+\.(?:jpg|png|gif|bmp))/g; const imageMatches = status.content.match(imageRegex); if (imageMatches) { media += '<div>'; imageMatches.forEach(url => { media += '<img src="' + url + '" width="50%"><br>'; }); media += '</div>'; } } } } } } } // Check for video and audio attachments if (status.media_attachments.length > 0) { media = '<div>'; status.media_attachments.forEach(attachment => { if (attachment.type === 'image') { media += '<img src="' + attachment.url + '" width="50%"><br>'; } else if (attachment.type === 'video') { media += '<video src="' + attachment.url + '" controls width="50%"></video><br>'; } else if (attachment.type === 'audio') { media += '<audio src="' + attachment.url + '" controls></audio><br>'; } }); media += '</div>'; } // Check for spoiler tag if (status.url.includes('activity')) { avatar = '<!--<details><summary>Avatar (spoiler)</summary><img src="' + status.account.avatar + '" width="100px"></details>--><br>'; tootLink = '<!--<details><summary>View on Mastodon (spoiler)</summary><a href="' + status.url + '" target="_blank">Click here to view activity</a></details>-->'; } else { avatar = '<img src="' + status.account.avatar + '" width="100px"><br>'; tootLink = '<a href="' + status.url + '" target="_blank">View on Mastodon</a>'; } // Get the date of the status const date = new Date(status.created_at); // Append content to feed const contentHtml = `<div>${content}</div>`; const statusHtml = `${contentHtml}${media}${avatar}${tootLink}&nbsp;${date.toLocaleString()}<hr>`; document.getElementById('feed').innerHTML += statusHtml; }); }) .catch(error => console.error(error)); } // Add emoji renderer const emojiEndpoint = `https://${instance}/api/v1/custom_emojis`; fetch(emojiEndpoint) .then(response => response.json()) .then(customEmojis => { // Store custom emojis for later use window.customEmojis = customEmojis; // Event listener for radio button change const radioButtons = document.querySelectorAll('input[name="apiUrl"]'); radioButtons.forEach(radioButton => { radioButton.addEventListener('change', function() { const selectedApiUrl = this.value; fetchData(selectedApiUrl); }); }); // Initial fetch using the default API URL const defaultApiUrl = `https://${instance}/api/v1/accounts/${userid}/statuses`; fetchData(defaultApiUrl); }); </script> </body> _________________________________________ Display mutes for account (rqs apikey): -------------------------------------------------------- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> $(document).ready(function() { var entities = []; var muteCount = 0; // Variable to store the number of mutes function fetchEntities(url) { $.ajax({ url: url, type: 'GET', dataType: 'json', headers: { 'Authorization': 'Bearer apikeyhere' }, success: function(response, textStatus, xhr) { var newEntities = response; for (var i = 0; i < newEntities.length; i++) { var entity = newEntities[i]; entities.push(entity); muteCount++; // Increment the mute count } var linkHeader = xhr.getResponseHeader('Link'); var nextLink = extractLinkUrl(linkHeader, 'next'); if (entities.length >= 1000) { // Render the fetched entities renderEntities(); } else if (nextLink) { // Fetch the next set of entities fetchEntities(nextLink); } else { console.log('Finished fetching entities'); // Render the fetched entities renderEntities(); } }, error: function(xhr, status, error) { console.log('Error: ' + error); } }); } function extractLinkUrl(linkHeader, rel) { var links = linkHeader.split(','); for (var i = 0; i < links.length; i++) { var link = links[i].trim(); var regex = /<([^>]+)>;\s*rel="([^"]+)"/g; var match = regex.exec(link); if (match && match[2] === rel) { return match[1]; } } return null; } function renderEntities() { // Display the mute count at the top var muteCountHtml = '<p>Mute Count: ' + muteCount + '</p>'; $('#muteCount').html(muteCountHtml); for (var i = 0; i < entities.length; i++) { var entity = entities[i]; var entityHtml = '<p>'; entityHtml += '<a target="_blank" href="' + entity.url + '">' + entity.url + '</a><br>'; //entityHtml += 'Date: ' + entity.created_at + '<br>'; entityHtml += '</p>'; $('#entities').append(entityHtml); } } // Start fetching entities from the initial URL var initialUrl = 'https://pb.todon.de/api/v1/mutes'; fetchEntities(initialUrl); }); </script> </head> <body> <div id="muteCount"></div> <!-- Display the mute count here --> <div id="entities"></div> </body> </html> _________________________________ MTD Graph posts over last 7 days: --------------------------------- Posts/Day for @alcea@pb.todon.de<br> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <style> canvas { display: block; max-width: 800px; margin: 0 auto; } </style> </head> <body> <canvas id="dateChart"></canvas> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <div id="entities"></div> <script> $(document).ready(function() { var endpoint = 'https://pb.todon.de/api/v1/accounts/109629985010224381/statuses'; var entities = []; var dates = []; fetchEntities(); function fetchEntities() { var url = endpoint; if (entities.length > 0) { var linkHeader = '<https://pb.todon.de/api/v1/accounts/109629985010224381/statuses?max_id=' + entities[entities.length - 1].id + '>; rel="next"'; $.ajaxSetup({ headers: { 'Link': linkHeader } }); } $.ajax({ url: url, type: 'GET', dataType: 'json', headers: { 'Authorization': 'Bearer token' }, success: function(response, textStatus, xhr) { var newEntities = response; for (var i = 0; i < newEntities.length; i++) { var entity = newEntities[i]; entities.push(entity); // Filter out the date var date = new Date(entity.created_at); dates.push(date); } var linkHeader = xhr.getResponseHeader('Link'); var nextLink = extractLinkUrl(linkHeader, 'next'); if (entities.length >= 200) { // Render the fetched entities renderEntities(); // Plot the dates plotDates(); } else if (nextLink) { // Fetch the next set of entities endpoint = nextLink; fetchEntities(); } else { console.log('Finished fetching 200 entities'); // Render the fetched entities renderEntities(); // Plot the dates plotDates(); } }, error: function(xhr, status, error) { console.log('Error: ' + error); } }); } function extractLinkUrl(linkHeader, rel) { var links = linkHeader.split(','); for (var i = 0; i < links.length; i++) { var link = links[i].trim(); var regex = /<([^>]+)>;\s*rel="([^"]+)"/g; var match = regex.exec(link); if (match && match[2] === rel) { return match[1]; } } return null; } function renderEntities() { for (var i = 0; i < entities.length; i++) { var entity = entities[i]; // Render the URL and date on the page var entityHtml = '<p>'; //entityHtml += 'URL: <a href="' + entity.url + '">' + entity.url + '</a><br>'; //entityHtml += 'Date: ' + entity.created_at + '<br>'; //entityHtml += '</p>'; $('#entities').append(entityHtml); } } function plotDates() { // Filter the dates for the last 7 days var today = new Date(); var lastSevenDays = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000); var filteredDates = dates.filter(function(date) { return date >= lastSevenDays && date <= today; }); // Count the occurrences of each date var dateCounts = {}; filteredDates.forEach(function(date) { var dateString = date.toISOString().split('T')[0]; dateCounts[dateString] = (dateCounts[dateString] || 0) + 1; }); // Extract the dates and counts as separate arrays var chartLabels = Object.keys(dateCounts); var chartData = Object.values(dateCounts); // Create the chart using Chart.js var ctx = document.getElementById('dateChart').getContext('2d'); var chart = new Chart(ctx, { type: 'line', data: { labels: chartLabels, datasets: [{ label: 'Date Counts', data: chartData, fill: false, borderColor: 'rgba(0, 123, 255,0.8)', borderWidth: 2 }] }, options: { scales: { x: { display: true, title: { display: true, text: 'Date' }, reverse: true // Add this line to reverse the x-axis }, y: { display: true, title: { display: true, text: 'Count' }, beginAtZero: true, stepSize: 1 } } } }); } }); </script> __________________________________________________ MTD Fetch more than 40 statuse (via pagination): -------------------------------------------------- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <div id="entities"></div> <script> $(document).ready(function() { var endpoint = 'https://pb.todon.de/api/v1/accounts/109629985010224381/statuses'; var entities = []; fetchEntities(); function fetchEntities() { var url = endpoint; if (entities.length > 0) { var linkHeader = '<https://pb.todon.de/api/v1/accounts/109629985010224381/statuses?max_id=' + entities[entities.length - 1].id + '>; rel="next"'; $.ajaxSetup({ headers: { 'Link': linkHeader } }); } $.ajax({ url: url, type: 'GET', dataType: 'json', headers: { 'Authorization': 'Bearer token' }, success: function(response, textStatus, xhr) { var newEntities = response; for (var i = 0; i < newEntities.length; i++) { var entity = newEntities[i]; entities.push(entity); } var linkHeader = xhr.getResponseHeader('Link'); var nextLink = extractLinkUrl(linkHeader, 'next'); if (entities.length >= 200) { // Render the fetched entities renderEntities(); } else if (nextLink) { // Fetch the next set of entities endpoint = nextLink; fetchEntities(); } else { console.log('Finished fetching 200 entities'); // Render the fetched entities renderEntities(); } }, error: function(xhr, status, error) { console.log('Error: ' + error); } }); } function extractLinkUrl(linkHeader, rel) { var links = linkHeader.split(','); for (var i = 0; i < links.length; i++) { var link = links[i].trim(); var regex = /<([^>]+)>;\s*rel="([^"]+)"/g; var match = regex.exec(link); if (match && match[2] === rel) { return match[1]; } } return null; } function renderEntities() { for (var i = 0; i < entities.length; i++) { var entity = entities[i]; // Render the URL and date on the page var entityHtml = '<p>'; entityHtml += 'URL: <a href="' + entity.url + '">' + entity.url + '</a><br>'; entityHtml += 'Date: ' + entity.created_at + '<br>'; entityHtml += '</p>'; $('#entities').append(entityHtml); } } }); </script> __________________________________________ Find Emojo/Blobcat Game: ----------------------------------------- <a target="_blank" href="?instance=mastodon.social&random=no">QueryString</a><br><br> <style> .emoji { width: 45px; cursor: pointer; } .highlight { outline: 2px solid green; } .mirror { transform: scaleX(1); } </style> </head> <body> <div id="emojiContainer"></div> <script> document.addEventListener('DOMContentLoaded', function() { var customEmojis = []; function fetchEmojis() { var endpoint = 'https://pb.todon.de/api/v1/custom_emojis'; var urlParams = new URLSearchParams(window.location.search); var instanceParam = urlParams.get('instance'); if (instanceParam) { endpoint = 'https://' + instanceParam + '/api/v1/custom_emojis'; } fetch(endpoint) .then(function(response) { return response.json(); }) .then(function(data) { customEmojis = data; displayEmojis(); }); } function displayEmojis() { var emojiContainer = document.getElementById('emojiContainer'); emojiContainer.innerHTML = ''; var urlParams = new URLSearchParams(window.location.search); var randomParam = urlParams.get('random'); var shouldRandomize = randomParam !== 'no'; var shuffledEmojis = shouldRandomize ? customEmojis.sort(function() { return 0.5 - Math.random(); }) : customEmojis; shuffledEmojis.forEach(function(customEmoji) { var shortcode = customEmoji.shortcode; var url = customEmoji.url; var emojiElement = document.createElement('img'); emojiElement.src = url; emojiElement.alt = shortcode; emojiElement.width = '45px'; emojiElement.title = shortcode; emojiElement.classList.add('emoji'); emojiElement.addEventListener('click', function() { emojiElement.classList.add('highlight'); setTimeout(function() { emojiElement.classList.remove('highlight'); checkEmojiFound(customEmoji); }, 1000); }); emojiContainer.appendChild(emojiElement); }); var randomIndex = Math.floor(Math.random() * customEmojis.length); var randomEmoji = customEmojis[randomIndex]; var emojiToFind = document.createElement('div'); emojiToFind.classList.add('emoji-to-find'); var emojiToFindImage = document.createElement('img'); emojiToFindImage.src = randomEmoji.url; emojiToFindImage.alt = randomEmoji.shortcode; emojiToFindImage.width = '45px'; emojiToFindImage.title = randomEmoji.shortcode; emojiToFind.appendChild(emojiToFindImage); var emojiToFindText = document.createElement('p'); //emojiToFindText.textContent = 'Find this emoji: ' + randomEmoji.shortcode; emojiToFind.appendChild(emojiToFindText); emojiContainer.insertBefore(emojiToFind, emojiContainer.firstChild); var emojiImages = document.querySelectorAll('.emoji'); var randomEmojiImage = emojiImages[Math.floor(Math.random() * emojiImages.length)]; var emojiToFindImageClone = randomEmojiImage.cloneNode(true); emojiToFindImageClone.classList.add('mirror'); emojiToFindImage.parentNode.replaceChild(emojiToFindImageClone, emojiToFindImage); } function checkEmojiFound(emoji) { var emojiContainer = document.getElementById('emojiContainer'); var emojiToFind = emojiContainer.querySelector('.emoji-to-find'); var foundEmoji = emojiToFind.querySelector('img').alt; if (emoji.shortcode === foundEmoji) { emojiContainer.removeChild(emojiToFind); fetchEmojis(); } } fetchEmojis(); }); </script> _____________________________________________ Redirect to Mastodon home JS Scriptlet: --------------------------------------------------------------- javascript:(function() { var encodedUrl = encodeURIComponent(window.location.href); var redirectUrl = "https://alcea-wisteria.de/PHP/0demo/2023-10-22-MastodonTootSearch/searchmastourl.php?search=" + encodedUrl + "&pbUrl=https%3A%2F%2Fpb.todon.de&apikey=apikeyhere"; window.location.href = redirectUrl; })(); ________________________________________________ Multifeed Mastodon Scroll base (with "open at home", if ?takemehome is in the url) -------------------------------------------- <!--https://pb.todon.de/@alcea/111285111844791912--https://fedionfire.stream/--> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <style> #messageContainer { height: 100%; overflow: auto; border: 1px solid #ccc; padding: 10px; } .message { display: flex; align-items: center; margin-bottom: 10px; padding: 5px; background-color: #f5f5f5; border: 1px solid #ddd; } .avatar { width: 40px; height: 40px; border-radius: 50%; margin-right: 10px; } .message-content { flex-grow: 1; } .message-link { color: #007bff; text-decoration: none; } </style> </head> <body> <div id="messageContainer"></div> <script> $(document).ready(function() { function fetchMastodonStatuses(instanceUrls) { const limit = 35; instanceUrls.forEach(function(instanceUrl) { const url = `${instanceUrl}/api/v1/timelines/public?limit=${limit}&exclude_bots=true`; $.ajax({ url: url, method: 'GET', dataType: 'json', success: function(data) { const statuses = data; parseMastodonStatuses(statuses); }, error: function(error) { console.error('Error fetching Mastodon statuses:', error); } }); }); } function parseMastodonStatuses(statuses) { var messageContainer = $('#messageContainer'); statuses.forEach(function(status) { var avatarUrl = status.account.avatar; var displayName = status.account.display_name; var content = status.content; //var postUrl = status.url; //var postUrl = `https://alcea-wisteria.de/PHP/0demo/2023-10-22-MastodonTootSearch/searchmastourl.php?search=${encodeURIComponent(status.url)}&pbUrl=https://pb.todon.de&apikey=apikey`; //var postUrl = window.location.search.includes('takemehome') ? `https://alcea-wisteria.de/PHP/0demo/2023-10-22-MastodonTootSearch/searchmastourl.php?search=${encodeURIComponent(status.url)}&pbUrl=https://pb.todon.de&apikey=5x1Wr4gL-rM_Q0akdLZIQrcO2PBhEby39HatYQFmVvs` : status.url; var postUrl = window.location.search.includes('takemehome') && !status.url.includes('notes') && !status.url.includes('notice') ? `https://alcea-wisteria.de/PHP/0demo/2023-10-22-MastodonTootSearch/searchmastourl.php?search=${encodeURIComponent(status.url)}&pbUrl=https://pb.todon.de&apikey=5x1Wr4gL-rM_Q0akdLZIQrcO2PBhEby39HatYQFmVvs` : status.url; var message = $('<div>').addClass('message'); var avatar = $('<img>').attr('src', avatarUrl).addClass('avatar'); var messageContent = $('<div>').addClass('message-content').html(content); var link = $('<a>').attr('href', postUrl).attr('target', '_blank').addClass('message-link').text('View Post'); message.append(avatar); message.append(messageContent); messageContent.append('<br>'); messageContent.append(link); messageContainer.append(message); }); } var instanceUrls = ['https://mastodon.social', 'https://pb.todon.de', 'https://kopimi.space', 'https://sunnygarden.org', 'https://mementomori.social']; fetchMastodonStatuses(instanceUrls); }); </script> </body> </html> __________________________ Mastodon Public Timeline(s) (with "open at home" feature): -------------------------------------------------- <!DOCTYPE html> <html> <head> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head> <body> <select id="instanceDropdown"> <option value="https://pb.todon.de">pb.todon.de</option> <option value="https://mastodon.social">mastodon.social</option> <option value="https://kopimi.space">kopimi.space</option> <option value="https://sunny.garden">Sunny.Garden</option> </select> <label for="localCheckbox"> <input type="checkbox" id="localCheckbox"> Local </label> <input type="text" id="textboxUrl" placeholder="Enter home instance URL"> <input type="password" id="apikey" placeholder="API key"> <div id="statuses"></div> <script> // Function to fetch Mastodon statuses based on the selected instance and local checkbox state function fetchMastodonStatuses(instanceUrl) { const localCheckbox = $('#localCheckbox').prop('checked'); const url = `${instanceUrl}/api/v1/timelines/public?limit=50${localCheckbox ? '&local=true' : ''}`; $.ajax({ url: url, method: 'GET', dataType: 'json', headers: { 'Authorization': `Bearer ${$('#apikey').val()}` }, success: function(data) { const statuses = data; parseMastodonStatuses(statuses); }, error: function(error) { console.error('Error fetching Mastodon statuses:', error); } }); } // Function to handle shortcode function handleShortcode(shortcode) { // Your shortcode handling logic here console.log(`Handling shortcode: ${shortcode}`); } // Function to parse Mastodon statuses function parseMastodonStatuses(statuses) { $('#statuses').empty(); // Clear existing statuses const textboxUrl = $('#textboxUrl').val(); statuses.forEach((status) => { // Check if the status has a shortcode if (status.shortcode) { handleShortcode(status.shortcode); } // Process other status data as needed console.log(`Processing status: ${status.content}`); // Create a link to the status const statusLink = `${status.content}`; const apikey = $('#apikey').val(); // Check if the textbox URL is already part of the status URL const url = textboxUrl && !status.url.includes(textboxUrl) && !status.url.includes('notes' || 'plasmatrap') ? `https://alcea-wisteria.de/PHP/0demo/2023-10-22-MastodonTootSearch/searchmastourl.php?search=${status.url}&pbUrl=${textboxUrl}&apikey=${apikey}` : status.url; // Render the status and link on the page $('#statuses').append(`${status.content} <a href="${url}" target="_blank">${status.account.display_name}</a></p><hr>`); }); } // Function to handle dropdown change event function handleDropdownChange() { // Get the selected instance URL const selectedInstance = $('#instanceDropdown').val(); // Fetch Mastodon statuses for the selected instance fetchMastodonStatuses(selectedInstance); } // Add event listener to the dropdown $('#instanceDropdown').on('change', handleDropdownChange); // Add event listeners to the URL and API key textboxes $('#textboxUrl, #apikey').on('input', function() { // Call the fetchMastodonStatuses function to refetch the current state const selectedInstance = $('#instanceDropdown').val(); fetchMastodonStatuses(selectedInstance); }); // Initial fetch for the default instance const defaultInstance = 'https://pb.todon.de'; fetchMastodonStatuses(defaultInstance); </script> </body> </html> <script> window.onload = function() { const homeUrlParam = new URLSearchParams(window.location.search).get('homeurl'); const apiKeyParam = new URLSearchParams(window.location.search).get('apikey'); const homeUrlInput = document.getElementById('textboxUrl'); const apiKeyInput = document.getElementById('apikey'); if (homeUrlParam && apiKeyParam) { homeUrlInput.value = homeUrlParam; apiKeyInput.value = apiKeyParam; } }; </script> <!--php-helper--> <a href="?search=https://sunny.garden/@Iva852/109293246960188756&pbUrl=https://pb.todon.de&apikey=orfvAYfV1Sy4DtJCYN32J48Mbjh50kFTywcWEWmLHSM">test</a><br> <?php // Check if the API key is submitted if (isset($_POST['apikey'])) { $apiKey = $_POST['apikey']; $pbUrl = $_POST['pbUrl']; $search = $_POST['url']; $url = $pbUrl . '/api/v2/search/?q=' . urlencode($search) . '&limit=1&resolve=true'; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $apiKey ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Execute the request $response = curl_exec($ch); if (curl_errno($ch)) { echo 'Error: ' . curl_error($ch); } curl_close($ch); $data = json_decode($response, true); if (isset($data['statuses'][0]['id'])) { $id = $data['statuses'][0]['id']; $urlParts = parse_url($search); $pathParts = explode('/', trim($urlParts['path'], '/')); $username = $pathParts[0]; $domain = $urlParts['host']; //echo 'Search URL: ' . $search . '<br>'; //echo 'Username: ' . $username . '<br>'; //echo 'Domain: ' . $domain . '<br>'; //echo 'ID: ' . $id . '<br>'; $newUrl = $pbUrl . '/' . $username . '@' . $domain . '/' . $id; //echo 'New URL: ' . $newUrl; echo 'New URL: <a href="' . $newUrl . '">' . $newUrl . '</a>'; } else { echo 'ID not found in the response.'; } } ?> <!-- HTML form to input the API key, $pbUrl, and URL --> <form method="POST" action=""> <label for="apikey">API Key:</label> <input type="password" id="apikey" name="apikey" required> <br> <label for="pbUrl">pbUrl:</label> <input type="text" id="pbUrl" name="pbUrl" required> <br> <label for="url">URL:</label> <input type="text" id="url" name="url" required> <input type="submit" value="Submit"> </form> <script> window.onload = function() { const urlParams = new URLSearchParams(window.location.search); const stop = urlParams.get('stop'); if (stop) { return; // Stop the script right at the beginning } const search = urlParams.get('search'); const pbUrl = urlParams.get('pbUrl'); const apiKey = urlParams.get('apikey'); if (search && pbUrl && apiKey) { document.getElementById('apikey').value = apiKey; document.getElementById('pbUrl').value = pbUrl; document.getElementById('url').value = search; //document.forms[0].submit(); // Add the 'stop' query parameter to the URL const currentUrl = window.location.href; const updatedUrl = currentUrl + (currentUrl.includes('?') ? '&' : '?') + 'stop=true'; window.history.replaceState({}, '', updatedUrl); return; // Break off the script here } // Continue with other code if needed }; </script> ___________________________________ Fetch vidlii latest videos: (rqs proxy) --------------------------- <script> const url = 'https://api.codetabs.com/v1/proxy?quest=https://www.vidlii.com/user/repeekyraidcero/videos'; fetch(url) .then(response => response.text()) .then(html => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const links = doc.getElementsByTagName('a'); const displayedLinks = []; for (let i = 0; i < links.length; i++) { const link = links[i]; const href = link.getAttribute('href'); const className = link.getAttribute('class'); if (href && href.startsWith('/watch?v=') && className === 'ln2' && !displayedLinks.includes(href)) { const title = link.textContent; const linkElement = document.createElement('a'); linkElement.href = 'https://www.vidlii.com' + href; linkElement.target = '_blank'; linkElement.textContent = title; document.body.appendChild(linkElement); document.body.appendChild(document.createElement('br')); document.body.appendChild(document.createElement('br')); displayedLinks.push(href); } } }) .catch(error => console.error(error)); </script> </body> </html> <!---PHP-ver--> <?php $url = 'https://www.vidlii.com/user/repeekyraidcero/videos'; $html = file_get_contents($url); $dom = new DOMDocument(); libxml_use_internal_errors(true); $dom->loadHTML($html); libxml_clear_errors(); $links = $dom->getElementsByTagName('a'); $displayedLinks = array(); foreach ($links as $link) { $href = $link->getAttribute('href'); $class = $link->getAttribute('class'); if (strpos($href, '/watch?v=') === 0 && $class === 'ln2' && !in_array($href, $displayedLinks)) { $title = $link->nodeValue; echo '<a href="https://www.vidlii.com' . $href . '" target="_blank">' . $title . '</a><br><br>' . PHP_EOL; $displayedLinks[] = $href; } } ?> ______________________________________________ Load Mastodon Followers & Followings (Custom) ----------------------------------- <!DOCTYPE html> <html> <head> <style> .green { color: green; } .red { color: red; } .column { float: left; width: 50%; } </style> </head> <body> <label for="userId">User ID:</label> <input type="text" id="userId" name="userId" value="109629985010224381"><br><br> <label for="instance">Instance:</label> <input type="text" id="instance" name="instance" value="pb.todon.de"><br><br> <button onclick="displayFollowersAndFollowings()">Display Followers and Followings</button><br><br> <div class="column"> <h2>Followers</h2> <div id="followers"></div> </div> <div class="column"> <h2>Followings</h2> <div id="followings"></div> </div> <script> // Function to make API requests async function mastodonRequest(url, accessToken) { const response = await fetch(url, { headers: { 'Authorization': `Bearer ${accessToken}` } }); const data = await response.json(); return data; } // Function to resolve user's username async function resolveUser(id, instance, accessToken) { const userUrl = `https://${instance}/api/v1/accounts/${id}`; const user = await mastodonRequest(userUrl, accessToken); return user.acct; } // Function to display followers and followings async function displayFollowersAndFollowings() { const urlParams = new URLSearchParams(window.location.search); const instanceParam = urlParams.get('instance'); const userIdsParam = urlParams.get('userids'); const userIdTextbox = document.getElementById('userId'); const instanceTextbox = document.getElementById('instance'); if (instanceParam && userIdsParam) { instanceTextbox.value = instanceParam; userIdTextbox.value = userIdsParam; } const userId = userIdTextbox.value; const instance = instanceTextbox.value; const accessToken = 'YOUR_ACCESS_TOKEN'; const followerUrl = `https://${instance}/api/v1/accounts/${userId}/followers?limit=100`; const followingUrl = `https://${instance}/api/v1/accounts/${userId}/following?limit=100`; try { const [followers, followings] = await Promise.all([ mastodonRequest(followerUrl, accessToken), mastodonRequest(followingUrl, accessToken) ]); const followerIds = followers.map(follower => follower.id); const followingIds = followings.map(following => following.id); const inBothLists = followerIds.filter(id => followingIds.includes(id)); const followersDiv = document.getElementById('followers'); const followingsDiv = document.getElementById('followings'); followersDiv.innerHTML = ''; followingsDiv.innerHTML = ''; for (const follower of followers) { const followerId = follower.id; const followerUsername = await resolveUser(followerId, instance, accessToken); const followerElement = document.createElement('span'); if (inBothLists.includes(followerId)) { followerElement.textContent = followerUsername; followerElement.classList.add('green'); } else { followerElement.textContent = followerUsername; followerElement.classList.add('red'); } followersDiv.appendChild(followerElement); followersDiv.appendChild(document.createElement('br')); } for (const following of followings) { const followingId = following.id; const followingUsername = await resolveUser(followingId, instance, accessToken); const followingElement = document.createElement('span'); if (inBothLists.includes(followingId)) { followingElement.textContent = followingUsername; followingElement.classList.add('green'); } else { followingElement.textContent = followingUsername; followingElement.classList.add('red'); } followingsDiv.appendChild(followingElement); followingsDiv.appendChild(document.createElement('br')); } } catch (error) { console.error('Error:', error); } } // Check if query strings are present in the URL const urlParams = new URLSearchParams(window.location.search); const instanceParam = urlParams.get('instance'); const userIdsParam = urlParams.get('userid'); if (instanceParam && userIdsParam) { const instanceTextbox = document.getElementById('instance'); const userIdTextbox = document.getElementById('userId'); instanceTextbox.value = instanceParam; userIdTextbox.value = userIdsParam; displayFollowersAndFollowings(); } </script> </body> </html> _______________________ Countdown Redirect -------------------------------- <script> var time = 5 * 1; setInterval(function() { //var seconds = time % 60; //var minutes = (time - seconds) / 60; var seconds = time; var minutes = 0; if (seconds.toString().length == 1) { seconds = "0" + seconds; } if (minutes.toString().length == 1) { minutes = "0" + minutes; } document.getElementById("time").innerHTML = minutes + ":" + seconds; time--; if (time == 0) { window.location.href = "https://alceawis.de"; } }, 1000); </script> <div class="timer" onload="timer(1800)"> <div class="time"><strong>Redirecting in <span id="time"> x seconds</span></strong></div></div> (Source: https://stackoverflow.com/questions/50771115/javascript-countdown-to-redirect-another-page-when-it-hits-0000 ) __________________ DeviceType identifier: ---------------------------------- Wurlf:<br> <script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js" type="text/javascript"></script> <!---<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">--> <script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script> <li class="is-mobile">Mobile: <span class="wurfl-data"></span>.</li> <li class="form-factor">Form factor: <span class="wurfl-data"></span>.</li> <li class="device-name">Device: <span class="wurfl-data"></span>.</li> </ul> <script src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script> <script src='//wurfl.io/wurfl.js'></script> <script> console.log(WURFL); $('.is-mobile .wurfl-data').html( '' + WURFL.is_mobile + '' ); $('.form-factor .wurfl-data').html( WURFL.form_factor + '' ); $('.device-name .wurfl-data').html( WURFL.complete_device_name + '' ); </script> <br>======JS=======<br> Quick:<br> <meta charset="UTF-8"> <div class="container"><span class="user-agent"></span></div> <script> var userAgent = window.navigator.userAgent; if (userAgent) { var span = document.querySelector('.user-agent'); span.innerHTML = userAgent; } </script> <br>Concise:<br> <div class="container">Device: <span class="user-agentt"></span></div> <script> var userAgent = window.navigator.userAgent; if (userAgent) { var span = document.querySelector('.user-agentt'); var openingParenIndex = userAgent.indexOf('('); var closingParenIndex = userAgent.indexOf(')'); if (openingParenIndex !== -1 && closingParenIndex !== -1) { var extractedString = userAgent.substring(openingParenIndex, closingParenIndex + 1); span.innerHTML = extractedString; } } </script> _________________________ MediaSession AudioPlayer (with buttons also!) ------------------------------ <!---Media-Session-Event-Listener--> <script> // Function to handle the previous button click event function onPrevButtonClick() { // Your code logic for handling previous button click goes here console.log('Previous button clicked'); } // Function to handle the next button click event function onNextButtonClick() { // Your code logic for handling next button click goes here console.log('Next button clicked'); } // Media session event listener for previous media session event navigator.mediaSession.setActionHandler('previoustrack', () => { onPrevButtonClick(); }); // Media session event listener for next media session event navigator.mediaSession.setActionHandler('nexttrack', () => { onNextButtonClick(); }); </script> <!--------MediaPlayer------> <!---Prevent accidental reload --> <!-- <script type="text/javascript"> window.onbeforeunload = function() { return "Are you sure you want to leave? Think of the kittens!"; } </script>--> <meta charset="UTF-8"> </head> <body> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Player</title> </head> <body> <button id="prevButton">Previous</button> <button id="playButton">▶ PLAY</button> <button id="nextButton">Next</button> <div id="cue"> <a id="lastMusicLink"></a> <b><a id="currentMusicLink"></a></b> <a id="nextMusicLink"></a> </div> <script> let audio = document.createElement('audio'); let playlist = getAwesomePlaylist(); let index = 0; function onPrevButtonClick() { index = (index - 1 + playlist.length) % playlist.length; playAudio(); } function onNextButtonClick() { index = (index + 1) % playlist.length; playAudio(); } function onPlayButtonClick() { playAudio(); } function onCueLinkClick(clickedIndex) { index = clickedIndex; playAudio(); } function playAudio() { audio.src = playlist[index].src; audio.play() .then(_ => updateMetadata()) .catch(error => console.log(error)); } function updateMetadata() { let track = playlist[index]; console.log('Playing ' + track.title); document.title = track.title; // Update cue links let lastMusicIndex = (index - 1 + playlist.length) % playlist.length; let nextMusicIndex = (index + 1) % playlist.length; document.querySelector('#lastMusicLink').textContent = 'Last: ' + playlist[lastMusicIndex].title; document.querySelector('#lastMusicLink').href = playlist[lastMusicIndex].src; document.querySelector('#lastMusicLink').addEventListener('click', () => onCueLinkClick(lastMusicIndex)); document.querySelector('#currentMusicLink').textContent = '' + playlist[index].title; document.querySelector('#currentMusicLink').href = playlist[index].src; document.querySelector('#currentMusicLink').addEventListener('click', () => onCueLinkClick(index)); document.querySelector('#nextMusicLink').textContent = 'Next: ' + playlist[nextMusicIndex].title; document.querySelector('#nextMusicLink').href = playlist[nextMusicIndex].src; document.querySelector('#nextMusicLink').addEventListener('click', () => onCueLinkClick(nextMusicIndex)); } /* Previous Track & Next Track */ audio.addEventListener('ended', function() { // Play automatically the next track when audio ends. index = (index + 1) % playlist.length; playAudio(); }); document.addEventListener("DOMContentLoaded", () => { document.querySelector('#prevButton').addEventListener('click', onPrevButtonClick); document.querySelector('#playButton').addEventListener('click', onPlayButtonClick); document.querySelector('#nextButton').addEventListener('click', onNextButtonClick); }); /* Utils */ function getAwesomePlaylist() { const BASE_URL = ''; return [ { src: BASE_URL + 'https://ry3yr.github.io/OSTR/release/rnr/009_Guitar.mp3', title: 'RnR Guitar Theme', artist: 'diarykeeper', album: 'trackmix', artwork: undefined }, { src: BASE_URL + 'https://ry3yr.github.io/OSTR/release/z_other-reconstructs/Mirai_Nikki-12_Become_a_holder.mp3', title: 'Become a holder', artist: 'diarykeeper', album: 'trackmix', artwork: undefined } ]; } </script> <!---<div id="contentframe" style="position:relative; top: 0px; left: -30px;">--><br> <iframe src="index.html" name="audioplayer" style=" display:block; position: absolute; height: 100%; width: 100%" frameborder="0" ></iframe></div> </body> </html> ______________________________ 404 redirect via htaccess / httaccess: ----------------------------------- RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ 404.html [L,R=301] _______________________________ Redirect dir to file xy ----------------------- DirectoryIndex index.html _____________________________ Add Cors to curr dir: -------------------------- Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Methods "GET,PUT,POST,DELETE" Header set Access-Control-Allow-Headers "Content-Type, Authorization" (Source: https://urusai.social/@alcea/111650325029029518 https://gist.github.com/nixta/0b98d7975562bc31c4c9) _______________ Fix File Unicode Encoding issues: (txt file displaying mojibake f.e.) --------------------------------------- .htaccess ~~~~~~ AddDefaultCharset UTF-8 ______________________________ Redirect "non filenameextension" #requests to a #html ------------------------------ .htaccess ~~~~~~~ RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}\.html -f RewriteRule ^(.+)$ $1.html [L] (Source: https://pb.todon.de/@alcea/111193221007063341 ) _______________________________ Fix Unicode Rendering errors: ----------------------------------------- <meta charset="utf-8"> ___________________________ Current Chocolate Level (Mastodon) ------------------------------------------ <!DOCTYPE html> <html> <head> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head> <body> <div id="result"></div> <script> $(document).ready(function() { var apiUrl = "https://pb.todon.de/api/v1/accounts/109629985010224381/statuses"; $.ajax({ url: apiUrl, dataType: "json", success: function(response) { var targetPost = null; for (var i = 0; i < response.length; i++) { var content = response[i].content; if (content.toLowerCase().indexOf("chocolate") !== -1 || content.toLowerCase().indexOf("choco") !== -1) { targetPost = response[i]; break; // Exit the loop if a target post is found } } if (targetPost) { var content = targetPost.content; var matches = content.match(/(\d+([.,]\d+)?)%/); if (matches) { var chocolateValue = matches[1] + "%"; $("#result").text("Current chocolate value: " + chocolateValue); } else { checkNextPost(i + 1); } } else { checkNextPost(0); } }, error: function() { $("#result").text("Error retrieving data from the API."); } }); }); function checkNextPost(index) { $.ajax({ url: "https://pb.todon.de/api/v1/accounts/109629985010224381/statuses", dataType: "json", success: function(response) { if (index < response.length) { var content = response[index].content; var matches = content.match(/(\d+([.,]\d+)?)%/); if (matches) { var chocolateValue = matches[1] + "%"; $("#result").text("Current chocolate value: " + chocolateValue); } else { checkNextPost(index + 1); } } else { $("#result").text("100% Choco. Must be homemade !"); } }, error: function() { $("#result").text("Error retrieving data from the API."); } }); } </script> </body> </html> <!---RSS ver--(will-not-with-replies)--> <!--<head> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head> <body> <div id="result"></div> <script> $(document).ready(function() { var rssUrl = "https://pb.todon.de/@alcea.rss"; $.ajax({ url: rssUrl, dataType: "xml", success: function(xml) { var targetPost = null; $(xml).find("item").each(function() { var description = $(this).find("description").text(); if (description.toLowerCase().indexOf("chocolate") !== -1 || description.toLowerCase().indexOf("choco") !== -1) { targetPost = $(this); return false; // Exit the loop if a target post is found } }); if (targetPost) { var description = targetPost.find("description").text(); var matches = description.match(/(\d+([.,]\d+)?)%/); //var matches = description.match(/(\d+)%/); if (matches) { var chocolateValue = matches[1] + "%"; $("#result").text("Current chocolate value: " + chocolateValue); } else { checkNextPost(targetPost.next(), xml); } } else { checkNextPost($(xml).find("item").first(), xml); } }, error: function() { $("#result").text("Error retrieving RSS feed."); } }); }); function checkNextPost(post, xml) { if (post.length > 0) { var description = post.find("description").text(); var matches = description.match(/(\d+)%/); if (matches) { var chocolateValue = matches[1] + "%"; $("#result").text("Current chocolate value: " + chocolateValue); } else { var nextPost = post.next(); checkNextPost(nextPost, xml); } } else { $("#result").text("100% Choco. Must be homemade !"); } } </script> </body> </html>--> <!--php <?php $rss = file_get_contents('https://pb.todon.de/@alcea.rss'); $xml = simplexml_load_string($rss); $targetPost = null; foreach ($xml->channel->item as $item) { $description = $item->description; if (stripos($description, 'chocolate') !== false || stripos($description, 'choco') !== false) { $targetPost = $item; break; } } if ($targetPost) { $description = $targetPost->description; if (preg_match('/(\d+)%/', $description, $matches)) { $chocolateValue = $matches[1] . '%'; echo 'Current chocolate value: ' . $chocolateValue; } else { echo 'No chocolate value found.'; } } else { echo 'No post found containing chocolate or choco.'; } ?> --> ___________________________ Link w hover popup: ------------------------------- ----CSS---VER---: <style> .hover-alt-text{display:inline-flex;align-items:center;position:relative}.hover-alt-text:hover::before{content:attr(alt);position:absolute;background:#fff;border:1px solid #000;padding:5px;z-index:9999;top:-100%;white-space:nowrap;margin-top:-5px} </style> <a target="_blank" href="https://thinfi.com/0awck" alt="webdav.hidrive.strato.com/users/user/DB/Public/Phone_relatated/0Android/[Device_Backupl]/Tablet" class="hover-alt-text">Android Dvcs</a> --JS--version-- <a id="popupLink" target="_blank" href="https://thinfi.com/0awck" style="position: relative; display: inline-block; cursor: pointer;"> AndrdDvsBckp <span id="popupText" style="visibility: hidden; width: 900px; background-color: #555; color: #fff; text-align: center; border-radius: 6px; padding: 8px; position: absolute; z-index: 1; bottom: 125%; left: 50%; margin-left: -100px; opacity: 0; transition: opacity 0.3s;"> webdav.hidrive.strato.com/users/user/DB/Public/Phone_relatated/0Android/[Device_Backupl]/Tablet </span> </a> ________________________ Isola(tion) -2P Game --------------------------------- <style> #grid { display: grid; grid-template-columns: repeat(8, 50px); grid-template-rows: repeat(6, 50px); } .cell { width: 50px; height: 50px; border: 1px solid black; display: flex; align-items: center; justify-content: center; cursor: pointer; } .red-dot, .blue-dot { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; } .red-dot { background-color: red; } .blue-dot { background-color: blue; } .broken-field { background-color: gray; cursor: default; } #turn-display { position: absolute; top: 10px; right: 10px; font-weight: bold; } </style> </head> <body> <div id="grid"></div> <div id="turn-display"></div> <script> document.addEventListener("DOMContentLoaded", function () { const grid = document.querySelector("#grid"); const turnDisplay = document.querySelector("#turn-display"); const cells = []; const rows = 6; const columns = 8; for (let i = 0; i < rows; i++) { for (let j = 0; j < columns; j++) { const cell = document.createElement("div"); cell.classList.add("cell"); cell.dataset.row = i; cell.dataset.column = j; grid.appendChild(cell); cells.push(cell); } } let redDotPosition = { row: 2, column: 0 }; cells[redDotPosition.row * columns + redDotPosition.column].classList.add("red-dot"); let blueDotPosition = { row: rows - 3, column: columns - 1 }; cells[blueDotPosition.row * columns + blueDotPosition.column].classList.add("blue-dot"); let currentPlayer = "red"; let brokenFields = []; let currentAction = "move"; updateTurnDisplay(); cells.forEach(function (cell) { cell.addEventListener("click", function () { const clickedRow = parseInt(cell.dataset.row); const clickedColumn = parseInt(cell.dataset.column); if (currentAction === "move") { if (currentPlayer === "red" && isNeighboringCell(redDotPosition, clickedRow, clickedColumn)) { moveDot("red-dot", redDotPosition, clickedRow, clickedColumn); currentAction = "break"; } else if (currentPlayer === "blue" && isNeighboringCell(blueDotPosition, clickedRow, clickedColumn)) { moveDot("blue-dot", blueDotPosition, clickedRow, clickedColumn); currentAction = "break"; } } else if (currentAction === "break") { if (!isPlayerCell(clickedRow, clickedColumn) && !isBrokenField(clickedRow, clickedColumn)) { breakField(clickedRow, clickedColumn); currentAction = "move"; currentPlayer = currentPlayer === "red" ? "blue" : "red"; } } updateTurnDisplay(); }); }); function moveDot(dotClass, currentPosition, newRow, newColumn) { const newPosition = { row: newRow, column: newColumn }; const newPositionCell = cells[newPosition.row * columns + newPosition.column]; if (!newPositionCell.classList.contains("red-dot") && !newPositionCell.classList.contains("blue-dot")) { cells[currentPosition.row * columns + currentPosition.column].classList.remove(dotClass); newPositionCell.classList.add(dotClass); if (dotClass === "red-dot") { redDotPosition = newPosition; } else if (dotClass === "blue-dot") { blueDotPosition = newPosition; } invalidateGridPoint(newPosition); } else { console.log("Cannot move to an occupied spot."); } } function isNeighboringCell(currentPosition, clickedRow, clickedColumn) { const rowDiff = Math.abs(clickedRow - currentPosition.row); const columnDiff = Math.abs(clickedColumn - currentPosition.column); const isBroken = isBrokenField(clickedRow, clickedColumn); if (isBroken) { return false; // Prevent moving onto a broken field } return ( (rowDiff === 1 && columnDiff === 0) || (rowDiff === 0 && columnDiff === 1) || (rowDiff === 1 && columnDiff === 1) ); } function isPlayerCell(row, column) { return (row === redDotPosition.row && column === redDotPosition.column) || (row === blueDotPosition.row && column === blueDotPosition.column); } function isBrokenField(row, column) { return brokenFields.some((field) => field.row === row && field.column === column); } function breakField(row, column) { const brokenField = { row: row, column: column }; brokenFields.push(brokenField); cells[row * columns + column].classList.add("broken-field"); console.log("Field broken at (" + row + ", " + column + ")"); } function invalidateGridPoint(position) { const cell = cells[position.row * columns + position.column]; cell.style.backgroundColor = ""; } function updateTurnDisplay() { turnDisplay.textContent = "Current Turn: " + currentPlayer + " (" + currentAction + ")"; } }); </script> ______________________________ Download Time / Speed Calculator: --------------------------------------------------- <script> function calculateSpeed() { var fileSize = parseFloat(document.getElementById("fileSize").value); // File size in megabytes var transmissionSpeed = parseFloat(document.getElementById("transmissionSpeed").value); // Transmission speed in megabits or gigabits per second var unit = document.getElementById("unit").value; if (unit === "Mbit/s") { transmissionSpeed /= 8; // Convert speed to megabytes per second } else if (unit === "Gbit/s") { transmissionSpeed *= 125; // Convert speed to megabytes per second } var downloadTime = fileSize / transmissionSpeed; // Calculate download time in seconds // Determine the selected unit for output var outputUnit; if (document.getElementById("seconds").checked) { outputUnit = "seconds"; } else if (document.getElementById("minutes").checked) { outputUnit = "minutes"; downloadTime /= 60; // Convert download time to minutes } else if (document.getElementById("hours").checked) { outputUnit = "hours"; downloadTime /= 3600; // Convert download time to hours } // Display the download time document.getElementById("result").innerHTML = "Download Time: " + downloadTime.toFixed(2) + " " + outputUnit; } </script> </head> <body> <h1>Download Speed Calculator</h1> <label for="fileSize">File Size (MB):</label> <input type="text" id="fileSize" style="width: 60px;"/> -&nbsp;<input type="text" id="transmissionSpeed" style="width: 25px;" value="2"/> <select id="unit"> <option value="Mbit/s">Mbit/s</option> <option value="Gbit/s">Gbit/s</option> </select><br><br> <input type="radio" id="seconds" name="outputUnit" value="seconds"> <label for="seconds">Seconds</label> <input type="radio" id="minutes" name="outputUnit" value="minutes"> <label for="minutes">Minutes</label> <input type="radio" id="hours" name="outputUnit" value="hours" checked> <label for="hours">Hours</label><br><br> <button onclick="calculateSpeed()">Calculate</button><br><br> <div id="result"></div> </body> </html> ________________________________ Advanced Mastodon Renderer ( Attachments, Youtube, Pixiv, Tenor, imgbb, dailymotion, vidlii, soundcloud shortcode-emoji) -------------------------------------------- <body> <div id="feed"></div> <script> // Function to replace emoji shortcodes with images function replaceEmojis(content, customEmojis) { customEmojis.forEach(customEmoji => { const shortcode = customEmoji.shortcode; const url = customEmoji.url; const shortcodePattern = new RegExp(':' + shortcode + ':', 'g'); const emojiTag = '<img src="' + url + '" alt="' + shortcode + '" width="20px">'; content = content.replace(shortcodePattern, emojiTag); }); return content; } // Function to fetch data from the selected API URL function fetchData(apiUrl) { const accessToken = 'your-access-token'; fetch(apiUrl, { headers: { 'Authorization': 'Bearer ' + accessToken } }) .then(response => response.json()) .then(data => { // Clear the feed before appending new content document.getElementById('feed').innerHTML = ''; data.forEach(status => { let content = replaceEmojis(status.content, customEmojis); let media = ''; let avatar = ''; let tootLink = ''; // Check for YouTube video link const youtubeRegex = /https?:\/\/(www\.)?(m\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/; const youtubeMatch = status.content.match(youtubeRegex); if (youtubeMatch) { // Extract video ID from YouTube link const videoId = youtubeMatch[3]; // Create embedded player for YouTube video media = `<div><iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe></div>`; } else { // Check for Pixiv artwork link const pixivRegex = /https?:\/\/(?:www\.)?pixiv\.net\/(?:en\/)?artworks\/(\d+)/; const pixivMatch = status.content.match(pixivRegex); if (pixivMatch) { // Extract artwork ID from Pixiv link const artworkId = pixivMatch[1]; // Create image preview for Pixiv artwork media = `<div><img src="https://embed.pixiv.net/decorate.php?illust_id=${artworkId}&mode=sns-automator" width="50%"></div>`; } else { const tenorRegex = /https?:\/\/(?:www\.)?tenor\.com\/view\/[a-zA-Z0-9-]+-(\d+)/; const tenorMatch = status.content.match(tenorRegex); if (tenorMatch) { // Extract Tenor.com video ID const videoId = tenorMatch[1]; // Create embedded player for Tenor.com video media = `<div><iframe src="https://tenor.com/embed/${videoId}" frameborder="0" allowfullscreen="true" width="100%" height="400px"></iframe></div>`; } else { // Check for Dailymotion video link const dailymotionRegex = /https?:\/\/(www\.)?dailymotion\.com\/video\/([a-zA-Z0-9_-]+)/; const dailymotionMatch = status.content.match(dailymotionRegex); if (dailymotionMatch) { const videoId = dailymotionMatch[2]; media = `<div><iframe frameborder="0" width="480" height="270" src="https://www.dailymotion.com/embed/video/${videoId}?autoplay=0" allowfullscreen allow="autoplay"></iframe></div>`; }else{ // Check for Vidlii video link const vidliiRegex = /https?:\/\/(www\.)?(m\.)?vidlii\.com\/watch\?v=([a-zA-Z0-9_-]+)/; const vidliiMatch = status.content.match(vidliiRegex); if (vidliiMatch) { const videoId = vidliiMatch[3]; media = `<iframe allowfullscreen src="https://www.vidlii.com/embed?v=${videoId}" frameborder="0" width="640" height="360"></iframe><br>`; }else{ // Check Soundcloud Link const soundcloudRegex = /https?:\/\/(m\.)?soundcloud\.com\/([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_-]+)/; const soundcloudMatch = status.content.match(soundcloudRegex); if (soundcloudMatch) { const soundcloudUrl = `https://w.soundcloud.com/player/?url=${encodeURIComponent(soundcloudMatch[0] )}&color=0066cc&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true`; //media = `<iframe width="100%" height="300" scrolling="no" frameborder="no" allow="autoplay" src="${soundcloudUrl}"></iframe>`; media = `<iframe allowfullscreen src="${status.url}/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="600" height=400"></iframe><script src="embed.js" async><\/\script><br>`; } else { // Check for imgbb image URLs in the status content const imageRegex = /(https?:\/\/(?:www\.)?i\.ibb\.co\/[^ ]+\.(?:jpg|png|gif|bmp))/g; const imageMatches = status.content.match(imageRegex); if (imageMatches) { media += '<div>'; imageMatches.forEach(url => { media += '<img src="' + url + '" width="50%"><br>'; }); media += '</div>'; } } } } } } } // Check for video and audio attachments if (status.media_attachments.length > 0) { media = '<div>'; status.media_attachments.forEach(attachment => { if (attachment.type === 'image') { media += '<img src="' + attachment.url + '" width="50%"><br>'; } else if (attachment.type === 'video') { media += '<video src="' + attachment.url + '" controls width="50%"></video><br>'; } else if (attachment.type === 'audio') { media += '<audio src="' + attachment.url + '" controls></audio><br>'; } }); media += '</div>'; } // Check for spoiler tag if (status.url.includes('activity')) { avatar = '<!--<details><summary>Avatar (spoiler)</summary><img src="' + status.account.avatar + '" width="100px"></details>--><br>'; tootLink = '<!--<details><summary>View on Mastodon (spoiler)</summary><a href="' + status.url + '" target="_blank">Click here to view activity</a></details>-->'; } else { avatar = '<img src="' + status.account.avatar + '" width="100px"><br>'; tootLink = '<a href="' + status.url + '" target="_blank">View on Mastodon</a>'; } // Get the date of the status const date = new Date(status.created_at); // Append content to feed const contentHtml = `<div>${content}</div>`; const statusHtml = `${contentHtml}${media}${avatar}${tootLink}&nbsp;${date.toLocaleString()}<hr>`; document.getElementById('feed').innerHTML += statusHtml; }); }) .catch(error => console.error(error)); } // Add emoji renderer const emojiEndpoint = 'https://pb.todon.de/api/v1/custom_emojis'; fetch(emojiEndpoint) .then(response => response.json()) .then(customEmojis => { // Store custom emojis for later use window.customEmojis = customEmojis; // Event listener for radio button change const radioButtons = document.querySelectorAll('input[name="apiUrl"]'); radioButtons.forEach(radioButton => { radioButton.addEventListener('change', function() { const selectedApiUrl = this.value; fetchData(selectedApiUrl); }); }); // Initial fetch using the default API URL const defaultApiUrl = 'https://pb.todon.de/api/v1/accounts/109629985010224381/statuses'; fetchData(defaultApiUrl); }); </script> </body> _____________________________ Code Display Page: ------------------------------ <style> body {font-family: Arial, sans-serif;line-height: 1.5;margin: 20px;} a {text-decoration: none;color: #007bff;} a:hover {text-decoration: underline;} .commands-list {margin-bottom: 10px;} .commands-list a {margin-right: 10px;border-radius: 9px;padding: 4px 8px;border: 1px solid transparent;border-color: #007bff;} .commands-list .command-link a {margin-right: 0;border-radius: 0;padding: 0;border: none;background-color: transparent;} .command-link {margin-right: 10px;border-radius: 9px;padding: 4px 8px;border: 1px solid transparent;border-color: #007bff;} </style> <!-- HTML content --> <a target="_blank" href="code.html">CMDS</a> <div class="commands-list"> <!--<a target="_blank" href="Computerstuff/Commands">(↑)</a>--> </div> <div class="commands-list"> <a target="_blank" href="other/Computerstuff/Commands/Autohotkey/Commands.txt">Autohotkey</a> <a target="_blank" href="other/Computerstuff/Commands/DOS/Commands.txt">DOS</a> <a target="_blank" href="other/Computerstuff/Commands/FFMPEG%20Commands.txt">FFMPEG</a> <a target="_blank" href="other/Computerstuff/Commands/Commands_ImageMagick.txt">Imagemagick</a> <a target="_blank" href="other/Computerstuff/Commands/Sox_CMDS.txt">Sox</a> <a target="_blank" href="other/Computerstuff/Commands/HTML_Codes.txt">HTML</a> <span class="command-link"><a target="_blank" href="other/Computerstuff/Commands/PHP.txt">PHP</a><a target="_blank" href="https://ry3yr.github.io/php">(↓)</a></span> <a target="_blank" href="other/Computerstuff/Commands/Python.txt">Python</a> <a target="_blank" href="other/Computerstuff/Commands/Android-Keyevents.txt">AndroidCMDS</a> </div> <br> <br> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 20px; } button { background-color: #4CAF50; border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin-bottom: 20px; cursor: pointer; } button:hover { opacity: 0.8; } plaintext { display: block; white-space: pre-wrap; font-family: monospace; background-color: #f5f5f5; margin-bottom: 20px; } </style> <script> const txtUrls = [ 'https://ry3yr.github.io/OSTR/release/other/Computerstuff/Commands/Autohotkey/Commands.txt', 'https://ry3yr.github.io/OSTR/release/other/Computerstuff/Commands/HTML_Codes.txt', 'https://ry3yr.github.io/OSTR/release/other/Computerstuff/Commands/DOS/Commands.txt', 'https://ry3yr.github.io/OSTR/release/other/Computerstuff/Commands/FFMPEG%20Commands.txt', 'https://ry3yr.github.io/OSTR/release/other/Computerstuff/Commands/Commands_ImageMagick.txt', 'https://ry3yr.github.io/OSTR/release/other/Computerstuff/Commands/PHP.txt', 'https://ry3yr.github.io/OSTR/release/other/Computerstuff/Commands/Python.txt', ]; function getRandomTxtUrl() { const randomIndex = Math.floor(Math.random() * txtUrls.length); return txtUrls[randomIndex]; } function fetchRandomTxt() { const randomTxtUrl = getRandomTxtUrl(); fetch(randomTxtUrl) .then(response => response.text()) .then(fileContents => { const pattern = /------(.*?)_____/gs; const matches = fileContents.match(pattern).slice(1); const randomIndex = Math.floor(Math.random() * matches.length); const randomMatch = matches[randomIndex]; const numLines = randomMatch.split('\n').length; if (numLines >= 3) { const plaintext = document.createElement('plaintext'); plaintext.textContent = randomMatch; document.body.appendChild(plaintext); } else { // Retry fetching if plaintext is empty or doesn't have enough lines fetchRandomTxt(); } }) .catch(error => { // Retry fetching if there was an error fetchRandomTxt(); }); } fetchRandomTxt(); const fetchButton = document.createElement('button'); fetchButton.textContent = 'Fetch Random Code'; fetchButton.onclick = () => { document.querySelectorAll('plaintext').forEach(e => e.remove()); fetchRandomTxt(); }; </script> <button onclick='document.querySelectorAll("plaintext").forEach(e => e.textContent = "");fetchRandomTxt();'>Fetch Random Code</button> ____________________________ Terrible T9 Keypad ----------------------------- <!DOCTYPE html> <html> <head> <style> .keypad { display: grid; grid-template-columns: repeat(3, 1fr); grid-gap: 10px; max-width: 200px; } .key { padding: 10px; text-align: center; background-color: #e0e0e0; cursor: pointer; } </style> </head> <body> <div class="keypad"> <div class="key" onclick="pressKey('1')">1</div> <div class="key" onclick="pressKey('2')">2<br>a b c</div> <div class="key" onclick="pressKey('3')">3<br>d e f</div> <div class="key" onclick="pressKey('4')">4<br>g h i</div> <div class="key" onclick="pressKey('5')">5<br>j k l</div> <div class="key" onclick="pressKey('6')">6<br>m n o</div> <div class="key" onclick="pressKey('7')">7<br>p q r s</div> <div class="key" onclick="pressKey('8')">8<br>t u v</div> <div class="key" onclick="pressKey('9')">9<br>w x y z</div> <div class="key" onclick="pressKey('*')">*</div> <div class="key" onclick="pressKey('0')">0</div> <div class="key" onclick="pressKey('#')">#</div> </div><br> <input type="text" id="input-field"> <div id="link-container" style="display: none;"> <a id="website-link" href="https://www.website.com" target="_blank">Visit Website.com</a> </div> <script> let inputValue = ''; function pressKey(key) { inputValue += key; document.getElementById('input-field').value = inputValue; checkSequence(inputValue); } function checkSequence(value) { if (value === '1234') { document.getElementById('link-container').style.display = 'block'; } else { document.getElementById('link-container').style.display = 'none'; } } </script> </body> </html> __________________________________ Prefill text field with query string: -------------------------------------------------- <script> // Get the query parameter value from the URL function getQueryParameter(name) { const urlSearchParams = new URLSearchParams(window.location.search); return urlSearchParams.get(name); } // Prefill the input field with the query parameter value document.addEventListener('DOMContentLoaded', function() { const apiKeyInput = document.getElementById('api_key'); const apiKeyValue = getQueryParameter('apikey'); if (apiKeyValue) { apiKeyInput.value = apiKeyValue; } }); </script> _________________________ Lazyloading: -------------------- <img src="https://i0.wp.com/www.chrismadden.co.uk/images/cartoons/room-for-one-more-plant-cartoon-cjmadden.gif" alt="garden" width="50%" loading="lazy"> ______________________________ eBay search Url constructor: ---------------------------------------- <html> <head> <title>eBay Search</title> </head> <body> <h1>eBay Search</h1> <form method="GET" action=""> <label for="query">Search Query:</label> <input type="text" name="query" id="query" required><br><br> <label for="country">Country:</label> <select name="country" id="country" > <option value="EBAY-US">United States</option> <option value="EBAY-UK">United Kingdom</option> <option value="EBAY-AU">Australia</option> <option value="EBAY-DE" selected>Germany</option> <!-- Add more country options here --> </select><br><br> <label for="sortBy">Sort By:</label> <select name="sortBy" id="sortBy"> <option value="BestMatch">Best Match</option> <option value="15">Price + Shipping: Lowest First</option> <option value="16" selected>Price + Shipping: Highest First</option> <!-- Add more sorting options here --> </select><br><br> <label for="buyNow">Buy Now/Bid:</label> <select name="buyNow" id="buyNow"> <option value="BuyNow">Buy Now</option> <option value="Auction">Bid</option> </select><br><br> <label for="minPrice">Minimum Price:</label> <input type="number" name="minPrice" id="minPrice"><br><br> <label for="maxPrice">Maximum Price:</label> <input type="number" name="maxPrice" id="maxPrice"><br><br> <label for="locationPref">Location Preference:</label> <select name="locationPref" id="locationPref"> <option value="1">Home</option> <option value="2">Worldwide</option> <option value="3">Europe</option> </select><br><br> <input type="submit" value="Search"> </form> <script> document.querySelector('form').addEventListener('submit', function(event) { event.preventDefault(); var query = document.getElementById('query').value; var country = document.getElementById('country').value; var sortBy = document.getElementById('sortBy').value; var buyNow = document.getElementById('buyNow').value; var minPrice = document.getElementById('minPrice').value; var maxPrice = document.getElementById('maxPrice').value; var locationPref = document.getElementById('locationPref').value; var baseUrl = ''; if (country === 'EBAY-DE') { baseUrl = 'https://www.ebay.de'; } else if (country === 'EBAY-UK') { baseUrl = 'https://www.ebay.co.uk'; } else { baseUrl = 'https://www.ebay.com'; } var sortingOption = ''; if (sortBy === '15') { sortingOption = '&_sop=15'; } else if (sortBy === '16') { sortingOption = '&_sop=16'; } var priceOption = ''; if (minPrice !== '') { priceOption += '&_udlo=' + minPrice; } if (maxPrice !== '') { priceOption += '&_udhi=' + maxPrice; } var locationOption = ''; if (locationPref === '1') { locationOption = '&LH_PrefLoc=1'; } else if (locationPref === '2') { locationOption = '&LH_PrefLoc=2'; } else if (locationPref === '3') { locationOption = '&LH_PrefLoc=3'; } var searchUrl = baseUrl + '/sch/i.html?_nkw=' + encodeURIComponent(query) + '&_ipg=50' + sortingOption + '&LH_BIN=' + (buyNow === 'BuyNow' ? '1' : '0') + '&LH_LocatedIn=' + encodeURIComponent(country) + priceOption + locationOption; // Display the search URL var searchUrlElement = document.createElement('a'); searchUrlElement.href = searchUrl; searchUrlElement.target = '_blank'; searchUrlElement.innerText = searchUrl; // Remove the existing link var existingLink = document.querySelector('a'); if (existingLink) { existingLink.parentNode.removeChild(existingLink); } // Append the new link to the document body //document.body.appendChild(document.createElement('br')); //document.body.appendChild(document.createElement('br')); //document.body.appendChild(document.createElement('h2')).innerText = 'Search URL:'; //document.body.appendChild(document.createElement('br')); document.body.appendChild(searchUrlElement); }); </script> </body> </html> ________________________________________________ YT Comment Finder: --------------------------------- <!DOCTYPE html> <html> <body> <form id="searchForm"> <label for="yturl">YouTube URL:</label> <input type="text" id="yturl" name="yturl" placeholder="Enter YouTube video URL" required><br> <label for="keyword">Keyword:</label> <input type="text" id="keyword" name="keyword" placeholder="Enter yt comment keyword" required><br> <button type="submit">Search</button> </form> <div id="commentsContainer"> <ul id="commentsList"></ul> </div> <script> document.getElementById('searchForm').addEventListener('submit', function (event) { event.preventDefault(); var videoUrl = document.getElementById('yturl').value; var keyword = document.getElementById('keyword').value; findYouTubeComments(videoUrl, keyword); }); function findYouTubeComments(videoUrl, keyword) { var videoId = getVideoIdFromUrl(videoUrl); var apiKey = atob('Base64APIKey'); var url = 'https://www.googleapis.com/youtube/v3/commentThreads?part=snippet&maxResults=10&videoId=' + videoId + '&searchTerms=' + encodeURIComponent(keyword) + '&key=' + apiKey; fetch(url) .then(function (response) { return response.json(); }) .then(function (data) { displayComments(data.items); }) .catch(function (error) { console.log('Error:', error); }); } function displayComments(items) { var commentsList = document.getElementById('commentsList'); commentsList.innerHTML = ''; if (items.length === 0) { commentsList.innerHTML = '<li>No comments found.</li>'; } else { items.forEach(function (item) { var commentId = item.id; var comment = item.snippet.topLevelComment.snippet.textDisplay; var commentUrl = 'https://www.youtube.com/watch?v=' + item.snippet.videoId + '&google_comment_id=' + commentId; var listItem = document.createElement('li'); listItem.innerHTML = '<a href="' + commentUrl + '">' + comment + '</a>'; commentsList.appendChild(listItem); }); } } function getVideoIdFromUrl(url) { var videoId = ''; var pattern = /(?:\?v=|\/embed\/|\/\d\/|\/vi\/|\/v\/|https?:\/\/(?:www\.)?youtube\.com\/v\/|https?:\/\/(?:www\.)?youtube\.com\/embed\/|https?:\/\/youtu\.be\/|https?:\/\/(?:www\.)?youtube\.com\/user\/[^#\/]+#p\/[^#\/]+\/|https?:\/\/(?:www\.)?youtube\.com\/s[^#\/]+\/|https?:\/\/(?:www\.)?youtube\.com\/playlist\?)([^#\&\?\/]+)/; var matches = url.match(pattern); if (matches && matches.length > 1) { videoId = matches[1]; } return videoId; } </script> </body> </html> ________________________________________________ YT Studio esque: ------------------------- <a target="_blank" href="https://ry3yr.github.io/ytsearch">YT General Search</a>&nbsp; <a target="_blank" href="https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/ytchannelsearch-dkpr.html">YTChannel Search</a>&nbsp; <a target="_blank" href="https://ry3yr.github.io/ytstudio">YTStudio Overview</a><br> <!---LatestYTComments---> <iframe src="data:text/html;base64, PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KCiAgICA8dGl0bGU+RmV0Y2ggWW91VHViZSBDb21tZW50czwvdGl0bGU+CjwvaGVhZD4KPGJvZHk+CiAgICA8IS0tIEhUTUwgZm9ybSBmb3IgQVBJIGtleSBhbmQgY2hhbm5lbCBzZWxlY3Rpb24gLS0+CiAgICA8Zm9ybSBpZD0iY29tbWVudEZvcm0iIG1ldGhvZD0iUE9TVCI+CiAgICAgICAgPGxhYmVsIGZvcj0iYXBpX2tleSI+QVBJIEtleTo8L2xhYmVsPgogICAgICAgIDxpbnB1dCB0eXBlPSJ0ZXh0IiBuYW1lPSJhcGlfa2V5IiBpZD0iYXBpX2tleSIgdmFsdWU9IiI+CiAgICAgICAgPGxhYmVsIGZvcj0iY2hhbm5lbF9pZCI+Q2hhbm5lbDo8L2xhYmVsPgogICAgICAgIDxzZWxlY3QgaWQ9ImNoYW5uZWxfaWQiIG5hbWU9ImNoYW5uZWxfaWQiIHJlcXVpcmVkPgogICAgICAgICAgICA8b3B0aW9uIHZhbHVlPSJVQ3JsdEdpaDExQV9OYXl6NmhHNVh0SXciPkRpYXJ5a2VlcGVyPC9vcHRpb24+CiAgICAgICAgICAgIDxvcHRpb24gdmFsdWU9IlVDbUlwT25kNUJWeDVTaTJocDBXTktadyIgc2VsZWN0ZWQ+UmVwZWVreXJhaWRfQ2Vybzwvb3B0aW9uPgogICAgICAgICAgICA8b3B0aW9uIHZhbHVlPSJVQ0Q3bVJCdWp5eFVSZXNRNmppaUd5cFEiPlBCPC9vcHRpb24+CiAgICAgICAgPC9zZWxlY3Q+CiAgICAgICAgPGJ1dHRvbiB0eXBlPSJzdWJtaXQiPkZldGNoIENvbW1lbnRzPC9idXR0b24+CiAgICA8L2Zvcm0+CiAgICA8IS0tIENvbnRhaW5lciB0byBkaXNwbGF5IHRoZSBmZXRjaGVkIGNvbW1lbnRzIC0tPgogICAgPGRpdiBpZD0iY29tbWVudHNDb250YWluZXIiPjwvZGl2PgogICAgPHNjcmlwdD4KICAgICAgICBjb25zdCBmb3JtID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2NvbW1lbnRGb3JtJyk7CiAgICAgICAgY29uc3QgY29tbWVudHNDb250YWluZXIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnY29tbWVudHNDb250YWluZXInKTsKICAgICAgICBmb3JtLmFkZEV2ZW50TGlzdGVuZXIoJ3N1Ym1pdCcsIGFzeW5jIChldmVudCkgPT4gewogICAgICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpOwogICAgICAgICAgICBjb25zdCBhcGlLZXkgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnYXBpX2tleScpLnZhbHVlOwogICAgICAgICAgICBjb25zdCBjaGFubmVsSWQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnY2hhbm5lbF9pZCcpLnZhbHVlOwogICAgICAgICAgICBjb25zdCBhcGlVcmwgPSBgaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20veW91dHViZS92My9jb21tZW50VGhyZWFkcz9wYXJ0PXNuaXBwZXQmYWxsVGhyZWFkc1JlbGF0ZWRUb0NoYW5uZWxJZD0ke2NoYW5uZWxJZH0mbWF4UmVzdWx0cz0xMCZvcmRlcj10aW1lJmtleT0ke2FwaUtleX1gOwogICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaChhcGlVcmwpOwogICAgICAgICAgICAgICAgY29uc3QgZGF0YSA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKTsKICAgICAgICAgICAgICAgIGNvbnN0IGNvbW1lbnRzID0gZGF0YS5pdGVtczsKICAgICAgICAgICAgICAgIC8vIENsZWFyIHByZXZpb3VzIGNvbW1lbnRzCiAgICAgICAgICAgICAgICBjb21tZW50c0NvbnRhaW5lci5pbm5lckhUTUwgPSAnJzsKICAgICAgICAgICAgICAgIC8vIERpc3BsYXkgdGhlIGZldGNoZWQgY29tbWVudHMKICAgICAgICAgICAgICAgIGNvbW1lbnRzLmZvckVhY2goKGNvbW1lbnQpID0+IHsKICAgICAgICAgICAgICAgICAgICBjb25zdCBzbmlwcGV0ID0gY29tbWVudC5zbmlwcGV0LnRvcExldmVsQ29tbWVudC5zbmlwcGV0OwogICAgICAgICAgICAgICAgICAgIGNvbnN0IGF1dGhvciA9IHNuaXBwZXQuYXV0aG9yRGlzcGxheU5hbWU7CiAgICAgICAgICAgICAgICAgICAgY29uc3QgdGV4dCA9IHNuaXBwZXQudGV4dERpc3BsYXk7CiAgICAgICAgICAgICAgICAgICAgY29uc3QgdmlkZW9JZCA9IHNuaXBwZXQudmlkZW9JZDsKICAgICAgICAgICAgICAgICAgICBjb25zdCB2aWRlb1VybCA9IGBodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PSR7dmlkZW9JZH1gOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IGNvbW1lbnRFbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7CiAgICAgICAgICAgICAgICAgICAgY29tbWVudEVsZW1lbnQuaW5uZXJIVE1MID0gYAogICAgICAgICAgICAgICAgICAgICAgICA8cD48c3Ryb25nPiR7YXV0aG9yfTwvc3Ryb25nPjogJHt0ZXh0fTwvcD4KICAgICAgICAgICAgICAgICAgICAgICAgPHA+RnJvbSBWaWRlbzogPGEgaHJlZj0iJHt2aWRlb1VybH0iIHRhcmdldD0iX2JsYW5rIj4ke3ZpZGVvVXJsfTwvYT48L3A+CiAgICAgICAgICAgICAgICAgICAgYDsKICAgICAgICAgICAgICAgICAgICBjb21tZW50c0NvbnRhaW5lci5hcHBlbmRDaGlsZChjb21tZW50RWxlbWVudCk7CiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0Vycm9yIGZldGNoaW5nIGNvbW1lbnRzOicsIGVycm9yKTsKICAgICAgICAgICAgfQogICAgICAgIH0pOwogICAgPC9zY3JpcHQ+CjwvYm9keT4KPC9odG1sPg== " style="border:0px #ffffff none;" name="statusit" scrolling="auto" frameborder="0" marginheight="0px" marginwidth="0px" height="220px" width="800px" allowfullscreen></iframe> <!DOCTYPE html> <html> <head> <title>YouTube Channel Videos</title> <style> body { font-family: Arial, sans-serif; background-color: #f2f2f2; } form { max-width: 600px; margin: 0 auto; padding: 20px; background-color: #fff; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,.1); } label { display: block; margin-bottom: 10px; font-weight: bold; } select, input[type="text"], button { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; margin-bottom: 20px; font-size: 14px; } input[type="checkbox"], label[for="show_thumbnail"] { display: inline; margin-right: 5px; } button[type="submit"] { background-color: #4CAF50; color: #fff; cursor: pointer; } </style> </head> <body> <!-- HTML form --> <form method="POST" action=""> <label for="channel_id">Channel ID:</label> <select id="channel_id" name="channel_id" required> <option value="UCrltGih11A_Nayz6hG5XtIw">Diarykeeper</option> <option value="UCmIpOnd5BVx5Si2hp0WNKZw">Repeekyraid_Cero</option> </select> <label for="api_key">API Key:</label> <input type="text" id="api_key" name="api_key" required> <input type="checkbox" id="show_thumbnail" name="show_thumbnail"> <label for="show_thumbnail">Show Thumbnail</label><br> <button type="submit">Get Channel Videos</button> </form> <script> document.addEventListener('DOMContentLoaded', function() { var form = document.querySelector('form'); form.addEventListener('submit', function(event) { event.preventDefault(); var channelId = document.getElementById('channel_id').value; var apiKey = document.getElementById('api_key').value; var url = `https://www.googleapis.com/youtube/v3/channels?part=contentDetails&id=${channelId}&key=${apiKey}`; fetch(url) .then(function(response) { if (!response.ok) { throw new Error('Error: cURL request failed'); } return response.json(); }) .then(function(data) { var uploadsPlaylistId = data.items[0].contentDetails.relatedPlaylists.uploads; var playlistUrl = `https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=50&playlistId=${uploadsPlaylistId}&key=${apiKey}`; return fetch(playlistUrl); }) .then(function(playlistResponse) { if (!playlistResponse.ok) { throw new Error('Error: cURL playlist request failed'); } return playlistResponse.json(); }) .then(function(playlistData) { var items = playlistData.items; items.forEach(function(item) { var videoId = item.snippet.resourceId.videoId; var videoTitle = item.snippet.title; var videoThumbnail = item.snippet.thumbnails.default.url; var videoPublishedAt = item.snippet.publishedAt; var videoContainer = document.createElement('div'); if (document.getElementById('show_thumbnail').checked) { var thumbnailImg = document.createElement('img'); thumbnailImg.src = videoThumbnail; thumbnailImg.alt = 'Thumbnail'; videoContainer.appendChild(thumbnailImg); } var videoLink = document.createElement('a'); videoLink.href = `https://www.youtube.com/watch?v=${videoId}`; videoLink.textContent = videoTitle; videoContainer.appendChild(videoLink); var videoIdText = document.createElement('p'); videoIdText.textContent = 'Video ID: ' + videoId; videoContainer.appendChild(videoIdText); var uploadDateText = document.createElement('p'); uploadDateText.textContent = 'Upload Date: ' + videoPublishedAt; videoContainer.appendChild(uploadDateText); var commentsUrl = `https://www.googleapis.com/youtube/v3/commentThreads?part=snippet&maxResults=5&videoId=${videoId}&key=${apiKey}`; fetch(commentsUrl) .then(function(commentsResponse) { if (!commentsResponse.ok) { throw new Error('Error: cURL comments request failed'); } return commentsResponse.json(); }) .then(function(commentsData) { var comments = commentsData.items; comments.forEach(function(comment) { var commentText = document.createElement('p'); commentText.textContent = 'Comment: ' + comment.snippet.topLevelComment.snippet.textDisplay; videoContainer.appendChild(commentText); }); }) .catch(function(error) { console.error(error); }); document.body.appendChild(videoContainer); }); }) .catch(function(error) { console.error(error); }); }); }); </script> ___________________ Youtube Channel Search ------------------------------------ <br> Set Channel: <a href="#" onclick="document.getElementById('channelId').value = 'UCmIpOnd5BVx5Si2hp0WNKZw'; return false;">Repeekyraid</a> <a href="#" onclick="document.getElementById('channelId').value = 'UCrltGih11A_Nayz6hG5XtIw'; return false;">Diarykeeper</a><br> <!DOCTYPE html> <html> <head> <title>YouTube Channel Content Search</title> </head> <body> <h1>YouTube Channel Content Search</h1> <label for="channelName">Channel Name:</label> <input type="text" id="channelName"><br><br> <label for="channelId">Channel ID:</label> <input type="text" id="channelId" value="UCrltGih11A_Nayz6hG5XtIw"><br><br> <label for="searchText">Search Text:</label> <input type="text" id="searchText" required><br><br> <button onclick="searchVideos()">Search</button> <div id="searchResults"></div> <script> function searchVideos() { var channelName = document.getElementById("channelName").value; var channelId = document.getElementById("channelId").value; var searchText = document.getElementById("searchText").value; if (searchText === "") { alert("Please enter a search text."); return; } // Set your YouTube Data API key var apiKey = 'APIKEY'; // Determine the channel parameter based on channel name or channel ID var channelParam = ""; if (channelId !== "") { channelParam = "channelId=" + encodeURIComponent(channelId); } else if (channelName !== "") { channelParam = "channel=" + encodeURIComponent(channelName); } // Make a request to the YouTube Data API var url = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=" + encodeURIComponent(searchText) + "&maxResults=10&key=" + apiKey + "&" + channelParam; fetch(url) .then(response => response.json()) .then(data => { var searchResultsDiv = document.getElementById("searchResults"); searchResultsDiv.innerHTML = ""; if (data.items && data.items.length > 0) { var resultsHeading = document.createElement("h2"); resultsHeading.textContent = "Search Results"; searchResultsDiv.appendChild(resultsHeading); data.items.forEach(function(item) { if (item.id.kind !== "youtube#video") { return; // Skip non-video results } var videoId = item.id.videoId; var videoTitle = item.snippet.title; var videoThumbnail = item.snippet.thumbnails.default.url; var videoLink = "https://www.youtube.com/watch?v=" + videoId; var videoLinkElement = document.createElement("a"); videoLinkElement.href = videoLink; videoLinkElement.target = "_blank"; var thumbnailImageElement = document.createElement("img"); thumbnailImageElement.src = videoThumbnail; thumbnailImageElement.alt = videoTitle; var videoTitleElement = document.createElement("p"); videoTitleElement.textContent = videoTitle; videoLinkElement.appendChild(thumbnailImageElement); videoLinkElement.appendChild(videoTitleElement); searchResultsDiv.appendChild(videoLinkElement); }); } else { var noResultsMessage = document.createElement("p"); noResultsMessage.textContent = "No videos found for the specified channel and search text."; searchResultsDiv.appendChild(noResultsMessage); } }) .catch(error => { console.error(error); alert("An error occurred during the API request."); }); } </script> </body> </html> ______________________ Youtube Search ------------------------- <!DOCTYPE html> <html> <head> <title>YouTube Search</title> <style> .additional-info { color: #888; } </style> </head> <body> <label for="api_key">API Key:</label> <input type="text" id="api_key" required><br><br> <label for="search_query">Search Query:</label> <input type="text" id="search_query" required><br><br> <label for="max_results">Max Results:</label> <input type="number" id="max_results" min="1" max="50" value="50" required><br><br> <input type="checkbox" id="show_additional_info"> <label for="show_additional_info">Show Additional Info</label><br><br> <button onclick="searchYouTube()">Search</button> <br><br> <div id="results"></div> <script> function searchYouTube() { var apiKey = document.getElementById('api_key').value; var searchQuery = document.getElementById('search_query').value; var maxResults = document.getElementById('max_results').value; var showAdditionalInfo = document.getElementById('show_additional_info').checked; var apiUrl = 'https://www.googleapis.com/youtube/v3/search'; var requestUrl = apiUrl + '?part=snippet&maxResults=' + maxResults + '&q=' + encodeURIComponent(searchQuery) + '&key=' + apiKey; fetch(requestUrl) .then(function(response) { return response.json(); }) .then(function(data) { var resultsDiv = document.getElementById('results'); resultsDiv.innerHTML = ''; data.items.forEach(function(item) { if (item.id.kind === 'youtube#video') { var videoId = item.id.videoId; var title = item.snippet.title; var link = 'https://www.youtube.com/watch?v=' + videoId; var titleElement = document.createElement('p'); titleElement.textContent = 'Title: ' + title; if (showAdditionalInfo) { var channelId = item.snippet.channelId; var channelTitle = item.snippet.channelTitle; var uploadDate = item.snippet.publishedAt; var channelElement = document.createElement('p'); channelElement.classList.add('additional-info'); channelElement.textContent = 'Channel: ' + channelTitle + ' (ID: ' + channelId + ')'; var uploadDateElement = document.createElement('p'); uploadDateElement.classList.add('additional-info'); uploadDateElement.textContent = 'Upload Date: ' + uploadDate; resultsDiv.appendChild(channelElement); resultsDiv.appendChild(uploadDateElement); } var linkElement = document.createElement('a'); linkElement.href = link; linkElement.textContent = 'Link'; linkElement.target = '_blank'; // Add target="_blank" attribute resultsDiv.appendChild(titleElement); resultsDiv.appendChild(linkElement); resultsDiv.appendChild(document.createElement('br')); } }); }) .catch(function(error) { console.log('Error:', error); }); } </script> </body> </html> _____________________________ Disable site elements (here: object & iframe) if "keyword" is part of url: ----------------------------------------------------------------------------------------------------------- <script> // Check if "lowbandwidth" is present in the URL if (window.location.href.includes("lowbandwidth")) { // Create a <style> element var style = document.createElement("style"); // Define the CSS rules style.innerHTML = ` iframe, object { display: none !important; } `; // Add the <style> element to the document's head document.head.appendChild(style); } </script> _____________________ Disable all img & iframe onpage: -------------------------------------------------- <style> img, iframe { display: none !important; } </style> _____________ HTML Render ---------------------- <!DOCTYPE html> <html> <head> <title>HTML Render</title> <script type="text/javascript"> function renderContent() { var content = document.getElementById("content").value; var base64 = btoa(content); var iframe = document.getElementById("output"); iframe.src = "data:text/html;base64," + base64; } </script> </head> <body> <textarea id="content" rows="15" cols="50"></textarea> <br /> <button onclick="renderContent()">Render</button> <hr> <div> <iframe id="output" style="border: 0px #ffffff none;" name="statusit" scrolling="auto" frameborder="0" marginheight="0px" marginwidth="0px" height="1200px" width="80%" allowfullscreen ></iframe> </div> </body> </html> ______________________________ Folderlister (with urlconversion, link support and download to .html) ------------------------------------------ <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Drag and Drop Folder</title> </head> <body> <button id="downloadButton">Download List</button> <div id="dropzone">Drop a folder here</div> <ul id="fileList"></ul> <input type="text" id="prependText" placeholder="Enter text to prepend" value="http://10.10.10.254/data/UsbDisk1/Volume1/"> <label> <input type="checkbox" id="wrapLinesCheckbox"> Wrap lines as links </label> <label> <input type="checkbox" id="convertToUrlCheckbox" checked> Convert paths to URL-conforming characters </label> <script> var dropzone = document.getElementById('dropzone'); var fileList = document.getElementById('fileList'); var prependText = document.getElementById('prependText'); var wrapLinesCheckbox = document.getElementById('wrapLinesCheckbox'); var convertToUrlCheckbox = document.getElementById('convertToUrlCheckbox'); var downloadButton = document.getElementById('downloadButton'); dropzone.addEventListener('dragover', function(event) { event.preventDefault(); }); dropzone.addEventListener('drop', function(event) { event.preventDefault(); // Clear the file list fileList.innerHTML = ''; var items = event.dataTransfer.items; for (var i = 0; i < items.length; i++) { var item = items[i]; if (item.kind === 'file') { var entry = item.webkitGetAsEntry(); if (entry) { if (entry.isDirectory) { traverseDirectory(entry, ''); } else if (entry.isFile) { addFileToList(entry.fullPath); } } } } }); function traverseDirectory(directory, parentPath) { var directoryReader = directory.createReader(); directoryReader.readEntries(function(entries) { for (var i = 0; i < entries.length; i++) { var entry = entries[i]; if (entry.isFile) { addFileToList(parentPath + entry.name); } else if (entry.isDirectory) { traverseDirectory(entry, parentPath + entry.name + '/'); } } }, function(error) { console.error('Error reading directory:', error); }); } function addFileToList(filePath) { var li = document.createElement('li'); var lineContent = prependText.value + filePath; if (convertToUrlCheckbox.checked) { lineContent = encodeURI(lineContent); } if (wrapLinesCheckbox.checked) { var link = document.createElement('a'); link.href = lineContent; link.textContent = lineContent; li.appendChild(link); } else { li.textContent = lineContent; } fileList.appendChild(li); } downloadButton.addEventListener('click', function() { var content = fileList.innerHTML; var filename = 'extHDD.html'; var element = document.createElement('a'); element.setAttribute('href', 'data:text/html;charset=UTF-8,' + encodeURIComponent(content)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); }); </script> <style> #dropzone { width: 500px; height: 100px; border: 2px dashed black; text-align: center; padding-top: 80px; font-size: 24px; } </style> </body> </html> ________________________ imgbb uploader (via api) ----------------------------------- <body> <input type="file" id="imageInput"> <button onclick="uploadImage()">Upload</button> <div id="imageURL"></div> <div id="imageViewURL"></div> <script> function uploadImage() { const apiKey = 'Apikey'; const expiration = 0; const imageInput = document.getElementById('imageInput'); const imageFile = imageInput.files[0]; if (!imageFile) { console.log('Please select an image file.'); return; } const formData = new FormData(); formData.append('image', imageFile); fetch(`https://api.imgbb.com/1/upload?key=${apiKey}&expiration=${expiration}`, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { console.log(data); // JSON response from the API if (data.status === 200) { console.log('Image uploaded successfully!'); const imageURL = data.data.url; const imageViewURL = data.data.url_viewer; document.getElementById('imageURL').textContent = `Image URL: ${imageURL}`; document.getElementById('imageViewURL').textContent = `Viewer URL: ${imageViewURL}`; // Perform any further actions with the uploaded image data } else { console.log('Image upload failed. Error:', data.error.message); } }) .catch(error => { console.error('Error:', error); }); } </script> </body> </html> ___________________________ Youtube URL Link Extractor ------------------------------------------ <script> var xhr = new XMLHttpRequest(); // Define the URL of the webpage var url = 'https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/UnderratedContent.html'; // Send a GET request to the URL xhr.open('GET', url, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { // Get the response text var html = xhr.responseText; // Create a regular expression to match URLs containing "watch" and their corresponding link names var regex = /<a[^>]+href=["']([^"']*\bwatch\b[^"']*)["'][^>]*>(.*?)<\/a>/gi; // Find all matches in the HTML var matches = html.matchAll(regex); // Create a container div to hold the textareas var containerDiv = document.createElement('div'); containerDiv.style.display = 'flex'; // Create a textarea element to display the URLs var urlTextarea = document.createElement('textarea'); urlTextarea.rows = 10; urlTextarea.cols = 50; urlTextarea.style.overflow = 'auto'; // Create a textarea element to display the link names var nameTextarea = document.createElement('textarea'); nameTextarea.rows = 10; nameTextarea.cols = 50; nameTextarea.style.overflow = 'auto'; // Add each match to the textareas if (matches) { for (var match of matches) { // Extract the URL and link name from the match var url = match[1]; var name = match[2]; // Append the URL to the URL textarea urlTextarea.value += url + '\n'; // Append the link name to the name textarea nameTextarea.value += name + '\n'; } } else { urlTextarea.value = 'No URLs containing "watch" found.'; nameTextarea.value = 'No link names found.'; } // Append the textareas to the container div containerDiv.appendChild(urlTextarea); containerDiv.appendChild(nameTextarea); // Append the container div to the document body document.body.appendChild(containerDiv); // Set up event listener to synchronize scrolling urlTextarea.addEventListener('scroll', function () { nameTextarea.scrollTop = urlTextarea.scrollTop; }); nameTextarea.addEventListener('scroll', function () { urlTextarea.scrollTop = nameTextarea.scrollTop; }); } }; // Send the request xhr.send(); </script> <iframe src="data:text/html;base64, CjxzY3JpcHQ+CnZhciB4aHIgPSBuZXcgWE1MSHR0cFJlcXVlc3QoKTsKdmFyIHVybCA9ICdodHRwczovL3J5M3lyLmdpdGh1Yi5pby9PU1RSL0RpYXJ5a2VlcGVyc19Ib21lcGFnZS9VbmRlcnJhdGVkQ29udGVudC5odG1sJzsKeGhyLm9wZW4oJ0dFVCcsIHVybCwgdHJ1ZSk7Cnhoci5vbnJlYWR5c3RhdGVjaGFuZ2UgPSBmdW5jdGlvbiAoKSB7CiAgaWYgKHhoci5yZWFkeVN0YXRlID09PSA0ICYmIHhoci5zdGF0dXMgPT09IDIwMCkgewogICAgdmFyIGh0bWwgPSB4aHIucmVzcG9uc2VUZXh0OwogICAgdmFyIHJlZ2V4ID0gLzxhW14+XStocmVmPVsiJ10oW14iJ10qXGJ3YXRjaFxiW14iJ10qKVsiJ11bXj5dKj4oLio/KTxcL2E+L2dpOwogICAgdmFyIG1hdGNoZXMgPSBodG1sLm1hdGNoQWxsKHJlZ2V4KTsKICAgIHZhciBteW5ld0RpdiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpOwogICAgaWYgKG1hdGNoZXMpIHsKICAgICAgZm9yICh2YXIgbWF0Y2ggb2YgbWF0Y2hlcykgewogICAgICAgIHZhciB1cmwgPSBtYXRjaFsxXTsKICAgICAgICB2YXIgbmFtZSA9IG1hdGNoWzJdOwogICAgICAgIHZhciBsaW5rID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYScpOwogICAgICAgIGxpbmsuaHJlZiA9IHVybDsKICAgICAgICBsaW5rLnRhcmdldCA9ICdfYmxhbmsnOwogICAgICAgIGxpbmsudGV4dENvbnRlbnQgPSBuYW1lOwogICAgICAgIG15bmV3RGl2LmFwcGVuZENoaWxkKGxpbmspOwogICAgICAgIG15bmV3RGl2LmFwcGVuZENoaWxkKGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2JyJykpOwogICAgICB9CiAgICB9IGVsc2UgewogICAgICBteW5ld0Rpdi50ZXh0Q29udGVudCA9ICdObyBsaW5rcyBmb3VuZC4nOwogICAgfQogICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChteW5ld0Rpdik7CiAgfQp9Owp4aHIuc2VuZCgpOwo8L3NjcmlwdD4= " style="border:0px #ffffff none;" name="statusit" scrolling="auto" frameborder="0" marginheight="0px" marginwidth="0px" height="250px" width="500px" allowfullscreen></iframe> ______________________________________________________ Pixiv Dynamic Owlcarousel generator (with embed as img): ----------------------------------------------------- <!DOCTYPE html> <html> <head> <title>Ryedai1's new Art n Doodle's</title> <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.1/animate.min.css'> <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.0.0-beta.3/assets/owl.carousel.min.css'> <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.0.0-beta.3/assets/owl.theme.default.min.css'> </head> <body> <!-- partial:index.partial.html --> <div class="owl-carousel owl-theme" id="carousel"></div> <!-- partial --> <script src='https://code.jquery.com/jquery-1.12.2.min.js'></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.0.0-beta.3/owl.carousel.min.js'></script> <script> $(document).ready(function(){ var imageUrl = "https://embed.pixiv.net/decorate.php?illust_id="; var artworks = [ { id: "101357774" }, { id: "98087776" }, ]; var carousel = $('#carousel'); // Dynamically generate the carousel items artworks.forEach(function(artwork) { var link = $('<a></a>').attr('target', '_blank').attr('href', 'https://www.pixiv.net/en/artworks/' + artwork.id); //var image = $('<img>').attr('src', imageUrl + artwork.id + '&mode=sns-automator').attr('height', '50%').attr('width', '50%'); var image = $('<img>').attr('src', imageUrl + artwork.id + '&mode=sns-automator').attr('height', '50%').attr('width', '50%').attr('loading', 'lazy'); var item = $('<div></div>').addClass('item').append(link.append(image)); carousel.append(item); }); // Initialize the Owl Carousel carousel.owlCarousel({ loop:true, margin:10, nav:true, center: true, responsive:{ 0:{ items:1 }, 600:{ items:3 }, 1000:{ items:5 } } }); }); </script> </body> </html> ___________________________________ Pseudo Password Box to reveal content: -------------------------------------------------------------- <script> function checkKeyword() { var e = document.getElementById("inputField").value; if (e.length === 4) { if (e === atob("NDU2Nw==")) { document.getElementById("inputField").style.display = "none"; var newElement = document.createElement("p"); newElement.innerHTML = decodeURIComponent(atob("aHR0cHM6Ly9odG1sZWRpdC5zcXVhcmVmcmVlLmNvbS8=")); document.body.appendChild(newElement); } else { document.getElementById("keywordForm").submit(); } } } </script> <form id="keywordForm" onsubmit="event.preventDefault(); checkKeyword()"> <input type="text" id="inputField" style="border:none;" placeholder="Enter keyword"> </form> ___________________________________________ Trigger event (from ext url etc.) when date-span is divisible by 'x' "birthday": ---------------------------------------------------------------------------------------------------------------- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script> $(document).ready(function() { var currentDate = new Date(); var targetDate = new Date('July 13, 2021'); var daysPassed = Math.floor((currentDate.getTime() - targetDate.getTime()) / (1000 * 60 * 60 * 24)); if (daysPassed % 750 === 0) { document.querySelector("a[href='javascript:mybdayFunction()']").click(); } }); function mybdayFunction() { $("#mybday").load("https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/site/effects/wisteria_petal_rain.html"); } </script> <a href="javascript:mybdayFunction()" style="color:transparent">bday</a> <div id="bigdivv" style="position:relative; top: 0px; left: -650px;"> <div class="formClass"><div id="mybday"></div></div> </div> _______________________________________ 3D Model render with url dropdown -------------------------------------- <body> <select id="model-selector"> <option value="">Select a model</option> <option value="https://s3-us-west-2.amazonaws.com/s.cdpn.io/127738/banana.obj">Banana</option> <option value="https://s3-us-west-2.amazonaws.com/s.cdpn.io/127738/banana.obj">Model 2</option> <option value="https://example.com/model3.obj">Model 3</option> </select> <canvas id="canvas"></canvas> <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js'></script> <script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/127738/OBJLoader.js'></script> <script> let scene, camera, renderer, mesh; function init() { // Create a new scene scene = new THREE.Scene(); // Create a new camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 50, 100); // Create a new renderer renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas') }); renderer.setSize(window.innerWidth, window.innerHeight*2); // Add a light to the scene const light = new THREE.PointLight(0xffffff, 1, 1000); light.position.set(0, 0, 50); scene.add(light); // Load the initial model loadModel(document.getElementById('model-selector').value); } function loadModel(url) { // Remove the old mesh from the scene if (mesh) { scene.remove(mesh); } // Load the new model const loader = new THREE.OBJLoader(); loader.load(url, (obj) => { // Set the scale and position of the mesh obj.scale.set(1, 1, 1); obj.position.set(0, 75, 50); // Add the mesh to the scene mesh = obj; scene.add(mesh); }); } // Initialize the renderer init(); // Listen for changes to the model selector document.getElementById('model-selector').addEventListener('change', (event) => { const url = event.target.value; if (url) { loadModel(url); } }); // Render the scene function render() { requestAnimationFrame(render); renderer.render(scene, camera); mesh.rotation.z+=.01; } render(); </script> </body> </html> __________________________________ Soundcloud URL to iframe ----------------------------------- <script> // The SoundCloud URL to extract the track ID from const url = 'https://m.soundcloud.com/winterworks_gmbh/metal-stage'; // The URL of the CodeTabs proxy const proxyUrl = 'https://api.codetabs.com/v1/proxy?quest='; // Fetch the SoundCloud page source using the CodeTabs proxy fetch(proxyUrl + url) .then(response => response.text()) .then(pageSource => { // Check if the page source contains a "404 Not Found" error if (pageSource.includes('<title>404 Not Found</title>')) { throw new Error('The SoundCloud track could not be found.'); } // Extract the track ID from the page source const regex = /sounds:(\d+)/; const match = pageSource.match(regex); if (!match) { throw new Error('The SoundCloud track ID could not be found.'); } const trackID = match[1]; // Embed the SoundCloud player with the track ID const playerURL = `https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/${trackID}&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true`; const iframe = `<iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay" src="${playerURL}"></iframe>`; // Output the embedded player document.body.innerHTML = iframe; }) .catch(error => { console.error('Error:', error); }); </script> <plaintext> <?php // The SoundCloud URL to extract the track ID from $url = 'https://m.soundcloud.com/winterworks_gmbh/metal-stage'; // Fetch the SoundCloud page source $pageSource = file_get_contents($url); // Extract the track ID from the page source preg_match('/sounds:(\d+)/', $pageSource, $matches); $trackID = $matches[1]; // Embed the SoundCloud player with the track ID $playerURL = 'https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/' . $trackID . '&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true'; $iframe = '<iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay" src="' . $playerURL . '"></iframe>'; // Output the embedded player echo $iframe; ?> ________________________________________________ Text Interspersion remover ----------------------------------------- <body> <textarea id="input-textbox" cols="60" rows="4"></textarea><br><br> <textarea id="output-textbox" cols="60" rows="4" style="border: none;"></textarea> <script> document.addEventListener("DOMContentLoaded", function() { // Get the input and output textbox elements const inputTextbox = document.getElementById("input-textbox"); const outputTextbox = document.getElementById("output-textbox"); // Add an event listener to the input textbox to detect changes inputTextbox.addEventListener("input", function() { // Remove all formatting characters from the textbox value const plainText = inputTextbox.value.replace(/[^\x00-\x7F]/g, ""); // Update the output textbox value with the plain text outputTextbox.value = plainText; }); }); </script> </body> </html> _________________________________ Reload page after X seconds ---------------------------------------------- <meta http-equiv="refresh" content="5" > https://stackoverflow.com/questions/4644027/how-to-automatically-reload-a-page-after-a-given-period-of-inactivity _____________________________________________________ Show last Github CommitAction + what was changed: ----------------------------------------------------------------------------------- <script> fetch('https://api.github.com/repos/Ry3yr/OSTR/commits?per_page=1') .then(res => res.json()) .then(res => { const latestCommit = res[0]; const message = latestCommit.commit.message; const date = new Date(latestCommit.commit.author.date).toLocaleDateString(); document.getElementById('message').innerHTML = `${message} - ${date}`; }) </script> <pre id="message">Loading</pre> _____________________________________ Display LastGithubUpdate: (requires proxy because of CORS) ---------------------------------------------------------------------------------------- <p id="last-updated"></p> <script> const atomFeedUrl = 'https://api.codetabs.com/v1/proxy?quest=https://github.com/Ry3yr/OSTR/releases.atom'; fetch(atomFeedUrl) .then(response => response.text()) .then(xmlText => { const parser = new DOMParser(); const xmlDocument = parser.parseFromString(xmlText, 'application/xml'); const lastUpdated = xmlDocument.querySelector('updated').textContent; const lastUpdatedElement = document.getElementById('last-updated'); lastUpdatedElement.textContent = `Last updated: ${lastUpdated}`; }) .catch(error => { console.error('Failed to fetch Atom feed:', error); }); </script> </body> </html> ____________________________________ Display (random) Code from txt file: ---------------------------------------- <script> fetch('https://ry3yr.github.io/OSTR/release/other/Computerstuff/Commands/HTML_Codes.txt') .then(response => response.text()) .then(fileContents => { const pattern = /------(.*?)_____/gs; const matches = fileContents.match(pattern).slice(1); // remove first match const randomIndex = Math.floor(Math.random() * matches.length); const randomMatch = matches[randomIndex]; const numLines = randomMatch.split('\n').length; if (numLines >= 3) { const plaintext = document.createElement('plaintext'); plaintext.textContent = randomMatch; document.body.appendChild(plaintext); } }); </script> ____________________________ Mastodon profile renderer: --------------------------------------- <script> const profileUrl = 'https://mastodon.social/api/v1/accounts/109977878421486714'; // Make a GET request to the profile URL using fetch fetch(profileUrl) .then(response => response.json()) .then(data => { // Access the fields in the JSON object const id = data.id; const username = data.username; const display_name = data.display_name; const locked = data.locked; const bot = data.bot; const discoverable = data.discoverable; const group = data.group; const created_at = data.created_at; const note = data.note; const url = data.url; const avatar = data.avatar; const avatar_static = data.avatar_static; const header = data.header; const header_static = data.header_static; const followers_count = data.followers_count; const following_count = data.following_count; const statuses_count = data.statuses_count; const last_status_at = data.last_status_at; const noindex = data.noindex; const emojis = data.emojis; const roles = data.roles; const fields = data.fields; // Do something with the data document.write(`${username}@mastodon.social<br>\n`); document.write(`<img src=${avatar} width=12%>`); document.write("<br><hr>"); document.write(`${note}`); document.write("<hr>"); //document.write(`${display_name}\n`); document.write(`Following: <b>${following_count}</b>\n`); document.write(`Followers: <b>${followers_count}</b>\n`); document.write(`Posts: <b>${statuses_count}</b>\n`); }) .catch(error => console.error(error)); </script> _____________________________________________ Youtube RSS render (with iframe support) ------------------------------------------------------------- <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-dateFormat/1.0/jquery.dateFormat.js"></script> <style> .youtube-video { margin: 20px; border: 1px solid #ccc; padding: 10px; border-radius: 5px; box-shadow: 2px 2px 5px #ccc; display: flex; flex-wrap: wrap; align-items: center; } .youtube-video img { margin-right: 10px; max-width: 200px; } .youtube-video h2 { margin-top: 0; } .youtube-video iframe { margin-left: 20px; } </style> </head> <body> <div id="youtube-feed"></div> <script> $(function() { var $content = $('#youtube-feed'); var data = { rss_url: 'https://www.youtube.com/feeds/videos.xml?channel_id=UCmIpOnd5BVx5Si2hp0WNKZw' }; $.get('https://api.rss2json.com/v1/api.json', data, function(response) { if (response.status == 'ok') { var output = ''; $.each(response.items, function(k, item) { const title = item.title; const titleShortened = (title.length > 60) ? title.substring(0, 60) + "..." : title; const thumbnail = item.thumbnail; const videoId = item.guid.split(':')[2]; const count = item.viewCount; const date = new Date(item.pubDate).toLocaleDateString(); const url = `https://www.youtube.com/watch?v=${videoId}`; const urlShortened = url.substring(0,60)+'...'; const embedUrl = `https://www.youtube.com/embed/${videoId}`; output += '<div class="youtube-video">'; output += `<a href="${url}" target="_blank"><img src="${thumbnail}" /></a>`; output += `<iframe width="260" height="115" src="${embedUrl}" frameborder="0" allowfullscreen></iframe>`; output += '<br>'; output += '<div>'; output += `<h2><a href="${url}" target="_blank">${titleShortened}</a></h2>`; output += `<p>Date: ${date}</p>`; output += '</div>'; output += '</div>'; return k < 9; }); $content.html(output).hide(); $content.fadeIn('slow'); } }); }); </script> </body> </html> __________________________________ Nitter RSS render (with image support) --------------------------------------------------------- <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-dateFormat/1.0/jquery.dateFormat.js"></script> <style> .medium-blog-image { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; } .medium-blog-image img { margin: 5px; max-width: 100%; max-height: 200px; } </style> </head> <body> <div id="prita-medium-content"></div> <script> $(function() { var $content = $('#prita-medium-content'); var data = { rss_url: 'https://nitter.absturztau.be/alceawisteria/rss' }; $.get('https://api.rss2json.com/v1/api.json', data, function(response) { if (response.status == 'ok') { var output = ''; $.each(response.items, function(k, item) { output += '<div class="medium-blog-post" onclick="window.open(\'' + item.guid + '\',\'mywindow\');">' output += '<div class="medium-blog-title"><h2>' + item.title + '</h2></div>'; output += '<div class="medium-blog-date">' + $.format.date(item.pubDate, "MMM dd, yyyy") + '</div>'; output += '<div class="medium-blog-image">'; var $itemContent = $('<div>').html(item.content); $itemContent.find('img').each(function() { output += '<img src="' + $(this).attr('src') + '" />'; }); output += '</div>'; output += '</div>'; return k < 8; }); $content.html(output).hide(); $content.fadeIn('slow'); } }); }); </script> </body> <!-- partial --> </body> </html> https://codepen.io/ryedai1/pen/oNQompj __________________________________ Convert #Textfield "Enter" #events to "•" ----------------------------------------------------------- (To stop single line approaches from breaking ) <script> const descriptionTextarea = document.getElementById('description'); descriptionTextarea.addEventListener('keydown', function (event) { if (event.keyCode === 13) { event.preventDefault(); const currentValue = this.value; this.value = currentValue + '•'; } }); </script> _____________________________ Mastodon account ID lookup: ------------------------------------------ <!---account-id-lookup-https://pb.todon.de/api/v1/accounts/lookup?acct=@ryedai@mastodon.social--> <body> <label for="instance">Mastodon instance:</label> <input type="text" id="instance" value="mastodon.social"> <label for="username">Username:</label> <input type="text" id="username" value="ryedai"> <button onclick="fetchAvatarUrl()">Get UserID</button> <p id="avatarUrlContainer"></p> <script> function fetchAvatarUrl() { const instance = document.getElementById('instance').value; const username = document.getElementById('username').value; const rssUrl = `https://${instance}/@${username}.rss`; fetch(rssUrl) .then(response => response.text()) .then(xml => { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xml, 'text/xml'); const urlElement = xmlDoc.querySelector('url'); const url = urlElement.textContent; const startIndex = url.indexOf('avatars/') + 'avatars/'.length; const endIndex = url.indexOf('/original'); const avatarUrl = url.slice(startIndex, endIndex).replace(/\//g, ''); // Display the URL in the DOM const urlContainer = document.getElementById('avatarUrlContainer'); urlContainer.textContent = avatarUrl; }) .catch(error => console.error(error)); } </script> </body> </html> <!--<script> fetch('https://mastodon.social/@ryedai.rss') .then(response => response.text()) .then(xml => { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xml, 'text/xml'); const urlElement = xmlDoc.querySelector('url'); const url = urlElement.textContent; const startIndex = url.indexOf('avatars/') + 'avatars/'.length; const endIndex = url.indexOf('/original'); const avatarUrl = url.slice(startIndex, endIndex).replace(/\//g, ''); const urlContainer = document.createElement('p'); urlContainer.textContent = avatarUrl; document.body.appendChild(urlContainer); }) .catch(error => console.error(error)); </script>--> _______________________________ Multi Account Mastodon api tl parser (with image/video/audio & youtube support & pixiv & tenor & imgbb-dir-img-link) ---------------------------------------------------------- <body> <div> <input type="radio" name="apiUrl" value="https://mastodon.social/api/v1/accounts/109977878421486714/statuses" checked> ryedai1 <input type="radio" name="apiUrl" value="https://pb.todon.de/api/v1/accounts/109629985010224381/statuses"> Alcea <!-- Add more radio buttons for additional instances --> <div id="feed"></div> </div> <script> // Function to fetch data from the selected API URL function fetchData(apiUrl) { const accessToken = 'your-access-token'; fetch(apiUrl, { headers: { 'Authorization': 'Bearer ' + accessToken } }) .then(response => response.json()) .then(data => { // Clear the feed before appending new content document.getElementById('feed').innerHTML = ''; data.forEach(status => { const content = '<div>' + status.content + '</div>'; let media = ''; let avatar = ''; let tootLink = ''; // Check for video and audio attachments if (status.media_attachments.length > 0) { media = '<div>'; status.media_attachments.forEach(attachment => { if (attachment.type === 'image') { media += '<img src="' + attachment.url + '" width="50%"><br>'; } else if (attachment.type === 'video') { media += '<video src="' + attachment.url + '" controls width="50%"></video><br>'; } else if (attachment.type === 'audio') { media += '<audio src="' + attachment.url + '" controls></audio><br>'; } }); media += '</div>'; } else { // Check for YouTube video link const youtubeRegex = /https?:\/\/(www\.)?(m\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/; const youtubeMatch = status.content.match(youtubeRegex); if (youtubeMatch) { // Extract video ID from YouTube link const videoId = youtubeMatch[3]; // Create embedded player for YouTube video media = `<div><iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe></div>`; } else { // Check for Pixiv artwork link const pixivRegex = /https?:\/\/(?:www\.)?pixiv\.net\/(?:en\/)?artworks\/(\d+)/; const pixivMatch = status.content.match(pixivRegex); if (pixivMatch) { // Extract artwork ID from Pixiv link const artworkId = pixivMatch[1]; // Create image preview for Pixiv artwork media = `<div><img src="https://embed.pixiv.net/decorate.php?illust_id=${artworkId}&mode=sns-automator" width="50%"></div>`; } else { const tenorRegex = /https?:\/\/(?:www\.)?tenor\.com\/view\/[a-zA-Z0-9-]+-(\d+)/; const tenorMatch = status.content.match(tenorRegex); if (tenorMatch) { // Extract Tenor.com video ID const videoId = tenorMatch[1]; // Create embedded player for Tenor.com video media = `<div><iframe src="https://tenor.com/embed/${videoId}" frameborder="0" allowfullscreen="true" width="100%" height="400px"></iframe></div>`; }else{ // Check for imgbb image URLs in the status content const imageRegex = /(https?:\/\/(?:www\.)?i\.ibb\.co\/[^ ]+\.(?:jpg|png|gif|bmp))/g; const imageMatches = status.content.match(imageRegex); if (imageMatches) { media += '<div>'; imageMatches.forEach(url => { media += '<img src="' + url + '" width="50%"><br>'; }); media += '</div>'; } } } } } // Check for spoiler tag if (status.url.includes('activity')) { avatar = '<!--<details><summary>Avatar (spoiler)</summary><img src="' + status.account.avatar + '" width="100px"></details>--><br>'; tootLink = '<!--<details><summary>View on Mastodon (spoiler)</summary><a href="' + status.url + '" target="_blank">Click here to view activity</a></details>-->'; } else { avatar = '<img src="' + status.account.avatar + '" width="100px"><br>'; tootLink = '<a href="' + status.url + '" target="_blank">View on Mastodon</a>'; } // Get the date of the status const date = new Date(status.created_at); // Append content to feed document.getElementById('feed').innerHTML += content + media + avatar + tootLink + '&nbsp;' + date.toLocaleString() + '<hr>'; }); }) .catch(error => console.error(error)); } // Event listener for radio button change const radioButtons = document.querySelectorAll('input[name="apiUrl"]'); radioButtons.forEach(radioButton => { radioButton.addEventListener('change', function() { const selectedApiUrl = this.value; fetchData(selectedApiUrl); }); }); // Initial fetch using the default API URL const defaultApiUrl = 'https://mastodon.social/api/v1/accounts/109977878421486714/statuses'; fetchData(defaultApiUrl); </script> </body> ______________________________ Mastodon api timeline parser (with image/video/audio & youtube support & pixiv) ------------------------------------------------------------------------------------------- <body> <div id="feed"></div> <script> const apiUrl = 'https://mastodon.social/api/v1/accounts/109977878421486714/statuses'; const accessToken = 'your-access-token'; fetch(apiUrl, { headers: { 'Authorization': 'Bearer ' + accessToken } }) .then(response => response.json()) .then(data => { data.forEach(status => { const content = '<div>' + status.content + '</div>'; let media = ''; let avatar = ''; let tootLink = ''; // Check for video and audio attachments if (status.media_attachments.length > 0) { media = '<div>'; status.media_attachments.forEach(attachment => { if (attachment.type === 'image') { media += '<img src="' + attachment.url + '" width="50%"><br>'; } else if (attachment.type === 'video') { media += '<video src="' + attachment.url + '" controls width="50%"></video><br>'; } else if (attachment.type === 'audio') { media += '<audio src="' + attachment.url + '" controls></audio><br>'; } }); media += '</div>'; } else { // Check for YouTube video link const youtubeRegex = /https?:\/\/(www\.)?(m\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/; const youtubeMatch = status.content.match(youtubeRegex); if (youtubeMatch) { // Extract video ID from YouTube link const videoId = youtubeMatch[3]; // Create embedded player for YouTube video media = `<div><iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe></div>`; } else { // Check for Pixiv artwork link const pixivRegex = /https?:\/\/(?:www\.)?pixiv\.net\/(?:en\/)?artworks\/(\d+)/; const pixivMatch = status.content.match(pixivRegex); if (pixivMatch) { // Extract artwork ID from Pixiv link const artworkId = pixivMatch[1]; // Create image preview for Pixiv artwork media = `<div><img src="https://embed.pixiv.net/decorate.php?illust_id=${artworkId}&mode=sns-automator" width="50%"></div>`; } } } // Check for spoiler tag if (status.url.includes('activity')) { avatar = '<!--<details><summary>Avatar (spoiler)</summary><img src="' + status.account.avatar + '" width="100px"></details>--><br>'; tootLink = '<!--<details><summary>View on Mastodon (spoiler)</summary><a href="' + status.url + '" target="_blank">Click here to view activity</a></details>-->'; } else { avatar = '<img src="' + status.account.avatar + '" width="100px"><br>'; tootLink = '<a href="' + status.url + '" target="_blank">View on Mastodon</a>'; } // Get the date of the status const date = new Date(status.created_at); // Append content to feed document.getElementById('feed').innerHTML += content + media + avatar + tootLink + '&nbsp;' + date.toLocaleString() + '<hr>'; }); }) .catch(error => console.error(error)); </script> </body> __________________________________________________ Mastodon api timeline parser (with image & youtube support) ------------------------------------------------------------------------------------------- <body> <div id="feed"></div> <script> const apiUrl = 'https://mastodon.social/api/v1/accounts/109977878421486714/statuses'; const accessToken = 'your-access-token'; fetch(apiUrl, { headers: { 'Authorization': 'Bearer ' + accessToken } }) .then(response => response.json()) .then(data => { data.forEach(status => { const content = '<div>' + status.content + '</div>'; let media = ''; let avatar = ''; let tootLink = ''; // Check for YouTube video link const youtubeRegex = /https?:\/\/(www\.)?(m\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/; const youtubeMatch = status.content.match(youtubeRegex); if (youtubeMatch) { // Extract video ID from YouTube link const videoId = youtubeMatch[3]; // Create embedded player for YouTube video media = `<div><iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allowfullscreen></iframe></div>`; } else { // Check for image attachments if (status.media_attachments.length > 0) { media = '<div>'; status.media_attachments.forEach(attachment => { if (attachment.type === 'image') { media += '<img src="' + attachment.url + '" width="50%"><br>'; } }); media += '</div>'; } } // Check for spoiler tag if (status.url.includes('activity')) { avatar = '<!--<details><summary>Avatar (spoiler)</summary><img src="' + status.account.avatar + '" width="100px"></details>--><br>'; tootLink = '<!--<details><summary>View on Mastodon (spoiler)</summary><a href="' + status.url + '" target="_blank">Click here to view activity</a></details>-->'; } else { avatar = '<img src="' + status.account.avatar + '" width="100px"><br>'; tootLink = '<a href="' + status.url + '" target="_blank">View on Mastodon</a>'; } // Get the date of the status const date = new Date(status.created_at); // Append content to feed document.getElementById('feed').innerHTML += content + media + avatar + tootLink + '&nbsp;' + date.toLocaleString() + '<hr>'; }); }) .catch(error => console.error(error)); </script> </body> ________________________________ Mastodon api timeline parser: ---------------------------------------------- <div id="feed"></div> <script> const apiUrl = 'https://mastodon.social/api/v1/accounts/109977878421486714/statuses'; //const userId = '109977878421486714'; const accessToken = 'your-access-token'; fetch(apiUrl, { headers: { 'Authorization': 'Bearer ' + accessToken } }) .then(response => response.json()) .then(data => { data.forEach(status => { const content = '<div>' + status.content + '</div>'; const avatar = '<img src="' + status.account.avatar + '" width="100px"><br><hr>'; document.getElementById('feed').innerHTML += content + avatar; }); }) .catch(error => console.error(error)); </script> <!-- partial --> </body> </html> __________________________________ DragNDrop FileLister (&Search) ------------------------------ <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Drag and Drop Folder</title> </head> <body> <img src="https://www.solar-fox.de/files/oneo/img/soledos/logos/meteocontrol_neu.png" width="30%"><br> <input type="file" id="folder-selector" webkitdirectory directory="C:\Konfi-Data\Export"><br><br> <div id="dropzone">Drop a folder here</div> <input type="text" id="searchBox" placeholder="Search..."> <input type="text" id="keyword" placeholder="Count element"> <div id="count"></div> <button id="countTodayButton">Done today</button> <ul id="fileList"></ul> <script> var dropzone = document.getElementById('dropzone'); var fileList = document.getElementById('fileList'); var timestamps = { today: [], yesterday: [] }; var elementsDoneToday = 0; dropzone.addEventListener('dragover', function(event) { event.preventDefault(); }); dropzone.addEventListener('drop', function(event) { event.preventDefault(); var items = event.dataTransfer.items; for (var i = 0; i < items.length; i++) { var item = items[i]; if (item.kind === 'file' && item.webkitGetAsEntry().isDirectory) { traverseFileTree(item.webkitGetAsEntry(), function(file) { var timestamp = new Date(file.lastModified); var today = new Date(); today.setHours(0,0,0,0); var yesterday = new Date(today); yesterday.setDate(today.getDate() - 1); if (timestamp >= today) { timestamps.today.push(file.lastModified); } else if (timestamp >= yesterday) { timestamps.yesterday.push(file.lastModified); } var li = document.createElement('li'); li.textContent = file.name + " (" + timestamp.toLocaleString() + ")"; fileList.appendChild(li); var index = timestamps.today.indexOf(file.lastModified); if (index === -1) { index = timestamps.yesterday.indexOf(file.lastModified) + timestamps.today.length; } fileList.insertBefore(li, fileList.childNodes[index]); if (timestamp >= today) { elementsDoneToday++; } }); } } countDiv.textContent = 'Elements done today: ' + (timestamps.today.length); }); function traverseFileTree(item, callback) { var dirReader = item.createReader(); var entries = []; function readEntries() { dirReader.readEntries(function(results) { if (!results.length) { // All entries have been read entries.forEach(function(entry) { if (entry.isFile) { entry.file(function(file) { callback(file); }); } else if (entry.isDirectory) { traverseFileTree(entry, callback); } }); } else { // Read next set of entries entries = entries.concat(Array.from(results)); readEntries(); } }); } readEntries(); } var searchBox = document.getElementById('searchBox'); searchBox.addEventListener('input', function(event) { var searchTerm = event.target.value.toLowerCase(); var fileItems = fileList.getElementsByTagName('li'); for (var i = 0; i < fileItems.length; i++) { var fileName = fileItems[i].textContent.toLowerCase(); if (fileName.indexOf(searchTerm) === -1) { fileItems[i].classList.add('hidden'); } else { fileItems[i].classList.remove('hidden'); } } }); var keywordInput = document.getElementById('keyword'); var countDiv = document.getElementById('count'); var keywordCount = 0; keywordInput.addEventListener('input', function(event) { var keyword = event.target.value.toLowerCase(); var fileItems = fileList.getElementsByTagName('li'); keywordCount = 0; for (var i = 0; i < fileItems.length; i++) { var fileName = fileItems[i].textContent.toLowerCase(); if (fileName.indexOf(keyword) !== -1) { keywordCount++; } } countDiv.textContent = 'Number of files containing "' + keyword + '": ' + keywordCount; }); var countTodayButton = document.getElementById('countTodayButton'); countTodayButton.addEventListener('click', function() { var today = new Date(); today.setHours(0,0,0,0); elementsDoneToday = 0; for (var i = 0; i < timestamps.today.length; i++) { if (timestamps.today[i] >=today) { elementsDoneToday++; } } countDiv.textContent = 'Elements done today: ' + elementsDoneToday; }); </script> <style> #dropzone { width: 500px; height: 100px; border: 2px dashed black; text-align: center; padding-top: 80px; font-size: 24px; } .hidden { display: none; } </style> </body> </html> ___________________________________________ GDrive Direct link transform: ------------------------------------------ <body> <textarea id="myGDriveTextBox" style="width: 500px; height: 300px;"></textarea> <br> <button onclick="modifyLinks()">Modify Links</button> <br> <div id="modifiedLinks"></div> <script> function modifyLinks() { var textBox = document.getElementById("myGDriveTextBox"); var text = textBox.value; text = text.replace(/https:\/\/drive.google.com\/file\/d\/([\w-]+)\/view\?usp=sharing/g, "https://drive.google.com/uc?export=download&id=$1"); var modifiedLinksDiv = document.getElementById("modifiedLinks"); modifiedLinksDiv.innerHTML = "<pre>" + text + "</pre>"; } </script> </body> </html> ________________________ Pixiv Style Color BG Hashtag link: -------------------------------------------------- <a target="_blank" href="https://www.pixiv.net/en/users/75406576/artworks/%E6%B5%81%E6%98%9F%E3%81%AE%E3%83%AD%E3%83%83%E3%82%AF%E3%83%9E%E3%83%B3" style="background-color: red; padding: 5px; border-radius: 10px; text-decoration: none; color: white;">#Megaman Starforce</a> ______________________ Extract PlaystoreAppID from URL: ----------------------------------------------------- <script> function extractElement() { const inputUrl = document.getElementById('input-url').value; const regex = /details\?id=([^&]+)&hl=/; const matches = inputUrl.match(regex); if (matches && matches.length > 1) { const extractedElement = matches[1]; document.getElementById('result').textContent = extractedElement; } else { document.getElementById('result').textContent = 'Element not found'; } } </script> </head> <body> <label for="input-url">Enter URL:</label> <input type="text" id="input-url" style="width: 80%;"> <button onclick="extractElement()">Extract</button> <p id="result"></p> </body> </html> ____________________ Dekudeals JSON parser ---------------------------------------------------------------------------- <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Aoi's Collection | Deku Deals</title> </head> <body> <a target="_blank" href="https://www.dekudeals.com/collection/nw7vnrstb9">Link</a>&nbsp; <a target="_blank" href="http://alceawisteria.byethost7.com/PHP/0demo/2023-04-30-parse-json/parse.php#inks/more/67087617">reload</a> <a target="_blank" href="http://gg.gg/dekudealsgamezisecollectionjson">Add (Gamesizes) to json</a><br> <style> .tooltip { position: relative; display: inline-block; cursor: pointer; } .tooltip .tooltiptext { visibility: hidden; width: 200px; background-color: #000; color: #fff; text-align: center; border-radius: 5px; padding: 5px; position: absolute; z-index: 1; bottom: 125%; left: 50%; transform: translateX(-50%); opacity: 0; transition: opacity 0.3s; } .tooltip:hover .tooltiptext { visibility: visible; opacity: 1; } </style> </head> <body> <div id="collection-table"></div> <script> fetch('https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/collection.json') .then(response => response.json()) .then(data => { let totalPhysicalGames = 0; let totalPhysicalPrice = 0; let totalAllPrice = 0; let tableHtml = ` <table> <tr> <th>Link</th> <th>Format <a href="?sort=format_asc">^</a> / <a href="?sort=format_desc">v</a></th> <th>Price <a href="?sort=price_asc">^</a> / <a href="?sort=price_desc">v</a></th> <th>Status <a href="?sort=status_asc">^</a> / <a href="?sort=status_desc">v</a></th> <th>Date Added <a href="?sort=date_asc">^</a> / <a href="?sort=date_desc">v</a></th> <th>Gamesize</th> </tr> `; const urlParams = new URLSearchParams(window.location.search); const sortBy = urlParams.get('sort'); if (sortBy === 'price_asc') { data.items.sort((a, b) => a.price_paid - b.price_paid); } else if (sortBy === 'price_desc') { data.items.sort((a, b) => b.price_paid - a.price_paid); } else if (sortBy === 'format_asc') { data.items.sort((a, b) => a.format.localeCompare(b.format)); } else if (sortBy === 'format_desc') { data.items.sort((a, b) => b.format.localeCompare(a.format)); } else if (sortBy === 'date_asc') { data.items.sort((a, b) => new Date(a.added_at) - new Date(b.added_at)); } else if (sortBy === 'date_desc') { data.items.sort((a, b) => new Date(b.added_at) - new Date(a.added_at)); } else if (sortBy === 'status_asc') { data.items.sort((a, b) => (a.status || '').localeCompare(b.status || '')); } else if (sortBy === 'status_desc') { data.items.sort((a, b) => (b.status || '').localeCompare(a.status || '')); } data.items.forEach(item => { let status = item.status || ''; let formatCell = item.format; let extrainfo = ''; if (item.note && item.note.includes("DigitalAlso")) { extrainfo = ' DigitalAlso'; } let imageUrl = item.image; tableHtml += ` <tr> <td> <div class="tooltip"> <img src="${imageUrl}" width="20" height="20"> <span class="tooltiptext">${item.note}</span> <a target="_blank" href="${item.link}">${item.name}</a>${extrainfo} </div> </td> <td>${formatCell}</td> <td>${item.price_paid}</td> <td>${status}</td> <td>${new Date(item.added_at).toLocaleDateString()}</td> <td>${item.gamesize}</td> </tr> `; if (item.format === "physical") { totalPhysicalGames++; totalPhysicalPrice += item.price_paid; } totalAllPrice += item.price_paid; }); tableHtml += ` </table> <p>• Total Games: ${data.items.length} • Physical Games: ${totalPhysicalGames} <br><hr> • Physical Price: ${totalPhysicalPrice} • Total All Price: ${totalAllPrice}</p> `; const collectionTableDiv = document.getElementById('collection-table'); collectionTableDiv.innerHTML = tableHtml; }) .catch(error => console.log(error)); </script> _________________ Extract "€" values and display subtotal (JS version - broken url import) ---------------------------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Calculate Total</title> <script> async function calculateTotal(event) { event.preventDefault(); const urlInput = document.querySelector('#url'); const textAreaInput = document.querySelector('textarea[name="input"]'); const keywordInput = document.querySelector('input[name="keyword"]'); const resultElement = document.querySelector('#result'); const matchingLinesElement = document.querySelector('#matching-lines'); let input = ''; const url = urlInput.value; if (url) { const response = await fetch(url); const text = await response.text(); input = text.replace(/<[^>]+>/g, ''); // strip tags } else { input = textAreaInput.value; } let total = 0; const keyword = keywordInput.value; const lines = input.split('\n'); const matchingLines = []; let isFirstLine = true; lines.forEach((line) => { if (!keyword || line.includes(keyword)) { if (isFirstLine && url) { isFirstLine = false; return; } const matches = line.matchAll(/(\d+)[€]/g); for (const match of matches) { total += parseInt(match[1]); } matchingLines.push(line); } }); resultElement.textContent = `Total value for '${keyword || 'all'}' lines: €${total}`; if (matchingLines.length > 0 && keyword) { matchingLinesElement.innerHTML = '<h2>Matching lines:</h2><ul>' + matchingLines.map(line => `<li>${line}</li>`).join('') + '</ul>'; } else { matchingLinesElement.innerHTML = ''; } } </script> </head> <body> <form onsubmit="calculateTotal(event)"> <label for="url">URL:</label> <input type="text" name="url" id="url"> <br> <textarea name="input"></textarea> <br> Keyword: <input type="text" name="keyword"> <br> <button type="submit">Calculate total</button> </form> <div id="result"></div> <div id="matching-lines"></div> </body> </html> __________________ Redirect Code URL Transformer with dld function: -------------------------------- <textarea id="url-input" rows="15" cols="60"><meta http-equiv="refresh" content="0; https://website.com"> <script type="text/javascript"> window.location.href = "https://website.com" </script> <title>Homepage</title> </head> <body> <!-- Note: don't tell people to `click` the link, just tell them that it is a link. --> If you are not redirected automatically, follow this <a href='https://website.com'>-</a>. </body> </html></textarea><br> <input type="text" id="new-url"> <button onclick="changeUrl()">Change URL</button> <button onclick="downloadHtml()">DLD</button> <script> function changeUrl() { var newUrl = document.getElementById('new-url').value; if (newUrl !== '') { var html = document.getElementById('url-input').value; html = html.replaceAll('https://website.com', newUrl); document.getElementById('url-input').value = html; document.querySelector('meta[http-equiv="refresh"]').setAttribute('content', '0; ' + newUrl); } } function downloadHtml() { var htmlContent = document.getElementById('url-input').value; var filename = 'page.html'; var blob = new Blob([htmlContent], {type: 'text/html'}); var link = document.createElement('a'); link.download = filename; link.href = URL.createObjectURL(blob); document.body.appendChild(link); link.click(); document.body.removeChild(link); } </script> _________________ Date URL Transformer (Nintendo Direct): --------------------------------- <div id="url-container"></div> <script> const currentDate = new Date(); const day = currentDate.getDate(); const month = currentDate.getMonth() + 1; const year = currentDate.getFullYear(); const formattedDay = day < 10 ? `0${day}` : day; const formattedMonth = month < 10 ? `0${month}` : month; const url = `https://www.nintendo.com/nintendo-direct/${formattedMonth}-${formattedDay}-${year}/`; const urlContainer = document.getElementById('url-container'); urlContainer.innerHTML = `<a target="_blank" href="${url}">${url}</a>`; </script> ___________ Parse RSS + Description: -------------------------------------- <script> fetch('https://paper.wf/alcea/feed/') .then(response => response.text()) .then(data => { const parser = new DOMParser(); const xml = parser.parseFromString(data, 'text/xml'); const items = xml.querySelectorAll('item'); const titles = Array.from(items).map(item => item.querySelector('title').textContent); const descriptions = Array.from(items).map(item => item.querySelector('description').innerHTML); const links = Array.from(items).map(item => item.querySelector('link').textContent); const container = document.createElement('div'); titles.forEach((title, index) => { const item = document.createElement('div'); const itemTitle = document.createElement('h3'); const itemDescription = document.createElement('p'); const itemLink = document.createElement('a'); itemTitle.textContent = title; itemDescription.innerHTML = descriptions[index]; itemLink.textContent = 'Read more'; itemLink.href = links[index]; item.appendChild(itemTitle); item.appendChild(itemDescription); item.appendChild(itemLink); container.appendChild(item); }); document.body.appendChild(container); }) .catch(error => console.log(error)); </script> _________________________________ Parse RSS with image support: ----------------------------------------------- <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-dateFormat/1.0/jquery.dateFormat.js"></script> <style> .medium-blog-image { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; } .medium-blog-image img { margin: 5px; max-width: 100%; max-height: 200px; } </style> </head> <body> <div id="prita-medium-content"></div> <script> $(function() { var $content = $('#prita-medium-content'); var data = { rss_url: 'https://rsshub.app/pixiv/user/75406576' }; $.get('https://api.rss2json.com/v1/api.json', data, function(response) { if (response.status == 'ok') { var output = ''; $.each(response.items, function(k, item) { output += '<div class="medium-blog-post" onclick="window.open(\'' + item.guid + '\',\'mywindow\');">' output += '<div class="medium-blog-title"><h2>' + item.title + '</h2></div>'; output += '<div class="medium-blog-date">' + $.format.date(item.pubDate, "MMM dd, yyyy") + '</div>'; output += '<div class="medium-blog-image">'; $(item.content).find('img').each(function() { output += '<img src="' + $(this).attr('src') + '" />'; }); output += '</div>'; output += '</div>'; return k < 3; }); $content.html(output).hide(); $content.fadeIn('slow'); } }); }); </script> </body> ________________ Simple RSS Render: ------------------------------ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css"> </head> <body> <!-- partial:index.partial.html --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script type="text/javascript" src="https://www.jquery-plugins.net/FeedEK/FeedEk.min.js"></script> <div id="divRss" class="perrea_nena"></div> <script> $('#divRss').FeedEk({ FeedUrl : 'https://rsshub.app/pixiv/user/75406576', MaxCount : 5, ShowDesc : true, DescCharacterLimit:80, ShowPubDate:false, TitleLinkTarget:'_blank', }); </script> <!-- partial --> <script src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script> </body> ________________ YT Channel Preview Tag html generator ----------------------------------------------------------- <hr> <body> <div> <label for="channel-url-input">Channel URL:</label> <input type="text" id="channel-url-input"> </div> <div> <label for="channel-image-input">Channel Image URL:</label> <input type="text" id="channel-image-input"> </div> <div> <label for="channel-name-input">Channel Name:</label> <input type="text" id="channel-name-input"> </div> <div> <label for="video-url-input">Video URL:</label> <input type="text" id="video-url-input"> </div> <div> <label for="video-title-input">Video Title:</label> <input type="text" id="video-title-input"> </div> <button onclick="fillVideo()">Fill Video</button> <br><br> <div style="display: flex;"> <div> <label for="html-output">HTML Output:</label> <textarea id="html-output"></textarea> </div> <div id="preview-container" style="margin-left: 50px;"></div> </div> <script> function fillVideo() { let channelUrl = document.getElementById("channel-url-input").value.trim(); let channelImage = document.getElementById("channel-image-input").value.trim(); let channelName = document.getElementById("channel-name-input").value.trim(); let videoUrl = document.getElementById("video-url-input").value.trim(); let videoTitle = document.getElementById("video-title-input").value.trim(); let channelLink = document.createElement("a"); channelLink.href = channelUrl; let channelImageElement = document.createElement("img"); channelImageElement.alt = "Channel Image"; channelImageElement.src = channelImage; channelImageElement.height = 50; channelImageElement.width = 50; channelLink.appendChild(channelImageElement); let channelNameElement = document.createElement("span"); channelNameElement.textContent = channelName; let videoLink = document.createElement("a"); videoLink.href = videoUrl; let videoTitleElement = document.createElement("span"); videoTitleElement.textContent = videoTitle; let videoContainer = document.createElement("div"); videoContainer.appendChild(channelLink); videoContainer.appendChild(channelNameElement); videoContainer.appendChild(document.createElement("br")); videoContainer.appendChild(videoLink); videoLink.appendChild(videoTitleElement); let htmlString = videoContainer.outerHTML; let htmlOutput = document.getElementById("html-output"); htmlOutput.value = htmlString; let previewContainer = document.getElementById("preview-container"); previewContainer.innerHTML = htmlString; } </script> </body> <hr> __________________ Render html/text as "plaintext": (useful for codedumps) -------------------------------------- <plaintext> text </plaintext> ____________________________________ Load URL into dom onload automatically -------------------------------------------------------- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <a href="#" id="navLink" style="color:transparent">-</a> <div id="outputnav"></div> <script> $(document).ready(function() { $("#navLink").trigger("click"); }); $("#navLink").on("click", function() { $("#outputnav").load("https://ry3yr.github.io/OSTR/Unresoundtracker/navigationalt.html"); }); </script> ________________________________________________________________________________________________ Fetch sitepart between via delimiter ---------------------------------------- <details> <div id="content"></div> <script> const baseUrl = "https://alceawis.de"; // Base URL fetch("https://alceawis.de/code.html") .then(response => response.text()) .then(data => { const start = data.indexOf("CMDS") + 4; const end = data.indexOf("CodeDistribution") - 0; const content = data.substring(start, end); // Create a temporary element to parse the content as HTML const tempElement = document.createElement("div"); tempElement.innerHTML = content; // Find and convert relative URLs to absolute URLs const relativeUrls = tempElement.querySelectorAll("a[href], img[src]"); relativeUrls.forEach(url => { const originalUrl = url.getAttribute("href") || url.getAttribute("src"); const absoluteUrl = new URL(originalUrl, baseUrl).href; url.setAttribute("href", absoluteUrl); url.setAttribute("src", absoluteUrl); }); // Append the modified content to the target element document.getElementById("content").appendChild(tempElement); }); </script> </details><br> ====relative version: === <body> <div id="content"></div> <script> fetch("https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Cool_Stuff.html") .then(response => response.text()) .then(data => { const start = data.indexOf("Tools") + 5; const end = data.indexOf("Hobbies") - 7; const content = data.substring(start, end); document.getElementById("content").innerHTML = content; }) .catch(error => console.log(error)); </script> </body> </html> (Source: to php equiv: "now do it in js and display into dom") ________________________________________________________________________________________________ Render PDF on button press: ----------------------------------------------- <div id="holder"></div> <button id="renderButton">YGODECK</button> <script src='https://cdn.jsdelivr.net/npm/pdfjs-dist@2.0.385/build/pdf.min.js'></script> <script> function renderPDF(url, canvasContainer, options) { options = options || { scale: 2 }; function renderPage(page) { var viewport = page.getViewport(options.scale); var wrapper = document.createElement("div"); wrapper.className = "canvas-wrapper"; var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); var renderContext = { canvasContext: ctx, viewport: viewport }; canvas.height = viewport.height; canvas.width = viewport.width; wrapper.appendChild(canvas) canvasContainer.appendChild(wrapper); page.render(renderContext); } function renderPages(pdfDoc) { for(var num = 1; num <= pdfDoc.numPages; num++) pdfDoc.getPage(num).then(renderPage); } PDFJS.disableWorker = true; // Add event listener to the render button document.getElementById('renderButton').addEventListener('click', function() { PDFJS.getDocument(url).then(renderPages); // Disable the button after rendering this.disabled = true; }); } renderPDF('https://ry3yr.github.io/0curr-irl.pdf', document.getElementById('holder')); </script> ________________________________ Render pdf directly ----------------------------- <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css"> <link rel="stylesheet" href="./style.css"> </head> <body> <!-- partial:index.partial.html --> <div id="holder"></div> <!-- partial --> <script src='https://cdn.jsdelivr.net/npm/pdfjs-dist@2.0.385/build/pdf.min.js'></script> <script> function renderPDF(url, canvasContainer, options) { options = options || { scale: 1 }; function renderPage(page) { var viewport = page.getViewport(options.scale); var wrapper = document.createElement("div"); wrapper.className = "canvas-wrapper"; var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); var renderContext = { canvasContext: ctx, viewport: viewport }; canvas.height = viewport.height; canvas.width = viewport.width; wrapper.appendChild(canvas) canvasContainer.appendChild(wrapper); page.render(renderContext); } function renderPages(pdfDoc) { for(var num = 1; num <= pdfDoc.numPages; num++) pdfDoc.getPage(num).then(renderPage); } PDFJS.disableWorker = true; PDFJS.getDocument(url).then(renderPages); } renderPDF('https://ry3yr.github.io/OSTR/release/other/game/ygo/deck/0curr-irl.pdf', document.getElementById('holder')); </script> </body> </html> ________________________________ Render pdf in webbrowser ----------------------------------------- <!DOCTYPE html> <html> <head> <title>PDF Viewer</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.2/pdf.js"></script> </head> <body> <label for="pdf-upload">Upload PDF:</label> <input type="file" id="pdf-upload" accept="application/pdf"/> <label for="pdf-url">PDF URL:</label> <input type="text" id="pdf-url" placeholder="Enter PDF URL"/> <button id="load-pdf">Load PDF</button> <button id="zoom-in" style="background:transparent; border:transparent;display: none;">Zoom In</button> <div id="zoom-percent" style="background:transparent; border:transparent;display: none;">60</div> <button id="zoom-out" style="background:transparent; border:transparent;display: none;">Zoom Out</button> <button id="zoom-reset" style="background:transparent; border:transparent;display: none;">Reset Zoom</button> <div id="pages"></div> <script> pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.2/pdf.worker.js'; document.querySelector("#pdf-upload").addEventListener("change", function(e){ document.querySelector("#pages").innerHTML = ""; zoomReset(); var file = e.target.files[0] if(file.type != "application/pdf"){ alert(file.name + " is not a pdf file.") return } var fileReader = new FileReader(); fileReader.onload = function() { var typedarray = new Uint8Array(this.result); pdfjsLib.getDocument(typedarray).promise.then(function(pdf) { // you can now use *pdf* here console.log("the pdf has", pdf.numPages, "page(s)."); for (var i = 0; i < pdf.numPages; i++) { (function(pageNum){ pdf.getPage(i+1).then(function(page) { // you can now use *page* here var viewport = page.getViewport(2.0); var pageNumDiv = document.createElement("div"); pageNumDiv.className = "pageNumber"; pageNumDiv.innerHTML = "Page " + pageNum; var canvas = document.createElement("canvas"); canvas.className = "page"; canvas.title = "Page " + pageNum; document.querySelector("#pages").appendChild(pageNumDiv); document.querySelector("#pages").appendChild(canvas); canvas.height = viewport.height; canvas.width = viewport.width; page.render({ canvasContext: canvas.getContext('2d'), viewport: viewport }).promise.then(function(){ console.log('Page rendered'); }); page.getTextContent().then(function(text){ console.log(text); }); }); })(i+1); } }); }; fileReader.readAsArrayBuffer(file); }); document.querySelector("#load-pdf").addEventListener("click", function(){ document.querySelector("#pages").innerHTML = ""; zoomReset(); var url = document.querySelector("#pdf-url").value.trim(); if (!url) { alert("Please enter a PDF URL."); return; } if (!url.endsWith(".pdf")) { alert("Please enter a valid PDF URL."); return; } fetch(url).then(function(response) { if (!response.ok) { throw new Error(response.statusText); } return response.arrayBuffer(); }).then(function(buffer) { pdfjsLib.getDocument(buffer).promise.then(function(pdf) { // you can now use *pdf* here console.log("the pdf has", pdf.numPages, "page(s)."); for (var i = 0; i < pdf.numPages; i++) { (function(pageNum){ pdf.getPage(i+1).then(function(page) { // you can now use *page* here var viewport = page.getViewport(2.0); var pageNumDiv = document.createElement("div"); pageNumDiv.className = "pageNumber"; pageNumDiv.innerHTML = "Page " + pageNum; var canvas = document.createElement("canvas"); canvas.className = "page"; canvas.title = "Page " + pageNum; document.querySelector("#pages").appendChild(pageNumDiv); document.querySelector("#pages").appendChild(canvas); canvas.height = viewport.height; canvas.width = viewport.width; page.render({ canvasContext: canvas.getContext('2d'), viewport: viewport }).promise.then(function(){ console.log('Page rendered'); }); page.getTextContent().then(function(text){ console.log(text); }); }); })(i+1); } }); }).catch(function(error) { alert("Failed to load PDF: " + error.message); }); }); var curWidth = 60; function zoomIn(){ if (curWidth < 150) { curWidth += 10; document.querySelector("#zoom-percent").innerHTML = curWidth; document.querySelectorAll(".page").forEach(function(page){ page.style.width = curWidth + "%"; }); } } function zoomOut(){ if (curWidth > 20) { curWidth -= 10; document.querySelector("#zoom-percent").innerHTML = curWidth; document.querySelectorAll(".page").forEach(function(page){ page.style.width = curWidth + "%"; }); } } function zoomReset(){ curWidth = 60; document.querySelector("#zoom-percent").innerHTML = curWidth; document.querySelectorAll(".page").forEach(function(page){ page.style.width = curWidth + "%"; }); } document.querySelector("#zoom-in").onclick = zoomIn; document.querySelector("#zoom-out").onclick = zoomOut; document.querySelector("#zoom-reset").onclick = zoomReset; window.onkeypress = function(e){ if (e.code == "Equal") { zoomIn(); } if (e.code == "Minus") { zoomOut(); } }; </script> </body> </html> https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/pdffilerender.html https://codepen.io/ryedai1/full/KKGYemE ________________________________________________________________________________________________ Fetch all links from site & display --------------------------------------------------- <script> fetch('https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Art.html') .then(response => response.text()) .then(data => { const parser = new DOMParser(); const htmlDoc = parser.parseFromString(data, 'text/html'); const base = htmlDoc.createElement('base'); base.href = 'https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Art.html'; htmlDoc.head.appendChild(base); const links = htmlDoc.querySelectorAll('a[href]'); const linkList = document.createElement('ul'); links.forEach(link => { const listItem = document.createElement('li'); const anchor = document.createElement('a'); anchor.href = link.href; anchor.target = '_blank'; // Set target to _blank anchor.textContent = link.href; listItem.appendChild(anchor); linkList.appendChild(listItem); }); document.body.appendChild(linkList); }) .catch(error => console.error(error)); </script> ______________________ Fetch and Display images from website: -------------------------------------- <script> const url = 'https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/index.html'; fetch(url) .then(response => response.text()) .then(data => { const parser = new DOMParser(); const html = parser.parseFromString(data, "text/html"); const images = html.querySelectorAll("img"); images.forEach(image => { const imageUrl = image.getAttribute("src"); const img = document.createElement("img"); img.src = imageUrl; document.body.appendChild(img); }); }); </script> (Source: chatgpt.org.ua "write javascript image parser for https://app.box.com/s/j7i88s6jb4yyk4wmiti4tol8ejoikdhl" ) _____________________________________ Click link on page load (if# exists in url): --------------------------------- <a id=linky href=""></a> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script> $(document).ready(function() { if (window.location.href.indexOf("alceawisteria") > -1) { //alert("your url contains the name franky"); document.getElementById('linky').click(); window.history.back(); window.close(); } }); </script> (put script part at very end of page to make it work) (Source: https://stackoverflow.com/questions/4597050/how-to-check-if-the-url-contains-a-given-string ) __________________________ Change link color: -------------------------- <a target="_blank" href="http://alceawisteria.byethost7.com/PHP/0demo/2023-03-25-BoxNet/rollingyourpage.html" style="color:lightgrey">rollingyourpage</a> (Source: https://www.rapidtables.com/web/html/link/html-link-color.html _________________________________ Location values: -------------------------- Let's suppose you have this url: http://localhost:4200/landing?query=1# location values, as follow: window.location.hash: "#2" window.location.host: "localhost:4200" window.location.hostname: "localhost" window.location.href: "http://localhost:4200/landing?query=1#2" window.location.origin: "http://localhost:4200" window.location.pathname: "/landing" window.location.port: "4200" window.location.protocol: "http:" (source: https://stackoverflow.com/questions/11401897/get-the-current-domain-name-with-javascript-not-the-path-etc ) ___________________________________ Disable links in iframe: --------------------------------- iframe { pointer-events: none; } (Source: https://stackoverflow.com/questions/69644930/can-links-inside-iframe-that-link-to-another-domain-be-ignored-disabled ) ___________________________________ Download current webpage: ----------------------------------------- <a onclick="this.href='data:text/html;charset=UTF-8,'+encodeURIComponent(document.documentElement.outerHTML)" href="#" download="page.html">Download</a> (See also: https://codepen.io/theConstructor/pen/vKNRdE ) (Source: https://stackoverflow.com/questions/26219180/download-current-html-file ) ____________________________________ Run console command via button: --------------------------------------------------- <button onclick="exec(`document.querySelector('button')`)">Test</button> <script> Function exec(code){ console.log(Function(`return (${ code })`)())} </script> (Source: https://stackoverflow.com/questions/70295800/how-to-run-a-console-lwith-a-button-in-html ) _____________________________ Resize all images to XY% ---------------------------------------- <style> img{ width: 80%; height: 80%; margin-top: .4rem; //border-radius: 50rem; } </style> ________________ Set Basis for relative links: ------------------------------------------- <base href="https://m.box.net"> (Source: https://stackoverflow.com/questions/3329499/convert-a-relative-url-to-an-absolute-url-with-simple-html-dom ) __________________ Make all #links in current dom open in new #tab --------------------- <base target="_blank"> ________________ Nocache meta tag: ---------------------------- (prevent site from being cached /caching ) <meta http-equiv="Cache-control" content="No-Cache"> https://stackoverflow.com/questions/4480304/how-to-set-http-headers-for-cache-control#4480318 _________________ Scroll < h> text (horizontally) ------------------------------ (Replace translateX with "translateY" to scroll vertically) <style>h2 {animation: example linear 5s infinite;}@keyframes example {from { transform: translateX(-40%); } to { transform: translateX(100%);}}</style> <h2>This is an announcement</h2> (source: https://stackoverflow.com/questions/75291198/animate-text-to-make-it-scroll ) ____________________ Embed code into website w.o. js or iframe: ----------------------- <object type="text/html" data="https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/index.html" style="width:100%; height:100%"></object> (Source: https://stackoverflow.com/questions/46690121/embed-html-content-from-another-http-request-without-an-iframe ) _________________________ Test RSS: ---------------- <div class="updatebox"> <script src="//rss.bloople.net/?url= https://www.youtube.com/feeds/videos.xml?channel_id=UCmIpOnd5BVx5Si2hp0WNKZw &showtitle=false&type=js"></script></div> ________________ Render base64 encoded html directly as iframe inpage: ------------------------------------------------------------------------------- <iframe src="data:text/html;base64, Base64Code " style="border:0px #ffffff none;" name="statusit" scrolling="auto" frameborder="0" marginheight="0px" marginwidth="0px" height="350px" width="500px" allowfullscreen></iframe> _________ Image as button: ------------------------- <button type="button"> <img src="https://i.ibb.co/CW5Wvry/buttonpng.png" alt="buttonpng" border="0" height=45px id="btn" /> </button> ___________ Subscribe to RSS Button --------------------------------------- &nbsp;<a target="_blank" href="https://backend.deviantart.com/rss.xml?q=gallery:aoikurayami1/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/Generic_Feed-icon.svg/1920px-Generic_Feed-icon.svg.png" height="15" width="15"></a> ______________________________ Cool animated font: ------------------- <style> @import url(https://fonts.googleapis.com/css?family=Montserrat);body,html{height:100%;font-weight:800;margin:0;padding:0}body{background:#03032100;font-family:Arial}.container{display:flex;height:100%;align-items:center}svg{display:block;font:10.5em Montserrat;width:960px;height:300px;margin:0 auto}.text-copy{fill:none;stroke:white;stroke-dasharray:6% 29%;stroke-width:5px;stroke-dashoffset:0%;animation:5.5s linear infinite stroke-offset}.text-copy:first-child{stroke:#4D163D;animation-delay:-1}.text-copy:nth-child(2){stroke:#840037;animation-delay:-2s}.text-copy:nth-child(3){stroke:#BD0034;animation-delay:-3s}.text-copy:nth-child(4){stroke:#BD0034;animation-delay:-4s}.text-copy:nth-child(5){stroke:#FDB731;animation-delay:-5s}@keyframes stroke-offset{100%{stroke-dashoffset:-35%}} </style><div class="container"> <svg viewBox="0 0 1100 220"> <symbol id="s-text"> <text text-anchor="middle" x="50%" y="80%">Diarykeeper</text> </symbol><g class = "g-ants"> <use xlink:href="#s-text" class="text-copy"></use> <use xlink:href="#s-text" class="text-copy"></use> <use xlink:href="#s-text" class="text-copy"></use> <use xlink:href="#s-text" class="text-copy"></use> <use xlink:href="#s-text" class="text-copy"></use> </g></svg></div> (Source: ) _____________________________ Roll Text over site: -------------------- <marquee> Want to join this <strong>HeLLa CoOL</strong> webring??</marquee> _____________________________ Add Url post one: ------------------------- <a target="_blank" href="?https://www.url"> _____________________________ Action on mouseover (f.e. "change image) ----------------------- <a href="mailme.html" target="rainbowmain" onmouseover="image1.src='images/df17_merged_invisbg_12over.png';" onmouseout="image1.src='images/df17_merged_invisbg_12.png';"><img src="images/df17_merged_invisbg_12.png" name="image1" width="34" height="35" alt=""></a></td> (Source: view-source:https://www.jellyfishforest.com/duckyfeet/ ) _________________________ Call JS/ CSS: ------------------- <script class="u-script" type="text/javascript" src="jquery.min.js" defer="defer"></script> <link rel="stylesheet" href="/html/styles.css"> (Source: https://www.w3schools.com/html/html_css.asp ) ______________ Reload page within href: ------------------------ <a href="javascript:window.location.reload(true)">Reload</a> (Source: https://stackoverflow.com/questions/8174282/link-to-reload-current-page ) ______________________________ HTML redirect (auto) ------------------------------ <!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8"> <meta http-equiv="refresh" content="0; url=https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/index.html"> <script type="text/javascript"> window.location.href = "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/index.html" </script> <title>Homepage</title> </head> <body> <!-- Note: don't tell people to `click` the link, just tell them that it is a link. --> If you are not redirected automatically, follow this <a href='https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/index.html'>-</a>. </body> </html> (Source: ) _________________________________ Custom font in your website: ---------------------------- (Source: https://www.fontsquirrel.com/tools/webfont-generator https://www.pagecloud.com/blog/how-to-add-custom-fonts-to-any-website) _________________________________ Bookmark link ("goto" part of page) ---------------- <a href="#z">Scripting Languages</a> … <a name="z">Scripting Languages</a> (Source: https://www.tutorialspoint.com/How-to-create-a-bookmark-link-in-HTML ) __________________ Display base64 image: ----------------------- <img src=" data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPoAAAD6AQMAAACyIsh+AAAABlBMVEX///8AAABVwtN+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACC0lEQVRogeWZMa6EMAxEHVFQcgSOwtHgaDkKR6CkQPh7xs4u2l9svU6KFQlPK8UajxMj8m2sinGKTPtcn1M+6JYFwF6HczymXWTRTbl5nxrfEaB6DSdmc10U74dLGDcLUzZgVMWKjXIzEp0CMh1cX/UuV0rAZa/HrK6HQnn8y4vsgPse8t3yYlvlBmDTD5/8caANW9i578IXh3yO5AACdQ1Yoh42BYA0mfelGp8EEFGzNVN9FHfTg7rbo8ZRD10AsMGWF8rIlHbm8WkSQLHvkdVdFry/h2s8af6Y9gPobWlxhu2Z3ZeWJlolEYBDC+KAfDd5hOrhefZy6wWQqOYQANP95pl2giuK22AOAEfU0asYdO7yEJ7k/Q+6AFZE5nFFfeSFByoJ4LLHK5e9PVyocZg+8iI7IAyUsrgzTq2423NtefH7wIpqfiEOOzSuLO5K04ui1geAai4PG2SHAoGz9A8bzADYzl0AvnE/2qHW2yzyog8g8gJ9mPpqu3nc6tsnEwAlDi0QQLTd2I7wC0w3gN9ZWkOiVT2MGo2aBEAMNF5oc9FsPPwM0w+werpHPwoKKF7c95cNpgAQBbYT1V3O+02Ig+u+G6AJwBfQlWGP/Z04qYDJV+yXpsfiLs9E6AWIbyhmg7inT3u05bIAEgKgyWlruz0upH0AjM7Jm/mi/HToH0z10Y7uAPg2/gCJcpY5m9E2HAAAAABJRU5ErkJggg==" alt="Red dot" /> </div> </div> (Source: https://www.base64-image.de/ ) _____________________________ Generate QR Code from URL: ------------------------------------------- <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <script type="text/javascript"> function generateBarCode() { var nric = $('#text').val(); var url = 'https://api.qrserver.com/v1/create-qr-code/?data=' + nric + '&amp;size=50x50'; $('#barcode').attr('src', url); } </script> </head> <body> <input id="text" type="text" value="UnderratedContent" style="Width:20%" onblur='generateBarCode();' /> <img id='barcode' src="https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/UnderratedContent.html&amp;size=100x100" alt="" title="UnderratedContent" width="50" height="50" /> </body> </html> (Source: https://stackoverflow.com/questions/30115242/generating-a-simple-qr-code-with-just-html ) https://blog.qr4.nl/Online-QR-Code-Decoder.aspx _____________________ Midway viewport (android tablet): ------------------------------ <meta id="myViewport" name="viewport" content="width = 600"> (Source: https://stackoverflow.com/questions/24523996/how-to-set-viewport-dynamically) ___________ Force Desktop page: ------------------- <head> <meta id="viewport" name="viewport" content="width=1024, height=768, initial-scale=0, minimum-scale=0.25" /> (Source: https://stackoverflow.com/questions/3588628/can-i-change-the-viewport-meta-tag-in-mobile-safari-on-the-fly) ___________________ Disable sites mobile view: ------------------------------------- Remove <meta name="viewport" content="width=device-width, initial-scale=1"> from your html file. (Source: https://stackoverflow.com/questions/46905233/how-to-disable-mobile-version-of-website _________________________________ Load link into existing iframe: ------------------------------- <div> <a href="https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Project.html" target="iframeblog">Art Gatekeeping Rant</a> <a href="https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Comments.html" target="iframeblog">+2</a> <a href="#">Link 3</a> <a href="#">Link 4</a> </div> <br> </div> </body> <br><iframe src="demo_iframe.htm" name="iframeblog" height="100%" width="100%"scrolling="no" frameborder="0" allowtransparency="true"></iframe> (Source: https://www.w3schools.com/tags/att_iframe_name.asp https://stackoverflow.com/questions/740816/open-link-in-iframe ) _______________________________________________ Mini dropdown menu (2023\12\31) ------------------------------------------------- <style>.dropdown-content{display:none;position:absolute;background-color:#f9f9f9;min-width:160px;box-shadow:0 8px 16px 0 rgba(0,0,0,.2);z-index:1}.dropbtn:hover+.dropdown-content,.dropdown-content:hover{display:block}</style> <div class="dropdown"> <button class="dropbtn">List</button> <div class="dropdown-content"> <a href="?instance=mstdn.animexx.de&userid=111676830721936824" style="color: blue;">@Alcea@Animexx</a> <a href="?instance=mstdn.animexx.de&userid=111676830721936824" style="color: blue;">url2</a> </div> </div> _______________________________________________ Compact dropdown button (with images) ------------------------------------------------------------' <style> .dropdown {position: absolute;display: inline-block;} .dropdown-content { display: none;position: absolute;background-color: #ffffff;min-width: 0px;box-shadow:5px 5px 0px 0px rgba(0,0,0,0.2);z-index: 1;} .dropdown-content a { color: black;padding: 2px 2px;text-decoration: none;display: block;} .dropdown-content a:hover {background-color: #fff;} .dropdown:hover .dropdown-content {display: block;} .dropdown:hover .dropbtn {background-color: transparent;} </style> <div class="dropdown" hidden>  <button class="dropbtn"><img src="https://cdn-icons-png.flaticon.com/512/2327/2327670.png" height="45" width="45"></button> <div class="dropdown-content"> <a target="_blank" href="https://pb.todon.de/@alcea.rss"><img src="https://upload.wikimedia.org/wikipedia/commons/4/48/Mastodon_Logotype_%28Simple%29.svg" height="45" width="45"></a> <a target="_blank" href="https://nitter.absturztau.be/ryedai1/rss"><img src="https://upload.wikimedia.org/wikipedia/commons/4/4f/Twitter-logo.svg" height="45" width="45"></a> <a target="_blank" href="mailto:hax0rxy@googlemail.com"><img src="https://upload.wikimedia.org/wikipedia/commons/4/4e/Mail_%28iOS%29.svg" height="45" width="45"></a> </div> </div> __________________________ Hoverable Dropbdown menu: -------------------------- <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> .dropbtn { background-color: #04AA6D;color: white;padding: 16px;font-size: 16px;border: none; } .dropdown { position: relative;display: inline-block; } .dropdown-content { display: none;position: absolute;background-color: #f1f1f1;min-width: 160px;box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);z-index: 1; } .dropdown-content a { color: black;padding: 12px 16px;text-decoration: none;display: block; } .dropdown-content a:hover {background-color: #ddd;} .dropdown:hover .dropdown-content {display: block;} .dropdown:hover .dropbtn {background-color: #3e8e41;} </style> </head> <body> <div class="dropdown"> <button class="dropbtn">Dropdown</button> <div class="dropdown-content"> <a href="#">Link 1</a> <a href="#">Link 2</a> <a href="#">Link 3</a> <a href="#">Link 4</a> </div> </div> </body> (Source: https://www.w3schools.com/howto/howto_css_dropdown.asp) _______________________ Twitter Box: ----------------- https://www.codingnepalweb.com/tweet-box-character-limit-highlighting-javascript/ _______________________________ Embed img from url: ------------------------------ <img src="/wp-content/uploads/flamingo.jpg"> (Source: https://html.com/attributes/img-src/ ) ______________________________ Remove frame border: ----------------------------- ...src="https://www.site.com" frameBorder="0"> (Source: stackoverflow.com/questions/65034/remove-border-from-iframe) __________________________________ Display txt content in iframe: ------------------------------------------ <iframe src="yourtext.txt"></iframe> (Source: https://www.generacodice.com/en/articolo/789689/Is+there+any+way+similar+to+%26lt%3Bimg%26gt%3B+to+display+contents+of+a+.txt+file ) _________________________________ Render html from text file ---------------------------- <embed src = "file.txt"> (Source: https://www.codegrepper.com/code-examples/html/how+to+render+html+from+text+file) ______________________________ Zoom iframe content: --------------------- <style type="text/css"> #iframe { zoom: 0.15; -moz-transform:scale(0.75); -moz-transform-origin: 0 0; -o-transform: scale(0.75); -o-transform-origin: 0 0; -webkit-transform: scale(0.75); -webkit-transform-origin: 0 0; } </style> (Source: https://stackoverflow.com/questions/7203064/how-to-zoom-iframe-content-only-without-using-a-frame-set) _____________________ Scroll iframe to specific positions: ----------------------------------- <style> #mydiv { width : 100%; height : 100%; overflow : hidden; position : relative; } #myframe { position : absolute; top : -500px; left : 0px; width : 1280px; height : 1200px; } </style> <body style="margin: 0 auto;"> <div id="mydiv"> <div id="myframe"> <iframe src="http://www.example.com" id="myframe" scrolling="no"></iframe> </div> For XFrame Bypass: ------------------ <body style="margin: 0 auto;"> <div id="mydiv"> <div id="myframe"> <script type="module" src="https://unpkg.com/x-frame-bypass"></script> <iframe is="x-frame-bypass" src="https://github.com/Ry3yr/OSTR/tree/main/Diarykeepers_Homepage" height="100%" width="100%"scrolling="no" frameborder="0" allowtransparency="true"></iframe> <p style=""> </p> (Source: https://stackoverflow.com/questions/14117763/how-do-you-put-only-a-certain-section-of-a-website-into-an-iframe/14117980) ____________________ Scale iframe content: --------------------- <style> #wrap { width: 600px; height: 390px; padding: 0; overflow: hidden; } #frame { width: 800px; height: 520px; border: 1px solid black; } #frame { zoom: 0.75; -moz-transform: scale(0.75); -moz-transform-origin: 0 0; } </style> <div id="wrap"> <iframe id="frame" src="test2.html"></iframe> </div> (Source: https://stackoverflow.com/questions/166160/how-can-i-scale-the-content-of-an-iframe) ____________________ HTML reload page: -------------------------- Reload once upon open: window.onload = function() { if(!window.location.hash) { window.location = window.location + '#loaded'; window.location.reload(); } } (Source: https://stackoverflow.com/questions/6985507/one-time-page-refresh-after-first-page-load) Keep refreshing every 'x' <meta http-equiv="refresh" content="1"> ( Source: https://stackoverflow.com/questions/5294842/refresh-a-page-using-javascript-or-html ) ____________________ Uhrzeit & Kalender --------------------------- https://www.schulferien.org/Uhrzeit/ __________________________ Transparent text: ------------------------- Transparent color code: When you have a 6 digit color code e.g. #ffffff, replace it with #ffffff00. Just add 2 zeros at the end to make the color transparent. (Source: https://stackoverflow.com/questions/18189201/is-there-a-color-code-for-transparent-in-html/18189313 ) Option b) color:rgba(255,0,0,0.0); (Source: https://stackoverflow.com/questions/20048225/invisible-transparent-font-color-with-css-on-all-major-browsers ) _________________________ HTML Comment: ------------------- <!-- and the comment closes with --> https://blog.christosoft.de/2015/04/html5-bogus-comment/ _________________________________ Copy current URL to clipboard: ----------------------------- <button onclick="myFunction()">CopyURL</button> <script> function myFunction() { var copyText = navigator.clipboard.writeText(window.location.href); } </script> (Source: https://stackoverflow.com/questions/49618618/copy-current-url-to-clipboard ) ________________________ Copy text to clipboard: ------------------------------- <input type="text" value="text" id="myInput"> <button onclick="myFunction()">Copy text</button> <script> function myFunction() { var copyText = document.getElementById("myInput"); copyText.select(); copyText.setSelectionRange(0, 99999); navigator.clipboard.writeText(copyText.value); } </script> (Source: https://www.w3schools.com/howto/howto_js_copy_clipboard.asp ) _______________________ Display current url: --------------------------- <script> function getURL() { alert("The URL of this page is: " + window.location.href); } </script> <button type="button" onclick="getURL();">ClickMe</button> (Source: https://www.tutorialrepublic.com/faq/how-to-get-the-current-url-with-javascript.php) ____________________________ Encode Mail to Anti Spam: ------------------ https://www.viathinksoft.de/tools/antispam/index.php ________________________ Visitor Counter: ------------------- <a href="https://www.daniel-marschall.de/counter/"><img src="https://www.daniel-marschall.de/counter/counter.php?id=1347&amp;format=graphic&amp;theme=digital" alt="Visitors" title="Visitors"></a> Premade: smallseotools.com/de/visitor-hit-counter https://github.com/brentvollebregt/hit-counter https://www.powr.io/plugins/hit-counter/standalone https://www.daniel-marschall.de/counter/ Make your own: https://www.ionos.de/digitalguide/websites/webseiten-erstellen/besucherzaehler-selbst-erstellen-so-funktionierts/ _____________________ Make part scrollable: ------------------- <head> <style> h1 { color:Green; } div.scroll { margin:4px, 4px; padding:4px; background-color: white; width: 500px; height: 110px; overflow-x: hidden; overflow-y: auto; text-align:justify; } </style> </head> <body> <div class="scroll"> </div> </center> </body> </html> (Source: https://www.geeksforgeeks.org/making-a-div-vertically-scrollable-using-css/ ) _____________ Add tab-icon to weppage (favicon): ------------------- <link rel="icon" type="image/png" href="favicon.png"><!-- Major Browsers --> <!--[if IE]><link rel="SHORTCUT ICON" href="favicon.ico"/><![endif]--><!-- Internet Explorer--> Add 32x32 favicon.png to websites main dir https://www.hostinger.com/tutorials/how-to-add-favicon-to-website https://realfavicongenerator.net/ OR Add to html directly via base64 Encoded Image directly inside the tag like this: <link href="data:image/x-icon;base64,IMAGE-BASE64" rel="icon" type="image/x-icon" /> https://stackoverflow.com/questions/16375592/favicon-not-showing-up-in-google-chrome/16375622#16375622 ___________________________ Tile image over background: -------------------------- <style> body { background-image: url("/examples/images/tile.png"); } </style> (Source: https://www.tutorialrepublic.com/css-tutorial/css-background.php) ============ One line version ============= <body background="http://www.geocities.ws/jaup/bgrnd40.gif"> ________________________ Html Audioplayer ---------------- https://github.com/NelsWebDev/BetterAudioPlaylist (Source: https://m.youtube.com/watch?v=PZuvCpx5lKY) (Q: https://stackoverflow.com/questions/69810359/interface-with-audioplayer-within-spoiler-tag) ___________________ Keep Element on bottom-right page corner: ----------------- <html> <head> <meta charset=utf-8 /> <title>Test</title> <style> #foo { position: fixed; bottom: 0; right: 0; } </style> </head> <body> <div id="foo">Hello World</div> </body> </html> (Source: https://stackoverflow.com/questions/3616572/how-to-position-a-div-in-bottom-right-corner-of-a-browser) (Bottom right audioplayer: stackoverflow.com/questions/38156970/how-do-i-make-a-simple-audio-element-stay-at-the-bottom-of-the-browser-window) ____________________________________________________ Open link in program + fallback ------------------------------- <html> <script> function openApp () { var start, end, elapsed; var params = window.location.href.split("lp=")[1]; var url = params.split("&customURI=")[0]; var customURI = params.split("&customURI=")[1]; var time = (new Date()).getTime(); document.location = customURI+url; setTimeout(function(){ var now = (new Date()).getTime(); if((now - time)<2550) { javascript: console.log(new Date().getTime()); document.location = url; } else{ window.open('', '_self', ''); window.close(); } }, 2000); } </script> <body onload="openApp()"> <a onclick="openApp()" id="newButton">Click Here to open the App</a> </body> (Source: https://stackoverflow.com/questions/11710902/can-i-make-a-link-that-will-open-in-my-app-if-its-installed-and-fall-back-to-a) ____________________ Comment / Guestbook script: ---------------------------- https://www.htmlcommentbox.com/ https://www.123guestbook.com/ )Alts https://www.quackit.com/html/codes/add_comments_to_website.cfm •You can find more solutions by searching for 'comments' at https://www.thenewdynamic.org. https://stackoverflow.com/questions/59096243/adding-comments-in-blog-posts-on-github-pages ) https://www.freecodecamp.org/news/how-you-can-build-your-own-free-serverless-comment-box-dc9d4f366d12/ ___________________ Add Disquis comment section to your page: ------------------------ https://www.create.net/support/how-to-add-a-disqus-comment-section-to-your-website (must add own domain to https://diarykeeper.disqus.com/admin/settings/advanced/ !!!) [ Enable Guestpost: https://help.disqus.com/en/articles/1717211-guest-commenting https://i.ibb.co/xs9kMNs/Screenshot-20211108-131308.jpg ] __________________________ Resizeable Button: ------------------ <a href="http://bitdegree.org" target="_blank"> <button class="button">Submit</button> <style> .button {height: 50px; width: 100px;}</style> (Source: https://www.quora.com/How-do-you-edit-a-button-size-in-HTML) ____________________________ Button: ------- <a href="http://bitdegree.org" target="_blank"> <button>Click me!</button> (Source: https://www.bitdegree.org/learn/html-button) ___________ Own Forum: --------------- https://www.forumotion.com/ https://www.makeuseof.com/tag/5-great-sites-create-forum/ ________ Link to Whatsapp / WA via browser ------------------------------------------- https://api.whatsapp.com/send/?phone=%2B491625385381&text&app_absent=0 https://wa.me/+491625385381 ____________________________ Change font color: ------------------- <span style="color: rgb(51, 51, 255);">me</style> __________________________ Change font size: ------------------ <font size="-1">text</font> _________________________________ Change img size via html: ------------------------- <img src='image.jpg' alt="Image" height="100" width="100"> https://www.codegrepper.com/code-examples/html/resize+image+in+html _________________________________ Link to website-image on another page: --------------------------------- <p> <a href="https://www.computerhope.com/"> <img src="https://www.computerhope.com/cdn/media/logo-200-gray.png"> </a> </p> https://www.computerhope.com/issues/ch000059.htm _______________________________________________________________ Use Github Pages to host website: --------------------------------- http://docsbeta.pinegrow.com/host-html-website-github-pages-free/ (Result: => https://ry3yr.github.io/OSTR/All_Project_(spoiler_button).html) (Push git change via windows: https://gitforwindows.org/) _______________________________________________________________ Bypass iFrame Limitation: -------------------------------------- <script type="module" src="https://unpkg.com/x-frame-bypass"></script> <iframe is="x-frame-bypass" src="https://vgmdb.net"></iframe> https://github.com/niutech/x-frame-bypass _______________________________________________________________ Fullscreen iframe: ------------------ <body style="margin: 0 auto;"> <iframe src="https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Startseite.html" frameborder="0" style="overflow:hidden; display:block; position: absolute; height: 100%; width: 100%"> <p style=""> Your browser does not support iframes. </p> </body> ______________________ Render webpage into frame: -------------------------- <iframe src="https://www.abcsubmit.com/view/id_1f8339emu_jn1?utm=abcsubmit" width="400" height="400"></iframe> _______________________________________________________________ RSS Feed Widget --------------- <div id="widgetmain" style="text-align:left;overflow-y:auto;overflow-x:hidden;width:600px;background-color:#transparent; border:1px solid #333333;"><div id="rsswidget" style="height:500px;"><iframe src="http://us1.rssfeedwidget.com/getrss.php?time=1626283386459&amp;x=http%3A%2F%2Ffeeds.feedburner.com%2Fvgmdb_album_new%3Fformat%3Dxml&amp;w=600&amp;h=500&amp;bc=333333&amp;bw=1&amp;bgc=transparent&amp;m=20&amp;it=true&amp;t=vgmdb&amp;tc=333333&amp;ts=15&amp;tb=transparent&amp;il=true&amp;lc=0000FF&amp;ls=14&amp;lb=false&amp;id=true&amp;dc=333333&amp;ds=14&amp;idt=true&amp;dtc=284F2D&amp;dts=12" border="0" hspace="0" vspace="0" marginwidth="0" marginheight="0" style="border:0; padding:0; margin:0; width:600px; height:500px;" id="rssOutput" frameborder="no">Reading RSS Feed ...</iframe></div><div style="text-align:right;margin-bottom:0;border-top:1px solid #333333;" id="widgetbottom"><span style="font-size:70%"></span><br></div></div> www.rssfeedwidget.com (Source: https://stackoverflow.com/questions/15821278/how-to-make-a-html-page-to-show-content-from-another-url) # You have to check for HTTP response header X-Frame-Option of those sites. if its value is "DENY or SAMEORIGIN", then you can not load those website in the iframes. DENY = No one can load the website in iframe. Even the same domain page wont be able to load. SAMEORIGIN = only a page which is in same domain can load this website in iframe. https://stackoverflow.com/questions/6663244/cant-show-some-websites-in-iframe-tag Alt RSS Widget: <script src="https://apps.elfsight.com/p/platform.js" defer></script> <div class="elfsight-app-8283fde1-03d2-4c5e-a789-3e2e667372e4"></div> ____________________________________________ Embed Gdrive folder via iframe: -------------------------------- <iframe src=" https://drive.google.com/embeddedfolderview?id=1QbDDSVbCEZOXjJKICwuJNEHFKWwl_AvX#list" style="width:100%; height:600px; border:0;"></iframe> (Source: https://stackoverflow.com/questions/20681974/how-to-embed-a-google-drive-folder-in-a-website) ____________________________________________ Embed Gdrive file via iframe ------------------------- <iframe src=" https://drive.google.com/file/d/1z0_ygovRUlwMRjc-UQVO8HbwW57Qs_On/preview" width="640" height="480"></iframe> (Source:https://stackoverflow.com/questions/44164367/how-to-embed-google-drive-document-pdf-in-blogger-post) ____________________________________________ Contact form (mail): -------------------------- GDRIVE: you can still view or host HTML files that are in public folders as follows: Replace https://drive.google.com/?authuser=0#folders/ with "googledrive.com/host/" Your new URL will look something like this: http://www.googledrive.com/host/0B81vQexWOx6hRDVMQWZTekx4Umc https://superuser.com/questions/813701/view-rendered-html-of-html-document-in-new-google-drive https://www.freecontactform.com/form-guides/html-email-form#htmlform https://hwpi.harvard.edu/assetslibrary/host-css-or-js-file-google-drive https://discuss.codecademy.com/t/linking-css-to-html-via-google-drive/64725/4 [Service] https://www.abcsubmit.com ____________________________________________ GoogleDrive embed: (dot menu -> Embed item... -> copy code) ---------------------------- <iframe src="https://drive.google.com/file/d/-url-identifier-gdrive/preview" width="640" height="480"></iframe> url-identifier-gdrive: f.e.: (between "/d/" and "/view") https://drive.google.com/file/d/1pOZxwsDoRQ3V3ubonniGEbpSo6BXjQnr/view --hotlink--file: (https://stackoverflow.com/questions/10311092/displaying-files-e-g-images-stored-in-google-drive-on-a-website) https://docs.google.com/uc?id=FILE-ID ___________________________________________ Obfuscate html --------------------------- https://www.wmtips.com/tools/html-obfuscator/ (Source: https://community.spiceworks.com/topic/790618-hiding-view-source-on-websites) ____________________________________________ HTML Password encrypt (via pagecrypt) ------------------------------------------------------- https://www.maxlaumeister.com/pagecrypt/ E:\Dropbox\Public\00_Powershell\[Context_menu_handler_sendto]\encrypt_html.bat ____________________________________________ HTML Password protect (via htaccess): ------------------------------------------------------ https://www.lifewire.com/password-protect-single-file-with-htaccess-3467922 ____________________________________________ Append to Form input (query) via onsubmit: ------------------------------------------- (Source: https://stackoverflow.com/questions/34128361/appending-form-input-value-to-action-url-as-path) ____________________________________________ Searchbox (w.o. q=?, only "/"): --------------------------------------- <div id="tfheader"> <form id="tfnewsearch" method="get" action="http://de.wiktionary.org/wiki/"> <input type="text" class="tftextinput" id="tftextinput" name="q" size="21" maxlength="120"><input type="submit" value="search" class="tfbutton"> </form> <div class="tfclear"></div> </div> <script> var a = document.getElementById('tfnewsearch'); a.addEventListener('submit',function(e) { e.preventDefault(); var b = document.getElementById('tftextinput').value; window.location.href = 'http://de.wiktionary.org/wiki/'+b; }); </script> https://stackoverflow.com/questions/27580420/html-how-to-create-search-bar ________________________________________________________________________________________________________ Search bar (works on: PC, iPad, Android, 3DS, WiiU) ------------------------------------------------------------ <!DOCTYPE html> <html> <head> <title>Search Box Example 1</title> <meta name="ROBOTS" content="NOINDEX, NOFOLLOW" /> <!-- CSS styles for standard search box --> <style type="text/css"> #tfheader{ background-color:#c3dfef; } #tfnewsearch{ float:right; padding:20px; } .tftextinput{ margin: 0; padding: 5px 15px; font-family: Arial, Helvetica, sans-serif; font-size:14px; border:1px solid #0076a3; border-right:0px; border-top-left-radius: 5px 5px; border-bottom-left-radius: 5px 5px; } .tfbutton { margin: 0; padding: 5px 15px; font-family: Arial, Helvetica, sans-serif; font-size:14px; outline: none; cursor: pointer; text-align: center; text-decoration: none; color: #ffffff; border: solid 1px #0076a3; border-right:0px; background: #0095cd; background: -webkit-gradient(linear, left top, left bottom, from(#00adee), to(#0078a5)); background: -moz-linear-gradient(top, #00adee, #0078a5); border-top-right-radius: 5px 5px; border-bottom-right-radius: 5px 5px; } .tfbutton:hover { text-decoration: none; background: #007ead; background: -webkit-gradient(linear, left top, left bottom, from(#0095cc), to(#00678e)); background: -moz-linear-gradient(top, #0095cc, #00678e); } /* Fixes submit button height problem in Firefox */ .tfbutton::-moz-focus-inner { border: 0; } .tfclear{ clear:both; } </style> </head> <body> <!-- HTML for SEARCH BAR --> <div id="tfheader"> <form id="tfnewsearch" method="get" action="http://www.google.com"> <input type="text" class="tftextinput" name="q" size="21" maxlength="120"><input type="submit" value="search" class="tfbutton"> </form> <div class="tfclear"></div> </div> </body> </html> (Source: https://www.textfixer.com/tutorials/html-search-box.php) https://pastebin.com/MzWCYjeD ________________________________________________________________________________________________ Search Bar Example (with set off text) ------------------------------------------------- <!DOCTYPE HTML> <html lang="de"> <head> <style> body{ padding:20px; } *{ margin: 0; padding:0px; } .tftextinput{ font-family: Arial, Helvetica, sans-serif; font-size:14px; border:1px solid #0076a3; border-bottom-left-radius:5px; border-bottom-right-radius:5px; } </style> <title>savepage</title> </head> <body> <div id="tfheader"> <form id="tfnewsearch" method="GET" action="https://api.github.com/users/xxx/events/public"> <input class="tftextinput" id="text" size="21" maxlength="120" type="text"><br> <input value="GitHubUser" class="tfbutton" type="submit"></form> </div> <script> var ele=document.getElementById('tfnewsearch'); ele.addEventListener("submit",function(evt){ let alt=ele.getAttribute('action'); let such_link=document.getElementById('text').value; let neuer_link=alt.replace('xxx',`${such_link}`) neu=ele.setAttribute('action',neuer_link); //ele.submit(); // evt.preventDefault(); console.log(ele.getAttribute('action')) }); </script> </body> </html> (Source: https://www.html-seminar.de/forum/thread/8884-html-submit-form-searchbox-ver%C3%A4nderte-position-suchparameter/#post60465) ________________________________________________________________________________________________________ Hide texrt data: -------------------- .hidden { background: #000; font-color: #000; transition: background 0.5s ease; } .hidden:hover { background: none; } Here is a large block of text with a hidden <span class="hidden">password</span> that is only revealed on hover. (Source:https://discourse.joplinapp.org/t/hiding-passwords-with-markdown/1588/2 ) ________________________________________________________________________________________________________ CSS Data uri: --------------------- https://css-tricks.com/data-uris/ ________________________________________________________________________________________________________ Create colored button: ---------------------- <button style="background-color: white; border-color:white; color: white" > https://www.wikihow.com/Change-the-Button-Color-in-HTML ______________________________________________________ Borderless (Spoiler) Button: ----------------------------- Add: "padding: 0; border: none;" to remove border <div class="pre-spoiler"> <input style="color: rgb(255, 255, 255);padding: 0; border: none;" name=" " onclick="if (this.parentNode.getElementsByTagName('div')[0].style.display != 'none') { this.parentNode.getElementsByTagName('div')[0].style.display = 'none'; this.value = ' '; } else { this.parentNode.getElementsByTagName('div')[0].style.display = 'block'; this.value = '-';}" value=" " background-color="" border-color:white="" color="" white="" type=" style="> <div class="spoiler" style="display: none;"> --- </div> (Source: https://stackoverflow.com/questions/11497094/remove-border-from-buttons ) ___________________________ CSS-Spoiler_Button ------------------ <style> body{padding-left:150px}p{font-size:16px;color:#2f4f4f}button:focus{outline:0}img{margin:10px}#spoiler_button{font-size:20px;font-weight:700;width:175px;height:55px;margin:0 10px;color:#fff;border-radius:5px;border:none}#spoiler_button:hover{font-weight:700;color:#fff;box-shadow:5px 5px 5px #000}#spoiler_button:active{font-weight:700;box-shadow:none}.fadein,.fadeout{height:0;-moz-transition:opacity 1s ease-in-out;-o-transition:opacity 1s ease-in-out;-webkit-transition:opacity 1s ease-in-out;opacity:0}.fadein{height:100%;opacity:1} </style> <button name="spoiler_button" id="spoiler_button" type="button">Show Spoilers</button> <div id="spoiler_text" class="fadeout" > <br>Text </div> <script> var divToggleVis=document.getElementById("spoiler_text"),button=document.getElementById("spoiler_button");button.onclick=function(){"fadeout"===divToggleVis.className?divToggleVis.className="fadein":divToggleVis.className="fadeout","Show Spoilers"===button.innerHTML?button.innerHTML="Hide Spoilers":button.innerHTML="Show Spoilers"}; </script> (Source: https://trujared.medium.com/how-to-easily-add-a-spoiler-button-to-your-webpage-dec050c4867f ) ________________________ HTML Spoiler Tag code / Spoiler button ------------------------------------- <div class="pre-spoiler"> <input name="Deutsch" type="button" onClick="if (this.parentNode.getElementsByTagName('div')[0].style.display != 'none') { this.parentNode.getElementsByTagName('div')[0].style.display = 'none'; this.value = 'ButtontextSpoiler'; } else { this.parentNode.getElementsByTagName('div')[0].style.display = 'block'; this.value = 'ButtontextSpoiler';}" value="ButtontextSpoiler"> <div class="spoiler" style="display: none;"> <br>Text </div> #Make button white, via "KompoZer's "split-view" -> edit button" <input name="Buttonz" onclick="if (this.parentNode.getElementsByTagName('div')[0].style.display != 'none') { this.parentNode.getElementsByTagName('div')[0].style.display = 'none'; this.value = 'Buttonz'; } else { this.parentNode.getElementsByTagName('div')[0].style.display = 'block'; this.value = 'Buttonz';}" value="Buttonz" background-color:="" white;="" border-color:white;="" color:="" white="" type=" style="> (SOURCE: https://forum.chip.de/discussion/1381745/spoiler-in-html) ________________________________________________________________________________________________________ HTML Spoiler Tag code / Spoiler button (invisible) ------------------------------------- <div class="pre-spoiler"><input name="Deutsch" type="button" onClick="if (this.parentNode.getElementsByTagName('div')[0].style.display != 'none') { this.parentNode.getElementsByTagName('div')[0].style.display = 'none'; this.value = 'ButtontextSpoiler'; } else { this.parentNode.getElementsByTagName('div')[0].style.display = 'block'; this.value = 'ButtontextSpoiler';}" value="ButtontextSpoiler" style="background:transparent; border:none; color:transparent;"><div class="spoiler" style="display: none;" > text </div> (Source: ) ________________________________________________________ HTML "Mouseover" spoiler button: <div class="pre-spoiler"> <input name="Deutsch" type="button" onmouseover="if (this.parentNode.getElementsByTagName('div')[0].style.display != 'none') { this.parentNode.getElementsByTagName('div')[0].style.display = 'none'; this.value = 'ButtontextSpoiler'; } else { this.parentNode.getElementsByTagName('div')[0].style.display = 'block'; this.value = 'ButtontextSpoiler';}" value="ButtontextSpoiler"> <div class="spoiler" style="display: none;"> <br>Text </div> __________________________________________________________________________________________________________ Alt Spoiler Button: ------------------- <div id="spoiler" style="display:none"> <iframe src="https://alceawisteria.codeberg.page/Diarykeepers_Homepage/stash/webamp.html" frameBorder="no" width=500px height=250></iframe> </div> <button title="Click to show/hide content" type="button" onclick="if(document.getElementById('spoiler') .style.display=='none') {document.getElementById('spoiler') .style.display=''}else{document.getElementById('spoiler') .style.display='none'}">Winamp</button> (Source: ) _________________________ ================= ===htmlunicodecharas== (enter on "Source page" in NVU-HTML_Editor "f.e."&#9742 => ☎) &#9986 -> ✂ https://www.w3schools.com/charsets/ref_utf_symbols.asp xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==========xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx===brknz===xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ________ Search Bar Example: (broken in 3DS/iPad) ---------------------------- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <style type="text/css"> #tfheader{ } #tfnewsearch{ float:left; padding:0px; } .tftextinput{ margin: 0; padding: 0px 0px; font-family: Arial, Helvetica, sans-serif; font-size:14px; border:1px solid #0076a3; border-left:0px; border-top-left-radius: 0px 0px; border-bottom-left-radius: 5px 5px; } </style> <title>savepage</title> </head> <body> <!-- HTML for SEARCH BAR --> <div id="tfheader"> <form id="tfnewsearch" method="get" action="http://www.google.com"> <input class="tftextinput" name="q" size="21" maxlength="120" type="text"><input value="search" class="tfbutton" type="submit"></form> </div> </body> </html> (Source: https://www.textfixer.com/tutorials/html-search-box.php) https://www.informatikzentrale.de/formulare-get-post-auswerten.html https://shipow.github.io/searchbox/ ==================== ==htmlknowledge====== Percent-encoding, also known as URL encoding, is a mechanism for encoding information in a Uniform Resource Identifier (URI) under certain circumstances. Although it is known as URL encoding, it is, in fact, used more generally within the main Uniform Resource Identifier (URI) set, which includes both Uniform Resource Locator (URL) and Uniform Resource Name (URN). As such, it is also used in the preparation of data of the application/x-www-form-urlencoded media type, as is often used in the submission of HTML form data in HTTP requests. https://en.wikipedia.org/wiki/Percent-encoding https://stackoverflow.com/questions/2949173/how-can-i-stop-the-browser-from-url-encoding-form-values-on-get https://help.surveygizmo.com/help/url-variables _______________________________________________ _______________________________________________ Host (direct link files) via GDrive: ------------------------------------ (download gdrive file via "browser's download function" => extract url via download manager: https://doc-08-28-docs.googleusercontent.com/docs/securesc/3pli3vb6c3fjid7vrr3dsc370lictgus/s0gn0auvde7qio9pka1m5fu34rjh8eec/1626641550000/12239065730730513726/06275268013771742302Z/1n2SyolVouApQftVAEWXqOKJ_F4bN2W4H?e=download&nonce=r0nhojk7dh1uu&user=06275268013771742302Z&hash=rbp7if89k7n8l4bgu8kpineqce7mj3f5 => remove "e=download&" https://doc-08-28-docs.googleusercontent.com/docs/securesc/3pli3vb6c3fjid7vrr3dsc370lictgus/s0gn0auvde7qio9pka1m5fu34rjh8eec/1626641550000/12239065730730513726/06275268013771742302Z/1n2SyolVouApQftVAEWXqOKJ_F4bN2W4H?nonce=r0nhojk7dh1uu&user=06275268013771742302Z&hash=rbp7if89k7n8l4bgu8kpineqce7mj3f5 _______________________________________________ ================== == hostwebsite === Use Github Pages to host website: --------------------------------- http://docsbeta.pinegrow.com/host-html-website-github-pages-free/ (Result:) => https://ry3yr.github.io/OSTR/Diarykeepers_Homepage =================================================== =================================================== =================================================== =================================================== =============== javascriptz ======================= ** javascript test site: jsfiddle** In HTML, JavaScript code is inserted between <script> and </script> tags - w3schools.com/js/js_whereto.asp https://cdnjs.com/libraries (prehosted JS script links) JSONEScape tool https://www.freeformatter.com/json-escape.html#ad-output Check Javascript errors: https://www.shellcheck.net/ •MinifyJS: https://www.toptal.com/developers/javascript-minifier/ •CompressJS: https://jscompress.com/ ----------------------------------------- Autoadvance video playlist player ------------------------------------------ https://codepen.io/ryedai1/pen/mdKGVOz https://cdpn.io/ryedai1/fullpage/mdKGVOz?nocache=true ________________________ Yesterdays happenings ----------------------------------- <li><a id="link" href="#">Yesterdays Happenings</a></li> <script> Date.prototype.toShortFormat = function() { const monthNames = ["January", "February", "March", "April", "May", "Juni", "July", "August", "September", "October", "November", "December"]; const day = this.getDate()-1; const monthIndex = this.getMonth(); const monthName = monthNames[monthIndex]; const year = this.getFullYear(); return `${year}_${monthName}_${day}`; } let anyDate = new Date(1528578000000); console.log(anyDate.toShortFormat()); let today = new Date(); console.log(today.toShortFormat()); var link = document.getElementById("link"); link.setAttribute("href","https://en.m.wikipedia.org/w/index.php?title=Portal:Current_events/" + today.toShortFormat()); </script> <!--https://stackoverflow.com/questions/4822852/how-to-get-the-day-of-week-and-the-month-of-the-year--> <!-- partial --> ________________________ Click text in page: ------------ if (document.all !== undefined) { var items = document.all; } else { var items = document.getElementsByTagName("*"); }; for (var i = 0; i < items.length; ++i) { if (items[i].textContent.includes("Your Text Here")) { console.log("success"); items[i].click(); } } (Source: https://stackoverflow.com/questions/62722876/how-to-click-a-specific-text-in-the-page-javascript) _________________ Javascript Weather (snow): https://www.cssscript.com/minimalist-falling-snow-effect-with-pure-javascript-snow-js/ ______________________________ Quote of the day button w linksupport ----------------------------------- <head><meta charset="UTF-8"><style> div {text-align: left;} h1 {font-size: 11px;font-family: Arial;} button {width: 128px;height: 28px;background-color: white;color: black;} {font-size: 11px;} button:hover{background-color: white;} </style></head><body> <div><button onclick="generateQuote();">QuoteOfTheDay</button><p id="quoteOutput"></div> <script>const arrayOfQuotes = [ {'quote': 'Startdust Road - YGO Vrains OST 2 -Shinkichi Mitsumune'}, {'quote': '<a target="_blank" href="https://m.youtube.com/watch?v=TU_bS6ttM4Q">Can\'t stay mad at you - Haven Danger</a>'}, {'quote': '<a target="_blank" href="https://m.youtube.com/watch?v=cqAawjQyieg">Kellys Lullaby - Another Code R - Satoshi Okubo</a>'}, {'quote': 'Abandon Me - Chicory OST - Lena Raine'}, {'quote': '<a target="_blank" href="https://youtube.com/watch?v=jCYB1BeDv1w">A Fathers Letter - MftO. Vol I - Stephen Barton</a>'}, ];function generateQuote(){const random = Number.parseInt(Math.random()*arrayOfQuotes.length + 1); document.querySelector('#quoteOutput').innerHTML = `\"${arrayOfQuotes[random].quote}\"`;} </script> </body> (Source: ) DayQuotes random line + Linksupport + external files + search --------------------------------------------------------------------------- <!DOCTYPE html> <!--https://run.plnkr.co/plunks/6wCVvXqoIGl6hN6Q--> <style> div {text-align: left;} h1 {font-size: 11px;font-family: Arial;} button {width: 128px;height: 28px;background-color: white;color: black;} {font-size: 11px;} button:hover{background-color: white;} </style> <html> <head> <meta charset="utf-8" /> <!--<link rel="stylesheet" href="style.css" />--> <script src="https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/jquery.min.js"></script> <script> var lines; var randomNumber; var lastRandomNumber; $(document.body).ready(function () { // load the trivia from the server $.ajax({ url: 'https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Daymusic_array.js' }).done(function(content) { lines = content.replace(/\r\n|\r/g, '\n').trim().split('\n'); if (lines && lines.length) { $('#searchInput').on('keyup', function () { searchLines($(this).val()); }); $('#showLine').on('click', function () { while (randomNumber === lastRandomNumber) { randomNumber = parseInt(Math.random() * lines.length); if (lines.length === 1) { break; } } lastRandomNumber = randomNumber; var line = lines[randomNumber].replace("{'quote': '", "").replace("'},", ""); $('#trivia').html(line); $('#searchResults').empty(); }); } }); }); function searchLines(query) { // filter the list of lines based on the search query var filteredLines = lines.filter(function(line) { return line.toLowerCase().includes(query.toLowerCase()); }); // display the filtered results var resultsHtml = ''; filteredLines.forEach(function(line) { resultsHtml += '<div>' + line + '</div>'; }); $('#searchResults').html(resultsHtml); } </script> </head> <body> <input type="text" id="searchInput" placeholder="Search..."> <div id="searchResults"></div> <button id="showLine">CurrListening</button> <p id="trivia"></p> </body> • <a target="_blank" href="https://-db/ratings.php?do=view&userid=24381">vgmdb ratings</a> • <a target="_blank" href="">LastFM </a> <!----------------viewsource---> • <a target="_blank" href="Daymusic_array.js">List</a> <!--<a id="myLink" title="Click to do something" href="#" onclick="viewSource();return false;">List</a> <script> function viewSource(){; var source = "<html>"; source += document.getElementsByTagName('html')[0].innerHTML; source += "</html>"; source = source.replace(/</g, "&lt;").replace(/>/g, "&gt;"); source = "<pre>"+source+"</pre>"; sourceWindow = window.open('','Source of page','height=800,width=800,scrollbars=1,resizable=1'); sourceWindow.document.write(source); sourceWindow.document.close(); if(window.focus) sourceWindow.focus(); } </script>--> </html> ______________ Quote of the Day (button, no nr) ---------------- <head> <meta charset="UTF-8"> <style> div {text-align: center;} h1 { font-size: 24px;font-family: Arial; } button { width: 100px;height: 40px;background-color: white;color: black; } p{ font-size: 24px; } button:hover{ background-color: white; } </style> </head> <body> <div> <button onclick="generateQuote();">QuoteOfTheDay</button> <p id="quoteOutput"><p id="authorOutput"> </div> <script> const arrayOfQuotes = [ {'author': 'Jim Rohn','quote': 'Beware of what you become in pursuit of what you want.'}, {'author': 'Epictetus','quote': 'It\'s not what happens to you, but how you react to it that matters.'}, {'author': 'Frank Sinatra','quote': 'The best revenge is massive success.'}, {'author': 'Wayne Gretzy','quote': 'You miss 100% of the shots you don\'t take.'}, {'author': 'Nelson Mandela','quote': 'Resentment is like drinking poison and waiting for your enemies to die.'}, {'author': 'Elbert Hubbard','quote': 'Do not take life too seriously. You will not get out alive.'}, ]; function generateQuote(){ const random = Number.parseInt(Math.random()*arrayOfQuotes.length + 1); document.querySelector('#quoteOutput').textContent = `\"${arrayOfQuotes[random].quote}\"`; document.querySelector('#authorOutput').textContent = `-${arrayOfQuotes[random].author}`; } </script> </body> https://github.com/JS-Beginners/quote-of-the-day-project/blob/main/quotes.js _____________________________________________________________ JS Fallback script: ----------------- <script src="https://mipage.com/pathtoscript/owlscript.js" onerror="loadFallbackScript()"></script> and then loading the fallback script from code: <script> function createScriptElement(src) { const script = document.createElement('script'); script.src = src; return script; } function loadFallbackScript() { const fallbackScriptElement = createScriptElement('fallback.js'); document.head.append(fallbackScriptElement); } </script> (Source: https://stackoverflow.com/questions/70229312/implement-fallback-incase-original-script-is-not-found/70235593#70235593 ) ______________________________ Searchbar with clickable links: ------------------------------- <!--- https://stackoverflow.com/questions/36634118/creating-a-clickable-search-result-from-a-json-table ---> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> </head> <body> <div class="container" style="padding:1px 1px;"> <form role="form"> <div class="form-group"> <input type="input" class="form-control input-lg" id="txt-search" placeholder="Type your search character"> </div> </form> <div id="filter-records"></div> </div> </body> </html> <script type="text/javascript"> $(document).ready(function(){ var data = [ {"id":"1","page_name":"Startseite","html_name":"index.html","undefined_value":"61","profile_image":"null.png"}, {"id":"2","page_name":"New Art","html_name":"0newart.html","undefined_value":"00","profile_image":"null.png"}, {"id":"99","page_name":"Pagename","html_name":"null","undefined_value":"00","profile_image":"null.png"}]; $('#txt-search').keyup(function(){ var searchField = $(this).val(); if(searchField === '') { $('#filter-records').html(''); return; } var regex = new RegExp(searchField, "i"); var output = '<div class="row">'; var count = 1; $.each(data, function(key, val){ if ((val.html_name.search(regex) != -1) || (val.page_name.search(regex) != -1)) { output += '<div class="col-md-4"><a href="'+val.html_name+''; output += '">'; output += '<p>' + val.html_name + '</p>' output += '</div>'; output += '</div>'; if(count%2 == 0){ output += '</div><div class="row">' } count++; } }); output += '</div>'; $('#filter-records').html(output); }); }); </script> (Source: https://stackoverflow.com/questions/70222874/json-search-fails-after-adding-11th-entry) (Source: https://www.js-tutorials.com/jquery-tutorials/live-search-json-objects-data-using-jquery/) ______________________________ Simulate back button -------------------- history.go(-1) (Source: https://stackoverflow.com/questions/13490407/how-to-emulate-browser-back-button-using-javascript) ________________________ JS Alerts (Popup/Prompt/Entry): ----------------------- https://www.w3schools.com/js/js_popup.asp __________________ Alert user to switch to Landscape ---------------- <script> if(window.innerHeight > window.innerWidth){ alert("Please use Landscape!"); } </script> (Source: https://stackoverflow.com/questions/4917664/detect-viewport-orientation-if-orientation-is-portrait-display-alert-message-ad ) ____________________________________________ Adaptive height iFrame: ---------------------------- Option a): --------- <style> iframe {width: 1px;min-width: 100%;} </style> <iframe id="myIframe" src="0newart.html"></iframe> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.2/iframeResizer.min.js"></script> <script type="text/javascript"> iFrameResize({ log: true }, '#myIframe') </script> Option b): ---------- <head> <script> function resizeIframe(obj) { obj.style.height = obj.contentWindow.document.documentElement.scrollHeight + 'px'; }</script> .. <iframe src="..." frameborder="0" scrolling="no" onload="resizeIframe(this)" /> (Source: https://codehunter.cc/a/javascript/make-iframe-automatically-adjust-height-according-to-the-contents-without-using-scrollbar-duplicate ) _________________________________________________________________ Load html from (same domain site) via jquery: ------------------------------------ <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ var baseUrl = window.location.href.match(/(.*)[\/\\]/)[1]; $("#output").load(baseUrl + "/Search.html"); }); </script> <div class="formClass"> <div id="output"> </div> (Source: https://stackoverflow.com/questions/70268604/direct-path-alternative-for-offsite-html-loading-via-js) ______________________________________________ Load html from offsite via jquery: ---------------------------------- a) Automatically: ------------------------ <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ $("#output").load("https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Search.html"); }); </script> <div class="formClass"> <div id="output"> </div> b) Requiring user button press: --------------------------------------------- <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ $("button").click(function(){ $("#output").load("https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Project.html"); }); }); </script> <body> <div class="formClass"> <div id="output"> </div> <button type="button">Click to Load Content</button> </div> </body> </html> (Source: https://laratutorials.com/jquery-load-load-html-content-from-another-page-using-ajax/ ) AJAX cannot work from file:// but http:// (Source: https://stackoverflow.com/questions/16821432/loading-local-html-file-into-div) (Source: https://stackoverflow.com/questions/70254443/convert-js-to-autoload ) Due to security issues (same origin policy), javascript access to local files is restricted if without user interaction. https://stackoverflow.com/questions/18637418/trying-to-load-local-json-file-to-show-data-in-a-html-page-using-jquery _________________________________________________________________________________ Button load local html file (kinda iframe-y): --------------------------- <!DOCTYPE HTML> <div id="topBar"> <a href ="#" onclick="load_home()"> HOME </a> </div> <div id ="content"> </div> <script> function load_home() { document.getElementById("content").innerHTML='<object type="text/html" data="Search.html" ></object>'; } </script> (Source: https://stackoverflow.com/questions/17636528/how-do-i-load-an-html-page-in-a-div-using-javascript#comment25679730_17636528) ______________________________________ Display variable in popup: -------------------------------------- } alert(urls) (Source: https://stackoverflow.com/questions/1487588/extract-all-links-from-a-string ) ______________________________________________________________________________- Load (scalable) image from user input (buttonpress): ------------------------------ <body> <form> <input type="text" id="imagename" value="https://image.jpg" /> <input type="button" id="btn" value="LatestDAimage"/> </form> <html> <script type="text/javascript"> document.getElementById('btn').onclick = function() { var val = document.getElementById('imagename').value, src = val , img = document.createElement('img'); img.src = src; img.setAttribute('width', '333px'); document.body.appendChild(img); } </script> </body> </html> (Source: https://stackoverflow.com/questions/226847/what-is-the-best-javascript-code-to-create-an-img-element ___________________________________________________ Load image from user input (buttonpress): ------------------------------ <body> <form> <input type="text" id="imagename" value="http://euobtl-44e3133c-aa9e-4b16-b7ca-6645b4610d29.png/v1/fill/w_250,h_250,strp/dreaming_of_virtual_sheep_by_ryedai1_deuobtl-250t.png " /> <input type="button" id="btn" value="LatestDAimage"/> </form> <html> <script type="text/javascript"> document.getElementById('btn').onclick = function() { var val = document.getElementById('imagename').value, src = ' val, img = document.createElement('img'); img.src = src; document.body.appendChild(img); } </script> </body> </html> (Source: https://stackoverflow.com/questions/17634019/javascript-load-an-image-from-url-and-display) ___________________________________________________ Autoclick button on pageload: ----------------------------- <script> window.onload = function(){ document.getElementById('btn').click(); var scriptTag = document.createElement("script"); scriptTag.src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"; document.getElementsByTagName("head")[0].appendChild(scriptTag); } </script> (Source: https://stackoverflow.com/questions/50925683/javascript-auto-click-on-a-button-on-page-load/50925730 ) ______________________________________________________ Javascript load iframe: ------------------------ <div id="placeholder"></div> <script id="iframeTemplate" type="text/html"> <iframe src="..."> </iframe> </script> <script type="text/javascript"> var element, html, template; element = document.getElementById("placeholder"); template = document.getElementById("iframeTemplate"); html = template.innerHTML; element.innerHTML = html; </script> (Source: https://stackoverflow.com/questions/8726455/creating-an-iframe-using-javascript/8726513) ___________________________ JS open url ---------------- window.open("https://www.youraddress.com","_self") (Source: https://stackoverflow.com/questions/8454510/open-url-in-same-window-and-in-same-tab ) __________________ Read window url to variable: ----------- <script> var currentUrl = window.location.href; </script> _________________________________________________________________ Copy to clipboard: ------------------ function copyToClipboard(text) { const elem = document.createElement('textarea'); elem.value = text; document.body.appendChild(elem); elem.select(); document.execCommand('copy'); document.body.removeChild(elem); } (Source: https://newbedev.com/html-html-copy-to-clipboard-without-javascript-code-example) ______________________________________________________ Disable right click --------------------------- <script type="text/javascript"> <!-- // Created by smartgb.com, http://www.smartgb.com/free.php document.oncontextmenu = new Function("return false"); // --> </script> (Source: https://www.smartgb.com/free_javascript.php) ______________________ Display current month: --------------------------------- <p id="currmonth"></p> <script> const month = ["January","February","March","April","May","June","July","August","September","October","November","December"]; const d = new Date(); let name = month[d.getMonth()]; document.getElementById("currmonth").innerHTML = name; </script> </body> (Source: https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_date_getmonth ) ____________________ Show image (or "do x") at certain season (month period): ------------------------- <div id="banner-container" style="width:400px;height:300px;"></div> <script> var currentTime = new Date(); var month = currentTime.getMonth() + 1; var total = month; // Summer if (total >= 6 && total <= 8) {document.getElementById("banner-container").style.backgroundImage = "url('summer.png')";} // Autumn else if (total >= 9 && total <= 11) {document.getElementById("banner-container").style.backgroundImage="url('fall.png')";} // Winter else if (total == 12 || total == 1 || total == 2) {document.getElementById("banner-container").style.backgroundImage = "url('winter.png')";} // Spring else if (total >= 2 && total <= 6) {document.getElementById("banner-container").style.backgroundImage = "url('spring.png')";} else {document.getElementById("banner-container").style.backgroundImage = "url('summer.png')";} </script> (Source: https://stackoverflow.com/questions/27176180/automatically-change-the-picture-by-month-with-javascript?noredirect=1&lq=1) _________________________________________________ Call js from within javascript: ------------------------------ <script> document.write('<script src="https://ry3yr.github.io/OSTR/myhomepage/snow.js"><\/script>'); </script> <body> </body> ====OR - call if month = december:=== <script> if (new Date().getMonth()===11) document.write('<script src="https://ry3yr.github.io/OSTR/myhomepage/snow.js"><\/script>'); </script> <body> </body> (Source: https://stackoverflow.com/questions/70357122/execute-js-if-month-is-december) _______________ Obfuscate ("encrypt") JS Code: ---------------------------- https://www.cleancss.com/javascript-obfuscate/ http://www.jsobfuscate.com/ _______________ Make a "view source button in css popup" ------------------------ https://css-tricks.com/make-a-view-source-button/ __________________ Source in new window: --------------------------- <button type ="button" onclick="viewSource()">List</button> <script> function viewSource(){; var source = "<html>"; source += document.getElementsByTagName('html')[0].innerHTML; source += "</html>"; source = source.replace(/</g, "&lt;").replace(/>/g, "&gt;"); source = "<pre>"+source+"</pre>"; sourceWindow = window.open('','Source of page','height=800,width=800,scrollbars=1,resizable=1'); sourceWindow.document.write(source); sourceWindow.document.close(); if(window.focus) sourceWindow.focus(); } </script> (Source: https://stackoverflow.com/questions/41945094/add-option-to-view-source-of-current-html-page ) _________________________ Load just containerpart of site into dom: -------------------------------- <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script type="text/javascript"> { $(document).ready(function(){ $("#bitbucketissue").load("https://bitbucket.org/alceawisteria/ostr/issues?status=new&status=open #issues-list"); }); } </script> <div class="formClass"> <div id="bitbucketissue"> </div> (Source: https://stackoverflow.com/questions/74993624/loading-only-part-of-site-into-dom-via-jquery) ____________________________ Link to new site based on appended hashtag ------------------------------------------------------------------------ <a rel="me" href="https://pb.todon.de/@alcea"> </a><br> <script type="text/javascript"> if (location.href.indexOf("#underrated") != -1) { window.open("UnderratedContent.html", "_self"); } </script> (Source: https://stackoverflow.com/questions/298503/how-can-you-check-for-a-hash-in-a-url-using-javascript ) ----------------------------------------- Reenable purposely broken element: ------------------------------------------------------- <button id="mypxlbtn" onclick="myPXFDFunction()">Pixelfed</button> <script> function myPXFDFunction() { document.body.innerHTML = document.body.innerHTML.replace('pixlframe', 'iframe'); //document.getElementById('mypxlbtn').style.visibility = 'hidden'; } </script> <pixlframe src="https://pixelfed.de/aoikurayami/embed" width="80%" height="800" scrolling="yes" frameborder="0"></pixlframe><!--<script async defer src="https://pixelfed.de/embed.js"></script>--> ______________________________ Download #JSFiddle via #curl -------------------------------- curl "http://fiddle.jshell.net/${fiddleId}/show/" -H "Referer: http://fiddle.jshell.net/${fiddleId}/" --output "${fiddleId}.html" (Source: https://stackoverflow.com/questions/9851878/is-there-a-download-function-in-jsfiddle/ ) ______________ Easy Copy button ---------------------------- <button onclick="copyToClipboard()" style="background:transparent; border:transparent;">(C)</button> </div> <script> function copyToClipboard() { const str = "curl 'http://fiddle.jshell.net/GSSCD/203//show/' -H 'Referer: http://fiddle.jshell.net/GSSCD/203//' --output 'fiddle.html'" const el = document.createElement('textarea') el.value = str el.setAttribute('readonly', '') el.style.position = 'absolute' el.style.left = '-9999px' document.body.appendChild(el) el.select() document.execCommand('copy') document.body.removeChild(el) } </script> _______________ Slide Toggle --------------------- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script> <script> $(document).ready(function(){ $("button").click(function(){ $("p").slideToggle(); }); }); </script> </head> <body> <p>Text</p> <button>Toggle </button> </body> ______________ Userinput formfield => create url ------------------------------------------------- <form> <input type="text" id="pixivurl" value="75406576"/> <input type="button" id="btn" value="pixivuserid"/> </form> <script type="text/javascript"> document.getElementById('btn').onclick = function() { var val = document.getElementById('pixivurl').value; var url = "https://rsshub.app/pixiv/user/" + val; var a = document.createElement('a'); var linkText = document.createTextNode(url); a.appendChild(linkText); a.title = "pixivuser"; a.target= '_blank'; a.href = url; a.style.display = 'block'; document.body.appendChild(a); } </script> _____________ Turn YT Channel ID into ChannelURLPlaylist ---------------------------------------------- <form> <input type="text" id="yturl" value="UCn-K7GIs62ENvdQe6ZZk9-w" /> <input type="button" id="btn" value="ChannelPlayListURL" /> </form> <script> document.getElementById('btn').onclick = function() { var val = document.getElementById('yturl').value; val = val.replace('UC', 'UU'); var url = "https://www.youtube.com/embed/videoseries?list=" + val; var a = document.createElement('a'); var linkText = document.createTextNode(url); a.appendChild(linkText); a.title = "yturl"; a.target = '_blank'; a.href = url; a.style.display = 'block'; document.body.appendChild(a); } </script> _______________ Random iFrame: ------------------------- <div id="bigdiv" style="position:fixed; top: 0px; left: 505px;"> <button id="btn" class="box">Random</button> <iframe id="frame" src="rss-scrolling-source01.html" frameborder="0" scrolling="no" height=65 width=400></iframe> </div> <script> var cat1 = [    "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/rss-scrolling-source01.html",    "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/rss-scrolling-source02.html" ]; var myFrame = document.getElementById("frame"); function getRandomUrl(myFrame) { var index = Math.floor(Math.random()*cat1.length); var url = cat1[index]; myFrame.src = url; } btn.addEventListener("click", function() { getRandomUrl(myFrame); }); </script> ___________________ Click button on pageload: -------------------------------------- <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ document.getElementById("btn").click(); });</script> _________________________________ Texteditor with save function --------------------------------------------- <!---http://fiddle.jshell.net/ourcodeworld/rce6nn3z/2//show--> <script type="text/javascript" src="/js/lib/dummy.js"></script> <link rel="stylesheet" type="text/css" href="/css/result-light.css"> <style id="compiled-css" type="text/css"> /* EOS */ </style> <script id="insert"></script> </head> <body> <textarea id="text-val" rows="4">Enter Text here</textarea><br/> <input type="button" id="dwn-btn" value="Download"/> <script type="text/javascript">//<![CDATA[ function download(filename, text) { var element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } // Start file download. document.getElementById("dwn-btn").addEventListener("click", function(){ // Generate download of hello.txt file with some content var text = document.getElementById("text-val").value; var filename = "hello.txt"; download(filename, text); }, false); //]]></script> <script> // tell the embed parent frame the height of the content if (window.parent && window.parent.parent){ window.parent.parent.postMessage(["resultsFrame", { height: document.body.getBoundingClientRect().height, slug: "rce6nn3z" }], "*") } // always overwrite window.name, in case users try to set it manually //window.name = "result" </script> </body> </html> _______________ Copy today date to clipboard: ------------------------------------------------ <a href="javascript:copydateToClipboard()">Date</a> <script> function copydateToClipboard() { var nowd = new Date().toUTCString().slice(0,16); const el = document.createElement('textarea') el.value = nowd el.setAttribute('readonly', '') el.style.position = 'absolute' el.style.left = '-9999px' document.body.appendChild(el) el.select() document.execCommand('copy') document.body.removeChild(el) }</script> _______________________ Days-since-online-counter --------------------------------------- Site online since <button id='counter' style="width:26px;height:20px;background:transparent; border:transparent;" onclick="window.open('https://web.archive.org/web/https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Startseite.html', '_blank');"></button> &nbsp;&nbsp;days. <!--<button id='today' ></button>--> <script src='https://code.jquery.com/jquery-2.2.4.min.js'></script> <script>$(document).ready(function(){ var oneDay = 24*60*60*1000; var start = new Date(2021, 06, 13); var now = new Date(); var elapsed = Math.round(Math.abs((now.getTime() - start.getTime())/(oneDay)-1)); $('#counter').html(` ${elapsed} `); var nowd = new Date().toUTCString().slice(0,16); $('#today').html(` ${nowd} `);});</script> ______________________________ Datepicker ---------------- <link rel="stylesheet" href="//code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css"> <script src="https://code.jquery.com/jquery-3.6.0.js"></script> <script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script> <script>$( function() { $( "#datepicker" ).datepicker({changeMonth: true,changeYear: true});} );</script> <p>Date: <input type="text" id="datepicker"></p> ______________________________ Random image on pageload: ------------------------------------------- <!----RandomImage---> <button id="myrdpbtn" style="background:transparent; border:transparent;" onclick="swap()">RandomPicture</button> <style type="text/css"> #banner {height:100%;object-fit: contain; //padding:0;margin:0;text-align:right; //position:relative; //position:absolute; top: 0px; left: 1200px; //background-position: center center; background-size: contain; background-repeat: no-repeat;}</style> <script type="text/javascript"> if (document.getElementById) { window.onload = swap }; function swap() { var numimages=8; rndimg = new Array( "https://m.box.com/file/903146245504/https%3A%2F%2Fapp.box.com%2Fs%2Fj7i88s6jb4yyk4wmiti4tol8ejoikdhl/preview/preview.png", "https://m.box.com/file/905690094898/https%3A%2F%2Fapp.box.com%2Fs%2Fj7i88s6jb4yyk4wmiti4tol8ejoikdhl/preview/preview.png", "https://cdnb.artstation.com/p/assets/images/images/055/144/127/large/ryedai1-2022-10-19-aoi-realist-no2.jpg?1666212593", "https://other00.deviantart.net/c476/o/2021/321/8/f/subaru_and_misora_by_pasc91.png", "https://m.box.com/file/900365515983/https%3A%2F%2Fapp.box.com%2Fs%2Fj7i88s6jb4yyk4wmiti4tol8ejoikdhl/preview/preview.png", "https://m.box.com/file/889506897379/https%3A%2F%2Fapp.box.com%2Fs%2Fj7i88s6jb4yyk4wmiti4tol8ejoikdhl/preview/preview.png", "https://m.box.com/file/903146245504/https%3A%2F%2Fapp.box.com%2Fs%2Fj7i88s6jb4yyk4wmiti4tol8ejoikdhl/preview/preview.png", "https://m.box.com/file/896387464705/https%3A%2F%2Fapp.box.com%2Fs%2Fj7i88s6jb4yyk4wmiti4tol8ejoikdhl/preview/preview.png" ); x=(Math.floor(Math.random()*numimages)); randomimage=(rndimg[x]); document.getElementById("banner").style.backgroundImage = "url("+ randomimage +")"; }</script> </head> <div id="banner""></div> _____________________ Random image on pageload (v2 15-7-23) --------------------------- (added dropdown picker) <button id="myrdpbtn" style="background:transparent; border:transparent;" onclick="swap()">RandomPicture</button> <select id="image-selector" style="width:250px;"> <option value="">Select an image</option> </select> <style type="text/css"> #banner { height:100%; object-fit: contain; background-size: contain; background-repeat: no-repeat; } </style> <script type="text/javascript"> if (document.getElementById) { window.onload = swap }; var urls = [ "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2022-10-18-TheOrgOfTwo.png#01", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2022-10-19-Aoi-Realist-no2.png#02", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-03-01-KisaraRelease.png#03", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-03-19-PunkLuv.png#04", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-03-17-HappyAlcea.png#05", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-05-06-ShokaConfidentBusinessReaper.png#06", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-05-08-YuNAoiFlippedHappiness.gif#07", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-05-10-RollbackCea.png#08", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-05-11-HibikiMisoraSemiRealism-BoredMissy.png#09", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-05-19-MisoraSelfFromElse.png#10", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-05-21-LinkIntoVRAINSTime.png#11", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2023-05-21-ZeriouslyContentAlcea.png#12", "https://alceawisteria.codeberg.page/images/arthost/2023-06-22-ShokaHi.png#13", "https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/ca81d24e-918d-4918-bd5a-f5b53c49858b/dg0nhf2-77f59ee8-51d9-4e73-a016-ea7a9af7900a.png/v1/fill/w_1117,h_715/2023_06_23_happyyu_by_aoikurayami1_dg0nhf2-pre.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9ODIwIiwicGF0aCI6IlwvZlwvY2E4MWQyNGUtOTE4ZC00OTE4LWJkNWEtZjViNTNjNDk4NThiXC9kZzBuaGYyLTc3ZjU5ZWU4LTUxZDktNGU3My1hMDE2LWVhN2E5YWY3OTAwYS5wbmciLCJ3aWR0aCI6Ijw9MTI4MCJ9XV0sImF1ZCI6WyJ1cm46c2VydmljZTppbWFnZS5vcGVyYXRpb25zIl19.PFzoLqNOce2sMzH4IDpTYhxIvC7YT4TlKU5X_MYsb-Q#14", "https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/ca81d24e-918d-4918-bd5a-f5b53c49858b/dg2ph6q-5ba53964-efcb-4f30-9dd0-6f97520e11de.png/v1/fill/w_1117,h_715,q_70,strp/backhug_by_aoikurayami1_dg2ph6q-pre.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9ODIwIiwicGF0aCI6IlwvZlwvY2E4MWQyNGUtOTE4ZC00OTE4LWJkNWEtZjViNTNjNDk4NThiXC9kZzJwaDZxLTViYTUzOTY0LWVmY2ItNGYzMC05ZGQwLTZmOTc1MjBlMTFkZS5wbmciLCJ3aWR0aCI6Ijw9MTI4MCJ9XV0sImF1ZCI6WyJ1cm46c2VydmljZTppbWFnZS5vcGVyYXRpb25zIl19.aE2GYnZKfcTbmoUfWBm8t3RxnroMSTc6vn0Sokhov6o#15", "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/arthost/2022-11-02-RealistShoka.png" ]; var select = document.getElementById("image-selector"); for (var i = 0; i < urls.length; i++) { var option = document.createElement("option"); option.value = urls[i]; var filename = urls[i].substring(urls[i].lastIndexOf('/')+1); option.text = "Image " + (i+1) + " (" + filename + ")"; select.add(option); } function swap() { var imageSelector = document.getElementById("image-selector"); var selectedImage = imageSelector.options[imageSelector.selectedIndex].value; if (selectedImage) { document.getElementById("banner").style.backgroundImage = "url(" + selectedImage + ")"; } else { var numimages = urls.length; var x = Math.floor(Math.random() * numimages); var randomimage = urls[x]; document.getElementById("banner").style.backgroundImage = "url(" + randomimage + ")"; } } document.getElementById("image-selector").addEventListener("change", swap); </script> <div id="banner"></div> ______________________ Random background on pageload: -------------------------------------------------- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css"> <style> #random{ width: 100%; height: 100%; //border: 1px solid black; background-image: url(''); //background-position: center center; //background-size: cover; //background-repeat: no-repeat;} </style> <body> <body onload="randombg()"><div id="random" ></div> <script> function randombg(){ var random= Math.floor(Math.random() * 6) + 0; var bigSize = ["url('https://media1.giphy.com/media/l0HlVrqPItQH8D4iI/giphy.gif')", "url('https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/bgfiles/cool_stuff_anim.gif')", "url('https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/bgfiles/cool_stuff_anim.gif')", "url('https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/bgfiles/comments_anim.gif')", "url('https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/extrafiles/images/bgfiles/comments_anim.gif')", "url('https://media1.giphy.com/media/l0HlVrqPItQH8D4iI/giphy.gif')"]; document.getElementById("random").style.backgroundImage=bigSize[random];}</script> (Source: https://stackoverflow.com/questions/75237942/load-random-tiled-image-into-style-background/75239481#75239481 ) ________________ Convert page to source code: --------------------------------------------- <button id="mypxlbtn" style="background:transparent; border:transparent;" onclick="myPXXFDFunction()">Source!</button> <script> function myPXXFDFunction() { document.documentElement.innerHTML = `<pre>${document.documentElement.innerHTML.replace(/</g,"&lt;")}</pre>`; } </script> _______________ Change favicon ------------------------ <button id="button">Change favicon</button> <script>function changeFavicon() {var link; link = document.createElement('link'); link.type = 'image/x-icon'; link.rel = 'shortcut icon'; link.href = 'https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/favicon.ico'; document.getElementsByTagName('head')[0].appendChild(link);}; document.getElementById('button').addEventListener('click', function(event) { changeFavicon();}, false); document.body.addEventListener('online', function() { console.log('online')}, false); document.body.addEventListener('offline', function() { console.log('offline')}, false); </script> ___________________ Check if url string exists ------------------------------------- <script src="jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function() { if (window.location.href.indexOf("Startseiteplayer") > -1) { alert("You have the player open already !"); window.history.back(); } else { //window.open("Startseiteplayerrun.html","_self"); link = document.createElement("a"); link.href = "Startseiteplayerrun.html"; //link.target = "_blank"; link.click() ;}}); </script><br> <a href="index.html">home</a> _______________ Check if iframed ---------- <script> if ( window.location !== window.parent.location ) { alert("You have the player open already !"); window.history.back(); } else { // The page is not in an iframe } </script> <!---https://tommcfarlin.com/check-if-a-page-is-in-an-iframe/--> ________________ Allow user txt file open & display lines --------------------------------------------------- <script> const $output = document.getElementById('output') document.getElementById('file').onchange = function() { var file = this.files[0]; var reader = new FileReader(); reader.onload = function(progressEvent) { // Entire file const text = this.result; $output.innerText = text // By lines var lines = text.split('\n'); for (var line = 0; line < lines.length; line++) { console.log(lines[line]); } }; reader.readAsText(file); };</script> <input type="file" name="file" id="file"> <div id='output'> ...</div> (Source: https://stackoverflow.com/questions/23331546/how-to-use-javascript-to-read-local-text-file-and-read-line-by-line ) ______________________________ Display random line from file: --------------------------------------------- <!DOCTYPE html> <!--https://run.plnkr.co/plunks/6wCVvXqoIGl6hN6Q--> <html> <head> <meta charset="utf-8" /> <!--<link rel="stylesheet" href="style.css" />--> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script> <script> var lines; var randomNumber; var lastRandomNumber; $(document.body).ready(function () { // load the trivia from the server $.ajax({ url: 'https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/Dayquotes.html' }).done(function(content) { // normalize the line breaks, then split into lines lines = content.replace(/\r\n|\r/g, '\n').trim().split('\n'); // only set up the click handler if there were lines found if (lines && lines.length) { $('#showLine').on('click', function () { // loop to prevent repeating the last random number while (randomNumber === lastRandomNumber) { randomNumber = parseInt(Math.random() * lines.length); // check to prevent infinite loop if (lines.length === 1) { break; } } // keep track of the last random number lastRandomNumber = randomNumber; // show the corresponding line $('#trivia').text(lines[randomNumber]); }); } }); }); </script> </head> <body><button id="showLine">Random!</button> <p id="trivia"></p></body> </html> (Source: https://jsfiddle.net/aoikurayami/1yhjods7/ ) _______________________ Random Line from file (autoclick + linksupport) -------------------------------------------- <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script> var lines, randomNumber, lastRandomNumber; $(document.body).ready(function() { $.ajax({url: "https://alceawis.de/Daymusic_array.js"}).done(function(e) { lines = e.replace(/\r\n|\r/g, "\n").trim().split("\n"); if (lines.length) { $("#showLine").on("click", function() { for (; randomNumber === lastRandomNumber && (randomNumber = parseInt(Math.random() * lines.length), 1 !== lines.length);); lastRandomNumber = randomNumber; var line = lines[randomNumber].replace("{'quote': '", "").replace("'},", ""); $("#trivia").html(line); }).trigger("click"); document.getElementById("showLine").style.visibility = "hidden"; } }); }); </script> <button id="showLine">CurrListening</button> <p id="trivia"></p> (source: https://stackoverflow.com/questions/75279944/hiding-button-by-id-works-but-clicking-it-not/75279960#75279944) ______________________ Add background behind text element: ---------------------------------- <style>h39 { display: table; font-size:20px;background-color:white;}</style> <h39><p id="trivia"></p></h39> (Source: https://stackoverflow.com/questions/14310154/how-do-i-set-a-background-color-for-the-width-of-text-not-the-width-of-the-enti ) ______________________ Prevent accidental page reload: ----------------------------------------- window.onbeforeunload = function() { return "Are you sure you want to leave? Think of the kittens!"; } __________________________ Move / interact (with element),based on screen orientation: ------------------------------------------ <script>if(window.innerHeight > window.innerWidth){ document.write('<div id="bigdiv2" style="position:relative; top: 70px; left: -410px;">');} </script> _____________________________ Url with anti-cache string --------------------------------- <a href="javascript:cachereload()">LATEST</a> <script> function cachereload() { const url = new URL('http://alceawisteria.byethost7.com/PHP/0demo/2023-03-25-BoxNet/BoxCOMArt.php'); url.searchParams.set('reloadTime', Date.now().toString()); let a= document.createElement('a'); a.target= '_blank'; a.href= url.toString(); a.click(); } </script> iframe http: (only on localhost...) 1) Create file on https, with redirect redirect-to-http.html: <script> document.location.href ="http://alceawisteria.byethost7.com/PHP/0demo/2023-03-25-BoxNet/yourpage.html"; </script> 2) iframe it <iframe src="redirect-to-http.html"></iframe> (Source: https://stackoverflow.com/questions/18327314/how-to-allow-http-content-within-an-iframe-on-a-https-site ) _____________________________________ Render site in ajax popup: ------------------------------- <script src="https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/jquery.min.js"></script> <script> $.ajax({ url : "https://ry3yr.github.io/OSTR/Diarykeepers_Homepage/index.html", success : function(result){ alert(result); } }); </script> (Source: https://stackoverflow.com/questions/8197709/javascript-read-html-from-url-into-string ) _______________________________ Render Site via js xmlhttprequest fetch ------------------------------------------ <script> var req = new XMLHttpRequest(); req.open('GET', 'https://ry3yr.github.io', false); req.send(null); if(req.status == 200) //alert(req.responseText); document.write(req.responseText); </script> (Source: https://stackoverflow.com/questions/5299646/javascript-how-to-fetch-the-content-of-a-web-page ) ___________________________________________________ Self unlocking -truly encrypted html page-: ------------------------------------------------- 1) Encrypt html via: https://www.maxlaumeister.com/pagecrypt/ 2) Add to html beginning: Autosubmit: <script> window.onload = function(){ document.getElementById('submitPass').click(); var scriptTag = document.createElement("script"); scriptTag.src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"; document.getElementsByTagName("head")[0].appendChild(scriptTag); } </script> 3) replace in html Autopass (replace in xx-protected.html) <input id="pass" type="password" name="pass" value="1234" autofocus> _______________________________ HTML5 Audioplayer / Musicplayer ------------------------------------------------------ (with playlist) https://codepen.io/craigstroman/pen/aOyRYx