/*
Theme Name: Happy Turtle
Theme URI: https://happyturtlecustomgifts.com
Author: Happy Turtle Custom Gifts
Author URI: https://happyturtlecustomgifts.com
Description: A warm, handmade block theme for a small gift-basket studio. Built on core WordPress blocks only — colours, type and spacing all live in theme.json, and every page section ships as an editable block pattern.
Version: 1.6.0
Requires at least: 6.6
Tested up to: 6.8
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: happy-turtle
Tags: block-theme, full-site-editing, one-column, two-columns, custom-colors, custom-menu, custom-logo, featured-images, block-patterns
*/

/* -------------------------------------------------------------------------
 * Almost everything visual is defined in theme.json. This file is only for
 * the handful of things theme.json cannot express: block style variations,
 * decorative pseudo-elements, and a couple of graceful-degradation rules.
 * ---------------------------------------------------------------------- */


/* --- Block style: Heart Divider (core/paragraph) -------------------------
 * The small "— ♥ —" rule that sits under section headings.
 *
 * The heart in the content is U+2665 BLACK HEART SUIT, a typographic symbol.
 * iOS gives it emoji presentation anyway — a glossy red Apple heart, the one
 * thing on the page that isn't in the brand palette. The character itself is
 * saved in the page content, so it can't be swapped from a stylesheet.
 *
 * So the glyph is collapsed to nothing and the heart is drawn here instead.
 * It sits in the flex gap between the two rules, which is why the text can go
 * to `font-size: 0` without the spacing changing. A background rather than a
 * mask because the rules either side are pseudo-elements, and a mask on the
 * parent would take them with it — hence the one hard-coded colour in this
 * file, green-light, matching the rules it sits between.
 * ---------------------------------------------------------------------- */
.is-style-heart-divider {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0.75rem;
	color: var(--wp--preset--color--green-light);
	font-size: 0;
	line-height: 1;
	margin-block: var(--wp--preset--spacing--sm);
	min-height: 12px;
	background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='%236F8A55'%3E%3Cpath%20d='M12%2021C10%2019.4%203.6%2015.2%203.6%2010.4A4.2%204.2%200%200%201%2012%208.2a4.2%204.2%200%200%201%208.4%202.2C20.4%2015.2%2014%2019.4%2012%2021Z'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: center;
	background-size: 12px 12px;
}

/*
 * The same character also ends the handwritten line ("thank you for supporting
 * my small business! ♥"). That one is mid-sentence, so it can't be swapped for
 * a drawn heart — this asks the browser for text presentation instead, which
 * Safari honours from iOS 17.4 and is silently ignored anywhere older.
 */
.is-style-script {
	font-variant-emoji: text;
}

.is-style-heart-divider::before,
.is-style-heart-divider::after {
	content: "";
	display: block;
	width: 42px;
	height: 1px;
	background: currentColor;
	opacity: 0.45;
}


/* --- Inline text colour --------------------------------------------------
 * Core implements "colour just these words" with a <mark>, and suppresses the
 * browser's default yellow highlight with an inline background-color of
 * transparent. wp_kses_post() strips that rgba() declaration whenever content
 * is saved by a user without unfiltered_html — which is what happens on any
 * programmatic install — and the hero headline comes out highlighted in
 * yellow. The class alone means "this mark carries a colour, not a highlight",
 * so suppress it here rather than depending on the inline style surviving.
 * ---------------------------------------------------------------------- */
mark.has-inline-color {
	background-color: transparent;
}


/* --- Block style: Handwritten (core/paragraph, core/heading) ------------- */
.is-style-script {
	font-family: var(--wp--preset--font-family--script);
	font-size: var(--wp--preset--font-size--lg);
	line-height: 1.4;
	color: var(--wp--preset--color--green);
}


/* --- Block style: Eyebrow Label (core/paragraph) ------------------------- */
.is-style-eyebrow {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 500;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	color: var(--wp--preset--color--green-light);
	margin-bottom: var(--wp--preset--spacing--xs);
}


/* --- Block style: Plain list (core/list) -------------------------------- */
.is-style-plain {
	list-style: none;
	padding-left: 0;
}


/* --- Decorative icons used in the value-prop and about sections ---------- */
.ht-icon {
	display: inline-flex;
	color: var(--wp--preset--color--green-light);
}

.ht-icon svg {
	display: block;
}


/* --- Restoring the line icons --------------------------------------------
 * The icons ship as inline <svg> inside wp:html blocks. wp_kses_post() strips
 * <svg> whenever content is saved by a user without unfiltered_html, which is
 * exactly what a programmatic install is — so on any real install the four
 * promises and the three trust points come out as empty spans, leaving holes
 * in the layout. Same class of failure as the <mark> above.
 *
 * Re-uploading the theme cannot fix it: the pages already exist in the
 * database with the SVG gone, and patterns are only read when a page is first
 * created. So the icons are painted back on from here, as mask images on the
 * span that did survive. A mask rather than a background so they still take
 * their colour from `currentColor`.
 *
 * `:empty` is the guard. On an install where the SVG survived, the span has a
 * child and none of this applies — no doubled-up icon, nothing to undo later.
 * ---------------------------------------------------------------------- */
.ht-icon:empty {
	display: inline-block;
	width: 34px;
	height: 34px;
	background-color: currentColor;
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-size: contain;
	mask-size: contain;
}

/* The four promises, in the order they appear in the band. */
.ht-divided > *:nth-child(1) .ht-icon:empty {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M12%2020.6C9.9%2019%203.8%2014.9%203.8%2010.4A4.1%204.1%200%200%201%2012%208.3a4.1%204.1%200%200%201%208.2%202.1c0%204.5-6.1%208.6-8.2%2010.2Z'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M12%2020.6C9.9%2019%203.8%2014.9%203.8%2010.4A4.1%204.1%200%200%201%2012%208.3a4.1%204.1%200%200%201%208.2%202.1c0%204.5-6.1%208.6-8.2%2010.2Z'/%3E%3C/svg%3E");
}

.ht-divided > *:nth-child(2) .ht-icon:empty {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='3'%20y='9'%20width='18'%20height='11.5'%20rx='1'/%3E%3Crect%20x='2'%20y='5.5'%20width='20'%20height='3.5'%20rx='1'/%3E%3Cpath%20d='M12%205.5v15'/%3E%3Cpath%20d='M12%205.5S10.8%202.5%208.8%202.5a2%202%200%200%200%200%204'/%3E%3Cpath%20d='M12%205.5s1.2-3%203.2-3a2%202%200%200%201%200%204'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='3'%20y='9'%20width='18'%20height='11.5'%20rx='1'/%3E%3Crect%20x='2'%20y='5.5'%20width='20'%20height='3.5'%20rx='1'/%3E%3Cpath%20d='M12%205.5v15'/%3E%3Cpath%20d='M12%205.5S10.8%202.5%208.8%202.5a2%202%200%200%200%200%204'/%3E%3Cpath%20d='M12%205.5s1.2-3%203.2-3a2%202%200%200%201%200%204'/%3E%3C/svg%3E");
}

.ht-divided > *:nth-child(3) .ht-icon:empty {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M20%204S19%2015%2012.5%2017.5C8%2019.2%204.5%2017%204.5%2017S5%206.5%2013%204.5c3.5-.9%207-.5%207-.5Z'/%3E%3Cpath%20d='M4%2020.5S6.5%2013%2012.5%209.5'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M20%204S19%2015%2012.5%2017.5C8%2019.2%204.5%2017%204.5%2017S5%206.5%2013%204.5c3.5-.9%207-.5%207-.5Z'/%3E%3Cpath%20d='M4%2020.5S6.5%2013%2012.5%209.5'/%3E%3C/svg%3E");
}

.ht-divided > *:nth-child(4) .ht-icon:empty {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M9%2017.5h5.5V6H6'/%3E%3Cpath%20d='M14.5%209.5H18l2.5%203v5H18'/%3E%3Ccircle%20cx='7'%20cy='17.5'%20r='2'/%3E%3Ccircle%20cx='16'%20cy='17.5'%20r='2'/%3E%3Cpath%20d='M1.5%208.5H5'/%3E%3Cpath%20d='M0.5%2012H4'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M9%2017.5h5.5V6H6'/%3E%3Cpath%20d='M14.5%209.5H18l2.5%203v5H18'/%3E%3Ccircle%20cx='7'%20cy='17.5'%20r='2'/%3E%3Ccircle%20cx='16'%20cy='17.5'%20r='2'/%3E%3Cpath%20d='M1.5%208.5H5'/%3E%3Cpath%20d='M0.5%2012H4'/%3E%3C/svg%3E");
}

/*
 * The three trust points beside "Hi, I'm so glad you're here!". They are the
 * only nowrap flex groups on the site with an icon inside them, so the child
 * combinator is what keeps this from reaching anything else.
 */
.is-nowrap:nth-child(1) > .ht-icon:empty {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M12%2020.6C9.9%2019%203.8%2014.9%203.8%2010.4A4.1%204.1%200%200%201%2012%208.3a4.1%204.1%200%200%201%208.2%202.1c0%204.5-6.1%208.6-8.2%2010.2Z'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M12%2020.6C9.9%2019%203.8%2014.9%203.8%2010.4A4.1%204.1%200%200%201%2012%208.3a4.1%204.1%200%200%201%208.2%202.1c0%204.5-6.1%208.6-8.2%2010.2Z'/%3E%3C/svg%3E");
}

.is-nowrap:nth-child(2) > .ht-icon:empty {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M5.5%208.5H15V15a4%204%200%200%201-4%204H9.5a4%204%200%200%201-4-4V8.5Z'/%3E%3Cpath%20d='M15%2010.5h1.6a2.4%202.4%200%200%201%200%204.8H15'/%3E%3Cpath%20d='M9%205.8c0-1.1%201-1.1%201-2.2'/%3E%3Cpath%20d='M12.4%205.8c0-1.1%201-1.1%201-2.2'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M5.5%208.5H15V15a4%204%200%200%201-4%204H9.5a4%204%200%200%201-4-4V8.5Z'/%3E%3Cpath%20d='M15%2010.5h1.6a2.4%202.4%200%200%201%200%204.8H15'/%3E%3Cpath%20d='M9%205.8c0-1.1%201-1.1%201-2.2'/%3E%3Cpath%20d='M12.4%205.8c0-1.1%201-1.1%201-2.2'/%3E%3C/svg%3E");
}

.is-nowrap:nth-child(3) > .ht-icon:empty {
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'/%3E%3Cpath%20d='M8.5%2014.2a4.3%204.3%200%200%200%207%200'/%3E%3Cpath%20d='M9%209.6h.01'/%3E%3Cpath%20d='M15%209.6h.01'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'/%3E%3Cpath%20d='M8.5%2014.2a4.3%204.3%200%200%200%207%200'/%3E%3Cpath%20d='M9%209.6h.01'/%3E%3Cpath%20d='M15%209.6h.01'/%3E%3C/svg%3E");
}

.is-nowrap > .ht-icon:empty {
	width: 26px;
	height: 26px;
	flex: 0 0 auto;
}


/* --- Value-prop strip: column count --------------------------------------
 * Four items, so the row should only ever be four across or two. The block's
 * `minimumColumnWidth` can't say that — it fits as many 14rem columns as will
 * go, which lands on *three* anywhere between about 800px and 1050px, leaving
 * the fourth promise orphaned on a second row against the left edge. That is
 * what reads as "not centred" on a narrow laptop; at full desktop width the
 * same grid is fine, which is why it hides so well.
 *
 * Pinning the count is the whole fix. 1080px is where four 14rem columns and
 * their gaps clear the wide container.
 * ---------------------------------------------------------------------- */
@media (min-width: 1080px) {
	.ht-divided.is-layout-grid {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

@media (max-width: 1079.98px) {
	.ht-divided.is-layout-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}


/* --- Value-prop strip: hairline dividers between items -------------------
 * Only in the four-across layout. In the 2x2 the third item starts a new row,
 * so a left border on "every item but the first" draws a stray rule down the
 * left edge of the second row.
 *
 * The rule is the cell's own left border, and the cells are held apart by a
 * 40px gap — so each rule sits at the near edge of its gutter rather than in
 * the middle. A reader reads a "column" as the space *between two rules*, and
 * that space is offset 20px from the cell inside it, so all four blocks of
 * text look pushed to one side. Measured: +20, +19.5, +19.5, -0.5. The content
 * was never off centre; the lines it was being judged against were.
 *
 * Centring each rule in its gutter fixes the middle two and leaves the outer
 * two at ±10, because those have a rule on one side only. Closing the gutter
 * instead makes each cell *be* the column a reader sees, which puts all four
 * on zero — the cells are 310px with 260px of content, so there is still 25px
 * of air between a rule and the words.
 *
 * Only the column gap goes; the row gap still separates the 2x2 below.
 * ---------------------------------------------------------------------- */
@media (min-width: 1080px) {
	.ht-divided.is-layout-grid {
		column-gap: 0;
	}

	.ht-divided > * + * {
		border-left: 1px solid rgba(79, 99, 55, 0.18);
	}
}


/* --- Value-prop strip on a phone -----------------------------------------
 * Four full-width rows came to 654px — most of a phone screen for four
 * one-line promises. Two across halves it. The cells are narrow at that point,
 * so the per-item side padding comes off to give the words the full column;
 * `!important` because the pattern sets that padding as an inline style, which
 * nothing in a stylesheet outranks.
 * ---------------------------------------------------------------------- */
@media (max-width: 599px) {
	.ht-divided.is-layout-grid {
		gap: var(--wp--preset--spacing--md);
	}

	.ht-divided > .wp-block-group {
		padding-left: 0 !important;
		padding-right: 0 !important;
	}

	.ht-divided .ht-icon svg {
		width: 28px;
		height: 28px;
	}
}


/* --- Occasion tiles on a phone -------------------------------------------
 * The row is tuned to land seven tiles across at full width, which means the
 * column minimum is small enough that a phone fits exactly one — seven
 * full-width tiles, about two and a half screens of scrolling before the page
 * gets going. Two across is the same information in a quarter of the height.
 *
 * An explicit column count rather than a smaller minimum: the minimum is what
 * pins the desktop row at seven, and a number small enough to give two columns
 * on a 360px phone is also small enough to let an eighth column appear at
 * 1240px, with an empty track on the end.
 *
 * Only phones: from 600px up the grid finds three tracks on its own, and four
 * by tablet width, which is the right shape for those screens.
 * ---------------------------------------------------------------------- */
@media (max-width: 599px) {
	.ht-occasions.is-layout-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}


/* --- The scroll-aware header ---------------------------------------------
 * Slides away going down, comes back the moment the reader scrolls up, and is
 * always present in the first screenful. The class is added by header.js, so
 * without JavaScript the header stays exactly as it was — an ordinary one that
 * scrolls off. Nothing here applies until the script has run.
 *
 * The z-index sits above the page but well below the mobile menu overlay,
 * which core puts at 100000.
 * ---------------------------------------------------------------------- */
.ht-header-sticky {
	position: sticky;
	top: 0;
	z-index: 500;
	transition: transform 300ms ease;
}

/*
 * The mobile menu is `position: fixed`, and so are the logo and Instagram icon
 * pinned into it — all three expect the viewport as their containing block.
 * Anything that gives the header a containing block of its own takes them with
 * it: the overlay stops covering the screen and collapses to a strip the size
 * of the header. Measured at 375x812: 375x84, sitting near the top.
 *
 * `will-change: transform` does that on its own, before any transform exists,
 * which is why it isn't here — the transition is smooth enough without it. The
 * slide itself does it too, so the header drops out of the way entirely while
 * the menu is open. It isn't needed then: the overlay covers the whole screen,
 * and header.js already leaves the header alone until the menu closes.
 */
.ht-header-sticky:has(.wp-block-navigation__responsive-container.is-menu-open) {
	position: static;
	transform: none;
}

/*
 * Logged-in, on a wide screen, the admin bar is fixed over the top of the
 * window — so a header stuck at zero has its first 32px hidden behind it and
 * comes out looking like the top padding has been cut off. The offset only
 * applies where the bar is actually fixed: at 782px and below core switches it
 * to absolute so it scrolls away, and holding the header down by 46px there
 * would leave a gap instead.
 *
 * The variable is core's own, and is undefined for logged-out visitors — who
 * are almost everyone — so they fall back to zero.
 */
@media (min-width: 783px) {
	.ht-header-sticky {
		top: var(--wp-admin--admin-bar--height, 0px);
	}
}

.ht-header-sticky.ht-header--hidden {
	transform: translateY(-100%);
}

/* A header that slides is decoration; honour a request to stop it moving. */
@media (prefers-reduced-motion: reduce) {
	.ht-header-sticky {
		transition: none;
	}

	.ht-header-sticky.ht-header--hidden {
		transform: none;
	}
}


/* --- Category + gift tiles -----------------------------------------------
 * `.ht-tile` is on the *grid*, not on each card — so `.ht-tile:hover img`
 * matched every image in the grid at once, and pointing at one basket lifted
 * all seven together. The child combinator moves the hover onto the individual
 * card, which is what the effect was always meant to say: this one.
 * ---------------------------------------------------------------------- */
.ht-tile img {
	transition: transform 400ms ease, opacity 400ms ease;
}

.ht-tile > *:hover img {
	transform: scale(1.03);
	opacity: 0.92;
}

.ht-tile figure {
	overflow: hidden;
	border-radius: 4px;
}


/* --- Hero band: the content box ------------------------------------------
 * WordPress 7.0.4 rewrote the rule that positions a cover block's inner
 * container. It used to be `.has-custom-content-position … { margin: 0 }` at two
 * classes; it is now five classes (core doubles `.has-custom-content-position`
 * to buy specificity) and it also sets `width: auto`.
 *
 * Both halves break this hero. `width: auto` on a flex item means shrink-to-fit,
 * and since the copy inside is sized as a percentage of the container the two
 * resolve against each other and settle at about 40% of the band — the headline
 * comes out four words tall in a narrow gutter. And the raised specificity
 * beats the theme's old three-class selector, so `margin-inline: auto` stopped
 * centring the box on wide monitors and the copy drifted away from the column
 * every other section lines up with.
 *
 * Matching core's weight fixes both. Doubling the class is core's own trick,
 * kept here so the two selectors are visibly the same shape.
 *
 * The cover zeroes its own padding, so the gutter belongs here too, at every
 * width — both hero queries below depend on this box.
 * ---------------------------------------------------------------------- */
.wp-block-cover.has-custom-content-position.has-custom-content-position.ht-hero
	> .wp-block-cover__inner-container {
	box-sizing: border-box;
	width: 100%;
	padding-inline: var(--wp--preset--spacing--md);
}


/* --- Hero band -----------------------------------------------------------
 * The banner is pre-blended: photograph on the right, dissolving leftwards
 * into a wash of its own colours. All this has to do is keep the words inside
 * the calm half and stop the picture growing taller than the band.
 * ---------------------------------------------------------------------- */
@media (min-width: 1100px) {
	/*
	 * The band's height tracks its width so the box stays at roughly the
	 * banner's own 2.2:1 — a fixed height would letterbox the picture on a
	 * wide monitor and cut the bottom off the basket. !important because the
	 * cover block writes min-height as an inline style.
	 */
	.ht-hero {
		min-height: clamp(500px, 45vw, 860px) !important;
	}

	/*
	 * Cap the box at the wide column and centre it, so the hero's words start
	 * where every other section's do. Same doubled class as above: core pins
	 * the container to `margin: 0` at five classes, and anything lighter loses.
	 */
	.wp-block-cover.has-custom-content-position.has-custom-content-position.ht-hero
		> .wp-block-cover__inner-container {
		max-width: calc(var(--wp--style--global--wide-size) + 2 * var(--wp--preset--spacing--md));
		margin-inline: auto;
	}

	/*
	 * Inset from the wide column rather than flush with it. Every other section
	 * starts its text at the column edge, but they all sit on a flat band —
	 * the hero's words sit on a picture, and starting them at the same 24px
	 * gutter leaves the headline hard against the left of the screen on a
	 * laptop. The extra step in gives the display-size type somewhere to
	 * breathe without moving it far enough to read as a different column.
	 */
	.ht-hero .ht-hero-copy {
		max-width: 44%;
		margin-inline: var(--wp--preset--spacing--xl) auto;
	}
}


/* --- Hero band, narrow ---------------------------------------------------
 * Under 1100px there's no room for words beside the basket, so the copy goes
 * over the picture instead. The blended banner is the wrong image for that —
 * its whole left half is wash, which on a phone crop is just an empty field —
 * so the narrow band uses `hero-scene.jpg`, the same scene without the blend,
 * and leans on a scrim for legibility rather than on the blur.
 *
 * The image arrives as a CSS background rather than the cover block's own
 * <img>, because swapping a src isn't something CSS can do. The cover's dim
 * layer, unused at these widths, becomes the scrim.
 *
 * Numbers here are set by measurement, not taste, and the number that matters
 * is the worst patch behind the words rather than the average: the elephant's
 * ears and the milestone plaque sit right under the headline, and they are far
 * darker than the wall around them. Over the bare photograph ink-soft body text
 * averages 1.5:1. At a flat 78% sand veil, with that text switched to
 * full-strength ink, the body copy holds 8.6:1 against its darkest patch and
 * the green in the headline — display-size, so judged against the 3:1
 * large-text threshold — holds 4:1. A lighter veil looks fine on average and
 * still loses the headline over the ears: at 58% that worst case is 2.8:1.
 * ---------------------------------------------------------------------- */
@media (max-width: 1099px) {
	.ht-hero {
		min-height: clamp(540px, 96vw, 760px) !important;
		align-items: flex-start;
		padding-block: var(--wp--preset--spacing--xl) !important;
		background-image: url(assets/images/hero-scene.jpg);
		background-size: cover;
		background-position: 82% 50%;
	}

	.wp-block-cover.ht-hero > img.wp-block-cover__image-background {
		display: none !important;
	}

	.wp-block-cover.ht-hero > .wp-block-cover__background {
		opacity: 1 !important;
		background: rgba(242, 236, 225, 0.78) !important;
	}

	/*
	 * ink-soft is right on a flat background and too faint on a photograph.
	 * !important because WordPress emits the preset colour classes with it.
	 */
	.ht-hero .ht-hero-copy .has-ink-soft-color {
		color: var(--wp--preset--color--ink) !important;
	}

	/*
	 * On a phone the copy runs the full width, so the basket sits centred
	 * under it rather than off to one side.
	 *
	 * The band is 540px tall at every phone size, which scales the picture to
	 * about 1190px wide and leaves a window roughly 31% of the frame. The
	 * basket is a shade wider than that window, so it can't be framed whole —
	 * 89% is the position that trims it evenly on both sides, which is what
	 * reads as centred. Arrived at by eye against the rendered page; the
	 * arithmetic gets you close but the basket's edges are soft.
	 */
	@media (max-width: 781px) {
		.ht-hero {
			background-position: 89% 50%;
		}
	}
}


/* --- About split, stacked ------------------------------------------------
 * Side by side the card is vertically centred, so the band's own height gives
 * it room. Once core stacks the columns at 782px that room disappears and the
 * card butts straight against the top of the sage band, which reads as a
 * mistake.
 *
 * Padding rather than margin, for two reasons: WordPress zeroes the first
 * child's margin with `:root :where(.is-layout-flow) > :first-child`, which at
 * (0,2,0) outweighs a plain class selector; and a top margin on the first
 * child would collapse out through the band and push the whole section down
 * instead of opening space inside it, leaving the gap in the wrong colour.
 * ---------------------------------------------------------------------- */
@media (max-width: 781px) {
	.ht-about-card {
		padding-top: var(--wp--preset--spacing--lg);
	}
}


/* --- When the menu stops fitting ------------------------------------------
 * Seven menu items, an Instagram icon and a call-to-action button need about
 * 1010px next to a 190px logo. Below that the row has to give, and the two ways
 * it can give are both worse than a hamburger: wrapping leaves a ragged second
 * line, stacking costs a fifth of a tablet screen before any content appears.
 *
 * So the overlay menu — which core turns on at 600px — is extended up to
 * 1120px. Core does that with a single media query, and undoing it is a matter
 * of restoring the two declarations it overrides. Nothing here touches the
 * overlay's own open state, which is driven by a class rather than a width.
 *
 * If menu items are added or removed, remeasure and move this number. It
 * appears three times below and in the header layout query that follows.
 * ---------------------------------------------------------------------- */
@media (min-width: 600px) and (max-width: 1119.98px) {
	.ht-header-nav .wp-block-navigation__responsive-container-open:not(.always-shown) {
		display: flex;
	}

	.ht-header-nav
		.wp-block-navigation__responsive-container:not(.hidden-by-default):not(.is-menu-open) {
		display: none;
	}
}


/* --- Compact header ------------------------------------------------------
 * Whenever the hamburger is showing there are only a few things left to place,
 * so they go on one row: menu, logo, Instagram, order.
 *
 * The hamburger and the button live inside .ht-header-nav, so `display:
 * contents` dissolves that wrapper and lets all of them sit as siblings in the
 * header row — otherwise the logo can't get between them.
 *
 * The logo takes the middle as a flex-1 slot and centres inside it, so it
 * stays centred regardless of how wide the button ends up, and shrinks rather
 * than overflowing on narrow phones.
 * ---------------------------------------------------------------------- */
@media (max-width: 1119.98px) {
	/*
	 * `.wp-block-group` is here to out-specify the container class WordPress
	 * generates for blockGap, which otherwise holds the gap at 24px. With four
	 * items in the row that's 72px of gap — enough to squeeze the logo down to
	 * a few pixels on a small phone.
	 */
	.ht-header-inner.wp-block-group {
		flex-wrap: nowrap;
		justify-content: space-between;
		gap: var(--wp--preset--spacing--sm);
	}

	/*
	 * Child selector, not a bare class: WordPress ships
	 * `body .is-layout-flex { display: flex }` at (0,1,1), which outranks a
	 * lone `.ht-header-nav` at (0,1,0). Without this the wrapper stays a flex
	 * box, takes the full row, and squeezes the logo to zero width.
	 */
	.ht-header-inner > .ht-header-nav {
		display: contents;
	}

	/*
	 * Menu, logo, Instagram, order — the logo takes the middle as a flexible
	 * slot and centres inside it, so it stays put whatever width the button
	 * ends up, and shrinks rather than overflowing on a narrow phone.
	 */
	.ht-header-inner .wp-block-navigation {
		order: 1;
		flex: 0 0 auto;
	}

	.ht-header-inner > .wp-block-site-logo {
		order: 2;
		flex: 1 1 auto;
		min-width: 0;
		display: flex;
		justify-content: center;
	}

	.ht-header-nav .ht-header-social {
		order: 3;
	}

	.ht-header-inner .wp-block-buttons {
		order: 4;
		flex: 0 0 auto;
	}
}


/* --- Phone header --------------------------------------------------------
 * Same row, tighter: the logo and the button both come down a size, and
 * Instagram comes out of the bar altogether.
 * ---------------------------------------------------------------------- */
@media (max-width: 600px) {
	header > .wp-block-group {
		padding-top: 0.85rem !important;
		padding-bottom: 0.85rem !important;
	}

	.ht-header-inner.wp-block-group {
		gap: 0.5rem;
	}

	.ht-header-inner > .wp-block-site-logo img {
		width: 150px;
		max-width: 100%;
		height: auto;
	}

	/*
	 * Instagram comes out of the bar on a phone — four items can't share that
	 * width without the logo suffering. It moves into the menu instead, which
	 * is where someone browsing on a phone goes looking anyway.
	 *
	 * The overlay is rendered by the Navigation block, so there's no way to put
	 * markup inside it from a template. Instead the same icon is hidden in the
	 * bar and pinned into the overlay when it opens — `:has()` on the wrapper
	 * is what lets a sibling react to the menu's state.
	 */
	.ht-header-nav .ht-header-social {
		display: none; /* scoped, or core's .wp-block-social-links wins the tie */
	}

	.ht-header-nav:has(.wp-block-navigation__responsive-container.is-menu-open)
		.ht-header-social {
		display: flex;
		position: fixed;
		bottom: var(--wp--preset--spacing--lg);
		left: var(--wp--preset--spacing--lg);
		z-index: 100001; /* the overlay itself sits at 100000 */
	}

	/*
	 * The logo joins it in the opposite corner. Same problem, same answer: the
	 * overlay is core's markup, so the logo is pinned over it rather than put
	 * inside it. The `:has()` sits on .ht-header-inner rather than
	 * .ht-header-nav because the logo is the nav's sibling, not its child.
	 *
	 * It's the header's own logo being moved, not a second copy — the overlay
	 * covers the bar it normally sits in, so nothing is left behind.
	 */
	.ht-header-inner:has(.wp-block-navigation__responsive-container.is-menu-open)
		> .wp-block-site-logo {
		position: fixed;
		right: var(--wp--preset--spacing--lg);
		bottom: var(--wp--preset--spacing--lg);
		left: auto;
		z-index: 100001;
		width: auto;
		flex: 0 0 auto;
		opacity: 0.85;
	}

	.ht-header-inner:has(.wp-block-navigation__responsive-container.is-menu-open)
		> .wp-block-site-logo img {
		width: 128px;
	}

	.ht-header-nav:has(.wp-block-navigation__responsive-container.is-menu-open)
		.ht-header-social .wp-block-social-link-anchor {
		width: 32px;
		height: 32px;
	}

	.ht-header-inner .wp-block-button__link {
		font-size: 0.6875rem;
		letter-spacing: 0.07em;
		padding: 0.75rem 0.8rem;
	}
}


/* --- Short phones --------------------------------------------------------
 * Seven links at display size come to 400px. Below about 700px of screen that
 * doesn't leave room for the close button above and the Instagram icon below,
 * so the list gives a step: the gap between links comes down, and the bands
 * reserved at top and bottom tighten with it.
 *
 * Remeasure if a page is added to the menu — the list grows by about 55px each
 * time, and this is the size where that runs out first.
 * ---------------------------------------------------------------------- */
@media (max-width: 600px) and (max-height: 700px) {
	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__container.wp-block-navigation {
		gap: var(--wp--preset--spacing--sm);
	}

	.wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation__responsive-container-content {
		padding-bottom: 5rem;
	}
}


/* --- The mobile menu overlay ---------------------------------------------
 * Core's overlay ships unstyled — white ground, black system text, hard left
 * alignment. On a phone this is the site's main navigation, so it shouldn't be
 * the one screen that looks like stock WordPress. Cream ground, the display
 * serif, centred like every other section.
 * ---------------------------------------------------------------------- */
/*
 * Core sets the white ground with
 * `.wp-block-navigation:not(.has-background) … .is-menu-open` — four classes,
 * so a plain `.is-menu-open` never wins. Matching its selector shape and
 * relying on load order beats it without resorting to !important.
 */
/*
 * The overlay's colours come from the Navigation block's own
 * overlayBackgroundColor / overlayTextColor attributes, set in parts/header.html
 * — not from here. Core paints the default white ground with a five-class
 * selector (`…:not(.disable-default-overlay-styles)`), which no reasonable
 * theme selector outranks; the block attributes are the supported way in.
 */
.wp-block-navigation__responsive-container.is-menu-open {
	padding: var(--wp--preset--spacing--lg) var(--wp--preset--spacing--md);
}

/*
 * Core nests two block-level wrappers between the overlay and the links, and
 * both shrink to their content — so `justify-content: center` was centring the
 * links inside a 336px box and stranding them in the top third. Letting the
 * whole chain stretch is what makes the centring mean anything.
 */
.wp-block-navigation__responsive-container.is-menu-open
	> .wp-block-navigation__responsive-close,
.wp-block-navigation__responsive-container.is-menu-open
	.wp-block-navigation__responsive-dialog {
	flex: 1;
	display: flex;
	flex-direction: column;
}

/*
 * The links read as a list from the top left, not as a centred block. Two
 * reasons beyond the look: a left edge gives the eye one line to travel down
 * instead of a ragged centre, and a top-aligned list ends at a predictable
 * point — which is what lets the Instagram icon share the screen without the
 * two colliding on a short phone.
 *
 * The bottom padding reserves the band the Instagram icon and logo sit in.
 *
 * Top padding is core's, not ours: it sets `padding-top: calc(24px + 2rem)` to
 * clear the close button, with a `:not()` in the selector that puts it out of
 * reach of any sane theme selector. Setting one here looks like it works and is
 * silently dropped. The list adds its own top spacing below instead.
 */
.wp-block-navigation__responsive-container.is-menu-open
	.wp-block-navigation__responsive-container-content {
	flex: 1;
	justify-content: flex-start;
	align-items: stretch;
	padding: 0 var(--wp--preset--spacing--lg) 6rem;
}

/*
 * `.wp-block-navigation__container.wp-block-navigation` gets to (0,4,0),
 * enough to carry over core's own alignment declaration.
 */
.wp-block-navigation__responsive-container.is-menu-open
	.wp-block-navigation__container.wp-block-navigation {
	gap: var(--wp--preset--spacing--md);
	align-items: flex-start;
	text-align: left;
	/* Core's own top padding stops level with the close button; this is what
	   puts daylight between the button and the first link. */
	margin-top: var(--wp--preset--spacing--sm);
}

/*
 * Each <li> is a *column* flex container, so justify-content works vertically,
 * not across. Letting the link fill its row and aligning the text inside it
 * sidesteps the axis question entirely.
 */
.wp-block-navigation__responsive-container.is-menu-open
	.wp-block-navigation-item {
	align-items: flex-start;
	width: 100%;
}

.wp-block-navigation__responsive-container.is-menu-open
	.wp-block-navigation-item__content {
	display: block;
	width: 100%;
	text-align: left;
	font-family: var(--wp--preset--font-family--display);
	font-size: var(--wp--preset--font-size--xl);
	font-weight: 500;
	line-height: 1.2;
	color: var(--wp--preset--color--ink);
}

.wp-block-navigation__responsive-container.is-menu-open
	.current-menu-item > .wp-block-navigation-item__content {
	color: var(--wp--preset--color--green);
	box-shadow: none;
}

/*
 * Core pins the close button hard into the corner with `top: 0; right: 0` and
 * relies on the dialog's padding to inset it — padding this theme sets to zero
 * so the link list can own the edges. So the inset is set here instead, which
 * also gives the tap target room to breathe away from the screen edge.
 */
.wp-block-navigation__responsive-container-close {
	color: var(--wp--preset--color--ink);
	top: var(--wp--preset--spacing--md);
	right: var(--wp--preset--spacing--md);
}

/* Focus moves into the menu when it opens, so the ring is seen often — it
   should be the brand green rather than the browser's default. */
.wp-block-navigation__responsive-container.is-menu-open
	.wp-block-navigation-item__content:focus-visible,
.wp-block-navigation__responsive-container-close:focus-visible {
	outline: 2px solid var(--wp--preset--color--green);
	outline-offset: 4px;
	border-radius: 2px;
}


/* --- Page title banner ---------------------------------------------------
 * Interior pages drop the title band straight onto the first content section,
 * and the palette is deliberately low-contrast, so the seam can disappear:
 * Reviews is sand on sand (identical), About is sand on sage (one shade apart).
 * A hairline gives the boundary somewhere to land without introducing a hard
 * colour step the design doesn't want. Same value as the value-prop dividers,
 * so it reads as one device rather than a new one.
 * ---------------------------------------------------------------------- */
.ht-page-header {
	border-bottom: 1px solid rgba(79, 99, 55, 0.18);
}


/* --- Instagram in the header --------------------------------------------
 * The studio posts far more often than the site changes, so the feed is the
 * liveliest thing it can point at — it belongs somewhere permanent rather than
 * only in the footer.
 *
 * Core renders the icon at 24px, which is well under the ~44px that guidance
 * asks of a touch target, so the padding here is doing accessibility work
 * rather than decoration. The link already carries a screen-reader name from
 * the block's `label` attribute.
 * ---------------------------------------------------------------------- */
.ht-header-social {
	margin: 0;
}

.ht-header-social .wp-block-social-link {
	margin: 0;
}

/*
 * The touch target is the *hit area*, not the layout box. Reserving a full
 * 44px of width here crushed the logo on a phone — at 320px it disappeared
 * altogether. So the anchor stays icon-sized in the layout and an inset
 * pseudo-element carries the target out to 44x44. The expansion matches the
 * 8px header gap exactly, so neighbouring controls touch but never overlap.
 */
.ht-header-social .wp-block-social-link-anchor {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	padding: 0;
	border-radius: 4px;
	transition: opacity 150ms ease;
}

.ht-header-social .wp-block-social-link-anchor::after {
	content: "";
	position: absolute;
	inset: -8px;
}

.ht-header-social .wp-block-social-link-anchor:hover {
	opacity: 0.65;
}

.ht-header-social .wp-block-social-link-anchor:focus-visible {
	outline: 2px solid var(--wp--preset--color--green);
	outline-offset: 2px;
}


/* --- Navigation: underline the page you're on --------------------------- */
.wp-block-navigation .wp-block-navigation-item.current-menu-item > a {
	box-shadow: inset 0 -1px 0 0 currentColor;
}


/* --- Anchored headings ---------------------------------------------------
 * The occasion tiles and the Browse Baskets jump links both land on a heading,
 * and the inquiry form returns to its own anchor after sending. Without a
 * scroll margin the target lands hard against the top of the window with no air
 * above it — and on Browse Baskets it would sit above the top edge of its own
 * coloured band, which reads as landing in the wrong section.
 * ---------------------------------------------------------------------- */
.wp-block-heading[id],
.wp-block-group[id] {
	scroll-margin-top: var(--wp--preset--spacing--xl);
}

@media (prefers-reduced-motion: no-preference) {
	html {
		scroll-behavior: smooth;
	}
}


/* --- Jump links ----------------------------------------------------------
 * Browse Baskets runs to eight bands, five or six screens of scrolling. A row
 * of pills at the top turns that into one click, and doubles as a contents
 * page: someone who came for a retirement basket can see straight away that
 * there is one.
 *
 * Outlined rather than filled, because they sit directly under the intro
 * paragraph and a row of eight solid green pills would out-shout the heading.
 * ---------------------------------------------------------------------- */
.ht-chips p {
	margin: 0;
}

.ht-chips a {
	display: inline-block;
	padding: 0.45rem 0.95rem;
	border: 1px solid rgba(79, 99, 55, 0.28);
	border-radius: 999px;
	color: var(--wp--preset--color--ink);
	letter-spacing: 0.05em;
	white-space: nowrap;
	transition: background-color 150ms ease, border-color 150ms ease,
		color 150ms ease;
}

.ht-chips a:hover,
.ht-chips a:focus-visible {
	background-color: var(--wp--preset--color--green);
	border-color: var(--wp--preset--color--green);
	color: var(--wp--preset--color--cream);
	text-decoration: none; /* the global link hover underlines; a filled pill doesn't want it */
}

.ht-chips a:focus-visible {
	outline: 2px solid var(--wp--preset--color--green);
	outline-offset: 2px;
}


/* --- "Often includes" ----------------------------------------------------
 * A list, marked up as one, set two-up rather than as a single stacked column.
 * Four items down the page under every one of eight headings reads as a form to
 * fill in; the same four in a small block read as a sample of what's inside,
 * which is what they are — and two-up keeps the block roughly as tall as the
 * photograph beside it instead of stretching the band.
 *
 * A grid, not a flowing run with separators: the items are phrases rather than
 * single words, so a run wraps unpredictably and a wrapped line can open with a
 * stranded separator. Fixed tracks wrap where they're told.
 * ---------------------------------------------------------------------- */
.ht-includes {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0.5rem var(--wp--preset--spacing--md);
	margin: 0;
	color: var(--wp--preset--color--ink-soft);
}

.ht-includes li {
	position: relative;
	margin: 0;
	padding-left: 1rem;
	line-height: 1.5;
}

.ht-includes li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.6em;
	width: 5px;
	height: 5px;
	border-radius: 50%;
	background-color: var(--wp--preset--color--green-light);
}

/*
 * Once the bands have stacked: one track, because two in a phone-width column
 * leaves three words per line — and the photograph goes first every time.
 *
 * The bands alternate photograph left and right, which on a phone becomes
 * alternately photograph-first and words-first as the columns stack in document
 * order. Side by side the alternation is the rhythm of the page; stacked it just
 * reads as inconsistent, so the picture is pulled to the top of every band.
 */
@media (max-width: 781px) {
	.ht-includes {
		grid-template-columns: minmax(0, 1fr);
	}

	.ht-browse > .ht-browse-media {
		order: -1;
	}
}


/* --- Back to top ---------------------------------------------------------
 * Browse Baskets only, and it lives in that page's own content rather than in
 * the footer template — nothing else on the site is long enough to need it, and
 * a button that follows you down every page is noise.
 *
 * It fades in once the page has actually been scrolled, using a scroll-driven
 * animation: no JavaScript, and no button hovering over the top of the page
 * offering to take you where you already are. Where scroll timelines aren't
 * supported yet — Firefox, at the time of writing — the animation simply never
 * applies and the chevron is visible throughout, which is the old behaviour and
 * perfectly usable.
 *
 * 44px square is the touch-target guidance, the same number the header's
 * Instagram icon is padded out to.
 * ---------------------------------------------------------------------- */
.ht-to-top {
	position: fixed;
	right: var(--wp--preset--spacing--md);
	bottom: var(--wp--preset--spacing--md);
	z-index: 50; /* above the page, far below the navigation overlay at 100000 */
	margin: 0;
	line-height: 0;
}

.ht-to-top a {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border: 1px solid rgba(79, 99, 55, 0.28);
	border-radius: 50%;
	background-color: var(--wp--preset--color--cream);
	box-shadow: var(--wp--preset--shadow--soft);
	color: var(--wp--preset--color--green);
	transition: background-color 150ms ease, color 150ms ease,
		border-color 150ms ease;
}

.ht-to-top a:hover {
	background-color: var(--wp--preset--color--green);
	border-color: var(--wp--preset--color--green);
	color: var(--wp--preset--color--cream);
	text-decoration: none;
}

.ht-to-top a:focus-visible {
	outline: 2px solid var(--wp--preset--color--green);
	outline-offset: 3px;
}

.ht-to-top svg {
	display: block;
}

/*
 * The chevron is another inline <svg> that wp_kses_post() strips on a
 * programmatic install, which leaves the circle drawn and nothing inside it.
 * Painted back on the same way as the line icons, but on a pseudo-element
 * rather than the link itself: the link is already carrying the cream circle
 * as its own background, and it needs to keep it.
 *
 * `currentColor` is what makes the hover still work — the link flips from
 * green to cream, and the arrow follows it.
 */
.ht-to-top a:empty::before {
	content: "";
	display: block;
	width: 18px;
	height: 18px;
	background-color: currentColor;
	-webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.6'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='m6%2014%206-6%206%206'/%3E%3C/svg%3E");
	mask-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='1.6'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='m6%2014%206-6%206%206'/%3E%3C/svg%3E");
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-position: center;
	mask-position: center;
	-webkit-mask-size: contain;
	mask-size: contain;
}

@supports (animation-timeline: scroll()) {
	/*
	 * On the wrapper, not the link. The wrapper is the fixed box; hiding only
	 * the link inside it leaves a 44px hole in the corner of the page that
	 * swallows clicks without showing anything.
	 */
	.ht-to-top {
		animation: ht-to-top-in linear both;
		animation-timeline: scroll(root block);
		animation-range: 20vh 60vh;
	}

	/*
	 * Opacity and visibility only — no movement, so there's nothing here that
	 * needs turning off for prefers-reduced-motion.
	 *
	 * The middle keyframe is load-bearing. Visibility doesn't interpolate like
	 * other properties: the moment either end of an interval is `visible`, it is
	 * visible for the whole interval. Animating straight from hidden to visible
	 * therefore shows the element at the first pixel of scroll — invisible at
	 * opacity 0, but still sitting there catching clicks in the corner. Holding
	 * `hidden` across the first 40% keeps that interval hidden at both ends, so
	 * it stays genuinely gone until the fade actually starts.
	 */
	@keyframes ht-to-top-in {
		0% {
			opacity: 0;
			visibility: hidden;
		}

		40% {
			opacity: 0;
			visibility: hidden;
		}

		100% {
			opacity: 1;
			visibility: visible;
		}
	}
}


/* --- The link that closes a section -------------------------------------
 * Each band on Browse Baskets ends with its own way in to the contact form,
 * because the moment someone decides is the moment they've finished reading
 * about their own occasion. Underlined by default rather than on hover: it's a
 * call to action sitting on its own, and it should look like one before the
 * pointer arrives.
 * ---------------------------------------------------------------------- */
.ht-more a {
	text-decoration: underline;
	text-underline-offset: 0.25em;
	text-decoration-thickness: 1px;
	text-decoration-color: rgba(79, 99, 55, 0.4);
}

.ht-more a:hover {
	text-decoration-color: currentColor;
}


/* --- The inquiry form ----------------------------------------------------
 * Rendered by the plugin's [happy_turtle_contact_form] shortcode, so it is
 * plain HTML rather than blocks and has to be styled here. Everything below
 * reads from the same theme.json presets as the rest of the site, so a palette
 * or type change carries through without touching this block.
 *
 * The submit button carries .wp-element-button, which is the class WordPress
 * generates the theme.json button styles against — so it matches every other
 * button on the site rather than being a lookalike that drifts.
 * ---------------------------------------------------------------------- */
.ht-form__field {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
	margin: 0 0 var(--wp--preset--spacing--md);
}

.ht-form label {
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--xs);
	font-weight: 500;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--wp--preset--color--green-light);
}

.ht-form input,
.ht-form select,
.ht-form textarea {
	width: 100%;
	box-sizing: border-box;
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--base);
	line-height: 1.5;
	color: var(--wp--preset--color--ink);
	background-color: var(--wp--preset--color--white);
	border: 1px solid rgba(79, 99, 55, 0.28);
	border-radius: 2px;
	padding: 0.7rem 0.85rem;
}

.ht-form textarea {
	resize: vertical;
}

.ht-form input:focus-visible,
.ht-form select:focus-visible,
.ht-form textarea:focus-visible {
	outline: 2px solid var(--wp--preset--color--green);
	outline-offset: 1px;
	border-color: transparent;
}

.ht-form__hint {
	font-size: var(--wp--preset--font-size--xs);
	line-height: 1.5;
	color: var(--wp--preset--color--ink-soft);
}

.ht-form__req {
	color: var(--wp--preset--color--green-light);
}

/*
 * The honeypot. Off-screen rather than display:none — some bots skip anything
 * that is genuinely hidden, and aria-hidden plus tabindex="-1" on the input
 * already keep it away from screen readers and the keyboard.
 */
.ht-form__pot {
	position: absolute !important;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

.ht-form__actions {
	margin: 0;
}

.ht-form button.wp-element-button {
	border: 0;
	cursor: pointer;
	font-family: var(--wp--preset--font-family--body);
}

.ht-form__note {
	border-left: 3px solid var(--wp--preset--color--green-light);
	padding: var(--wp--preset--spacing--sm) var(--wp--preset--spacing--md);
	margin-bottom: var(--wp--preset--spacing--md);
	font-size: var(--wp--preset--font-size--sm);
	background-color: var(--wp--preset--color--sage-mist);
}

.ht-form__note--bad {
	border-left-color: var(--wp--preset--color--ink);
	background-color: var(--wp--preset--color--sand);
}


/* --- Images ------------------------------------------------------------
 * Patterns ship pointing at assets/images/placeholder.svg. WordPress strips
 * <img> tags with no src entirely, so an empty image block collapses the
 * column to zero height — a real placeholder file is the only way to keep an
 * unfinished site looking intentional. The owner replaces them in the editor.
 * ---------------------------------------------------------------------- */
.wp-block-image img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: 4px;
}


/* --- Full-bleed sections sit flush ---------------------------------------
 * Each section pattern carries its own vertical padding, so the root blockGap
 * between two adjacent full-width bands shows up as an unwanted stripe of page
 * background between them. Only collapse the gap where two full-bleed sections
 * actually meet — normal content spacing inside a page is untouched.
 * ---------------------------------------------------------------------- */
.wp-block-post-content > .alignfull + .alignfull,
.wp-block-post-content > .alignfull:first-child,
main > .alignfull + .alignfull {
	margin-block-start: 0;
}

.wp-block-post-content > .alignfull:last-child,
main > .alignfull:last-child {
	margin-block-end: 0;
}

/* Header/footer template parts meet the page body flush too. */
.wp-site-blocks > header + main,
.wp-site-blocks > main + footer {
	margin-block-start: 0;
}

/*
 * page.html wraps the content in a padded group so ordinary text pages get
 * breathing room. Pages assembled entirely from full-bleed patterns don't want
 * it — that padding shows as a stripe of page background above the first band.
 * Drop it only when a full-bleed section is actually the first/last thing in
 * the content. !important because block spacing is emitted as an inline style.
 */
main > *:has(> .wp-block-post-content > .alignfull:first-child) {
	padding-top: 0 !important;
	margin-block-start: 0 !important;
}

main > *:has(> .wp-block-post-content > .alignfull:last-child) {
	padding-bottom: 0 !important;
	margin-block-end: 0 !important;
}


/* --- Editor-only nicety: give locked patterns a little breathing room ---- */
.editor-styles-wrapper .is-style-heart-divider::before,
.editor-styles-wrapper .is-style-heart-divider::after {
	flex: 0 0 42px;
}


/* --- Sections rising in ---------------------------------------------------
 * Each section fades up as it comes into view. Scroll-driven, so there is no
 * JavaScript: the browser ties the animation to the section's own position in
 * the scrollport rather than to a timer or an observer, which means it cannot
 * fall out of step with the scroll and cannot leave a section stuck invisible
 * if a script fails to load.
 *
 * Both guards matter, and in this order:
 *
 * `prefers-reduced-motion: no-preference` — for anyone who has asked their
 * system for less movement, the whole thing is simply never declared. This is
 * motion for its own sake, so it is the first thing that should go.
 *
 * `@supports (animation-timeline: view())` — where scroll-driven animations
 * aren't supported yet, nothing is declared either, and every section is
 * plainly visible. That is the same bargain as the back-to-top chevron above:
 * the enhancement is invisible in its absence, never a blank page.
 *
 * Deliberately small: 1.25rem of travel and a fade. The site sells calm, and a
 * section that flies in from the side or scales up reads as a template.
 * ---------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
	@supports (animation-timeline: view()) {
		/*
		 * The page's own sections, and only those.
		 *
		 * Both exclusions are about the back-to-top chevron, and both are needed.
		 * A transform makes an element the containing block for any fixed
		 * descendant, so an animated section stops its chevron being fixed and
		 * lets it scroll away with the band — for as long as the transform lasts,
		 * which is exactly while the animation is running. So the chevron is
		 * excluded (`:not(.ht-to-top)`), and so is whatever section it lives
		 * inside (`:not(:has(.ht-to-top))`) — on Browse Baskets it sits inside
		 * the first band, not beside it.
		 *
		 * Anything fixed added later needs the same pair of exemptions. Losing one
		 * band's animation is a fair price; a chevron that slides up the page as
		 * you scroll is a bug anyone would notice.
		 */
		.wp-block-post-content > *:not(.ht-to-top):not(:has(.ht-to-top)) {
			animation: ht-rise-in linear both;
			animation-timeline: view();

			/*
			 * Starts as the section's top edge appears and finishes about a third
			 * of the way through it arriving, so a band is settled well before it
			 * is centred rather than still moving under the reader's eye. The
			 * range is a proportion of the section's own height, which is why a
			 * tall band takes longer than a short one — they finish at about the
			 * same place on screen, which is what actually looks even.
			 */
			animation-range: entry 0% entry 35%;
		}

		@keyframes ht-rise-in {
			from {
				opacity: 0;
				transform: translateY(1.25rem);
			}

			to {
				opacity: 1;
				transform: none;
			}
		}
	}
}
