Blog

  • Is $60,000 Enough to Live in Brampton? (October 2026 Data)

    Is $60,000 Enough to Live in Brampton? (October 2026 Data)

    The short answer

    Yes — $60,000 is enough to live in Brampton, but housing will take a big bite. An average 1-bedroom rents for $1,778 a month, which is about 42% of your take-home pay. That leaves roughly $2,435 a month for everything else: food, transport, bills, and savings. It’s workable for a single person, tight for a family.

    What $60,000 leaves you after tax in Ontario

    • Gross: $60,000/year
    • Estimated take-home: ~$50,552/year (~$4,213/month) after federal and Ontario income tax (2025 tax year)
    • Note: estimate for a single filer with no dependents; actual take-home varies with RRSP contributions, benefits, and credits.
    Monthly budget split: $1,778 average 1-bedroom rent vs $2,435 remaining from $4,213 take-home pay in Brampton

    Rent in Brampton right now

    Average asking rents in Brampton (CMHC, October 2025):

    • Studio: $1,430/month
    • 1 bedroom: $1,778/month
    • 2 bedroom: $2,032/month

    Brampton rents run noticeably below Toronto proper, which is the main reason the city works on a $60,000 salary. A 2-bedroom at $2,032 would take 48% of take-home — doable, but it leaves little breathing room.

    Average rent in Brampton by bedroom type, CMHC October 2025

    What people actually earn around here

    Median wages in the Toronto economic region (Job Bank, 2025 release), converted to yearly:

    • Cashiers: ~$36,608/year
    • Cooks: ~$37,440/year
    • Customer service representatives: ~$46,800/year
    • Transport truck drivers: ~$52,208/year
    • Licensed practical nurses: ~$64,480/year
    • Registered nurses: ~$84,427/year

    At $60,000 you’re earning above the median for service, retail, and driving jobs in the region — roughly in line with skilled trades and practical nursing.

    Median yearly wages for common jobs in the Toronto region vs $60,000, Job Bank 2025

    The monthly math

    $/month
    Take-home pay$4,213
    Average 1-bed rent$1,778
    Remaining for everything else$2,435

    Financial advisors often suggest keeping housing under 30–35% of gross income. At 42% of take-home on a 1-bedroom, you’re above that guideline — the classic Brampton trade-off: cheaper than Toronto, but still a stretch on a single $60k income. A studio at $1,430 (34% of take-home) fits the guideline comfortably.

    What changed since last month

    This is the first edition of this guide (October 2026 baseline). CMHC and Job Bank release annually, so these figures hold until the next releases — expected fall/winter 2026. We’ll update the comparison here when new data lands.

    Where our data comes from

    We publish our sources so you can check our math:

    Charts on this page are our own, drawn from the sources above. Figures were current as of October 2026; sources revise their data periodically.

  • AMD Ryzen AI Halo (PRO 495): Specs, Price, Release Date & Everything We Know

    AMD Ryzen AI Halo (PRO 495): Specs, Price, Release Date & Everything We Know

    AMD has unveiled the next version of its Ryzen AI Halo Developer Platform, a compact desktop built for running serious AI workloads locally. Powered by the new Ryzen AI Max+ PRO 495 processor and packing up to 192GB of unified memory, it is designed to run 300-billion-parameter language models entirely on your desk — no cloud required. Here is everything we know.

    What Is the Ryzen AI Halo Developer Platform?

    The Ryzen AI Halo is not a traditional desktop PC. It is a compact developer workstation — roughly the size of a small mini-PC — purpose-built for building, testing, and running AI models locally. The first version arrived earlier this year with the Ryzen AI Max+ 395 chip, 128GB of memory, and a $3,999 price tag, rated for models up to 200 billion parameters.

    The new version takes a big step up: the Ryzen AI Max+ PRO 495 chip, up to 192GB of LPDDR5x unified memory, and support for 300-billion-parameter models and larger at 4-bit quantization. It is aimed squarely at AI developers, researchers, and creators who want datacenter-class local AI without renting cloud GPUs.

    Ryzen AI Max+ PRO 495: Full Specifications

    The heart of the platform is the Ryzen AI Max+ PRO 495, which combines three processors on a single chip sharing one memory pool:

    • CPU: Up to 16 Zen 5 cores (32 threads), boosting up to 5.2GHz from a 3.1GHz base
    • GPU: 40 AMD RDNA 3.5 compute units (Radeon 8065S integrated graphics)
    • NPU: XDNA 2 neural processor rated up to 55 TOPS
    • Memory: Up to 192GB LPDDR5x unified memory (up to 160GB allocatable as VRAM)
    • Storage: 2TB SSD
    • Wireless: Wi-Fi 7, Bluetooth 5.4
    • FP16 AI compute: Up to 61 TFLOPS

    AMD notes that the 55 TOPS NPU figure is a peak rating under optimal conditions, and real-world throughput varies with system configuration, model, and software version.

    192GB of Unified Memory: Why It Matters for Local AI

    The headline feature is the memory. In a normal PC, system RAM and graphics memory are separate pools, and large AI models have to squeeze into whichever is smaller. The Ryzen AI Max+ PRO 495 puts the CPU, GPU, and NPU on a single high-bandwidth memory pool — up to 192GB of it, with as much as 160GB assignable to the GPU as VRAM.

    That changes what fits on a desk. AMD says the platform can hold 300-billion-parameter language models at 4-bit quantization entirely in memory, alongside datasets, applications, and working context. Developers can also run multiple large AI agents concurrently, each with its own KV cache — something that simply does not fit on systems with smaller GPU memory.

    Built for AI Agents, Not Just Chatbots

    AMD is positioning the platform around the shift from chatbots to AI agents — software that reads files, calls tools, coordinates parallel processes, and produces finished deliverables like decks, memos, and charts. In that setup, each part of the chip has a job: the CPU orchestrates the agent’s tools and processes, the GPU and NPU accelerate model inference and content generation, and the unified memory keeps models and working data loaded for all three.

    AMD argues that raw tokens-per-second still matters, but for agent workloads, orchestration, memory capacity, and end-to-end task completion matter just as much.

    Generative AI Performance: AMD’s Claims vs Intel and Apple

    AMD tested the PRO 495 on native x86 Windows with ROCm software across 18 image, video, music, and 3D generation workloads — including FLUX.2, Qwen Image, Ideogram V4, and Stable Music — and published comparisons against two rivals:

    • Vs Intel Core Ultra X9 388H: Higher relative throughput across all 18 workloads, with a top result of 32.2x.
    • Vs Apple M5 Pro: Higher throughput across the workloads, with a top result of 34.3x. AMD notes several workloads did not run on the Apple system at all.

    Two caveats worth knowing: these are AMD’s own tests from September 2026, not independent reviews, and the comparison systems carried 64GB of memory against the Halo’s 192GB — a gap that decides which workloads can even run on each machine.

    Software: Ryzen AI Developer Center and AI Playbooks

    Hardware is only half the story. AMD bundles a software layer meant to take developers from first boot to a running model with minimal setup:

    • Ryzen AI Developer Center: A single app for software, platform controls, features, and updates, with remote access and software sync.
    • Preloaded apps and models: Installed out of the box so testing can start immediately.
    • AI Playbooks: Guided paths through supported AI workflows.
    • Best-Known Configuration: AMD validates ROCm software, drivers, frameworks, applications, and models together as one set, restorable through the Developer Center.

    Windows-based versions will ship with Microsoft’s Windows Project Zenith, a ready-to-code Windows experience with VS Code, WSL, GitHub Copilot CLI, and PowerShell preinstalled.

    Price and Availability

    The Ryzen AI Halo Developer Platform with the Ryzen AI Max+ PRO 495 is available to order now from Micro Center in the US at $7,299.99 — $3,300 more than the first-generation 128GB model. Note that Micro Center lists it as in-store pickup only, with no shipping. Windows Project Zenith versions are coming soon. AMD has not announced pricing for other regions.

    Ryzen AI Halo vs NVIDIA RTX Spark: The Local AI PC Race

    The timing is no coincidence. NVIDIA just opened pre-orders for its RTX Spark AI PCs — laptops and compact desktops with up to 128GB of unified memory starting at $2,599. AMD’s answer is a more expensive, more memory-heavy developer box: 192GB vs 128GB, $7,299 vs $2,599.

    The two target different buyers. RTX Spark is a consumer-and-creator platform that also plays AAA games; the Ryzen AI Halo is a developer-first machine for people whose models genuinely need 160GB of VRAM. But together they mark the same turning point: 2026 is the year serious AI moved from the cloud to the desk.

    Should You Buy It?

    The Ryzen AI Halo makes sense for a narrow audience: AI developers and researchers running very large local models or multi-agent workflows who would otherwise pay for cloud GPU time. For everyone else — including most creators — the $7,299 price and developer focus make it a tough sell against cheaper local-AI options.

    Still, as a statement of where the industry is going, it matters. Unified memory architectures are rapidly becoming the default for local AI, and AMD just raised the bar for how much of it you can get in a box that sits quietly on a desk.

  • NVIDIA RTX Spark AI PCs: Specs, Price, Release Date & Everything We Know

    NVIDIA RTX Spark AI PCs: Specs, Price, Release Date & Everything We Know

    NVIDIA and Microsoft have officially kicked off a new era of AI computing. Their joint platform, NVIDIA RTX Spark, puts datacenter-class AI power into laptops and compact desktops — and the first machines hit store shelves on October 16, 2026. Preorders opened October 7, with six PC makers shipping RTX Spark hardware at launch, a scale no AI-focused chip launch has reached before.

    But what exactly is RTX Spark, and should you care? Here’s everything we know so far.

    What Is NVIDIA RTX Spark?

    RTX Spark is a new chip platform co-developed by NVIDIA and Microsoft, built around one idea: running powerful AI models locally on your PC instead of sending every request to a cloud datacenter.

    NVIDIA calls it a two-part platform. RTX Spark itself is the hardware half — a CPU-plus-GPU “superchip.” Microsoft provides the software half, called Microsoft Execution Containers (MXC), which is baked into Windows 11 and manages how AI workloads run on the machine.

    The pitch is simple: chatbots, coding assistants, image generators, and autonomous AI agents running entirely on your laptop — no subscription, no latency, no data leaving your device.

    RTX Spark Specs: What’s Inside the Superchip

    The numbers NVIDIA has shared are serious:

    • GPU: Blackwell-generation RTX graphics with up to 6,144 CUDA cores
    • CPU: 20-core NVIDIA Grace processor based on the Arm architecture (a mix of Cortex-X925 and Cortex-A725 cores)
    • Interconnect: NVLink-C2C chip-to-chip link instead of a conventional PCIe connection
    • Memory: up to 128GB of unified memory shared between the CPU and GPU
    • AI acceleration: fifth-generation Tensor Cores with FP4 precision support
    • Claimed performance: up to 1 petaflop of AI compute

    The standout spec is the unified memory. In a normal PC, the CPU and GPU have separate memory pools, and large AI models get split awkwardly between them. RTX Spark’s shared 128GB pool means a big language model can sit in one place, accessible to both processors at full speed.

    Microsoft’s Half: Execution Containers

    Hardware is only half the story. Microsoft Execution Containers (MXC) is the software layer in Windows 11 that schedules and sandboxes AI workloads on RTX Spark machines.

    Think of it as the traffic controller: it decides which parts of an AI task run on the GPU’s Tensor Cores, which run on the CPU, and how memory gets allocated — all without the user thinking about it. For developers, it means AI apps can target one consistent platform instead of wrestling with drivers and frameworks per machine.

    Which RTX Spark PCs Can You Buy?

    Six PC makers are shipping RTX Spark systems at launch. The two with the most detail so far:

    Dell XPS 16 Creator Edition — Dell’s flagship creator laptop, rebuilt around a 20-core NVIDIA N1X processor (the top of the RTX Spark stack). It pairs the chip with a 3.2K OLED display running at 120Hz and a 99Wh battery. Preorders are open now at a starting price of $3,799.99, with shipments beginning October 16, 2026. Notably, this is the first Dell XPS ever built on NVIDIA silicon instead of Intel or AMD.

    HP RTX Spark AI PCs — HP unveiled its RTX Spark lineup alongside the ZGX Fury GB300 workstation, with Perplexity AI integration built in. Pricing and exact configurations are still rolling out.

    Expect the remaining makers to announce their models through the launch window.

    Battery Life: The Big Claim

    NVIDIA makes one more bold promise: RTX Spark laptops are designed to hold full AI and graphics performance while unplugged, with all-day battery life.

    If true, that fixes one of the oldest complaints about powerful laptops — that they throttle hard the moment you leave the charger. But it’s worth noting this claim has not yet been independently benchmarked by reviewers. Take it as a target, not a guarantee, until third-party tests land.

    Who Is RTX Spark Actually For?

    Let’s be honest: most people don’t need a petaflop on their lap. RTX Spark makes the most sense for:

    • Developers building and testing AI agents and apps locally
    • Creators running image and video generation models without cloud fees
    • Researchers and students working with large open models
    • Privacy-conscious users who want AI that never phones home

    If your heaviest workload is a browser with forty tabs, a conventional ultrabook remains the smarter buy — and far cheaper.

    RTX Spark Price and Release Date

    • Preorders opened: October 7, 2026
    • Retail availability: October 16, 2026
    • Dell XPS 16 Creator Edition: from $3,799.99
    • HP models: pricing to be confirmed
    • Form factors: laptops and compact desktops, all running Windows 11

    Should You Wait for RTX Spark?

    If you run AI workloads daily and you’re shopping for a new machine this fall, RTX Spark is worth a serious look — it’s the first platform genuinely designed around local AI rather than bolting an NPU onto a standard laptop. If you don’t, there’s no reason to pay the early-adopter premium; prices will only come down as more makers join in.

    Either way, October 16 is the date to watch. That’s when the first independent benchmarks should start appearing — and benchmarks, not launch slides, will tell us whether RTX Spark delivers on its petaflop promise.


  • Bulk image search and download from free image websites

    Free Asset Downloader: A Single-File Tool for Grabbing Images, Music & SFX

    ToolsWeb DevContent Creation

    I Built a Free Asset Downloader for Images, Music & SFX (No Sign-ups Needed)

    A single self-contained HTML page that searches multiple free stock-asset APIs at once, lets you preview results, and batch-downloads whatever you pick — all client-side, no backend, no account required.

    Why I Made This

    Every time I needed a quick background photo, a bit of royalty-free music, or a UI sound effect for a project, I found myself bouncing between five different websites, creating accounts, and copy-pasting URLs. So I put together a small tool that does the searching for me: one search box, multiple free sources queried in parallel, and instant previews before you commit to downloading anything.

    What It Does

    • Multi-source search — searches several free, largely keyless APIs at once and merges the results into one grid.
    • Comma-separated queries — type dog, cat, fish and it runs each term as its own search, so you get a batch of unrelated assets in one go.
    • Live previews — play audio clips right in the results list before downloading, and see image thumbnails up front.
    • One-click and batch download — grab a single asset or select a pile of them and download everything at once.
    • Runs entirely in the browser — it’s one HTML file. No server, no build step, no install.

    Sources It Pulls From

    TypeSourceAPI key needed?
    ImagesOpenverse (CC-licensed photos & illustrations)No
    ImagesPixabay / PexelsOptional, for higher volume
    MusicWikimedia Commons AudioNo
    SFXWikimedia Commons AudioNo
    Audio (fallback)Bensound / Mixkit curated samplesNo
    Audio (optional)FreesoundOptional
    Note on matching: some sources (like Bensound/Mixkit) are small curated packs rather than real search engines, so they only appear as a labeled fallback when a query doesn’t match anything from the real search APIs — that way you always know whether a result actually matches what you searched for.

    How to Use It

    1. Open the HTML file in any browser — no installation needed.
    2. Pick a type: image, music, or SFX.
    3. Type one search term, or several separated by commas.
    4. Preview results, then download individually or in bulk.

    The Code

    It’s a single self-contained HTML file — just save it as asset_downloader.html and open it in your browser. No dependencies, no build tools.

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Asset Downloader</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
    <style>
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,sans-serif;background:#0f1117;color:#e2e8f0;min-height:100vh}
    .app{max-width:1200px;margin:0 auto;padding:24px}
    h1{font-size:22px;font-weight:600;margin-bottom:4px;color:#f1f5f9}
    .subtitle{font-size:13px;color:#64748b;margin-bottom:24px}
    .search-panel{background:#1a1d27;border:1px solid #2d3148;border-radius:12px;padding:20px;margin-bottom:16px}
    .row{display:flex;gap:10px;margin-bottom:12px;flex-wrap:wrap;align-items:flex-start}
    .row:last-child{margin-bottom:0}
    textarea{flex:1;min-width:200px;background:#0f1117;border:1px solid #2d3148;border-radius:8px;color:#e2e8f0;padding:10px;font-size:13px;resize:vertical;min-height:52px;font-family:inherit}
    textarea::placeholder{color:#475569}
    textarea:focus{outline:none;border-color:#6366f1}
    select{background:#0f1117;border:1px solid #2d3148;border-radius:8px;color:#e2e8f0;padding:9px 12px;font-size:13px;cursor:pointer;height:40px}
    select:focus{outline:none;border-color:#6366f1}
    button{border-radius:8px;padding:9px 18px;font-size:13px;font-weight:500;cursor:pointer;border:none;transition:all .15s}
    .btn-primary{background:#6366f1;color:#fff;height:40px}
    .btn-primary:hover{background:#5457d4}
    .btn-primary:disabled{background:#2d3148;color:#475569;cursor:not-allowed}
    .btn-ghost{background:transparent;color:#94a3b8;border:1px solid #2d3148}
    .btn-ghost:hover{background:#1e2235;color:#e2e8f0}
    .btn-success{background:#065f46;color:#6ee7b7}
    .btn-success:hover{background:#047857}
    .api-section{background:#1a1d27;border:1px solid #2d3148;border-radius:12px;padding:16px;margin-bottom:16px}
    .api-toggle{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;color:#94a3b8;user-select:none}
    .api-toggle:hover{color:#e2e8f0}
    .api-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px;margin-top:14px}
    .api-field label{font-size:11px;color:#64748b;display:block;margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px}
    .api-field input{width:100%;background:#0f1117;border:1px solid #2d3148;border-radius:6px;color:#e2e8f0;padding:8px 10px;font-size:12px;font-family:monospace}
    .api-field input:focus{outline:none;border-color:#6366f1}
    .api-field input::placeholder{color:#334155}
    .status-bar{background:#1a1d27;border:1px solid #2d3148;border-radius:8px;padding:10px 14px;margin-bottom:14px;font-size:12px;color:#94a3b8;display:flex;align-items:center;gap:8px}
    .dot{width:8px;height:8px;border-radius:50%;background:#334155;flex-shrink:0}
    .dot.loading{background:#f59e0b;animation:pulse 1s infinite}
    .dot.done{background:#10b981}
    .dot.error{background:#ef4444}
    .results-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:wrap;gap:8px}
    .results-header h2{font-size:15px;font-weight:500;color:#94a3b8}
    .selection-actions{display:flex;gap:8px;align-items:center}
    .count-badge{background:#1e2235;border:1px solid #2d3148;border-radius:6px;padding:3px 10px;font-size:12px;color:#94a3b8}
    .results-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px;margin-bottom:100px}
    .card{background:#1a1d27;border:1px solid #2d3148;border-radius:10px;overflow:hidden;cursor:pointer;transition:border-color .15s,transform .1s;position:relative;display:flex;flex-direction:column}
    .card:hover{border-color:#6366f1;transform:translateY(-1px)}
    .card.selected{border-color:#6366f1;background:#1b1c36}
    .card-check{position:absolute;top:8px;right:8px;width:22px;height:22px;background:#6366f1;border-radius:5px;display:none;align-items:center;justify-content:center;font-size:13px;color:#fff;font-weight:700;z-index:5;pointer-events:none}
    .card.selected .card-check{display:flex}
    .img-wrap{width:100%;height:130px;background:#12111a;overflow:hidden;position:relative;flex-shrink:0}
    .img-wrap img{width:100%;height:100%;object-fit:cover;display:block;opacity:0;transition:opacity .4s}
    .img-wrap img.loaded{opacity:1}
    .img-placeholder{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:30px;color:#2d3148}
    /* PNG preview: checkerboard bg to show transparency */
    .png-wrap{width:100%;height:130px;background:
      repeating-conic-gradient(#1e1e2e 0% 25%,#2a2a3e 0% 50%)
      0 0/20px 20px;display:flex;align-items:center;justify-content:center;overflow:hidden;position:relative;flex-shrink:0}
    .png-wrap img{max-width:90%;max-height:110px;object-fit:contain;opacity:0;transition:opacity .3s}
    .png-wrap img.loaded{opacity:1}
    .icon-wrap{width:100%;height:130px;background:#12111a;display:flex;align-items:center;justify-content:center;flex-shrink:0}
    .icon-wrap img{width:72px;height:72px;object-fit:contain;opacity:0;transition:opacity .3s}
    .icon-wrap img.loaded{opacity:1}
    .audio-wrap{width:100%;height:130px;background:#12111a;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;flex-shrink:0}
    .play-btn{width:52px;height:52px;border-radius:50%;background:#6366f1;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s,transform .1s}
    .play-btn:hover{background:#5457d4;transform:scale(1.06)}
    .play-btn:active{transform:scale(.95)}
    .play-icon{width:22px;height:22px;fill:#fff;margin-left:3px}
    .pause-icon{width:22px;height:22px;fill:#fff}
    .audio-dur{font-size:11px;color:#475569}
    .prog-bar{width:80%;height:4px;background:#2d3148;border-radius:2px;cursor:pointer;overflow:hidden}
    .prog-inner{height:100%;background:#6366f1;width:0%;pointer-events:none;transition:width .15s linear}
    .card-info{padding:10px;flex:1}
    .card-title{font-size:12px;font-weight:500;color:#e2e8f0;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .card-meta{font-size:11px;color:#475569}
    .card-source{display:inline-block;font-size:10px;background:#1e2235;border:1px solid #2d3148;border-radius:4px;padding:1px 6px;color:#64748b;margin-top:4px}
    /* PNG source badge – distinct purple tint */
    .card-source.png{background:#1e1b36;border-color:#4338a0;color:#a5b4fc}
    .tag-pills{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
    .pill{font-size:11px;padding:3px 10px;border-radius:20px;background:#1e2235;border:1px solid #2d3148;color:#64748b;cursor:pointer}
    .pill:hover{border-color:#6366f1;color:#a5b4fc}
    .error-msg{background:#1a0a0a;border:1px solid #7f1d1d;border-radius:8px;padding:10px 14px;font-size:12px;color:#fca5a5;margin-bottom:12px}
    .dl-bar{position:fixed;bottom:0;left:0;right:0;background:#0f1117;border-top:1px solid #2d3148;padding:14px 24px;display:flex;gap:10px;align-items:center;justify-content:flex-end;z-index:100}
    .dl-bar.hidden{display:none}
    .empty{text-align:center;padding:60px 20px;color:#475569;grid-column:1/-1}
    @keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
    </style>
    </head>
    <body>
    <div class="app">
      <h1>🎨 Asset Downloader</h1>
      <p class="subtitle">Search images, PNGs, icons, music &amp; SFX from free sources. Multi-select → download as ZIP or save to folder.</p>
    
      <div class="search-panel">
        <div class="row">
          <textarea id="searchInput" placeholder='Comma-separated: fire, water, tech&#10;Or JSON: [{"query":"fire","type":"sfx"},{"query":"arrow","type":"png"}]' rows="2"></textarea>
          <select id="typeFilter">
            <option value="all">All types</option>
            <option value="image">🖼 Images</option>
            <option value="png">🟣 PNG (transparent)</option>
            <option value="icon">🔷 Icons</option>
            <option value="music">🎵 Music</option>
            <option value="sfx">🔊 SFX</option>
          </select>
          <button class="btn-primary" onclick="runSearch()" id="searchBtn">Search</button>
        </div>
        <div class="tag-pills" id="recentTags"></div>
      </div>
    
      <div class="api-section">
        <div class="api-toggle" onclick="toggleApi()">
          <span id="apiArrow">▶</span>
          <span>API keys — optional, enables more sources &amp; results</span>
        </div>
        <div id="apiPanel" style="display:none">
          <div class="api-grid">
            <div class="api-field"><label>Unsplash Access Key</label><input type="password" id="keyUnsplash" placeholder="paste key..."></div>
            <div class="api-field"><label>Pexels API Key</label><input type="password" id="keyPexels" placeholder="paste key..."></div>
            <div class="api-field"><label>Pixabay API Key</label><input type="password" id="keyPixabay" placeholder="paste key... (also used for PNGs)"></div>
            <div class="api-field"><label>Freesound API Key</label><input type="password" id="keyFreesound" placeholder="paste key..."></div>
          </div>
          <p style="font-size:11px;color:#475569;margin-top:10px">Keys stored in localStorage only. Never sent to any server. App always falls back to no-key sources.</p>
        </div>
      </div>
    
      <!-- PNG Sources info box -->
      <div style="background:#12111a;border:1px solid #2d3148;border-radius:10px;padding:14px 18px;margin-bottom:16px;font-size:12px;color:#64748b;line-height:1.7">
        <span style="color:#a5b4fc;font-weight:600">🟣 Free PNG Sources</span> &nbsp;·&nbsp;
        <strong style="color:#94a3b8">OpenClipart</strong> — free API, no key needed, clipart &amp; illustrations &nbsp;·&nbsp;
        <strong style="color:#94a3b8">Wikimedia Commons</strong> — free API, no key needed, huge transparent PNG library &nbsp;·&nbsp;
        <strong style="color:#94a3b8">Pixabay PNG filter</strong> — requires Pixabay API key above, high-quality photos + cutouts &nbsp;·&nbsp;
        <strong style="color:#94a3b8">Sticker.guru</strong> — free, no key, sticker-style PNGs
      </div>
      <div style="background:#12111a;border:1px solid #2d3148;border-radius:10px;padding:14px 18px;margin-bottom:16px;font-size:12px;color:#64748b;line-height:1.7">
        <span style="color:#6ee7b7;font-weight:600">🎵 Free Audio &amp; 🖼 Extra Image Sources</span> &nbsp;·&nbsp;
        <strong style="color:#94a3b8">Openverse</strong> — free API, no key needed, CC-licensed photos &amp; illustrations, query-matched &nbsp;·&nbsp;
        <strong style="color:#94a3b8">Wikimedia Commons Audio</strong> — free API, no key needed, real query-matched music &amp; SFX &nbsp;·&nbsp;
        <strong style="color:#94a3b8">Bensound / Mixkit samples</strong> — curated fallback, only used (and labeled) when no query match is found
      </div>
    
      <div id="statusBar" class="status-bar" style="display:none">
        <div class="dot" id="statusDot"></div>
        <span id="statusText">Searching...</span>
      </div>
      <div id="errorMsg" class="error-msg" style="display:none"></div>
    
      <div id="resultsSection" style="display:none">
        <div class="results-header">
          <h2 id="resultsCount">0 results</h2>
          <div class="selection-actions">
            <span class="count-badge" id="selectedCount">0 selected</span>
            <button class="btn-ghost" style="padding:5px 12px;font-size:12px" onclick="selectAll()">Select all</button>
            <button class="btn-ghost" style="padding:5px 12px;font-size:12px" onclick="clearSel()">Clear</button>
          </div>
        </div>
        <div class="results-grid" id="resultsGrid"></div>
      </div>
    
      <div class="dl-bar hidden" id="dlBar">
        <span style="font-size:13px;color:#94a3b8" id="dlCount">0 selected</span>
        <button class="btn-success" onclick="downloadZip()">⬇ Download ZIP</button>
        <button class="btn-ghost" onclick="saveToFolder()">📁 Save to Folder</button>
      </div>
    </div>
    
    <script>
    let results=[], selected=new Set(), activeAudio=null, activePlayBtn=null, activeIdx=-1;
    let recent=JSON.parse(localStorage.getItem('adl_recent')||'[]');
    
    function gk(k){return localStorage.getItem('adl_'+k)||document.getElementById(k)?.value?.trim()||''}
    function saveKeys(){['keyUnsplash','keyPexels','keyPixabay','keyFreesound'].forEach(k=>{const v=document.getElementById(k).value.trim();if(v)localStorage.setItem('adl_'+k,v)})}
    function loadKeys(){['keyUnsplash','keyPexels','keyPixabay','keyFreesound'].forEach(k=>{const v=localStorage.getItem('adl_'+k);if(v)document.getElementById(k).value=v})}
    
    let apiOpen=false;
    function toggleApi(){
      apiOpen=!apiOpen;
      document.getElementById('apiPanel').style.display=apiOpen?'block':'none';
      document.getElementById('apiArrow').textContent=apiOpen?'▼':'▶';
    }
    
    function parseInput(raw){
      raw=raw.trim();
      if(raw.startsWith('[')){try{return JSON.parse(raw)}catch(e){}}
      const t=document.getElementById('typeFilter').value;
      return raw.split(',').map(q=>q.trim()).filter(Boolean).map(q=>({query:q,type:t}));
    }
    
    function setStatus(state,msg){
      document.getElementById('statusBar').style.display='flex';
      document.getElementById('statusText').textContent=msg;
      document.getElementById('statusDot').className='dot '+(state==='loading'?'loading':state==='done'?'done':'error');
    }
    function showErr(m){const e=document.getElementById('errorMsg');e.style.display='block';e.textContent=m}
    function hideErr(){document.getElementById('errorMsg').style.display='none'}
    
    async function runSearch(){
      const raw=document.getElementById('searchInput').value;
      if(!raw.trim()){showErr('Enter search terms.');return}
      saveKeys();hideErr();stopAudio();
      results=[];selected=new Set();
      document.getElementById('resultsSection').style.display='none';
      document.getElementById('dlBar').classList.add('hidden');
      setStatus('loading','Searching...');
      document.getElementById('searchBtn').disabled=true;
      addRecent(raw);
      const queries=parseInput(raw);
      let all=[];
      for(const q of queries){
        const types=(!q.type||q.type==='all')?['image','png','icon','music','sfx']:[q.type];
        for(const t of types){
          try{const r=await fetchType(q.query,t);all=all.concat(r)}catch(e){console.warn(t,e)}
        }
      }
      results=all;
      renderResults();
      document.getElementById('searchBtn').disabled=false;
      setStatus('done',results.length+' assets found');
    }
    
    async function fetchType(q,type){
      let items=[];
      if(type==='image') items=await fetchImages(q);
      else if(type==='png')   items=await fetchPNGs(q);
      else if(type==='icon')  items=await fetchIcons(q);
      else if(type==='music'||type==='sfx') items=await fetchAudio(q,type);
      /* Rename every file to just the search query + counter.
         e.g. query "apple red" → apple_red.jpg, apple_red_2.jpg, apple_red_3.jpg */
      const base=san(q);
      items.forEach((item,i)=>{
        const ext=item.ext||'jpg';
        item.filename=i===0?`${base}.${ext}`:`${base}_${i+1}.${ext}`;
      });
      return items;
    }
    
    /* ── IMAGES ─────────────────────────── */
    async function fetchImages(q){
      const items=[];
      /* Openverse – free, no key, huge CC-licensed photo/illustration search that
         actually matches the query text. This is now the primary keyless source. */
      try{
        const d=await apiFetch(`https://api.openverse.org/v1/images/?q=${enc(q)}&page_size=12`);
        (d.results||[]).forEach(h=>{
          const ext=(h.url.split('.').pop()||'jpg').split('?')[0].slice(0,4);
          items.push({id:'ov_'+h.id,title:h.title||q,thumb:h.thumbnail||h.url,downloadUrl:h.url,source:'Openverse',type:'image',ext,filename:san(h.title||q)+'_openverse_'+h.id+'.'+ext});
        });
      }catch(e){console.warn('Openverse',e)}
      const pxk=gk('keyPixabay');
      if(pxk){
        try{
          const d=await apiFetch(`https://pixabay.com/api/?key=${pxk}&q=${enc(q)}&image_type=photo&per_page=9&safesearch=true`);
          (d.hits||[]).forEach(h=>items.push({id:'px_'+h.id,title:h.tags||q,thumb:h.previewURL,downloadUrl:h.largeImageURL,source:'Pixabay',type:'image',ext:'jpg',filename:san(q)+'_pixabay_'+h.id+'.jpg'}));
        }catch(e){}
      }
      const usk=gk('keyUnsplash');
      if(usk){
        try{
          const d=await apiFetch(`https://api.unsplash.com/search/photos?query=${enc(q)}&per_page=9`,{Authorization:'Client-ID '+usk});
          (d.results||[]).forEach(h=>items.push({id:'us_'+h.id,title:h.alt_description||q,thumb:h.urls.small,downloadUrl:h.urls.regular,source:'Unsplash',type:'image',ext:'jpg',filename:san(q)+'_unsplash_'+h.id+'.jpg'}));
        }catch(e){}
      }
      const pxlk=gk('keyPexels');
      if(pxlk){
        try{
          const d=await apiFetch(`https://api.pexels.com/v1/search?query=${enc(q)}&per_page=9`,{Authorization:pxlk});
          (d.photos||[]).forEach(h=>items.push({id:'pxl_'+h.id,title:h.alt||q,thumb:h.src.medium,downloadUrl:h.src.large,source:'Pexels',type:'image',ext:'jpg',filename:san(q)+'_pexels_'+h.id+'.jpg'}));
        }catch(e){}
      }
      /* Picsum is just random placeholder photos keyed by seed number — it does
         NOT search by keyword, so it never actually matches the query. Only use
         it as a last-resort filler when every real search above found nothing,
         and label it clearly so it's not mistaken for a real match. */
      if(items.length===0){
        const seed=encodeURIComponent(q);
        for(let i=0;i<6;i++){
          const s=seed+i;
          items.push({id:'picsum_'+s,title:q+' (random placeholder, not query-matched)',thumb:`https://picsum.photos/seed/${s}/400/300`,downloadUrl:`https://picsum.photos/seed/${s}/1200/900`,source:'Picsum (random)',type:'image',ext:'jpg',filename:san(q)+'_placeholder_'+i+'.jpg'});
        }
      }
      return items;
    }
    
    /* ── PNGs (transparent) ─────────────── */
    async function fetchPNGs(q){
      const items=[];
    
      /* 1. OpenClipart – free REST API, no key, SVG+PNG clipart */
      try{
        const d=await apiFetch(`https://openclipart.org/search/json/?query=${enc(q)}&amount=12&offset=0`);
        (d.payload||[]).forEach(clip=>{
          /* thumbnail is SVG-rendered PNG via their CDN */
          const thumb=clip.svg?.png_2400||clip.svg?.png_480||'';
          const dlUrl=clip.svg?.png_2400||clip.svg?.url||'';
          if(!thumb) return;
          items.push({
            id:'oc_'+clip.id,
            title:clip.title||q,
            thumb,
            downloadUrl:dlUrl,
            source:'OpenClipart',
            type:'png',
            ext: dlUrl.endsWith('.svg')?'svg':'png',
            filename:san(clip.title||q)+'_openclipart_'+clip.id+(dlUrl.endsWith('.svg')?'.svg':'.png'),
            isPng:true
          });
        });
      }catch(e){console.warn('OpenClipart',e)}
    
      /* 2. Wikimedia Commons – free MediaWiki API, no key */
      try{
        const url=`https://commons.wikimedia.org/w/api.php?action=query&generator=search&gsrnamespace=6&gsrsearch=${enc(q)}+filetype:png&gsrlimit=12&prop=imageinfo&iiprop=url|mime|size|thumburl&iiurlwidth=300&format=json&origin=*`;
        const d=await apiFetch(url);
        const pages=Object.values(d?.query?.pages||{});
        pages.forEach(p=>{
          const ii=p.imageinfo?.[0];
          if(!ii||ii.mime!=='image/png') return;
          items.push({
            id:'wm_'+p.pageid,
            title:p.title?.replace('File:','')||q,
            thumb:ii.thumburl||ii.url,
            downloadUrl:ii.url,
            source:'Wikimedia',
            type:'png',
            ext:'png',
            filename:san(p.title?.replace('File:','')||q)+'.png',
            isPng:true
          });
        });
      }catch(e){console.warn('Wikimedia',e)}
    
      /* 3. Pixabay PNG filter (transparent clipart/vector) – needs key */
      const pxk=gk('keyPixabay');
      if(pxk){
        try{
          const d=await apiFetch(`https://pixabay.com/api/?key=${pxk}&q=${enc(q)}&image_type=clipart&colors=transparent&per_page=10&safesearch=true`);
          (d.hits||[]).forEach(h=>{
            items.push({
              id:'pxpng_'+h.id,
              title:h.tags||q,
              thumb:h.previewURL,
              downloadUrl:h.largeImageURL,
              source:'Pixabay PNG',
              type:'png',
              ext:'png',
              filename:san(q)+'_pixabay_png_'+h.id+'.png',
              isPng:true
            });
          });
        }catch(e){console.warn('Pixabay PNG',e)}
      }
    
      /* 4. Sticker.guru – free, no-key, sticker PNG CDN via search */
      try{
        /* They expose a simple JSON endpoint used by their site */
        const d=await apiFetch(`https://sticker.guru/api/search?q=${enc(q)}&limit=8`);
        (d.results||d.stickers||[]).slice(0,8).forEach((s,i)=>{
          const thumb=s.thumbnail||s.url||s.image||'';
          const dlUrl=s.url||s.image||thumb;
          if(!thumb) return;
          items.push({
            id:'sg_'+i+'_'+q,
            title:s.title||q+' sticker',
            thumb,
            downloadUrl:dlUrl,
            source:'Sticker.guru',
            type:'png',
            ext:'png',
            filename:san(s.title||q)+'_sticker_'+i+'.png',
            isPng:true
          });
        });
      }catch(e){/* sticker.guru may 404 if endpoint changes – silent */}
    
      return items;
    }
    
    /* ── ICONS ─────────────────────────── */
    async function fetchIcons(q){
      const items=[];
      try{
        const d=await apiFetch(`https://api.iconify.design/search?query=${enc(q)}&limit=24`);
        (d.icons||[]).slice(0,20).forEach(icon=>{
          const [prefix,name]=icon.split(':');
          const thumbUrl=`https://api.iconify.design/${prefix}/${name}.svg?color=%236366f1`;
          const dlUrl=`https://api.iconify.design/${prefix}/${name}.svg`;
          items.push({id:'ify_'+icon,title:name+' ('+prefix+')',thumb:thumbUrl,downloadUrl:dlUrl,source:'Iconify',type:'icon',ext:'svg',filename:san(name)+'.svg'});
        });
      }catch(e){}
      return items;
    }
    
    /* ── AUDIO ─────────────────────────── */
    async function fetchAudio(q,subtype){
      const items=[];
      const fsk=gk('keyFreesound');
      if(fsk){
        try{
          const filter=subtype==='music'?'&filter=tag:music':'';
          const d=await apiFetch(`https://freesound.org/apiv2/search/text/?query=${enc(q)}${filter}&fields=id,name,previews,duration&token=${fsk}&page_size=10`);
          (d.results||[]).forEach(s=>{
            const prev=s.previews?.['preview-lq-mp3']||'';
            if(!prev)return;
            items.push({id:'fs_'+s.id,title:s.name,previewUrl:prev,downloadUrl:prev,source:'Freesound',type:subtype,ext:'mp3',duration:Math.round(s.duration),filename:san(s.name)+'.mp3'});
          });
        }catch(e){console.warn('Freesound',e)}
      }
    
      /* Wikimedia Commons audio – free, no key, and actually matches the query
         (searches ogg/mp3/wav/flac files whose title/description contains q) */
      try{
        const url=`https://commons.wikimedia.org/w/api.php?action=query&generator=search&gsrnamespace=6&gsrsearch=${enc(q)}+(filetype:ogg+OR+filetype:mp3+OR+filetype:wav+OR+filetype:flac)&gsrlimit=12&prop=imageinfo&iiprop=url|mime|size|duration&format=json&origin=*`;
        const d=await apiFetch(url);
        const pages=Object.values(d?.query?.pages||{});
        pages.forEach(p=>{
          const ii=p.imageinfo?.[0];
          if(!ii||!/^audio\//.test(ii.mime||'')) return;
          const ext=(ii.url.split('.').pop()||'mp3').toLowerCase();
          const title=p.title?.replace('File:','')||q;
          items.push({id:'wma_'+p.pageid,title,previewUrl:ii.url,downloadUrl:ii.url,source:'Wikimedia',type:subtype,ext,duration:ii.duration?Math.round(ii.duration):undefined,filename:san(title)+'.'+ext});
        });
      }catch(e){console.warn('Wikimedia audio',e)}
    
      /* Curated sample packs (Bensound/Mixkit) only kick in as a fallback when
         nothing above actually matched the query — and only if a track's own
         name loosely matches the search term, so they don't get mixed into
         unrelated queries. */
      if(items.length===0){
        const ql=q.toLowerCase();
        if(subtype==='music'){
          const tracks=[
            {t:'Ukulele',u:'https://www.bensound.com/bensound-music/bensound-ukulele.mp3'},
            {t:'Sunny',u:'https://www.bensound.com/bensound-music/bensound-sunny.mp3'},
            {t:'Acoustic Breeze',u:'https://www.bensound.com/bensound-music/bensound-acousticbreeze.mp3'},
            {t:'Creative Minds',u:'https://www.bensound.com/bensound-music/bensound-creativeminds.mp3'},
            {t:'Epic',u:'https://www.bensound.com/bensound-music/bensound-epic.mp3'},
          ];
          const matched=tracks.filter(t=>t.t.toLowerCase().includes(ql)||ql.includes(t.t.toLowerCase()));
          const pool=matched.length?matched:tracks;
          pool.forEach((t,i)=>items.push({id:'bs_'+i+'_'+san(q),title:'Bensound – '+t.t+(matched.length?'':' (sample, not query-matched)'),previewUrl:t.u,downloadUrl:t.u,source:'Bensound',type:'music',ext:'mp3',filename:'bensound_'+san(t.t)+'.mp3'}));
        }
        if(subtype==='sfx'){
          const sfx=[
            {t:'Notification Bell',u:'https://assets.mixkit.co/sfx/preview/mixkit-software-interface-start-2574.mp3'},
            {t:'Button Click',u:'https://assets.mixkit.co/sfx/preview/mixkit-select-click-1109.mp3'},
            {t:'Success Chime',u:'https://assets.mixkit.co/sfx/preview/mixkit-achievement-bell-600.mp3'},
            {t:'Alert Tone',u:'https://assets.mixkit.co/sfx/preview/mixkit-alarm-clock-beep-988.mp3'},
            {t:'Game Win',u:'https://assets.mixkit.co/sfx/preview/mixkit-winning-chimes-2015.mp3'},
            {t:'Swoosh',u:'https://assets.mixkit.co/sfx/preview/mixkit-fast-small-sweep-transition-166.mp3'},
          ];
          const matched=sfx.filter(s=>s.t.toLowerCase().includes(ql)||ql.includes(s.t.toLowerCase()));
          const pool=matched.length?matched:sfx;
          pool.forEach((s,i)=>items.push({id:'mx_'+i+'_'+san(q),title:'Mixkit – '+s.t+(matched.length?'':' (sample, not query-matched)'),previewUrl:s.u,downloadUrl:s.u,source:'Mixkit',type:'sfx',ext:'mp3',filename:'mixkit_'+san(s.t)+'.mp3'}));
        }
      }
      return items;
    }
    
    async function apiFetch(url,headers={}){
      const r=await fetch(url,{headers:{...headers}});
      if(!r.ok) throw new Error('HTTP '+r.status);
      return r.json();
    }
    
    /* ── RENDER ─────────────────────────── */
    function renderResults(){
      const grid=document.getElementById('resultsGrid');
      grid.innerHTML='';
      document.getElementById('resultsSection').style.display='block';
      document.getElementById('resultsCount').textContent=results.length+' results';
      if(!results.length){
        grid.innerHTML='<div class="empty"><p>No results. Try different terms or add API keys for more sources.</p></div>';
        return;
      }
      results.forEach((item,idx)=>{
        const card=document.createElement('div');
        card.className='card';
        card.dataset.idx=idx;
        let preview='';
        if(item.type==='png'){
          /* checkerboard bg to reveal transparency */
          preview=`<div class="png-wrap">
            <img src="${esc(item.thumb)}" loading="lazy" decoding="async"
              onload="this.classList.add('loaded')"
              onerror="this.outerHTML='<span style=\'font-size:36px\'>🖼️</span>'"
              alt="${esc(item.title)}">
          </div>`;
        } else if(item.type==='image'){
          preview=`<div class="img-wrap">
            <div class="img-placeholder">🖼</div>
            <img src="${esc(item.thumb)}" loading="lazy" decoding="async"
              onload="this.classList.add('loaded');this.previousElementSibling.style.display='none'"
              onerror="this.remove()"
              alt="${esc(item.title)}">
          </div>`;
        } else if(item.type==='icon'){
          preview=`<div class="icon-wrap">
            <img src="${esc(item.thumb)}" loading="lazy" decoding="async"
              onload="this.classList.add('loaded')"
              onerror="this.outerHTML='<span style=\'font-size:40px;color:#6366f1\'>🔷</span>'"
              alt="${esc(item.title)}" style="width:72px;height:72px">
          </div>`;
        } else {
          const dur=item.duration?fmtDur(item.duration):'';
          preview=`<div class="audio-wrap">
            <button class="play-btn" onclick="togglePlay(event,${idx})" aria-label="Play preview">
              <svg class="play-icon" viewBox="0 0 24 24"><polygon points="6,3 20,12 6,21" fill="white"/></svg>
              <svg class="pause-icon" viewBox="0 0 24 24" style="display:none"><rect x="5" y="3" width="4" height="18" fill="white"/><rect x="15" y="3" width="4" height="18" fill="white"/></svg>
            </button>
            ${dur?`<div class="audio-dur">${dur}</div>`:''}
            <div class="prog-bar" onclick="seekAudio(event,${idx})">
              <div class="prog-inner" id="prog_${idx}"></div>
            </div>
          </div>`;
        }
        const sourceCls=item.isPng?'card-source png':'card-source';
        card.innerHTML=`
          <div class="card-check">✓</div>
          ${preview}
          <div class="card-info">
            <div class="card-title" title="${esc(item.title)}">${esc(item.title)}</div>
            <div class="card-meta" title="${esc(item.filename||'')}">${(item.ext||'').toUpperCase()}${item.isPng?' · transparent':''}<br><span style="color:#6366f1;font-size:10px;word-break:break-all">${esc((item.filename||'').slice(0,32))}${(item.filename||'').length>32?'…':''}</span></div>
            <span class="${sourceCls}">${item.source}</span>
          </div>`;
        card.addEventListener('click',e=>{
          if(e.target.closest('.play-btn')||e.target.closest('.prog-bar')) return;
          toggleSel(idx,card);
        });
        grid.appendChild(card);
      });
      updateUI();
    }
    
    /* ── AUDIO PLAYBACK ─────────────────── */
    function togglePlay(e,idx){
      e.stopPropagation();
      const item=results[idx];
      if(!item?.previewUrl) return;
      const btn=e.currentTarget;
      if(activeIdx===idx&&activeAudio){
        if(activeAudio.paused){activeAudio.play();setPlayState(btn,true)}
        else{activeAudio.pause();setPlayState(btn,false)}
        return;
      }
      stopAudio();
      const audio=new Audio(item.previewUrl);
      audio.preload='auto';
      audio.addEventListener('timeupdate',()=>{
        if(!audio.duration) return;
        const bar=document.getElementById('prog_'+idx);
        if(bar) bar.style.width=(audio.currentTime/audio.duration*100).toFixed(2)+'%';
      });
      audio.addEventListener('ended',()=>{setPlayState(btn,false);const bar=document.getElementById('prog_'+idx);if(bar)bar.style.width='0%';activeIdx=-1});
      audio.addEventListener('error',()=>{setPlayState(btn,false);activeIdx=-1});
      audio.play().then(()=>setPlayState(btn,true)).catch(()=>setPlayState(btn,false));
      activeAudio=audio;activePlayBtn=btn;activeIdx=idx;
    }
    function seekAudio(e,idx){
      if(!activeAudio||activeIdx!==idx) return;
      const rect=e.currentTarget.getBoundingClientRect();
      activeAudio.currentTime=((e.clientX-rect.left)/rect.width)*activeAudio.duration;
    }
    function stopAudio(){
      if(activeAudio){activeAudio.pause();activeAudio.src='';activeAudio=null}
      if(activePlayBtn){setPlayState(activePlayBtn,false);activePlayBtn=null}
      document.querySelectorAll('.prog-inner').forEach(b=>b.style.width='0%');
      activeIdx=-1;
    }
    function setPlayState(btn,playing){
      const pi=btn.querySelector('.play-icon'),pa=btn.querySelector('.pause-icon');
      if(pi)pi.style.display=playing?'none':'block';
      if(pa)pa.style.display=playing?'block':'none';
    }
    
    /* ── SELECTION ──────────────────────── */
    function toggleSel(idx,card){selected.has(idx)?selected.delete(idx):selected.add(idx);card.classList.toggle('selected',selected.has(idx));updateUI()}
    function selectAll(){results.forEach((_,i)=>selected.add(i));rerenderSel()}
    function clearSel(){selected.clear();rerenderSel()}
    function rerenderSel(){document.querySelectorAll('.card').forEach(c=>c.classList.toggle('selected',selected.has(+c.dataset.idx)));updateUI()}
    function updateUI(){
      const n=selected.size;
      document.getElementById('selectedCount').textContent=n+' selected';
      document.getElementById('dlCount').textContent=n+' selected';
      document.getElementById('dlBar').classList.toggle('hidden',n===0);
    }
    
    /* ── DOWNLOAD ───────────────────────── */
    async function downloadZip(){
      const items=[...selected].map(i=>results[i]);
      if(!items.length) return;
      setStatus('loading','Packaging '+items.length+' files into ZIP...');
      const zip=new JSZip(),seen={};
      for(const item of items){
        try{
          let fn=item.filename||san(item.title)+'.'+item.ext;
          if(seen[fn]){seen[fn]++;fn=fn.replace(/(\.\w+)$/,'_'+seen[fn]+'$1')}else seen[fn]=1;
          const r=await fetch(item.downloadUrl,{mode:'cors'});
          if(!r.ok) throw new Error('HTTP '+r.status);
          zip.file(fn,await r.blob());
        }catch(er){console.warn('Skipped',item.title,er.message)}
      }
      const blob=await zip.generateAsync({type:'blob',compression:'DEFLATE'});
      const a=document.createElement('a');
      a.href=URL.createObjectURL(blob);
      a.download='assets_'+Date.now()+'.zip';
      a.click();
      setStatus('done','ZIP downloaded!');
    }
    async function saveToFolder(){
      if(!window.showDirectoryPicker){showErr('Save to Folder requires Chrome/Edge. Use ZIP download instead.');return}
      const items=[...selected].map(i=>results[i]);
      if(!items.length) return;
      try{
        const dir=await window.showDirectoryPicker({mode:'readwrite'});
        setStatus('loading','Saving '+items.length+' files...');
        const seen={};
        for(const item of items){
          try{
            let fn=item.filename||san(item.title)+'.'+item.ext;
            if(seen[fn]){seen[fn]++;fn=fn.replace(/(\.\w+)$/,'_'+seen[fn]+'$1')}else seen[fn]=1;
            const r=await fetch(item.downloadUrl,{mode:'cors'});
            if(!r.ok) throw new Error('HTTP '+r.status);
            const fh=await dir.getFileHandle(fn,{create:true});
            const w=await fh.createWritable();
            await w.write(await r.blob());
            await w.close();
          }catch(er){console.warn('Skipped',item.title,er.message)}
        }
        setStatus('done','Files saved!');
      }catch(er){if(er.name!=='AbortError')showErr('Folder save failed: '+er.message)}
    }
    
    /* ── HELPERS ────────────────────────── */
    function san(s){return (s||'file').replace(/[^a-zA-Z0-9_\-]/g,'_').slice(0,40)}
    function enc(s){return encodeURIComponent(s||'')}
    function esc(s){return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')}
    function fmtDur(s){const m=Math.floor(s/60);return m+':'+(s%60<10?'0':'')+Math.round(s%60)}
    function addRecent(q){recent=recent.filter(r=>r!==q);recent.unshift(q);recent=recent.slice(0,8);localStorage.setItem('adl_recent',JSON.stringify(recent));renderRecent()}
    function renderRecent(){
      const c=document.getElementById('recentTags');c.innerHTML='';
      recent.slice(0,6).forEach(q=>{const p=document.createElement('span');p.className='pill';p.textContent=q.length>30?q.slice(0,28)+'…':q;p.onclick=()=>{document.getElementById('searchInput').value=q;runSearch()};c.appendChild(p)});
    }
    
    loadKeys();renderRecent();
    document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Enter'&&(e.ctrlKey||e.metaKey))runSearch()});
    ['keyUnsplash','keyPexels','keyPixabay','keyFreesound'].forEach(k=>document.getElementById(k).addEventListener('change',saveKeys));
    </script>
    </body>
    </html>
    

    Copy the code above into a file named asset_downloader.html, or grab the file directly if you downloaded it from this post.

    What’s Next

    Planning to add a few more free, keyless sources over time, plus better duplicate detection across sources. If you have a favorite free asset API that doesn’t require sign-up, let me know in the comments.

  • Transcribe Audio for Free

    How to Transcribe Audio for Free Using Whisper on Kaggle (No Third-Party Uploads)

    If you make YouTube videos and need accurate subtitles, you don’t need to upload your audio to some random third-party website. In this tutorial, I’ll show you how I generate word-by-word, sentence-level, and karaoke-style subtitles completely for free, using a Kaggle notebook running OpenAI’s Whisper model — with everything staying under my control.

    Why This Approach?

    Privacy matters, especially if your audio contains anything sensitive or unfinished. Instead of sending your files to a third-party subtitle service, this method runs entirely inside your own Kaggle notebook. Your audio never leaves a platform you control.

    What You’ll Need

    • A free Kaggle account
    • An mp3, mp4, wav, or m4a audio file
    • Basic familiarity with running a Kaggle notebook

    Step 1: Upload Your Audio as a Kaggle Dataset

    Create a new dataset in Kaggle and upload your audio file to it. This keeps your file private and lets you swap it out later by simply bumping the dataset version.

    Step 2: Run the Transcription Script

    Here’s the full script. It uses stable-ts (built on top of OpenAI’s Whisper) to transcribe and align your audio, then exports three types of subtitle files.

    import os
    import glob
    os.system('pip install -q stable-ts')
    import stable_whisper
    
    WHISPER_MODEL = 'large'
    LANGUAGE      = 'en'
    OUTPUT_DIR    = '/kaggle/working'
    
    audio_files = (
        glob.glob('/kaggle/input/**/*.mp3', recursive=True) +
        glob.glob('/kaggle/input/**/*.mp4', recursive=True) +
        glob.glob('/kaggle/input/**/*.wav', recursive=True) +
        glob.glob('/kaggle/input/**/*.m4a', recursive=True)
    )
    if not audio_files:
        raise FileNotFoundError('No audio found in /kaggle/input')
    
    AUDIO_FILE = audio_files[0]
    print(f'Using: {AUDIO_FILE}')
    
    import torch
    device = 'cpu'
    if torch.cuda.is_available():
        try:
            cap = torch.cuda.get_device_capability(0)
            if cap >= (7, 0):
                device = 'cuda'
            else:
                print(f'GPU capability {cap} too old, using CPU instead.')
        except Exception as e:
            print(f'GPU check failed ({e}), using CPU instead.')
    
    model = stable_whisper.load_model(WHISPER_MODEL, device=device)
    result = model.transcribe(AUDIO_FILE, language=LANGUAGE, word_timestamps=True, verbose=False)
    
    base = os.path.splitext(os.path.basename(AUDIO_FILE))[0]
    word_srt = os.path.join(OUTPUT_DIR, f'{base}_word_level.srt')
    result.to_srt_vtt(word_srt, word_level=True, tag=('', ''))
    
    sent_srt = os.path.join(OUTPUT_DIR, f'{base}_sentence.srt')
    result.to_srt_vtt(sent_srt, word_level=False)
    
    print('Done! Files saved to /kaggle/working')
    

    Step 3: Pull Your Output

    Once the kernel finishes running, use the Kaggle API to pull your output files directly to your computer:

    kaggle kernels push
    kaggle kernels output <your-kernel-name> -p ./output

    Step 4: Import Into Your Video Editor

    Bring the exported SRT files into your video editor (CapCut, Premiere, etc.) to add synced captions to your video.

    Wrap-Up

    That’s it — accurate, free subtitles without ever handing your audio to a third-party website. If you found this useful, subscribe for more tutorials like this.

  • Free AI Image Generator on Kaggle: Bulk Generate Cartoon Images with Stable Diffusion (No Cost, No GPU Needed)

    If you create cartoon content, YouTube thumbnails, kids' educational videos, or any kind of visual assets, you've probably run into the same wall: AI image generators are either expensive, rate-limited, or watermark your output. In this tutorial, I'll show you how to set up a **completely free AI image generator** using Kaggle's GPU notebooks and Stable Diffusion XL — and how to generate images in **bulk batches** with just a list of prompts.

    By the end of this guide, you'll have a reusable notebook where you can drop in 10, 20, or even 50 prompts, hit run, and walk away with a folder of finished images — zipped and ready to download.

    Why Kaggle for AI Image Generation?

    Most free AI image generator tools online limit you to a handful of images per day, strip out resolution, or require a subscription for anything beyond basic use. Running Stable Diffusion locally is an option, but it requires a powerful GPU — something most laptops simply don’t have.

    Kaggle solves this elegantly. Every Kaggle account comes with free access to NVIDIA T4 GPUs, with roughly 30 hours of GPU time per week at no cost. That’s more than enough to generate hundreds of high-quality images weekly, especially if you’re working on a content calendar that needs a steady stream of visual assets.

    The setup runs entirely in your browser through a Jupyter-style notebook. No installation on your computer, no driver issues, nothing to maintain.

    What You’ll Need

    A free Kaggle account (sign up at kaggle.com if you don't have one)

    - About 15 minutes for initial setup

    - A list of prompts for whatever images you want to create

    That's it. No credit card, no API keys, no subscriptions.

    Create a New Notebook with GPU Enabled

    Once you're logged into Kaggle, head to the Notebooks section and create a new notebook. Before doing anything else, you need to enable GPU access:

    1. Open the notebook settings panel (usually on the right side)

    2. Under "Accelerator," select **GPU T4 x2** (or whichever T4 option is available)

    3. Save the settings

    This step is critical. Without enabling the GPU, the notebook will try to run everything on CPU, which would make image generation painfully slow — we're talking minutes per image instead of seconds.

    Install the Required Libraries

    The first cell in the notebook handles installing everything needed to run Stable Diffusion. This includes the Diffusers library (which handles the actual image generation pipeline), Transformers, Accelerate for performance optimization, and a few supporting packages.

    This installation step typically takes under a minute. One thing worth knowing: Kaggle notebooks come pre-loaded with a lot of data science packages, and you might see some dependency warning messages during installation. These are harmless — they're conflicts between Kaggle's pre-installed packages and the new ones, but they don't affect the image generation pipeline at all.

    Load the Image Generation Model

    This is where the magic happens. The second cell loads the actual AI model — in this case, we're using Playground v2.5, a Stable Diffusion XL model known for producing vibrant, high-quality results that work well for both realistic and stylized/cartoon imagery.

    The first time you run this cell, it will download the model files, which total a few gigabytes. This download happens once per session — if your notebook restarts, you'll need to run this cell again, but Kaggle often caches it for faster reloads.

    A few optimizations are baked into this step that are worth understanding even if you're not technical:

    Memory-efficient attention** allows the GPU to process images using less memory, which means you can generate larger images without running into "out of memory" errors.

    VAE slicing and tiling** breaks the final image decoding step into smaller chunks. This is what allows you to run a long batch of images back-to-back without the GPU running out of room — a problem that commonly occurs when generating many images in sequence without this optimization.

    Once this cell finishes, you'll see a confirmation message that the model is loaded and ready.

    Set Up the Generator Function

    The third cell defines a reusable function that handles the actual image creation process. Think of this as the "engine" — you won't need to touch this cell directly, but it's what powers everything that follows

    This function takes care of several things automatically

    Generating the image based on your prompt

    - Applying a "negative prompt" — essentially a list of things you don't want in the image, like blurry results, distorted anatomy, or watermarks

    - Saving each image with a unique filename based on the time it was created

    Displaying the image directly in the notebook so you can preview it immediately

    Cleaning up GPU memory after each image, which prevents crashes during long batch run

    You only need to run this cell once per session.

    Add Your Prompts and Generate in Bulk

    This is the cell you'll actually edit every time you want new images. It contains a simple list where each entry has two parts: a name (used for the filename) and a prompt (the description of what you want generated).

    or example, if you’re creating a set of cartoon animal images for a kids’ educational video, your list might include entries for a lion, an elephant, a rabbit, and so on — each with a detailed prompt describing the art style, the animal’s pose, expression, and background.

    Download Everything as a ZIP File

    Once your batch finishes, the final cell bundles every generated image into a single ZIP file and provides a direct download link right inside the notebook. Click the link, and the entire batch downloads to your computer in one go — no need to right-click and save each image individually.

    With this setup, you now have access to a genuinely free, GPU-powered image generation pipeline that can produce dozens of images per session without any subscription costs or daily limits beyond Kaggle's generous weekly GPU allowance. Whether you're building out a content calendar for a YouTube channel, creating thumbnail assets, or just experimenting with AI art, this notebook gives you a solid, repeatable foundation.

    If you found this tutorial helpful, check out our other guides on automating content creation workflows — including video editing automation, text-to-speech pipelines, and more.

    please see the video link for detailed video

    Download code from think google drive link https://drive.google.com/file/d/1HZ1zFriQIA6n1siueqSMNShBiuxe9N1X/view?usp=sharing

  • Free After Effects Expression Applier Script – Add Animations in One Click (2025)

    Free After Effects Expression

    Stop Writing Expressions Manually – Use This Free Script Instead

    Are you tired of copying and pasting the same After Effects expressions over and over? Want to add professional animations like wiggle, pulse, and bounce effects without memorizing code? This free Expression Applier script gives you instant access to 18+ pre-built expressions with just one click.

    Perfect for motion designers, video editors, and content creators who want to speed up their workflow and create stunning animations effortlessly.


    What is the Expression Applier Script?

    The Expression Applier is a free After Effects automation script that provides a library of commonly-used expressions. Instead of typing or copying code, you simply:

    1. Select your layers
    2. Choose an expression from the library
    3. Click “Apply”
    4. Done! ✅

    Why You Need This Script

    ✅ Save Time – No more googling expressions or searching old projects ✅ No Coding Required – User-friendly interface for beginners ✅ 18+ Professional Effects – Wiggle, bounce, pulse, loop, and more ✅ Batch Apply – Add expressions to multiple layers at once ✅ Organized Categories – Position, Rotation, Scale, and Opacity tabs ✅ See Descriptions – Know what each expression does before applying


    Expression Library – What’s Included

    🎯 Position Expressions (Movement)

    Wiggle – Smooth

    • Creates gentle, natural random movement
    • Perfect for: Floating elements, breath effects, subtle animation
    • 2 wiggles per second with 30px movement

    Wiggle – Fast

    • Rapid, jittery movement effect
    • Perfect for: Glitch effects, nervous energy, camera shake
    • 5 wiggles per second with 50px movement

    Bounce – Loop

    • Continuous up and down bouncing
    • Perfect for: Ball bounces, floating objects, playful animations
    • Loops forever at 2 bounces per second

    Follow Null

    • Links position to a controller null layer
    • Perfect for: Complex animations, parent controls, rigging
    • Requires a null layer named “Controller”

    Loop In / Loop Out / Ping Pong

    • Repeats your keyframe animations automatically
    • Perfect for: Seamless loops, repeating patterns
    • Requires at least 2 keyframes

    🔄 Rotation Expressions (Spinning)

    Spin – Clockwise

    • Continuous 360° rotation every second
    • Perfect for: Wheels, gears, loading spinners, clock hands
    • Smooth constant rotation

    Spin – Counter-Clockwise

    • Reverse direction spinning
    • Perfect for: Counter animations, opposite rotations
    • Negative rotation value

    Wiggle Rotation

    • Random rotation shake effect
    • Perfect for: Drunk effect, vibration, unstable objects
    • 3 wiggles per second, ±15° range

    📏 Scale Expressions (Size)

    Pulse

    • Fast, rhythmic growing and shrinking
    • Perfect for: Heartbeats, music sync, attention-grabbing
    • 2 pulses per second with ±20% scale

    Breathing

    • Slow, subtle size variation
    • Perfect for: Living objects, organic feels, calm animations
    • 1 breath per second with ±5% scale

    Random Scale

    • Unpredictable size changes
    • Perfect for: Glitch, digital effects, randomization
    • 1 wiggle per second, ±10% variation

    Audio React

    • Scales based on audio amplitude
    • Perfect for: Music visualizers, sound-reactive graphics
    • Requires audio layer named “Audio”

    👁️ Opacity Expressions (Visibility)

    Blink

    • Random on/off flashing effect
    • Perfect for: Warning lights, digital displays, blinking eyes
    • Unpredictable timing

    Fade Loop

    • Smooth continuous fading in and out
    • Perfect for: Breathing lights, pulsing glow, attention effects
    • 1 cycle per second, 0-100% opacity

    Flicker

    • Rapid random opacity changes
    • Perfect for: Old film, broken lights, static interference
    • 70-100% opacity range

    How to Install the Expression Applier Script

    Step 1: Download and Save the Script

    1. Copy the script code
    2. Open a text editor (Notepad on Windows, TextEdit on Mac)
    3. Paste the code
    4. Save as ExpressionApplier.jsx (make sure it has .jsx extension)

    Step 2: Install in After Effects

    Windows:

    C:\Program Files\Adobe\Adobe After Effects [version]\Support Files\Scripts
    

    Mac:

    /Applications/Adobe After Effects [version]/Scripts
    

    Step 3: Enable Scripting

    1. Open After Effects
    2. Go to Edit > Preferences > Scripting & Expressions
    3. Check “Allow Scripts to Write Files and Access Network”
    4. Restart After Effects

    How to Use: Complete Tutorial

    Basic Usage (Single Layer)

    1. Select a layer in your composition timeline
    2. Go to File > Scripts > Run Script File
    3. Browse to ExpressionApplier.jsx and open it
    4. The Expression Applier window opens
    5. Click a tab (Position, Rotation, Scale, or Opacity)
    6. Select an expression from the list
    7. Read the description at the bottom
    8. Click “Apply Expression”
    9. Your layer now has the expression applied!

    Advanced Usage (Multiple Layers)

    Apply to multiple layers at once:

    1. Select multiple layers (Shift+Click or Ctrl/Cmd+Click)
    2. Run the script
    3. Choose your expression
    4. Click Apply
    5. Expression applies to ALL selected layers instantly! ✨

    Pro Tip: This is perfect for creating uniform effects across text layers, shape layers, or graphic elements.

    Understanding the Interface

    Four Main Tabs:

    • Position Tab – Movement and location expressions (7 options)
    • Rotation Tab – Spinning and rotation effects (3 options)
    • Scale Tab – Size and growth expressions (4 options)
    • Opacity Tab – Visibility and fade effects (3 options)

    Description Panel: Shows what each expression does before you apply it – no guessing.


    Download script file from this linkhttps://drive.google.com/file/d/1u_DJPmWV5a-XGoxzbmXNj53OqYuvGwyd/view?usp=sharing. i will approve your access request.

    Real-World Use Cases

    Use Case 1: Social Media Graphics

    Scenario: Creating an Instagram story with 10 text layers

    • Select all text layers
    • Apply “Wiggle – Smooth” for subtle movement
    • Result: Professional floating text effect in 5 seconds

    Use Case 2: Logo Animation

    Scenario: Making your logo pulse with energy

    • Select logo layer
    • Apply “Pulse” from Scale tab
    • Result: Rhythmic heartbeat effect that loops forever

    Use Case 3: Music Visualizer

    Scenario: Creating audio-reactive bars

    1. Create 20 rectangle shape layers
    2. Import your audio as “Audio” layer
    3. Select all rectangles
    4. Apply “Audio React” expression
    5. Result: Bars that dance to your music!

    Use Case 4: Loading Spinner

    Scenario: Creating a loading icon

    • Draw a circular shape with anchor point off-center
    • Apply “Spin – Clockwise”
    • Result: Perfect loading animation, no keyframes needed

    Use Case 5: Glitch Effect

    Scenario: Adding glitch to text

    1. Duplicate text layer 3 times
    2. Select all copies
    3. Apply “Wiggle – Fast” to position
    4. Apply “Flicker” to opacity
    5. Result: Intense glitch effect

    Pro Tips and Tricks

    Tip 1: Customize After Applying

    Expressions aren’t locked! After applying:

    1. Select the layer
    2. Press E to reveal expressions
    3. Alt+Click (Win) or Option+Click (Mac) on the stopwatch
    4. Edit the expression values

    Example: Change wiggle speed

    // Original
    wiggle(2, 30);
    
    // Modified for faster wiggle
    wiggle(5, 30);
    

    Tip 2: Combine Multiple Expressions

    You can layer effects:

    1. Apply “Spin – Clockwise” to Rotation
    2. Apply “Wiggle – Smooth” to Position
    3. Apply “Pulse” to Scale
    4. Result: Complex, multi-dimensional animation!

    Tip 3: Control Expression Intensity

    Modify the values in the expression:

    Wiggle:

    • wiggle(frequency, amplitude)
    • Higher frequency = faster shaking
    • Higher amplitude = bigger movement

    Pulse/Breathing:

    • freq = 2; – Change number for faster/slower
    • amp = 20; – Change for more/less intensity

    Tip 4: Time-Based Expressions

    For expressions that run forever (wiggle, pulse, spin), you can add time limits:

    // Only wiggle for first 3 seconds
    if (time < 3) {
      wiggle(2, 30);
    } else {
      value;
    }
    

    Tip 5: Audio Reactive Setup

    For “Audio React” to work:

    1. Import your audio file
    2. Rename the audio layer to exactly “Audio”
    3. Apply the expression to Scale
    4. Adjust the amp = 200; value for sensitivity

    Troubleshooting Common Issues

    Problem: Script Window Doesn’t Open

    Solutions:

    • Check if scripting is enabled in Preferences
    • Make sure file has .jsx extension
    • Try restarting After Effects
    • Verify file is in the Scripts folder

    Problem: “No Composition Found” Error

    Solution:

    • Make sure you have a composition open
    • The composition must be active (highlighted)
    • Click on the comp timeline before running script

    Problem: “No Layers Selected” Error

    Solution:

    • Select at least one layer in your timeline
    • Make sure layers aren’t locked
    • Shy layers still work if selected

    Problem: Expression Doesn’t Show Effect

    Solutions:

    • Check if you applied to correct property (Position expressions won’t affect Rotation)
    • Make sure layer isn’t parented to another layer
    • Verify expression has no syntax errors (red text)
    • Try disabling then re-enabling the expression

    Problem: “Audio” Layer Not Found (Audio React)

    Solution:

    • Your audio layer must be named exactly “Audio” (case-sensitive)
    • Layer must be in the same composition
    • Audio must have audio waveform visible

    Problem: Loop Expressions Don’t Work

    Solution:

    • Loop expressions require keyframes to work
    • You need at least 2 keyframes on the property
    • Make sure keyframes have different values

    Keyboard Shortcuts for Faster Workflow

    Speed up your expression workflow with these shortcuts:

    • U – Show all modified properties (including expressions)
    • E – Show only expressions
    • Alt/Opt + Click Stopwatch – Enable/disable expression
    • Ctrl/Cmd + D – Duplicate layer with expressions intact
    • Ctrl/Cmd + Shift + D – Split layer (expressions remain on both)

    Frequently Asked Questions

    Q: Does this work with After Effects 2024? A: Yes! Works with After Effects CC 2017 and all later versions including 2024.

    Q: Can I use these expressions in commercial projects? A: Absolutely! These are standard After Effects expressions, free to use anywhere.

    Q: Will expressions slow down my composition? A: Slightly, but modern computers handle these simple expressions easily. Audio reactive expressions are more intensive.

    Q: Can I add my own expressions to the library? A: Yes! The script is customizable. Open the .jsx file and add to the expressionLibrary object following the same format.

    Q: Do expressions export with my video? A: Yes! Expressions are “baked in” when you render. The animation becomes real keyframes in the final export.

    Q: Can I remove an expression after applying? A: Yes. Select the layer, press E, then Alt/Opt+Click the stopwatch to remove it.

    Q: What’s the difference between expressions and keyframes? A: Expressions are dynamic code that generates animation automatically. Keyframes are manual points you set. Expressions are easier for repetitive effects!

    Q: Can I apply multiple expressions to one property? A: No, one expression per property. But you can apply different expressions to different properties (Position, Rotation, Scale, Opacity).

    Q: Does “Follow Null” work with any null name? A: The default code looks for a null named “Controller”. You can edit the expression to use any null name.

    Q: How do I make expressions loop a specific number of times? A: You’ll need to modify the expression code to add a time limit or counter. Check the “Pro Tips” section for examples.


    Best Practices for Using Expressions

    1. Name Your Layers Properly

    Before applying expressions, use clear layer names:

    • ❌ “Shape Layer 1”
    • ✅ “Logo_Main”

    This helps when expressions reference other layers.

    2. Use Adjustment Layers for Global Effects

    Apply expressions to adjustment layers to affect everything below:

    • Add wiggle to adjustment layer position = everything shakes
    • Add opacity flicker = entire composition flickers

    3. PreCompose for Complex Setups

    If you have many layers with expressions:

    1. Select all layers
    2. Right-click > Pre-compose
    3. Apply expressions to the precomp Result: Easier to manage and faster performance

    4. Test on Short Previews First

    Some expressions are CPU-intensive:

    • Test on 3-5 seconds first
    • If it plays smoothly, apply to full timeline
    • Consider reducing complexity if laggy

    5. Save Expression Presets

    After customizing an expression:

    1. Select the property with expression
    2. Animation > Save Animation Preset
    3. Name and save
    4. Drag from Effects & Presets panel to reuse

    Advanced Customization Guide

    Creating Custom Wiggle Speeds

    Modify the wiggle values for different feels:

    // Subtle
    wiggle(1, 10);
    
    // Normal (default)
    wiggle(2, 30);
    
    // Aggressive
    wiggle(8, 100);
    
    // Earthquake
    wiggle(20, 200);
    

    Making Expressions Stop After Time

    Add conditional timing:

    // Wiggle for 5 seconds then stop
    if (time < 5) {
      wiggle(2, 30);
    } else {
      value;
    }
    

    Randomize Each Layer Differently

    Use seedRandom() for unique randomness per layer:

    seedRandom(index, true);
    wiggle(2, 30);
    

    Now each layer wiggles differently!

    Link to Slider Control

    For dynamic control without editing expressions:

    1. Add Effects > Expression Controls > Slider Control
    2. Modify expression:
    speed = effect("Slider Control")("Slider");
    wiggle(speed, 30);
    

    Now adjust wiggle speed with a slider!



    Conclusion

    The Expression Applier script is a must-have tool for any After Effects user. Whether you’re a beginner learning expressions or a professional speeding up your workflow, having instant access to 18+ pre-built expressions saves countless hours.

    Quick Summary:

    ✅ Free and easy to use – No coding required ✅ 18+ professional expressions – Position, Rotation, Scale, Opacity ✅ Apply to multiple layers – Batch processing saves time
    ✅ Organized interface – Tabbed categories with descriptions ✅ Customizable – Edit expressions after applying ✅ Works with all AE versions – CC 2017 and newer

    What You Can Create:

    • 🎬 Smooth floating animations
    • 🔄 Perfect loading spinners
    • 💓 Pulsing logos and text
    • 🎵 Audio-reactive visualizers
    • ✨ Glitch and flicker effects
    • 🎯 Complex motion graphics

    Stop wasting time searching for expressions online. Download the Expression Applier script today and speed up your After Effects workflow instantly!


    Ready to supercharge your animations? Download the script above and start creating stunning effects in seconds!

    Have questions or want more expressions added? Drop a comment below and let me know what effects you’d like to see!


    imation #AdobeAfterEffects #ContentCreation #VisualEffects #MotionDesign #Automation

  • How to Add Animated Captions to Videos in After Effects (Free Script 2024)

    Add Professional SRT Captions to Your Videos in Minutes

    Are you struggling to add eye-catching captions to your videos? Whether you’re creating content for YouTube, Instagram, or TikTok, this free After Effects script makes it incredibly easy to import SRT files and create stunning animated captions that keep your audience engaged.


    What is the Caption Creator?

    The Caption Creator is a free After Effects script that automatically converts your SRT subtitle files into beautifully animated text layers. Perfect for:

    • Social media content creators
    • YouTube video editors
    • Punjabi language content
    • Music video producers
    • Educational content
    • Marketing videos

    Key Features

    ✅ Automatic SRT Import – Drag and drop your subtitle file ✅ 5 Animation Styles – Fade, Pop, Slide, Bounce, and Zoom ✅ Auto-Scaling Text – Font size adjusts to fit your box perfectly ✅ Customizable Effects – Glow, shadow, and stroke options ✅ Single Container – Move all captions together easily ✅ Bold & Readable – Enhanced text styling for maximum visibility


    How to Install the Script

    Step 1: Download the Script

    1. Copy the script code to a text editor
    2. Save it as srtmaker.jsx
    3. Place it in your After Effects Scripts folder:
      • Windows: C:\Program Files\Adobe\Adobe After Effects [version]\Support Files\Scripts
      • Mac: /Applications/Adobe After Effects [version]/Scripts

    Step 2: Enable Scripts in After Effects

    1. Open After Effects
    2. Go to Edit > Preferences > Scripting & Expressions (Windows) or After Effects > Preferences > Scripting & Expressions (Mac)
    3. Check Allow Scripts to Write Files and Access Network
    4. Restart After Effects

    How to Use: Step-by-Step Tutorial

    Step 1: Prepare Your SRT File

    First, you need an SRT subtitle file. Here’s what it should look like:

    1
    00:00:01,000 --> 00:00:03,500
    this is my fist video
    
    2
    00:00:03,600 --> 00:00:06,000
    watch till last
    

    Pro Tip: You can generate SRT files using:

    • YouTube’s auto-caption feature
    • Subtitle editing software like Aegisub
    • Online SRT generators
    • AI transcription tools

    Step 2: Create Your Composition

    1. Open After Effects
    2. Create a new composition (recommended: 1920×1080, 30fps)
    3. Import your video footage
    4. Make sure your composition is the active/selected item

    Step 3: Run the Script

    1. Go to File > Scripts > Run Script File
    2. Select srtmaker.jsx
    3. Browse and select your SRT file
    4. The settings dialog will appear

    Step 4: Configure Your Caption Settings

    Box Position & Size

    • Position X, Y: Where the caption box appears on screen
      • Default: 960, 900 (bottom center for 1920×1080)
      • Adjust based on your composition size
    • Size W, H: Dimensions of the caption container
      • Default: 1600×400 (wide bottom banner)
      • Larger = more text fits

    Text Styling

    • Text Color: Choose from Green, White, or Yellow
      • Green works great for most content
      • White for dark backgrounds
      • Yellow for high visibility
    • Padding: Space between text and box edges (default: 40px)
    • Auto-scale Font: Automatically adjusts text size to fit

    Animation Options

    Choose from 5 professional animations:

    1. Fade – Classic smooth appearance (best for professional content)
    2. Pop – Dynamic scale effect (great for energetic content)
    3. Slide Up – Text slides from bottom (smooth and modern)
    4. Bounce – Playful spring effect (perfect for fun content)
    5. Zoom – Attention-grabbing zoom in (high-impact)

    Visual Effects

    • Glow: Adds a vibrant glow around text (recommended: ON)
    • Shadow: Creates depth with drop shadow (recommended: ON)
    • Stroke: Black outline for better contrast (recommended: ON)

    Step 5: Generate Captions

    1. Click Create
    2. Wait for the script to process (takes 5-30 seconds depending on caption count)
    3. You’ll see a confirmation message with details

    Customizing Your Captions

    Moving Captions Around

    The beauty of this script is that all captions are in one container:

    1. Select the “Captions” precomp layer in your timeline
    2. Press P to reveal Position
    3. Drag or animate the position – all text moves together!

    Adjusting Individual Captions

    If you need to edit specific captions:

    1. Double-click the “Captions” precomp in your Project panel
    2. Find the caption layer you want to edit
    3. Modify text, position, or timing as needed

    Changing Colors After Creation

    1. Select a text layer in the precomp
    2. Open Character panel (Ctrl/Cmd + 6)
    3. Click the fill color to change it
    4. Use the Edit > Select All to change all at once

    Pro Tips for Better Captions

    1. Optimize Box Size for Your Video Format

    • YouTube/Horizontal Videos: 1600×400 at bottom
    • Instagram Stories/TikTok: 800×600 centered
    • Square Videos: 1000×500 at bottom third

    2. Choose the Right Animation

    • Tutorial/Educational: Fade (professional, not distracting)
    • Music Videos: Pop or Bounce (energetic, matches beat)
    • Vlogs: Slide Up (modern, smooth)
    • Promotional: Zoom (attention-grabbing)

    3. Font Size Matters

    The script auto-scales, but you can adjust box dimensions to control text size:

    • Larger box = smaller text (more words fit)
    • Smaller box = larger text (fewer words, more impact)

    4. Contrast is Key

    • Use White text on dark videos
    • Use darker colors on bright videos
    • Always keep Stroke ON for maximum readability

    5. Position Strategy

    Common positioning:

    • Bottom 10%: Standard for most content
    • Top 20%: When showing products/faces at bottom
    • Center: For dramatic effect or lyrics

    Troubleshooting Common Issues

    “No composition found” Error

    Solution: Make sure you have a composition open and selected before running the script.

    Captions Don’t Appear

    Solution:

    • Check if the layer is below your footage (move it to top)
    • Verify the timing matches your SRT file
    • Check if the precomp is visible and not hidden

    Text is Too Small/Large

    Solution:

    • Adjust the box Width and Height settings
    • Turn OFF auto-scale and manually set font size in the precomp
    • Modify padding to give text more room

    Script Won’t Run

    Solution:

    • Enable “Allow Scripts to Write Files” in Preferences
    • Make sure the script file has .jsx extension
    • Try restarting After Effects

    Captions Overlap

    Solution: The script automatically adds 1 frame gap. If still overlapping:



    Conclusion

    The Punjabi Caption Creator script is a game-changer for video editors working with subtitle files. In just a few clicks, you can transform plain SRT files into stunning, animated captions that boost engagement and accessibility.

    Quick Recap:

    1. ✅ Install the script in After Effects
    2. ✅ Prepare your SRT file
    3. ✅ Run the script and configure settings
    4. ✅ Generate professional captions instantly
    5. ✅ Customize and export your video

    Ready to create stunning captions? Download the script and start making your videos more engaging today!


    FAQ

    Q: Does this work with other languages besides Punjabi? A: Yes! This script works with any language supported by After Effects, including English, Hindi, Urdu, and more.

    Q: Can I use this for commercial projects? A: Absolutely! This script is free to use for both personal and commercial projects.

    Q: What version of After Effects do I need? A: The script works with After Effects CC 2017 and later versions.

    Q: Can I change the animation after creating captions? A: Yes, you can modify animations by opening the precomp and adjusting keyframes manually.

    Q: How many captions can this handle? A: The script can handle hundreds of captions. Processing time increases with more captions but typically stays under 1 minute.


    Have questions or suggestions? Leave a comment below! Don’t forget to share this tutorial if it helped you create amazing captions.

    Tags: #AfterEffects #VideoEditing #Captions #Subtitles #PunjabiContent #SocialMediaMarketing #YouTubeTips #ContentCreation