/** Shopify CDN: Minification failed

Line 1019:0 Unexpected "}"

**/
/* =============================================================================
   BALMiso brand layer
   -----------------------------------------------------------------------------
   OWNED BY FOXPOP. Not a Purevea vendor file. A G5 update must never overwrite
   it. If a vendor update conflicts, fix it here, never in theme.css.

   Source of truth: Brand Guidelines/BALMiso_Brand_Guide1.pdf (7pp, Aug 2026)
   Colour SCHEMES live in config/settings_data.json, not here.

   Three faces, one job each, per the guide:
     Manrope      headings and product names. Kartik's substitution for
                  Maharlika, which read too high-contrast in the FAQ block.
     The Seasons  short editorial lines, 3-5 words. Never decision-critical copy.
     Open Sans    body, buttons, labels, prices, all interface. Shopify serves it.

   NOTE: both brand guides name Maharlika as the heading face. Manrope is a
   deliberate departure and needs Tanya's sign-off. Maharlika stays declared
   below; to revert, point --balmiso-font-display back at it.

   Files are the licensed retail builds subset to Latin: 4 faces, 81KB total.
   To swap a family later, change only its @font-face block and the matching
   --balmiso-font-* variable below.
   ========================================================================== */

@font-face {
  font-family: "Maharlika";
  src: url("//www.balmiso.com/cdn/shop/t/9/assets/balmiso-maharlika-400.woff2?v=65614351339010565811788159899") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
/* Manrope is NOT declared here. It is a Theme settings font, chosen in the
   Typography font picker (typo_font_body), and Shopify serves the files. A
   local @font-face under the same family name used to sit here; it was inert
   while the picker supplied every weight in use, but it would have been
   reached for silently the moment a rule asked for a weight the picker set
   does not cover. One family, one source. */

@font-face {
  font-family: "The Seasons";
  src: url("//www.balmiso.com/cdn/shop/t/9/assets/balmiso-seasons-300.woff2?v=128546612002098474521788159900") format("woff2");
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "The Seasons";
  src: url("//www.balmiso.com/cdn/shop/t/9/assets/balmiso-seasons-400.woff2?v=22438263147161841601788159902") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "The Seasons";
  src: url("//www.balmiso.com/cdn/shop/t/9/assets/balmiso-seasons-400i.woff2?v=100903333903534056671788159903") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}

/* The brand faces are retired from typography. Maharlika and The Seasons stay
   installed as @font-face above, unreferenced and therefore never downloaded,
   so this is one line to reverse. Every rule below still writes
   var(--balmiso-font-*), but the three now resolve to the theme's own
   variables, which Theme settings > Typography fills from Shopify's font
   pickers. One place to change a font, and it is Shopify's. */
:root {
  --balmiso-font-display:   var(--gsp-body-font-family);
  --balmiso-font-maharlika: var(--gsp-heading-font-family);
  --balmiso-font-editorial: var(--gsp-heading-font-family);
}

/* =============================================================================
   THE TYPE SYSTEM. Three faces, three jobs, declared once.

     Maharlika    headings
     The Seasons  subheadings and eyebrows
     Manrope      body and everything that is running text

   Bound to Purevea's own variables so the theme's components pick them up on
   their own terms. Heading and sub-heading are separate slots in this theme,
   which is exactly the split the guide asks for.

   Body was the gap: nothing bound --gsp-body-font-family, so it fell straight
   through to the Open Sans in settings_data and Manrope was declared as a face
   but never referenced, meaning the browser never even downloaded it.
   ========================================================================== */
:root {

  /* Purevea splits type across nine slots, not three. Binding only the obvious
     four left navigation, buttons and prices sitting on the theme's Open Sans,
     which is how a "system" quietly becomes four faces on one page. A product
     card title is a heading; the rest are running UI and take the body face. */

  --gsp-heading-font-weight:    600;
  --gsp-subheading-font-weight: 400;
}

/* The theme also stamps the body face onto <body> from settings_data rather than
   only through the variable, so the variable alone does not carry the page. */
body { font-family: var(--gsp-body-font-family); }

/* A system that any block can opt out of is not a base. Both of these hold the
   face against per-block font pickers, which otherwise let a heading or an
   eyebrow drift onto whatever the editor last chose. Doubling the class lifts
   specificity above the theme's own typography rule without !important, so
   sizes, weights and spacing set in the editor all still work: face is fixed,
   everything else stays editable.

   The balmiso-maharlika utility still overrides an eyebrow deliberately,
   because it swaps the variable rather than the property. */
.gsp-heading-title.gsp-heading-title         { font-family: var(--gsp-heading-font-family) !important; }
.gsp-heading-sub-title.gsp-heading-sub-title { font-family: var(--gsp-subheading-font-family) !important; }

/* Running text takes the body face. Buttons, prices and badges are deliberately
   NOT in this list: Theme settings > Typography has its own groups for those,
   and including them here silently overrode the panel. Buttons were rendering
   in the body face while the setting said heading. */
p, li, td, th, label, input, select, textarea,
small, .small, figcaption {
  font-family: var(--gsp-body-font-family);
}

/* Manrope carries real weights across 200-800, so headings can take a genuine
   600 rather than the 400 Maharlika was pinned to. Nothing here synthesises. */
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { font-weight: 600; }

/* --- Wordmark ---------------------------------------------------------------
   Placed artwork, never live text. balmiso-wordmark.svg is the glyph outlines
   of BALM (Maharlika) + iso (The Seasons Italic) at one size with a 19.7-unit
   gap, matched to the supplied VectorBalsam.png at 5.320:1.

   Purevea prints shop.name in a span when no logo image is set. We keep that
   span for the accessible name and mask the SVG over it. currentColor means one
   file serves the dark wordmark on light headers and the light one on dark.

   Purevea emits `.gsp-site-logo-{id} .gsp-site-logo-text { font-size: Npx
   !important }` inline in the body. `.gsp-element.gsp-site-logo` gives three
   classes to their two, so we win on specificity without knowing the id.
   -------------------------------------------------------------------------- */

@supports ((mask: url("#x")) or (-webkit-mask: url("#x"))) {
  .gsp-element.gsp-site-logo .gsp-site-logo-text {
    font-size: 0 !important;
    line-height: 1;
  }
  .gsp-element.gsp-site-logo .gsp-site-logo-text::before {
    content: "";
    display: block;
    width: 158px;
    height: 30px;
    background-color: currentColor;
    -webkit-mask: url("balmiso-wordmark.svg") no-repeat center / contain;
            mask: url("balmiso-wordmark.svg") no-repeat center / contain;
  }
  @media (max-width: 767px) {
    .gsp-element.gsp-site-logo .gsp-site-logo-text::before { width: 100px; height: 19px; }
  }
}

/* No mask support: the shop name shows in Maharlika, not the heading face. The
   wordmark is BALM in Maharlika + iso in The Seasons, so the fallback should
   look like the artwork it stands in for. */
.gsp-site-logo-text { font-family: var(--balmiso-font-maharlika); }

/* Footer wordmark. Same masked span as the header, but the footer uses it as a
   sign-off mark rather than navigation, so it runs large. The footer logo block
   emits `.gsp-block__{id} .gsp-site-logo-text { font-size: Npx !important }`
   inline; our three-class selector above already outranks it, and this rule
   only needs to resize the painted mask. */
.gsp-block-logo .gsp-element.gsp-site-logo .gsp-site-logo-text::before {
  width: clamp(280px, 60vw, 1180px);
  height: auto;
  aspect-ratio: 5.32 / 1;
}
.gsp-block-logo .gsp-element.gsp-site-logo,
.gsp-block-logo .gsp-site-logo__inner { width: 100%; }

@media (max-width: 767px) {
  .gsp-block-logo .gsp-element.gsp-site-logo .gsp-site-logo-text::before {
    width: min(86vw, 420px);
  }
}

/* Marquee bands keep Maharlika. Kartik's call: Manrope carries the headings,
   but the scrolling bands read better in the serif. .gsp-marquee-item covers
   all three on the site, the top strip, the logo band and the text band.

   The block has no font setting in its schema, only text / uppercase / italic,
   so this has to be CSS rather than a theme-editor value. */
/* The Seasons is kept for exactly one job. Everything else on the site now
   takes whatever Typography is set to, but the scrolling bands read better in
   the serif, so the face is named directly here rather than through
   --balmiso-font-editorial, which follows the theme now. Naming it here is
   also what makes the browser download it at all: the @font-face is otherwise
   unreferenced. */
:root { --balmiso-font-seasons: "The Seasons", Georgia, "Times New Roman", serif; }

.gsp-marquee-item {
  font-family: var(--balmiso-font-seasons);
  font-weight: 400;
}

/* Numerals stay in Open Sans wherever they appear, per the brand guide. */
.gsp-marquee-item .num,
.gsp-marquee-item .price,
.gsp-marquee-item .money { font-family: var(--gsp-body-font-family); }

/* --- 6. Coming soon products ----------------------------------------------
   Purevea renders a custom badge for any product tag shaped badge__TYPE_LABEL,
   emitting a span with the class custom-badges-TYPE. So the tag

       badge__soon_Coming Soon

   gives every card for that product a .custom-badges-soon badge reading
   "Coming Soon", with no Liquid changes at all.

   From there :has() lets us restyle the whole card off the presence of that
   badge: blur the photography, centre the label, and hide price and buy
   controls. Supported in every current browser, and if it ever is not, the
   card degrades to a normal product card carrying a small badge, which is
   still correct rather than broken.

   To mark a product coming soon: add the tag. To launch it: remove the tag.
   -------------------------------------------------------------------------- */

.gsp-product-card:has(.custom-badges-soon) .gsp-product-card-image img,
.gsp-product-card:has(.custom-badges-soon) .gsp-product-card-image video {
  filter: blur(14px) saturate(.85);
  transform: scale(1.08);            /* hides the soft edge blur leaves behind */
  transition: none;
}

/* the badge stops being a corner chip and becomes the centred label */
.gsp-product-card:has(.custom-badges-soon) .gsp-product-card__badges {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  margin: 0;
  padding: 0;
}

.gsp-product-card .custom-badges-soon {
  background: transparent;
  /* --balmiso-pearl was never defined, so this resolved to inherit and the badge
     took whatever colour sat behind it. It reads over photography with a shadow,
     so it is light on purpose and does not follow the scheme. */
  color: #F5F3F3;
  font-family: var(--balmiso-font-editorial);
  font-size: var(--gsp-h4-size-base);
  letter-spacing: .14em;
  text-transform: uppercase;
  text-shadow: 0 1px 18px rgba(49, 47, 50, .55);
  padding: 0;
}

/* nothing to buy while it is unreleased */
.gsp-product-card:has(.custom-badges-soon) .gsp-product-card__prices,
.gsp-product-card:has(.custom-badges-soon) .gsp-product-card__action,
.gsp-product-card:has(.custom-badges-soon) form[action*="/cart/add"] {
  display: none;
}

/* the whole card stops inviting a click through to an empty page */
.gsp-product-card:has(.custom-badges-soon) {
  pointer-events: none;
}

/* --- 7. Hero card font -----------------------------------------------------
   Maharlika on the hero card only. Everything else stays on
   --balmiso-font-display, which is Manrope.

   Set through the variables rather than font-family, because theme.css has
   .typo-h1 { font-family: var(--gsp-heading-font-family) !important }
   and a direct property rule could never win against it.

   Font only. No geometry, spacing or placement here: the box keeps its
   original layout.
   -------------------------------------------------------------------------- */

.balmiso-hero-card {

  /* The heading ignores its own 40px setting: the typo-h1 preset overrides it
     with the theme's h1 scale, !important. Sized through the variable instead. */
  --gsp-h1-font-size: var(--gsp-h2-size-base);
  --gsp-h1-line-height: 1.2;
}

.balmiso-hero-card .gsp-image-card-subheading { font-size: var(--gsp-h6-size-base); }
/* sub-line stays on the body face, deliberately not Maharlika */
.balmiso-hero-card .gsp-image-card-desc       { font-size: calc(var(--gsp-body-font-size) * 1.06); }

@media (max-width: 767px) {
  .balmiso-hero-card { --gsp-h1-font-size: calc(var(--gsp-h1-size-base) * .65); }
  .balmiso-hero-card .gsp-image-card-desc { font-size: var(--gsp-h5-size-base); }
}

/* --- 8. .seasons-font utility ----------------------------------------------
   Add custom_class "seasons-font" to any block to set it in The Seasons,
   uppercase, with editorial letter-spacing.

   Everything goes through variables because theme.css declares all four
   properties !important on .typo-h1 through .typo-h6:
     font-family / font-weight / letter-spacing / text-transform
   A direct property rule loses to those regardless of how it is written.
   -------------------------------------------------------------------------- */

.seasons-font {
  --gsp-heading-text-transform: uppercase;
  --gsp-heading-letter-spacing: .08em;
  --gsp-heading-font-weight:    400;
}

/* --- 9. Ingredient index ---------------------------------------------------
   Pairs with sections/balmiso-ingredient-index.liquid.
   Names on the left, square image on the right with its text underneath.
   Section padding is a flat 42px top and bottom, per spec.
   -------------------------------------------------------------------------- */

.balmiso-ing {
  padding-block: var(--balmiso-ing-pt-mobile, 32px) var(--balmiso-ing-pb-mobile, 32px);
}
@media (min-width: 900px) {
  .balmiso-ing {
    padding-block: var(--balmiso-ing-pt, 42px) var(--balmiso-ing-pb, 42px);
  }
}

.balmiso-ing__head { max-width: 980px; margin: 0 auto 28px; }
.balmiso-ing__eyebrow {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .18em; text-transform: uppercase;
  margin: 0 0 10px; opacity: .7;
}
.balmiso-ing__title { margin: 0 0 8px; }
.balmiso-ing__intro { max-width: 56ch; opacity: .85; }

.balmiso-ing__grid { display: grid; gap: 32px; }
@media (min-width: 900px) {
  /* Two columns of one height. The heading and the names stack down column
     one; the panel spans both rows, so it starts level with the eyebrow and
     ends level with the last name. The image absorbs whatever height is left
     after the caption, which is why its 1/1 ratio is dropped here. */
  .balmiso-ing__grid {
    /* Matches the theme's own accordion section, which splits its container
       into two equal columns (measured: 160->1000 and 1000->1840). A fixed
       460px image column left the name rows running ~1160px, far too long
       for a one-word label. */
    grid-template-columns: 1fr 1fr;
    /* Four rows: flexible spacers above and below, heading and list between.
       Changing the weight of the two spacers moves the pair top, centre or
       bottom without touching anything else. Set from the theme editor. */
    grid-template-rows: var(--ing-space-top, 1fr) auto auto var(--ing-space-bottom, 1fr);
    gap: 0 60px;
    align-items: stretch;
  }
  .balmiso-ing__head  { grid-column: 1; grid-row: 2; max-width: none; margin: 0 0 28px; align-self: end; }
  .balmiso-ing__list  { grid-column: 1; grid-row: 3; align-self: start; }

  .balmiso-ing--v-start  { --ing-space-top: 0fr; --ing-space-bottom: 1fr; }
  .balmiso-ing--v-center { --ing-space-top: 1fr; --ing-space-bottom: 1fr; }
  .balmiso-ing--v-end    { --ing-space-top: 1fr; --ing-space-bottom: 0fr; }
  .balmiso-ing__panel { grid-column: 2; grid-row: 1 / -1;
                        display: flex; flex-direction: column; min-height: 0; }

  .balmiso-ing__panel .balmiso-ing__figure {
    flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0;
  }
  /* has to outrank the display:flex above, or a hidden entry still shows */
  .balmiso-ing__panel .balmiso-ing__figure[hidden] { display: none; }

  /* The image keeps a square and sits against the right edge of its column.
     Flexing it to the left column's height made it 810x410, and a square
     cut-out inside that letterboxed to near-nothing. */
  .balmiso-ing__panel .balmiso-ing__media {
    flex: 0 0 auto; width: 100%; max-width: 560px; margin-inline-start: auto;
  }
  .balmiso-ing__panel .balmiso-ing__img {
    aspect-ratio: 1 / 1; height: auto; object-fit: cover;
  }
  .balmiso-ing__panel .balmiso-ing__caption { max-width: 560px; margin-inline-start: auto; }
  .balmiso-ing__panel .balmiso-ing__caption { flex: 0 0 auto; }
}

.balmiso-ing__figure { margin: 0; }
.balmiso-ing__figure[hidden] { display: none; }

/* Media wrapper. The theme's own .gsp-radius-* and .img-hover-* classes do the
   corners and the zoom; this only has to make the box behave. */
.balmiso-ing__media { display: block; overflow: hidden; }

/* square image, text underneath */
.balmiso-ing__img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 1 / 1; object-fit: cover;
}
.balmiso-ing__img--empty { border: 1px dashed currentColor; opacity: .3; }

.balmiso-ing__caption { margin-top: 16px; }
.balmiso-ing__name {
  font-family: var(--balmiso-font-maharlika);
  font-size: clamp(var(--gsp-h4-size-base), 2vw, var(--gsp-h3-size-base)); line-height: 1.2; margin: 0 0 8px;
}
.balmiso-ing__desc { font-size: var(--gsp-h5-size-base); line-height: 1.6; }
.balmiso-ing__desc p { margin: 0; }
.balmiso-ing__used {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .14em; text-transform: uppercase;
  margin: 12px 0 0; opacity: .55;
}

/* per-ingredient detail: INCI, approved percentage, and the INCI link */
.balmiso-ing__meta {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); line-height: 1.5; margin: 10px 0 0; opacity: .7;
}
.balmiso-ing__inci { font-style: italic; }
.balmiso-ing__link {
  display: inline-block; margin-top: 12px;
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .08em; text-transform: uppercase;
  text-decoration: none; border-bottom: 1px solid currentColor;
  padding-bottom: 2px; opacity: .8;
}
.balmiso-ing__link:hover, .balmiso-ing__link:focus-visible { opacity: 1; }

/* Two columns of three. One word under an 810px rule read as a mistake;
   this halves the rule and fills the column. */
.balmiso-ing__list { display: grid; grid-template-columns: 1fr; }
@media (min-width: 700px) {
  .balmiso-ing__list { grid-template-columns: 1fr 1fr; column-gap: 44px; }
}
/* The list sits in a 1fr grid row, so without this its own rows stretch to
   fill the column and each name gets ~170px of height. */
.balmiso-ing__list { align-content: start; grid-auto-rows: min-content; }
/* Purevea styles every bare <button> as a full button component: fixed
   height, pill radius, centred text, a 2px border and a background. All of
   that has to be undone here, or the list renders as a stack of pills.
   .balmiso-ing__list .balmiso-ing__item gives two classes to their one. */
.balmiso-ing__list .balmiso-ing__item {
  appearance: none; -webkit-appearance: none;
  display: block;
  height: auto; min-height: 0; line-height: 1.25;
  background: none; background-color: transparent;
  border: 0; border-radius: 0;
  border-top: 1px solid currentColor;
  padding: 12px 0; margin: 0;
  text-align: left; width: 100%; cursor: pointer;
  color: inherit; opacity: .5;
  font-size: inherit;
  transition: opacity .18s ease, padding-left .18s ease;
}
@media (min-width: 700px) {
  /* last row of each column closes with a bottom rule */
  .balmiso-ing__list .balmiso-ing__item:nth-last-child(-n+2) { border-bottom: 1px solid currentColor; }
}
@media (max-width: 699px) {
  .balmiso-ing__list .balmiso-ing__item:last-child { border-bottom: 1px solid currentColor; }
}
.balmiso-ing__list .balmiso-ing__item-name {
  font-family: var(--balmiso-font-maharlika);
  font-size: clamp(calc(var(--gsp-h4-size-base) * .95), 1.8vw, var(--gsp-h3-size-base)); line-height: 1.25;
}
.balmiso-ing__list .balmiso-ing__item:hover,
.balmiso-ing__list .balmiso-ing__list .balmiso-ing__item:focus-visible { opacity: .8; padding-left: 8px; }
.balmiso-ing__list .balmiso-ing__item.is-active { opacity: 1; font-style: italic; }
.balmiso-ing__item:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .balmiso-ing__item { transition: none; }
  .balmiso-ing__item:hover, .balmiso-ing__item:focus-visible { padding-left: 0; }
}

/* -----------------------------------------------------------------------------
   HERO VIDEO HEIGHT — vendor bug fix

   Purevea styles the hero video as
       .gsp-section-hero .gsp-hero-media-inner .gsp-video
       { object-fit: cover; position: absolute; inset: 0 }
   and never sets a height. A <video> is a replaced element, so inset:0 cannot
   stretch it: per CSS 2.1 10.6.5 the height stays intrinsic and `bottom` is
   ignored. With width:100% the video renders at width / its own aspect ratio,
   so any clip that is not exactly the section ratio leaves a strip of section
   background below it. G5 never saw this because their demo clip is 16:9.

   Ours is 1280x676 (1.89:1) in a 16:9 box, which left a ~69px gap at the
   bottom. height:100% lets object-fit:cover do the cropping it was meant to.
   -------------------------------------------------------------------------- */
.gsp-section-hero .gsp-hero-media-inner .gsp-video { height: 100%; }

/* -----------------------------------------------------------------------------
   PRODUCT INDEX — image on the left

   sections/balmiso-product-index.liquid reuses the ingredient index classes,
   so everything above already applies. This only flips the columns: the panel
   moves to column one and the heading and names to column two.
   -------------------------------------------------------------------------- */
@media (min-width: 900px) {
  .balmiso-ing--media-start .balmiso-ing__head  { grid-column: 2; grid-row: 1; }
  .balmiso-ing--media-start .balmiso-ing__list  { grid-column: 2; grid-row: 2; }
  .balmiso-ing--media-start .balmiso-ing__panel { grid-column: 1; grid-row: 1 / -1; }
}

/* -----------------------------------------------------------------------------
   HOUSE EXPLAINER — sections/balmiso-house.liquid

   Full-bleed image, text carried by a bottom-up gradient rather than a flat
   overlay so the top of the frame stays clean. Content sits centred-left on
   desktop and pinned bottom-left on mobile, per the approved mockups.
   -------------------------------------------------------------------------- */

.balmiso-house {
  position: relative;
  height: var(--balmiso-house-h-mobile, 70vh);
  overflow: hidden;
  display: flex;
  align-items: flex-end;          /* mobile: text sits at the bottom */
  isolation: isolate;
}
.balmiso-house__img {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%; object-fit: cover;
}
.balmiso-house__img--empty { background: #1b1b18; }

/* the mobile art direction wins only while the mobile image exists */
.balmiso-house__img--mobile { display: none; }
@media (max-width: 767px) {
  .balmiso-house:has(.balmiso-house__img--mobile) .balmiso-house__img:not(.balmiso-house__img--mobile) { display: none; }
  .balmiso-house__img--mobile { display: block; }
}

.balmiso-house__shade {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(
    to top,
    rgba(0,0,0, var(--balmiso-house-shade, .7)) 0%,
    rgba(0,0,0, calc(var(--balmiso-house-shade, .7) * .55)) 38%,
    rgba(0,0,0, 0) 78%
  );
}

.balmiso-house__inner {
  position: relative;
  width: 100%;
  padding: var(--balmiso-house-inset-mobile, 28px);
  color: #fff;
}

.balmiso-house__eyebrow {
  font-family: var(--balmiso-font-editorial);
  font-size: calc(var(--gsp-h6-size-base) * .84); letter-spacing: .26em; text-transform: uppercase;
  line-height: 1.4; margin: 0; opacity: .8;
}
.balmiso-house__rule {
  display: block; width: 72px; height: 1px; margin: 14px 0 18px;
  background: currentColor; opacity: .45;
}
.balmiso-house__title {
  /* an h2 and the largest heading on the page, so it takes the heading face */
  font-family: var(--balmiso-font-maharlika);
  font-weight: 400; font-size: var(--gsp-h2-size-base); line-height: 1.12; letter-spacing: .01em;
  margin: 0 0 16px; color: #fff;
}
.balmiso-house__body {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-body-font-size); line-height: 1.62; max-width: var(--balmiso-house-mw, 520px);
  opacity: .92;
}
.balmiso-house__body p { margin: 0; }
.balmiso-house__body p + p { margin-top: 12px; }

@media (min-width: 768px) {
  .balmiso-house {
    height: var(--balmiso-house-h, 80vh);
    align-items: center;          /* desktop: text sits on the mid-left */
  }
  .balmiso-house__inner { padding: 0 var(--balmiso-house-inset, 120px); }
  .balmiso-house__eyebrow { font-size: var(--gsp-h6-size-base); letter-spacing: .24em; }
  .balmiso-house__rule   { width: 84px; margin: 18px 0 22px; }
  .balmiso-house__title  { font-size: clamp(calc(var(--gsp-h2-size-base) * 1.12), 3.8vw, var(--gsp-display-size-base)); margin-bottom: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  .balmiso-house__img { transition: none; }
}

/* -----------------------------------------------------------------------------
   MAHARLIKA HEADING UTILITY

   Add "balmiso-maharlika" to a section's custom class to set its headings in
   Maharlika. Overrides the variable, not the property: theme.css declares
   .typo-h1 { font-family: var(--gsp-heading-font-family) !important }, so a
   direct font-family rule loses no matter how specific it is.

   Scoped to headings only, so buttons and body keep the body face.
   -------------------------------------------------------------------------- */

/* --- 10. Collection card: title on the image --------------------------------
   Purevea's cc_skin only offers info-top, info-bottom and bounded, none of
   which puts the label over the picture. This lifts the whole .card-body
   (title, item count and arrow) onto the image.

   Opt in per section: put `balmiso-card-overlay` in the section's
   "Extended classes" field. Add a modifier to move it:
     balmiso-card-overlay--top      label at the top of the image
     balmiso-card-overlay--center   label centred
     (no modifier)                  label at the bottom, the default
   Add `balmiso-card-overlay--plain` to drop the scrim if the photography
   already carries the text on its own.
   -------------------------------------------------------------------------- */

.balmiso-card-overlay .gsp-collection-card { position: relative; }

/* the image carries a 16px bottom margin meant for the label that used to sit
   under it; with the label lifted off, that margin is dead space the overlay
   would otherwise hang below */
.balmiso-card-overlay .gsp-collection-card .card-img-top { margin-bottom: 0; }

.balmiso-card-overlay .gsp-collection-card .card-body {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  z-index: 2;
  padding: 22px 24px !important;
  border-end-start-radius: inherit;
  border-end-end-radius: inherit;
  background: linear-gradient(to top,
              rgba(49, 47, 50, .78) 0%,
              rgba(49, 47, 50, .42) 45%,
              rgba(49, 47, 50, 0) 100%);
}

/* the theme colours the title, the count and the arrow individually, each with
   more specificity than a section-scoped rule can reach, so this is forced */
.balmiso-card-overlay .gsp-collection-card .card-body,
.balmiso-card-overlay .gsp-collection-card .card-body * {
  color: #F5F3F3 !important;
}
.balmiso-card-overlay .gsp-collection-card .card-body svg {
  fill: currentColor;
  stroke: currentColor;
}
.balmiso-card-overlay .gsp-collection-card .card-body [class*="view-more"] * {
  border-color: currentColor !important;
}

.balmiso-card-overlay--top .gsp-collection-card .card-body {
  bottom: auto; top: 0;
  border-radius: inherit;
  background: linear-gradient(to bottom,
              rgba(49, 47, 50, .78) 0%,
              rgba(49, 47, 50, .42) 45%,
              rgba(49, 47, 50, 0) 100%);
}

.balmiso-card-overlay--center .gsp-collection-card .card-body {
  top: 50%; bottom: auto;
  transform: translateY(-50%);
  background: none;
  text-shadow: 0 1px 18px rgba(49, 47, 50, .75);
}

.balmiso-card-overlay--plain .gsp-collection-card .card-body {
  background: none;
  text-shadow: 0 1px 18px rgba(49, 47, 50, .75);
}

/* size of the label once it is on the image. The theme's 18px title reads as a
   caption under a card; over a photograph it needs to carry the card.
   Dial it with a modifier alongside `balmiso-card-overlay`:
     balmiso-card-overlay--sm   18px, the theme's original
     (no modifier)              24px
     balmiso-card-overlay--lg   30px
     balmiso-card-overlay--xl   38px
   All three upper sizes shrink with the viewport, so they never wrap. */
.balmiso-card-overlay { --bal-ov-title: clamp(19px, 1.5vw, 24px); --bal-ov-count: 12px; }
.balmiso-card-overlay--sm { --bal-ov-title: 18px; }
.balmiso-card-overlay--lg { --bal-ov-title: clamp(22px, 2vw, 30px); }
.balmiso-card-overlay--xl { --bal-ov-title: clamp(26px, 2.6vw, 38px); }

.balmiso-card-overlay .gsp-collection-card .gsp-collection-card-title {
  font-size: var(--bal-ov-title);
  line-height: 1.12;
}
.balmiso-card-overlay .gsp-collection-card .gsp-collection-card-count {
  font-size: var(--bal-ov-count);
  letter-spacing: .12em;
  text-transform: uppercase;
  opacity: .82;
  margin-top: 4px;
}

/* Desktop only: the photographs sit held back, and the one under the cursor
   comes up to full. Gives the row a resting state that reads as one piece and
   makes hovering feel like picking something up.

   Touch is excluded on purpose. There is no cursor to follow, so a phone would
   only ever show the dimmed state, which is the wrong picture. `hover: hover`
   catches that; the width floor keeps it off small laptops in tablet posture.

   The theme's own shine sweep runs on a pseudo-element, and its zoom runs on
   transform, so brightness stacks with both instead of fighting them.

   Dial it with a modifier:
     balmiso-card-overlay--dim-soft     barely held back
     (no modifier)                      the default
     balmiso-card-overlay--dim-strong   deep, closer to a duotone rest
   -------------------------------------------------------------------------- */
@media (min-width: 992px) and (hover: hover) {
  /* Colour is left alone. The photographs sit a little darker at rest and come
     up to full under the cursor, so the only thing that changes is light.

     saturate stays in the filter at 1, which is a no-op, so the desaturation
     modifiers below remain available without being on by default.

     .75 to 1 is a deliberate step. A gentler knock reads as an accident of the
     photography rather than a state, and the point is that the lift is obvious. */
  .balmiso-card-overlay { --bal-ov-sat: 1; --bal-ov-dim: .75; --bal-ov-lit: 1; }
  .balmiso-card-overlay--dim-soft   { --bal-ov-dim: .85; }
  .balmiso-card-overlay--dim-strong { --bal-ov-dim: .62; }
  /* opt in only if the colour should drop back as well as the light */
  .balmiso-card-overlay--sat-soft   { --bal-ov-sat: .7; }
  .balmiso-card-overlay--sat-strong { --bal-ov-sat: .35; }

  .balmiso-card-overlay .gsp-collection-card .card-img-top img {
    filter: saturate(var(--bal-ov-sat)) brightness(var(--bal-ov-dim));
    transition: filter .45s ease;
  }
  /* .is-lit is set by balmiso.js. It carries the state inside the Shopify theme
     editor, where the editor's own selection surface sits over the page and
     :hover never reaches the card. On the live site :hover alone is enough. */
  .balmiso-card-overlay .gsp-collection-card:hover .card-img-top img,
  .balmiso-card-overlay .gsp-collection-card:focus-within .card-img-top img,
  .balmiso-card-overlay .gsp-collection-card.is-lit .card-img-top img {
    filter: saturate(1) brightness(var(--bal-ov-lit));
  }
}

/* someone who has asked their system to stop animation still gets the two
   states, just without the fade between them */
@media (prefers-reduced-motion: reduce) {
  .balmiso-card-overlay .gsp-collection-card .card-img-top img { transition: none; }
}

/* --- 11. Collection tabs: numbered list beside the image --------------------
   Measured before rebuilding this: 19 words of copy across an 1800x831 section,
   a 32px number column spread over 502px of height, and a 415px dead band on
   the right. That is not an alignment fault, it is too little content in too
   large a frame, and every attempt to align it made the emptiness bigger.

   So the number rejoins the name. Six rows that each say something, one column
   instead of three, and the description under the list. The name now appears in
   exactly one place, which was the actual fault in the theme's own version
   where it sat in both the tab and the panel.

       eyebrow  ....................................  (row 1, full width)
       image    |  No.1 Grounding Ritual Shampoo      (row 2)
                |  No.2 Fluid Renewal Conditioner
                |  ...
                |  description of the active row      (row 3)

   display:contents dissolves .nav-wrap, .nav-inner and .gsp-collection-tabs-top
   so their children become grid items of the outer row directly.

   Tokens:  --bal-tabs-media  image column width
            --bal-tabs-inset  image to list
   -------------------------------------------------------------------------- */
@media (min-width: 992px) {
  .balmiso-tabs-split {
    --bal-tabs-media: 46%;
    /* Measured at 1800px: 88px to the left of the block, 428px to the right, so
       it hung off the image instead of sitting in its own space. This centres it
       in the room beside the image. Left-aligned edges are kept rather than
       centring the two items independently, which would pull the one-line
       description out of step with the list above it. The clamp keeps the shift
       proportional so it does not crush the block on a narrower desktop. */
    --bal-tabs-inset: clamp(88px, 14vw, 260px);
    --bal-tabs-head-gap: 44px;
    --bal-tabs-row: 44px;
  }

  /* Bootstrap ships .row and .d-flex as !important, so every display here has
     to match that weight to take the box over. */
  .balmiso-tabs-split .gsp-collection-tabs-inner {
    display: grid !important;
    grid-template-columns: var(--bal-tabs-media) minmax(0, 1fr);
    grid-template-rows: auto auto 1fr;
    align-items: start;
    column-gap: 0;
    margin-inline: 0;
    padding-block: var(--bal-tabs-pad, 40px);
  }
  .balmiso-tabs-split .nav-wrap,
  .balmiso-tabs-split .nav-inner,
  .balmiso-tabs-split .gsp-collection-tabs-top { display: contents !important; }

  .balmiso-tabs-split .gsp-block__wrap {
    grid-column: 1 / -1;
    grid-row: 1;
    text-align: start;
    white-space: nowrap;
    margin-bottom: var(--bal-tabs-head-gap);
  }

  .balmiso-tabs-split .gsp-collection-tabs-featured {
    grid-column: 1;
    grid-row: 2 / -1;
    width: auto;
    max-width: none;
    padding-inline: 0;
    margin-top: 0 !important;
  }

  .balmiso-tabs-split .gsp-collection-tabs-title {
    grid-column: 2;
    grid-row: 2;
    margin-inline-start: var(--bal-tabs-inset);
    margin-top: 0 !important;
    /* a tight group again. Spreading six items over 500px was what stopped them
       reading as one list. */
    align-self: start;
  }

  .balmiso-tabs-split .gsp-tab-extra-content {
    grid-column: 2;
    grid-row: 3;
    margin-inline-start: var(--bal-tabs-inset) !important;
    margin-top: 28px !important;
    justify-self: start !important;
    max-width: none !important;
    align-self: start;
  }

  /* the name lives in the list now, so the panel carries the line only */
  .balmiso-tabs-split .gsp-tab-extra-content-description .title { display: none; }
  .balmiso-tabs-split .gsp-tab-extra-content-description .desc-wrap { max-width: 44ch; }
  .balmiso-tabs-split .gsp-tab-extra-content-description .desc { opacity: .82; }

  /* Each row reserves the height of its active state, so the list cannot jump
     as the selection moves. The section auto-advances every 3.5s, and a list
     that reflowed on every step would twitch on its own with nobody touching it. */
  .balmiso-tabs-split .gsp-collection-tabs-title .nav-link-item {
    min-height: var(--bal-tabs-row);
    display: flex;
    align-items: center;
  }
  .balmiso-tabs-split .gsp-collection-tabs-title .nav-link {
    font-size: calc(var(--gsp-h4-size-base) * 1.1) !important;
    line-height: 1.25;
    text-align: start;
    transition: font-size .25s ease, opacity .25s ease;
  }
  .balmiso-tabs-split .gsp-collection-tabs-title .nav-link.active {
    font-size: calc(var(--gsp-h3-size-base) * 1.2) !important;
  }

  /* 45% of the foreground on this ground measures 3.4:1, under the 4.5 line for
     body text. 62% brings it to about 4.3 and still reads as secondary. */
  .balmiso-tabs-split .gsp-collection-tabs-title .nav-link:not(.active) { opacity: .62; }

  /* The accent marks, it does not letter. Colouring the active row itself
     backfires on a dark ground: Tan is 5.9:1 where Pearl is 10.1, so the
     selected item would read as the faintest one in the column. Full-strength
     text stays the signal for active, and the accent goes to the rule under it.
     Tan is 5.9:1 on Green but 1.6:1 on Mist and Brick is the exact inverse, so
     the hue is opted into per section rather than fixed:
       balmiso-tabs-split--accent-tan     dark grounds (Green, Balsam)
       balmiso-tabs-split--accent-brick   light grounds (Mist, Pearl, Blush) */
  .balmiso-tabs-split--accent-tan   { --bal-tabs-accent: #CABAA7; }
  .balmiso-tabs-split--accent-brick { --bal-tabs-accent: #5B5247; }

  .balmiso-tabs-split .gsp-collection-tabs-title .nav-link.active,
  .balmiso-tabs-split .gsp-collection-tabs-title .nav-link:hover { opacity: 1; }
  .balmiso-tabs-split .gsp-collection-tabs-title .nav-link.active {
    border-bottom-color: var(--bal-tabs-accent, currentColor) !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .balmiso-tabs-split .gsp-collection-tabs-title .nav-link { transition: none; }
}

/* The theme reveals this panel's text with animate.css, and the section's
   auto-advance keeps switching tabs while the section is off-screen. By the
   time you scroll to it the animation has usually already run, and the panel
   is left sitting at opacity 0 with the product name invisible. It is
   intermittent, which is worse than broken.

   Confirmed pre-existing: with this whole stylesheet disabled the text still
   holds at opacity 0.

   !important outranks an animation, so this pins the text visible while the
   transform keyframes still play. The slide survives, only the fade goes. */
.balmiso-tabs-split .gsp-tab-extra-content [data-animate] { opacity: 1 !important; }

/* The eyebrow here is meant to read as the section's own heading, matching
   "Four rituals, one house." above it. Size, face and weight come from the
   block's own typography settings so the editor sliders still drive them.
   Case cannot: .subheading carries text-transform from the theme with no
   setting behind it, and the heading it is matching is sentence case. */
.balmiso-tabs-split .gsp-heading-sub-title {
  text-transform: none;
  letter-spacing: normal;
}

/* --- 12. Collection tabs, one screen tall ------------------------------------
   The media column carries aspect-ratio 4/5, so at 890px wide it stood 1113px
   tall and set the height of the whole section. Against a 952px viewport that
   overflowed by 161px, which is why the first entry was cut off: the list and
   the active product's line could not be on screen at the same time.

   Capping the section at one viewport and releasing that aspect ratio is the
   whole fix. The image is object-fit: cover inside a clipped frame, so the
   shorter frame crops it rather than squashing it.

   Desktop only. Mobile stacks these columns and a forced 100vh there would
   crush the stack and fight the URL bar.

   Opt in with `balmiso-tabs-screen` in the section's Extended classes.
   -------------------------------------------------------------------------- */
@media (min-width: 992px) {
  /* min() rather than a flat 900px: on a shorter screen it falls back to the
     viewport so the section can never overflow again, which was the original
     fault. On a taller one it stays at 900 instead of growing. */
  .balmiso-tabs-screen { --bal-screen-h: min(900px, 100vh); height: var(--bal-screen-h); }

  /* the eyebrow up a fifth, 16px to 19.2. Case is left to the theme, which
     already uppercases this class, so the caps hold either way. */
  /* the typography preset declares this size as !important, so matching that
     weight is the only way through; the preset picker still governs everything
     else on the block */
  .balmiso-tabs-screen .gsp-heading-sub-title { font-size: calc(var(--gsp-h6-size-base) * 1.6) !important; }

  /* A percentage height only resolves against a parent with a definite one, and
     the theme puts two more wrappers between the section and the row. Skipping
     them left the media column resolving 100% of auto, which is zero: the image
     vanished rather than filling the screen. Every link in the chain is needed. */
  .balmiso-tabs-screen .container-section,
  .balmiso-tabs-screen .gsp-section-content__wrap,
  .balmiso-tabs-screen .gsp-collection-tabs-inner,
  .balmiso-tabs-screen .gsp-collection-tabs-featured,
  .balmiso-tabs-screen .gsp-collection-tabs-featured-inner,
  .balmiso-tabs-screen .tab-pane {
    height: 100%;
    min-height: 0;
  }
  /* the 4/5 ratio is what was setting the height, so it has to go or it wins */
  .balmiso-tabs-screen .gsp-collection-tabs-featured-inner { aspect-ratio: auto !important; }
  .balmiso-tabs-screen .gsp-image,
  .balmiso-tabs-screen .gsp-image-inner { height: 100%; }
  .balmiso-tabs-screen .gsp-image img { height: 100%; object-fit: cover; }
}

/* -----------------------------------------------------------------------------
   PRODUCT INGREDIENTS — sections/balmiso-product-ingredients.liquid

   Tiles driven by the product's own custom.ingredients metafield, so one
   shared product template shows the right formula on every product.
   The role line only exists once approved wording is appended to an entry.
   -------------------------------------------------------------------------- */

.balmiso-ingr { padding-block: var(--balmiso-ingr-pt, 64px) var(--balmiso-ingr-pb, 64px); }

.balmiso-ingr__head { margin-bottom: 28px; }
.balmiso-ingr__eyebrow {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .18em; text-transform: uppercase;
  margin: 0 0 8px; opacity: .7;
}
.balmiso-ingr__title { margin: 0; }

.balmiso-ingr__grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 1px;
  grid-template-columns: 1fr;
  /* a hairline grid: the gap shows the background through, so no borders
     have to be drawn or de-duplicated at the edges */
  background: currentColor;
  border-block: 1px solid currentColor;
}
@media (min-width: 600px)  { .balmiso-ingr__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px)  { .balmiso-ingr__grid { grid-template-columns: repeat(var(--balmiso-ingr-cols, 3), 1fr); } }

.balmiso-ingr__item {
  background: var(--gsp-section-bg, var(--bs-body-bg, #fff));
  padding: 22px 24px;
  display: flex; flex-direction: column; gap: 6px;
  min-height: 96px;
}
.balmiso-ingr__name {
  font-family: var(--balmiso-font-maharlika);
  font-size: clamp(calc(var(--gsp-h4-size-base) * .85), 1.5vw, var(--gsp-h4-size-base)); line-height: 1.25;
}
.balmiso-ingr__role {
  font-family: var(--gsp-body-font-family);
  font-size: calc(var(--gsp-body-font-size) * .875); line-height: 1.55; opacity: .75;
}
.balmiso-ingr__link {
  display: inline-block; margin-top: 22px;
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .1em; text-transform: uppercase;
  text-decoration: none; border-bottom: 1px solid currentColor; padding-bottom: 2px;
}

/* A thumbnail beside the text, not above it. Stacked, one picture made its
   whole grid row 202px against 98px for the text-only rows; floated, a tile
   with an image is the same height as one without. */
.balmiso-ingr__item--has-image { display: block; }
.balmiso-ingr__item--has-image .balmiso-ingr__name,
.balmiso-ingr__item--has-image .balmiso-ingr__role { display: block; }
.balmiso-ingr__img {
  float: inline-start;
  width:  var(--balmiso-ingr-img, 64px);
  height: var(--balmiso-ingr-img, 64px);
  object-fit: cover;
  margin: 2px 14px 6px 0;
}
}


/* -----------------------------------------------------------------------------
   INGREDIENT PANEL — centre the figure in its column

   The image takes its width from its own ratio once flex fixes the height, so
   it came out 560px inside an 810px column and sat hard against the right
   edge. Shrinking the figure to its content and centring it puts the picture
   in the middle of the column and lines the caption up with the image rather
   than with the column.
   -------------------------------------------------------------------------- */
@media (min-width: 900px) {
  .balmiso-ing__panel .balmiso-ing__figure {
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
  }
}


/* the collection-tab picture is wrapped in a link (see collection-tabs.liquid).
   The anchor has to fill its parent or the image collapses inside it. */
.gsp-tabs-media-link { display: block; height: 100%; width: 100%; color: inherit; text-decoration: none; }


/* -----------------------------------------------------------------------------
   PDP — one page grid, shared by every section

   The rails sat at 48px, the ingredients grid at 160px and its container at
   110px: three different left edges down one page. Everything on the PDP now
   uses this grid, so the rails, the ingredients and anything added later
   line up on the same columns.
   -------------------------------------------------------------------------- */
.balmiso-page-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
  max-width: 1680px;
  margin-inline: auto;
  padding-inline: 24px;
}
@media (min-width: 1000px) {
  .balmiso-page-grid {
    grid-template-columns: var(--balmiso-rails-rail, 300px) minmax(0, 1fr) var(--balmiso-rails-rail, 300px);
    gap: 64px;
    align-items: start;
    padding-inline: 48px;
  }
}

/* -----------------------------------------------------------------------------
   PDP RAILS — sections/balmiso-pdp-rails.liquid

   The rails stick; the media scrolls past. On the last frame the section
   gains .is-late and the rails hand over: intro to quote, availability to
   how to use. Both halves are always in the DOM so nothing reflows.
   -------------------------------------------------------------------------- */

.balmiso-rails { padding-block: 40px 0; }

@media (min-width: 1000px) {
  .balmiso-rails__rail {
    position: sticky;
    top: var(--balmiso-rails-top, 120px);
    height: calc(100vh - var(--balmiso-rails-top, 120px) - 48px);
    /* space-between pinned the button to the top of a full-height rail and
       left the rest empty. Now driven by the section's alignment setting. */
    display: flex; flex-direction: column;
    justify-content: var(--balmiso-rails-align, center);
    gap: 28px;
  }
}

.balmiso-rails__back {
  display: inline-block; margin-bottom: 24px;
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .16em; text-transform: uppercase;
  text-decoration: none; border-bottom: 1px solid currentColor; padding-bottom: 2px;
}
.balmiso-rails__intro, .balmiso-rails__quote p {
  font-family: var(--balmiso-font-editorial);
  font-size: clamp(calc(var(--gsp-body-font-size) * 1.15), 1.35vw, var(--gsp-h4-size-base)); line-height: 1.4; margin: 0;
}
.balmiso-rails__quote { margin: 0; }
.balmiso-rails__quote cite {
  display: block; margin-top: 14px; font-style: normal;
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .16em; text-transform: uppercase; opacity: .65;
}
.balmiso-rails__desc {
  font-family: var(--gsp-body-font-family);
  font-size: calc(var(--gsp-body-font-size) * .85); line-height: 1.65; opacity: .82; max-width: 34ch;
}
.balmiso-rails__desc p { margin: 0 0 8px; }
.balmiso-rails__link {
  display: inline-block; margin-top: 16px;
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .16em; text-transform: uppercase;
  text-decoration: none; border-bottom: 1px solid currentColor; padding-bottom: 2px;
}

.balmiso-rails__rail--right { text-align: left; }
.balmiso-rails__name {
  font-family: var(--gsp-heading-font-family);
  font-weight: var(--gsp-heading-font-weight);
  letter-spacing: var(--gsp-heading-letter-spacing);
  text-transform: var(--gsp-heading-text-transform);
  font-size: var(--gsp-h3-size-base);
  line-height: 1.2;
  margin: 0 0 8px;
}
.balmiso-rails__type {
  font-family: var(--gsp-subheading-font-family);
  font-weight: var(--gsp-subheading-font-weight);
  letter-spacing: var(--gsp-subheading-letter-spacing);
  text-transform: var(--gsp-subheading-transform);
  font-size: var(--gsp-h6-size-base, 13px);
  margin: 0; opacity: .65;
}
.balmiso-rails__note, .balmiso-rails__how-body {
  font-family: var(--gsp-body-font-family);
  font-size: calc(var(--gsp-body-font-size) * .85); line-height: 1.6; margin: 0; opacity: .82;
}
.balmiso-rails__how-label {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .16em; text-transform: uppercase;
  margin: 0 0 8px; opacity: .65;
}

/* the closing row: quote on the left, how to use on the right, on the same
   columns as the rails above it */
.balmiso-rails__closing { padding-block: 8vh 2vh; align-items: start; }
.balmiso-rails__closing-mid { min-height: 1px; }

.balmiso-rails__stack { display: flex; flex-direction: column; gap: 6vh; }
.balmiso-rails__frame { margin: 0; }
.balmiso-rails__media {
  display: block; width: 100%;
  height: var(--balmiso-rails-frame, 84vh);
  object-fit: cover;
}
.balmiso-rails__media--empty { background: currentColor; opacity: .08; }

/* --- 15. PDP rails: identity on the left, buying on the right -------------
   Every face, weight, size, letter-spacing and transform below comes from the
   theme's own type system, so Typography in theme settings drives this
   section. Nothing here names a typeface or a pixel size of its own.
   -------------------------------------------------------------------------- */

.balmiso-rails__number,
.balmiso-rails__contents-item,
.balmiso-rails__options-label,
.balmiso-rails__back {
  font-family: var(--gsp-subheading-font-family);
  font-weight: var(--gsp-subheading-font-weight);
  letter-spacing: var(--gsp-subheading-letter-spacing);
  text-transform: var(--gsp-subheading-transform);
  font-size: var(--gsp-h6-size-base, 13px);
}
.balmiso-rails__number { margin: 0 0 6px; opacity: .55; }
.balmiso-rails__options-label { margin: 0; opacity: .6; }

.balmiso-rails__descriptor {
  font-family: var(--gsp-body-font-family);
  font-weight: var(--gsp-body-font-weight);
  font-size: var(--gsp-body-font-size);
  line-height: var(--gsp-body-line-height);
  letter-spacing: var(--gsp-body-letter-spacing);
  margin: 0; opacity: .62;
}

.balmiso-rails__price {
  font-family: var(--gsp-pcard-price-font-family);
  font-weight: var(--gsp-pcard-price-font-weight);
  letter-spacing: var(--gsp-pcard-price-letter-spacing);
  font-size: var(--gsp-h4-size-base);
  margin: 20px 0 0;
}

.balmiso-rails__rail--left .balmiso-rails__desc { margin-top: 20px; }

.balmiso-rails__contents {
  display: flex; flex-direction: column;
  margin-top: 28px; border-top: 1px solid rgba(var(--color-foreground-rgb), .12);
}
.balmiso-rails__contents-item {
  text-decoration: none; color: inherit;
  padding: 13px 0; border-bottom: 1px solid rgba(var(--color-foreground-rgb), .12);
  transition: opacity .25s ease;
}
.balmiso-rails__contents-item:hover { opacity: .55; }

.balmiso-rails__buy { display: flex; flex-direction: column; gap: 16px; }

.balmiso-rails__options { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.balmiso-rails__option {
  display: flex; flex-direction: column; gap: 3px;
  padding: 12px 10px; text-align: center; text-decoration: none; color: inherit;
  border: 1px solid rgba(var(--color-foreground-rgb), .18); border-radius: var(--gsp-image-radius, 4px);
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-body-font-size);
  transition: border-color .25s ease;
}
.balmiso-rails__option.is-selected { border-color: currentColor; }
.balmiso-rails__option-price { opacity: .6; }

.balmiso-rails__cta {
  display: flex; align-items: center; justify-content: center;
  min-height: var(--gsp-btn-height);
  padding: 0 20px; text-decoration: none;
  border-radius: var(--gsp-btn-radius, 999px);
  background: var(--color-button); color: var(--color-button-text);
  font-family: var(--gsp-btn-font-family);
  font-size: var(--gsp-btn-font-size);
  letter-spacing: var(--gsp-btn-letter-spacing);
  text-transform: var(--gsp-btn-transform);
  transition: opacity .25s ease;
}
.balmiso-rails__cta:hover { opacity: .88; color: var(--color-button-text); }

.balmiso-rails__rail--right .balmiso-rails__note { margin-top: 2px; }

@media (max-width: 999px) {
  .balmiso-rails__rail { display: flex; flex-direction: column; gap: 18px; }
  /* media first, then who it is, then how to buy it */
  .balmiso-rails__rail--left { order: -1; }
  .balmiso-rails__rail--right { order: 0; text-align: left; }
  .balmiso-rails__contents { margin-top: 20px; }

  /* One frame at a time, swiped sideways, rather than a long vertical scroll.
     Snap is proximity, not mandatory: mandatory cancels the native smooth
     scrolling the dots rely on. */
  .balmiso-rails__stack {
    order: -2;
    display: flex; flex-direction: row; gap: 8px;
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .balmiso-rails__stack::-webkit-scrollbar { display: none; }
  .balmiso-rails__frame { flex: 0 0 100%; scroll-snap-align: center; min-width: 0; }
  .balmiso-rails__media { height: 60vh; }

  .balmiso-rails__dots { display: flex; justify-content: center; gap: 8px; order: -2; margin-top: 12px; }
  .balmiso-rails__dot {
    width: 7px; height: 7px; padding: 0; border: 0; border-radius: 999px;
    background: var(--color-button); opacity: .28;
    transition: width .42s cubic-bezier(.22,.61,.36,1), opacity .3s ease;
  }
  .balmiso-rails__dot.is-active { width: 26px; opacity: 1; }
}

@media (min-width: 1000px) {
  .balmiso-rails__dots { display: none; }
}


/* the ingredients section sits on the shared PDP grid: its content spans the
   centre and right columns so it starts where the media does, not at the far
   left of the page */
.balmiso-anchor { display: block; position: relative; top: -140px; visibility: hidden; }
@media (min-width: 1000px) {
  /* the heading takes the left column the rails use for their copy, so the
     gutter carries something instead of sitting empty, and the tiles line up
     with the media above them */
  .balmiso-ingr__page > .balmiso-ingr__head  { grid-column: 1; align-self: start; }
  .balmiso-ingr__page > .balmiso-ingr__main  { grid-column: 2; }
  .balmiso-ingr__page > .balmiso-ingr__aside { grid-column: 3; align-self: start; }
  .balmiso-ingr__page { align-items: start; }
  .balmiso-ingr__main .balmiso-ingr__grid { margin: 0; max-width: none; }
}
@media (max-width: 999px) {
  .balmiso-ingr__page { display: block; }
}


/* how to use, now sitting in the ingredients row rather than a row of its own */
.balmiso-ingr__aside-label {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .16em; text-transform: uppercase;
  margin: 0 0 8px; opacity: .65;
}
.balmiso-ingr__aside-body, .balmiso-ingr__aside-note {
  font-family: var(--gsp-body-font-family);
  font-size: calc(var(--gsp-body-font-size) * .82); line-height: 1.6; margin: 0; opacity: .82;
}
.balmiso-ingr__aside-note { margin-top: 14px; opacity: .6; }
@media (max-width: 999px) { .balmiso-ingr__aside { margin-top: 24px; } }

/* =============================================================================
   PDP VARIATION B — the Fresh shape
   -----------------------------------------------------------------------------
   Full-bleed halves that alternate sides, and a hero whose left column doubles
   as the page's contents. Separate from the rails PDP; both exist so they can
   be compared rather than argued about.
   ========================================================================== */

/* --- hero ---------------------------------------------------------------- */
.balmiso-contents { padding-block: 32px 0; }
.balmiso-contents__grid {
  display: grid; grid-template-columns: 1fr; gap: 32px;
  padding-inline: 24px;
}
@media (min-width: 1000px) {
  .balmiso-contents__grid {
    grid-template-columns: minmax(260px, 340px) minmax(0, 1fr) minmax(220px, 300px);
    gap: 56px; align-items: start; padding-inline: 48px;
  }
  .balmiso-contents__aside,
  .balmiso-contents__side { position: sticky; top: var(--balmiso-contents-top, 120px); }
}
.balmiso-contents__back {
  display: inline-block; margin-bottom: 20px;
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .16em; text-transform: uppercase;
  text-decoration: none; border-bottom: 1px solid currentColor; padding-bottom: 2px;
}
.balmiso-contents__title {
  font-family: var(--balmiso-font-editorial);
  font-weight: 400; font-size: clamp(calc(var(--gsp-h2-size-base) * .88), 3vw, calc(var(--gsp-h1-size-base) * 1.1)); line-height: 1.1;
  margin: 0 0 8px;
}
.balmiso-contents__descriptor {
  font-family: var(--gsp-body-font-family);
  font-size: calc(var(--gsp-body-font-size) * .875); margin: 0 0 20px; opacity: .7;
}
.balmiso-contents__desc {
  font-family: var(--gsp-body-font-family);
  font-size: calc(var(--gsp-body-font-size) * .875); line-height: 1.7; max-width: 40ch;
}
.balmiso-contents__desc p { margin: 0 0 10px; }

/* the contents list: a rule per row, the way a table of contents reads */
.balmiso-contents__nav { display: flex; flex-direction: column; margin-top: 36px; }
.balmiso-contents__nav-item {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .14em; text-transform: uppercase;
  text-decoration: none; padding: 14px 0;
  border-top: 1px solid currentColor;
  opacity: .75; transition: opacity .2s ease;
}
.balmiso-contents__nav-item:last-child { border-bottom: 1px solid currentColor; }
.balmiso-contents__nav-item:hover { opacity: 1; }

.balmiso-contents__media { display: flex; flex-direction: column; gap: 16px; }
.balmiso-contents__frame { margin: 0; }
.balmiso-contents__img { display: block; width: 100%; height: auto; }
.balmiso-contents__img--empty { aspect-ratio: 1/1; background: currentColor; opacity: .08; }

.balmiso-contents__side-label, .balmiso-contents__note {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .16em; text-transform: uppercase;
  margin: 0 0 8px; opacity: .65;
}
.balmiso-contents__scent {
  font-family: var(--balmiso-font-editorial);
  font-size: clamp(var(--gsp-h4-size-base), 1.7vw, var(--gsp-h3-size-base)); margin: 0 0 14px;
}
.balmiso-contents__side-body {
  font-family: var(--gsp-body-font-family);
  font-size: calc(var(--gsp-body-font-size) * .82); line-height: 1.65; margin: 0 0 24px; opacity: .82;
}
.balmiso-contents__note { text-transform: none; letter-spacing: 0; font-size: calc(var(--gsp-body-font-size) * .82); opacity: .8; }

/* --- split panel --------------------------------------------------------- */
.balmiso-split {
  display: grid; grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .balmiso-split { grid-template-columns: 1fr 1fr; min-height: var(--balmiso-split-min, 80vh); }
  /* the flip is a column order swap, so the markup stays in reading order for
     anyone not seeing the layout */
  .balmiso-split--flip .balmiso-split__text  { order: 2; }
  .balmiso-split--flip .balmiso-split__media { order: 1; }
}
.balmiso-split__text {
  display: flex; align-items: center;
  padding: var(--balmiso-split-pad, 96px) clamp(24px, 5vw, 96px);
}
.balmiso-split__inner { width: 100%; max-width: 46ch; }
.balmiso-split__eyebrow {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); letter-spacing: .18em; text-transform: uppercase;
  margin: 0 0 28px; opacity: .7;
}
/* the oversized list. The last line carries the weight, which is how Fresh
   marks the hero ingredient without a separate label. */
.balmiso-split__lead {
  font-family: var(--balmiso-font-display);
  font-size: clamp(var(--gsp-h3-size-base), 3.4vw, calc(var(--gsp-display-size-base) * .93)); line-height: 1.18; letter-spacing: -.01em;
  margin: 0 0 28px;
}
.balmiso-split__lead br + * { display: block; }
.balmiso-split__link {
  display: inline-block; margin-bottom: 28px;
  font-family: var(--gsp-body-font-family);
  font-size: calc(var(--gsp-body-font-size) * .82); text-decoration: none;
  border-bottom: 1px solid currentColor; padding-bottom: 2px;
}
.balmiso-split__body {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h5-size-base); line-height: 1.7;
}
.balmiso-split__body p { margin: 0 0 12px; }
.balmiso-split__footnote {
  font-family: var(--gsp-body-font-family);
  font-size: var(--gsp-h6-size-base); line-height: 1.5; margin: 24px 0 0; opacity: .55;
}
.balmiso-split__media { position: relative; min-height: 52vh; }
.balmiso-split__img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  position: absolute; inset: 0;
}
.balmiso-split__img--empty { position: static; min-height: 52vh; background: currentColor; opacity: .08; }
@media (max-width: 899px) {
  .balmiso-split__media { min-height: 46vh; }
  .balmiso-split__img { position: static; height: 46vh; }
}


/* the complete INCI, under how to use. Small, dense and set in the editorial
   face the way a label reads, not styled like body copy. */
.balmiso-ingr__inci { margin-top: 28px; padding-top: 22px; border-top: 1px solid currentColor; }
.balmiso-ingr__inci-body {
  font-family: var(--balmiso-font-editorial);
  font-size: calc(var(--gsp-body-font-size) * .82); line-height: 1.75; margin: 0; opacity: .8;
}

/* ------------------------------------------------------------------
   Marquee strip: word + mark as one unit
   Pairs an @handle with its icon, or "As seen on" with a partner logo,
   so the two never drift apart at the section gap.
   Rendered by the balmiso_pair block in sections/marquee.liquid.
   ------------------------------------------------------------------ */
.gsp-marquee .balmiso-pair {
  display: inline-flex;
  align-items: center;
  gap: var(--balmiso-pair-gap, 10px);
  white-space: nowrap;
}

.balmiso-pair__text { display: inline-block; }

.balmiso-pair__icon {
  display: inline-flex;
  align-items: center;
  line-height: 0;
  flex: 0 0 auto;
}
.balmiso-pair__icon svg {
  display: block;
  width: 0.9em;
  height: 0.9em;
}

.balmiso-pair__logo {
  display: block;
  flex: 0 0 auto;
  width: auto;
  height: var(--balmiso-pair-logo-h-mobile, 18px);
  object-fit: contain;
}

@media (min-width: 768px) {
  .balmiso-pair__logo { height: var(--balmiso-pair-logo-h, 26px); }
}

/* ------------------------------------------------------------------
   Hero row, right card: a video occupying the exact box the image
   card held. The desktop ratio (111:116) is set on the block itself.
   The vendor video block has no mobile ratio setting, so the 2:3 the
   image card rendered at under 768px is restored here.
   ------------------------------------------------------------------ */
@media (max-width: 767.98px) {
  .balmiso-hero-video .gsp-element-video {
    --gsp-aspect-ratio-mobile: 2/3;
  }
}

/* Vendor gap. When a video block has a cover image but no video yet,
   el__video renders the poster inside a plain <div class="gsp-media-wrapper">
   rather than <video-element>, and theme.css only styles video-element's
   children. The poster is left at its natural size and overflows the box.
   This is the vendor's own video-element > img treatment, applied to that
   one case. Once a video is set, <video-element> renders and the vendor
   rules take over, so this selector stops matching. */
.gsp-element-video > div.gsp-media-wrapper > :is(img, svg) {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  border-radius: inherit;
}

/* Hover parity with the image card beside it. That card uses the theme's
   img-hover-zoom-in: scale(1.05) over 0.3s ease-in-out. The video block has
   no hover setting at all, so the same motion is applied here.
   theme.css already puts `transition: all 0.55s` on video-element's children
   to fade the poster out into the video, so the fade is restated rather than
   replaced, and only transform picks up the card's 0.3s timing.
   .gsp-element-video is already overflow:hidden, so nothing spills. */
.balmiso-hero-video .gsp-element-video :is(img, svg, video, iframe) {
  transform: scale(1);
  transition:
    opacity 0.55s cubic-bezier(0.4, 0, 0.2, 1),
    visibility 0.55s cubic-bezier(0.4, 0, 0.2, 1),
    transform 0.3s ease-in-out;
}

.balmiso-hero-video .gsp-element-video:hover :is(img, svg, video, iframe) {
  transform: scale(1.05);
}

@media (prefers-reduced-motion: reduce) {
  .balmiso-hero-video .gsp-element-video:hover :is(img, svg, video, iframe) {
    transform: scale(1);
  }
}

/* The zoom above gives the film a transform, and any transform makes an element
   paint in the same layer as absolutely positioned ones, in DOM order. Inside
   <video-element> the theme renders the poster, then the volume control, then
   the <video> last, so once the film was showing it painted straight over the
   mute button: visible over the poster, gone the moment the video faded in or
   the card was hovered. The bottom card has no zoom, which is why its button
   survived. Lifting the control keeps it above its own film whatever the film
   is doing. It stays below the sticky header and the drawers, which sit in
   their own higher stacking contexts. */
.video-interaction__control-volume {
  z-index: 2;
}

/* --- Scent panel: the detail must not move ----------------------------------
   Each accord runs a different number of lines, so the panel was a different
   height for every one. Inside a vertically centred grid that drags the image,
   the name and the notes to a new position on each switch. Measured: 416px tall
   on Amber Resin, 608px on Oud & Bergamot, a 192px jump between siblings.

   All six figures are already in the markup; only one is shown at a time. So
   instead of taking the other five out of the flow, they stay stacked in a
   single grid cell and are hidden with visibility. The cell is then always as
   tall as the longest accord, the panel has one fixed height, and nothing
   shifts. visibility:hidden keeps the inactive five out of the accessibility
   tree, which display:none was doing before, so nothing is read out twice.

   The figure also had width:fit-content, which made its width follow whatever
   image was showing and moved it sideways as well. A constant max-width, still
   centred, holds it in one place.
   -------------------------------------------------------------------------- */
@media (min-width: 900px) {
  .balmiso-ing__panel {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: start;
  }

  .balmiso-ing__panel .balmiso-ing__figure {
    grid-area: 1 / 1;
    width: 100%;
    max-width: 560px;
    margin-inline: auto;
  }

  /* theme.css line 598 carries Bootstrap's reboot rule, [hidden] { display:
     none !important }, which no amount of specificity can outrank. !important
     here is not a shortcut, it is the only way to answer another !important,
     and it is scoped to the five inactive figures in this one panel. */
  .balmiso-ing__panel .balmiso-ing__figure[hidden] {
    display: flex !important;
    visibility: hidden;
    pointer-events: none;
  }
}

/* Each figure is now stretched to the height of the longest accord, so the
   notes block can absorb that slack instead of the product line trailing
   whatever number of lines happens to be above it. The result is one baseline
   for the product line across all six, and the same gap above it every time.
   Measured before this: three different heights spanning 48px. */
@media (min-width: 900px) {
  .balmiso-ing__panel .balmiso-ing__caption {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }

  .balmiso-ing__panel .balmiso-ing__desc { flex: 1 1 auto; }
  .balmiso-ing__panel .balmiso-ing__used { flex: 0 0 auto; }
}

/* --- Hero: a 16:9 frame on phones, full-bleed on desktop --------------------
   The section runs height_mode: fill, so hero.liquid prints this inline:

       .gsp-section-{id} { --gsp-hero-min-height-mobile: 100vh !important; }

   and theme.css feeds that variable into the media height, which makes the
   video swallow the whole phone screen and read as 9:16. size_mode is a single
   setting shared by both breakpoints, so switching it to aspect-ratio mode
   would change the desktop too.

   Overriding the heights directly does not work: .gsp-hero-media also carries
   Bootstrap's h-100, its ancestors are all pinned to the same 100vh, and even
   height:auto !important down the whole chain still measured 812px. The height
   is not the cause, the variable is. So the variable is what gets replaced,
   and the theme's own machinery then produces the band.

   The inline rule is one class with !important, so this needs two classes on
   the same element plus !important to outrank it, order alone will not do it.
   Desktop is untouched: --gsp-hero-min-height still holds 100vh.
   -------------------------------------------------------------------------- */
@media (max-width: 767.98px) {
  .balmiso-hero-mobile-169.gsp-section__wrap {
    --gsp-hero-min-height-mobile: calc(100vw * 9 / 16) !important;
  }
}

/* Hero copy inside the phone band. At 16:9 the frame is only ~211px tall, so
   the desktop scale (22px heading over a 48px button, both centred) filled it
   edge to edge. Smaller type, and the pair sits on the floor of the frame
   instead of the middle, so the picture reads first.
   The content wrapper carries Bootstrap's align-items-center utility, which is
   !important, hence the same here. Desktop keeps its own scale and centring. */
@media (max-width: 767.98px) {
  .balmiso-hero-mobile-169 .gsp-hero-content {
    align-items: flex-end !important;
    padding-bottom: 18px;
  }

  .balmiso-hero-mobile-169 .gsp-heading-title {
    font-size: var(--gsp-h5-size-base) !important;
    line-height: 1.25 !important;
  }

  .balmiso-hero-mobile-169 .btn {
    --gsp-btn-height: 34px;
    font-size: var(--gsp-h6-size-base) !important;
    padding-inline: 16px !important;
  }
}

/* --- Scrolling cards: a swipe carousel on phones ----------------------------
   G5 turns the layered stack off on mobile in three places in
   scrolling-cards.section.js: enableAnimation() returns early on
   GSP.config.device.mobile, processCards() guards on it again, and a
   matchMobile listener calls resetCards() to strip every transform. So on a
   phone the section is six full cards stacked, measured at 2682px, 3.3 screens,
   a quarter of the whole home page, with nothing happening as you scroll.

   Shrinking the image was the wrong lever: at 21:9, the most extreme ratio the
   section offers, it still ran 2419px, because 188px of every card is badge,
   name, button and padding, which no ratio touches.

   The track is already a flex column, so this only turns it on its side. Native
   scroll-snap does the swiping, with the momentum and rubber-banding the phone
   already knows how to do, and no JavaScript in the scroll path. Cards sit at
   84% so the next one peeks, which is what tells you it moves.
   Desktop is untouched: the layered stack still runs above 768px.
   -------------------------------------------------------------------------- */
@media (max-width: 767.98px) {
  .balmiso-cards-swipe .scrolling-cards-inner {
    flex-direction: row !important;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    /* mandatory, so a swipe always lands a whole card rather than stopping
       wherever the finger left it. This only conflicts with NATIVE smooth
       scrolling, which is why the dots animate by assigning scrollLeft frame
       by frame instead: that is unaffected, verified landing card 4 dead
       centre under mandatory. */
    scroll-snap-type: x mandatory;
    scroll-padding-inline: 16px;
    gap: 12px !important;
    scrollbar-width: none;
    /* the sticky wrap is for the desktop stack and fights a horizontal track */
    position: static !important;
  }

  .balmiso-cards-swipe .scrolling-cards-inner::-webkit-scrollbar { display: none; }

  .balmiso-cards-swipe .gsp-scrolling-cards-item {
    flex: 0 0 84%;
    scroll-snap-align: center;
    position: static !important;
    top: auto !important;
  }

  /* position indicator, built by balmiso.js */
  .balmiso-dots {
    display: flex;
    justify-content: center;
    gap: 8px;
    margin-top: 20px;
  }

  /* Balanced Brick #5B5247, the brand guide's colour of action: "Brick carries
     the action. One button colour, one link colour, everywhere." Read through
     --color-button so it stays tied to the scheme rather than pinned.

     The active dot does not just brighten, it grows into a pill. Width is the
     animated property because it reads as one mark travelling along the row
     rather than six lights switching, which is what makes it feel like a
     position and not a state. currentColor was inheriting the card's light
     foreground and computing to the page background, so the inactive dots were
     invisible; both states are Brick now and differ in weight and width. */
  .balmiso-dots__dot {
    flex: 0 0 auto;
    width: 6px;
    height: 6px;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: var(--color-button, #5B5247);
    opacity: .22;
    transition: width .42s cubic-bezier(.22, .61, .36, 1),
                opacity .28s ease;
    cursor: pointer;
  }

  .balmiso-dots__dot[aria-current="true"] {
    width: 26px;
    opacity: 1;
  }

  @media (prefers-reduced-motion: reduce) {
    .balmiso-dots__dot { transition: none; }
  }
}

/* the dots are a phone affordance; the desktop stack has no use for them */
@media (min-width: 768px) {
  .balmiso-dots { display: none; }
}

/* Card interior on phones. The stack was built for a full-width desktop card:
   16px under the pill, 24px under the name, 16px of body padding either side
   of the button. On a 288px card that reads as a gap rather than a rhythm,
   and it is the padding, not the picture, that makes these cards tall. */
@media (max-width: 767.98px) {
  /* theme.css drives these from variables on the item, so they are set the
     same way rather than fought as properties. */
  .balmiso-cards-swipe .gsp-scrolling-cards-item {
    --cards-badge-sp: 0.5rem;        /* was 1rem under the pill */
    --card-body-pd: 0.625rem 0.5rem; /* was 1rem 0.5rem around the button */
    --card-inner-pd: 0.375rem;       /* was 0.5rem */
  }

  /* the name's spacing is a plain property at three classes, so this has to
     match that weight to land */
  .balmiso-cards-swipe .gsp-scrolling-cards-item .gsp-scrolling-cards-heading {
    margin-bottom: 0.875rem;         /* was 1.5rem */
  }
}

/* --- Scent index as an accordion on phones ----------------------------------
   The flag below is the single source of truth for which mode is in play.
   balmiso.js reads it back with getComputedStyle instead of measuring the
   viewport, because innerWidth and matchMedia can both disagree with what the
   stylesheet is actually doing, and when they disagreed the figures were moved
   while none of this applied.

   The figures are placed after their name, not inside it: the names are
   <button> elements and a button sizes to its content's max-content width, so
   a nested figure inflated the row to the image's natural 928px and pushed the
   grid off screen.
   -------------------------------------------------------------------------- */
@media (max-width: 899.98px) {
  .balmiso-ing__grid { --balmiso-accordion: 1; }

  .is-accordion .balmiso-ing__panel { display: none; }

  /* one column, so a figure always lands directly under its own name. Above
     700px the list is otherwise two columns, which would put them side by side */
  .is-accordion .balmiso-ing__list { grid-template-columns: 1fr; }

  .is-accordion .balmiso-ing__list .balmiso-ing__item { padding: 14px 0; }

  /* room for the mark, and the name stays the handle */
  .is-accordion .balmiso-ing__item-name {
    display: block;
    position: relative;
    padding-inline-end: 30px;
  }

  /* a plus that becomes a minus, drawn from two rules so it takes the colour */
  .is-accordion .balmiso-ing__item-name::before,
  .is-accordion .balmiso-ing__item-name::after {
    content: "";
    position: absolute;
    inset-inline-end: 2px;
    top: 50%;
    width: 11px;
    height: 1px;
    background: currentColor;
    transition: transform .3s cubic-bezier(.22,.61,.36,1);
  }
  .is-accordion .balmiso-ing__item-name::before { transform: rotate(90deg); }
  .is-accordion .balmiso-ing__item.is-active .balmiso-ing__item-name::before {
    transform: rotate(0deg);
  }

  /* the open figure is a grid child in its own right, sitting in the row
     immediately below the name that opened it. [hidden] keeps the other five
     out of the flow, which is what makes it an accordion at all. */
  .is-accordion .balmiso-ing__figure {
    display: block;
    grid-column: 1;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    margin: 0 0 18px;
    animation: balmiso-ing-open .34s cubic-bezier(.22,.61,.36,1) both;
  }

  .is-accordion .balmiso-ing__media { width: 100%; max-width: 100%; }
  .is-accordion .balmiso-ing__img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
  .is-accordion .balmiso-ing__caption { margin-top: 14px; max-width: none; }

  @keyframes balmiso-ing-open {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }
}

@media (max-width: 899.98px) and (prefers-reduced-motion: reduce) {
  .is-accordion .balmiso-ing__figure { animation: none; }
  .is-accordion .balmiso-ing__item-name::before { transition: none; }
}

/* --- 14. About page · "Life here asks different things" -------------------
   Replaces the old centred wall of rich text with two sections:
     balmiso_conditions  (icon-cards)      statement left, six condition chips right
     balmiso_reality     (image-with-text) image left, the response on the right
   Both carry sentences, not labels, so heading case is forced back to
   sentence case. theme.css declares text-transform !important on .typo-h1..h6,
   so it can only be beaten through the variable.
   -------------------------------------------------------------------------- */

/* Every heading on the editorial pages is a sentence, not a label, and the
   theme's global Title Case makes them read as neither. Scoped to
   body.template-page so home, product and collection pages keep the site
   setting. theme.css declares text-transform !important on .typo-h1..h6, so
   this can only be beaten through the variable. */

body.template-page {
  --gsp-heading-text-transform: none;
}

/* Chips carry no icon: the empty slot ships a 100px bottom margin. */
.balmiso-conditions .gsp-icon-cards .card-img-top {
  display: none;
}

.balmiso-conditions .gsp-icon-cards {
  --icon-card-padding: 22px 20px;
  display: flex;
  align-items: flex-end;
  min-height: 108px;
  border-radius: 14px;
  transition: transform .45s cubic-bezier(.22,.61,.36,1);
}

.balmiso-conditions .gsp-icon-cards:hover {
  transform: translateY(-4px);
}

.balmiso-conditions .gsp-icon-cards-heading {
  text-transform: none;
}

@media (max-width: 767.98px) {
  .balmiso-conditions .gsp-icon-cards {
    --icon-card-padding: 16px 14px;
    min-height: 78px;
  }
}

/* The closing question is the pay-off line: let it breathe. */
[class*="__balmiso_reality"] .gsp-simple-heading:last-child,
[class*="__balmiso_reality"] h3 {
  font-style: italic;
}

/* --- 16. PDP card rows: equal heights, and a full-bleed gallery on mobile ---
   Purevea lays both card rows out in a swiper, and a swiper slide sizes to its
   own content, so a two-line card and a four-line card end up different
   heights. Stretching the slide and letting the card fill it evens the row.
   -------------------------------------------------------------------------- */

.balmiso-pdp-cards .swiper-slide { height: auto; }

.balmiso-pdp-cards .swiper-slide > *,
.balmiso-pdp-cards .gsp-block__wrap,
.balmiso-pdp-cards .gsp-block,
.balmiso-pdp-cards .gsp-block__inner {
  height: 100%;
}

.balmiso-pdp-cards .gsp-image-box,
.balmiso-pdp-cards .gsp-icon-cards {
  height: 100%;
  display: flex;
  flex-direction: column;
}

/* The card body takes the slack, so the tallest card sets the row and the
   others pad out below their text rather than cropping the card. */
.balmiso-pdp-cards .gsp-image-box .gsp-image-box-content,
.balmiso-pdp-cards .gsp-icon-cards .card-body {
  flex: 1 1 auto;
}

/* Gallery: edge to edge and taller, rather than a small plate inside the
   page's own padding. */
@media (max-width: 999px) {
  .balmiso-rails__stack {
    width: 100vw;
    margin-inline: calc(50% - 50vw);
    gap: 0;
  }
  .balmiso-rails__frame { flex: 0 0 100vw; }
  .balmiso-rails__media { height: 72vh; }
}

/* theme.css reserves 100px under the icon on desktop, which is why our cards
   opened a hole between the icon and the heading. Purevea's own copy happens
   to be long enough to close it; ours is not, so the gap is set here instead
   of relying on the text to fill it. */
.balmiso-pdp-cards .gsp-icon-cards {
  --icon-card-spacing-icon: 32px;
}

@media (max-width: 1279.98px) {
  .balmiso-pdp-cards .gsp-icon-cards { --icon-card-spacing-icon: 26px; }
}
@media (max-width: 767.98px) {
  .balmiso-pdp-cards .gsp-icon-cards { --icon-card-spacing-icon: 20px; }
}

/* --- 17. PDP ritual tabs: hold the number row still ------------------------
   The step descriptions run from 17 to 77 characters across the six products,
   so the text block changed height as the tabs were clicked and the 01/02/03
   row jumped with it. Reserving three lines keeps the numbers on one spot
   whichever step is open. Pairs with the tabs in compare-and-explore.
   -------------------------------------------------------------------------- */

.balmiso-pdp-steps .tab-desc {
  min-height: calc(var(--gsp-body-line-height, 1.5) * 3em);
}

@media (max-width: 767.98px) {
  .balmiso-pdp-steps .tab-desc {
    min-height: calc(var(--gsp-body-line-height, 1.5) * 4em);
  }
}

/* --- 18. Formula card with no image ----------------------------------------
   The last card is a plain list of supporting ingredients: no name, no
   photograph, just the list. With the image frame gone the text fills the
   card, and it still has to sit at the same height as the cards beside it.
   -------------------------------------------------------------------------- */

.gsp-image-box--text-only {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* The equal-height rule stretches the card body to fill the row, so centring
   the wrapper alone leaves the text pinned to the top of a tall empty card.
   The body and its inner have to centre too. */
.gsp-image-box--text-only .gsp-image-box-content,
.gsp-image-box--text-only .gsp-image-box-content-inner {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.gsp-image-box--text-only .gsp-image-box-info { margin-top: 6px; }

.gsp-image-box--text-only .gsp-image-box-content {
  padding: 26px 22px;
}

.gsp-image-box--text-only .gsp-image-box-heading {
  font-size: var(--gsp-h5-size-base, 15px);
}

/* Sizes here must be absolute, never em. The card wrapper carries Bootstrap's
   .fs-0, which is font-size: 0 !important, and the other cards escape it only
   because the theme gives their text an inline size. An em resolves against
   zero and renders at no size at all. */
.gsp-image-box--text-only .gsp-image-box-desc {
  font-size: calc(var(--gsp-body-font-size, 16px) * .9) !important;
  line-height: 1.55;
}

/* The collapsed layout keeps a card's description at opacity 0 and reveals it
   on hover, which is fine for a six word line and wrong for a legal ingredient
   declaration. On the list card the text is always visible, and the content
   box grows to fit rather than being pinned to the height of a title. The
   theme sets these on hover with !important, so these have to match. */
.gsp-image-box--text-only .gsp-image-box-info {
  position: static !important;
  opacity: 1 !important;
  visibility: visible !important;
  transform: none !important;
}

.gsp-image-box--text-only .gsp-image-box-content-inner {
  height: auto !important;
  overflow: visible !important;
}

@media (max-width: 767.98px) {
  .gsp-image-box--text-only .gsp-image-box-content { padding: 20px 18px; }

}


/* --- 19. Ingredients dialog -------------------------------------------------
   The full declaration is 19 to 27 ingredients. Printed into a card it dwarfs
   the cards beside it; behind a trigger it stays available without taking the
   page over. Native <dialog>, so the browser handles the top layer, the
   backdrop and Escape.
   -------------------------------------------------------------------------- */

.balmiso-inci__trigger {
  display: inline-flex; align-items: center; gap: 7px;
  align-self: flex-start;
  margin-top: 18px;
  padding: 10px 18px;
  cursor: pointer;
  background: transparent;
  color: inherit;
  border: 1px solid rgba(var(--color-foreground-rgb), .2);
  /* the theme gives every button a pill radius, which turned a border-bottom
     into a curved line under the label */
  border-radius: var(--gsp-btn-radius, 999px);
  font-family: var(--gsp-body-font-family);
  line-height: 1;
  transition: background .25s ease, border-color .25s ease;
}
.balmiso-inci__trigger:hover {
  background: rgba(var(--color-foreground-rgb), .045);
  border-color: rgba(var(--color-foreground-rgb), .42);
}

.balmiso-inci__plus {
  line-height: 1; opacity: .55;
}

.balmiso-inci {
  border: 0; padding: 0; max-width: 440px; width: calc(100% - 32px);
  border-radius: 14px; background: var(--color-background); color: var(--color-foreground);
}
/* a viewport backdrop behind a modal: dark on every scheme, by design */
.balmiso-inci::backdrop { background: rgba(0,0,0,.42); }

.balmiso-inci__panel { position: relative; padding: 26px 24px 24px; }

.balmiso-inci__close {
  position: absolute; top: 14px; inset-inline-end: 14px;
  width: 32px; height: 32px; border: 0; border-radius: 999px;
  background: none; color: inherit; cursor: pointer;
  /* a glyph inside a fixed 32px control, not typography: deliberately not on
     the scale, or a large H4 would push it out of its own button */
  font-size: 22px !important; line-height: 1;
}
.balmiso-inci__close:hover { opacity: .55; }

.balmiso-inci__eyebrow {
  font-family: var(--gsp-subheading-font-family);
  font-weight: var(--gsp-subheading-font-weight);
  letter-spacing: var(--gsp-subheading-letter-spacing);
  text-transform: var(--gsp-subheading-transform);
  margin: 0 0 6px; opacity: .55;
}
.balmiso-inci__title {
  font-family: var(--gsp-heading-font-family);
  margin: 0 0 4px;
}
.balmiso-inci__note {
  font-family: var(--gsp-body-font-family);
  margin: 0 0 18px; opacity: .6;
}

/* One running line rather than a row per ingredient: 27 rows made a tall box
   that needed its own scroll, which is a second scroll on a page that already
   has one. Percentages sit in brackets, the way a formulation is written out.

   Sizes come from the theme's own presets on the markup (subheading, typo-h4,
   text-sm), not from numbers here. Those presets set font-size with
   !important, which also clears the .fs-0 the card wrapper carries. */
.balmiso-inci__body {
  font-family: var(--gsp-body-font-family);
  line-height: 1.95;
  margin: 18px 0 0;
  opacity: .82;
  max-height: 46vh;
  overflow-y: auto;
}

/* the eyebrow has to clear the close button */
.balmiso-inci__eyebrow {
  margin: 0 44px 10px 0;
  opacity: .55;
}

.balmiso-inci__title { margin: 0 0 6px; }

.balmiso-inci__note {
  margin: 0;
  padding-bottom: 16px;
  border-bottom: 1px solid rgba(var(--color-foreground-rgb), .1);
  opacity: .6;
}

.balmiso-inci__closeform { position: absolute; top: 18px; inset-inline-end: 18px; margin: 0; }

@media (max-width: 767.98px) {
  .balmiso-inci__panel { padding: 22px 18px 20px; }
  .balmiso-inci__body { max-height: 54vh; line-height: 1.9; }
}

/* =============================================================================
   Legal and policy pages
   -----------------------------------------------------------------------------
   One template, page.legal.json, serves Terms, Shipping, Returns, Privacy and
   Cookies. The words live in the page body in Shopify admin, so the client
   edits a policy without touching the theme, and a lawyer's revision is a
   paste rather than a deploy.

   Long-form legal is read, not skimmed, so the measure is capped. 78ch
   measured out at 761px, about 95 characters a line, well past the 60 to 75
   that stays readable over several screens. Manrope's zero is wide, so ch
   under-reports here; 640px is set directly.
   Everything else comes from Theme settings > Typography.
   ========================================================================== */
.balmiso-legal { max-width: 640px; margin-inline: auto; }

.balmiso-legal > *:first-child { margin-top: 0; }

.balmiso-legal h2 {
  font-family: var(--gsp-heading-font-family);
  font-weight: var(--gsp-heading-font-weight);
  letter-spacing: var(--gsp-heading-letter-spacing);
  text-transform: var(--gsp-heading-text-transform);
  font-size: var(--gsp-h3-font-size);
  line-height: var(--gsp-h3-line-height);
  margin: 52px 0 14px;
  padding-top: 26px;
  border-top: 1px solid rgba(var(--color-foreground-rgb), .14);
}

.balmiso-legal h3 {
  font-family: var(--gsp-heading-font-family);
  font-weight: var(--gsp-heading-font-weight);
  letter-spacing: var(--gsp-heading-letter-spacing);
  text-transform: var(--gsp-heading-text-transform);
  font-size: var(--gsp-h5-size-base);
  line-height: 1.4;
  margin: 28px 0 8px;
}

.balmiso-legal :is(p, li) {
  font-family: var(--gsp-body-font-family);
  font-weight: var(--gsp-body-font-weight);
  font-size: var(--gsp-body-font-size);
  line-height: var(--gsp-body-line-height);
  letter-spacing: var(--gsp-body-letter-spacing);
}

.balmiso-legal p { margin: 0 0 16px; }
.balmiso-legal ul { margin: 0 0 18px; padding-inline-start: 20px; }
.balmiso-legal li { margin-bottom: 7px; }

/* The entity block at the head of a policy: who you are contracting with. */
.balmiso-legal__entity {
  border: 1px solid rgba(var(--color-foreground-rgb), .16);
  padding: 20px 22px;
  margin: 0 0 12px;
}
.balmiso-legal__entity p { margin: 0 0 4px; opacity: .78; }
.balmiso-legal__entity p:last-child { margin: 0; }

@media (max-width: 767.98px) {
  .balmiso-legal h2 { margin-top: 38px; padding-top: 20px; }
}

/* =============================================================================
   FAQ support card
   -----------------------------------------------------------------------------
   The information block writes color-scheme-N onto the card but nothing paints
   it, because the vendor's own sections pair that class with `gradient` and the
   block does not. The scheme's background is therefore set and never used, so
   the card rendered flat on the page. This paints it and gives it the shape the
   reference panel has.
   ========================================================================== */
.gsp-information-card {
  background: rgb(var(--color-background-rgb));
  color: rgb(var(--color-foreground-rgb));
  border-radius: 10px;
  padding: 48px 32px;
  margin-top: 34px;
  text-align: center;
}
.gsp-information-card .gsp-block__inner { max-width: 46ch; margin-inline: auto; }
@media (max-width: 767.98px) {
  .gsp-information-card { padding: 34px 20px; margin-top: 24px; }
}


/* The hero video sits in a section that is not positioned, so its volume
   control needs an anchor of its own. Matches the placement theme.css gives
   every other video: 8px in from the top and trailing edge. */
.gsp-hero, .gsp-hero-video, [class*="hero"] > .gsp-video { position: relative; }
.balmiso-hero-volume { position: absolute; top: 16px; inset-inline-end: 16px; z-index: 3; }

/* =============================================================================
   Mobile header: tap targets
   -----------------------------------------------------------------------------
   Search and Cart rendered as 24x24 hit areas in a 46px header. Both Apple and
   Google put the minimum at 44px, and below that a control does not feel
   broken so much as unresponsive: taps land next to it rather than on it.

   The icon keeps its size; only the area that accepts a tap grows, so nothing
   moves visually.
   ========================================================================== */
@media (max-width: 991.98px) {
  .gsp-header__mobile-icons .gsp-header-addons-item-link,
  .gsp-header__mobile-icons a,
  .gsp-header__mobile-icons button,
  .gsp-header-inner .gsp-header-addons-item-link {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  /* The logo is a link too, and 30px tall is under the same threshold. */
  .gsp-header-inner .gsp-site-logo a { display: inline-flex; align-items: center; min-height: 44px; }
}

/* =============================================================================
   Mobile menu: the Login button in the drawer footer
   -----------------------------------------------------------------------------
   Vendor mismatch. theme.css sizes the web component's shadow part for an
   icon-only avatar:

     shopify-account::part(signed-out-avatar) { width: 40px; height: 40px; }

   but header-addons__button-login.liquid slots a full button into it, icon
   plus the word "Login", which measures 123px. The slotted content centres on
   the 44px part, so it starts at 16 + (44 - 123) / 2 = -23px: the label hangs
   off the left edge of the drawer and is cut off by its overflow:hidden.

   Only the drawer is touched. The header uses the same component as a bare
   40px avatar, where the fixed size is correct.
   ========================================================================== */
#offcanvasMenuMobile shopify-account::part(signed-out-avatar) {
  width: auto;
  height: auto;
}

/* Waitlist popup (sections/balmiso-waitlist.liquid) */
/* The popup is opened with show(), not showModal(): a modal dialog sits in the
   browser's top layer, and hCaptcha's challenge window — which Shopify needs
   before it will accept a signup — cannot be drawn above it, so every signup
   hung on "Joining...". As a plain overlay the challenge appears normally.
   The dialog itself is now the full-screen scrim and the card is the form. */
.balmiso-waitlist {
  width: 100%; max-width: none; height: 100%;
  inset: 0; margin: 0; padding: 0; border: 0;
  background: transparent; color: #1f1b1d; text-align: center;
  overflow: visible;
}
.balmiso-waitlist[open] {
  position: fixed; z-index: 2147483000;
  display: flex; align-items: center; justify-content: center;
}
.balmiso-waitlist[open]::before {
  content: ''; position: fixed; inset: 0; background: rgba(20, 18, 20, .55);
}
.balmiso-waitlist::backdrop { background: rgba(20, 18, 20, .55); }
.balmiso-waitlist__inner {
  position: relative; z-index: 1; padding: 44px 32px 32px;
  width: min(460px, calc(100vw - 32px));
  border-radius: 12px; background: #F5F3F3;
  max-height: calc(100dvh - 40px); overflow-y: auto;
}
.balmiso-waitlist__close {
  position: absolute; top: 8px; right: 8px; width: 44px; height: 44px;
  border: 0; background: none; color: inherit; font-size: 28px; line-height: 1; cursor: pointer;
}
.balmiso-waitlist__eyebrow { margin: 0 0 10px; font-size: 12px; letter-spacing: .12em; text-transform: uppercase; opacity: .7; }
.balmiso-waitlist__title { margin: 0 0 12px; }
.balmiso-waitlist__text { margin: 0 0 22px; opacity: .85; }
.balmiso-waitlist__text p { margin: 0; }
.balmiso-waitlist__row { display: flex; flex-direction: column; gap: 10px; }
.balmiso-waitlist__input {
  width: 100%; min-height: var(--gsp-btn-height, 48px); padding: 0 18px;
  border: 1px solid rgba(31, 27, 29, .25); border-radius: var(--gsp-btn-radius, 999px);
  background: #fff; color: inherit; font: inherit; text-align: center;
}
.balmiso-waitlist__submit {
  width: 100%; min-height: var(--gsp-btn-height, 48px); padding: 0 20px; border: 0; cursor: pointer;
  border-radius: var(--gsp-btn-radius, 999px);
  background: var(--color-button, #1f1b1d); color: var(--color-button-text, #fff);
  font-family: var(--gsp-btn-font-family); font-size: var(--gsp-btn-font-size);
  letter-spacing: var(--gsp-btn-letter-spacing);
}
.balmiso-waitlist__submit:hover { opacity: .88; }
.balmiso-waitlist__error { margin: 12px 0 0; color: #9b2c2c; font-size: 14px; }
.balmiso-waitlist__error ul { margin: 0; padding: 0; list-style: none; }
.balmiso-waitlist__note { margin: 14px 0 0; font-size: 12px; opacity: .65; }

/* Mega menu names shown but not clickable (header menu block: links_as_labels) */
.balmiso-menu-label { pointer-events: none; cursor: default; }

/* Desktop dropdowns never linger.
   1. Sliding from one menu item to the next, the old panel used to fade out
      over 0.3s on top of the item now hovered, which read as stuck. It now
      hides at once when another item in the menu bar is hovered.
   2. The vendor keeps a panel open while anything inside has focus
      (:focus-within). A mouse click leaves focus there, so the panel could stay
      open after the pointer left, or come back open after the browser Back
      button. Mouse focus no longer holds it open; keyboard focus still does. */
.gsp-main-menu:hover .gsp-menu-has-children:not(:hover) > .gsp-submenu { transition-duration: 0s; }
.gsp-main-menu .gsp-menu-has-children:focus-within:not(:hover):not(.active-mega):not(:has(:focus-visible)) > .gsp-submenu {
  opacity: 0; visibility: hidden; pointer-events: none;
}

/* Waitlist thank-you: a picture, a drawn check, the date, somewhere to go next */
.balmiso-waitlist { overflow: hidden; }
.balmiso-waitlist__close:focus { outline: none; }
.balmiso-waitlist__close:focus-visible { outline: 2px solid currentColor; outline-offset: -4px; border-radius: 50%; }
.balmiso-waitlist.is-done .balmiso-waitlist__close {
  top: 12px; right: 12px; width: 36px; height: 36px; font-size: 22px;
  border-radius: 50%; background: rgba(245, 243, 243, .92); z-index: 2;
  padding: 0; display: flex; align-items: center; justify-content: center;
}
.balmiso-waitlist__media { margin: -44px -32px 0; aspect-ratio: 16 / 10; overflow: hidden; }
.balmiso-waitlist__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.balmiso-waitlist__done-body { position: relative; }
.balmiso-waitlist__seal {
  display: flex; width: 60px; height: 60px; margin: -30px auto 14px; padding: 6px;
  border-radius: 50%; background: #F5F3F3; color: var(--color-button, #1f1b1d);
}
.balmiso-waitlist__done > .balmiso-waitlist__done-body:first-child .balmiso-waitlist__seal { margin-top: 0; }
.balmiso-waitlist__seal svg {
  width: 100%; height: 100%; fill: none; stroke: currentColor;
  stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round;
}
.balmiso-waitlist__seal circle { stroke-dasharray: 145; stroke-dashoffset: 145; }
.balmiso-waitlist__seal path { stroke-dasharray: 34; stroke-dashoffset: 34; }
.balmiso-waitlist.is-done .balmiso-waitlist__seal circle { animation: balmiso-draw .6s ease forwards; }
.balmiso-waitlist.is-done .balmiso-waitlist__seal path { animation: balmiso-draw .35s .5s ease forwards; }
.balmiso-waitlist.is-done .balmiso-waitlist__done-body { animation: balmiso-rise .5s ease both; }
@keyframes balmiso-draw { to { stroke-dashoffset: 0; } }
@keyframes balmiso-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.balmiso-waitlist__title[tabindex="-1"]:focus { outline: none; }
.balmiso-waitlist__date {
  display: inline-block; margin: 18px 0 0; padding: 7px 14px; border-radius: 999px;
  background: rgba(31, 27, 29, .06); font-size: 12px; letter-spacing: .1em; text-transform: uppercase;
}
.balmiso-waitlist__actions { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-top: 24px; }
.balmiso-waitlist__actions .balmiso-waitlist__submit {
  display: flex; align-items: center; justify-content: center; text-decoration: none;
}
.balmiso-waitlist__actions .balmiso-waitlist__submit:hover { color: var(--color-button-text, #fff); }
.balmiso-waitlist__link {
  background: none; border: 0; padding: 4px; cursor: pointer; color: inherit; font: inherit;
  font-size: 14px; opacity: .65; text-decoration: underline; text-underline-offset: 3px;
}
.balmiso-waitlist__link:hover { opacity: 1; }
.balmiso-waitlist__submit[disabled] { opacity: .6; cursor: progress; }
@media (prefers-reduced-motion: reduce) {
  .balmiso-waitlist__seal circle, .balmiso-waitlist__seal path { stroke-dashoffset: 0; animation: none !important; }
  .balmiso-waitlist.is-done .balmiso-waitlist__done-body { animation: none; }
}

/* FAQ page, Still need help? card: the button matches the sand-brown question
   boxes (scheme-8, #cabaa7) instead of the theme's dark primary. Scoped to this
   one block so every other primary button keeps its colour. */
.gsp-block__still_help .btn-primary,
.gsp-block__still_help .btn-primary .path-front,
.gsp-block__still_help .btn-primary .path-back {
  background-color: #f4e9e9 !important;
  border-color: #f4e9e9 !important;
  color: #312f32 !important;
}
.gsp-block__still_help .btn-primary:hover,
.gsp-block__still_help .btn-primary:hover .path-front,
.gsp-block__still_help .btn-primary:hover .path-back {
  background-color: #ecdbdb !important;
  border-color: #ecdbdb !important;
  color: #312f32 !important;
}

/* Ritual coming soon (snippets/balmiso-ritual-coming-soon.liquid) */
.balmiso-soon {
  /* sits inside the product grid row, so it must span the whole row */
  grid-column: 1 / -1; flex: 0 0 100%; width: 100%; max-width: 100%;
  padding: 0 !important;
  display: grid; grid-template-columns: minmax(0, 1fr); margin: 8px 0 32px;
  border-radius: 16px; overflow: hidden; background: #EFE8E0; color: #312f32; text-align: left;
}
.gsp-listing-inner > .balmiso-soon { width: auto; max-width: none; flex: 1 1 100%; margin-inline: calc(var(--bs-gutter-x, 1.5rem) * .5); }
/* The grid row carries a negative top margin meant for product tiles; with the
   card instead it pulled the card up over the section above. */
.gsp-listing-inner:has(> .balmiso-soon) { margin-top: 0 !important; }
.balmiso-soon { margin-top: 28px !important; margin-bottom: 40px !important; padding: 14px !important; column-gap: 0; }
.balmiso-soon__media { border-radius: 12px; }
@media (max-width: 767.98px) {
  .balmiso-soon { margin-top: 16px !important; margin-bottom: 24px !important; padding: 10px !important; }
  .balmiso-soon__media { border-radius: 10px; }
}

/* Ritual pages, next-ritual banner: 16:9 is too short on a phone for its words,
   which spilled outside the picture. Taller on phones only; desktop keeps 16:9. */
@media (max-width: 767.98px) {
  [id$="__cross_ritual"] .gsp-banner .gsp-image-inner { --aspect-ratio: 4/5 !important; }
}
@media (min-width: 990px) { .balmiso-soon { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.balmiso-soon__media { position: relative; min-height: 280px; overflow: hidden; background: #cabaa7; }
@media (min-width: 990px) { .balmiso-soon__media { min-height: 520px; } }
.balmiso-soon__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; animation: balmiso-soon-zoom 16s ease-in-out infinite alternate; }
.balmiso-soon__stamp {
  position: absolute; top: 22px; left: 22px; width: 96px; height: 96px; border-radius: 50%;
  display: grid; place-items: center; text-align: center; background: #F5F3F3; color: #312f32;
  font-family: var(--gsp-heading-font-family); font-weight: var(--gsp-heading-font-weight);
  font-size: 13px; line-height: 1.15; letter-spacing: .08em; text-transform: uppercase;
  transform: rotate(-12deg); box-shadow: 0 10px 28px rgba(20, 18, 20, .22);
  animation: balmiso-soon-bob 4s ease-in-out infinite;
}
.balmiso-soon__stamp::after { content: ""; position: absolute; inset: 6px; border: 1px dashed rgba(91, 82, 71, .55); border-radius: 50%; }
.balmiso-soon__body { position: relative; display: flex; flex-direction: column; justify-content: center; padding: 56px 52px; }
.balmiso-soon__badge {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 10px; margin: 0;
  padding: 8px 16px; border-radius: 999px; background: #5b5247; color: #F5F3F3;
  font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
}
.balmiso-soon__pulse { position: relative; width: 8px; height: 8px; border-radius: 50%; background: #cabaa7; }
.balmiso-soon__pulse::after { content: ""; position: absolute; inset: 0; border-radius: 50%; background: #cabaa7; animation: balmiso-soon-ping 1.6s ease-out infinite; }
.balmiso-soon__title {
  margin: 20px 0 14px; font-family: var(--gsp-heading-font-family); font-weight: var(--gsp-heading-font-weight);
  font-size: var(--gsp-h2-font-size); line-height: var(--gsp-h2-line-height); color: #312f32;
}
.balmiso-soon__text { margin: 0; max-width: 46ch; opacity: .85; font-size: calc(var(--gsp-body-font-size) * 1.06); line-height: var(--gsp-body-line-height); }
.balmiso-soon__label { margin: 26px 0 10px; font-size: 12px; letter-spacing: .14em; text-transform: uppercase; opacity: .6; }
.balmiso-soon__chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; }
.balmiso-soon__chips li {
  padding: 8px 14px; border-radius: 999px; background: #F5F3F3; border: 1px solid rgba(91, 82, 71, .25);
  font-size: 14px; animation: balmiso-soon-rise .6s ease both; animation-delay: calc(var(--i) * 90ms + 150ms);
}
.balmiso-soon__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 22px; margin-top: 30px; }
.balmiso-soon__link { color: inherit; opacity: .75; text-decoration: underline; text-underline-offset: 3px; font-size: 14px; }
.balmiso-soon__link:hover { opacity: 1; color: inherit; }
.balmiso-soon__spark { position: absolute; fill: #cabaa7; animation: balmiso-soon-twinkle 2.8s ease-in-out infinite; }
.balmiso-soon__spark--a { top: 34px; right: 46px; width: 22px; height: 22px; }
.balmiso-soon__spark--b { top: 70px; right: 86px; width: 12px; height: 12px; animation-delay: 1.2s; }
@keyframes balmiso-soon-zoom { from { transform: scale(1); } to { transform: scale(1.08); } }
@keyframes balmiso-soon-bob { 0%, 100% { transform: rotate(-12deg) translateY(0); } 50% { transform: rotate(-8deg) translateY(-6px); } }
@keyframes balmiso-soon-ping { 0% { transform: scale(1); opacity: .8; } 100% { transform: scale(3); opacity: 0; } }
@keyframes balmiso-soon-rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes balmiso-soon-twinkle { 0%, 100% { opacity: .35; transform: scale(.8) rotate(0); } 50% { opacity: 1; transform: scale(1.1) rotate(20deg); } }
@media (max-width: 767.98px) {
  .balmiso-soon__body { padding: 32px 22px 34px; }
  .balmiso-soon__stamp { width: 80px; height: 80px; font-size: 11px; top: 16px; left: 16px; }
  .balmiso-soon__spark--a { top: 22px; right: 22px; }
  .balmiso-soon__spark--b { top: 50px; right: 52px; }
}
@media (prefers-reduced-motion: reduce) {
  .balmiso-soon__img, .balmiso-soon__stamp, .balmiso-soon__pulse::after, .balmiso-soon__chips li, .balmiso-soon__spark { animation: none; }
}

/* Buttons across the House read as an outline and fill on hover.

   A page of solid brown blocks competed with the photography. Every button
   built on the theme's default style now carries its colour as an outline and
   fills only when the pointer arrives. The theme's own button variables are
   reassigned rather than the colours hard-coded, so a button on a dark section
   still uses that section's pair, and a change of brand colour in theme
   settings carries through. The sliding label animation is untouched.

   Left solid on purpose: add to cart and checkout, where a filled button is
   what people look for, the waitlist popup's own submit, the FAQ Customer Care
   button in its pink, and the hidden skip link. */
.btn.btn-default:not(.gsp-skip-to-content-link):not([name="checkout"]):not([name="add"]):not(.gsp-cart-checkout-btn):not(.product-form__submit),
.btn.btn-primary:not([name="checkout"]):not([name="add"]):not(.gsp-cart-checkout-btn):not(.product-form__submit) {
  --bs-btn-bg: transparent;
  --bs-btn-color: var(--color-button);
  --bs-btn-border-color: var(--color-button);
  --bs-btn-hover-bg: var(--color-button);
  --bs-btn-hover-color: var(--color-button-text);
  --bs-btn-hover-border-color: var(--color-button);
  --bs-btn-active-bg: var(--color-button);
  --bs-btn-active-color: var(--color-button-text);
  --bs-btn-active-border-color: var(--color-button);
  transition: background-color .3s ease, color .3s ease, border-color .3s ease;
}

/* One size for every section heading. The theme let each section carry its own
   heading size, so the page stepped between 34px and 40px from one block to the
   next. Section headings and page titles now all sit at the larger of the two,
   the size "Ritual of Selfcare" was already using, 40px on desktop and 34px on
   a phone. Left alone on purpose: the statement over the house photograph, the
   sticky product name in the PDP rail, and the cart and waitlist dialogs, which
   are not section headings. */
h1.gsp-heading-title,
h2.gsp-heading-title,
h2.gsp-simple-heading.gsp-heading,
h1.gsp-page-title,
h2.balmiso-ing__title {
  font-size: 40px !important;
  line-height: 1.2 !important;
}
@media (max-width: 767.98px) {
  h1.gsp-heading-title,
  h2.gsp-heading-title,
  h2.gsp-simple-heading.gsp-heading,
  h1.gsp-page-title,
  h2.balmiso-ing__title {
    font-size: 22px !important;
  }
}

/* Phones: every h1 comes down by a fifth. Section headings and page titles
   already sit at 22px above; this catches the ones that carry their own size,
   so no first heading on a phone is larger than it needs to be. The quiz
   question and the sticky product name keep their own scale, each cut by the
   same fifth. */
@media (max-width: 767.98px) {
  h1 {
    font-size: 22px !important;
    line-height: 1.2 !important;
  }
  h1.bq__title { font-size: 38px !important; }
  h1.balmiso-rails__name { font-size: 20px !important; }
}

/* The two buttons written for BALMiso rather than by the theme follow the same
   rule: the product page's waitlist call, and the popup's own submit. */
.balmiso-rails__cta {
  background: transparent !important;
  color: var(--color-button) !important;
  border: 2px solid var(--color-button);
  transition: background-color .3s ease, color .3s ease;
}
.balmiso-rails__cta:hover,
.balmiso-rails__cta:focus-visible {
  background: var(--color-button) !important;
  color: var(--color-button-text) !important;
  opacity: 1;
}
.balmiso-waitlist__submit {
  background: transparent;
  color: var(--color-button, #1f1b1d);
  border: 2px solid var(--color-button, #1f1b1d);
  transition: background-color .3s ease, color .3s ease;
}
.balmiso-waitlist__submit:hover,
.balmiso-waitlist__submit:focus-visible,
.balmiso-waitlist__actions .balmiso-waitlist__submit:hover {
  background: var(--color-button, #1f1b1d);
  color: var(--color-button-text, #fff);
  opacity: 1;
}

/* Phones: the rest of the type follows the headings down.

   The headings lost a fifth, so text that sat below them would have read as
   nearly the same size. Sub-headings, block titles and any text carrying its
   own size come down with them; body copy stops at 15px and nothing is allowed
   below 13px, because text a phone cannot read comfortably is worse than text
   that is slightly large. */
@media (max-width: 767.98px) {
  :root {
    --gsp-body-font-size: 15px;
  }
  .custom-typography {
    font-size: max(14px, calc(var(--typography-fs-mobile, 16px) * 0.85)) !important;
  }
}

/* Phones: nothing in the body of a page should shout louder than the heading
   above it. These carry their size from a preset rather than an editor field,
   so they are named here: the titles on the expandable cards, the statement
   over the house photograph, and the countdown digits. */
@media (max-width: 767.98px) {
  a.d-block.text-inherit { font-size: 20px !important; line-height: 1.25 !important; }
  h2.balmiso-house__title { font-size: 27px !important; }
  .day.number, .hour.number, .minute.number, .second.number { font-size: 28px !important; }
}

/* Ritual cards: the name over the photograph, a tenth smaller. */
[id$="__collection_list_WjHqbG"] .gsp-reversed-link__text {
  font-size: 17px !important;
}

/* Scrolling cards on phones: the product name a tenth smaller, and the line
   held at two lines' height so a two-line name and a one-line name leave the
   card the same size. */
@media (max-width: 767.98px) {
  .scrolling-card-layered a.text-inherit {
    font-size: 19px !important;
    line-height: 1.3 !important;
  }
  .scrolling-card-layered .card-body .gsp-block-card__heading,
  .scrolling-card-layered a.text-inherit {
    display: block;
    min-height: calc(19px * 1.3 * 2);
  }
}

/* Phones: every section header reads at the same three sizes.

   Sections carried their own eyebrow and paragraph sizes, so one section's
   small heading was 11px and the next was 17px. The inner circle section is the
   reference: eyebrow 11.25px, heading 22px (set above), text 16px. */
@media (max-width: 767.98px) {
  .gsp-heading-sub-title {
    font-size: 11.25px !important;
  }
  .gsp-heading-desc,
  .gsp-heading-desc p,
  .gsp-simple-text,
  .gsp-simple-text p {
    font-size: 16px !important;
  }
}

/* Phone menu: it must end where the screen ends.

   The drawer was sized at 100vh. On iPhone Safari that is the height of the
   screen without the address bar, so the last of the ritual cards sat under
   the browser's own toolbar and the panel would not scroll past it. Dynamic
   viewport height measures what is actually visible, the panel below the
   header takes the remaining space and scrolls on its own, and the last card
   clears the home indicator. */
@media (max-width: 991.98px) {
  .gsp-menu-offcanvas {
    height: 100dvh !important;
    max-height: 100dvh !important;
    display: flex;
    flex-direction: column;
  }
  .gsp-menu-offcanvas .offcanvas-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto !important;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  .gsp-menu-offcanvas .gsp-menu-mobile-inner,
  .gsp-menu-offcanvas .gsp-submenu-inner {
    max-height: none !important;
    overflow-y: auto !important;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding-bottom: calc(32px + env(safe-area-inset-bottom, 0px)) !important;
  }
}

/* Ritual pages: the hero copy must never touch the edge of the screen, and the
   button over a photograph must stay legible.

   The hero text block ran the full width of a phone, so the first and last
   words sat against the bezel. The next-ritual button took the brown outline
   from the site-wide rule, which disappears against a pale photograph, so over
   an image it carries a white outline and fills white with dark text. */
[id$="__hero"] .gsp-banner-content,
[id$="__hero"] .gsp-banner__content,
[id$="__hero"] .gsp-heading-inner,
[id$="__cross_ritual"] .gsp-heading-inner {
  padding-inline: 24px;
}
/* Any button that sits on top of a photograph or a film: white outline, a
   whisper of dark behind it so the words hold on a pale image, and a solid
   white fill on hover. The brown outline used everywhere else disappears
   against a bright picture. */
.gsp-banner-content a.btn,
.card-img-overlay a.btn,
[id$="__hero"] a.btn,
.gsp-collection-card__content a.btn {
  --bs-btn-color: #ffffff !important;
  --bs-btn-border-color: #ffffff !important;
  --bs-btn-bg: rgba(20, 18, 18, .22) !important;
  --bs-btn-hover-bg: #ffffff !important;
  --bs-btn-hover-color: #312f32 !important;
  --bs-btn-hover-border-color: #ffffff !important;
  --bs-btn-active-bg: #ffffff !important;
  --bs-btn-active-color: #312f32 !important;
  --bs-btn-active-border-color: #ffffff !important;
  backdrop-filter: blur(3px);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .18);
}
.gsp-banner-content a.btn:hover,
.card-img-overlay a.btn:hover,
[id$="__hero"] a.btn:hover,
.gsp-collection-card__content a.btn:hover {
  text-shadow: none;
}

/* Phones: the payment marks sit on one line rather than spilling onto a second
   row of two. Eight marks at 36px with a 6px gap fit inside the footer column. */
@media (max-width: 767.98px) {
  .gsp-payment-icons {
    flex-wrap: nowrap !important;
    gap: 6px !important;
  }
  .gsp-payment-icons > * {
    flex: 0 0 auto;
  }
  .gsp-payment-icons img,
  .gsp-payment-icons svg {
    width: 36px !important;
    height: auto !important;
  }
}

/* Complete the ritual, on phones: one row you swipe rather than a stack of
   pairs. The theme's own slider leaves its slides hidden until its script
   initialises, which it does not do here, so the grid itself scrolls. */
@media (max-width: 767.98px) {
  [id$="__product-recommendations"] .gsp-listing-inner {
    display: flex !important;
    flex-wrap: nowrap !important;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    padding-bottom: 8px;
  }
  [id$="__product-recommendations"] .gsp-listing-inner::-webkit-scrollbar { display: none; }
  [id$="__product-recommendations"] .gsp-listing-inner > * {
    flex: 0 0 62%;
    max-width: 62%;
    scroll-snap-align: start;
  }
}

/* "As seen on" in the marquee: the Falcons of Majlis mark is pale gold with no
   dark pixels at all, so on the cream strip it read as a smudge. The mark is
   left as it is and only deepened and enlarged: same artwork, same marquee,
   legible at a glance. */
.gsp-marquee-type-strip_seen_on .balmiso-pair__logo {
  height: 56px !important;
  width: auto !important;
  filter: brightness(.62) contrast(1.25) saturate(1.2);
}
@media (max-width: 767.98px) {
  .gsp-marquee-type-strip_seen_on .balmiso-pair__logo { height: 44px !important; }
}

/* Reviews: the role sits on its own line under the name. The theme lays the
   two out as one inline row, so the row becomes a column. */
.d-inline-flex:has(> .gsp-testimonial-name) {
  display: flex !important;
  flex-direction: column;
  align-items: flex-start !important;
}
.gsp-testimonial-position {
  margin-top: 2px;
  margin-inline-start: 0 !important;
}

/* Phone menu, second level (The Rituals, The Method, ...).

   Each second-level panel was 100vh tall and hung from under the menu header,
   so its bottom always sat past the end of the drawer. How much of it was lost
   depended on the phone's browser toolbar, which is why the rituals scrolled
   to the end on some phones and not on others. The panel is now pinned to
   both the header and the bottom of the drawer, and only its list scrolls.

   Login belongs to the first level only. It sat on top of every second-level
   panel and covered the last card, so it goes while one is open. The panel
   also gets the drawer's own background, so on a browser without :has() it
   still covers the footer instead of showing it through. */
@media (max-width: 991.98px) {
  #offcanvasMenuMobile .gsp-main-menu-mobile .gsp-submenu {
    top: var(--gsp-height-offcanvas-header, 0);
    bottom: 0;
    height: auto !important;
    display: flex;
    flex-direction: column;
    background-color: rgb(var(--color-background-rgb));
    z-index: 20;
  }
  #offcanvasMenuMobile .gsp-main-menu-mobile .gsp-submenu > .gsp-submenu-header {
    flex: 0 0 auto;
  }
  #offcanvasMenuMobile .gsp-main-menu-mobile .gsp-submenu > .gsp-menu-mobile-inner {
    flex: 1 1 auto;
    min-height: 0;
    height: auto !important;
    max-height: none !important;
    overflow-y: auto !important;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding-bottom: calc(32px + env(safe-area-inset-bottom, 0px)) !important;
  }
  #offcanvasMenuMobile:has(.gsp-main-menu-mobile.active) .offcanvas-footer {
    display: none !important;
  }
}

/* Welcome popup on phones: a centred card, not a sheet from the bottom.
   The theme docks every floating modal to the bottom edge below 576px, full
   width. This one is held to 90% of the screen, centred both ways, with every
   corner rounded, so it never touches the sides. Other floating modals (quick
   view and the like) keep the sheet. */
@media (max-width: 575.98px) {
  .gsp-popup-promotion.modal--floating .modal-dialog {
    align-items: center;
    width: 90%;
    max-width: 90%;
    margin: 0 auto !important;
  }
  .gsp-popup-promotion.modal--floating .modal-content {
    border-radius: 20px;
    max-height: calc(100dvh - 140px);
  }
}

/* PDP gallery: the whole photograph, never cropped.
   Frames used to be a fixed 84vh box with object-fit: cover, so a 4:5 photo
   in a wider box lost its top and bottom. Each image now keeps its own shape:
   as wide as the column allows, no taller than the frame height, centred. */
.balmiso-rails__media {
  width: auto; max-width: 100%;
  height: auto; max-height: var(--balmiso-rails-frame, 84vh);
  margin-inline: auto;
  object-fit: contain;
}
.balmiso-rails__media--empty { width: 100%; height: var(--balmiso-rails-frame, 84vh); }
@media (max-width: 999px) {
  .balmiso-rails__frame { display: flex; align-items: center; }
  .balmiso-rails__media { width: 100%; height: auto; max-height: none; }
}

/* Product cards before launch: on phones the theme shrinks the card button to
   a 40px round bag icon. "Join the waitlist" has to be read to be tapped, so
   it becomes a labelled pill across the bottom of the photo. */
@media (max-width: 767.98px) {
  .gsp-product-card .gsp-product-card-image .gsp-product-actions-layout-01 .gsp-product-actions-add-to-cart.is-balmiso-waitlist {
    width: auto;
    inset-inline-start: 10px;
    inset-inline-end: 10px;
    bottom: 10px;
  }
  .gsp-product-card .gsp-product-card-image .gsp-product-actions-add-to-cart.is-balmiso-waitlist .gsp-product-actions-button {
    --gsp-btn-height: 38px;
    --gsp-btn-font-size: 13px;
    width: 100%;
    border-radius: 999px;
    padding: 0 10px;
    white-space: nowrap;
  }
  .gsp-product-card .gsp-product-card-image .gsp-product-actions-add-to-cart.is-balmiso-waitlist .btn-content .text-atc {
    display: inline;
  }
}

/* Phone gallery: a swipe always lands on a whole photo. Proximity snapping let
   a short swipe stop between two frames; mandatory snaps every swipe to the
   next or previous photo, and snap-stop keeps a fast flick to one photo at a
   time. The dots scroll to a frame's own edge, a snap point, so they still
   glide. */
@media (max-width: 999px) {
  .balmiso-rails__stack { scroll-snap-type: x mandatory; }
  .balmiso-rails__frame { scroll-snap-align: center; scroll-snap-stop: always; }
}

/* Ritual pages, FAQ: the questions sit one under another, divided by their
   rules only. The theme's default 56px between rows read as empty bands. The
   heading keeps its breathing room above the first question. */
[id$="__education"] .image-with-accordion__items > .gsp-block__is-first { margin-bottom: 32px; }
@media (max-width: 767.98px) {
  [id$="__education"] .image-with-accordion__items > .gsp-block__is-first { margin-bottom: 20px; }
}

/* Product page, "One scent for each moment": six cut-outs on transparency,
   shown on white. Type comes from the same presets as the Formula in Focus
   cards (set on the section), so nothing is sized here.
   Image: every file is 4:5 with the object in the middle 58% of its height.
   The padding leaves a 4:5 box at the top of the card for object-fit to fit
   the file into, and the lift takes out the file's empty top margin, so each
   object shows whole, the same size, between the number and the name. The
   white fade only softens its lowest edge.
   Desktop shows one open card at a time, so the words keep the theme's place
   at the foot of the card and the cut-out fills the space above them.
   Phones show the cards side by side, so there the words start on one line
   and the arrow sits at the foot, keeping every card level. */
[id$="__expandable_gallery_DziGkN"] .gsp-image-card {
  border: 1px solid rgba(49, 47, 50, .12) !important;
}
[id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-image { background: #ffffff; }
[id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-img-overlay::before { background: none !important; }
[id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-img-overlay::after {
  top: 0 !important; height: auto !important;
  background: linear-gradient(to bottom, rgba(255,255,255,0) 50%, #ffffff 58%) !important;
  opacity: 1 !important;
}
[id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-image img {
  box-sizing: border-box;
  padding: 0 0 90px;
  object-fit: contain;
  object-position: center top;
  transform: translateY(-66px);
}
[id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-image-card-heading,
[id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-image-card-desc { color: #312f32; }
[id$="__expandable_gallery_DziGkN"] .gsp-image-card a.btn:not(.gallery-step) {
  background: #312f32 !important;
  color: #ffffff !important;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
/* Phones (cards 285 x 475, set on the section so the longest notes fit): the theme draws the image at twice the card and
   pins the arrow button to the corner. The image is held to the card and the
   button follows the notes, at the foot of the card. */
@media (max-width: 767.98px) {
  [id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-image img {
    width: 100% !important; height: 100% !important; max-width: none;
    padding: 0 0 227px !important;
    transform: translateY(-36px);
  }
  [id$="__expandable_gallery_DziGkN"] .gsp-image-card .card-body-inner {
    justify-content: flex-start !important;
    padding-top: 150px;
  }
  [id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-image-card-button { margin-top: auto !important; }
  [id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-img-overlay::after {
    background: linear-gradient(to bottom, rgba(255,255,255,0) 35%, #ffffff 42%) !important;
  }
  [id$="__expandable_gallery_DziGkN"] .gsp-image-card .gsp-image-card-button { position: static !important; }
}

/* Quiz opening screen: its words sit white on a dark moving image, and the
   site's outline button (taupe line, taupe words) disappeared into it. Here
   the button follows the words: white line and white label, filling white
   with dark words on hover and focus. */
.bq__entry--img .btn-primary {
  color: #ffffff !important;
  border-color: #ffffff !important;
  background: transparent !important;
}
.bq__entry--img .btn-primary:hover,
.bq__entry--img .btn-primary:focus-visible {
  color: #312f32 !important;
  background: #ffffff !important;
  border-color: #ffffff !important;
}
