/* The new product page.
   Mobile first, because that is where this shop's customers are.

   NO prefers-color-scheme BLOCKS, DELIBERATELY. An earlier version styled these
   components for a dark viewer, which is correct for a page that adapts -- and
   this shop does not. Woodmart renders light whatever the phone is set to, so
   on a customer with dark mode switched on the fit row and the promises came
   out near-black against a cream page while everything around them stayed
   light. One theme, matching the rest of the shop, on every device. */

.sp-wrap{max-width:1240px;margin:0 auto;padding:14px 16px 56px}

.sp-crumbs{font-size:12.5px;color:var(--wd-text-color,#777);margin-bottom:14px}
.sp-crumbs a{color:inherit;text-decoration:none}
.sp-crumbs a:hover{text-decoration:underline}

/* WHY minmax(0,1fr) AND NOT 1fr.
   A bare 1fr track is minmax(AUTO,1fr), and an auto minimum lets a grid item
   grow to its content's intrinsic width. The gallery is a flex row of six
   slides at 100% each, so its intrinsic width is six screens -- which stretched
   the whole column and pushed the price, the buttons, the categories and the
   promises off the right of every phone. overflow-x:auto does not save you:
   the track is sized before the scroller clips anything. min-width:0 on the
   children is the same fix said twice, deliberately. */
.sp-main{display:grid;grid-template-columns:minmax(0,1fr);gap:22px}
.sp-media,.sp-gallery,.sp-summary{min-width:0;max-width:100%}
@media (min-width:900px){
  .sp-main{grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:44px;align-items:start}
  .sp-summary{position:sticky;top:90px}
}

/* Nothing inside the summary may widen it either -- the categories and tags are
   long comma lists and were the first things to run off the screen. */
.sp-summary *{max-width:100%}
/* meta layout is handled in one place further down */

/* ---- gallery ------------------------------------------------------------ */
.sp-frame{position:relative}
.sp-stage{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;
          gap:8px;border-radius:14px;scrollbar-width:none;-webkit-overflow-scrolling:touch}
.sp-stage::-webkit-scrollbar{display:none}
.sp-slide{flex:0 0 100%;scroll-snap-align:start;margin:0;background:#f4f1f6;border-radius:14px;
          overflow:hidden;aspect-ratio:1/1;position:relative}
/* contain, not cover. Cover crops the edges off a photograph to fill the box,
   and on a lingerie shop the cropped edge is often the strap or the lace the
   customer is trying to see. The originals are square, so contain costs
   nothing here and protects the ones that are not. */
.sp-slide img{width:100%;height:100%;object-fit:contain;display:block}

/* When a slide carries a video, Woodmart returns its own wrapper and figure
   through the filter. They are made to FILL the slide rather than restyled --
   the play button, the popup and the mute control are Woodmart's and are left
   exactly as they are on the current page. */
.sp-slide .wd-carousel-item,
.sp-slide figure.woocommerce-product-gallery__image,
.sp-slide figure.woocommerce-product-gallery__image > a{
  display:block;width:100%;height:100%;margin:0;padding:0}
.sp-slide .wd-carousel-item{position:relative}
.sp-slide.has-video video,
.sp-slide.has-video iframe{width:100%;height:100%;display:block;border:0}

/* Woodmart's own discount / new / sold-out badges, drawn over the first photo
   exactly as they are on the current page. */
.sp-frame .product-labels{position:absolute;top:12px;left:12px;z-index:3;
                          display:flex;flex-direction:column;gap:6px;pointer-events:none}

/* ARROWS, AT HIGH ENOUGH SPECIFICITY TO WIN.
   These are <button>s and Woodmart styles buttons globally with a filled brand
   background, at a specificity a single class cannot beat -- so the first
   version lost every declaration including position, and the two arrows fell
   into normal flow as purple blocks stacked above the photograph. Scoped
   through .sp-frame they win honestly, without !important. */
.sp-gallery .sp-frame > button.sp-arrow{
  position:absolute;top:50%;transform:translateY(-50%);z-index:5;
  width:40px;height:40px;min-width:0;min-height:0;padding:0;margin:0;
  border-radius:50%;border:1px solid rgba(0,0,0,.08);
  background:rgba(255,255,255,.94);background-image:none;box-shadow:0 2px 10px rgba(25,16,36,.16);
  color:#191024;font-size:19px;font-weight:400;line-height:1;cursor:pointer;
  display:none;align-items:center;justify-content:center}
.sp-gallery .sp-frame > button.sp-arrow:hover{background:#fff}
.sp-gallery .sp-frame > button.sp-arrow[data-dir="prev"]{left:10px;right:auto}
.sp-gallery .sp-frame > button.sp-arrow[data-dir="next"]{right:10px;left:auto}
.sp-gallery .sp-frame > button.sp-arrow:focus-visible{outline:2px solid var(--wd-primary-color,#db7093);outline-offset:2px}
/* Shown on every screen. A phone can swipe, but nothing on the photograph says
   so -- the arrows are what tell a customer there is more than one picture.
   top:50% is of .sp-frame, which is exactly the height of the photograph, so
   they stay centred whatever the aspect ratio. */
.sp-gallery .sp-frame > button.sp-arrow{display:inline-flex}
.sp-gallery .sp-frame > button.sp-arrow[disabled]{opacity:.35;cursor:default}
@media (max-width:600px){
  .sp-gallery .sp-frame > button.sp-arrow{width:34px;height:34px;font-size:17px}
  .sp-gallery .sp-frame > button.sp-arrow[data-dir="prev"]{left:8px}
  .sp-gallery .sp-frame > button.sp-arrow[data-dir="next"]{right:8px}
}

/* ---- where am I in the set ----------------------------------------------
   The thumbnails answer this on a laptop, but on a phone they scroll out of
   view, so the dots are the only fixed indication of start, middle or end. */
/* ---- where am I in the set, drawn ON the photograph --------------------
   Twice these were reported invisible while the CSS was verifiably served and
   the markup verifiably rendered. A small neutral shape on an unknown
   background is the whole problem, so the pill brings its own contrast. */
.sp-frame .sp-pos{
  position:absolute;left:50%;bottom:12px;transform:translateX(-50%);z-index:5;
  display:flex;align-items:center;gap:7px;
  padding:7px 12px;border-radius:20px;
  background:rgba(20,14,26,.55);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  pointer-events:auto;max-width:calc(100% - 90px);overflow:hidden}
.sp-frame .sp-pos button.sp-dot{
  appearance:none;-webkit-appearance:none;
  width:7px;height:7px;min-width:0;min-height:0;flex:0 0 7px;
  padding:0;margin:0;border:0;border-radius:50%;
  background:rgba(255,255,255,.5);background-image:none;box-shadow:none;
  cursor:pointer;transition:background-color .18s,width .18s}
.sp-frame .sp-pos button.sp-dot.is-on{background:#fff;width:18px;flex-basis:18px;border-radius:4px}
.sp-frame .sp-pos button.sp-dot:focus-visible{outline:2px solid #fff;outline-offset:2px}
.sp-frame .sp-pos .sp-count{
  color:#fff;font-size:11.5px;line-height:1;margin-left:3px;
  font-variant-numeric:tabular-nums;letter-spacing:.03em;white-space:nowrap}
.sp-frame .sp-pos .sp-count b{font-weight:700;color:#fff}

/* ---- thumbnails ---------------------------------------------------------
   These are <button>s, and Woodmart styles buttons globally with a filled
   brand background at a specificity a single class cannot beat -- which is why
   they rendered as purple blocks with a small picture floating inside. The
   reset below is scoped and explicit rather than !important. */
.sp-thumbs{display:flex;gap:8px;margin-top:10px;overflow-x:auto;scrollbar-width:none;padding:2px}
.sp-thumbs::-webkit-scrollbar{display:none}
.sp-gallery .sp-thumbs .sp-thumb{
  appearance:none;-webkit-appearance:none;
  flex:0 0 64px;width:64px;height:64px;min-width:0;min-height:0;
  padding:0;margin:0;border:2px solid transparent;border-radius:9px;
  background:#f0ecf2;background-image:none;box-shadow:none;
  cursor:pointer;overflow:hidden;line-height:0;transition:border-color .15s}
.sp-gallery .sp-thumbs .sp-thumb img{width:100%;height:100%;object-fit:cover;display:block;border-radius:7px}
.sp-gallery .sp-thumbs .sp-thumb.is-on{border-color:var(--wd-primary-color,#db7093)}
.sp-gallery .sp-thumbs .sp-thumb:focus-visible{outline:2px solid var(--wd-primary-color,#db7093);outline-offset:2px}

/* ---- summary ------------------------------------------------------------ */
.sp-summary .product_title{font-size:clamp(20px,4.4vw,28px);line-height:1.22;margin:0 0 8px;
                           font-weight:700;letter-spacing:-.01em}
.sp-summary .price{font-size:22px;font-weight:700;margin:0 0 14px;display:block}
.sp-summary .woocommerce-product-details__short-description{font-size:14.5px;line-height:1.62;margin-bottom:16px}
.sp-summary form.cart{margin:0 0 18px}
.sp-summary .quantity{margin-bottom:12px}

/* ADD TO CART AND BUY NOW ARE ONE PAIR, NOT TWO DECISIONS.
   Woodmart prints Buy Now straight after Add to Cart, so left alone they stack
   as two mismatched blocks of different widths. Side by side, equal height,
   Add to Cart given the larger share because it is the ordinary choice. */
/* THE ROW IS .woocommerce-variation-add-to-cart, NOT THE FORM.
   The first attempt made form.cart a flex row, which did nothing: on a variable
   product -- and 115 of the 120 products here are variable -- WooCommerce nests
   the quantity box and both buttons inside .woocommerce-variation-add-to-cart,
   several levels below the form. The rule was styling the wrong element, which
   is why the buttons stayed stacked and mismatched. */
/* ONE ROW: QUANTITY, ADD TO CART, BUY NOW.
   Asked for directly. Previously .quantity took flex:0 0 100%, which is what
   put it on a line of its own and left the two buttons sharing the next one.

   THE QUANTITY BOX IS NOT RESIZED, and it never needed to be. Measured, the
   element is 380px wide -- the full row -- but that is the WRAPPER; its actual
   contents are minus 25 + input 30 + plus 25 = 80px, sitting left-aligned
   inside all that empty space. So flex:0 0 auto does not shrink the control,
   it just stops the wrapper claiming a whole line. The stepper a customer sees
   and taps is identical, which is what was asked for.

   That leaves 280px at 412 and 228px at 360 for the two buttons, hence the
   smaller type and tighter padding below. */
.sp-summary .woocommerce-variation-add-to-cart,
.sp-summary form.cart:not(.variations_form){
  display:flex;flex-wrap:nowrap;gap:8px;align-items:center}
.sp-summary .woocommerce-variation-add-to-cart .quantity,
.sp-summary form.cart:not(.variations_form) .quantity{flex:0 0 auto;margin:0}

/* Buy Now is class="wd-buy-now-btn btn button alt btn-accent", so Woodmart's
   own .button rules outrank a single class and the two buttons ended up
   different shapes and different text weights. Matching that specificity makes
   them one pair. */
.sp-summary .woocommerce-variation-add-to-cart button.single_add_to_cart_button,
.sp-summary .woocommerce-variation-add-to-cart button.wd-buy-now-btn,
.sp-summary form.cart button.single_add_to_cart_button,
.sp-summary form.cart button.wd-buy-now-btn{
  display:inline-flex;align-items:center;justify-content:center;text-align:center;
  min-height:46px;height:46px;padding:0 8px;margin:0;float:none;white-space:nowrap;
  border-radius:30px;border:0;
  font-size:13px;font-weight:700;letter-spacing:0;line-height:1;text-transform:none}

/* Both basis 0 so they split what the quantity box leaves, evenly and without
   either one dictating the split from its own text length. flex-shrink stays
   on: at 360 there is less to share, and a button that refuses to shrink is a
   button that pushes the row off the screen.

   46px rather than the old 54: the quantity stepper is 42px tall and untouched,
   so a 54px button beside it read as two different controls bolted together.
   Still above the 44px minimum for something a thumb has to hit. */
.sp-summary .woocommerce-variation-add-to-cart button.single_add_to_cart_button,
.sp-summary form.cart button.single_add_to_cart_button{flex:1 1 0}
.sp-summary .woocommerce-variation-add-to-cart button.wd-buy-now-btn,
.sp-summary form.cart button.wd-buy-now-btn{flex:1 1 0}

/* ---- the promises, which is what people actually ask in Messenger -------- */
/* Literal colours, not theme variables. The shop renders light on every
   device, so these must too -- a var that resolves dark somewhere else is how
   the promises box came out near-black on a phone. */
.sp-trust{list-style:none;margin:18px 0 0;padding:16px;display:grid;gap:9px;
          background:#FAF7F8;border:1px solid #ECE6EE;border-radius:13px;
          font-size:13.5px;line-height:1.45;color:#191024}
.sp-trust li{position:relative;padding-left:22px}
.sp-trust li::before{content:"";position:absolute;left:0;top:.42em;width:11px;height:11px;
                     border-radius:50%;background:var(--wd-primary-color,#db7093);opacity:.85}
.sp-trust strong{font-weight:700;color:inherit}

@media (prefers-reduced-motion:reduce){
  .sp-stage{scroll-behavior:auto}
  *{transition:none!important}
}


/* ---- the model viewer ----------------------------------------------------
   The model plugin prepends its viewer INTO the gallery and puts its toggle
   button immediately AFTER it. After it, in a two-column grid, means the button
   became a stray third grid item -- which is why it could not be found on the
   page. The gallery is its own flex column so both land where they belong. */
/* position:relative because the model plugin makes #model-toggle
   position:absolute on phones, expecting .woocommerce-product-gallery to be
   its containing block. The plugin injects the button as a SIBLING of that
   element, so with nothing positioned here it flew to the top of the document
   and left the phone with no "See on model" at all -- while desktop, where the
   button is static, looked perfect. */
.sp-media{display:flex;flex-direction:column;gap:10px;position:relative}
.sp-gallery{display:flex;flex-direction:column;gap:10px}
.sp-media > #model-toggle{align-self:flex-start;margin:2px 0 0}
#sassy-model-viewer{position:relative;border-radius:14px;overflow:hidden;background:#f4f1f6}
#sassy-model-viewer img{width:100%;height:auto;display:block}


/* ---- trim the empty space around the page -------------------------------
   Woodmart's content wrapper pads for a page that has a title block; this
   template draws its own breadcrumb and needs none of it. Reported as unused
   space above and below the photograph. */
.wd-content-layout:has(.sp-wrap){padding-top:0;padding-bottom:0}
.sp-wrap{padding-top:6px;padding-bottom:32px}
.sp-wrap .sp-crumbs{margin-bottom:10px}

/* ADD TO WISHLIST AND SIZE GUIDE, WITH AIR BETWEEN THEM.
   The first attempt used .wd-action-btn + .wd-action-btn, which never matched:
   the size-guide POPUP markup is emitted between the two buttons, so they are
   not adjacent siblings even though they render side by side. Spacing the
   shared class works whatever sits between them. */
.sp-summary .wd-action-btn{
  display:inline-flex;align-items:center;vertical-align:middle;
  margin:6px 22px 6px 0}
.sp-summary .wd-action-btn:last-of-type{margin-right:0}
/* SKU, CATEGORIES AND TAGS.
   Woodmart already lays these out correctly: a wrapping ROW whose spans each
   take flex:1 1 100%, so every one starts on its own line. My previous fix
   switched the axis to column, and in a column that 100% basis means HEIGHT --
   which stretched the block into the empty space below the categories and
   pushed the tags out of sight entirely.
   So the axis is left alone. All that was ever needed is permission to break a
   long line, and that has to be on the ANCHORS: it is the category and tag
   links that are long runs, not the spans holding them. */
.sp-summary .product_meta{
  margin-top:18px;padding-top:14px;border-top:1px solid rgba(0,0,0,.07);
  gap:8px;font-size:13px;line-height:1.55;min-width:0;max-width:100%}
.sp-summary .product_meta > span{min-width:0;max-width:100%;overflow-wrap:anywhere}
.sp-summary .product_meta a{overflow-wrap:anywhere;word-break:break-word}
.sp-summary .product_meta .meta-label{font-weight:700;color:#241C2B;margin-right:4px}


/* ================= DIRECTION TWO =========================================
   Answering, in order: what is it, what does it cost, does it fix my problem,
   will it fit, and what if it does not. */

/* The offer line. Silent until sassy_offer_threshold is set, so it costs
   nothing to ship before the shop decides to run an offer. */
.sp-offer{margin:0 0 14px;padding:10px 13px;border-radius:10px;font-size:12.5px;line-height:1.4;
          background:#FDF8E9;border:1px dashed #C9A227;color:#5E4A12}
.sp-offer b{font-weight:800}
.sp-offer-met{background:#E4F1EB;border-color:#7FBFA3;color:#14684B}

/* Fit help, immediately above the size buttons. An exchange costs the shop the
   delivery charge in both directions, so this is the cheapest thing here. */
.sp-fit{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;
        margin:0 0 10px;padding:11px 13px;border-radius:10px;
        background:#FBE9F0;border:1px solid rgba(180,40,92,.16)}
.sp-fit span{font-size:12.5px;color:#5A3A48}
.sp-summary .sp-fit .sp-fit-btn{
  appearance:none;-webkit-appearance:none;
  background:none;background-image:none;border:0;box-shadow:none;
  min-height:0;padding:0;margin:0;
  color:#B4285C;font-size:12.5px;font-weight:700;line-height:1.2;
  text-transform:none;letter-spacing:0;cursor:pointer;text-decoration:underline}
.sp-summary .sp-fit .sp-fit-btn:hover{color:#8E1D48}
.sp-fit-links{display:inline-flex;align-items:center;gap:14px;flex-wrap:wrap}
.sp-summary .sp-fit .sp-fit-guide{
  color:#5A3A48;font-size:12.5px;font-weight:600;text-decoration:underline;white-space:nowrap}
.sp-summary .sp-fit .sp-fit-guide:hover{color:#B4285C}

/* Woodmart's standalone Size guide button is now redundant -- the link above
   opens the same popup. Hidden rather than unhooked, because the popup markup
   it prints is exactly what that link opens. */
/* Not a direct child -- Woodmart nests it, so the child combinator matched
   nothing and the button stayed visible beside the wishlist. The popup it
   prints is a separate element and is untouched, which is what the Size guide
   link in the fit row opens. */
.sp-summary .wd-sizeguide-btn{display:none}

/* The solutions block already sits at priority 12, right after the price --
   exactly where direction two wants it. It only needed room around it. */
.sp-summary .sassy-solves,
.sp-summary .sassy-solutions{margin:0 0 16px}

/* The promises, under the button rather than under the tags. */
.sp-summary .sp-trust{margin:16px 0 4px}



/* ---- the two controls kept from the app ---------------------------------
   Everything else from the app port is reverted; these two the owner asked to
   keep. Both are full-width outlined buttons, in this page's own palette
   rather than the app's. */
.sp-summary .sp-fit{
  display:flex;flex-direction:column;align-items:stretch;gap:9px;
  margin:16px 0 16px;padding:16px 0 0;border-top:1px solid #E9E3E6;
  background:none;border-left:0;border-right:0;border-bottom:0;border-radius:0}
.sp-summary .sp-fit .sp-fit-btn,
.sp-summary .sp-care-btn{
  appearance:none;-webkit-appearance:none;
  display:block;width:100%;padding:14px 16px;margin:0;
  background:#fff;background-image:none;box-shadow:none;
  border-radius:12px;min-height:0;line-height:1.2;cursor:pointer;
  font-size:13.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  text-align:center;text-decoration:none}
.sp-summary .sp-fit .sp-fit-btn{border:1.5px solid var(--wd-primary-color,#db7093);
                                color:var(--wd-primary-color,#db7093)}
.sp-summary .sp-fit .sp-fit-btn:hover{background:var(--wd-primary-color,#db7093);color:#fff}
.sp-summary .sp-care-btn{border:1.5px solid #C9BFCD;color:#5A5060;margin:9px 0 24px}
/* The attributes start a new decision -- which size, which colour -- so they
   get air above them rather than butting straight up against the last button. */
.sp-summary .variations{margin-top:0}
.sp-summary .variations .label{padding-top:0}
.sp-summary .sp-care-btn:hover{border-color:#8A7E8C;color:#241C2B}

/* Woodmart's own Size guide button is redundant now -- Find my perfect fit
   opens the same popup. Hidden, not unhooked: that popup is what it prints. */
.sp-summary .wd-sizeguide-btn{display:none}

/* ---- washing instructions panel ---------------------------------------- */
.sp-care{border:0;border-radius:18px;padding:0;max-width:520px;width:calc(100% - 32px);
         background:#fff;color:#241C2B;box-shadow:0 20px 60px rgba(25,16,36,.3)}
.sp-care::backdrop{background:rgba(20,14,26,.55)}
.sp-care-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px 20px 0}
.sp-care-head h2{margin:0;font-size:19px;font-weight:800;letter-spacing:-.01em}
.sp-care-x{appearance:none;border:0;background:none;font-size:26px;line-height:1;color:#8A7E8C;
           cursor:pointer;padding:0 4px;min-height:0}
.sp-care-lede{margin:8px 20px 0;font-size:13px;color:#6B5F70;line-height:1.5}
.sp-care ul{list-style:none;margin:14px 0 0;padding:0 20px 20px;display:flex;flex-direction:column;gap:13px}
.sp-care li{display:flex;gap:12px;align-items:flex-start}
.sp-care-icon{flex:0 0 34px;width:34px;height:34px;border-radius:50%;background:#FBE9F0;
              display:flex;align-items:center;justify-content:center;font-size:16px}
.sp-care li strong{display:block;font-size:13.5px;font-weight:700;line-height:1.35}
.sp-care li em{display:block;font-style:normal;font-size:12.5px;color:#6B5F70;line-height:1.45;margin-top:1px}

/* ---- colour swatches showing the real photograph ------------------------
   The image is attached with CSS to the swatch Woodmart already rendered, so
   no markup changes hands and the variation form -- the one part of this page
   that must not break -- is not touched at all. The per-product rules are
   printed inline by sassy_swatch_photo_css(). */
/* Bigger, because these are photographs now rather than flat colours -- at
   52px a bra was an unreadable smudge. */
.sp-summary [data-id="pa_color"] .wd-swatch.wd-bg{
  width:66px;height:66px;margin:0 10px 10px 0;border:1px solid #E2DADD;overflow:hidden}
.sp-summary [data-id="pa_color"] .wd-swatch.wd-bg.wd-active{
  outline:2px solid var(--wd-primary-color,#db7093);outline-offset:2px}


/* ---- size chips, shaped like the app's ----------------------------------
   Woodmart draws these as pills from its own --wd-brd-radius, and its active
   state adds a second ring underneath. The app uses plain rounded rectangles
   with the chosen one simply drawn heavier, which reads faster at a glance. */
.sp-summary [data-id="pa_size"] .wd-swatch.wd-text{
  --wd-brd-radius:10px;
  min-width:64px;height:52px;padding:0 14px;margin:0 8px 8px 0;
  border:1px solid #DED7DA;border-radius:10px;
  background:#fff;background-image:none;box-shadow:none;
  font-size:15px;font-weight:600;color:#241C2B;letter-spacing:0;text-transform:none}
.sp-summary [data-id="pa_size"] .wd-swatch.wd-text.wd-active{
  border:2px solid #241C2B;background:#fff;box-shadow:none}
.sp-summary [data-id="pa_size"] .wd-swatch.wd-text:after{display:none}

/* ---- the order of the buy area, and a wishlist you can see ---------------
   Woodmart injects Add to wishlist with JavaScript, which appends it to the
   end of the summary -- so it landed BELOW the promises box no matter what
   priority the PHP used. Ordering the column is the only way to place
   something a script put there: everything above the cart keeps document
   order at 0, and the four pieces below it are placed by name.

   It was also drawn at the same size as a caption. It is the one control on
   this page a returning customer looks for, so it is now a bordered pill at a
   readable size. */
.sp-summary{display:flex;flex-direction:column}
.sp-summary > form.cart{order:1}
.sp-summary > .wd-action-btn{order:2}
.sp-summary > .sp-trust{order:3}
.sp-summary > .product_meta{order:4}
.sp-summary > .product_share,
.sp-summary > .wd-social-icons{order:5}

.sp-summary > .wd-action-btn{
  align-self:flex-start;margin:14px 0 2px;padding:11px 18px;
  border:1px solid #DED7DA;border-radius:26px;background:#fff}
.sp-summary > .wd-action-btn a{display:inline-flex;align-items:center;gap:9px;
  font-size:14.5px;font-weight:600;color:#241C2B;text-decoration:none}
.sp-summary > .wd-action-btn .wd-action-icon{--wd-action-icon-size:20px;font-size:20px}
.sp-summary > .wd-action-btn:hover{border-color:var(--wd-primary-color,#db7093)}
.sp-summary > .wd-action-btn:hover a{color:var(--wd-primary-color,#db7093)}


/* ---- "You Might Also Like" ------------------------------------------------
   The same selection the app shows on its own product screen (see
   sassy_related_product_ids() in functions.php): the most specific category a
   product carries, shuffled, six at a time. Drawn plainly rather than through
   Woodmart's carousel widget, which does not render on this template -- see
   the PHP comment next to that function for why. No carousel library here
   either, same as the gallery: this is one flex row with its own horizontal
   scroll, not a JS component. */
.sp-related{max-width:1240px;margin:0 auto;padding:8px 16px 40px}
.sp-related-title{font-size:clamp(17px,3.6vw,20px);font-weight:700;letter-spacing:-.01em;
                  margin:0 0 14px;color:#241C2B}
.sp-related-track{display:flex;gap:12px;overflow-x:auto;scrollbar-width:none;
                  -webkit-overflow-scrolling:touch;padding:2px 2px 6px}
.sp-related-track::-webkit-scrollbar{display:none}
.sp-related-card{flex:0 0 148px;display:flex;flex-direction:column;text-decoration:none;
                 color:inherit;background:#fff;border:1px solid #ECE6EE;border-radius:14px;
                 overflow:hidden}
.sp-related-img{display:block;aspect-ratio:1/1;background:#f4f1f6}
.sp-related-img img{width:100%;height:100%;object-fit:cover;display:block}
.sp-related-name{font-size:12.5px;font-weight:600;line-height:1.35;color:#241C2B;
                 padding:9px 10px 0;margin:0;
                 display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.sp-related-price{font-size:13.5px;font-weight:700;color:var(--wd-primary-color,#db7093);
                  padding:4px 10px 10px;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
.sp-related-price del{font-weight:400;font-size:11.5px;color:#8A7E8C;text-decoration:line-through;order:2}
.sp-related-price ins{text-decoration:none}
@media (min-width:900px){
  .sp-related{padding-left:0;padding-right:0}
  .sp-related-card{flex:0 0 200px}
}

/* ============ PRODUCT PAGE v2 -- PREVIEW (?sassy_pdp=1) ==================
   Every rule is scoped to body.sassy-pdp-v2, which only exists behind the
   flag, so none of this can reach a customer until it is published. */

/* ---- the bottom bar steps aside for the price --------------------------
   On a product page the one control worth following her down the page is the
   price and the button, not Home/Shop/Menu/Cart. Woodmart's sticky add-to-cart
   is switched on for this preview and occupies that space instead.

   The nav is hidden rather than removed so nothing reflows, and the body
   padding that bottom-nav.css reserves for it (78px) is retuned to the sticky
   bar's own height rather than dropped -- otherwise the foot of the page ends
   up underneath the button. */
body.sassy-pdp-v2 .wd-toolbar { display: none !important; }

body.sassy-pdp-v2.sticky-toolbar-on,
html body.sassy-pdp-v2.sticky-toolbar-on {
  padding-bottom: calc(52px + env(safe-area-inset-bottom, 0px)) !important;
}

/* Woodmart's own scroll-to-top was lifted above the nav bar; with the nav gone
   it would float in mid-air, so it drops to sit above the sticky button. */
body.sassy-pdp-v2 .scrollToTop { bottom: 60px; }

/* ---- the position indicator becomes hairlines --------------------------
   li-vy.com shows four short rules, no pill, no counter. Copying that exactly
   would fail HERE, and the reason is written above .sp-pos: their photographs
   sit on a mid-tone orange, ours sit on cream and white studio backgrounds, so
   white marks on no background would simply disappear.

   So: the pill goes, the counter goes, the shape becomes a hairline -- but the
   colour inverts to a dark translucent ink that reads on a pale photograph,
   with a soft white glow behind it so it survives a dark one too. Minimal in
   form, still legible on an unknown image, which is the constraint their design
   never had to solve. */
body.sassy-pdp-v2 .sp-frame .sp-pos {
  left: 16px;
  transform: none;
  bottom: 14px;
  gap: 5px;
  padding: 0;
  border-radius: 0;
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  max-width: calc(100% - 32px);
}

body.sassy-pdp-v2 .sp-frame .sp-pos button.sp-dot {
  width: 22px;
  flex: 0 0 22px;
  height: 2px;
  border-radius: 2px;
  background: rgba(20, 14, 26, .28);
  box-shadow: 0 0 0 .5px rgba(255, 255, 255, .45);
  transition: background-color .18s ease;
}

body.sassy-pdp-v2 .sp-frame .sp-pos button.sp-dot.is-on {
  width: 22px;
  flex-basis: 22px;
  border-radius: 2px;
  background: rgba(20, 14, 26, .92);
}

/* The number goes: four rules already say "there are more, you are on the
   first", and the count was only ever there because dots say roughly. */
body.sassy-pdp-v2 .sp-frame .sp-pos .sp-count { display: none; }

/* ---- two corrections found while previewing --------------------------- */

/* 1. THE FRAME WAS 91px TALLER THAN THE PHOTOGRAPH INSIDE IT, so anything
      positioned from its bottom edge floated in the gap below the image.
      This is not new -- the live page has it too, and the dark pill simply
      disguised it. Measured on three products, with and without video:

        .sp-frame 471px   .sp-stage 380px   gap 91px

      Cause: .sp-gallery is a column flex container and .sp-frame carries
      flex: 1 1 100%, so it stretches past its content. Telling it to size to
      its content puts the bottom edge back on the photograph, which is where
      the indicator is supposed to live. */
body.sassy-pdp-v2 .sp-gallery .sp-frame { flex: 0 0 auto; }

/* 2. SEVENTEEN HAIRLINES DO NOT FIT WHERE FOUR DO. li-vy.com shows four
      photographs; products here carry up to 17, and 17 fixed 22px rules need
      459px inside a 348px bar, so four of them were being clipped away by the
      container's overflow.

      So the rules share the space instead of claiming it: they stretch when
      there are few and compress when there are many, with a floor so they can
      never thin to nothing. Four photos look like the reference; seventeen
      still read as a row of ticks rather than a clipped one. */
body.sassy-pdp-v2 .sp-frame .sp-pos { width: calc(100% - 32px); }
body.sassy-pdp-v2 .sp-frame .sp-pos button.sp-dot {
  flex: 1 1 auto;
  width: auto;
  min-width: 8px;
  max-width: 26px;
}
body.sassy-pdp-v2 .sp-frame .sp-pos button.sp-dot.is-on {
  flex: 1 1 auto;
  width: auto;
}

/* ---- v2: attributes straight after the price ---------------------------
   Asked for: size and colour, then quantity, then Add to cart and Buy now --
   all four of which already live inside WooCommerce's own priority-30 block
   (.variations_form), so moving that one element moves the group intact.

   Done with `order` on a flex column, not by re-hooking. Four of the blocks in
   this summary are registered as ANONYMOUS closures, which cannot be removed by
   name, so re-prioritising them would have meant reaching into $wp_filter. The
   summary is already display:flex, and this is the same technique the homepage
   restyle used: what she SEES changes while the HTML stays exactly as it is, so
   the document still reads title, price, description, offer to a crawler.

   Everything not named here keeps order:10 and therefore keeps its DOM order
   relative to its neighbours. Only the four that need pinning are pinned. */
body.sassy-pdp-v2 .sp-summary > * { order: 10; }
body.sassy-pdp-v2 .sp-summary > .product_title   { order: 1; }
/* THE STARS BELONG BETWEEN THE NAME AND THE PRICE.
   WooCommerce prints .woocommerce-product-rating at priority 10, which put it
   in the catch-all bucket with everything else unnamed here -- measured at
   y=1108 on a reviewed product, below the promises and the solutions block,
   where nobody deciding whether to buy would ever see it. It only appears at
   all once a product has a review, which is why it went unnoticed while the
   rest of this order was being set.

   Social proof is worth most immediately before the price, so it is pinned at
   2 and everything after it shifts up one. Numbering left deliberately sparse
   in single steps rather than 10s: the whole point of this block is that a
   name gets a number, and a gap invites the next person to guess. */
body.sassy-pdp-v2 .sp-summary > .woocommerce-product-rating { order: 2; }
body.sassy-pdp-v2 .sp-summary > .price           { order: 3; }
body.sassy-pdp-v2 .sp-summary > .variations_form { order: 4; }
/* ADD TO WISHLIST SITS WITH THE BUY CONTROLS, NOT BELOW THE PROMISES.
   It was landing under the trust list and the solutions block -- a long way
   down -- for a dull reason: it is neither of the four elements pinned by
   name, so it took the catch-all order:10 and queued up behind everything
   that had a number. Pinning it at 4 puts it directly under .variations_form,
   which is the element holding quantity, Add to cart and Buy now, so it reads
   as the third, quieter option in that group rather than an afterthought.

   .wd-action-btn rather than a wishlist-specific class on purpose: the size
   guide carries the same class and the pair were always meant to sit side by
   side -- that is what the margin rule further up this file is for. Moving one
   without the other would split them.

   NOT VISIBLE TO A GUEST, which is why this needs saying. There is no wishlist
   markup in the page at all when logged out -- confirmed by fetching the HTML,
   which contains the word only in config and CSS -- so it renders for signed-in
   customers only. Verified here by injecting an element with this class into
   .sp-summary and checking where it lands, since no amount of reloading as a
   stranger would ever show it. */
body.sassy-pdp-v2 .sp-summary > .wd-action-btn   { order: 5; }
body.sassy-pdp-v2 .sp-summary > .sp-trust        { order: 6; }

/* The title sits 8px off what follows it and the price 14px off the form; the
   stars need their own or they crowd the name they belong to. */
body.sassy-pdp-v2 .sp-summary > .woocommerce-product-rating { margin: 0 0 10px; }
body.sassy-pdp-v2 .sp-summary > .product_meta    { order: 20; }

/* ---- v2: the indicator takes the brand navy ----------------------------
   Asked for the theme blue rather than near-black. #2C2A4A is the same navy
   the product title and the bottom bar icons already use, so the gallery stops
   introducing a colour of its own. */
body.sassy-pdp-v2 .sp-frame .sp-pos button.sp-dot {
  background: rgba(44, 42, 74, .30);
}
body.sassy-pdp-v2 .sp-frame .sp-pos button.sp-dot.is-on {
  background: rgba(44, 42, 74, .95);
}

/* ---- v2: the sticky bar carries the price ------------------------------ */
body.sassy-pdp-v2 .wd-sticky-btn .sp-sticky-price {
  font-family: Raleway, system-ui, sans-serif;
  font-weight: 700;
  font-size: 15px;
  line-height: 1;
  color: #2C2A4A;
  white-space: nowrap;
  margin-inline-end: 10px;
  align-self: center;
}
body.sassy-pdp-v2 .wd-sticky-btn .sp-sticky-price del { display: none; }
body.sassy-pdp-v2 .wd-sticky-btn .sp-sticky-price ins { text-decoration: none; }

/* ---- v2: stand aside for the real button -------------------------------
   pdp-v2.js sets .sp-atc-visible on <html> while the real Add to cart is on
   screen. Hidden by moving it, not by display:none, so nothing reflows and the
   bar slides rather than blinks -- and if the script never runs, the class is
   never set and the bar simply stays put, which is the safe failure. */
body.sassy-pdp-v2 .wd-sticky-btn {
  transition: transform .26s cubic-bezier(.4, 0, .2, 1), opacity .26s ease;
  will-change: transform;
}
html.sp-atc-visible body.sassy-pdp-v2 .wd-sticky-btn {
  transform: translateY(calc(100% + env(safe-area-inset-bottom, 0px)));
  opacity: 0;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  body.sassy-pdp-v2 .wd-sticky-btn { transition: none; }
}

/* ---- v2: mobile loses the breadcrumb trail and the dead thumbnail row --- */
@media (max-width: 1024px) {

  /* THE BREADCRUMBS GO, AND THE PHOTOGRAPH MOVES UP INTO THE SPACE.
     Asked for, and it costs nothing in search: checked on the live page, this
     trail carries NO structured data at all -- no BreadcrumbList in the JSON-LD
     graph, no itemtype/itemprop microdata on the markup. Google cannot be
     drawing breadcrumb rich results from it today, so hiding it loses no
     eligibility. The links stay in the HTML either way, and the trail is
     untouched on desktop, where there is room for it.

     (Worth doing separately one day: ADDING BreadcrumbList JSON-LD would be a
     real gain, and is completely independent of whether the visual trail shows
     on a phone.) */
  body.sassy-pdp-v2 .sp-crumbs { display: none; }

  /* THE THUMBNAIL ROW IS DEAD WEIGHT ON A PHONE -- this is the "big
     unnecessary white space". Measured on the same product at two widths:

       mobile 412    row 461px tall   17 images   0 loaded
       desktop 1440  row  68px tall   17 images  17 loaded

     So on a phone it reserves a band and draws nothing in it. Two faults
     compound: .sp-thumbs carries flex: 1 1 100% and stretches like .sp-frame
     did, and its images never resolve inside a horizontally scrolling
     container -- the same failure the menu icons and the header logo hit, where
     lazysizes cannot measure an element it cannot lay out.

     Hidden on phones only. Desktop keeps a thumbnail strip that genuinely
     works, and the hairline indicator already answers "where am I" on mobile,
     which is what the row was there for. */
  /* !important, AND IT IS EARNED. This is the one rule in the file that has to
     beat an INLINE style, which nothing in a stylesheet can do otherwise.

     The bug it fixes: on a product with "See on model", opening the model view
     and pressing "Back to product" left a huge empty band between the
     photograph and the title, and it never went away. The model plugin hides
     the gallery's parts on the way in and restores them on the way out with
     jQuery .show() -- and .show() does not undo a stylesheet rule, it writes
     style="display: block" on the element. That inline style outranks the
     plain rule above, so the row this template deliberately hides on phones
     came back, at the full height it has when nothing has laid it out.

     Measured across the three states on one product:

       before opening   .sp-thumbs  display:none   h=0
       model open       .sp-thumbs  display:none   h=0     (inline, plugin)
       after closing    .sp-thumbs  display:block  h=685   (inline, plugin)

     685px of empty band, which is exactly the "huge space" reported, and it
     persists because nothing ever clears that inline style again. The gallery
     went 412 -> 695 and the title fell from y=448 to y=731.

     Fixing it in JS would mean watching for the plugin's own event and undoing
     its work afterwards -- a race with somebody else's script on every close.
     One !important on a rule that is already unconditional on phones states
     the intent once and cannot be raced. */
  body.sassy-pdp-v2 .sp-gallery .sp-thumbs { display: none !important; }

  /* With both gone the photograph should sit close under the header rather
     than leaving the old breadcrumb's air behind it. */
  body.sassy-pdp-v2 .sp-gallery { margin-bottom: 14px; }
  body.sassy-pdp-v2 .sp-wrap    { padding-top: 6px; }
}

/* ============ THE PHOTOGRAPH MEETS THE HEADER — PREVIEW (?sassy_hdr=1) ====
   Phones only. Everything scoped to body.sassy-hdr-blend, which exists only
   behind the flag. */
@media (max-width: 1024px) {

  /* FULL BLEED. Measured before: the frame sat at left=16 with width=380 in a
     412px viewport, its slides rounded at 14px, and a 6px gap above -- three
     separate things all saying "this is a card sitting on a page". Breaking out
     of the container removes the gutter without touching the template's own
     padding, which the summary column below still needs.

     100vw rather than 100% because the gutter belongs to an ancestor, and
     50% - 50vw is the standard way to escape it without knowing which one. */
  body.sassy-hdr-blend .sp-gallery .sp-frame {
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    width: 100vw;
    max-width: none;
  }

  /* The corners go with it. A radius is what tells the eye "this is an object";
     removing it lets the photograph read as the surface of the page. */
  body.sassy-hdr-blend .sp-gallery .sp-slide,
  body.sassy-hdr-blend .sp-gallery .sp-stage {
    border-radius: 0;
  }

  /* THE VIDEO SLIDE KEPT ITS ROUNDED CORNERS WHILE EVERY PHOTOGRAPH LOST
     THEIRS, and the rule above is why it was missed. Squaring .sp-slide and
     .sp-stage covers an ordinary image slide completely, because on those the
     image IS the slide. A video slide is built differently: Woodmart wraps it
     in .wd-product-video and puts a 12px radius on that AND on the poster
     image inside it. Measured on the live page -- video slide 12px on both,
     every other slide 0px -- which is exactly the one-slide-in-seventeen the
     owner spotted.

     Covering the wrapper, the poster, and the player itself so it stays square
     once the video is running, not only before it starts.

     NO WILDCARD IN HERE ON PURPOSE. `.wd-product-video *` would square the
     corners of everything inside the slide, and the play button is a CIRCLE --
     border-radius:50% is what makes it one. Zeroing that would turn the one
     control on the photograph into a square block, which is a worse bug than
     the one being fixed and would look like the gallery had broken. The
     elements are named individually for that reason. */
  body.sassy-pdp-v2 .sp-gallery .sp-slide.has-video,
  body.sassy-pdp-v2 .sp-gallery .sp-slide.has-video img,
  body.sassy-pdp-v2 .sp-gallery .sp-slide.has-video iframe,
  body.sassy-pdp-v2 .sp-gallery .sp-slide.has-video video,
  body.sassy-pdp-v2 .sp-gallery .sp-slide .wd-product-video,
  body.sassy-pdp-v2 .sp-gallery .sp-slide .wd-product-video img,
  body.sassy-pdp-v2 .sp-gallery .sp-slide .wd-product-video iframe,
  body.sassy-pdp-v2 .sp-gallery .sp-slide .wd-product-video video,

  /* THE ONE THAT WAS ACTUALLY DOING IT. Everything above was measured at 0px
     and the corners were STILL round, because none of those elements was
     drawing them: WooCommerce wraps a video slide in

       <figure class="woocommerce-product-gallery__image">

     which carries border-radius:12px AND overflow:hidden -- so it CLIPS the
     image to a rounded shape no matter what radius the image itself has.
     Zeroing the child was cosmetically pointless; the parent was the scissors.

     This is also the whole explanation for "only the video slide". Counted on
     the live gallery: ONE figure across seven slides, on the video slide.
     Ordinary image slides have no figure at all -- the image sits straight in
     the slide -- which is why they were square from the start and why squaring
     .sp-slide was enough for them and never enough for this one.

     THE LESSON, WRITTEN DOWN BECAUSE IT COST A WRONG "FIXED": a computed
     border-radius of 0 on an element proves nothing when an ancestor has
     overflow:hidden. Check what CLIPS, not only what is rounded -- and confirm
     it in a screenshot, because the numbers all said 0 while the corners were
     visibly round. */
  body.sassy-pdp-v2 .sp-gallery .sp-slide figure.woocommerce-product-gallery__image {
    border-radius: 0;
  }

  /* THE HEADER FLOATS OVER THE PHOTOGRAPH, then takes its white on scroll.

     For the picture to show THROUGH the header, the header has to leave the
     flow -- otherwise it simply occupies the first 50px and there is nothing
     behind it to see. Fixed rather than sticky because the page content then
     starts at y=0 on its own and the full-bleed photograph fills the top of the
     screen with no negative margins to keep in step with the header's height.

     z-index 400 is deliberate: above the page (the header's own inner bar is
     390) and below the slide-out menu and cart drawers, which are 500 and must
     still cover it when open.

     This is what makes "prominent on scroll" mean anything. A header that
     scrolls away has no background to change; keeping it costs 50px of screen
     on every product page, which is the trade that comes with the effect. */
  body.sassy-hdr-blend .whb-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 400;
    background: transparent;
    box-shadow: none;
    transition: background-color .25s ease, box-shadow .25s ease;
  }

  /* THE COLOUR IS NOT ON .whb-header, AND CLEARING IT THERE LOOKS LIKE IT
     WORKED. Woodmart paints its three .whb-row children, not the wrapper:

       .whb-row.whb-top-bar         rgb(255,255,255)  height 0
       .whb-row.whb-general-header  rgb(255,255,255)  height 50   <- the visible bar
       .whb-row.whb-header-bottom   rgb(255,255,255)  height 0

     So the rule above made the WRAPPER transparent while an opaque white row
     sat inside it at full height, and the header looked exactly as solid as
     before. Reading getComputedStyle('.whb-header').backgroundColor confirms
     'rgba(0, 0, 0, 0)' and proves nothing -- the check has to be on the rows,
     or on the painted pixels. That misreading is why this shipped twice.

     Only the rows that actually have height matter, but all three are cleared:
     the two zero-height rows can be given content from Theme Settings at any
     time, and a header that silently stops being transparent because someone
     switched on a top bar is a trap for later. */
  body.sassy-hdr-blend .whb-header .whb-row {
    background-color: transparent;
    transition: background-color .25s ease;
  }

  /* Scrolled: the usual header comes back, on the rows that carry the colour.

     #fff, NOT the warm rgb(249,246,245) this first used. That value is the
     PAGE background, not the header's -- measured above, every row is pure
     white -- so restoring the warm tone made the bar a slightly different
     colour on product pages than on every other page in the shop. The point of
     the scrolled state is to be indistinguishable from the normal header. */
  html.sassy-hdr-solid body.sassy-hdr-blend .whb-header {
    box-shadow: 0 1px 12px rgba(20, 14, 26, .07);
  }

  html.sassy-hdr-solid body.sassy-hdr-blend .whb-header .whb-row {
    background-color: #fff;
  }

  /* The content no longer needs to start below a header that is out of flow,
     so the photograph fills the top of the screen and the header sits on it. */
  body.sassy-hdr-blend .wd-page-content { padding-top: 0; }
  body.sassy-hdr-blend .sp-wrap    { padding-top: 0; }
  body.sassy-hdr-blend .sp-gallery { margin-top: 0; }

  /* Woodmart hides this header on scroll-down by default. That fights the whole
     idea -- the bar is meant to stay and change colour, not leave. */
  body.sassy-hdr-blend .whb-header.whb-scroll-down,
  body.sassy-hdr-blend .whb-header.whb-sticked { transform: none; }

  /* The arrows were inset from a 380px frame; on a full-bleed one they would
     hug the screen edge, so they come in by the gutter they lost. */
  body.sassy-hdr-blend .sp-gallery .sp-frame > button.sp-arrow[data-dir="prev"] { left: 14px; }
  body.sassy-hdr-blend .sp-gallery .sp-frame > button.sp-arrow[data-dir="next"] { right: 14px; }

  /* The hairlines keep their 16px inset from the SCREEN edge, which is now a
     different measurement than before even though the number is the same. */
  body.sassy-hdr-blend .sp-frame .sp-pos { left: 16px; width: calc(100vw - 32px); }
}

/* ---- blend: keep the badges out from under the header ------------------
   With the header floating over the photograph, anything pinned to the top of
   the frame goes behind it. Measured on two products:

     .product-labels   top=10   (header ends at 50)
     #model-toggle     top=14   (header ends at 50)

   so a SALE or New badge -- the one thing on that image with a job to do --
   was sitting underneath the logo, and "See on model" was a button a customer
   could see the bottom 6px of. Both move below the header band instead.

   THE MODEL BUTTON IS NOT OURS. It is injected by this shop's model-preview
   plugin, which sets position:absolute;top:12px;right:12px from its own
   stylesheet -- there is no inline style to beat, but the rule is on a bare
   #model-toggle, so the override has to carry more than an id to win. Hence
   the full body.sassy-hdr-blend .sp-media > #model-toggle path rather than a
   second bare id rule, which would tie on specificity and be decided by
   whichever stylesheet happened to load last.

   Found by screenshot, not by selector search: its text is "👤 See On Model",
   so a search for the words alone matches nothing. The video play button is
   centred in the frame and was never affected -- only what is pinned to the
   top edge needs this. */
@media (max-width: 1024px) {
  body.sassy-hdr-blend .sp-frame .product-labels {
    top: 60px;
  }

  body.sassy-hdr-blend .sp-media > #model-toggle {
    top: calc(100vw - 70px);
    bottom: auto;
    right: calc(50% - 50vw + 14px);
    left: auto;
  }
}

/* ---- blend: the model button sits in the corner of the photograph -------
   Asked for after seeing it below the header: top-right put it over the
   model's face on most products, and it reads better as a control on the
   picture than as a badge above it.

   BOTTOM-RIGHT IS NOT `bottom: 12px`, AND THE OBVIOUS VERSION BREAKS THE
   MOMENT SOMEONE USES THE BUTTON. #model-toggle is absolute against
   .sp-media -- the nearest positioned ancestor -- and .sp-media is NOT the
   photograph. It is the gallery PLUS #sassy-model-viewer, which is what the
   button opens. Measured closed, .sp-media is 426 tall against a 412 frame;
   opened, it grows by the height of a second photograph, and anything
   anchored to its bottom edge travels down with it. So the button would sit
   in the corner right up until it was pressed, then leave the picture.

   Anchored from the top instead, where .sp-media and the frame agree (both
   top=0). The frame is a full-bleed square in this block, so its height IS
   100vw, and 100vw - 70px puts the button's lower edge 28px off the bottom
   of the image -- 12px of clear air above the hairline dots, which sit 16px
   up. That holds at any viewport this block covers rather than only at 412.

   The horizontal offset is the frame's own full-bleed trick reused: .sp-media
   keeps the 16px gutter the frame escaped, so calc(50% - 50vw) walks back out
   to the screen edge and +14px comes in by the same inset the arrows use. A
   flat right:-2px measures the same today and stops being true the moment the
   gutter changes. */
