Ready For Rehome

Looking for a Furry Overlord? We Have Just the Cat for You!

Attention, humans! Are you ready to serve a feline ruler with unwavering dedication and unconditional love? Well, you’re in luck because we have several cats and kittens searching for their forever homes (and their personal servants, of course).

From playful kittens with energy levels that defy the laws of physics to regal adult cats who expect their meals to be delivered on a silver platter (or, you know, a clean dish at the very least), we have the perfect match for every type of devoted subject – uh, we mean adopter.

Before You Pledge Your Allegiance…
We kindly ask all potential adopters to review our adoption process before applying. We know, we know, reading fine print isn’t as fun as looking at adorable cat pictures, but trust us, it’s important! Our process is designed to ensure that our furry friends find the best possible homes, and that their new humans know exactly what they’re signing up for (spoiler: it involves a lot of zoomies, head boops, and the occasional destruction of personal property).

What You Get in Return
Unconditional love (on their terms, of course)
Tiny toe beans to admire at all times
Free daily entertainment (who needs TV when you have a cat launching itself off furniture?)
A new boss who will supervise everything you do, from bathroom breaks to important Zoom meetings
Ready to Meet Your New Ruler?
If you’re prepared to be chosen by your feline overlord, check out our available cats and kittens and submit an adoption application today! Your new best friend (and future ruler of your household) is waiting.

🐱 StraySafe Adoption Process –

A Purr-sonalized Matchmaking Service! 🐾

Here at StraySafe, we don’t do adoptions on a first-come, first-served basis. Nope! We’re like feline matchmakers, working our magic to find the purr-fect home for each of our fabulous cats. Think of us as the Cupid of the cat world – minus the diaper (thankfully).

Step 1: Window Shopping (a.k.a. Browsing Our Cats).
Scroll through our adorable line-up of whiskered wonders (shown below) and complete an adoption form for whichever cat(s) or kitten(s) make your heart do that weird fluttery thing. Or, if you’re not sure of which cat(s) or kitten(s) you would like to adopt, tell us what kind of furry sidekick you’re looking for on your adoption form, and we’ll play matchmaker!

Step 2: Home Check – The Cat Inspector Cometh 🕵‍♂
Once you’ve fallen for a feline, we’ll arrange a home check to make sure your setup is up to cat standards. (Translation: If you have a giant dog that thinks cats are squeaky toys, we might need to chat).

Step 3: Meet & Greet – Love at First Meow?
Passed the home check? Great! Now it’s time to meet your potential new best friend and see if the chemistry is right. Sparks may fly. Or they may just blink at you like a tiny furry overlord deciding your fate. Either way, we’re here for it.

What’s Included? (Because Cats Deserve the VIP Package)

Kittens come microchipped, with their first vaccination, flea and worm treatment, and a vet check.

Adult cats get all that plus the all-important neutering – because the world doesn’t need any more surprise kittens.

Adoption Fees – Because Good Love Isn’t Free
£150.

Meet Our Adoptable Animals

Longest in our care are shown first

Every adoption changes a life. Thank you for supporting rescue work. 🐾

`; }; wrap.appendChild(frame); mount.appendChild(wrap); const hasRenderedForm = () => { try { const doc = frame.contentDocument || frame.contentWindow?.document; const target = doc ? doc.getElementById('asm3-onlineform') : null; if (!target) return false; const height = Math.max(doc.body?.scrollHeight || 0, doc.documentElement?.scrollHeight || 0); if (height > 200) frame.style.minHeight = `${Math.min(Math.max(height + 20, 860), 1800)}px`; return !!(target.querySelector('form, iframe, input, textarea, select, button') || target.children.length || target.textContent.trim()); } catch (e) { return false; } }; const showFormLoadError = () => { applyFormInitialising = false; if (status) { status.textContent = 'The application form loaded but did not render. Please refresh the page or use the application form below the animal listings.'; status.style.color = '#b91c1c'; status.classList.remove('hidden'); } }; let formWaitTimer = null; const waitForRenderedForm = () => { if (formWaitTimer) return; let tries = 0; formWaitTimer = window.setInterval(() => { if (hasRenderedForm()) { applyFormInitialising = false; if (status) status.classList.add('hidden'); window.clearInterval(formWaitTimer); formWaitTimer = null; return; } tries += 1; if (tries >= 44) { window.clearInterval(formWaitTimer); formWaitTimer = null; showFormLoadError(); } }, 250); }; frame.addEventListener('load', waitForRenderedForm, { once: true }); frame.srcdoc = frameDoc(ASM_WIDGET.applyFormScriptUrl); window.setTimeout(waitForRenderedForm, 100); }function showDetailsView(){ if (mediaCol) mediaCol.classList.remove("hidden"); if (infoCol) infoCol.classList.remove("hidden"); if (descCol) descCol.classList.remove("hidden"); if (formCol) formCol.classList.add("hidden"); if (applyToggle) applyToggle.classList.remove("hidden"); if (applyBackTop) applyBackTop.classList.add("hidden"); restoreMovedApplyForm(); if (scrollEl) { scrollEl.scrollTop = 0; requestAnimationFrame(() => { scrollEl.scrollTop = 0; }); } }function showFormView(){ ensureApplyForm(); if (mediaCol) mediaCol.classList.add("hidden"); if (infoCol) infoCol.classList.add("hidden"); if (descCol) descCol.classList.add("hidden"); if (formCol) formCol.classList.remove("hidden"); if (applyToggle) applyToggle.classList.add("hidden"); if (applyBackTop) applyBackTop.classList.remove("hidden"); if (scrollEl) { scrollEl.scrollTop = 0; requestAnimationFrame(() => { scrollEl.scrollTop = 0; }); } }const formWrap = qs("asm-apply-form-wrap"); if (formWrap && !formWrap.dataset.successScrollBound) { formWrap.dataset.successScrollBound = "1";const successPattern = /(thank you|successfully submitted|submitted successfully|submission successful|application (?:has been )?(?:received|submitted|sent)|form (?:has been )?(?:submitted|sent)|we have received|your response has been recorded)/i; let submissionArmedUntil = 0; let knownControlCount = 0; let iframeLoads = new WeakMap();const formViewIsOpen = () => !!formCol && !formCol.classList.contains("hidden");const forceModalFormTop = (smooth = false) => { if (!formViewIsOpen()) return; const behaviour = smooth ? "smooth" : "auto"; const mount = qs("asm-apply-form-mount"); const panel = qs("asm-modal-panel"); const modal = qs("asm-modal"); const candidates = [scrollEl, panel, formCol, formWrap, mount];let node = formWrap.parentElement; while (node && node !== document.body) { candidates.push(node); if (node === modal) break; node = node.parentElement; }[...new Set(candidates.filter(Boolean))].forEach((el) => { try { el.scrollTo({ top: 0, left: 0, behavior: behaviour }); } catch (e) { el.scrollTop = 0; el.scrollLeft = 0; } el.scrollTop = 0; });// ASM can add an inner scrolling wrapper, so reset any scrollable descendants too. formWrap.querySelectorAll('*').forEach((el) => { if (el.scrollTop > 0 || el.scrollHeight > el.clientHeight + 8) el.scrollTop = 0; });requestAnimationFrame(() => { if (scrollEl) scrollEl.scrollTop = 0; if (panel) panel.scrollTop = 0; requestAnimationFrame(() => { if (scrollEl) scrollEl.scrollTop = 0; if (panel) panel.scrollTop = 0; }); }); };const repeatTopCorrection = () => { [0, 120, 300, 600, 1000, 1600, 2400, 3500, 5000].forEach((delay, index) => { window.setTimeout(() => forceModalFormTop(index === 1), delay); }); };const armSubmissionWatch = () => { if (!formViewIsOpen()) return; submissionArmedUntil = Date.now() + 12000; knownControlCount = formWrap.querySelectorAll('input,select,textarea,button').length; // The response can arrive quickly, but do not jump immediately before ASM has processed the click. [500, 1000, 1800, 3000, 5000, 8000].forEach((delay) => { window.setTimeout(() => { if (Date.now() <= submissionArmedUntil && formViewIsOpen()) { const text = (formWrap.innerText || formWrap.textContent || '').replace(/\s+/g, ' ').trim(); const controls = formWrap.querySelectorAll('input,select,textarea,button').length; if (successPattern.test(text) || (knownControlCount > 2 && controls < Math.max(2, Math.floor(knownControlCount / 3)))) { repeatTopCorrection(); submissionArmedUntil = 0; } } }, delay); }); };formWrap.addEventListener("submit", armSubmissionWatch, true); formWrap.addEventListener("pointerdown", (event) => { const target = event.target && event.target.closest ? event.target.closest('button,input[type="submit"],input[type="button"],a') : null; if (!target) return; const label = String(target.textContent || target.value || target.getAttribute("aria-label") || "").trim(); if (target.matches('[type="submit"]') || /submit|send|apply|finish|complete|next/i.test(label)) armSubmissionWatch(); }, true); formWrap.addEventListener("click", (event) => { const target = event.target && event.target.closest ? event.target.closest('button,input[type="submit"],input[type="button"],a') : null; if (!target) return; const label = String(target.textContent || target.value || target.getAttribute("aria-label") || "").trim(); if (target.matches('[type="submit"]') || /submit|send|apply|finish|complete/i.test(label)) armSubmissionWatch(); }, true);const bindIframeLoads = () => { formWrap.querySelectorAll('iframe').forEach((frame) => { if (iframeLoads.has(frame)) return; iframeLoads.set(frame, 0); frame.addEventListener('load', () => { const count = (iframeLoads.get(frame) || 0) + 1; iframeLoads.set(frame, count); // A second iframe load commonly represents the submitted confirmation page. if (count > 1 || Date.now() <= submissionArmedUntil) repeatTopCorrection(); }); }); };const observer = new MutationObserver(() => { bindIframeLoads(); if (!formViewIsOpen()) return; const text = (formWrap.innerText || formWrap.textContent || "").replace(/\s+/g, " ").trim(); const controls = formWrap.querySelectorAll('input,select,textarea,button').length; const successVisible = successPattern.test(text); const formWasReplaced = Date.now() <= submissionArmedUntil && knownControlCount > 2 && controls < Math.max(2, Math.floor(knownControlCount / 3)); if (successVisible || formWasReplaced) { repeatTopCorrection(); submissionArmedUntil = 0; } }); observer.observe(formWrap, { childList: true, subtree: true, characterData: true, attributes: true }); bindIframeLoads();// Final fallback: while the application view is open, recognise confirmation text // even where ASM changes content without firing a conventional submit event. window.setInterval(() => { if (!formViewIsOpen()) return; const text = (formWrap.innerText || formWrap.textContent || "").replace(/\s+/g, " ").trim(); if (successPattern.test(text)) repeatTopCorrection(); }, 500); }showDetailsView();if (applyToggle) { applyToggle.onclick = () => { trackEvent("adoptables_apply_click"); showFormView(); }; }if (applyBackTop) { applyBackTop.onclick = () => showDetailsView(); }const shareBtn = qs("asm-modal-share"); const shareText = qs("asm-modal-share-text"); if (shareBtn && shareText) { shareText.textContent = ASM_WIDGET.shareButtonText; shareBtn.onclick = async () => { const url = modalUrl(a); try { if (navigator.share && /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent || '')) { await navigator.share({ url }); } else if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(url); } else { const input = document.createElement("input"); input.value = url; document.body.appendChild(input); input.select(); document.execCommand("copy"); input.remove(); } shareText.textContent = ASM_WIDGET.shareCopiedText; window.setTimeout(() => { shareText.textContent = ASM_WIDGET.shareButtonText; }, 1800); } catch (err) {} }; }const modalFavouriteBtn = qs("asm-modal-favourite"); const modalFavouriteText = qs("asm-modal-favourite-text"); const modalFavouriteIcon = qs("asm-modal-favourite-icon"); function syncModalFavouriteState(){ if (!modalFavouriteBtn) return; const fav = isFavourite(id); modalFavouriteBtn.setAttribute('aria-pressed', fav ? 'true' : 'false'); if (modalFavouriteIcon) modalFavouriteIcon.innerHTML = favouriteIconSvg(fav, 'w-4 h-4'); if (modalFavouriteText) modalFavouriteText.textContent = fav ? 'Saved to favourites' : 'Add to favourites'; } if (modalFavouriteBtn) { modalFavouriteBtn.classList.toggle('hidden', !ASM_WIDGET.enableFavourites); modalFavouriteBtn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); toggleFavourite(id); syncModalFavouriteState(); }; syncModalFavouriteState(); }modal.classList.add("asm-modal-ready"); openDialog(modal, qs("asm-modal-close")); }function closeModal(){ const modal = qs("asm-modal"); if (typeof cleanupCurrentModal === 'function') cleanupCurrentModal(); cleanupCurrentModal = null; const detailsView = qs("asm-modal-details-view"); const formView = qs("asm-modal-form-view"); if (formView) formView.classList.add("hidden"); if (detailsView) detailsView.classList.remove("hidden"); closeDialog(modal); currentModalIndex = -1; updateModalNav(); syncModalUrl(null); }async function fetchAdoptables(){ const res = await fetch(ASM_WIDGET.proxyBase, { method: "GET", credentials: "same-origin" }); if(!res.ok){ const t = await res.text().catch(()=>""); throw new Error(`Proxy request failed (${res.status}). ${t}`.trim()); } const data = await res.json(); if(!Array.isArray(data)) throw new Error("Proxy did not return an array."); return data; }async function initAdoptables(){ showStatus(LOADING_STATUS_TEXT); ensureModalInBody();qs("asm-prev")?.addEventListener("click", () => { pageIndex = Math.max(0, pageIndex - 1); renderPage(); }); qs("asm-next")?.addEventListener("click", () => { pageIndex = pageIndex + 1; renderPage(); }); qs("asm-prev-bottom")?.addEventListener("click", () => { pageIndex = Math.max(0, pageIndex - 1); renderPage(); }); qs("asm-next-bottom")?.addEventListener("click", () => { pageIndex = pageIndex + 1; renderPage(); });qs("asm-filters-panel")?.addEventListener("click", (e) => e.stopPropagation()); qs("asm-open-filters")?.addEventListener("click", openFilters); qs("asm-close-filters")?.addEventListener("click", closeFilters); qs("asm-filters-backdrop")?.addEventListener("click", closeFilters); qs("asm-apply-filters")?.addEventListener("click", () => { applyFilters(); closeFilters(); }); qs("asm-reset-filters")?.addEventListener("click", resetFilters); qs("asm-close-favourites-modal")?.addEventListener("click", closeFavouritesModal); qs("asm-favourites-backdrop")?.addEventListener("click", closeFavouritesModal); qs("asm-close-compare-modal")?.addEventListener("click", closeCompareModal); qs("asm-compare-backdrop")?.addEventListener("click", closeCompareModal);qs("asm-modal-close").addEventListener("click", closeModal); qs("asm-modal-backdrop").addEventListener("click", closeModal); qs("asm-modal-prev-animal").addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); openAdjacentModal(-1); }); qs("asm-modal-next-animal").addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); openAdjacentModal(1); });qs("asm-modal-panel").addEventListener("click", (e) => e.stopPropagation()); qs("asm-modal").addEventListener("click", (e) => { const panel = qs("asm-modal-panel"); if (!panel.contains(e.target)) closeModal(); });document.addEventListener("keydown", (e) => { const modal = qs("asm-modal"); const isOpen = modal && !modal.classList.contains("hidden"); const activeDialog = topDialog(); if (activeDialog && e.key === "Tab") trapDialogFocus(e, activeDialog); if(e.key === "Escape") { if (closeTopDialog()) e.preventDefault(); return; } if(!isOpen || activeDialog !== modal) return; if(e.key === "ArrowLeft") openAdjacentModal(-1); if(e.key === "ArrowRight") openAdjacentModal(1); });window.addEventListener("resize", () => { resetStableHeight(); renderPage(); requestAnimationFrame(() => updateStableHeight()); }, { passive: true });try { const animals = await fetchAdoptables(); let cats = ASM_WIDGET.catsOnly ? animals.filter(isCat) : animals; cats.sort((a,b) => daysOnShelter(b) - daysOnShelter(a));rawCats = cats; allCats = rawCats.slice(); filteredCats = rawCats.slice(); setupFilterUi(rawCats); showStatus(allCats.length ? "" : "No cats available.", !allCats.length); pageIndex = 0;applyFilters();let initialDeepLinkOpened = false; const openRequestedCat = () => { const wanted = requestedCatId(); if (!wanted) return false; const target = allCats.find(x => String(getCatId(x)).toLowerCase() === wanted || catShareSlug(x) === wanted); if (target) { openModal(target); return true; } return false; };window.addEventListener("popstate", () => { const wantedId = requestedCatId(); if (!wantedId) { closeModal(); return; } openRequestedCat(); });const reveal = () => { requestAnimationFrame(() => { requestAnimationFrame(() => { ROOT.classList.add("asm-ready"); if (!initialDeepLinkOpened) { initialDeepLinkOpened = true; openRequestedCat(); } }); }); };if (document.fonts && document.fonts.ready) { document.fonts.ready.then(reveal).catch(reveal); } else { reveal(); } } catch (err){ console.error(err); showStatus( "Could not load adoptables from the proxy endpoint. Check that the StraySafe ASM Proxy plugin is active and /wp-json/straysafe/v1/adoptables returns data.", true ); qs("asm-page-label").textContent = "Load failed."; qs("asm-page-label-bottom").textContent = "Load failed."; ROOT.classList.add("asm-ready"); } }if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initAdoptables); } else { initAdoptables(); } })();