/* STYLES FOR THE SIROCCO TEST SOAP SHOP */
/* Used by index.html (the shelf), product.html (one soap) and 404.html. */
/* The order of this file, top to bottom: */
/* 1. Fonts loaded from the fonts folder. */
/* 2. Design tokens: colors, sizes and shared effects stored as CSS variables on :root. */
/* 3. Base reset: sensible defaults for plain HTML elements. */
/* 4. Helpers: page width wrapper, screen reader text, skip link, status messages. */
/* 5. Shared pieces: the notice strip, ribbon, stamps, headings, buttons and links. */
/* 6. Page sections in the order they appear: header, hero shelf, facts, product grid, how it's made, call to action. */
/* 7. Product page, quantity steppers, the cart drawer, the 404 page and the footer. */
/* 8. Reduced motion settings at the very end so they win over everything above. */
/* Most rules are written on one line. Media queries sit right after the rules they change. */

/* FONTS */
/* Both fonts are served from this site, so no request goes to Google. */
/* font-weight 400 800 tells the browser Figtree is a variable font that covers every weight in that range from one file. */
/* font-display swap shows text in a fallback font straight away, then swaps in the real font once it downloads. */
@font-face{font-family:'Figtree';src:url('fonts/figtree.woff2') format('woff2');font-weight:400 800;font-style:normal;font-display:swap}
@font-face{font-family:'Young Serif';src:url('fonts/young-serif.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap}

/* DESIGN TOKENS */
/* Every color and shared value lives here as a variable. Change one here and it updates everywhere it is used. */
/* The names describe the color (oat, moss, clay) so they read like a soap maker's palette. */
/* The dk and lt versions are darker and lighter shades of the same color, used for hover states and soft backgrounds. */
:root{
  --oat:#f3eee2;
  --paper:#fffdf7;
  --ink:#25302a;
  --body:#48544c;
  --muted:#6d786f;
  --moss:#3d5a47;
  --moss-dk:#2c4334;
  --sage:#a9bfa6;
  --sage-lt:#dfe8da;
  --clay:#c26c4a;
  --clay-dk:#9d5032;
  --butter:#ead7a1;
  --rose:#dcb0a4;
  --wood:#b08d63;
  --line:#d9d1bf;
  /* --wrap is the widest the page content gets on a large screen. */
  --wrap:74rem;
  /* --gut is the side padding. clamp(min, preferred, max) means: 4% of the screen width, but never below 1rem or above 2rem. */
  --gut:clamp(1rem,4vw,2rem);
  /* Font stacks: if the custom font fails to load, the browser falls back to the next name in the list. */
  --serif:'Young Serif',Georgia,serif;
  --sans:'Figtree',system-ui,sans-serif;
  /* --bar is the soap bar look used on cards. It stacks three shadows, separated by commas: */
  /* 1. inset 0 2px white: a thin bright line along the top inside edge, like light catching the bar. */
  /* 2. inset 0 -6px dark at 6%: a faint shaded band along the bottom inside edge, so the bar looks thick. */
  /* 3. 0 10px 26px: a soft shadow underneath that lifts the card off the page. */
  --bar:inset 0 2px 0 rgba(255,255,255,.7),inset 0 -6px 0 rgba(37,48,42,.06),0 10px 26px rgba(37,48,42,.09);
  /* --bubbles is a small SVG drawing of circles stored right inside the CSS as a data URL, so no extra image file is needed. */
  /* Special characters are encoded: %3C is <, %3E is >, and %23 is # (so %23a9bfa6 is the sage color). */
  /* It is used as a repeating background by the .bubbles divider further down. */
  --bubbles:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='132' height='30'%3E%3Cg fill='none' stroke='%23a9bfa6' stroke-width='1.6'%3E%3Ccircle cx='11' cy='15' r='5'/%3E%3Ccircle cx='31' cy='9' r='3'/%3E%3Ccircle cx='50' cy='18' r='7.5'/%3E%3Ccircle cx='75' cy='10' r='4'/%3E%3Ccircle cx='96' cy='17' r='6'/%3E%3Ccircle cx='117' cy='8' r='2.6'/%3E%3C/g%3E%3C/svg%3E");
}

/* BASE RESET */
/* border-box makes width and height include padding and border, so a 100px box stays 100px after you add padding. */
*,*::before,*::after{box-sizing:border-box}
/* scroll-behavior smooth glides to #anchors instead of jumping. */
/* scroll-padding-top leaves 5.5rem of space at the top when jumping to an anchor, so the sticky header does not cover the heading. */
/* -webkit-text-size-adjust stops iPhones from enlarging text on their own when the phone is turned sideways. */
html{scroll-behavior:smooth; scroll-padding-top:5.5rem; -webkit-text-size-adjust:100%}
/* overflow-x hidden stops any wide decoration, like the soap shelf, from causing a sideways scrollbar on phones. */
body{margin:0; background:var(--oat); color:var(--body); font-family:var(--sans); font-size:1.0625rem; line-height:1.6; overflow-x:hidden}
/* Images become block elements so they lose the small gap below them, and they shrink to fit their container. */
img,svg{display:block; max-width:100%; height:auto}
a{color:var(--moss)}
a:hover{color:var(--moss-dk)}
/* text-wrap balance evens out the line lengths in headings, so you do not get one lonely word on the last line. */
h1,h2,h3{font-family:var(--serif); font-weight:400; color:var(--ink); line-height:1.1; margin:0; text-wrap:balance}
p{margin:0 0 1rem}
/* Lists lose their bullets and indent because every list here is used for layout, not as a bulleted list. */
ul,ol,dl,dd{margin:0; padding:0; list-style:none}
figure,fieldset{margin:0; padding:0; border:0}
/* Form controls do not inherit the page font by default. This makes them match the rest of the text. */
button,select,textarea{font:inherit; color:inherit}
/* A clear clay outline shows which element has keyboard focus. :focus-visible only shows it for keyboard users, not mouse clicks. */
:focus-visible{outline:3px solid var(--clay); outline-offset:3px; border-radius:6px}

/* HELPERS */
/* The .wrap container centres content and caps its width. min() picks whichever is smaller, so it is full width on phones. */
.wrap{width:min(var(--wrap),100%); margin-inline:auto; padding-inline:var(--gut)}
/* The .vh class means visually hidden. The text is shrunk to a 1px box and clipped so sighted users do not see it, */
/* but screen readers still read it aloud. It is used for extra words like (opens in a new tab). */
/* display none would hide it from screen readers too, which is why this pattern is used instead. */
.vh{position:absolute!important; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap}
/* The skip link lets keyboard users jump past the header to the main content. It sits above the top of the screen until focused. */
.skip{position:absolute; left:1rem; top:-4rem; z-index:99; background:var(--ink); color:#fff; padding:.7rem 1rem; border-radius:8px; font-weight:700}
/* When a keyboard user tabs to the skip link, it drops into view. */
.skip:focus{top:1rem}
/* Status messages are filled in by shop.js, for example Loading the shelf. */
.status{color:var(--muted)}
/* :empty matches an element with nothing inside it. Once shop.js clears the message, the empty paragraph is hidden so it takes up no space. */
.status:empty{display:none}
/* The hidden attribute normally hides an element, but any class that sets display flex or grid would overrule it and show it again. */
/* !important makes sure hidden always wins. shop.js relies on this when it hides and shows parts of the page. */
[hidden]{display:none!important}

/* NOTICE AND RIBBON */
/* The dark notice strip at the very top that explains this is a test shop. */
.notice{background:var(--ink); color:#dfe8da; font-size:.82rem; text-align:center; padding:.45rem var(--gut)}
.notice a{color:#fff}
/* The pale green ribbon under the notice. flex-wrap lets its short phrases wrap onto a second line on narrow screens. */
/* The two numbers in gap are row gap then column gap. */
.ribbon{margin:0; background:var(--sage-lt); color:var(--moss-dk); display:flex; flex-wrap:wrap; justify-content:center; gap:.2rem .9rem; padding:.4rem var(--gut); font-size:.78rem; font-weight:700; letter-spacing:.14em; text-transform:uppercase; text-align:center}
/* The b elements in the ribbon are the small clay separators between phrases. */
.ribbon b{color:var(--clay); font-weight:400}
/* On phones only the first phrase is kept. span:nth-of-type(n+2) matches the second span and every span after it. */
@media (max-width:40rem){.ribbon span:nth-of-type(n+2),.ribbon b{display:none}}

/* SHARED TEXT PIECES */
/* The .stamp is the small outlined pill label above headings. currentColor makes the border the same color as the text. */
/* border-radius 999px is a common trick for fully rounded pill ends at any size. */
.stamp{display:inline-block; margin:0 0 1rem; padding:.28rem 1rem .24rem; border:1.5px solid currentColor; border-radius:999px; color:var(--clay-dk); font-size:.72rem; font-weight:800; letter-spacing:.2em; text-transform:uppercase; line-height:1.4}
/* The .pop class is the italic clay word used for emphasis inside a heading. */
.pop{color:var(--clay-dk); font-style:italic}
/* Section headings grow with the screen: 4% of the screen width, kept between 1.9rem and 2.9rem. */
h2{font-size:clamp(1.9rem,4vw,2.9rem)}
/* The .lede is the larger intro paragraph under a main heading. max-width keeps lines short enough to read comfortably. */
.lede{font-size:clamp(1.08rem,1.5vw,1.22rem); max-width:38rem}

/* BUTTONS */
/* The base .btn is a rounded pill. min-height 46px keeps it big enough to tap easily on a phone. */
/* The two inset shadows add a darker band along the bottom and a light line along the top, so the button looks pressable. */
/* The transition makes the hover color and lift change smoothly over 0.15 seconds. */
.btn{display:inline-flex; align-items:center; justify-content:center; gap:.5rem; min-height:46px; padding:.6rem 1.4rem; border:0; border-radius:999px; background:var(--moss); color:#fff; font-weight:800; font-size:1rem; text-decoration:none; white-space:nowrap; cursor:pointer; box-shadow:inset 0 -4px 0 rgba(0,0,0,.18),inset 0 1px 0 rgba(255,255,255,.2); transition:background .15s, transform .15s}
/* On hover the button rises 1px. On click (:active) it sinks 1px, which feels like pressing it. */
.btn:hover{background:var(--moss-dk); color:#fff; transform:translateY(-1px)}
.btn:active{transform:translateY(1px)}
/* Disabled buttons turn grey, lose the shadows and do not move, so they clearly cannot be used. */
.btn:disabled{background:#cfd3cb; color:#5f6962; cursor:not-allowed; transform:none; box-shadow:none}
/* Modifier classes use the double dash naming style: .btn--big is a variation of .btn, added alongside it. */
.btn--big{min-height:54px; padding:.75rem 1.8rem; font-size:1.06rem}
.btn--sm{min-height:40px; padding:.4rem 1rem; font-size:.92rem}
.btn--wide{width:100%}
/* The ghost button is see through with an outline. The outline is an inset shadow, so it does not change the button size like a border would. */
.btn--ghost{background:transparent; color:var(--ink); box-shadow:inset 0 0 0 2px var(--ink)}
.btn--ghost:hover{background:var(--ink); color:#fff}
.btn--butter{background:var(--butter); color:var(--ink)}
.btn--butter:hover{background:#f4e6bd; color:var(--ink)}
.btn--cart{background:var(--ink)}
.btn--cart:hover{background:#000}
/* The .count bubble shows how many items are in the cart. inline-grid with place-items center centres the number in both directions. */
.count{display:inline-grid; place-items:center; min-width:1.55rem; height:1.55rem; padding:0 .4rem; border-radius:999px; background:var(--butter); color:var(--ink); font-size:.82rem}
.actions{display:flex; flex-wrap:wrap; gap:.8rem}
/* The .link-arrow is a text link with a clay underline and an arrow added after it. */
.link-arrow{font-weight:800; text-decoration:none; border-bottom:2px solid var(--clay); padding-bottom:.1rem; white-space:nowrap}
/* \2192 is the code for the right arrow character. CSS needs the code form inside content. */
.link-arrow::after{content:" \2192"}

/* HEADER */
/* position sticky keeps the header stuck to the top of the screen while you scroll. z-index 20 keeps it above the page content. */
/* The background is the oat color at 95% opacity, and backdrop-filter blurs whatever scrolls behind it. */
.topbar{position:sticky; top:0; z-index:20; background:rgba(243,238,226,.95); backdrop-filter:blur(6px); border-bottom:1px solid var(--line)}
/* min-height 4.4rem is the header height. The 5.5rem scroll-padding-top near the top of the file is set to clear it. */
.topbar-in{display:flex; align-items:center; gap:1.4rem; min-height:4.4rem}
/* margin-right auto on the brand pushes everything after it, the nav and cart button, over to the right side. */
.brand{display:flex; align-items:center; gap:.7rem; margin-right:auto; text-decoration:none; color:var(--ink)}
/* The small moss block with the shop initial, using the same inset shadow idea as the buttons. */
.brand-stamp{display:grid; place-items:center; width:3.1rem; height:2.2rem; border-radius:10px; background:var(--moss); color:var(--oat); font-family:var(--serif); font-size:1.3rem; box-shadow:inset 0 -5px 0 rgba(0,0,0,.2),inset 0 1px 0 rgba(255,255,255,.25)}
.brand-name{font-family:var(--serif); font-size:1.3rem; line-height:1; display:flex; flex-direction:column}
.brand-name small{font-family:var(--sans); font-size:.62rem; font-weight:800; letter-spacing:.24em; text-transform:uppercase; color:var(--muted); margin-top:.3rem}
.nav{display:flex; gap:1.6rem}
/* On phones only the first nav link shows. nth-child(n+2) hides the second link and every one after it. */
.nav a:nth-child(n+2){display:none}
.nav a{color:var(--ink); text-decoration:none; font-weight:700; padding:.3rem 0; border-bottom:2px solid transparent; white-space:nowrap}
/* aria-current marks the link for the page you are on, so it gets the clay underline too. */
.nav a:hover,.nav a[aria-current]{border-bottom-color:var(--clay)}
/* From 44rem wide and up, all nav links show again. */
@media (min-width:44rem){.nav a:nth-child(n+2){display:inline}}
/* On very small phones the brand tagline is hidden and the header spacing tightens so everything fits on one line. */
@media (max-width:26rem){.brand-name small{display:none} .topbar-in{gap:.9rem}}

/* HERO AND SOAP SHELF */
/* The hero is the top section on the shelf page. overflow hidden clips any soap bar that would poke past the edges. */
.hero{padding-top:clamp(2.6rem,7vw,5rem); overflow:hidden}
/* The main heading scales from 2.8rem on phones to 6rem on big screens. max-width 14ch limits each line to about 14 characters. */
.hero h1{font-size:clamp(2.8rem,8vw,6rem); letter-spacing:-.02em; margin-bottom:1.2rem; max-width:14ch}
.hero .lede{margin-bottom:1.8rem}
/* The .shelf is the wooden plank the soap bars sit on. The thick bottom border is the plank itself. */
/* The first shadow (0 6px 0) adds a darker strip under it, like the front edge of the wood. */
/* The second shadow is a soft blur pulled in with a negative spread (-18px), so it only shows as a gentle shadow below the shelf. */
.shelf{margin-top:clamp(2.5rem,6vw,4rem); border-bottom:14px solid var(--wood); box-shadow:0 6px 0 #8f6f49, 0 22px 30px -18px rgba(37,48,42,.45)}
/* align-items flex-end lines up the bottoms of the bars so they all sit on the shelf, whatever their height. */
.shelf-row{display:flex; justify-content:center; align-items:flex-end; gap:clamp(.5rem,1.4vw,1.1rem); padding-inline:1rem}
/* Each .bar is one drawn soap bar. flex none stops it from shrinking. The size scales with the screen using clamp. */
/* The border-radius has rounder top corners than bottom corners, like a bar of soap sitting on its base. */
/* The inset shadows give a bright top edge and a darker bottom band, so it looks like a thick block. */
.bar{flex:none; display:grid; place-items:center; width:clamp(7.4rem,13vw,11rem); height:clamp(4.2rem,7vw,5.8rem); border-radius:16px 16px 11px 11px; box-shadow:inset 0 3px 0 rgba(255,255,255,.4),inset 0 -9px 0 rgba(0,0,0,.1)}
/* The pressed label on each bar. Dark text at 40% plus a white text-shadow 1px below makes it look stamped into the soap. */
.bar > span{padding:.2rem .7rem; border:1.5px solid rgba(0,0,0,.2); border-radius:999px; font-size:clamp(.5rem,.85vw,.66rem); font-weight:800; letter-spacing:.2em; text-transform:uppercase; color:rgba(0,0,0,.4); text-shadow:0 1px 0 rgba(255,255,255,.35); white-space:nowrap}
/* Tall bars stand upright, so they are narrow and high. */
.bar--tall{width:clamp(4.6rem,8vw,6.6rem); height:clamp(7.4rem,13vw,10.6rem)}
/* writing-mode vertical-rl turns the label so it runs top to bottom on the upright bars. */
.bar--tall > span{writing-mode:vertical-rl; padding:.7rem .2rem}
/* A .bar-stack holds two bars piled on top of each other, with a 3px gap between them. */
.bar-stack{flex:none; display:flex; flex-direction:column; gap:3px}
/* One background color per soap scent. */
.bar--sage{background:var(--sage)}
.bar--butter{background:var(--butter)}
.bar--clay{background:#d08f73}
.bar--moss{background:#6f8c77}
.bar--cream{background:#f6ecd6}
.bar--char{background:#6b6f6c}
/* The charcoal bar is dark, so its label switches to light text and a light border to stay readable. */
.bar--char > span{color:rgba(255,255,255,.55); border-color:rgba(255,255,255,.3); text-shadow:none}
.bar--rose{background:var(--rose)}

/* FACTS STRIP */
/* The row of big numbers under the shelf. margin-top 6px makes room for the 6px wood edge shadow on the shelf above. */
.facts{background:var(--paper); border-bottom:1px solid var(--line); padding:clamp(2.6rem,6vw,4rem) 0; margin-top:6px}
.facts-grid{display:grid; gap:1.8rem}
/* Each fact is a two column grid: the big number takes only the width it needs (auto), the text takes the rest (1fr). */
.facts li{display:grid; grid-template-columns:auto 1fr; gap:1.1rem; align-items:center}
.facts b{font-family:var(--serif); font-weight:400; font-size:clamp(3.4rem,7vw,5rem); line-height:.9; color:var(--clay)}
.facts span{color:var(--body); max-width:17rem}
/* From 52rem wide, the three facts sit side by side in three equal columns. */
@media (min-width:52rem){.facts-grid{grid-template-columns:repeat(3,1fr); gap:2.5rem}}

/* SECTION HEADINGS */
/* The --row version puts the heading on the left and a link on the right, wrapping underneath on narrow screens. */
.sec-head{margin-bottom:1.8rem}
.sec-head--row{display:flex; flex-wrap:wrap; align-items:flex-end; justify-content:space-between; gap:1rem 2rem}
.sec-desc{max-width:44rem; margin:.7rem 0 0; color:var(--muted)}

/* THANK YOU BANNER */
/* Shown by shop.js after a customer comes back from Square checkout (the page address ends in ?thanks=1). It starts hidden. */
.thanks{background:var(--moss); color:#fff}
.thanks-in{display:flex; align-items:center; gap:.9rem; padding-block:1rem}
.thanks-stamp{display:grid; place-items:center; flex:none; width:2.2rem; height:2.2rem; border-radius:50%; background:var(--butter); color:var(--ink); font-weight:800}
.thanks p{margin:0}

/* SHOP SECTION AND FILTERS */
/* The heading row above the product grid, with the category filter buttons beside it. */
.shop{padding:clamp(3rem,7vw,5rem) 0}
.shop-head{display:flex; flex-wrap:wrap; align-items:flex-end; justify-content:space-between; gap:1.2rem 2rem; margin-bottom:1.8rem}
.filters{display:flex; flex-wrap:wrap; gap:.45rem}
/* Filter buttons use an inset shadow as their outline, so the outline can thicken on hover without the button changing size. */
.filters button{min-height:42px; padding:.35rem 1.1rem; border:0; border-radius:999px; background:var(--paper); box-shadow:inset 0 0 0 1.5px var(--line); color:var(--ink); font-weight:700; cursor:pointer}
.filters button:hover{box-shadow:inset 0 0 0 2px var(--moss)}
/* aria-pressed true marks the filter that is currently on. shop.js sets it, and screen readers announce the button as pressed. */
/* Styling the attribute itself means the look and the accessibility state can never disagree. */
.filters button[aria-pressed="true"]{background:var(--moss); color:#fff; box-shadow:inset 0 -4px 0 rgba(0,0,0,.18)}

/* PRODUCT GRID */
/* Two columns on phones, three from 40rem, four from 64rem. */
/* minmax(0,1fr) lets columns shrink below their content width, so a long product name cannot stretch the grid. */
.products{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:clamp(.8rem,2vw,1.5rem)}
@media (min-width:40rem){.products{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (min-width:64rem){.products{grid-template-columns:repeat(4,minmax(0,1fr))}}
/* Each product .card uses the soap bar shadow from --bar. overflow hidden clips the photo to the rounded corners. */
.card{position:relative; background:var(--paper); border-radius:22px; box-shadow:var(--bar); overflow:hidden; transition:transform .2s}
.card:hover{transform:translateY(-4px)}
/* The whole card is one link. Flex column plus height 100% lets every card in a row match the tallest one. */
.card-link{display:flex; flex-direction:column; height:100%; color:inherit; text-decoration:none}
/* aspect-ratio 1 keeps the photo area square. object-fit cover fills the square and crops the photo without stretching it. */
.card-media{display:block; aspect-ratio:1; background:var(--sage-lt)}
.card-media img{width:100%; height:100%; object-fit:cover}
/* The .card-blank placeholder fills the photo area when a product has no picture. shop.js puts the first letter of the soap name in it. */
/* The radial gradient makes a soft light spot near the middle that fades to sage at the edges. */
.card-blank{display:grid; place-items:center; width:100%; height:100%; font-family:var(--serif); font-size:4rem; color:var(--moss); background:radial-gradient(circle at 50% 40%,#eef3ea,var(--sage-lt) 65%)}
/* The Sold out badge, pinned to the top left corner of the card. The card is position relative so this stays inside it. */
.card-badge{position:absolute; top:.8rem; left:.8rem; padding:.2rem .7rem; border-radius:999px; background:var(--ink); color:#fff; font-size:.7rem; font-weight:800; letter-spacing:.14em; text-transform:uppercase}
.card-body{display:flex; flex-direction:column; gap:.25rem; flex:1; padding:1rem 1rem 1.1rem}
.card-name{font-family:var(--serif); font-size:clamp(1.05rem,1.6vw,1.25rem); line-height:1.2; color:var(--ink)}
.card-price{color:var(--ink); font-weight:800}
/* margin-top auto pushes the call to action to the bottom of the card, so they line up across a row. */
.card-cta{margin-top:auto; padding-top:.5rem; font-size:.88rem; font-weight:800; color:var(--clay-dk)}
.card-cta::after{content:" \2192"}
.card-link:hover .card-name{color:var(--clay-dk)}
/* shop.js adds .is-out to sold out cards. The photo fades and the call to action greys out. */
.is-out .card-media{opacity:.55}
.is-out .card-cta{color:var(--muted)}

/* BUBBLE DIVIDER */
/* A row of the bubble SVG from --bubbles, repeated sideways (repeat-x) as a decorative divider between sections. */
.bubbles{height:30px; background:var(--bubbles) repeat-x center}
.bubbles--light{opacity:.45; margin-bottom:2.4rem}

/* HOW IT'S MADE */
.made{padding:clamp(3rem,7vw,5rem) 0}
/* This repeats the .sec-head rule from above. It has the same values, so removing either one would change nothing. */
.sec-head{margin-bottom:1.8rem}
/* The steps are numbered with a CSS counter. counter-reset starts the count at zero on the list. */
.steps{counter-reset:step; display:grid; gap:1.2rem}
/* counter-increment adds one for each list item. */
.steps li{counter-increment:step; position:relative; background:var(--paper); border-radius:22px; box-shadow:var(--bar); padding:1.6rem 1.4rem 1.4rem}
/* ::before prints the number before each step. The literal 0 in front turns 1 into 01, 2 into 02, and so on. */
.steps li::before{content:"0" counter(step); display:block; font-family:var(--serif); font-size:1.1rem; color:var(--clay); margin-bottom:.5rem}
.steps h3{font-size:1.6rem; margin-bottom:.5rem}
.steps p{margin:0; font-size:.98rem}
/* Two columns from 40rem wide, four in a row from 64rem. */
@media (min-width:40rem){.steps{grid-template-columns:1fr 1fr}}
@media (min-width:64rem){.steps{grid-template-columns:repeat(4,1fr)}}

/* CALL TO ACTION BAND */
/* The moss colored strip near the bottom of the page. */
.cta{background:var(--moss); color:#fff}
.cta-in{display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:1.4rem; padding-block:clamp(2.4rem,6vw,3.6rem)}
.cta h2{color:#fff}

/* PRODUCT PAGE */
/* Breadcrumbs: the small trail of links at the top, The shelf, a slash, then the soap name. */
.crumbs-wrap{padding-top:1.6rem}
.crumbs{display:flex; flex-wrap:wrap; gap:.5rem; font-size:.9rem; color:var(--muted)}
.crumbs a{font-weight:700}
/* padding-block sets top then bottom padding. The page stacks gallery over details on phones, side by side later on. */
.product{display:grid; gap:2rem; padding-block:1rem clamp(3rem,7vw,5rem)}
/* The big main photo, square, with rounded corners and the soap bar shadow. */
.gallery-main{aspect-ratio:1; border-radius:26px; overflow:hidden; background:var(--sage-lt); box-shadow:var(--bar)}
.gallery-main img{width:100%; height:100%; object-fit:cover}
/* The placeholder letter is bigger here because the main photo area is bigger than a card. */
.gallery-main .card-blank{font-size:7rem}
.thumbs{display:flex; flex-wrap:wrap; gap:.6rem; margin-top:.8rem}
/* Small thumbnail buttons under the main photo. Inactive ones are slightly faded. */
.thumbs button{display:block; width:68px; height:68px; padding:0; border:0; border-radius:14px; overflow:hidden; background:var(--sage-lt); cursor:pointer; opacity:.7}
.thumbs img{width:100%; height:100%; object-fit:cover}
.thumbs button:hover{opacity:1}
/* The thumbnail that matches the main photo is marked with aria-pressed by shop.js and gets a moss outline. */
.thumbs button[aria-pressed="true"]{opacity:1; outline:3px solid var(--moss); outline-offset:2px}
/* If shop.js has nothing to put in the stamp above the title, it stays empty and :empty hides it. */
.p-info .stamp:empty{display:none}
.p-info h1{font-size:clamp(2.1rem,4.6vw,3.4rem); letter-spacing:-.01em; margin-bottom:.7rem}
.p-price{font-size:1.5rem; font-weight:800; color:var(--ink); margin-bottom:1.3rem}
.option{margin-bottom:1.2rem}
/* The legend is the small uppercase label above the option buttons, like Size. The b inside holds the chosen value. */
.option legend{padding:0; margin-bottom:.5rem; font-size:.78rem; font-weight:800; letter-spacing:.14em; text-transform:uppercase; color:var(--muted)}
.option legend b{color:var(--ink); letter-spacing:.04em}
.option-values{display:flex; flex-wrap:wrap; gap:.5rem}
/* Option buttons work the same way as the filter buttons on the shelf page. */
.option-values button{min-height:44px; padding:.4rem 1.1rem; border:0; border-radius:999px; background:var(--paper); box-shadow:inset 0 0 0 1.5px var(--line); font-weight:700; cursor:pointer}
.option-values button:hover{box-shadow:inset 0 0 0 2px var(--moss)}
.option-values button[aria-pressed="true"]{background:var(--moss); color:#fff; box-shadow:inset 0 -4px 0 rgba(0,0,0,.18)}
/* A sold out option is crossed out but still clickable, so the shopper can see it exists. */
.option-values .is-out{text-decoration:line-through; color:var(--muted); background:transparent}
/* If the shopper picks a sold out option anyway, it fills grey instead of moss. */
.option-values .is-out[aria-pressed="true"]{background:var(--muted); color:#fff}
/* The .buy row holds the quantity stepper and Add to cart button, with a line above it. */
.buy{display:flex; flex-wrap:wrap; align-items:center; gap:.9rem; margin:1.6rem 0 2rem; padding-top:1.4rem; border-top:1px solid var(--line)}
.p-desc{max-width:36rem}
/* From 56rem wide the photo and the details sit side by side, with the photo column a little wider. */
/* The gallery becomes sticky, so the photo stays in view while you scroll the description. top 6rem keeps it below the header. */
@media (min-width:56rem){.product{grid-template-columns:1.05fr .95fr; gap:clamp(2rem,5vw,4.5rem); align-items:start} .gallery{position:sticky; top:6rem}}
/* The More soaps section at the bottom of the product page. */
.related{padding-bottom:clamp(3rem,7vw,5rem)}
.related .bubbles{margin-bottom:clamp(2.4rem,6vw,4rem)}

/* QUANTITY STEPPERS */
/* The minus, number and plus controls. Used in the cart drawer and, in the --big version, on the product page. */
.qty{display:flex; align-items:center; gap:.45rem; margin-top:.5rem}
.qty button{min-width:38px; min-height:38px; border:0; border-radius:999px; background:var(--paper); box-shadow:inset 0 0 0 1.5px var(--line); font-weight:800; cursor:pointer}
.qty button:hover{box-shadow:inset 0 0 0 2px var(--moss)}
.qty-num{min-width:1.7rem; text-align:center; color:var(--ink); font-weight:800}
.qty--big{margin:0; padding:.3rem; border-radius:999px; background:var(--paper); box-shadow:inset 0 0 0 1.5px var(--line)}
.qty--big button{box-shadow:none; background:var(--sage-lt); min-width:44px; min-height:44px}
/* The Remove link in each cart line is a button made to look like an underlined text link. */
.qty .remove{min-width:0; padding:0 .3rem; background:none; box-shadow:none; color:var(--clay-dk); font-size:.88rem; font-weight:700; text-decoration:underline}
.qty .remove:hover{box-shadow:none}

/* CART DRAWER */
/* The cart is a native <dialog> element. shop.js opens it with showModal(), which traps keyboard focus inside and closes on Escape. */
/* Browsers centre a dialog and cap its size by default. These values undo that: */
/* height and max-height 100% make it full height, and margin 0 0 0 auto pushes it against the right edge like a side drawer. */
/* width min(30rem,100%) means 30rem wide on big screens and the full width on phones. */
.cart{width:min(30rem,100%); max-width:100%; height:100%; max-height:100%; margin:0 0 0 auto; padding:0; border:0; background:var(--oat); color:var(--body)}
/* The layout is only set while the dialog has the open attribute. */
/* Setting display flex on .cart itself would overrule the browser's hidden state and show the cart all the time. */
.cart[open]{display:flex; flex-direction:column}
/* ::backdrop is the layer the browser places behind a modal dialog. This dims the page behind the cart. */
.cart::backdrop{background:rgba(37,48,42,.5)}
.cart-head{display:flex; align-items:center; justify-content:space-between; gap:1rem; padding:1rem 1.2rem; background:var(--paper); border-bottom:1px solid var(--line)}
.cart-head h2{font-size:1.6rem}
/* The > means only a status message placed directly in the cart, used for checkout errors, so it is shown in clay. */
.cart > .status{padding:1.2rem 1.2rem 0; color:var(--clay-dk); font-weight:700}
/* flex 1 lets the list of items take all the space left between the cart header and footer. */
/* overflow-y auto makes only that list scroll, so the header and the checkout button always stay in view. */
.lines{flex:1; overflow-y:auto; padding:0 1.2rem}
/* Each cart line is a three column grid: a 64px photo, the name and stepper in the middle, and the price on the right. */
.line{display:grid; grid-template-columns:64px 1fr auto; gap:.9rem; align-items:start; padding:1rem 0; border-bottom:1px solid var(--line)}
.line img{width:64px; height:64px; object-fit:cover; border-radius:14px; background:var(--sage-lt)}
.line-title{margin:0; color:var(--ink); font-weight:800; line-height:1.3}
/* The variant (like the size) and any warning (like Sold out since you added it) share small text, then the warning gets clay and bold. */
.line-variant,.line-warn{margin:0; font-size:.88rem; color:var(--muted)}
.line-warn{color:var(--clay-dk); font-weight:700}
/* Hide the variant or warning lines when shop.js leaves them empty, so they do not leave blank gaps. */
.line-variant:empty,.line-warn:empty{display:none}
.line-total{margin:0; color:var(--ink); font-weight:800; white-space:nowrap}
/* The cart footer with the note box, subtotal and checkout button. shop.js hides it when the cart is empty. */
.cart-foot{display:grid; gap:.6rem; padding:1rem 1.2rem 1.3rem; background:var(--paper); border-top:1px solid var(--line)}
.cart-foot label{font-size:.78rem; font-weight:800; letter-spacing:.12em; text-transform:uppercase; color:var(--muted)}
/* resize vertical lets the shopper drag the note box taller but not wider, so it cannot break the layout. */
.cart-foot textarea{width:100%; padding:.55rem .7rem; border:1.5px solid var(--line); border-radius:12px; background:#fff; resize:vertical}
.subtotal{display:flex; justify-content:space-between; margin:0; color:var(--ink); font-size:1.2rem}
/* The .fine class is the small print, like the note that tax and shipping are added on the Square checkout page. */
.fine{margin:0; font-size:.85rem; color:var(--muted)}

/* 404 PAGE */
/* The page shown when an address does not exist. */
.missing{padding-block:clamp(4rem,10vw,7rem)}
.missing h1{font-size:clamp(2.6rem,6vw,4.4rem); margin-bottom:1rem}
.missing .lede{margin-bottom:1.8rem}

/* FOOTER */
/* One column on phones, two columns from 52rem wide (the first slightly wider). */
.site-footer{background:var(--ink); color:#c9d3c8; padding:2.6rem 0 1.8rem}
.footer-grid{display:grid; gap:2rem}
.footer-brand{font-family:var(--serif); font-size:1.5rem; color:#fff; margin-bottom:.5rem}
.footer-grid p{max-width:32rem}
/* Footer headings are styled small and uppercase so they read as labels, not big serif headings. */
.site-footer h2{font-family:var(--sans); font-size:.74rem; font-weight:800; letter-spacing:.2em; text-transform:uppercase; color:var(--sage); margin-bottom:.8rem}
.site-footer li{margin-bottom:.45rem; font-size:.96rem}
.site-footer a{color:#fff}
/* The bottom strip of the footer, separated by a faint white line at 14% opacity. */
.footer-base{margin-top:2.2rem; padding-top:1.2rem; border-top:1px solid rgba(255,255,255,.14); font-size:.84rem; color:#a9b5aa}
.footer-base p{margin:0}
@media (min-width:52rem){.footer-grid{grid-template-columns:1.2fr 1fr}}

/* REDUCED MOTION */
/* Some people turn on Reduce motion in their device settings because animation makes them feel unwell. */
/* When that is on, scrolling jumps instead of gliding and every hover transition is switched off. */
/* !important makes sure this beats the transitions set on individual elements earlier in the file. */
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto} *{transition:none!important}}
