1
0
Fork 0
forked from OpenNeo/impress
Commit graph

1458 commits

Author SHA1 Message Date
7688caebe1 Handle iframe outright failing to load in new item page preview
This is a nice extra error handler to have, but note that it *won't*
catch the case where the iframe successfully loads but the page returns
a bad status code. In this case, we'll just show the loading state
forever.
2024-07-08 10:44:01 -07:00
fcad7e2bc9 Add a clearer error indicator to new item page previews 2024-07-08 10:36:16 -07:00
d18f43d769 Fix a bug transitioning between two loading states in item page preview 2024-07-07 21:52:38 -07:00
059644f847 Improve the loading indicator for the new item page previews
Add some unobtrusive white background for contrast, show it when the
Turbo frame is loading too, add a spinner cursor, and fix a silliness
of how we put the `position: absolute` stuff into the component-y part
of the hanger spinner instead of this specific use case lol oops!
2024-07-07 21:32:41 -07:00
83eec2db60 Merge branch 'main' into simpler-item-previews 2024-07-07 21:11:32 -07:00
3f47f47ced Prevent analytics script delays from blocking other JS on the page
Oh oops, this is the first script on the page with the `defer`
attribute, which means it needs to run before other scripts with
`defer`—and in this moment, it's not loading for me, which means the
pages aren't working!

I assume Plausible told me to use `defer` rather than `async` because
it expects the page to be ready; okay! Let's just move this to the
very body of the `<head>` instead, so it isn't taking priority over
anything else.
2024-07-07 21:11:14 -07:00
6bc0c55000 Use our hanger loading spinner for the new item page previews
It's back, wowie! Also written up in a bit of a component-y way. Cute!
2024-07-07 19:05:48 -07:00
c8c4facb60 Fix styling for embed page for image-only SWF assets
This doesn't actually really matter, because this doesn't actually get
used in the app right now? But I figure, hey it's not hard to maintain,
let's just do it for consistency!
2024-07-06 12:52:00 -07:00
7ec900b6b6 Use {script,style}_src instead of _elem, for better compatibility
Oh, I didn't realize the `_elem` variant of these parts of the
`Content-Security-Policy` is newer, and so doesn't even work on my
current version of Safari on my Mac.

My rationale at the time was: `script_src_elem` is stricter against
things like imports, and I figured, ok let's do the strictest policy
that works. But since it's not fully compatible with browsers even
*I'm* using right now, and I'm not aware of an actual problem it would
prevent, let's back off that a bit! This should have the same effective
security properties for our case.

Note that the effect of this compatibility issue wasn't *weakening* the
policy; it was being *too* strict, by blocking the scripts and the
stylesheets. This is because `script_src_elem` was ignored, and
`script_src` was absent, so it fell back to `default_src none`.
2024-07-06 12:52:00 -07:00
57c08b5646 Use "morphing" for smoother item page preview changes
The most notable thing here is that we keep the movie iframes running!
So if you're trying different pets for an animated item, the animation
keeps going while the new pet layers load alongside it.

This is also nice for like, the species/color picker form, so we're not
taking away input elements from people who depend on e.g. keyboard
focus.
2024-07-03 21:52:43 -07:00
97e6c39402 Load movies in iframe for new item page preview
Hey hey, it's working! Still stuff to add like pause/play, but yeah!
2024-07-03 20:29:04 -07:00
5b2062754d swf_assets/show action to embed a canvas movie in a sandboxed iframe
Not using this on the item page preview yet, but we will!

I like this approach over e.g. a web component specifically for the
sandboxing: while I don't exactly *distrust* JS that we're loading from
Neopets.com, I don't like the idea of *any* part of the site that
executes arbitrary JS unsafely at runtime, even if we theoretically
trust where it theoretically came from. I don't want any failure
upstream to have effects on us!

I copied basically all of the JS from a related project
`impress-media-server` that I had spun up at one point, to investigate
similar embed techniques. Easy peasy drop-in-squeezy!
2024-07-03 19:50:41 -07:00
5ad320fa18 Remove unused swf_assets/links stylesheet
This must be for a page we got rid of! Ok bye!
2024-07-03 19:17:08 -07:00
81a58f8656 Extract outfit-viewer to a separate template
Just cuz this is gonna get more complex down the line!
2024-07-02 22:43:36 -07:00
76d2cc6c21 Add some data attributes to outfit-layer elements
This is mostly for debugging, the zone label especially, to just
eyeball what we're looking at!
2024-07-02 22:38:18 -07:00
e9145251a9 Refactor outfit-layer component to use internal state API, not attrs
Oh right okay, attributes like `status="loading"` are more of an API
for the caller, whereas the internal state API is where you wanna put
things that are meant to be used in CSS selectors and stuff.
2024-07-02 22:34:51 -07:00
3c415e9cd3 Refactor item page outfit-layer to use Web Components
Instead of doing all this listening to Turbo events etc to know when
outfit layers might have changed, making it a custom element and wiring
in the behavior to its actual lifecycle makes it always Just Work!
2024-07-02 22:24:26 -07:00
857812610a Refactor outfit_viewer_layers helper to just be inlined into template
I forget what complexity was in here previously that made this make
sense before, but now it's just a loop, whatever!
2024-07-02 22:03:43 -07:00
0a9193aed7 Add basic loading tracking to new item page preview
The UI for it is just basic for my own testing rn: it sets the preview
background to gray while loading, then back to white when done!

This uses the new CSS `:has()` selector: we have JS manage the loading
state on each layer, then the container just restyles itself based on
whether any currently-loading layers are present.
2024-07-01 17:59:07 -07:00
ac002f3151 Track preferred color/species for new item page previews
Also adapted from the Impress 2020 logic!

Note that I refactored `compatible_pet_type` to a series of scopes on
`PetType`. I think this is a simpler, clearer, and more flexible API!
2024-07-01 17:38:31 -07:00
fe6035d438 Default to compatible pet types in new item page preview
Just adapted from Impress 2020 logic again, easy peasy!
2024-07-01 17:20:38 -07:00
21a8a49f50 Remove redundant SwfAsset relation
Huh, this is just in here twice? Weird. Goodbye!
2024-07-01 17:20:05 -07:00
3f38fbd1b0 Support zone restriction in new item preview
Adapted from wardrobe-2020's `getVisibleLayers`! Thanks past-Matchu for
all the comments lol!
2024-07-01 16:54:39 -07:00
74748acaaf Refactor more of item outfit preview into the Outfit class
This is a cute thing that I think sets us up for other stuff down the
line: move more of the outfit appearance logic into the `Outfit` class!
Now, we set up the item page with a temporary instance of `Outfit`,
then ask for its `visible_layers`.

Still missing restricted-zones logic and such, that's next!
2024-07-01 16:07:25 -07:00
054c809052 Put the new item preview in a Turbo frame
Nice, gotta say, this is a pretty neat way of making things feel more
app-y! There's some missing pieces here about like, loading state etc,
but the vibes are pretty good, and the implementation was dead-easy!
2024-07-01 15:35:58 -07:00
1b000189c4 [WIP] Add species/color picker for simplified item page preview
Still a lot missing here, like choosing the right default for Baby etc
items, and saving the user's preferences. But it's a start!
2024-07-01 15:35:58 -07:00
ea17e76c39 [WIP] Start replacing item page preview with simpler HTML-based version
Just stripping out the big React component, and having Rails output it!

There's a lot of work rn in extracting the Impress 2020 dependency from
the `wardrobe-2020` React app, and I'm just curious to see if we can
simplify it at all by pulling this stuff *way* back to basics, and
deleting the item page part of `wardrobe-2020` altogether.

In this draft, we regress a lot of functionality: it just shows the
item on a Blue Acara, with no ability to change it! I'm gonna play with
putting more of that back in.

I also haven't actually removed any of the item page React code; I just
stopped calling it. That can be a cleanup for another time, once we're
confident in this experiment!
2024-07-01 15:35:58 -07:00
a70b70be7d Merge remote-tracking branch 'origin/main' 2024-07-01 14:56:08 -07:00
2a34e8be6d Oops, fix regex patterns to use \A to \z instead of ^ to $
Oh huh, TIL in Ruby `^` *always* means "start of line", whereas in many
languages' regular expression engines it means "start of string" unless
you enable a special multiline flag for the pattern.

I've fixed this in a number of expressions now!

I'm noticing this in the context of doing some security training work
where this the cause of a sample vulnerability, but, looking at our own
case, I don't think there was anything *abusable* here? But this is
just more correct, so let's be more correct!
2024-06-28 01:32:15 -07:00
5401ea984a Merge remote-tracking branch 'origin/main' 2024-06-22 18:47:10 -07:00
d5752eac2a Copy edits for Dyeworks in Item Getting Guide
I think the parens are silly now that this paragraph is just kinda all
bonus clarification info anyway. And I wanted to explain the cost
computation for the potions, and highlight the bundle thing!
2024-06-20 14:40:09 -07:00
302c116c8f Don't color PB shop/trade buttons purple in Item Getting Guide
I think it's clearer to just keep the purple meaning "NC", and in
particular "bulk NC Mall purchase"
2024-06-20 14:20:06 -07:00
568c30fa90 Wider tables for longer item names in Item Getting Guide
If the item names are long, it helps to give them more room to breathe!
Whereas if they're short, it looks silly and makes it harder to scan
the table.

Just an extra bit of help for e.g. Dyeworks items with long names!
2024-06-20 14:16:51 -07:00
b137eed4c4 Oops, handle date parsing errors in Dyeworks logic
Huh, I thought I'd tried some invalid dates and they gave me
*surprising* output instead of raising an error. Well, maybe it can do
both, depending on exactly the nature of the unexpected input?

In any case, I found that a bad month name like "UwU" raised an error.
So, let's catch it if so!
2024-06-20 14:08:40 -07:00
965725f9e9 Oops, fix silly bug in Dyeworks Owls date parsing
Oh right, if I assume "date in the past means it's for next year", then
that means that, when the date *does pass*, we won't realize it!

e.g. if Owls says "Dyeable Thru July 15", then on July 14 we'll parse
that as July 15, 2024; but on July 16 we'll parse it as July 16, 2025,
and so we'll think it's *still* dyeable. Under this logic, it's
actually impossible for a limited Dyeworks date to *ever* be in the
past, I think!

I think 3 months is a good compromise: it gives Owls plenty of time to
update, but allows for events that could last as long as 9 months into
the future, if I'm doing my math right.
2024-06-20 14:05:00 -07:00
341a8dd89c Disallow text wrapping in the "Total" cell in Item Getting Guide
The table layout algo can get a bit funky about how it assigns extra
space, I want to encourage things like "Total: 5 items" etc not to
wrap, esp in the Dyeworks case where it's quite long!
2024-06-20 13:55:04 -07:00
3d6abc84dd Layout tweaks to Dyeworks in Item Getting Guide
There's more and more going on in here! Let's omit the base item name,
increase the table width a bit in this case, and tweak the rest a bit
while we're here.
2024-06-20 13:50:48 -07:00
cec29682c4 Add NC Trades button to Dyeworks in Item Getting Guide 2024-06-20 13:50:04 -07:00
589d728c76 Add clearer Dyeworks explainer
I uhhh literally didn't know Dyeworks was a gacha system until Kaye
from the Owls team told me lmao

I should maybe uhh read more guides instead of assuming I've osmosed
things correctly oops!
2024-06-20 13:21:56 -07:00
2e3d5d2020 Vaguer potions info for Dyeworks in Item Getting Guide 2024-06-20 13:14:51 -07:00
97abd6e438 Add probabilities to Dyeworks items in Item Getting Guide
I'm gonna better explain the gacha nature, I'm doing this part first!
2024-06-20 12:54:39 -07:00
1d250f3148 Remove unused Item.with_closet_hangers scope
Idk why we thought this made sense way back when? But it evidently has
no call sites now so. Goodbye!
2024-06-19 17:49:18 -07:00
b6bd539fed Oops, fix indentation in Item::Dyeworks
Ahh, I started a tabs-y file (as I default to these days), but copied
code from a spaces-y file, and didn't notice. (My laptop editor isn't
configured to flag this for me, oops!)

Fixed!
2024-06-18 16:50:43 -07:00
10301900e2 Credit the Owls team for Dyeworks extra info in the Item Getting Guide 2024-06-18 16:49:09 -07:00
ec476f4c65 List the specific Dyeworks end date in Item Getting Guide, if we know 2024-06-18 16:47:05 -07:00
4e2110bf25 Add comment to Item#inferred_dyeworks_base_item 2024-06-18 15:26:28 -07:00
c6fbb9b797 Reorder Item::Dyeworks methods a bit 2024-06-18 15:23:39 -07:00
98ecbf029d Be case-insensitive when checking Owls values for Dyeworks status
Just to be a bit more resilient! I'm not aware of any issues rn but
this seems wise!
2024-06-18 15:22:15 -07:00
015010345a Extract Dyeworks methods into Item::Dyeworks module
There's just starting to be a lot going on, so I pulled them out into
here!

I also considered a like, `Item::DyeworksStatus` class, and then you'd
go like, `item.dyeworks.buyable?`. But idk, I think it's nice that the
current API is simple for callers, and being able to do things like
`items.filter(&:dyeworks_buyable?)` is pretty darn convenient.

This solution lets us keep the increasing number of Dyeworks methods
from polluting the main `item.rb`, while still keeping the API
identical!
2024-06-18 15:21:43 -07:00
26dfe13b0e Parse "Limited Dyeworks" items from Owls
Yay thank you Owls team! I might also try to parse the dates too, the
format seems to be "Owls: Limited Dyeworks - Dyeable Thru July 15"
2024-06-18 14:59:09 -07:00
1acb00e35a Add NC prices to item pages 2024-06-18 13:25:29 -07:00
bbd1849e19 Oops, fix crash for Dyeworks without Owls info in Item Getting Guide
Silly mistake, right, we might not have a trade value listed! This is
relevant for the new Dyeworks items that just came out like a few hours
ago, which Owls doesn't have info for yet.
2024-06-17 13:07:03 -07:00
598a9dac52 Oops, fix crashing bug when Item Getting Guide has *no* Dyeworks
Lmao I've been testing with an outfit that has all the kinds of items,
so I didn't notice that this new refactor to `@items[:dyeworks]` style
of tracking the items returns `nil` when there's none, instead of `[]`.
(I always make this mistake when I use `group_by` lmao sob)

In this change, we give the `@items` hash a default value, so that will
stop happening!
2024-06-17 13:03:12 -07:00
9f536f81b3 Refactor to use a new Item#source method for where an item is from
I'm doing this in preparation for maybe trying to load some of this
info into the outfit editor, too!
2024-06-16 12:37:53 -07:00
77f01a6cb9 Edge case: say "0 NC" for cost of no potions in Item Getting Guide 2024-06-16 12:26:55 -07:00
cd28c26ae7 Make thumbnail_url a manually overridable field for Alt Styles
Oh jeez, okay, the latest batch of Alt Styles are using a different URL
format for the thumbnail image!

This isn't something we can import via modeling, so we're gonna have to
keep on top of it manually. For now, I'll keep inferring the previous
format in case they keep using it, but here's also a console script to
fix up the latest batch. (At time of writing, not all of these are in
our database, which is fine; when pasting it into the console, those
lines will error and the script will continue.)

```rb
def update_style(color_name, species_name, thumbnail_url)
	AltStyle.find_by_color_id_and_species_id(
		Color.find_by_name(color_name),
		Species.find_by_name(species_name)
	).update!(thumbnail_url:)
end

update_style "Grey", "Blumaroo", "https://images.neopets.com/items/c0gk16fk.gif"
update_style "Grey", "Bori", "https://images.neopets.com/items/55qvx6mr.gif"
update_style "Grey", "Bruce", "https://images.neopets.com/items/6y6pyiuw.gif"
update_style "Grey", "Buzz", "https://images.neopets.com/items/7fh4avry.gif"
update_style "Grey", "Chia", "https://images.neopets.com/items/7b2jtn10.gif"
update_style "Grey", "Elephante", "https://images.neopets.com/items/0ne41rao.gif"
update_style "Grey", "Gnorbu", "https://images.neopets.com/items/75mwtqmh.gif"
update_style "Grey", "Hissi", "https://images.neopets.com/items/dxdi2mhm.gif"
update_style "Grey", "Kiko", "https://images.neopets.com/items/b9yiruxt.gif"
update_style "Grey", "Lenny", "https://images.neopets.com/items/c6cboc7e.gif"
update_style "Grey", "Lutari", "https://images.neopets.com/items/33fs4eqf.gif"
update_style "Grey", "Nimmo", "https://images.neopets.com/items/4karmgbl.gif"
update_style "Grey", "Ogrin", "https://images.neopets.com/items/dlw78fhk.gif"
update_style "Grey", "Quiggle", "https://images.neopets.com/items/0aipl0iw.gif"
update_style "Grey", "Ruki", "https://images.neopets.com/items/bjnjxsem.gif"
update_style "Grey", "Tuskaninny", "https://images.neopets.com/items/7rh57a0o.gif"
update_style "Grey", "Vandagyre", "https://images.neopets.com/items/6p8sgs69.gif"
update_style "Grey", "Xweetok", "https://images.neopets.com/items/bge9vp5e.gif"
```
2024-06-15 17:35:12 -07:00
36452a4704 Flag items you already own in the Item Getting Guide 2024-06-15 15:47:52 -07:00
56fe5e4889 Oops, fix bug in recent movie-pausing bugfix!
Oh right, this previously logic was silly: we can't count on the
*interval itself* to be reliably resetting the FPS counter state,
because the interval might not be firing!

I think this fix worked when I tried brief tests, but didn't work when
I did an (accidental) longer test, because the browser switched to a
more aggressive throttle mode, and the previous mode was close enough
on the resets for it to be fine, whereas this time the FPS counter
state got way too old.

Now, we reset the FPS counter state *exactly* when the page comes back.
2024-06-12 17:14:16 -07:00
60e9130891 Fix bug where changing tabs would *always* pause the outfit animations
We have a feature to check the movie's FPS, and pause it if it gets too
low, as a guard against especially low-performance movies. But this was
triggering in an *expected* case, where browsers intentionally throttle
interval events when a page is in the background (e.g. you switch to
another tab).

Now, our rendering is aware of page visibility: when the page is
hidden, don't bother rendering, and keep resetting the FPS counter
state, so that we can pick up with a fresh FPS counter when the page
comes back.
2024-06-12 17:05:53 -07:00
3310394fb8 Improve Owls value presentation in Item Getting Guide 2024-06-09 19:22:34 -07:00
be525d1d67 Oops, fix bug with empty subtitles in Item Getting Guide
Oh weird, even with `flush: true`, `content_for` will ignore an empty
block and *not* flush out the previous content. This could cause rows
whose subtitles *should* have been empty (e.g. no NC trade value) to
display the previous row's value instead.

Let's make this whole situation a bit more robust by having the
*template* clear out the subtitle right before calling the block. That
way, a previous row's value *can't* get in, no matter what.
2024-06-09 15:40:58 -07:00
3dbcf41e4f Restyle Owls link a bit in Item Getting Guide
I think it helps a bit to have only the label be dotted-underlined, to
hint that I'm offering help about what that *means*, but clear the way
for the value itself to be more visible and less cluttered.
2024-06-09 15:22:21 -07:00
9bc6be05f8 Add "Permanent" label to permanent Dyeworks items in Item Getting Guide 2024-06-09 15:21:42 -07:00
a3f910b7ce Add more line breaks to Item Getting Guide template source
Idk, as things get more complex, it was starting to get too crunchy!
2024-06-09 15:05:00 -07:00
1f0c8b87a6 Move Item Getting Guide subtitle UI out of helpers, into templates
I thought to myself, "I wonder if it's possible to use a sneaky hacky
`content_for` trick to be able to run this code in the template." And
indeed it is!

It's tricky cuz like, I want to render this template, and I want to
provide _multiple_ slots of content to it. So, in this variant, we keep
the block as being primarily for the actions, but also optionally
accept `content_for :subtitle` inside that block, too.

Executing that correctly is a bit tricky! The subtitle comes *before*
the actions. So, we `yield` the actions block immediately, save it to a
variable, and *then* get the subtitle block.
2024-06-09 15:03:35 -07:00
b22ccbc2a3 Use Owls to check for Permanent Dyeworks items
Previously, I added a Dyeworks section that was incorrect: the base
item being available in the NC Mall does *not* mean you can necessarily
dye it with a potion!

In this change, we lean on Owls to tell us more about Dyeworks status,
and only group items in this section that Owls has marked as "Permanent
Dyeworks".

We don't have support for limited-time Dyeworks items yet—I've sent out
a message asking the Owls team for more info on what they do for those
items!
2024-06-09 14:46:24 -07:00
5de9e2a27b Add Dyeworks section to Item Getting Guide (but it's currently wrong!)
I started writing this up, then sent a preview to a friend, and he was
like "oh cool, but also this is not correct?"

I didn't realize Dyeworks has limited-time support to be *able* to dye
certain items. Hey, glad we're writing this guide for people like me,
then! lol

I wonder if we can lean on Owls for this. It seems like they already
list "Permanent Dyeworks" for some items, I wonder if they say
something special for active limited-edition Dyeworks items!
2024-06-09 13:25:59 -07:00
857cb547ed Add Item#dyeworks_base_item database field, and populate it
In this change, instead of *always* inferring the Dyeworks base item
from the item name at runtime, we now have a database field that tracks
it, and auto-populates whenever an item *seems* to need a Dyeworks base
item but doesn't have one yet.

This will enable us to set the base item manually in cases where it
can't be inferred, and load Dyeworks base items for the Item Getting
Guide in one query with `includes(:dyeworks_base_item)`.

This migration does a bit more of the fix-em-up scripting work *in* the
migration itself than I usually do, mainly because there's so much in
this one that I think being extra-explicit is useful. We make sure to
do it gracefully though!
2024-06-07 20:10:06 -07:00
68cb44d159 Add logic to infer the base for Dyeworks items
This works for most of the current 1,094 Dyeworks items! But there are
a few exceptions, for cases where the base item name is not quite the
same (e.g. the Dyeworks version is more concise). Maybe we'll add a
database field to override this?

- Dyeworks Baby Blue: Baby Valentine Jumper
- Dyeworks Baby Pink: Baby Valentine Jumper
- Dyeworks Black: Field of Flowers
- Dyeworks Black: Games Master Challenge 2010 Lulu Shirt
- Dyeworks Blue: Field of Flowers
- Dyeworks Blue: Stars and Glitter Facepaint
- Dyeworks Brown: Hanging Winter Candles Garland
- Dyeworks Green: Stars and Glitter Facepaint
- Dyeworks Magenta: Lovely Berry Blush
- Dyeworks Orange & Pink: Winter Lights Effects
- Dyeworks Orange: Games Master Challenge 2010 Lulu Shirt
- Dyeworks Peach: Lovely Berry Blush
- Dyeworks Purple: Baby Valentine Jumper
- Dyeworks Purple: Games Master Challenge 2010 Lulu Shirt
- Dyeworks Purple: Hanging Winter Candles Garland
- Dyeworks Purple: Stars and Glitter Facepaint
- Dyeworks Red & Green: Winter Lights Effects
- Dyeworks Silver: Hanging Winter Candles Garland
- Dyeworks Soft Pink: Lovely Berry Blush
- Dyeworks Yellow & Magenta: Winter Lights Effects
- Dyeworks Yellow: Field of Flowers
2024-06-07 19:35:43 -07:00
341869fb17 Oops, say "these items" plural for basic colors in Item Getting Guide
We could do a whole thing about like, checking singular vs plural, but
I'd rather just keep it simpler; I think it's clear from context that
we're talking about a category, so plural is fine even if it's not
actually more than one.
2024-06-05 19:51:11 -07:00
31c281390d Add explanations for why Item#pb_{species,color} would return nil 2024-06-05 19:46:12 -07:00
a3bd841bb8 Fix handling of basic PB items in Item Getting Guide
Okay so, like 30 minutes ago I added fallback behavior for cases where
we can't correctly infer the color from a PB item's name… and then I
pulled it up in the color and found that, oh, right, there are already
3 PB items that *correctly* return `nil` for `Item#pb_color`: Aisha
Collar, Elephante Hat, and Ixi Collar.

This is because they're common items that apply to many colors, like
the basics, but also many other less-special or older color variants.
They are the most likely case where we'll return `nil`.

So, I've updated our fallback UI to, instead of talk vaguely about
missing data, just assume that we're dealing with basic items. In the
rare window of time where a new color is released, and we have PB items
for it but no manual color data yet, this can just incorrectly say
"Basic Colors" and that's fine.
2024-06-05 19:45:39 -07:00
bd6b6450d9 Handle newly-released species in Item Getting Guide
This is less likely than the newly-released color case for PB items,
but I figure let's be resilient anyway, especially since it's so easy
to—and also I figure this is less likely to be triggered by an *actual*
new species, and more likely to be triggered by a surprise in an item's
naming conventions.

But yeah, if `Item#pb_species` returns `nil` upstream, it'll be passed
to `Color#example_pet_type`, which will crash trying to read its ID. So
in this change, we update `Color#example_pet_type` to accept a `nil`
value, and fall back to the first Species (Acara) in that case.

This means that, if you e.g. take the Mutant Aisha Collar and delete
the word "Aisha" from the name, then load it in the Item Getting Guide,
you'll see a thumbnail of a Mutant Acara. Good enough!
2024-06-05 19:27:38 -07:00
3dab235335 Handle newly-released PB items in Item Getting Guide
Oh right, it's possible for `Item#pb?` to return true, but
`Item#pb_color` to return `nil`, if the item has the paintbrush item
description but we can't find a color whose name matches the item name.
This would be expected if a new color were added to Neopets, and PB
items for it were modeled by the community, but we hadn't manually
added the color to the database yet.

Previously, the Item Getting Guide would crash in this scenario. Now,
it correctly handles the possibility of a `nil` value for `pb_color`,
and shows some placeholder info.

To test this, I temporarily edited some item names to not contain the
color name anymore (e.g. "P-rate Elephante Shirt and Vest"), then
loaded the guide and made changes until it no longer crashed.
2024-06-05 19:23:57 -07:00
193b1fa5e3 Improve performance for occupies:X searches
I noticed in the app that these queries were slowwww! I was able to
track it down to a bad query plan, as we explain in the comment.

I searched online for "mysql query performance filter on one join table
sort by another", and was surprised to find this answer suggest a
subquery, which I've often been told to expect to be slower compared to
joins? But it certainly worked in this case!

https://stackoverflow.com/questions/35679693/mysql-optimize-join-with-filter-and-order-on-different-tables
2024-06-03 11:45:51 -07:00
63abfa51f7 Redesign "Get these items!" button to use a cart icon instead
This helps it fit in the kinda crunchy horizontal space a lot better!
2024-05-31 17:38:25 -07:00
1e99376449 Remove hundreds of unnecessary queries from /alt-styles page!
I think the Rails query cache handled these anyway? But `SwfAsset` has
a `before_save` hook that checks its zone's info, and
`SwfAsset.preload_manifests` saves all the assets, on the assumption
that saving is a no-op when the record didn't change anyway. And it
basically is!

But I figure that, now that I'm realizing hooks exist, simply not
attempting to save unchanged records is probably a better
representation of what we intend to do. So I'm fixing it like that!

Another potential fix would be to preload the zones for these assets,
but I think that confuses the intent too much; the method itself isn't
using the zones, it's just a weird incidental thing that a save hook
happens to use. (Would probably be better to refactor this old save
hook into a different situation altogether, but that's for another
time!)
2024-05-29 18:52:36 -07:00
244bb7627a Remove outdated info from Alt Styles page
Apparently I correctly updated the first paragraph, but not the third
one, oops lol!
2024-05-29 18:47:06 -07:00
285cf233f0 Assume new alt styles are "Nostalgic" until the end of 2024 2024-05-29 18:46:17 -07:00
b06e8a25c0 Validate presence of body ID when saving AltStyle
This is a minor nbd change, I just noticed when playing around in the
console that, unlike most other errors for this model, the `body_id`
being required is _only_ enforced in the database schema, so it isn't
returned with the usual errors. Not a big deal! Just feels like this is
clearer to work with, and more correct to what we *intend*.
2024-05-29 18:42:41 -07:00
c78d51ab01 Don't hardcode the series name in AltStyle thumbnail_url
This is just a bit of future-proofing! We also add a default thumbnail
URL of the cute "Neopets Circle Background", for cases where the series
name isn't known yet.
2024-05-29 18:36:30 -07:00
72c739ca0f Improve layout for "Get these items!" and "Save" buttons in editor
Make them the same size, add spacing between them, and also put the
"Get these items!" on the right, because the list is right-aligned and
the Save button has dynamic width (the save vs saving vs saved states),
so this makes things a lot more consistent and stable!
2024-05-28 17:32:18 -07:00
bb0d219508 Don't let action buttons wrap onto multiple lines in Item Getting Guide
A funny table-layout bug, where the item "Portal to the Unknown" had a
very long Owls listing ("Owls listing: Buyable - Magic Lens + Blank
Grey Tome (NP)"), and so the table layout tried to give it more room by
decreasing the width of the action cell and wrapping the "NC Trades"
action button text onto multiple lines.

The fix: don't allow that! The table layout will figure out how to
handle this being disallowed, and give the actions cell an appropriate
minimum width.
2024-05-28 17:16:41 -07:00
f84e340899 Add link to Item Getting Guide from outfit editor (behind secret link)
You can now go to `/outfits/new?features=get-these-items` to start
seeing the "Get These Items!" button in the outfit editor!

I haven't tested it a ton yet, but yeah here it is!
2024-05-28 17:06:23 -07:00
671e0becb6 Oops, fix recent bug in Item Getting Guide for most item types
Oh oops, I added an optional `subtitle` argument for the item table
list row template, but didn't realize that it would crash in cases when
not provided! Now, we add an initializer to set it to `nil` if
undefined.
2024-05-28 17:05:28 -07:00
3eb40b2a4f Preload SWF asset manifests during wardrobe item search
This doesn't matter a ton in production, where we already have most of
our manifests loaded! But it matters a lot on my relatively-fresh
development instance, at times like now when images.neopets.com is slow
to respond. A single item search was taking minutes before this change
(5 seconds of timeout per asset for 30 items!), but now takes a few
seconds the first time, as it should!
2024-05-28 17:05:04 -07:00
d3d3297a28 Oops, fix recent bug in Owls value widget on item page
Oh right, I was briefly planning to change the syntax for this stuff,
then changed my mind, but forgot to change it back on this page!
2024-05-28 16:21:03 -07:00
758b62e7d5 Improve performance of Owls values in Item Getting Guide
Now we preload them all concurrently, instead of in sequence when the
template gets around to asking for them!
2024-05-27 16:21:22 -07:00
551e8941f3 Add OWLS values to non-NC-Mall NC items in Item Getting Guide
Note that there's a known performance issue here: we should try to
fetch all the OWLS values at once, instead of doing them in sequence
while rendering the page!
2024-05-27 15:53:08 -07:00
c49cf52939 Remove CSS reset that removes focus outline from all elements by default
Oh jeez, idk why this was ever in here, but yeah no, I want to be using
default browser focus outlines unless specifically overridden otherwise.
Will help keyboard navigation a lot! Yikes!!
2024-05-27 15:49:12 -07:00
b2615eaf00 Use table layout for other NC items in Item Getting Guide
To start, we just link to DTI's own NC trades. I also want to add OWLS
values in here too!
2024-05-27 14:25:12 -07:00
d34bebc336 Use a pet face when there's no paint brush, in Item Getting Guide
Now, for colors like Mutant or Magma where there's no paint brush
image to show, we use a sample pet image instead, to help it have equal
visual weight and clarity as the cases with the paint brushes.

We do some cleverness in here to make sure to always show the relevant
species, if possible!
2024-05-22 17:53:52 -07:00
4e11ee4da7 Add paint brush images to Item Getting Guide
In the previous change, we started grouping PB items by color. But I
wanted to better express that the grouping itself *is* an item, and the
items below it are secondary!

The main change we make here is to leverage DTI's existing design
language that "thumbnail image means item", and record thumbnail URLs
as well as paint brush names now, too!

We're still not leveraging the full Item system here, just fields on
Color. Keeping it simple for now!

Here's the script I ran to add the paint brush images to all the
existing colors!

```rb
Color.find_by_name("Baby").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/babypaintbrush.gif")
Color.find_by_name("Biscuit").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_biscuit.gif")
Color.find_by_name("Blue").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/bluepntbrsh.gif")
Color.find_by_name("Brown").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/brownpntbrush.gif")
Color.find_by_name("Camouflage").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_camo.gif")
Color.find_by_name("Candy").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_candy.gif")
Color.find_by_name("Checkered").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/checkeredpntbrush.gif")
Color.find_by_name("Christmas").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/xmaspaintbrush.gif")
Color.find_by_name("Cloud").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/cloudpntbrush.gif")
Color.find_by_name("Darigan").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/pb_darigan.gif")
Color.find_by_name("Dimensional").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_dimensional.gif")
Color.find_by_name("Disco").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/discopntbrush.gif")
Color.find_by_name("Electric").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/electricbluepntbrush.gif")
Color.find_by_name("Eventide").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/eventidepaintbrush.gif")
Color.find_by_name("Faerie").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/faeriepntbrush.gif")
Color.find_by_name("Fire").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/firepntbrush.gif")
Color.find_by_name("Elderlyboy").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/pb_elderly.gif")
Color.find_by_name("Elderlygirl").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/pb_elderly.gif")
Color.find_by_name("Ghost").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/ghostpntbrush.gif")
Color.find_by_name("Glowing").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/glowingpntbrsh.gif")
Color.find_by_name("Gold").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/gold_pntbrush.gif")
Color.find_by_name("Green").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/greenpntbrsh.gif")
Color.find_by_name("Grey").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/greypntbrush.gif")
Color.find_by_name("Halloween").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/halloweenpntbrsh.gif")
Color.find_by_name("Invisible").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/invisiblepntbrsh.gif")
Color.find_by_name("Desert").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/desertpaintbrush.gif")
Color.find_by_name("Maractite").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/maractitepaintbrush.gif")
Color.find_by_name("Maraquan").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/maraquanpntbrush.gif")
Color.find_by_name("Marble").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_marble.gif")
Color.find_by_name("Island").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/islandpntbrush.gif")
Color.find_by_name("Oil Paint").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/oilpaintingpntbrsh.gif")
Color.find_by_name("Orange").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/orange_paint_brush.gif")
Color.find_by_name("Origami").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_origami.gif")
Color.find_by_name("Pastel").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_pastel.gif")
Color.find_by_name("Pink").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/pink_paint_brush.gif")
Color.find_by_name("Pirate").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/piratepntbrush.gif")
Color.find_by_name("Plushie").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/plushiepaintbrush.gif")
Color.find_by_name("Polka Dot").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_polkadot.gif")
Color.find_by_name("Purple").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/purplepntbrsh.gif")
Color.find_by_name("Rainbow").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/rainbowpntbrsh.gif")
Color.find_by_name("Red").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/redpntbrsh.gif")
Color.find_by_name("Relic").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/relic_pntbrush.gif")
Color.find_by_name("Royalboy").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_royal.gif")
Color.find_by_name("Royalgirl").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_royal.gif")
Color.find_by_name("Sketch").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/sketchpntbrush.gif")
Color.find_by_name("Shadow").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/shadowpntbrsh.gif")
Color.find_by_name("Silver").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/silverpntbrsh.gif")
Color.find_by_name("Skunk").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/skunkpntbrush.gif")
Color.find_by_name("Snow").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_snowman.gif")
Color.find_by_name("Speckled").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/greenwhitedotspntbrush.gif")
Color.find_by_name("Split").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/halfandhalfpntbrsh.gif")
Color.find_by_name("Spotted").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/spottedpntbrush.gif")
Color.find_by_name("Starry").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/starspntbrush.gif")
Color.find_by_name("Stealthy").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/pb_stealthy.gif")
Color.find_by_name("Steampunk").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_steampunk.gif")
Color.find_by_name("Strawberry").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/strawberrypntbrush.gif")
Color.find_by_name("Striped").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/stripedpntbrsh.gif")
Color.find_by_name("Swamp Gas").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/newpbsg2011color.gif")
Color.find_by_name("Toy").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_toy.gif")
Color.find_by_name("Transparent").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/transparentpaintbrush.gif")
Color.find_by_name("Tyrannian").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/tyrannianpntbrush.gif")
Color.find_by_name("Usuki Boy").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/usukipaintbrush.gif")
Color.find_by_name("Usuki Girl").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/usukipaintbrush.gif")
Color.find_by_name("Valentine").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_valentine.gif")
Color.find_by_name("Water").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_water.gif")
Color.find_by_name("White").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/whitepntbrsh.gif")
Color.find_by_name("Woodland").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_woodland.gif")
Color.find_by_name("Wraith").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_wraith.gif")
Color.find_by_name("Yellow").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/yellowpntbrsh.gif")
Color.find_by_name("Zombie").update!(pb_item_thumbnail_url: "https://images.neopets.com/items/paintbrush_zombie.gif")
```
2024-05-22 16:59:36 -07:00
1a76923ce6 Group PB items by color in Item Getting Guide
Ohh yeah, this helps communicate the process much better, especially
for what the Shops/Trades links mean.

I think I'm gonna also go get the paint brush thumbnail images and add
them to the database too, to help better communicate that this is a
paint brush item situation.
2024-05-22 16:34:48 -07:00
a8648fc9c1 Add a limit to the number of items for the Item Getting Guide
Just to make it harder to willfully overload the server or whatever!
2024-05-22 16:09:49 -07:00
1b03c2caed Add more PB item info and links to Item Getting Guide
I add some infrastructural support for inferring an item's paintbrush
color (if any), and a field to the database to manually track an item's
paint brush item name! This is both useful for tracking which colors
are even *available* via paint brush, and also for working with colors
with unusual paint brush names, like the "Get Off My Lawn Paint Brush"
(for Elderly pets).

Here's the script I ran to backfill this for current colors and their
paint brushes!

```rb
Color.find_by_name("Baby").update!(pb_item_name: "Baby Paint Brush")
Color.find_by_name("Biscuit").update!(pb_item_name: "Biscuit Paint Brush")
Color.find_by_name("Blue").update!(pb_item_name: "Blue Paint Brush")
Color.find_by_name("Brown").update!(pb_item_name: "Brown Paint Brush")
Color.find_by_name("Camouflage").update!(pb_item_name: "Camouflage Paint Brush")
Color.find_by_name("Candy").update!(pb_item_name: "Candy Paint Brush")
Color.find_by_name("Checkered").update!(pb_item_name: "Checkered Paint Brush")
Color.find_by_name("Christmas").update!(pb_item_name: "Christmas Paint Brush")
Color.find_by_name("Cloud").update!(pb_item_name: "Cloud Paint Brush")
Color.find_by_name("Darigan").update!(pb_item_name: "Darigan Paint Brush")
Color.find_by_name("Dimensional").update!(pb_item_name: "Dimensional Paint Brush")
Color.find_by_name("Disco").update!(pb_item_name: "Disco Fever Paint Brush")
Color.find_by_name("Electric").update!(pb_item_name: "Electric Blue Paint Brush")
Color.find_by_name("Eventide").update!(pb_item_name: "Eventide Paint Brush")
Color.find_by_name("Faerie").update!(pb_item_name: "Faerie Paint Brush")
Color.find_by_name("Fire").update!(pb_item_name: "Fire, Fire, Your Pants On Fire Paint Brush")
Color.find_by_name("Elderlyboy").update!(pb_item_name: "Get Off My Lawn Paint Brush")
Color.find_by_name("Elderlygirl").update!(pb_item_name: "Get Off My Lawn Paint Brush")
Color.find_by_name("Ghost").update!(pb_item_name: "Ghost Paint Brush")
Color.find_by_name("Glowing").update!(pb_item_name: "Glowing Paint Brush")
Color.find_by_name("Gold").update!(pb_item_name: "Golden Paint Brush")
Color.find_by_name("Green").update!(pb_item_name: "Green Paint Brush")
Color.find_by_name("Grey").update!(pb_item_name: "Grey Paint Brush")
Color.find_by_name("Halloween").update!(pb_item_name: "Halloween Paint Brush")
Color.find_by_name("Invisible").update!(pb_item_name: "Invisible Paint Brush")
Color.find_by_name("Desert").update!(pb_item_name: "Lost Desert Paint Brush")
Color.find_by_name("Maractite").update!(pb_item_name: "Maractite Paint Brush")
Color.find_by_name("Maraquan").update!(pb_item_name: "Maraquan Paint Brush")
Color.find_by_name("Marble").update!(pb_item_name: "Marble Paint Brush")
Color.find_by_name("Island").update!(pb_item_name: "Mystery Island Paint Brush")
Color.find_by_name("Oil Paint").update!(pb_item_name: "Oil Paint Brush")
Color.find_by_name("Orange").update!(pb_item_name: "Orange Paint Brush")
Color.find_by_name("Origami").update!(pb_item_name: "Origami Paint Brush")
Color.find_by_name("Pastel").update!(pb_item_name: "Pastel Paint Brush")
Color.find_by_name("Pink").update!(pb_item_name: "Pink Paint Brush")
Color.find_by_name("Pirate").update!(pb_item_name: "Pirate Paint Brush")
Color.find_by_name("Plushie").update!(pb_item_name: "Plushie Paint Brush")
Color.find_by_name("Polka Dot").update!(pb_item_name: "Polka Dot Paint Brush")
Color.find_by_name("Purple").update!(pb_item_name: "Purple Paint Brush")
Color.find_by_name("Rainbow").update!(pb_item_name: "Rainbow Paint Brush")
Color.find_by_name("Red").update!(pb_item_name: "Red Paint Brush")
Color.find_by_name("Relic").update!(pb_item_name: "Relic Paint Brush")
Color.find_by_name("Royalboy").update!(pb_item_name: "Royal Paint Brush")
Color.find_by_name("Royalgirl").update!(pb_item_name: "Royal Paint Brush")
Color.find_by_name("Sketch").update!(pb_item_name: "Scritchy Sketchy Paint Brush")
Color.find_by_name("Shadow").update!(pb_item_name: "Shadow Paint Brush")
Color.find_by_name("Silver").update!(pb_item_name: "Silver Paint Brush")
Color.find_by_name("Skunk").update!(pb_item_name: "Skunk Paint Brush")
Color.find_by_name("Snow").update!(pb_item_name: "Snow Paint Brush")
Color.find_by_name("Speckled").update!(pb_item_name: "Speckled Paint Brush")
Color.find_by_name("Split").update!(pb_item_name: "Split Paint Brush")
Color.find_by_name("Spotted").update!(pb_item_name: "Spotted Paint Brush")
Color.find_by_name("Starry").update!(pb_item_name: "Starry Paint Brush")
Color.find_by_name("Stealthy").update!(pb_item_name: "Stealth Paint Brush")
Color.find_by_name("Steampunk").update!(pb_item_name: "Steampunk Paint Brush")
Color.find_by_name("Strawberry").update!(pb_item_name: "Strawberry Fields Forever Paint Brush")
Color.find_by_name("Striped").update!(pb_item_name: "Striped Paint Brush")
Color.find_by_name("Swamp Gas").update!(pb_item_name: "Swamp Gas Paint Brush")
Color.find_by_name("Toy").update!(pb_item_name: "Toy Paint Brush")
Color.find_by_name("Transparent").update!(pb_item_name: "Transparent Paint Brush")
Color.find_by_name("Tyrannian").update!(pb_item_name: "Tyrannian Paint Brush")
Color.find_by_name("Usuki Boy").update!(pb_item_name: "Usuki Paint Brush")
Color.find_by_name("Usuki Girl").update!(pb_item_name: "Usuki Paint Brush")
Color.find_by_name("Valentine").update!(pb_item_name: "Valentine Paint Brush")
Color.find_by_name("Water").update!(pb_item_name: "Water Paint Brush")
Color.find_by_name("White").update!(pb_item_name: "White Paint Brush")
Color.find_by_name("Woodland").update!(pb_item_name: "Woodland Paint Brush")
Color.find_by_name("Wraith").update!(pb_item_name: "Wraith Paint Brush")
Color.find_by_name("Yellow").update!(pb_item_name: "Yellow Paint Brush")
Color.find_by_name("Zombie").update!(pb_item_name: "Zombie Paint Brush")

```
2024-05-22 16:09:49 -07:00
1bd5598b64 Improve row focus and keyboard nav in Item Getting Guide
Clearer focus states, row changes bg color on hover/focus to help you
track where you are, remove the redundant image thumbnail link from the
tab order (it's the same as the item name link!)
2024-05-22 14:58:02 -07:00
189cb6a132 Set fixed table width for Item Getting Guide tables
We'll need to dig into this more for mobile, but here's my initial
attempt at getting the tables to be consistently-sized and such!
2024-05-22 14:47:43 -07:00
ecde507b60 Add item page links to Item Getting Guide table rows
Just a simple one! Clicking the item image or name takes you to the DTI
item page for it now!
2024-05-21 18:57:32 -07:00
39c05b8dac Refactor Item Getting Guide table rows to use a shared template
I'm gonna add more stuff like linking to the item page and stuff, and
that's starting to get complex enough to not want to repeat myself!
2024-05-21 18:48:29 -07:00