[WV2] Pose picker popover
This commit is contained in:
parent
76496f8a6d
commit
6eace54c34
4 changed files with 158 additions and 105 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue