[WV2] Support closeted items as well as worn items
This commit is contained in:
parent
f5ad5d2b17
commit
fd881ee31d
10 changed files with 132 additions and 47 deletions
|
|
@ -79,7 +79,9 @@ select,
|
||||||
|
|
||||||
/* Icon button pattern - small action buttons with hover reveals */
|
/* Icon button pattern - small action buttons with hover reveals */
|
||||||
.item-remove-button,
|
.item-remove-button,
|
||||||
.item-add-button {
|
.item-add-button,
|
||||||
|
.item-hide-button,
|
||||||
|
.item-show-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
right: 0.5rem;
|
right: 0.5rem;
|
||||||
|
|
@ -129,6 +131,24 @@ select,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-hide-button {
|
||||||
|
right: 2.5rem;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-show-button {
|
||||||
|
right: 2.5rem;
|
||||||
|
background: rgba(68, 136, 68, 0.9);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(68, 136, 68, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Item card - shared layout for worn items and search results */
|
/* Item card - shared layout for worn items and search results */
|
||||||
.item-card {
|
.item-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -147,7 +167,7 @@ select,
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover :is(.item-add-button, .item-remove-button) {
|
&:hover :is(.item-add-button, .item-remove-button, .item-hide-button, .item-show-button) {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,6 +211,23 @@ select,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Worn item emphasis */
|
||||||
|
.item-card[data-is-worn] {
|
||||||
|
background: #eef5ee;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(68, 136, 68, 0.2);
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Closeted item de-emphasis */
|
||||||
|
.item-card[data-is-closeted] {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px dashed #ccc;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
/* Pagination links - treated as buttons for consistency */
|
/* Pagination links - treated as buttons for consistency */
|
||||||
.pagination {
|
.pagination {
|
||||||
a,
|
a,
|
||||||
|
|
@ -780,7 +817,7 @@ pose-picker-popover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.worn-items {
|
.outfit-items {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
|
|
||||||
.items-list {
|
.items-list {
|
||||||
|
|
|
||||||
|
|
@ -64,16 +64,19 @@ class WardrobeController < ApplicationController
|
||||||
@available_alt_styles = @selected_species ?
|
@available_alt_styles = @selected_species ?
|
||||||
AltStyle.where(species_id: @selected_species.id).by_name_grouped : []
|
AltStyle.where(species_id: @selected_species.id).by_name_grouped : []
|
||||||
|
|
||||||
# Load items from the objects[] parameter
|
# Load items from the objects[] and closet[] parameters
|
||||||
item_ids = params[:objects] || []
|
worn_item_ids = params[:objects] || []
|
||||||
items = Item.where(id: item_ids)
|
closeted_item_ids = params[:closet] || []
|
||||||
|
worn_items = Item.where(id: worn_item_ids)
|
||||||
|
closeted_items = Item.where(id: closeted_item_ids)
|
||||||
|
|
||||||
# Build the outfit
|
# Build the outfit
|
||||||
@outfit = Outfit.new(
|
@outfit = Outfit.new(
|
||||||
name: @saved_outfit ? @saved_outfit.name : (params[:name].presence || "Untitled outfit"),
|
name: @saved_outfit ? @saved_outfit.name : (params[:name].presence || "Untitled outfit"),
|
||||||
pet_state: @pet_state,
|
pet_state: @pet_state,
|
||||||
alt_style: @alt_style,
|
alt_style: @alt_style,
|
||||||
worn_items: items,
|
worn_items: worn_items,
|
||||||
|
closeted_items: closeted_items,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Preload the manifests for all visible layers, so they load efficiently
|
# Preload the manifests for all visible layers, so they load efficiently
|
||||||
|
|
@ -132,7 +135,7 @@ class WardrobeController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def outfit_state_params_present?
|
def outfit_state_params_present?
|
||||||
params[:species].present? || params[:color].present? || params[:objects].present?
|
params[:species].present? || params[:color].present? || params[:objects].present? || params[:closet].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_search_filters(query_params, outfit)
|
def build_search_filters(query_params, outfit)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,12 @@ module WardrobeHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unless except.include?(:closeted_items)
|
||||||
|
outfit.closeted_items.each do |item|
|
||||||
|
fields << hidden_field_tag('closet[]', item.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
unless except.include?(:q)
|
unless except.include?(:q)
|
||||||
(params[:q] || {}).each do |key, value|
|
(params[:q] || {}).each do |key, value|
|
||||||
fields << hidden_field_tag("q[#{key}]", value) if value.present?
|
fields << hidden_field_tag("q[#{key}]", value) if value.present?
|
||||||
|
|
@ -46,28 +52,33 @@ module WardrobeHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
# Group outfit items by zone, applying smart multi-zone simplification.
|
# Group outfit items by zone, applying smart multi-zone simplification.
|
||||||
# Returns an array of hashes: {zone:, items:}
|
# Returns an array of hashes: {zone_id:, zone_label:, items:}
|
||||||
|
# Each item entry is {item:, state: :worn/:closeted}.
|
||||||
# This matches the logic from wardrobe-2020's getZonesAndItems function.
|
# This matches the logic from wardrobe-2020's getZonesAndItems function.
|
||||||
def outfit_items_by_zone(outfit)
|
def outfit_items_by_zone(outfit)
|
||||||
return [] if outfit.pet_type.nil?
|
return [] if outfit.pet_type.nil?
|
||||||
|
|
||||||
# Get item appearances for this outfit
|
# Build a list of all items with their state (:worn or :closeted)
|
||||||
|
all_items = outfit.worn_items.map { |i| {item: i, state: :worn} } +
|
||||||
|
outfit.closeted_items.map { |i| {item: i, state: :closeted} }
|
||||||
|
|
||||||
|
# Get item appearances for all items at once
|
||||||
item_appearances = Item.appearances_for(
|
item_appearances = Item.appearances_for(
|
||||||
outfit.worn_items,
|
all_items.map { |e| e[:item] },
|
||||||
outfit.pet_type,
|
outfit.pet_type,
|
||||||
swf_asset_includes: [:zone]
|
swf_asset_includes: [:zone]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Separate incompatible items (no layers for this pet)
|
# Separate compatible and incompatible items
|
||||||
compatible_items = []
|
compatible_entries = []
|
||||||
incompatible_items = []
|
incompatible_entries = []
|
||||||
|
|
||||||
outfit.worn_items.each do |item|
|
all_items.each do |entry|
|
||||||
appearance = item_appearances[item.id]
|
appearance = item_appearances[entry[:item].id]
|
||||||
if appearance&.present?
|
if appearance&.present?
|
||||||
compatible_items << {item: item, appearance: appearance}
|
compatible_entries << entry.merge(appearance: appearance)
|
||||||
else
|
else
|
||||||
incompatible_items << item
|
incompatible_entries << entry
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -75,23 +86,19 @@ module WardrobeHelper
|
||||||
items_by_zone = Hash.new { |h, k| h[k] = [] }
|
items_by_zone = Hash.new { |h, k| h[k] = [] }
|
||||||
zones_by_id = {}
|
zones_by_id = {}
|
||||||
|
|
||||||
compatible_items.each do |item_with_appearance|
|
compatible_entries.each do |entry|
|
||||||
item = item_with_appearance[:item]
|
entry[:appearance].swf_assets.map(&:zone).uniq.each do |zone|
|
||||||
appearance = item_with_appearance[:appearance]
|
|
||||||
|
|
||||||
# Get unique zones for this item (an item may have multiple assets per zone)
|
|
||||||
appearance.swf_assets.map(&:zone).uniq.each do |zone|
|
|
||||||
zones_by_id[zone.id] = zone
|
zones_by_id[zone.id] = zone
|
||||||
items_by_zone[zone.id] << item
|
items_by_zone[zone.id] << {item: entry[:item], state: entry[:state]}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create zone groups with sorted items
|
# Create zone groups with sorted items
|
||||||
zones_and_items = items_by_zone.map do |zone_id, items|
|
zones_and_items = items_by_zone.map do |zone_id, entries|
|
||||||
{
|
{
|
||||||
zone_id: zone_id,
|
zone_id: zone_id,
|
||||||
zone_label: zones_by_id[zone_id].label,
|
zone_label: zones_by_id[zone_id].label,
|
||||||
items: items.sort_by { |item| item.name.downcase }
|
items: entries.sort_by { |e| e[:item].name.downcase }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -107,11 +114,13 @@ module WardrobeHelper
|
||||||
zones_and_items = disambiguate_zone_labels(zones_and_items)
|
zones_and_items = disambiguate_zone_labels(zones_and_items)
|
||||||
|
|
||||||
# Add incompatible items section if any
|
# Add incompatible items section if any
|
||||||
if incompatible_items.any?
|
if incompatible_entries.any?
|
||||||
zones_and_items << {
|
zones_and_items << {
|
||||||
zone_id: nil,
|
zone_id: nil,
|
||||||
zone_label: "Incompatible",
|
zone_label: "Incompatible",
|
||||||
items: incompatible_items.sort_by { |item| item.name.downcase }
|
items: incompatible_entries
|
||||||
|
.map { |e| {item: e[:item], state: e[:state]} }
|
||||||
|
.sort_by { |e| e[:item].name.downcase }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -123,13 +132,14 @@ module WardrobeHelper
|
||||||
# Simplify zone groups by removing redundant single-item groups.
|
# Simplify zone groups by removing redundant single-item groups.
|
||||||
# Keep groups with multiple items (conflicts). For single-item groups,
|
# Keep groups with multiple items (conflicts). For single-item groups,
|
||||||
# only keep them if the item doesn't appear in a multi-item group.
|
# only keep them if the item doesn't appear in a multi-item group.
|
||||||
|
# Each item entry is {item:, state:}.
|
||||||
def simplify_multi_zone_groups(zones_and_items)
|
def simplify_multi_zone_groups(zones_and_items)
|
||||||
# Find groups with conflicts (multiple items)
|
# Find groups with conflicts (multiple items)
|
||||||
groups_with_conflicts = zones_and_items.select { |g| g[:items].length > 1 }
|
groups_with_conflicts = zones_and_items.select { |g| g[:items].length > 1 }
|
||||||
|
|
||||||
# Track which items appear in conflict groups
|
# Track which items appear in conflict groups
|
||||||
items_with_conflicts = Set.new(
|
items_with_conflicts = Set.new(
|
||||||
groups_with_conflicts.flat_map { |g| g[:items].map(&:id) }
|
groups_with_conflicts.flat_map { |g| g[:items].map { |e| e[:item].id } }
|
||||||
)
|
)
|
||||||
|
|
||||||
# Track which items we've already shown
|
# Track which items we've already shown
|
||||||
|
|
@ -139,14 +149,13 @@ module WardrobeHelper
|
||||||
zones_and_items.select do |group|
|
zones_and_items.select do |group|
|
||||||
# Always keep groups with multiple items
|
# Always keep groups with multiple items
|
||||||
if group[:items].length > 1
|
if group[:items].length > 1
|
||||||
group[:items].each { |item| items_we_have_seen.add(item.id) }
|
group[:items].each { |e| items_we_have_seen.add(e[:item].id) }
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
# For single-item groups, only keep if:
|
# For single-item groups, only keep if:
|
||||||
# - Item hasn't been seen yet AND
|
# - Item hasn't been seen yet AND
|
||||||
# - Item won't appear in a conflict group
|
# - Item won't appear in a conflict group
|
||||||
item = group[:items].first
|
item_id = group[:items].first[:item].id
|
||||||
item_id = item.id
|
|
||||||
|
|
||||||
if items_we_have_seen.include?(item_id) || items_with_conflicts.include?(item_id)
|
if items_we_have_seen.include?(item_id) || items_with_conflicts.include?(item_id)
|
||||||
false
|
false
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,11 @@ class Outfit < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def same_wardrobe_state_as?(other)
|
def same_wardrobe_state_as?(other)
|
||||||
wardrobe_params == other.wardrobe_params
|
# Exclude :name because it's managed separately via atomic rename, not URL
|
||||||
|
# state. This also works around the @outfit (new) vs @saved_outfit
|
||||||
|
# (persisted) split in WardrobeController, where only the unpersisted
|
||||||
|
# outfit includes :name. We should consider keeping their names in sync.
|
||||||
|
wardrobe_params.except(:name) == other.wardrobe_params.except(:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def wardrobe_params
|
def wardrobe_params
|
||||||
|
|
@ -274,6 +278,7 @@ class Outfit < ApplicationRecord
|
||||||
closet: closeted_item_ids.sort,
|
closet: closeted_item_ids.sort,
|
||||||
}
|
}
|
||||||
params[:style] = alt_style_id if alt_style_id.present?
|
params[:style] = alt_style_id if alt_style_id.present?
|
||||||
|
params[:name] = name if !persisted? && name.present?
|
||||||
params
|
params
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -313,9 +318,23 @@ class Outfit < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create a copy of this outfit, but *not* wearing the given item.
|
# Create a copy of this outfit without the given item at all
|
||||||
|
# (removed from both worn and closeted).
|
||||||
def without_item(item)
|
def without_item(item)
|
||||||
dup.tap { |o| o.worn_items.delete(item) }
|
dup.tap do |o|
|
||||||
|
o.worn_items.delete(item)
|
||||||
|
o.closeted_items.delete(item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a copy of this outfit with the given item moved from worn to
|
||||||
|
# closeted. If it's not currently worn, returns the outfit unchanged.
|
||||||
|
def hide_item(item)
|
||||||
|
dup.tap do |o|
|
||||||
|
next unless o.worn_item_ids.include?(item.id)
|
||||||
|
o.worn_items.delete(item)
|
||||||
|
o.closeted_items << item unless o.closeted_item_ids.include?(item.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create a copy of this outfit, additionally wearing the given item.
|
# Create a copy of this outfit, additionally wearing the given item.
|
||||||
|
|
@ -325,6 +344,9 @@ class Outfit < ApplicationRecord
|
||||||
# Skip if item is nil, already worn, or outfit has no pet_state
|
# Skip if item is nil, already worn, or outfit has no pet_state
|
||||||
next if item.nil? || o.worn_item_ids.include?(item.id) || o.pet_state.nil?
|
next if item.nil? || o.worn_item_ids.include?(item.id) || o.pet_state.nil?
|
||||||
|
|
||||||
|
# If the item was closeted, remove it from closet (it's moving to worn)
|
||||||
|
o.closeted_items.delete(item) if o.closeted_item_ids.include?(item.id)
|
||||||
|
|
||||||
# Load appearances for the new item and all currently worn items
|
# Load appearances for the new item and all currently worn items
|
||||||
all_items = o.worn_items + [item]
|
all_items = o.worn_items + [item]
|
||||||
appearances = Item.appearances_for(all_items, o.pet_type,
|
appearances = Item.appearances_for(all_items, o.pet_type,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
- is_worn = @outfit.worn_items.include?(item)
|
- is_worn = @outfit.worn_items.include?(item)
|
||||||
%li.item-card
|
- is_closeted = @outfit.closeted_items.include?(item)
|
||||||
|
%li.item-card{data: {is_worn: is_worn || nil, is_closeted: is_closeted || nil}}
|
||||||
.item-thumbnail
|
.item-thumbnail
|
||||||
= image_tag item.thumbnail_url, alt: item.name, loading: "lazy"
|
= image_tag item.thumbnail_url, alt: item.name, loading: "lazy"
|
||||||
.item-info
|
.item-info
|
||||||
|
|
@ -8,6 +9,16 @@
|
||||||
= render "items/badges/kind", item: item
|
= render "items/badges/kind", item: item
|
||||||
= render "items/badges/first_seen", item: item
|
= render "items/badges/first_seen", item: item
|
||||||
- if is_worn
|
- if is_worn
|
||||||
|
= button_to @wardrobe_path, method: :get, class: "item-hide-button", title: "Hide #{item.name}", "aria-label": "Hide #{item.name}" do
|
||||||
|
👁️🗨️
|
||||||
|
= outfit_state_params @outfit.hide_item(item)
|
||||||
|
= button_to @wardrobe_path, method: :get, class: "item-remove-button", title: "Remove #{item.name}", "aria-label": "Remove #{item.name}" do
|
||||||
|
❌
|
||||||
|
= outfit_state_params @outfit.without_item(item)
|
||||||
|
- elsif is_closeted
|
||||||
|
= button_to @wardrobe_path, method: :get, class: "item-show-button", title: "Show #{item.name}", "aria-label": "Show #{item.name}" do
|
||||||
|
👁️
|
||||||
|
= outfit_state_params @outfit.with_item(item)
|
||||||
= button_to @wardrobe_path, method: :get, class: "item-remove-button", title: "Remove #{item.name}", "aria-label": "Remove #{item.name}" do
|
= button_to @wardrobe_path, method: :get, class: "item-remove-button", title: "Remove #{item.name}", "aria-label": "Remove #{item.name}" do
|
||||||
❌
|
❌
|
||||||
= outfit_state_params @outfit.without_item(item)
|
= outfit_state_params @outfit.without_item(item)
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@
|
||||||
- if @has_unsaved_changes
|
- if @has_unsaved_changes
|
||||||
= form_with url: outfit_path(@saved_outfit), method: :patch, class: "outfit-save-form" do |f|
|
= form_with url: outfit_path(@saved_outfit), method: :patch, class: "outfit-save-form" do |f|
|
||||||
= render "save_outfit_fields"
|
= render "save_outfit_fields"
|
||||||
- @saved_outfit.closeted_items.each do |item|
|
|
||||||
= hidden_field_tag "outfit[item_ids][closeted][]", item.id
|
|
||||||
= f.submit "Save", class: "outfit-save-button"
|
= f.submit "Save", class: "outfit-save-button"
|
||||||
- else
|
- else
|
||||||
%button.outfit-save-button{disabled: true} Saved!
|
%button.outfit-save-button{disabled: true} Saved!
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,5 @@
|
||||||
= hidden_field_tag "outfit[alt_style_id]", @alt_style.id
|
= hidden_field_tag "outfit[alt_style_id]", @alt_style.id
|
||||||
- @outfit.worn_items.each do |item|
|
- @outfit.worn_items.each do |item|
|
||||||
= hidden_field_tag "outfit[item_ids][worn][]", item.id
|
= hidden_field_tag "outfit[item_ids][worn][]", item.id
|
||||||
|
- @outfit.closeted_items.each do |item|
|
||||||
|
= hidden_field_tag "outfit[item_ids][closeted][]", item.id
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
.search-results
|
.search-results
|
||||||
- if @search_results.any?
|
- if @search_results.any?
|
||||||
= will_paginate @search_results, page_links: false, param_name: "q[page]", params: { q: params[:q], species: @outfit.species_id, color: @outfit.color_id, objects: params[:objects] }
|
= will_paginate @search_results, page_links: false, param_name: "q[page]", params: @outfit.wardrobe_params.merge(q: params[:q])
|
||||||
|
|
||||||
%ul.search-results-list
|
%ul.search-results-list
|
||||||
- @search_results.each do |item|
|
- @search_results.each do |item|
|
||||||
= render "item_card", item: item
|
= render "item_card", item: item
|
||||||
|
|
||||||
= will_paginate @search_results, param_name: "q[page]", params: { q: params[:q], species: @outfit.species_id, color: @outfit.color_id, objects: params[:objects] }
|
= will_paginate @search_results, param_name: "q[page]", params: @outfit.wardrobe_params.merge(q: params[:q])
|
||||||
|
|
||||||
- else
|
- else
|
||||||
.empty-state
|
.empty-state
|
||||||
|
|
|
||||||
|
|
@ -69,11 +69,11 @@
|
||||||
- else
|
- else
|
||||||
= render "outfit_rename_field"
|
= render "outfit_rename_field"
|
||||||
= render "save_button"
|
= render "save_button"
|
||||||
- if @outfit.worn_items.any?
|
- if @outfit.worn_items.any? || @outfit.closeted_items.any?
|
||||||
.worn-items
|
.outfit-items
|
||||||
- outfit_items_by_zone(@outfit).each do |zone_group|
|
- outfit_items_by_zone(@outfit).each do |zone_group|
|
||||||
.zone-group
|
.zone-group
|
||||||
%h3.zone-label= zone_group[:zone_label]
|
%h3.zone-label= zone_group[:zone_label]
|
||||||
%ul.items-list
|
%ul.items-list
|
||||||
- zone_group[:items].each do |item|
|
- zone_group[:items].each do |entry|
|
||||||
= render "item_card", item: item
|
= render "item_card", item: entry[:item]
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ The goal is a basic usable wardrobe. Species/color/pose selection, item search,
|
||||||
- Stale style params dropped gracefully when switching species
|
- Stale style params dropped gracefully when switching species
|
||||||
- Search results auto-filtered by alt style compatibility
|
- Search results auto-filtered by alt style compatibility
|
||||||
|
|
||||||
**Closeted Items**
|
**Closeted Items** (baseline done, progressive enhancement pending)
|
||||||
- Instead of just wearing/unwearing items, also support a "closeted" state: the user is *considering* this item,
|
- Instead of just wearing/unwearing items, also support a "closeted" state: the user is *considering* this item,
|
||||||
but it is not displayed on the pet itself right now.
|
but it is not displayed on the pet itself right now.
|
||||||
- Wearing an item will stop wearing, but keep in closet, items that are mutually incompatible with it.
|
- Wearing an item will stop wearing, but keep in closet, items that are mutually incompatible with it.
|
||||||
|
|
@ -66,7 +66,10 @@ The goal is a basic usable wardrobe. Species/color/pose selection, item search,
|
||||||
- Unworn items have "Add" (wear).
|
- Unworn items have "Add" (wear).
|
||||||
- Worn items have "Hide" (stop wearing, keep in closet) and "Remove" (remove from worn and closet).
|
- Worn items have "Hide" (stop wearing, keep in closet) and "Remove" (remove from worn and closet).
|
||||||
- Closeted items have "Show" (wear) and "Remove" (remove from closet).
|
- Closeted items have "Show" (wear) and "Remove" (remove from closet).
|
||||||
- Progressive enhancement:
|
- Visual distinction: worn items have green emphasis, closeted items have dashed border and reduced opacity.
|
||||||
|
- Closeted items appear in zone groups alongside worn items.
|
||||||
|
- `closet[]` URL params, saving/loading, and search pagination all work.
|
||||||
|
- Progressive enhancement (pending):
|
||||||
- In the outfit view, items in the same zone group (mutually-incompatible) are a radio group. Whichever radio button
|
- In the outfit view, items in the same zone group (mutually-incompatible) are a radio group. Whichever radio button
|
||||||
is checked is the worn item. The others are merely closeted.
|
is checked is the worn item. The others are merely closeted.
|
||||||
- In the search view, each item has a worn checkbox (analogous to the worn radio button).
|
- In the search view, each item has a worn checkbox (analogous to the worn radio button).
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue