From 72237f225cd5c5669a13be067dc49842b06bd1e9 Mon Sep 17 00:00:00 2001 From: Matchu Date: Mon, 6 Aug 2012 21:15:31 -0400 Subject: [PATCH] modeling hub --- app/controllers/items_controller.rb | 9 +- app/controllers/pets_controller.rb | 23 ++- app/helpers/items_helper.rb | 32 ++-- app/models/item.rb | 3 +- app/stylesheets/pets/_bulk.sass | 70 +++++++- app/views/outfits/new.html.haml | 2 +- app/views/pets/bulk.html.haml | 103 +++++++---- config/routes.rb | 2 +- public/javascripts/pets/bulk.js | 231 ++++++++++++++++++------- public/stylesheets/compiled/screen.css | 106 ++++++++++-- 10 files changed, 432 insertions(+), 149 deletions(-) diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index 6acc3610..8d52cb80 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -87,13 +87,18 @@ class ItemsController < ApplicationController params[:species] ) end + unless @pet_type raise ActiveRecord::RecordNotFound, 'Pet type not found' end + @items = @pet_type.needed_items.alphabetize 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 protected diff --git a/app/controllers/pets_controller.rb b/app/controllers/pets_controller.rb index cebe7c03..c6c5f5e9 100644 --- a/app/controllers/pets_controller.rb +++ b/app/controllers/pets_controller.rb @@ -4,12 +4,6 @@ class PetsController < ApplicationController rescue_from Pet::DownloadError, :with => :pet_download_error cache_sweeper :user_sweeper - - DESTINATIONS = { - 'needed_items' => '?', - 'root' => '#', - 'wardrobe' => '#' - } def load if params[:name] == '!' @@ -25,15 +19,12 @@ class PetsController < ApplicationController end respond_to do |format| format.html do - destination = params[:destination] || params[:origin] - destination = 'root' unless DESTINATIONS[destination] - query_joiner = DESTINATIONS[destination] - path = send("#{destination}_path") + query_joiner + @pet.wardrobe_query + path = destination + @pet.wardrobe_query redirect_to path end format.json do - render :json => points + render :json => {:points => points, :query => @pet.wardrobe_query} end end end @@ -41,6 +32,14 @@ class PetsController < ApplicationController 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 pet_load_error :long_message => 'Could not find any pet by that name. Did you spell it correctly?', :short_message => 'Pet not found', @@ -73,7 +72,7 @@ class PetsController < ApplicationController end format.json do - render :text => options[:short_message], :status => options[:status] + render :text => options[:long_message], :status => options[:status] end end end diff --git a/app/helpers/items_helper.rb b/app/helpers/items_helper.rb index b7cc96af..618363f4 100644 --- a/app/helpers/items_helper.rb +++ b/app/helpers/items_helper.rb @@ -51,25 +51,20 @@ module ItemsHelper def closet_list_verb(owned) ClosetHanger.verb(:you, owned) 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) content = ''.html_safe - if item.owned? - content << image_tag( - '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 << owned_icon if item.owned? + content << wanted_icon if item.wanted? content_tag :div, content, :class => 'closeted-icons' end @@ -77,9 +72,14 @@ module ItemsHelper def list_zones(zones, method=:label) zones.sort { |x,y| x.label <=> y.label }.map(&method).join(', ') end + + def nc_icon + image_tag 'nc.png', :title => 'NC Mall Item', :alt => 'NC', + :class => 'nc-icon' + end 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 def neoitems_url_for(item) diff --git a/app/models/item.rb b/app/models/item.rb index 0bf0e293..1f7aa39f 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -164,7 +164,8 @@ class Item < ActiveRecord::Base :zones_restrict => zones_restrict, :rarity_index => rarity_index, :owned => owned?, - :wanted => wanted? + :wanted => wanted?, + :nc => nc? } end diff --git a/app/stylesheets/pets/_bulk.sass b/app/stylesheets/pets/_bulk.sass index 608d8061..a8de64ea 100644 --- a/app/stylesheets/pets/_bulk.sass +++ b/app/stylesheets/pets/_bulk.sass @@ -1,9 +1,71 @@ 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 - textarea, div - +inline-block + border-top: 1px solid $module-border-color + margin-top: 12px + padding-top: 12px + textarea +inline-block @extend input[type=text] @@ -14,7 +76,7 @@ body.pets-bulk resize: none ul list-style: none - margin-bottom: 1em + margin-top: 1em li +inline-block background: #eee diff --git a/app/views/outfits/new.html.haml b/app/views/outfits/new.html.haml index acd57a85..1c8eece2 100644 --- a/app/views/outfits/new.html.haml +++ b/app/views/outfits/new.html.haml @@ -62,7 +62,7 @@ = image_tag 'http://images.neopets.com/items/mall_ac_garland_spotlight.gif' %h3 %a{:href => bulk_pets_path} - Modeling + Modeling Hub %div %h4 Found something? %p diff --git a/app/views/pets/bulk.html.haml b/app/views/pets/bulk.html.haml index 88b69820..ccffc2f8 100644 --- a/app/views/pets/bulk.html.haml +++ b/app/views/pets/bulk.html.haml @@ -1,33 +1,74 @@ -- unless user_signed_in? - %p.warning - = link_to 'You are not logged in!', login_path(:return_to => bulk_pets_path) - We award points for each new contribution to our database, and we don't want - you to miss out, since our users have made the Dress to Impress database - what it is today. Thanks for all your hard work! -%p.noscript - Note that submitting pets in bulk requires Javascript. -%p.script-only - We like to make it as easy as possible for you to contribute, so we thought - this might help! - %strong - Type a pet's name in the box below, and we'll - start loading it the moment you press enter! - If you're a power-user, - you can even submit a whole list at once — just put one pet per line, and - copy-paste it into the box. We do the rest! -%p - %strong - Thanks for all your hard work! -= form_tag load_pet_path, :id => 'bulk-pets-form' do - = origin_tag bulk_pets_path - %span.noscript - %input{:name => "name", :type => "text"}/ - %input{:type => "submit", :value => "Load pet"}/ - %span.script-only - %ul - %textarea - %button#bulk-pets-form-add{:type => "button"} Add - %button#bulk-pets-form-clear{:type => "button"} Clear +- title 'Modeling Hub' + +- cache do + = form_tag load_pet_path, :id => 'needed-items-form' do + %h3 Looking for ways to contribute? + + %p + Enter your pet's name below and we'll tell you what items you can help us + model. Thanks for your help! + + = origin_tag bulk_pets_path + = destination_tag 'needed_items' + + %input#needed-items-pet-name-field{:type => "text", :name => "name"}/ + %input{:type => "submit", :value => "Submit"}/ + + #needed-items-alert.alert + + #needed-items-pet.script-only + %h4 + %img#needed-items-pet-thumbnail.inline-image + Items + %span#needed-items-pet-name + can model + + %button#needed-items-reload Reload + + %ul#needed-items-pet-items + + = 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 - = include_javascript_libraries :jquery + = include_javascript_libraries :jquery, :jquery_tmpl = include_javascripts :bulk_pets_package diff --git a/config/routes.rb b/config/routes.rb index 98f9f3ba..9db81083 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,7 +44,7 @@ OpenneoImpressItems::Application.routes.draw do |map| match '/users/current-user/outfits' => 'outfits#index', :as => :current_user_outfits 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 '/logout' => 'sessions#destroy', :as => :logout diff --git a/public/javascripts/pets/bulk.js b/public/javascripts/pets/bulk.js index 13884f8b..7b9bd7dd 100644 --- a/public/javascripts/pets/bulk.js +++ b/public/javascripts/pets/bulk.js @@ -1,74 +1,177 @@ -var form = $('#bulk-pets-form'), - 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; +var DEBUG = (document.location.search == '?debug'); -$(document.body).addClass('js'); - -bulk_load_queue = new (function BulkLoadQueue() { - var pets = [], - standard_pet_el = $('
  • '), - url = form.attr('action') + '.json'; - standard_pet_el.append('').append($('', {'class': 'name'})) - .append($('', {'class': 'response', text: 'Waiting...'})); +/* Needed items form */ +(function () { + var UI = {}; + UI.form = $('#needed-items-form'); + UI.alert = $('#needed-items-alert'); + UI.pet_name_field = $('#needed-items-pet-name-field'); + UI.pet_thumbnail = $('#needed-items-pet-thumbnail'); + 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 el = standard_pet_el.clone() - .children('img').attr('src', petImage('cpn/' + name, 1)).end() - .children('span.name').text(name).end(); - el.appendTo(queue_el); + var current_request = { abort: function () {} }; + function sendRequest(options) { + current_request = $.ajax(options); + } + + 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 () { - 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 response = data === true ? 'Thanks!' : data + ' points'; - el.addClass('loaded'); - response_el.text(response); - }, - type: 'post', - url: url - }); - } + sendRequest({ + url: UI.form.attr('action') + '.json', + dataType: 'json', + data: {name: pet_name}, + error: petError, + success: function (data) { petSuccess(data, pet_name) }, + complete: petComplete + }); + + UI.form.removeClass('failed').addClass('loading-pet'); } - 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(); - } + function petComplete() { + UI.form.removeClass('loading-pet'); } + + 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 () { - 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] : ''; -}); +/* Bulk pets form */ +(function () { + var form = $('#bulk-pets-form'), + 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; -add_el.click(function () { - bulk_load_queue.add(names_el.val()); - names_el.val(''); -}); + $(document.body).addClass('js'); -clear_el.click(function () { - queue_el.children('li.loaded, li.failed').remove(); -}); + bulk_load_queue = new (function BulkLoadQueue() { + var pets = [], + standard_pet_el = $('
  • '), + url = form.attr('action') + '.json'; + standard_pet_el.append('').append($('', {'class': 'name'})) + .append($('', {'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(); + }); +})(); diff --git a/public/stylesheets/compiled/screen.css b/public/stylesheets/compiled/screen.css index 0ac5e4d1..86104bcf 100644 --- a/public/stylesheets/compiled/screen.css +++ b/public/stylesheets/compiled/screen.css @@ -4041,23 +4041,95 @@ body.outfits-show #outfit-items { text-align: center; } -/* line 1, ../../../app/stylesheets/pets/_bulk.sass */ -body.pets-bulk { +/* line 2, ../../../app/stylesheets/pets/_bulk.sass */ +body.pets-bulk #needed-items-form, body.pets-bulk #bulk-pets-form { text-align: center; } -/* line 5, ../../../app/stylesheets/pets/_bulk.sass */ -body.pets-bulk #bulk-pets-form textarea, body.pets-bulk #bulk-pets-form div { +/* line 6, ../../../app/stylesheets/pets/_bulk.sass */ +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; -moz-box-orient: vertical; display: inline-block; vertical-align: middle; *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 */ -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; } -/* 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 { display: -moz-inline-box; -moz-box-orient: vertical; @@ -4074,12 +4146,12 @@ body.pets-bulk #bulk-pets-form textarea { body.pets-bulk #bulk-pets-form textarea { *display: inline; } -/* line 15, ../../../app/stylesheets/pets/_bulk.sass */ +/* line 77, ../../../app/stylesheets/pets/_bulk.sass */ body.pets-bulk #bulk-pets-form ul { 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 { display: -moz-inline-box; -moz-box-orient: vertical; @@ -4095,39 +4167,39 @@ body.pets-bulk #bulk-pets-form ul li { body.pets-bulk #bulk-pets-form ul li { *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 { 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 { background: #fbe3e4; } -/* line 28, ../../../app/stylesheets/pets/_bulk.sass */ +/* line 90, ../../../app/stylesheets/pets/_bulk.sass */ body.pets-bulk #bulk-pets-form ul img { float: left; height: 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 { display: block; } -/* line 35, ../../../app/stylesheets/pets/_bulk.sass */ +/* line 97, ../../../app/stylesheets/pets/_bulk.sass */ body.pets-bulk #bulk-pets-form ul .response { font-size: 75%; font-style: italic; margin-left: 75px; } -/* line 42, ../../../app/stylesheets/pets/_bulk.sass */ +/* line 104, ../../../app/stylesheets/pets/_bulk.sass */ body.pets-bulk.js .noscript { display: none; } -/* line 44, ../../../app/stylesheets/pets/_bulk.sass */ +/* line 106, ../../../app/stylesheets/pets/_bulk.sass */ body.pets-bulk.js .script-only { display: block; } -/* line 47, ../../../app/stylesheets/pets/_bulk.sass */ +/* line 109, ../../../app/stylesheets/pets/_bulk.sass */ body.pets-bulk .script-only { display: none; }