[WV2] Pose picker popover

This commit is contained in:
Emi Matchu 2025-11-11 18:07:06 -08:00
parent 76496f8a6d
commit 6eace54c34
4 changed files with 158 additions and 105 deletions

View file

@ -6,7 +6,7 @@
* - Shows a submit button as fallback (if JS is disabled or slow to load)
* - Uses Custom Element internals API to communicate state to CSS
*/
class PosePicker extends HTMLElement {
class PosePickerPopover extends HTMLElement {
#internals;
constructor() {
@ -28,4 +28,4 @@ class PosePicker extends HTMLElement {
}
}
customElements.define("pose-picker", PosePicker);
customElements.define("pose-picker-popover", PosePickerPopover);

View file

@ -41,32 +41,91 @@ body.wardrobe-v2 {
font-size: 1.2rem;
}
/* Pose picker floats over the preview above the species/color picker */
pose-picker {
/* Preview controls container - groups species/color picker and pose picker */
.preview-controls {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1.5rem;
pointer-events: none;
/* Allow clicks through when hidden */
/* Start hidden, reveal on hover or focus */
opacity: 0;
transition: opacity 0.2s;
.pose-picker-form {
> * {
pointer-events: auto;
/* Re-enable clicks on the form itself */
background: rgba(0, 0, 0, 0.85);
}
}
/* Pose picker button */
.pose-picker-button {
padding: 0.5rem 0.75rem;
font-size: 0.95rem;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 0.375rem;
background: rgba(0, 0, 0, 0.7);
color: white;
cursor: pointer;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.2s;
.pose-emoji {
font-size: 1.1rem;
}
.pose-label {
font-weight: normal;
min-width: 3.5rem;
}
.chevron {
font-size: 0.8rem;
opacity: 0.7;
}
&:hover {
background-color: rgba(0, 0, 0, 0.8);
border-color: rgba(255, 255, 255, 0.5);
}
&:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1);
}
&[popovertargetopen] {
border-color: rgba(255, 255, 255, 0.8);
background-color: rgba(0, 0, 0, 0.8);
}
}
/* Pose picker popover */
pose-picker-popover {
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 12px;
padding: 1rem;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
padding: 1.25rem;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
margin: 0;
position-area: bottom;
inset: auto;
.pose-picker-form {
display: flex;
flex-direction: column;
}
.pose-picker-table {
@ -110,7 +169,7 @@ body.wardrobe-v2 {
height: 60px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
background: rgba(255, 255, 255, 0.1);
border: 2px solid transparent;
transition: all 0.2s;
@ -141,7 +200,7 @@ body.wardrobe-v2 {
/* Selected state */
input[type="radio"]:checked + .pose-thumbnail {
border-color: #48BB78;
box-shadow: 0 0 0 3px rgba(72, 187, 120, 0.3);
box-shadow: 0 0 0 3px rgba(72, 187, 120, 0.4);
transform: scale(1.05);
}
@ -154,7 +213,7 @@ body.wardrobe-v2 {
/* Focus state */
input[type="radio"]:focus + .pose-thumbnail {
border-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.2);
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.3);
}
/* Unavailable state */
@ -175,22 +234,20 @@ body.wardrobe-v2 {
font-size: 0.95rem;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 6px;
background: rgba(0, 0, 0, 0.7);
background: rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
width: 100%;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
&:hover {
background-color: rgba(0, 0, 0, 0.8);
background-color: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.5);
}
&:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1);
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.2);
}
}
@ -203,35 +260,17 @@ body.wardrobe-v2 {
}
}
/* Once auto-loading is ready, hide the submit button completely */
&:state(auto-loading) {
.pose-submit-button {
/* Once auto-submit is enabled, hide the submit button completely */
&:state(auto-loading) .pose-submit-button {
display: none;
}
}
}
/* Species/color picker floats over the preview at the bottom */
/* Species/color picker */
species-color-picker {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
pointer-events: none;
/* Allow clicks through when hidden */
/* Start hidden, reveal on hover or focus */
opacity: 0;
transition: opacity 0.2s;
form {
pointer-events: auto;
/* Re-enable clicks on the form itself */
}
gap: 0.5rem;
select {
padding: 0.5rem 2rem 0.5rem 0.75rem;
@ -312,23 +351,16 @@ body.wardrobe-v2 {
}
}
/* Show pickers on hover (real hover only, not simulated touch hover) */
/* Show controls on hover (real hover only, not simulated touch hover) */
@media (hover: hover) {
&:hover pose-picker {
opacity: 1;
}
&:hover species-color-picker {
&:hover .preview-controls {
opacity: 1;
}
}
/* Show pickers when they have focus */
&:has(pose-picker:focus-within) pose-picker {
opacity: 1;
}
&:has(species-color-picker:focus-within) species-color-picker {
/* Show controls when they have focus or when popover is open */
&:has(.preview-controls:focus-within) .preview-controls,
&:has(.pose-picker-button[popovertargetopen]) .preview-controls {
opacity: 1;
}
}

View file

@ -24,6 +24,20 @@ module WardrobeHelper
safe_join fields
end
# Get the emoji and label for a pose, for display in the pose picker button
def pose_emoji_and_label(pose)
case pose
when "HAPPY_MASC", "HAPPY_FEM"
{ emoji: "😀", label: "Happy" }
when "SAD_MASC", "SAD_FEM"
{ emoji: "😢", label: "Sad" }
when "SICK_MASC", "SICK_FEM"
{ emoji: "🤢", label: "Sick" }
else
{ emoji: "😀", label: "Default" }
end
end
# Group outfit items by zone, applying smart multi-zone simplification.
# Returns an array of hashes: {zone:, items:}
# This matches the logic from wardrobe-2020's getZonesAndItems function.

View file

@ -29,8 +29,28 @@
- else
= outfit_viewer @outfit
.preview-controls
%species-color-picker
= form_with url: wardrobe_v2_path, method: :get do |f|
= outfit_state_params except: [:color, :species]
= select_tag :color,
options_from_collection_for_select(@colors, "id", "human_name",
@selected_color&.id),
"aria-label": "Pet color"
= select_tag :species,
options_from_collection_for_select(@species, "id", "human_name",
@selected_species&.id),
"aria-label": "Pet species"
= submit_tag "Go", name: nil
- if @pet_type
%pose-picker
- pose_info = pose_emoji_and_label(@selected_pose)
%button#pose-picker-button.pose-picker-button{type: "button", popovertarget: "pose-picker-popover"}
%span.pose-emoji= pose_info[:emoji]
%span.pose-label= pose_info[:label]
%span.chevron ▾
%pose-picker-popover#pose-picker-popover{popover: "auto"}
= form_with url: wardrobe_v2_path, method: :get, class: "pose-picker-form" do |f|
= outfit_state_params except: [:pose]
%table.pose-picker-table
@ -64,19 +84,6 @@
= render "pose_option", pose: "SICK_FEM", pet_state: @available_poses["SICK_FEM"], selected: @selected_pose == "SICK_FEM"
= submit_tag "Change pose", name: nil, class: "pose-submit-button"
%species-color-picker
= form_with url: wardrobe_v2_path, method: :get do |f|
= outfit_state_params except: [:color, :species]
= select_tag :color,
options_from_collection_for_select(@colors, "id", "human_name",
@selected_color&.id),
"aria-label": "Pet color"
= select_tag :species,
options_from_collection_for_select(@species, "id", "human_name",
@selected_species&.id),
"aria-label": "Pet species"
= submit_tag "Go", name: nil
.outfit-controls-section
%h1 Customize your pet