Replace will_paginate's view helpers with a custom prev/next + page
dropdown control.
Layout: [← Prev] [Page <select> of N] [Next →]
- Prev/Next are regular links (work without JS)
- Dropdown wrapped in <auto-submit-form> for JS-enhanced navigation
- "Go" submit button appears when JS is disabled
- Single pagination bar at top of results (removed bottom duplicate)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace the structured search filter approach with from_text(), enabling
the full search syntax (is:nc, occupies:background, user:owns, etc.) in
a single text field.
Changes:
- Search form now uses single `q` text param instead of `q[name]`
- Controller uses Item::Search::Query.from_text instead of from_params
- Auto-fit filter applied via .merge(Item.fits(body_id)) in controller
- Added error handling for Item::Search::Error
- Updated pagination param from `q[page]` to `page`
- Removed build_search_filters method (no longer needed)
- Fixed outfit_state_params helper to handle q as string
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Show "Own" (green) and "Want" (blue) badges on item cards when a logged-in user has items in their closet lists. Badges appear on both search results and outfit items.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Show which zones each item occupies in search result cards. Zone badges appear as small, muted labels (e.g., "Background", "Hat", "Static") after the NC/NP and date badges. Items that occupy multiple zones show multiple badges.
Zone badges only appear in search results, not in outfit items, since outfit items are already grouped by zone which provides sufficient context.
Implementation:
- Created zones badge partial to render zone labels from item appearances
- Updated controller to load item appearances with zone data for search results
- Pass appearance data through the rendering chain (search_results → item_card → item_card_content)
- Added subtle CSS styling for zone badges (light gray background, gray text)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add an info link (ℹ️) to each item card that opens the item's detail
page in a new tab. The link is:
- Subtle and hidden by default (opacity: 0)
- Visible on hover or focus-within for mouse/keyboard users
- Always visible (60% opacity) on touch devices
- Accessible with proper aria-label
- Opens in new tab with target="_blank" and rel="noopener"
This is the first commit of Phase 2 (Detail-Oriented Experience),
adding contextual navigation to item details.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Chrome has a known issue where `Vary: Origin` is not respected by its in-memory cache w/r/t CORS. By requesting the original image with `crossorigin="anonymous"` set, we provide `Origin` during this request, which gets the appropriate CORS response set in the cache.
Reproduce:
1. Add an item with animations, and play them.
2. Remove the item.
3. Add it back.
4. Observe the button shows up in "Paused" state, even though it's playing.
This is because the server-side template wasn't doing anything to try to keep the play/pause button it renders in sync with the current saved state in the cookies, so it was always causing a morph to the pause state. Now we listen to the cookie instead!
I also updated the JS behavior to be a bit more consistent: treat the behavior as defaulting to true, unless it's explicitly set to the string "false".
Rather than surface the fact that pose and style are independent values, in this change we treat them as basically mutually exclusive appearance options.
If there's no alt style selected, a pose option is visibly selected instead. If there's an alt style selected, no pose option is visibly selected (even though the data model contains one), and selecting one removes the alt style.
Note that these queries are a bit slow. I don't think these new subpages will be accessed anywhere near often enough for their ~2sec query time to be a big deal. But if we start getting into trouble with it (e.g. someone starts slamming us for fun), we can look into how how cache these values over time.
I pulled the source map for the Neopets renderer, and had Claude compare it to ours. It noticed the key issue responsible for a high number of unsolved rendering issues: we weren't setting up the `MotionGuidePlugin`, which I've never heard of before. Whoops!
In addition to this, we made some other minor fixes for consistency:
- Use whatever Stage object the library exports (will sometimes be StageGL)
- Resize the stage rather than the clip (shouldn't matter?)
- Send a callback to the library when done (I'm not aware of any anims that use this but some may!)
The specific item I was debugging was "Food Fight Shower", and it works now! But I also know we've had a solid handful of similar inexplicable wild rendering bugs, which I imagine this solves as well.
We might want to consider auditing our Known Glitches on SWF assets to see how many of them can be removed, now that this is resolved.
It says "This item is part of a deluxe paint brush set.", with a period instead of exclamation mark.
There are also a couple cases where the paint brush item additionally has an "inspired by" credit after the first sentence. Rather than get picky about the pattern, we expand the `pb?` method to just check for the sentence as a substring, like the DB filter logic for `Item.is_pb` already does.