/* Home-page styles for templates/siteindex_b2c.php - the B2C storefront homepage.
   Extracted 27 Aug 2026 from a 13.7KB inline <style> in that file. It carried no
   PHP interpolation at all, so it was static bytes re-sent on every homepage view
   and never cached - on the body most domains render. As an external file it is
   gzipped and cached like the rest of templates/assets.
   Linked from siteindex_b2c.php via msh_css_asset(); run
   `php .claude/minify-css.php` after editing (add it to $SOURCES there first). */

/* Home page hero image (see the domain_slide block below). Every image the
   domain has configured, in the order it was configured; .homehero carries its
   aspect-ratio inline so the page reserves the right height before the first
   one loads. */
DIV.homehero{position:relative;top:0;left:0;width:100%;overflow:hidden;}
DIV.homeheroslide{width:100%;cursor:pointer;position:relative;text-align:center;}
DIV.homeheroslide picture,DIV.homeheroslide img{display:block;}

/* Auto-cycling banner. The [data-hero-cycle] attribute is emitted ONLY when the
   domain resolved more than one home page image, so a single-image domain keeps
   the plain in-flow markup above byte for byte and none of this applies. With
   two or more, the slides are stacked in the same box and cross-faded by
   wlhHeroCycle() below. The box keeps the FIRST slide's aspect ratio (the page
   must reserve one height, not resize itself every few seconds), so the rest are
   object-fit:cover'd into it - the img's own inline style carries that, because
   an inline height:auto would beat a stylesheet rule here.
   pointer-events is load-bearing, not tidiness: the slides are stacked, so
   without it the LAST one in source order sits on top of the visible one and
   every click on the banner follows that slide's link instead of the one the
   visitor can actually see. */
DIV.homehero[data-hero-cycle] DIV.homeheroslide{position:absolute;inset:0;opacity:0;pointer-events:none;transition:opacity .9s ease-in-out;}
DIV.homehero[data-hero-cycle] DIV.homeheroslide.is-active{opacity:1;pointer-events:auto;}
/* The search pill / call to action is rendered ONCE, as a sibling of the slides
   rather than inside one: inside, it would fade out with its slide, and a copy
   per slide would repeat id='bigsearch' down the page. */
DIV.homehero .homehero-overlay{z-index:2;}

/* --- White-label homepage (.wlh-*) ------------------------------------------
   This page's own design, scoped here rather than in msh.css because no other
   page uses it. Colours/fonts all come from the per-domain theme tokens.
   Every selector carries the body.msh prefix so it outranks msh.css's own
   element rules (body.msh h2 { color: var(--ink) } and friends are
   (0,1,2) - a bare .wlh-signup h2 at (0,1,1) LOSES to them, which rendered
   the newsletter heading ink-on-ink, i.e. invisible). */

/* Text hero, shown only when the domain has no usable home page image. */
body.msh .wlh-hero{background:var(--paper-2);border-bottom:1px solid var(--line);text-align:center;padding:clamp(2.5rem,7vw,5.5rem) 1rem;}
body.msh .wlh-hero h1{margin:0 0 .75rem;}
body.msh .wlh-lede{color:var(--ink-3);max-width:60ch;margin:0 auto;}
body.msh .wlh-search{display:flex;align-items:center;margin:1.6rem auto 0;max-width:540px;background:var(--card);border:1px solid var(--line-2);border-radius:999px;padding:4px 6px 4px 18px;}
body.msh .wlh-search input[type=search]{flex:1;min-width:0;border:none;box-shadow:none;background:transparent;padding:10px 6px;font-size:1rem;color:var(--ink);}
body.msh .wlh-search button{border:none;cursor:pointer;border-radius:999px;padding:.6rem 1.3rem;background:var(--btn);color:var(--btn-text);font-weight:600;}
body.msh .wlh-search button:hover{background:var(--btn-hover);}
body.msh .wlh-cta{margin-top:1.5rem;display:flex;gap:.6rem;justify-content:center;flex-wrap:wrap;}

/* Slim USP strip between two hairlines. */
body.msh .wlh-usp{display:flex;flex-wrap:wrap;justify-content:space-evenly;gap:.5rem 1.5rem;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:1rem;margin-top:1.5rem;font-weight:600;}
body.msh .wlh-usp div{white-space:nowrap;}

/* Section: centred heading with a short brass rule under it - the gallery
   plaque treatment that gives this page its own voice. */
body.msh .wlh-sect{padding-block:clamp(2rem,5vw,3.5rem);}
body.msh .wlh-sect>h2{text-align:center;margin:0;}
body.msh .wlh-sect>h2::after{content:'';display:block;width:64px;height:3px;margin:.6rem auto 0;background:var(--brass);}
body.msh .wlh-sub{text-align:center;color:var(--ink-3);max-width:60ch;margin:.9rem auto 0;}
body.msh .wlh-foot{text-align:center;margin:1.8rem 0 0;}
body.msh .wlh-note{text-align:center;color:var(--ink-3);max-width:74ch;margin:.7rem auto 0;font-size:.875rem;}

/* Gallery-wall collections grid: square tiles, label over the picture. */
body.msh .wlh-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-top:1.8rem;}
body.msh .wlh-tile{position:relative;display:block;border-radius:var(--radius);overflow:hidden;box-shadow:var(--sh-1);background:var(--paper-3);}
body.msh .wlh-tile img{width:100%;height:100%;aspect-ratio:1/1;object-fit:cover;display:block;transition:transform .6s var(--ease-out);}
body.msh .wlh-tile span{position:absolute;left:0;right:0;bottom:0;padding:2.2rem .8rem .7rem;color:#fff;font-weight:600;text-align:center;background:linear-gradient(180deg,rgba(0,0,0,0) 0%,rgba(0,0,0,.72) 85%);}
body.msh .wlh-tile em{display:block;font-style:normal;font-weight:400;font-size:.78rem;opacity:.85;}
@media(hover:hover) and (pointer:fine){
  body.msh .wlh-tile:hover img{transform:scale(1.05);}
}

/* Print rail: a shelf of framed prints at a shared hanging height, native
   horizontal scroll with snap points - no carousel library. Each picture
   keeps its own aspect ratio inside a card-coloured mat and hairline frame,
   with a museum-label caption underneath. */
body.msh .wlh-railwrap{position:relative;margin-top:1.8rem;}
/* The rail scrolls but shows no scrollbar: the nudge buttons and the prints
   running off the edge are the affordance. All three declarations are needed -
   Firefox honours scrollbar-width, old Edge -ms-overflow-style, and
   WebKit/Blink only the ::-webkit-scrollbar rule below. */
/* "safe center" and not plain "center": a centred flex row that DOES overflow
   puts its first items at a negative offset, and scrollLeft cannot go below 0,
   so those prints become permanently unreachable. The safe keyword falls back
   to flex-start exactly when the row overflows, which is what we want. A
   browser that doesn't know the keyword drops the declaration and keeps
   flex-start, i.e. the pre-centring behaviour, never the broken one. */
body.msh .wlh-rail{display:flex;gap:26px;align-items:flex-start;justify-content:safe center;overflow-x:auto;scroll-snap-type:x proximity;padding:10px 4px 18px;scrollbar-width:none;-ms-overflow-style:none;}
body.msh .wlh-rail::-webkit-scrollbar{display:none;width:0;height:0;}
body.msh .wlh-exhibit{flex:0 0 auto;scroll-snap-align:start;max-width:260px;text-align:center;text-decoration:none;color:inherit;display:block;}
/* width:fit-content + auto inline margins as well as inline-block: the frame
   is itself a <span>, so a descendant selector like ".wlh-exhibit span" beats
   ".wlh-frame" on specificity and can quietly force it to display:block - at
   which point the frame stretches to the exhibit's width (set by the caption)
   and the picture hangs off the left edge with a wide empty mat beside it.
   Keeping the frame centred by its own box model means that can't recur. */
body.msh .wlh-frame{display:inline-block;width:fit-content;margin-inline:auto;background:var(--card);padding:10px;border:1px solid var(--line);box-shadow:var(--sh-1);transition:box-shadow .35s var(--ease),transform .35s var(--ease);}
body.msh .wlh-frame img{display:block;height:190px;width:auto;max-width:230px;object-fit:contain;}
/* Caption only - deliberately NOT ".wlh-exhibit span", which would also match
   the frame above. */
body.msh .wlh-exhibit .wlh-label{display:block;margin-top:.55rem;font-size:.82rem;color:var(--ink-3);line-height:1.35;overflow-wrap:anywhere;}
@media(hover:hover) and (pointer:fine){
  body.msh .wlh-exhibit:hover .wlh-frame{box-shadow:var(--sh-2);transform:translateY(-3px);}
}
body.msh .wlh-railbtn{position:absolute;top:90px;z-index:2;width:38px;height:38px;border-radius:50%;border:1px solid var(--line-2);background:var(--card);color:var(--ink);cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:var(--sh-1);transition:transform .25s var(--ease),background .25s var(--ease);}
body.msh .wlh-railbtn:hover{background:var(--paper-2);transform:scale(1.12);}
body.msh .wlh-railbtn--prev{left:-8px;}
body.msh .wlh-railbtn--next{right:-8px;}

/* Price list: the product range as a shop price card - name, dotted leader,
   "from" price - rather than the portal's format panels. */
body.msh .wlh-pricelist{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:.2rem 3rem;max-width:920px;margin:1.8rem auto 0;}
body.msh .wlh-priceline{display:block;padding:.8rem 0;border-bottom:1px dotted var(--line-2);color:inherit;text-decoration:none;}
body.msh .wlh-priceline b{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;}
body.msh .wlh-priceline b i{font-style:normal;color:var(--brass-text);white-space:nowrap;font-weight:600;}
body.msh .wlh-priceline>span{display:block;font-size:.85rem;color:var(--ink-3);margin-top:.2rem;}
body.msh .wlh-priceline:hover b{color:var(--link);}

/* Blog teasers. */
body.msh .wlh-posts{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:24px;margin-top:1.8rem;}
body.msh .wlh-post{display:block;color:inherit;text-decoration:none;}
/* Centred both ways (21 Aug 2026, Derek, three passes: "thumbnails ... should be
   center not top left", "center the news icons vertically as well", "should be in
   the vertical middle"): image_source() in helper_funcs.php returns its <img>
   with an INLINE style='max-width:Npx;max-height:Npx' at the media's own preview
   size. Inline beats the width/height below, so any blog picture whose preview is
   smaller than the card renders at its natural size and, in a plain block box,
   sits jammed into the top-left corner with the panel showing round two sides.
   Absolute 50%/50% + translate(-50%,-50%) rather than centred grid or flex
   auto-margins - both of those were tried and both left it top-aligned
   vertically, because whether the img's height:100% counts as "full height" or
   "free space to distribute" depends on how the browser resolves a percentage
   against an aspect-ratio'd box. Centring on the box's own midpoint asks the
   browser no such question: wherever the used size lands, its centre is the
   card's centre. The img still asks for width/height:100% + object-fit:cover, so
   a picture big enough to fill the card still covers it edge to edge exactly as
   before, and only a capped (smaller) one floats in the middle of the panel. */
body.msh .wlh-postimg{position:relative;aspect-ratio:16/10;border-radius:var(--radius);overflow:hidden;background:var(--paper-3);box-shadow:var(--sh-1);display:block;}
body.msh .wlh-postimg img{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:100%;height:100%;object-fit:cover;display:block;}
body.msh .wlh-post b{display:block;margin-top:.7rem;}
body.msh .wlh-post>span{display:block;margin-top:.25rem;font-size:.85rem;color:var(--ink-3);}

/* --- Animations -------------------------------------------------------------
   Below-the-fold sections ride the shared [data-reveal] scroll-reveal
   (msh.js adds .is-in); the extra states here build on that. Above-the-fold
   pieces animate CSS-only with `both` fill, so a browser that ignores
   @keyframes just shows the finished state. msh.css's global
   prefers-reduced-motion block zeroes all durations/delays; the two initial
   states that would otherwise stay stuck are reset explicitly below. */
@keyframes wlh-rise{from{opacity:0;transform:translateY(18px);}to{opacity:1;transform:none;}}
body.msh .wlh-hero>*{animation:wlh-rise .55s var(--ease-out) both;}
body.msh .wlh-hero>*:nth-child(2){animation-delay:.08s;}
body.msh .wlh-hero>*:nth-child(3){animation-delay:.16s;}
body.msh .wlh-hero>*:nth-child(4){animation-delay:.24s;}
/* Slow settle on the uploaded home page image (.homehero clips the overflow). */
@keyframes wlh-heroimg{from{transform:scale(1.035);}to{transform:none;}}
body.msh DIV.homeheroslide img{animation:wlh-heroimg 1.4s var(--ease-out) both;}
/* On a cycling banner the settle is re-run per slide instead of once per page:
   removing/adding .is-active restarts the animation, so each image arrives with
   the same slow settle the single-image banner gets on load. */
body.msh DIV.homehero[data-hero-cycle] DIV.homeheroslide img{animation:none;}
body.msh DIV.homehero[data-hero-cycle] DIV.homeheroslide.is-active img{animation:wlh-heroimg 1.4s var(--ease-out) both;}
/* The brass rule draws itself in once its heading scrolls into view. */
body.msh .wlh-sect>h2[data-reveal]::after{transform:scaleX(0);transition:transform .55s var(--ease-out) .3s;}
body.msh .wlh-sect>h2[data-reveal].is-in::after{transform:scaleX(1);}
/* Prints settle onto the rail with a hint of rotation. These override the
   shared [data-reveal] states, so the .is-in return-to-rest must be restated
   (this style block loads after msh.css and would otherwise win the tie). */
body.msh .wlh-exhibit[data-reveal]{transform:translateY(18px) rotate(1.2deg);}
body.msh .wlh-exhibit[data-reveal].is-in{transform:none;}
body.msh .wlh-priceline{transition:padding-left .25s var(--ease),background .25s var(--ease);}
body.msh .wlh-priceline:hover{padding-left:.4rem;}
body.msh .wlh-search:focus-within{box-shadow:var(--sh-2);}
@media (prefers-reduced-motion: reduce){
  body.msh .wlh-sect>h2[data-reveal]::after{transform:none;}
  body.msh .wlh-exhibit[data-reveal]{transform:none;}
}

@media(max-width:640px){
  body.msh .wlh-frame img{height:150px;max-width:180px;}
  body.msh .wlh-railbtn{top:70px;}
  body.msh .wlh-tiles{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;}
}
