Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ Tabby is a Manifest V3 extension (Chrome and Edge) that replaces the new tab pag
- "New" and "Senior" badges are computed server-side from optional RescueGroups fields — `availableDate` (with an adopted-then-returned branch keyed on `adoptedDate`) for New, `birthDate` with an `ageGroup` fallback for Senior. When the field an org needs isn't populated, that cat is simply excluded from the badge's evaluation rather than guessed at.
- Content-aware crop for portrait photos: a row-wise edge-energy heuristic finds the likely subject band instead of always anchoring to the top, via a small hostname-locked analysis-thumbnail proxy (`GET /api/photo-thumb`) that works around RescueGroups' CDN sending no CORS headers.
- "Share this cat" via the Web Share API, attaching the actual photo (fetched through a second hostname-locked, share-sized proxy, `GET /api/photo-share`, for the same CORS reason as the crop analysis above) alongside the cat's details, a Tabby tagline, and its RescueGroups profile link. Degrades to a link-only native share if the photo can't be attached, and to a clipboard-copy if the platform has no Web Share API at all.
- Save a cat via the heart icon on its photo: stores a snapshot (name, photo, rescue/profile links, etc.) in `chrome.storage.local`, not `.sync` — see the "Saved cats storage" note below for why — so a saved card still renders on the dedicated Saved cats page (`extension/saved.html`) even after the cat is adopted or drops out of the regular feed.
- No third-party runtime dependencies; Node's built-in test runner.

### Saved cats storage (issue #43)

Saved cats persist via `chrome.storage.local`, not `chrome.storage.sync`. Sync would survive an uninstall or follow the user to a new device, which is a better fit for "save something to revisit indefinitely" — but `webstore/WEBSTORE.md`'s Permissions Justification table explicitly promises Chrome Web Store reviewers that Tabby's `storage` permission never uses `chrome.storage.sync` and that nothing is synced to Google's servers. Switching to sync would mean deliberately rewriting that reviewed privacy claim, not just a code change, so for now saved cats stay local-only. Revisit if cross-device persistence becomes a real ask.

## Run locally

1. Copy `.env.example` to `.env` and fill in your real `RG_API_KEY`. `.env` is gitignored and never committed — never place the key in extension code either.
Expand Down
4 changes: 4 additions & 0 deletions extension/faq.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ <h2>Using Tabby</h2>
<summary>How do I share a cat with a friend?</summary>
<p>Click the share icon on any cat card. You can send it via text, email, or social media, or just copy the link.</p>
</details>
<details class="faq-item">
<summary>How do I save a cat to look at later?</summary>
<p>Click the heart icon on a cat's photo to save it. Saved cats stay on your Saved cats page (the heart icon in the header) even after they're adopted elsewhere or drop out of your regular feed. <strong>Saved cats are stored only on this device</strong>, not synced across your other computers or backed up anywhere else. Uninstalling Tabby, or using your browser's "Clear browsing data" with site data included, will erase your saved list.</p>
</details>
<details class="faq-item">
<summary>Is Tabby free?</summary>
<p>Yes, completely. No ads, no trackers, no premium tier.</p>
Expand Down
113 changes: 113 additions & 0 deletions extension/newtab.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ button:hover { background: var(--moss-dark); }
.icon-btn:hover { background: var(--wall); }
.icon-btn svg { width: 18px; height: 18px; }

/* Signals at least one saved cat exists (issue #43 follow-up) -- just a
stroke-color change via currentColor, not a filled/solid heart, so the
header icon doesn't start looking like a pressed toggle (it isn't one;
it always just navigates to the Saved cats page). */
.icon-btn.has-saved { color: var(--stamp); }

h1 { margin: 0; font-weight: 700; color: var(--ink); }

.card {
Expand Down Expand Up @@ -152,6 +158,33 @@ h1 { margin: 0; font-weight: 700; color: var(--ink); }
.badge.new { background: var(--moss); }
.badge.senior { background: var(--stamp); }

/* Save toggle (issue #43) -- mirrors .badges' overlay positioning but
anchored top-right instead, so it never collides with the New/Senior
badges at top-left. A solid translucent circle (rather than a bare icon)
keeps the heart legible against photos of any brightness, same reasoning
as the badge pills. */
.save-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
padding: 0;
border-radius: 50%;
border: none;
background: rgba(42, 38, 32, 0.45);
color: var(--paper);
cursor: pointer;
}
.save-btn svg { width: 18px; height: 18px; }
.save-btn:hover { background: rgba(42, 38, 32, 0.6); }
.save-btn.saved { color: var(--stamp); background: var(--paper); }
.save-btn.saved:hover { background: var(--paper); }

.content { padding: 16px 22px 22px; }

.name {
Expand Down Expand Up @@ -438,6 +471,86 @@ input { display: block; width: 100%; padding: 10px; margin-top: 4px; border: 1px
.faq-item[open] summary::after { transform: rotate(45deg); }
.faq-item p { margin: 0 2px 14px; color: var(--ink-soft); font-size: 13px; line-height: 1.55; }

/* Saved cats page (issue #43) */
.saved-list { display: flex; flex-direction: column; gap: 14px; }
.saved-item {
display: flex;
gap: 14px;
align-items: center;
border: 1px solid var(--card-edge);
border-radius: 10px;
padding: 10px;
}
/* 96px -- a clear step up from the original 64px without the row growing
tall enough to feel like a grid of photos instead of a compact list. */
.saved-item-photo-btn {
flex-shrink: 0;
width: 96px;
height: 96px;
padding: 0;
border: 0;
border-radius: 8px;
background: var(--wall);
cursor: pointer;
overflow: hidden;
}
.saved-item-photo-btn:hover { opacity: 0.85; }
.saved-item-photo {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.saved-item-info { flex: 1 1 auto; min-width: 0; }
.saved-item-name { font-family: var(--font-display); font-weight: 600; font-size: 1.05rem; margin: 0; }
.saved-item-meta { font-family: var(--font-mono); font-size: 0.75rem; color: var(--ink-soft); margin: 2px 0 0; }
.saved-item-links { margin: 4px 0 0; font-size: 0.8125rem; }
.saved-item-links a { color: var(--moss-dark); text-decoration: underline; text-underline-offset: 2px; }
.saved-item-remove {
flex-shrink: 0;
background: transparent;
color: var(--stamp);
border: 1.5px solid var(--stamp);
padding: 8px 12px;
font-size: 0.8125rem;
border-radius: 8px;
}
.saved-item-remove:hover { background: var(--stamp); color: var(--paper); }

/* Lightbox (issue #43 follow-up) -- a small hand-built overlay rather than
a library: the only behavior needed is "show the original-resolution
photo, dismiss on click-outside/Escape/close button", which doesn't
justify an external dependency. */
.lightbox-overlay {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
background: rgba(20, 18, 14, 0.85);
}
.lightbox-img { max-width: 90vw; max-height: 90vh; border-radius: 10px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5); }
.lightbox-close {
position: absolute;
top: 18px;
right: 18px;
display: flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
padding: 0;
border: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.15);
color: var(--paper);
cursor: pointer;
}
.lightbox-close:hover { background: rgba(255, 255, 255, 0.3); }
.lightbox-close svg { width: 20px; height: 20px; }

.footer {
margin-top: 32px;
padding-top: 18px;
Expand Down
1 change: 1 addition & 0 deletions extension/newtab.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
</div>
<div class="header-actions">
<button id="explore" class="icon-btn" type="button" aria-label="Explore another area" title="Explore another area"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"/></svg></button>
<button id="saved" class="icon-btn" type="button" aria-label="Saved cats" title="Saved cats"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg></button>
<button id="faq" class="icon-btn" type="button" aria-label="Frequently asked questions" title="Frequently asked questions"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg></button>
<button id="settings" class="icon-btn" type="button" aria-label="Settings" title="Settings"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/><circle cx="12" cy="12" r="3"/></svg></button>
</div>
Expand Down
Loading
Loading