/**
 * TCB Global
 *
 * Site-wide rules that apply to every TCB build, loaded on every front-end
 * page. Unlike every other stylesheet in this plugin, nothing here is scoped to
 * a widget — so keep it small, and only add a rule when it is genuinely true of
 * every client site.
 */

/* --------------------------------------------------------------------------
   Collapse containers left empty by unset dynamic content
   --------------------------------------------------------------------------
   Every TCB build binds its sections to ACF, and not every page fills every
   field. Elementor already drops a widget whose dynamic tag resolves to
   nothing — the heading and the text editor genuinely leave the DOM — but it
   keeps the container that held them:

       <div class="elementor-element elementor-element-xxxxxxx e-con e-child">
               </div>

   That wrapper is still a flex child, so it still consumes the parent's
   row-gap. Two empty blocks in a column produce a hole the height of two gaps
   with nothing in it, and it reads as a broken layout rather than as absent
   copy.

   Notes on the selector, each of which is load-bearing:

   - `:has(*)` tests for ELEMENT children only, so a wrapper holding nothing but
     Elementor's whitespace matches `:not(:has(*))`. Plain `:empty` does not
     work — whitespace text nodes defeat it.

   - `[data-settings]` is excluded. A container with a background emits
     `data-settings='{"background_background":"classic"}'`, and containers like
     that are decorative and meant to render empty. Same for `.e-con-inner`,
     which is laid out by its parent.

   - `.e-child` and `!important` are not belt-and-braces. Elementor's
     `widget-nested-tabs.css` ships `.e-con:first-child{display:flex}` at the
     same specificity (0,2,0), so without them a first-child tab pane would win
     on source order and the tabs would break.

   - `body:not(.elementor-editor-active)` keeps this off inside the editor
     preview. An empty container has to stay visible there or it cannot be
     selected and nothing can be dropped into it.
   -------------------------------------------------------------------------- */
body:not(.elementor-editor-active) .e-con.e-child:not(.e-con-inner):not([data-settings]):not(:has(*)){
	display: none !important;
}

/* --------------------------------------------------------------------------
   Keep injected links inline inside icon lists
   --------------------------------------------------------------------------
   Elementor styles any anchor in a list item as a flex box:

       .elementor-widget .elementor-icon-list-item a { display: flex }

   That is correct for its own markup, where the anchor wraps the whole item
   (icon + text) when the item is linked. It is wrong for an anchor injected
   *inside* the text — which is what auto-internal-linking plugins do when a
   list item happens to contain one of their keywords. The link becomes
   block-level and the item breaks onto two lines: "Surrey" on one, "BC" on the
   next.

   Scoped to anchors inside .elementor-icon-list-text so Elementor's own
   full-item links, which are siblings of that element rather than children,
   keep their flex layout.
   -------------------------------------------------------------------------- */
.elementor-widget .elementor-icon-list-item .elementor-icon-list-text a{
	display: inline;
	align-items: initial;
}

/* --------------------------------------------------------------------------
   Stop post-content prose spacing leaking into embedded Elementor templates
   --------------------------------------------------------------------------
   The child theme spaces prose with `.article-content p { margin-top: 16px;
   margin-bottom: 16px }`. That is right for the body of a post and wrong for a
   template mounted inside it via [elementor-template] — a form panel, a CTA —
   because those widgets already carry their own spacing, so the first
   paragraph in a widget gains 16px that the same template does not have
   anywhere else on the site.

   The existing per-site rule cancels the bottom margin with a :last-child
   selector but has no counterpart for the top, which is the half that shows:
   it lands between a heading and the paragraph under it.

   Specificity here is (0,3,0) against the theme's (0,1,1), so no !important
   is needed. Scoped to the first child so paragraphs *within* a text editor
   keep the spacing between them.
   -------------------------------------------------------------------------- */
.article-content .elementor-widget-container > *:first-child{
	margin-top: 0;
}

/* ---------------------------------------------------------------------------
 * Elementor inside a theme: keep paragraph rhythm out of the theme's hands.
 *
 * Themes style paragraphs with a bare element selector, so their rules reach
 * straight into Elementor widgets. Betheme is typical: `p { margin: 0 0 15px }`
 * in be.css, plus a per-site `p { margin-bottom: 27px }` emitted through
 * mfn-custom-inline-css. Both are (0,0,1), so they win against Elementor's
 * defaults and leave arbitrary trailing space under every text widget —
 * spacing the section's own gap and padding controls never asked for.
 *
 * Scoping to .elementor-element reaches (0,1,1), which outranks a bare `p`
 * regardless of load order, so no !important is needed. Inter-paragraph
 * spacing becomes proportional (1em) instead of a fixed pixel value, and the
 * final paragraph carries none — the container's gap owns that.
 * ------------------------------------------------------------------------- */
.elementor-element p{
    margin: 0 0 1em;
}

.elementor-element p:last-child{
    margin-bottom: 0;
}

/* --- Case study card ------------------------------------------------------
 * The logo, pills and impact line are emitted by shortcodes in
 * productivity/case-study.php, so their structural styling belongs here rather
 * than as per-element CSS on one loop item. Everything a design would want to
 * change per site — colour, type, spacing — stays on the Elementor controls and
 * the global kit.
 */

/* A fixed 168x108 box, and the logo contained inside it. Client logos arrive as
 * wide wordmarks, square badges and tall stacks; sizing by height alone lets a
 * wide one overrun the card and leaves a square one looking tiny next to it.
 * The box gives every card the same optical weight whatever the file is. */
.tcb-case-logo-box {
	display: flex;
	align-items: center;
	justify-content: flex-start;
	width: 100%;
	max-width: 168px;
	aspect-ratio: 168 / 108;
}

.tcb-case-logo {
	max-width: 100%;
	max-height: 100%;
	width: auto;
	height: auto;
	object-fit: contain;
}

/* The no-logo fallback sits in the same box so the header row does not change
 * height when a client has no file on record. */
.tcb-case-logo--text {
	display: block;
	text-wrap: balance;
}

.tcb-case-pills {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 10px;
}

/* Plain text, not chips. Three bordered pills in a card that already carries a
 * figure, a photo, a headline and an impact line is one bordered thing too many
 * — the separator does the same job for none of the weight. */
.tcb-case-pill + .tcb-case-pill::before {
	content: "\00b7";
	margin-right: 10px;
}

/* The logo lines up with the block beside it — company name plus the industry
 * and city line — rather than sitting in a box of its own. Height comes from
 * the row, width follows the file.
 *
 * height:100% only resolves against an ancestor that has a definite height, and
 * Elementor puts two auto-height wrappers between the flex row and the logo:
 * .elementor-widget-container, and the <p> the text editor wraps a shortcode in.
 * Both have to pass the height down or the logo falls back to its own size,
 * which is what left it shorter than the two lines beside it. Keyed off our own
 * class with :has(). Every branch is a direct-child match on purpose: the
 * descendant form matched any wrapper with a logo anywhere beneath it, which
 * included the widget mounting the whole Case Studies section and pushed that
 * section 31px off centre on mobile. */
.elementor-widget:has(> .elementor-widget-container > .tcb-case-logo-box--fit) > .elementor-widget-container,
.elementor-widget:has(> .elementor-widget-container > p > .tcb-case-logo-box--fit) > .elementor-widget-container,
.elementor-widget-container:has(> .tcb-case-logo-box--fit),
.elementor-widget-container:has(> p > .tcb-case-logo-box--fit),
p:has(> .tcb-case-logo-box--fit) {
	height: 100%;
	margin: 0;
	display: flex;
	align-items: center;
	justify-content: flex-end;
}

.tcb-case-logo-box--fit {
	height: 100%;
	width: auto;
	max-width: 100%;
	aspect-ratio: auto;
	justify-content: flex-end;
	align-items: center;
}

.tcb-case-logo-box--fit .tcb-case-logo {
	height: 100%;
	max-height: 100%;
	width: auto;
	max-width: 100%;
	object-fit: contain;
	object-position: right center;
}

/* The whole card is the link. The anchor covers it rather than wrapping it, so
 * the card's own markup stays a plain container and nothing inside has to be
 * re-nested. z-index keeps it above the card's content but below anything that
 * genuinely needs its own interaction. */
/* The widget that holds the link must not be a box of its own.
 *
 * Elementor widgets are positioned, so inset:0 resolved against the widget —
 * a zero-height div at the bottom of the card — rather than against the card.
 * The link became a strip nobody could hit, and the card still reserved a flex
 * gap for the empty wrapper. display:contents removes the wrapper from layout
 * entirely, so the anchor's containing block is the card and nothing is spent
 * on the widget itself. */
.elementor-widget:has(> .tcb-case-stretch),
.elementor-widget:has(> .elementor-widget-container > .tcb-case-stretch) {
	display: contents;
}

.elementor-element.tcb-case-card.tcb-case-card {
	position: relative;
}

.tcb-case-stretch {
	position: absolute;
	inset: 0;
	z-index: 2;
	border-radius: inherit;
}

.tcb-case-stretch__sr {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* Service pills sitting on the cover image. Translucent white so they read on
 * a photo or a screenshot without a solid block fighting the picture. */
.tcb-case-cover .tcb-case-pills {
	gap: 6px;
}

/* No font-size here: the pills inherit whatever typography the widget is set
 * to, so the size is an Elementor control rather than a number in a stylesheet
 * nobody opens. */
.tcb-case-cover .tcb-case-pill {
	background: rgba(255, 255, 255, .94);
	color: #121212;
	padding: 6px 13px;
	border-radius: 100px;
	line-height: 1.3;
	white-space: nowrap;
}

/* The separator belongs to the plain-text version of these pills, not to chips
 * that already read as separate objects. */
.tcb-case-cover .tcb-case-pill + .tcb-case-pill::before {
	content: none;
	margin: 0;
}

/* Decoration: the stretched anchor is what people actually click. */
.tcb-case-arrow {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 34px;
	height: 34px;
	border-radius: 50%;
	background: #121212;
	color: #FFFFFF;
	font-size: 15px;
	line-height: 1;
}

/* --- Case study card: hover ------------------------------------------------
 * The whole card is a link, so the pointer has to be told. The card itself does
 * not move: lifting it shifted everything inside, including the picture, which
 * read as the image box changing size. Only the image zooms, and only inside
 * its own frame.
 */
.elementor-element.tcb-case-card.tcb-case-card {
	cursor: pointer;
}

/* Keyboard users get a real ring — there is no motion standing in for focus. */
.tcb-case-stretch:focus-visible {
	outline: 2px solid #121212;
	outline-offset: 3px;
}

.tcb-case-cover.tcb-case-cover {
	border-radius: 8px;
}

/* Clip at the image widget, not at the container around it.
 *
 * The container is taller than the picture — it also holds the pills and the
 * arrow — so clipping there let a scaled image grow past the top and bottom of
 * its own box before anything cut it off. The widget's own wrapper is exactly
 * the size of the image, so the zoom happens inside the frame and the box never
 * changes height. */
/* Elementor 4 renders the <img> as a direct child of the widget — there is no
 * .elementor-widget-container around it any more. Clipping the wrapper that
 * older versions emit matched nothing here, which is why the scaled image kept
 * pushing its box open. Both shapes are covered so this holds whichever version
 * a site is on. */
.tcb-case-cover .elementor-widget-image,
.tcb-case-cover .elementor-widget-image > .elementor-widget-container {
	overflow: hidden;
	border-radius: 8px;
	line-height: 0;
}

.tcb-case-cover .elementor-widget-image img {
	display: block;
	transition: transform .5s cubic-bezier(.2, .7, .3, 1);
	will-change: transform;
}

.tcb-case-card:hover .tcb-case-cover .elementor-widget-image img,
.tcb-case-card:has(.tcb-case-stretch:focus-visible) .tcb-case-cover .elementor-widget-image img {
	transform: scale(1.06);
}

/* The arrow picks up the accent, so the thing that says "go" is the thing that
 * changes. */
.tcb-case-arrow {
	transition: background-color .25s ease, transform .25s cubic-bezier(.2, .7, .3, 1);
}

.tcb-case-card:hover .tcb-case-arrow {
	background: #E84C40;
	transform: scale(1.18);
}

@media (prefers-reduced-motion: reduce) {
	.tcb-case-cover .elementor-widget-image img,
	.tcb-case-arrow {
		transition: none;
	}

	.tcb-case-card:hover .tcb-case-cover .elementor-widget-image img,
	.tcb-case-card:hover .tcb-case-arrow {
		transform: none;
	}
}

/* --- TCB Comparison Cards --------------------------------------------------
 * Structure only. Colour, type, padding and radii are all controls, because a
 * comparison on a plumber's site should not have to look like one on ours.
 */
.tcb-cmp {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	align-items: stretch;
}

@media (max-width: 767px) {
	.tcb-cmp {
		grid-template-columns: minmax(0, 1fr);
	}
}

/* Swipe row instead of a stack.
 *
 * The row breaks out of the boxed column and spans the viewport, because a
 * scroll container only as wide as the content clips the next card at the
 * content edge — which reads as a broken layout rather than as "there is more
 * to the right". The padding puts the first card back on the content grid, so
 * nothing looks out of line with the sections above it.
 *
 * calc(50vw - 50%) is the page gutter without having to know it: percentages
 * still resolve against the original boxed parent after the width change.
 *
 * Cards snap centred rather than to the start, so part of the next card is
 * always visible. Snapped to the start, moving to the second card pushes the
 * first one entirely off screen and the row stops looking like a row.
 */
@media (max-width: 1024px) {
	.tcb-cmp--swipe-tablet {
		--tcb-bleed: max(0px, calc((100vw - 656px) / 2));
		display: flex;
		width: 100vw;
		margin-inline: calc(-1 * var(--tcb-bleed));
		padding-inline-start: var(--tcb-bleed);
		scroll-padding-inline: var(--tcb-bleed);
		overflow-x: auto;
		/* A scroll container scrolls both ways unless told otherwise, which let
		 * the cards be dragged up and down inside the row. The shadow room
		 * below is padding, so it survives the clip. */
		/* hidden, not clip: with overflow-x set to auto the spec forces clip
		 * back to hidden anyway, so the box is a scroll container on both
		 * axes no matter what. The vertical scroll area is zero, which is
		 * what actually stops it moving. */
		overflow-y: hidden;
		/* A scroll container clips vertically as well, which cut the cards'
		 * shadows off top and bottom. The padding gives them room inside the
		 * scrollport and the negative margin takes it back off the layout. */
		padding-block: var(--tcb-swipe-shadow-room, 18px);
		margin-block: calc(-1 * var(--tcb-swipe-shadow-room, 18px));
		scroll-snap-type: x mandatory;
		/* x only. Containing both axes stops a vertical drag inside the row
		 * from chaining to the page, and since the row has no vertical scroll
		 * room the gesture just rubber-bands the cards instead — worst on a
		 * row taller than the screen, where the finger never leaves it. */
		overscroll-behavior-x: contain;
		scrollbar-width: none;
	}

	.tcb-cmp--swipe-tablet::-webkit-scrollbar {
		display: none;
	}

	/* Stands in for the trailing gutter that used to be padding, so the last
	 * card still clears the screen edge when the row is scrolled to the end. */
	.tcb-cmp--swipe-tablet::after {
		content: '';
		flex: 0 0 calc((var(--tcb-bleed) + var(--tcb-swipe-peek, 70px)) / 2);
	}

	.tcb-cmp--swipe-tablet > .tcb-cmp__card {
		/* Sized off the viewport rather than as a percentage of the row, so the
		 * strip of the next card stays the same width on a 360 and on a 430. */
		flex: 0 0 var(--tcb-cmp-swipe-width, calc(100vw - var(--tcb-bleed) - var(--tcb-cmp-gap, 24px) - var(--tcb-cmp-peek, 70px)));
		scroll-snap-align: center;
		/* The peeking card is the only thing telling the reader the row moves, and
		 * the Coding Bull card is white on a white page — a 1px border is not
		 * enough to read as a card at 40-odd pixels wide. The shadow gives the
		 * edge something to sit against. Off in the grid layout, where each card
		 * is fully visible and needs no help. */
		box-shadow: var(--tcb-cmp-swipe-shadow, 0 2px 16px rgba(0, 0, 0, .08));
	}
}

@media (max-width: 767px) {
	/* Both classes, not just the mobile one. "Tablet and mobile" keeps the
	 * --swipe-tablet class down here too, and without this it kept the 656px
	 * gutter — which is zero on a phone, so the card ran off the screen. */
	.tcb-cmp--swipe-tablet,
	.tcb-cmp--swipe-mobile {
		--tcb-bleed: max(0px, calc((100vw - 328px) / 2));
		display: flex;
		width: 100vw;
		margin-inline: calc(-1 * var(--tcb-bleed));
		padding-inline-start: var(--tcb-bleed);
		scroll-padding-inline: var(--tcb-bleed);
		overflow-x: auto;
		/* A scroll container scrolls both ways unless told otherwise, which let
		 * the cards be dragged up and down inside the row. The shadow room
		 * below is padding, so it survives the clip. */
		/* hidden, not clip: with overflow-x set to auto the spec forces clip
		 * back to hidden anyway, so the box is a scroll container on both
		 * axes no matter what. The vertical scroll area is zero, which is
		 * what actually stops it moving. */
		overflow-y: hidden;
		/* A scroll container clips vertically as well, which cut the cards'
		 * shadows off top and bottom. The padding gives them room inside the
		 * scrollport and the negative margin takes it back off the layout. */
		padding-block: var(--tcb-swipe-shadow-room, 18px);
		margin-block: calc(-1 * var(--tcb-swipe-shadow-room, 18px));
		scroll-snap-type: x mandatory;
		/* x only. Containing both axes stops a vertical drag inside the row
		 * from chaining to the page, and since the row has no vertical scroll
		 * room the gesture just rubber-bands the cards instead — worst on a
		 * row taller than the screen, where the finger never leaves it. */
		overscroll-behavior-x: contain;
		scrollbar-width: none;
	}

	.tcb-cmp--swipe-mobile::-webkit-scrollbar {
		display: none;
	}

	/* Stands in for the trailing gutter that used to be padding, so the last
	 * card still clears the screen edge when the row is scrolled to the end. */
	.tcb-cmp--swipe-mobile::after {
		content: '';
		flex: 0 0 calc((var(--tcb-bleed) + var(--tcb-swipe-peek, 70px)) / 2);
	}

	.tcb-cmp--swipe-mobile > .tcb-cmp__card {
		/* Sized off the viewport rather than as a percentage of the row, so the
		 * strip of the next card stays the same width on a 360 and on a 430. */
		flex: 0 0 var(--tcb-cmp-swipe-width, calc(100vw - var(--tcb-bleed) - var(--tcb-cmp-gap, 24px) - var(--tcb-cmp-peek, 70px)));
		scroll-snap-align: center;
		/* The peeking card is the only thing telling the reader the row moves, and
		 * the Coding Bull card is white on a white page — a 1px border is not
		 * enough to read as a card at 40-odd pixels wide. The shadow gives the
		 * edge something to sit against. Off in the grid layout, where each card
		 * is fully visible and needs no help. */
		box-shadow: var(--tcb-cmp-swipe-shadow, 0 2px 16px rgba(0, 0, 0, .08));
	}
}

/* The button sits at the foot whatever each column's list length, so the two
 * cards end level even when one side runs a line longer. */
.tcb-cmp__card {
	display: flex;
	flex-direction: column;
	box-sizing: border-box;
}

.tcb-cmp__head {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
}

.tcb-cmp__title {
	margin: 0;
}

.tcb-cmp__badge {
	display: inline-flex;
	align-items: center;
	border-radius: 100px;
	padding: 4px 10px;
	line-height: 1.2;
	white-space: nowrap;
}

.tcb-cmp__intro {
	margin: 10px 0 0;
	text-wrap: pretty;
}

.tcb-cmp__points {
	display: flex;
	flex-direction: column;
	margin: 22px 0 0;
	padding: 0;
	list-style: none;
}

.tcb-cmp__row {
	display: flex;
	align-items: flex-start;
	gap: 12px;
}

/* Fixed box so a tick and a cross occupy identical space and the two columns
 * stay aligned line for line. */
.tcb-cmp__mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	line-height: 1;
	/* Nudged onto the first line's optical centre rather than its box top.
	 * Overridable per instance by the widget's Adjust Vertical Position. */
	margin-top: var(--tcb-cmp-mark-offset, .15em);
}

.tcb-cmp__point {
	min-width: 0;
	text-wrap: pretty;
}


/* The internal-link plugin injects jargon links into the points, on purpose —
 * a reader who does not know a term can go and read about it. Styled as a
 * glossary term rather than a link, so it does not fight the card: the point
 * keeps its own colour and only the dotted underline marks it. */
.tcb-cmp__point .ilink {
	color: inherit;
	text-decoration: underline;
	text-decoration-style: dotted;
	text-decoration-thickness: 1px;
	text-underline-offset: 3px;
	transition: text-decoration-color .2s ease;
}

.tcb-cmp__point .ilink:hover,
.tcb-cmp__point .ilink:focus-visible {
	text-decoration-style: solid;
}

/* margin-top:auto is what pins the button to the bottom of a flex column. */
.tcb-cmp__cta {
	display: block;
	margin-top: auto;
	padding-top: 14px;
	text-align: center;
	text-decoration: none;
	transition: opacity .2s ease;
}

.tcb-cmp__cta:hover {
	opacity: .88;
}

/* The points block needs air above the button when the list is short enough
 * that margin-top:auto has nothing to push against. */
.tcb-cmp__points + .tcb-cmp__cta {
	margin-top: 24px;
}

/* Reviews section — removes itself when there is nothing to show.
 *
 * The section leads with a heading and an intro paragraph, so a page whose
 * service or industry has no reviews yet would render the promise with an
 * empty space under it. Elementor also leaves a bare nothing-found div behind
 * for the video row, which keeps the container from collapsing on its own.
 * Neither a video card nor a written card means there is nothing to say. */
.tcb-reviews-section:not(:has(.e-loop-item)):not(:has(.tcb-review_post_list-item)) {
	display: none;
}

/* Same reasoning one level down: the written-review widget still emits its
 * wrapper and inline script when the query matched nothing, which left a strip
 * of empty space under the video row on a page whose reviews are all video. */
.elementor-widget-tcb-review-post-list:not(:has(.tcb-review_post_list-item)) {
	display: none;
}

/* And the video row above it. Elementor leaves a nothing-found div behind
 * rather than collapsing the widget, so an empty grid still held its height
 * and its top margin — a screen of white between the intro and the written
 * reviews on any page with no video to show. Scoped to this section, because
 * an empty loop grid elsewhere may legitimately want its message. */
.tcb-reviews-section .elementor-widget-loop-grid:not(:has(.e-loop-item)) {
	display: none;
}

/* FAQ section — removes itself when there are no questions.
 *
 * Same shape as the reviews rules above, and the same reason. The section
 * leads with an eyebrow, a heading and an intro, all bound to ACF *with
 * fallback text*, so all three print on a page that has no FAQ rows at all —
 * a heading promising answers over a blank space. The widget now renders
 * nothing when its repeater is empty, so "no `.tcb-faq-container`" says
 * exactly "no questions to show".
 *
 * Add the class `tcb-faq-section` to the container holding the heading and
 * the FAQs widget.
 *
 * Why this is CSS rather than the `tcb-hide-if-empty` container filter: for a
 * container, `elementor/frontend/container/should_render` fires around the
 * OPENING wrapper, before any child widget renders. Verified by tracing a
 * render on Elementor 4.1.1:
 *
 *     before_render  container 5c65986
 *     after_render   container 5c65986     <- both, then children
 *     before_render  widget    ef52226
 *     ...
 *
 * So nothing a widget reports during its own render can reach that decision,
 * and there is no later hook for a container. The filter mechanism only works
 * for legacy `section` elements, which do buffer their children.
 *
 * Why here and not in tcb-faqs.css: that file is the widget's own
 * get_style_depends(), and this rule has to apply on pages where the widget
 * produces no output. tcb-global.css is enqueued on every page. */
.tcb-faq-section:not(:has(.tcb-faq-container)) {
	display: none;
}

/* ---------------------------------------------------------------------------
 * A linked heading keeps its weight
 *
 * Elementor's heading widget passes colour, font-size and line-height into the
 * anchor of a linked heading, and stops there. The kit styles every `a` with the
 * body-text global, and a rule that matches the anchor directly beats anything
 * the heading merely passes down — so a heading set to 700 renders at the body
 * weight the moment someone gives it a link, at the right size and the wrong
 * weight. Nothing announces it: the h3 computes 700, only the text inside is
 * lighter.
 *
 * Found on the blog card, where the title is a link and rendered Satoshi 500
 * against Satoshi 700 on the same card on production.
 * ------------------------------------------------------------------------ */

.elementor-widget-heading .elementor-heading-title > a,
.elementor-widget-theme-post-title .elementor-heading-title > a {
	font-family: inherit;
	font-weight: inherit;
	letter-spacing: inherit;
	text-transform: inherit;
}

/* ---------------------------------------------------------------------------
 * Swipe utility for Elementor grids
 *
 * Put `tcb-swipe--tablet` (or `tcb-swipe--mobile`) in a widget's CSS Classes
 * field and its grid becomes a snapping swipe row below that breakpoint.
 *
 * The three containers listed are Elementor's loop grid, Elementor's posts widget, and the
 * TCB Loop Grid. One row implementation for all three — the alternative is three that drift.
 *
 * Deliberately the same rules as the comparison-card row above — same bleed,
 * same leading gutter, same centre snapping, same viewport-derived card width.
 * They were written separately once and drifted, which is how one row ended up
 * touching the screen edge while the other sat correctly inside the column.
 *
 * Requires the section to be a boxed container, so the row has a content
 * column to break out of and return to.
 * ------------------------------------------------------------------------ */

@media (max-width: 1024px) {
	.tcb-swipe--tablet :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items) {
		--tcb-bleed: max(0px, calc((100vw - 656px) / 2));
		display: flex;
		gap: var(--grid-column-gap, 24px);
		width: 100vw;
		margin-inline-start: calc(-1 * var(--tcb-bleed));
		padding-inline-start: var(--tcb-bleed);
		overflow-x: auto;
		/* A scroll container scrolls both ways unless told otherwise, which let
		 * the cards be dragged up and down inside the row. The shadow room
		 * below is padding, so it survives the clip. */
		/* hidden, not clip: with overflow-x set to auto the spec forces clip
		 * back to hidden anyway, so the box is a scroll container on both
		 * axes no matter what. The vertical scroll area is zero, which is
		 * what actually stops it moving. */
		overflow-y: hidden;
		/* A scroll container clips vertically as well, which cut the cards'
		 * shadows off top and bottom. The padding gives them room inside the
		 * scrollport and the negative margin takes it back off the layout. */
		padding-block: var(--tcb-swipe-shadow-room, 18px);
		margin-block: calc(-1 * var(--tcb-swipe-shadow-room, 18px));
		scroll-snap-type: x mandatory;
		/* x only. Containing both axes stops a vertical drag inside the row
		 * from chaining to the page, and since the row has no vertical scroll
		 * room the gesture just rubber-bands the cards instead — worst on a
		 * row taller than the screen, where the finger never leaves it. */
		overscroll-behavior-x: contain;
		scrollbar-width: none;
	}

	.tcb-swipe--tablet :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items)::-webkit-scrollbar {
		display: none;
	}

	.tcb-swipe--tablet :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items) > * {
		flex: 0 0 var(--tcb-swipe-width, calc(100vw - var(--tcb-bleed) - var(--grid-column-gap, 24px) - var(--tcb-swipe-peek, 70px)));
		scroll-snap-align: center;
	}

	.tcb-swipe--tablet :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items)::after {
		content: '';
		flex: 0 0 calc((var(--tcb-bleed) + var(--tcb-swipe-peek, 70px)) / 2);
	}
}

@media (max-width: 767px) {
	.tcb-swipe--tablet :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items),
	.tcb-swipe--mobile :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items) {
		--tcb-bleed: max(0px, calc((100vw - 328px) / 2));
	}

	.tcb-swipe--mobile :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items) {
		display: flex;
		gap: var(--grid-column-gap, 24px);
		width: 100vw;
		margin-inline-start: calc(-1 * var(--tcb-bleed));
		padding-inline-start: var(--tcb-bleed);
		overflow-x: auto;
		/* A scroll container scrolls both ways unless told otherwise, which let
		 * the cards be dragged up and down inside the row. The shadow room
		 * below is padding, so it survives the clip. */
		/* hidden, not clip: with overflow-x set to auto the spec forces clip
		 * back to hidden anyway, so the box is a scroll container on both
		 * axes no matter what. The vertical scroll area is zero, which is
		 * what actually stops it moving. */
		overflow-y: hidden;
		/* A scroll container clips vertically as well, which cut the cards'
		 * shadows off top and bottom. The padding gives them room inside the
		 * scrollport and the negative margin takes it back off the layout. */
		padding-block: var(--tcb-swipe-shadow-room, 18px);
		margin-block: calc(-1 * var(--tcb-swipe-shadow-room, 18px));
		scroll-snap-type: x mandatory;
		/* x only. Containing both axes stops a vertical drag inside the row
		 * from chaining to the page, and since the row has no vertical scroll
		 * room the gesture just rubber-bands the cards instead — worst on a
		 * row taller than the screen, where the finger never leaves it. */
		overscroll-behavior-x: contain;
		scrollbar-width: none;
	}

	.tcb-swipe--mobile :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items)::-webkit-scrollbar {
		display: none;
	}

	.tcb-swipe--tablet :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items) > *,
	.tcb-swipe--mobile :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items) > * {
		flex: 0 0 var(--tcb-swipe-width, calc(100vw - var(--tcb-bleed) - var(--grid-column-gap, 24px) - var(--tcb-swipe-peek, 70px)));
		scroll-snap-align: center;
	}

	.tcb-swipe--mobile :is(.elementor-grid, .elementor-posts-container, .tcb-loop-grid__items)::after {
		content: '';
		flex: 0 0 calc((var(--tcb-bleed) + var(--tcb-swipe-peek, 70px)) / 2);
	}
}
