1
0
Fork 0
forked from OpenNeo/impress

modeling hub

This commit is contained in:
Emi Matchu 2012-08-06 21:15:31 -04:00
parent 2435c7f7e9
commit 72237f225c
10 changed files with 432 additions and 149 deletions

View file

@ -87,13 +87,18 @@ class ItemsController < ApplicationController
params[:species] params[:species]
) )
end end
unless @pet_type unless @pet_type
raise ActiveRecord::RecordNotFound, 'Pet type not found' raise ActiveRecord::RecordNotFound, 'Pet type not found'
end end
@items = @pet_type.needed_items.alphabetize @items = @pet_type.needed_items.alphabetize
assign_closeted! assign_closeted!
@pet_name = params[:name]
render :layout => 'application' respond_to do |format|
format.html { @pet_name = params[:name] ; render :layout => 'application' }
format.json { render :json => @items }
end
end end
protected protected

View file

@ -4,12 +4,6 @@ class PetsController < ApplicationController
rescue_from Pet::DownloadError, :with => :pet_download_error rescue_from Pet::DownloadError, :with => :pet_download_error
cache_sweeper :user_sweeper cache_sweeper :user_sweeper
DESTINATIONS = {
'needed_items' => '?',
'root' => '#',
'wardrobe' => '#'
}
def load def load
if params[:name] == '!' if params[:name] == '!'
@ -25,15 +19,12 @@ class PetsController < ApplicationController
end end
respond_to do |format| respond_to do |format|
format.html do format.html do
destination = params[:destination] || params[:origin] path = destination + @pet.wardrobe_query
destination = 'root' unless DESTINATIONS[destination]
query_joiner = DESTINATIONS[destination]
path = send("#{destination}_path") + query_joiner + @pet.wardrobe_query
redirect_to path redirect_to path
end end
format.json do format.json do
render :json => points render :json => {:points => points, :query => @pet.wardrobe_query}
end end
end end
end end
@ -41,6 +32,14 @@ class PetsController < ApplicationController
protected protected
def destination
case (params[:destination] || params[:origin])
when 'wardrobe' then wardrobe_path + '#'
when 'needed_items' then needed_items_path + '?'
else root_path + '#'
end
end
def pet_not_found def pet_not_found
pet_load_error :long_message => 'Could not find any pet by that name. Did you spell it correctly?', pet_load_error :long_message => 'Could not find any pet by that name. Did you spell it correctly?',
:short_message => 'Pet not found', :short_message => 'Pet not found',
@ -73,7 +72,7 @@ class PetsController < ApplicationController
end end
format.json do format.json do
render :text => options[:short_message], :status => options[:status] render :text => options[:long_message], :status => options[:status]
end end
end end
end end

View file

@ -51,25 +51,20 @@ module ItemsHelper
def closet_list_verb(owned) def closet_list_verb(owned)
ClosetHanger.verb(:you, owned) ClosetHanger.verb(:you, owned)
end end
def owned_icon
image_tag 'owned.png', :title => 'You own this', :alt => 'Own'
end
def wanted_icon
image_tag 'wanted.png', :title => 'You want this', :alt => 'Want'
end
def closeted_icons_for(item) def closeted_icons_for(item)
content = ''.html_safe content = ''.html_safe
if item.owned? content << owned_icon if item.owned?
content << image_tag( content << wanted_icon if item.wanted?
'owned.png',
:title => 'You own this',
:alt => 'Own'
)
end
if item.wanted?
content << image_tag(
'wanted.png',
:title => 'You want this',
:alt => 'Want'
)
end
content_tag :div, content, :class => 'closeted-icons' content_tag :div, content, :class => 'closeted-icons'
end end
@ -77,9 +72,14 @@ module ItemsHelper
def list_zones(zones, method=:label) def list_zones(zones, method=:label)
zones.sort { |x,y| x.label <=> y.label }.map(&method).join(', ') zones.sort { |x,y| x.label <=> y.label }.map(&method).join(', ')
end end
def nc_icon
image_tag 'nc.png', :title => 'NC Mall Item', :alt => 'NC',
:class => 'nc-icon'
end
def nc_icon_for(item) def nc_icon_for(item)
image_tag 'nc.png', :title => 'NC Mall Item', :alt => 'NC', :class => 'nc-icon' if item.nc? nc_icon if item.nc?
end end
def neoitems_url_for(item) def neoitems_url_for(item)

View file

@ -164,7 +164,8 @@ class Item < ActiveRecord::Base
:zones_restrict => zones_restrict, :zones_restrict => zones_restrict,
:rarity_index => rarity_index, :rarity_index => rarity_index,
:owned => owned?, :owned => owned?,
:wanted => wanted? :wanted => wanted?,
:nc => nc?
} }
end end

View file

@ -1,9 +1,71 @@
body.pets-bulk body.pets-bulk
text-align: center #needed-items-form, #bulk-pets-form
text-align: center
#needed-items-form
#needed-items-pet
border-top: 1px solid $soft-border-color
display: none
margin-top: 1em
padding-top: 1em
h4
font-size: 150%
margin-bottom: .5em
#needed-items-reload
+inline-block
font-size: 12px
margin-left: 1em
vertical-align: middle
#needed-items-alert
display: none
margin-top: .5em
#needed-items-pet-thumbnail
height: 50px
width: 50px
#needed-items-pet-items
li.owned
background: $module-bg-color
border: 1px solid $module-border-color
.object-owned
color: $soft-text-color
display: block
font-size: 75%
font-style: italic
padding-bottom: .25em
&.loading-pet, &.loading-items
#needed-items-pet-name-field
background:
image: image-url("loading.gif")
position: center right
repeat: no-repeat
#needed-items-pet-items
+opacity(.50)
&.loading-pet
#needed-items-pet h4
+opacity(.50)
&.loaded
#needed-items-pet
display: block
&.failed
#needed-items-alert
display: block
#bulk-pets-form #bulk-pets-form
textarea, div border-top: 1px solid $module-border-color
+inline-block margin-top: 12px
padding-top: 12px
textarea textarea
+inline-block +inline-block
@extend input[type=text] @extend input[type=text]
@ -14,7 +76,7 @@ body.pets-bulk
resize: none resize: none
ul ul
list-style: none list-style: none
margin-bottom: 1em margin-top: 1em
li li
+inline-block +inline-block
background: #eee background: #eee

View file

@ -62,7 +62,7 @@
= image_tag 'http://images.neopets.com/items/mall_ac_garland_spotlight.gif' = image_tag 'http://images.neopets.com/items/mall_ac_garland_spotlight.gif'
%h3 %h3
%a{:href => bulk_pets_path} %a{:href => bulk_pets_path}
Modeling Modeling Hub
%div %div
%h4 Found something? %h4 Found something?
%p %p

View file

@ -1,33 +1,74 @@
- unless user_signed_in? - title 'Modeling Hub'
%p.warning
= link_to 'You are not logged in!', login_path(:return_to => bulk_pets_path) - cache do
We award points for each new contribution to our database, and we don't want = form_tag load_pet_path, :id => 'needed-items-form' do
you to miss out, since our users have made the Dress to Impress database %h3 Looking for ways to contribute?
what it is today. Thanks for all your hard work!
%p.noscript %p
Note that submitting pets in bulk requires Javascript. Enter your pet's name below and we'll tell you what items you can help us
%p.script-only model. Thanks for your help!
We like to make it as easy as possible for you to contribute, so we thought
this might help! = origin_tag bulk_pets_path
%strong = destination_tag 'needed_items'
Type a pet's name in the box below, and we'll
start loading it the moment you press enter! %input#needed-items-pet-name-field{:type => "text", :name => "name"}/
If you're a power-user, %input{:type => "submit", :value => "Submit"}/
you can even submit a whole list at once &mdash; just put one pet per line, and
copy-paste it into the box. We do the rest! #needed-items-alert.alert
%p
%strong #needed-items-pet.script-only
Thanks for all your hard work! %h4
= form_tag load_pet_path, :id => 'bulk-pets-form' do %img#needed-items-pet-thumbnail.inline-image
= origin_tag bulk_pets_path Items
%span.noscript %span#needed-items-pet-name
%input{:name => "name", :type => "text"}/ can model
%input{:type => "submit", :value => "Load pet"}/
%span.script-only %button#needed-items-reload Reload
%ul
%textarea %ul#needed-items-pet-items
%button#bulk-pets-form-add{:type => "button"} Add
%button#bulk-pets-form-clear{:type => "button"} Clear = form_tag load_pet_path, :id => 'bulk-pets-form' do
%h3 Model pets in bulk
%p
Got a lot of pets to model? Just keep typing them into the box below, or
even paste in a whole list of names, one name per line. Thanks for your
help!
= origin_tag bulk_pets_path
%div.noscript
%input{:name => "name", :type => "text"}/
%input{:type => "submit", :value => "Load pet"}/
%div.script-only
%textarea
%button#bulk-pets-form-add{:type => "button"} Add
%button#bulk-pets-form-clear{:type => "button"} Clear
%ul
%script#item-template{:type => 'text/x-jquery-tmpl'}
%li{:class => 'object{{if owned}} owned{{/if}}'}
= link_to item_path(':id').sub(':id', '${id}') do
%img{:src => '${thumbnail_url}', :alt => '${description}', :title => '${description}'}
%span.name ${name}
{{if nc}}
= nc_icon
{{/if}}
.closeted-icons
{{if owned}}
= owned_icon
{{/if}}
{{if wanted}}
= wanted_icon
{{/if}}
{{if owned}}
%span.object-owned You own this item
{{/if}}
- content_for :javascripts do - content_for :javascripts do
= include_javascript_libraries :jquery = include_javascript_libraries :jquery, :jquery_tmpl
= include_javascripts :bulk_pets_package = include_javascripts :bulk_pets_package

View file

@ -44,7 +44,7 @@ OpenneoImpressItems::Application.routes.draw do |map|
match '/users/current-user/outfits' => 'outfits#index', :as => :current_user_outfits match '/users/current-user/outfits' => 'outfits#index', :as => :current_user_outfits
match '/pets/load' => 'pets#load', :method => :post, :as => :load_pet match '/pets/load' => 'pets#load', :method => :post, :as => :load_pet
match '/pets/bulk' => 'pets#bulk', :as => :bulk_pets match '/modeling' => 'pets#bulk', :as => :bulk_pets
match '/login' => 'sessions#new', :as => :login match '/login' => 'sessions#new', :as => :login
match '/logout' => 'sessions#destroy', :as => :logout match '/logout' => 'sessions#destroy', :as => :logout

View file

@ -1,74 +1,177 @@
var form = $('#bulk-pets-form'), var DEBUG = (document.location.search == '?debug');
queue_el = form.find('ul'),
names_el = form.find('textarea'),
add_el = $('#bulk-pets-form-add'),
clear_el = $('#bulk-pets-form-clear'),
bulk_load_queue;
$(document.body).addClass('js'); /* Needed items form */
(function () {
bulk_load_queue = new (function BulkLoadQueue() { var UI = {};
var pets = [], UI.form = $('#needed-items-form');
standard_pet_el = $('<li/>'), UI.alert = $('#needed-items-alert');
url = form.attr('action') + '.json'; UI.pet_name_field = $('#needed-items-pet-name-field');
standard_pet_el.append('<img/>').append($('<span/>', {'class': 'name'})) UI.pet_thumbnail = $('#needed-items-pet-thumbnail');
.append($('<span/>', {'class': 'response', text: 'Waiting...'})); UI.pet_name = $('#needed-items-pet-name');
UI.reload = $('#needed-items-reload');
UI.pet_items = $('#needed-items-pet-items');
UI.item_template = $('#item-template');
function Pet(name) { var current_request = { abort: function () {} };
var el = standard_pet_el.clone() function sendRequest(options) {
.children('img').attr('src', petImage('cpn/' + name, 1)).end() current_request = $.ajax(options);
.children('span.name').text(name).end(); }
el.appendTo(queue_el);
function cancelRequest() {
if(DEBUG) console.log("Canceling request", current_request);
current_request.abort();
}
/* Pet */
function loadPet(pet_name) {
// If there is a request in progress, kill it. Our new pet request takes
// priority, and, if I submit a name while the previous name is loading, I
// don't want to process both responses.
cancelRequest();
this.load = function () { sendRequest({
var response_el = el.children('span.response').text('Loading...'); url: UI.form.attr('action') + '.json',
$.ajax({ dataType: 'json',
complete: function (data) { data: {name: pet_name},
pets.shift(); error: petError,
if(pets.length) { success: function (data) { petSuccess(data, pet_name) },
pets[0].load(); complete: petComplete
} });
},
data: {name: name}, UI.form.removeClass('failed').addClass('loading-pet');
dataType: 'json',
error: function (xhr) {
el.addClass('failed');
response_el.text(xhr.responseText);
},
success: function (data) {
var response = data === true ? 'Thanks!' : data + ' points';
el.addClass('loaded');
response_el.text(response);
},
type: 'post',
url: url
});
}
} }
this.add = function (name) { function petComplete() {
name = name.replace(/^\s+|\s+$/g, ''); UI.form.removeClass('loading-pet');
if(name.length) {
var pet = new Pet(name);
pets.push(pet);
if(pets.length == 1) pet.load();
}
} }
function petError(xhr) {
UI.alert.text(xhr.responseText);
UI.form.addClass('failed');
}
function petSuccess(data, pet_name) {
UI.pet_thumbnail.attr('src', petThumbnailUrl(pet_name));
UI.pet_name.text(pet_name);
loadItems(data.query);
}
function petThumbnailUrl(pet_name) {
return 'http://pets.neopets.com/cpn/' + pet_name + '/1/1.png';
}
/* Items */
function loadItems(query) {
UI.form.addClass('loading-items');
sendRequest({
url: '/items/needed.json',
dataType: 'json',
data: query,
success: itemsSuccess
});
}
function itemsSuccess(items) {
if(DEBUG) {
// The dev server is missing lots of data, so sends me 2000+ needed
// items. We don't need that many for styling, so limit it to 100 to make
// my browser happier.
items = items.slice(0, 100);
}
UI.pet_items.empty();
UI.item_template.tmpl(items).appendTo(UI.pet_items);
UI.form.removeClass('loading-items').addClass('loaded');
}
UI.form.submit(function (e) {
e.preventDefault();
loadPet(UI.pet_name_field.val());
});
UI.reload.click(function () {
loadPet(UI.pet_name.text());
});
})(); })();
names_el.keyup(function () { /* Bulk pets form */
var names = this.value.split('\n'), x = names.length - 1, i, name; (function () {
for(i = 0; i < x; i++) { var form = $('#bulk-pets-form'),
bulk_load_queue.add(names[i]); queue_el = form.find('ul'),
} names_el = form.find('textarea'),
this.value = (x >= 0) ? names[x] : ''; add_el = $('#bulk-pets-form-add'),
}); clear_el = $('#bulk-pets-form-clear'),
bulk_load_queue;
add_el.click(function () { $(document.body).addClass('js');
bulk_load_queue.add(names_el.val());
names_el.val('');
});
clear_el.click(function () { bulk_load_queue = new (function BulkLoadQueue() {
queue_el.children('li.loaded, li.failed').remove(); var pets = [],
}); standard_pet_el = $('<li/>'),
url = form.attr('action') + '.json';
standard_pet_el.append('<img/>').append($('<span/>', {'class': 'name'}))
.append($('<span/>', {'class': 'response', text: 'Waiting...'}));
function Pet(name) {
var el = standard_pet_el.clone()
.children('img').attr('src', petImage('cpn/' + name, 1)).end()
.children('span.name').text(name).end();
el.appendTo(queue_el);
this.load = function () {
var response_el = el.children('span.response').text('Loading...');
$.ajax({
complete: function (data) {
pets.shift();
if(pets.length) {
pets[0].load();
}
},
data: {name: name},
dataType: 'json',
error: function (xhr) {
el.addClass('failed');
response_el.text(xhr.responseText);
},
success: function (data) {
var points = data.points;
var response = (points === true) ? 'Thanks!' : points + ' points';
el.addClass('loaded');
response_el.text(response);
},
type: 'post',
url: url
});
}
}
this.add = function (name) {
name = name.replace(/^\s+|\s+$/g, '');
if(name.length) {
var pet = new Pet(name);
pets.push(pet);
if(pets.length == 1) pet.load();
}
}
})();
names_el.keyup(function () {
var names = this.value.split('\n'), x = names.length - 1, i, name;
for(i = 0; i < x; i++) {
bulk_load_queue.add(names[i]);
}
this.value = (x >= 0) ? names[x] : '';
});
add_el.click(function () {
bulk_load_queue.add(names_el.val());
names_el.val('');
});
clear_el.click(function () {
queue_el.children('li.loaded, li.failed').remove();
});
})();

View file

@ -4041,23 +4041,95 @@ body.outfits-show #outfit-items {
text-align: center; text-align: center;
} }
/* line 1, ../../../app/stylesheets/pets/_bulk.sass */ /* line 2, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk { body.pets-bulk #needed-items-form, body.pets-bulk #bulk-pets-form {
text-align: center; text-align: center;
} }
/* line 5, ../../../app/stylesheets/pets/_bulk.sass */ /* line 6, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form textarea, body.pets-bulk #bulk-pets-form div { body.pets-bulk #needed-items-form #needed-items-pet {
border-top: 1px solid #aaddaa;
display: none;
margin-top: 1em;
padding-top: 1em;
}
/* line 12, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form #needed-items-pet h4 {
font-size: 150%;
margin-bottom: 0.5em;
}
/* line 16, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form #needed-items-pet #needed-items-reload {
display: -moz-inline-box; display: -moz-inline-box;
-moz-box-orient: vertical; -moz-box-orient: vertical;
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
*vertical-align: auto; *vertical-align: auto;
font-size: 12px;
margin-left: 1em;
vertical-align: middle;
} }
/* line 7, ../../../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.10.6/frameworks/compass/stylesheets/compass/css3/_inline-block.scss */ /* line 7, ../../../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.10.6/frameworks/compass/stylesheets/compass/css3/_inline-block.scss */
body.pets-bulk #bulk-pets-form textarea, body.pets-bulk #bulk-pets-form div { body.pets-bulk #needed-items-form #needed-items-pet #needed-items-reload {
*display: inline; *display: inline;
} }
/* line 7, ../../../app/stylesheets/pets/_bulk.sass */ /* line 22, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form #needed-items-alert {
display: none;
margin-top: 0.5em;
}
/* line 26, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form #needed-items-pet-thumbnail {
height: 50px;
width: 50px;
}
/* line 31, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form #needed-items-pet-items li.owned {
background: #eeffee;
border: 1px solid #006600;
}
/* line 35, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form .object-owned {
color: #448844;
display: block;
font-size: 75%;
font-style: italic;
padding-bottom: 0.25em;
}
/* line 43, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form.loading-pet #needed-items-pet-name-field, body.pets-bulk #needed-items-form.loading-items #needed-items-pet-name-field {
background-image: url('/images/loading.gif?1315327995');
background-position: center right;
background-repeat: no-repeat;
}
/* line 49, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form.loading-pet #needed-items-pet-items, body.pets-bulk #needed-items-form.loading-items #needed-items-pet-items {
-moz-opacity: 0.5;
-webkit-opacity: 0.5;
-o-opacity: 0.5;
-khtml-opacity: 0.5;
}
/* line 53, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form.loading-pet #needed-items-pet h4 {
-moz-opacity: 0.5;
-webkit-opacity: 0.5;
-o-opacity: 0.5;
-khtml-opacity: 0.5;
}
/* line 57, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form.loaded #needed-items-pet {
display: block;
}
/* line 61, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #needed-items-form.failed #needed-items-alert {
display: block;
}
/* line 64, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form {
border-top: 1px solid #006600;
margin-top: 12px;
padding-top: 12px;
}
/* line 69, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form textarea { body.pets-bulk #bulk-pets-form textarea {
display: -moz-inline-box; display: -moz-inline-box;
-moz-box-orient: vertical; -moz-box-orient: vertical;
@ -4074,12 +4146,12 @@ body.pets-bulk #bulk-pets-form textarea {
body.pets-bulk #bulk-pets-form textarea { body.pets-bulk #bulk-pets-form textarea {
*display: inline; *display: inline;
} }
/* line 15, ../../../app/stylesheets/pets/_bulk.sass */ /* line 77, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form ul { body.pets-bulk #bulk-pets-form ul {
list-style: none; list-style: none;
margin-bottom: 1em; margin-top: 1em;
} }
/* line 18, ../../../app/stylesheets/pets/_bulk.sass */ /* line 80, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form ul li { body.pets-bulk #bulk-pets-form ul li {
display: -moz-inline-box; display: -moz-inline-box;
-moz-box-orient: vertical; -moz-box-orient: vertical;
@ -4095,39 +4167,39 @@ body.pets-bulk #bulk-pets-form ul li {
body.pets-bulk #bulk-pets-form ul li { body.pets-bulk #bulk-pets-form ul li {
*display: inline; *display: inline;
} }
/* line 24, ../../../app/stylesheets/pets/_bulk.sass */ /* line 86, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form ul li.loaded { body.pets-bulk #bulk-pets-form ul li.loaded {
background: #e6efc2; background: #e6efc2;
} }
/* line 26, ../../../app/stylesheets/pets/_bulk.sass */ /* line 88, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form ul li.failed { body.pets-bulk #bulk-pets-form ul li.failed {
background: #fbe3e4; background: #fbe3e4;
} }
/* line 28, ../../../app/stylesheets/pets/_bulk.sass */ /* line 90, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form ul img { body.pets-bulk #bulk-pets-form ul img {
float: left; float: left;
height: 50px; height: 50px;
width: 50px; width: 50px;
} }
/* line 33, ../../../app/stylesheets/pets/_bulk.sass */ /* line 95, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form ul span { body.pets-bulk #bulk-pets-form ul span {
display: block; display: block;
} }
/* line 35, ../../../app/stylesheets/pets/_bulk.sass */ /* line 97, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk #bulk-pets-form ul .response { body.pets-bulk #bulk-pets-form ul .response {
font-size: 75%; font-size: 75%;
font-style: italic; font-style: italic;
margin-left: 75px; margin-left: 75px;
} }
/* line 42, ../../../app/stylesheets/pets/_bulk.sass */ /* line 104, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk.js .noscript { body.pets-bulk.js .noscript {
display: none; display: none;
} }
/* line 44, ../../../app/stylesheets/pets/_bulk.sass */ /* line 106, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk.js .script-only { body.pets-bulk.js .script-only {
display: block; display: block;
} }
/* line 47, ../../../app/stylesheets/pets/_bulk.sass */ /* line 109, ../../../app/stylesheets/pets/_bulk.sass */
body.pets-bulk .script-only { body.pets-bulk .script-only {
display: none; display: none;
} }