/* ==========================================================================
   JKT Core — global stylesheet
   Design tokens, self-hosted @font-face, and the three shortcode components.
   Values follow design/ (JKT Home.dc.html, JKT Product Pages.dc.html §2a).
   Coral (--jkt-coral) is for interaction only — never decorative.
   ========================================================================== */

/* ---- Fonts: self-hosted WOFF2 (no Google Fonts on the front end) ---------- */
/* Supply the files listed in assets/fonts/README.md. font-display: swap. */

@font-face {
	font-family: "Zilla Slab";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url("../fonts/ZillaSlab-SemiBold.woff2") format("woff2");
}
@font-face {
	font-family: "Zilla Slab";
	font-style: normal;
	font-weight: 700;
	font-display: swap;
	src: url("../fonts/ZillaSlab-Bold.woff2") format("woff2");
}

@font-face {
	font-family: "Public Sans";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/PublicSans-Regular.woff2") format("woff2");
}
@font-face {
	font-family: "Public Sans";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("../fonts/PublicSans-Medium.woff2") format("woff2");
}
@font-face {
	font-family: "Public Sans";
	font-style: normal;
	font-weight: 600;
	font-display: swap;
	src: url("../fonts/PublicSans-SemiBold.woff2") format("woff2");
}

@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/IBMPlexMono-Regular.woff2") format("woff2");
}
@font-face {
	font-family: "IBM Plex Mono";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("../fonts/IBMPlexMono-Medium.woff2") format("woff2");
}
@font-face {
	font-family: "IBM Plex Mono";
	font-style: italic;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/IBMPlexMono-Italic.woff2") format("woff2");
}
@font-face {
	font-family: "IBM Plex Mono";
	font-style: italic;
	font-weight: 500;
	font-display: swap;
	src: url("../fonts/IBMPlexMono-MediumItalic.woff2") format("woff2");
}

/* ---- Design tokens -------------------------------------------------------- */

:root {
	/* Colours */
	--jkt-navy: #1F2B5E;        /* page background, flat everywhere */
	--jkt-navy-deep: #0F1837;   /* scrim base, image tiles behind pack shots */
	--jkt-coral: #F0522F;       /* interaction ONLY */
	--jkt-ice: #EEF2F5;         /* light text/graphics on navy */
	--jkt-white: #FFFFFF;

	/* Hairlines (ice, varying alpha) */
	--jkt-hair-subtle: rgba(238, 242, 245, .18);
	--jkt-hair: rgba(238, 242, 245, .25);
	--jkt-hair-strong: rgba(238, 242, 245, .35);

	/* Muted ice text */
	--jkt-ice-label: rgba(238, 242, 245, .55);
	--jkt-ice-body: rgba(238, 242, 245, .85);
	--jkt-ice-fine: rgba(238, 242, 245, .5);

	/* Seasonality fills */
	--jkt-seas-track: rgba(238, 242, 245, .08);
	--jkt-seas-peak: var(--jkt-ice);
	--jkt-seas-standard: rgba(238, 242, 245, .45);
	--jkt-seas-limited: rgba(238, 242, 245, .45);
	--jkt-seas-limited-h: 32%;  /* "limited" is a low bar (README) */

	/* Type families */
	--jkt-font-display: "Zilla Slab", Georgia, serif;
	--jkt-font-body: "Public Sans", system-ui, sans-serif;
	--jkt-font-mono: "IBM Plex Mono", ui-monospace, monospace;

	/* Spacing scale: 8 / 16 / 24 / 40 / 64 / 96 / 144 */
	--jkt-space-1: 8px;
	--jkt-space-2: 16px;
	--jkt-space-3: 24px;
	--jkt-space-4: 40px;
	--jkt-space-5: 64px;
	--jkt-space-6: 96px;
	--jkt-space-7: 144px;
}

/* ==========================================================================
   Page background treatment
   Flat navy comes from the Oxygen global background setting; this adds the
   grain tile over it. Fixed and behind all content (z-index: -1) so that
   images and buttons are never grained over, per design/README.md.
   ========================================================================== */

body::before {
	content: "";
	position: fixed;
	inset: 0;
	z-index: -1;
	background-image: url("../img/grain.png");
	background-repeat: repeat;
	background-size: 256px;
	mix-blend-mode: overlay;
	opacity: .08;
	pointer-events: none;
}

/* ==========================================================================
   Ghost fish watermarks
   Drawn as pseudo-elements on the section classes so they add no markup and
   carry no alt text — they are pure decoration. Values from design/README.md:
   380–520px wide, opacity .035–.05, rotated, fading toward the page edge via
   mask-image, drifting on a slow 11–17s loop. Behind all content (z-index:-1).
   ========================================================================== */

@keyframes jkt-fishdrift {
	0%, 100% { transform: translate(0, 0) rotate(var(--jkt-fish-ra, 0deg)); }
	50%      { transform: translate(10px, -14px) rotate(var(--jkt-fish-rb, 0deg)); }
}

.jkt-prod-hero::before,
.jkt-prod-factory::before,
.jkt-prod-range::before,
.jkt-archive::before,
.jkt-archive::after,
.jkt-home-range::before,
.jkt-family::before,
.jkt-film::before {
	content: "";
	position: absolute;
	z-index: -1;
	aspect-ratio: 693 / 879;
	background-image: url("../img/fish-white.png");
	background-repeat: no-repeat;
	background-size: contain;
	pointer-events: none;
	animation: jkt-fishdrift 14s ease-in-out infinite;
}

/* Entering from the left, high on the page. */
.jkt-prod-hero::before,
.jkt-archive::before,
.jkt-home-range::before {
	top: 180px;
	left: -150px;
	width: 460px;
	opacity: .05;
	--jkt-fish-ra: -24deg;
	--jkt-fish-rb: -27deg;
	transform: rotate(-24deg);
	animation-duration: 11s;
	-webkit-mask-image: linear-gradient(105deg, rgba(0, 0, 0, 0) 4%, #000 45%);
	mask-image: linear-gradient(105deg, rgba(0, 0, 0, 0) 4%, #000 45%);
}

/* Entering from the right, inverted. */
.jkt-prod-factory::before,
.jkt-archive::after,
.jkt-family::before {
	top: 10%;
	right: -170px;
	width: 520px;
	opacity: .035;
	--jkt-fish-ra: 148deg;
	--jkt-fish-rb: 144deg;
	transform: rotate(148deg);
	animation-duration: 14s;
	-webkit-mask-image: linear-gradient(255deg, rgba(0, 0, 0, 0) 4%, #000 50%);
	mask-image: linear-gradient(255deg, rgba(0, 0, 0, 0) 4%, #000 50%);
}

/* Low and central, fading downward. */
.jkt-prod-range::before,
.jkt-film::before {
	bottom: 60px;
	left: 22%;
	width: 380px;
	opacity: .04;
	--jkt-fish-ra: 12deg;
	--jkt-fish-rb: 8deg;
	transform: rotate(12deg);
	animation-duration: 17s;
	-webkit-mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 6%, #000 60%);
	mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 6%, #000 60%);
}

/*
 * The home film section now sits on the light history/timeline background, so
 * its white ghost fish has nothing to read against. Dropped here, after the
 * rules that place it, rather than restyled — the history and timeline it now
 * runs with carry no watermark either.
 */
.jkt-film::before {
	display: none;
}

/*
 * The watermarks hang outside their section on purpose (they enter from the
 * page edge), so every host has to clip them. Without this the one on the
 * products archive pushed the document 305px wider than the viewport and the
 * whole page could be scrolled sideways off-centre — the fish itself stayed
 * invisible, being at 4% opacity behind everything, so only the stray
 * scrollbar gave it away. Clipping costs nothing: the mask-image above already
 * fades each fish out before it reaches the boundary.
 */
.jkt-prod-hero,
.jkt-prod-factory,
.jkt-prod-range,
.jkt-archive,
.jkt-home-range,
.jkt-family,
.jkt-film {
	overflow: hidden;
}

/* Too large to read as a watermark on a phone. */
@media (max-width: 767px) {
	.jkt-prod-hero::before,
	.jkt-prod-factory::before,
	.jkt-prod-range::before,
	.jkt-archive::before,
	.jkt-archive::after,
	.jkt-home-range::before,
	.jkt-family::before,
	.jkt-film::before {
		display: none;
	}
}

/* ==========================================================================
   Scroll reveal
   Progressive enhancement: the hidden state applies only once assets/js/jkt.js
   has flagged <html class="jkt-js">, so with JS off — or if the script fails to
   run — the content stays visible instead of being stuck at opacity 0.
   The .jkt-reveal transition and .is-on end state live in the Oxygen selectors.
   ========================================================================== */

.jkt-js .jkt-reveal:not(.is-on) {
	opacity: 0;
	transform: translateY(26px);
}

/*
 * LIGHT SECTIONS — reveal the contents, not the section.
 *
 * The page behind every section is navy, so fading a light section up from
 * opacity 0 flashes blue before it settles: the animation reads as the section
 * changing colour rather than arriving. These three (the history, the timeline
 * and the film, which run together as one light block) therefore opt out of the
 * whole-element fade and hand it to their children. The background is painted
 * at full strength from the first frame and the content still slides in, so the
 * light block simply blends with the one above it.
 *
 * The children's transition is declared here rather than in an Oxygen selector
 * because it belongs with the hidden state it undoes — the two must always be
 * added and removed together.
 */
.jkt-js .jkt-history:not(.is-on),
.jkt-js .jkt-timeline:not(.is-on),
.jkt-js .jkt-film:not(.is-on) {
	opacity: 1;
	transform: none;
}

.jkt-history > *,
.jkt-timeline > *,
.jkt-film > * {
	transition: opacity .9s ease, transform .9s ease;
}

.jkt-js .jkt-history:not(.is-on) > *,
.jkt-js .jkt-timeline:not(.is-on) > *,
.jkt-js .jkt-film:not(.is-on) > * {
	opacity: 0;
	transform: translateY(26px);
}

/*
 * OXYGEN BUILDER CANVAS ONLY — reveal the reveals.
 *
 * The canvas is a real front-end render inside an iframe, so jkt.css AND jkt.js
 * both load and <html> does get the .jkt-js flag. What does NOT happen is the
 * IntersectionObserver ever firing, because nothing scrolls the iframe the way
 * a visitor scrolls a page. So .is-on is never added, the rule above keeps
 * every .jkt-reveal block at opacity 0, and the builder canvas paints an empty
 * navy page while the front end is perfect. Measured: 6 reveal blocks on
 * Smoked, all opacity 0, none with .is-on.
 *
 * .builder-element is added by Oxygen to every element it renders in the
 * builder and appears NOWHERE on the front end (verified: 0 occurrences in the
 * rendered page source, 46 in the canvas). Keying on it means this rule cannot
 * reach the live site, so the scroll animation is untouched for visitors.
 *
 * .jkt-js is included purely for specificity — (0,4,0) beats the (0,3,0) rule
 * above regardless of source order.
 *
 * DELIBERATELY NOT TOUCHED: the .jkt-timeline__panel / .jkt-proc__caption /
 * .jkt-certs__panel display rules. Those look like the same class of bug but
 * are not — the markup ships .is-active on one panel, so exactly one shows in
 * the canvas, which is the intended design. Neutralising them would stack every
 * panel on top of each other and make Home worse, not better.
 */
.jkt-js .jkt-reveal.builder-element:not(.is-on) {
	opacity: 1;
	transform: none;
}

/* Same escape hatch for the light sections, whose children carry the hidden
   state instead of the section. */
.jkt-js .jkt-history.builder-element:not(.is-on) > *,
.jkt-js .jkt-timeline.builder-element:not(.is-on) > *,
.jkt-js .jkt-film.builder-element:not(.is-on) > * {
	opacity: 1;
	transform: none;
}

/*
 * OXYGEN BUILDER CANVAS ONLY — show both certification panels.
 *
 * The tabs on /certifications/ are JS-driven: initGroup() adds .is-active to
 * the first tab and panel on load, and the CSS above hides any panel without
 * it. That works on the front end. In the builder it does not, because the
 * canvas re-renders elements from its own state and discards classes our
 * script added at runtime — measured: jkt.js present, .jkt-js set, and still
 * 0 of 2 panels active. Both panels were therefore display:none and the whole
 * accreditations block, logos included, was invisible and unreachable in the
 * editor while rendering perfectly for visitors.
 *
 * Both panels are shown stacked here so every element can be selected and
 * edited. Keyed on .builder-element, which exists only inside the builder.
 *
 * SCOPED TO THE CERTS PANELS ON PURPOSE. .jkt-timeline__panel and
 * .jkt-proc__caption use the same pattern but ship .is-active in the markup, so
 * exactly one of each is correctly visible in the canvas already — measured on
 * Home. Un-hiding those too would stack every panel and make that page worse.
 */
.jkt-js .jkt-certs__panel.builder-element:not(.is-active) {
	display: flex;
}

@media (prefers-reduced-motion: reduce) {
	.jkt-js .jkt-reveal:not(.is-on),
	.jkt-js .jkt-history:not(.is-on) > *,
	.jkt-js .jkt-timeline:not(.is-on) > *,
	.jkt-js .jkt-film:not(.is-on) > * {
		opacity: 1;
		transform: none;
	}

	.jkt-prod-hero::before,
	.jkt-prod-factory::before,
	.jkt-prod-range::before,
	.jkt-archive::before,
	.jkt-archive::after,
	.jkt-home-range::before,
	.jkt-family::before,
	.jkt-film::before {
		animation: none;
	}

	/* The client marquee is a pure CSS loop, so it is stopped here rather
	   than in jkt.js like the two photo crossfades. */
	.jkt-clients__track {
		animation: none;
	}
}

/* ==========================================================================
   Home page
   The header template is global, so the "nav floats over the hero video"
   treatment is scoped to the front page by body class rather than by adding a
   modifier to the shared header.
   ========================================================================== */

body.home .jkt-header {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	border-bottom-color: var(--jkt-hair-subtle);
}

/* --------------------------------------------------------------------------
   Home hero: the logo intro

   On load the JKT mark appears large in the centre of the hero, holds, then
   dissolves as the nav logo fades in and the headline and index strip arrive
   under it. Pure CSS: the sequence is four delayed animations, so it runs once
   per page load, needs no script, and cannot leave anything stuck hidden if JS
   fails — unlike the scroll reveal above, which is deliberately JS-gated.

   `both` fill mode is what holds the headline, strip and nav logo at their
   from-state through the delay. Nothing here animates a property that affects
   layout, so the hero never reflows: the mark is absolutely positioned and the
   rest only move by transform.

   The mark's own position and size sit in the Oxygen selector
   (.jkt-hero__mark / .jkt-hero__mark-img) so they stay editable in the builder;
   only the motion lives here. Timings are from design/hero handoff.
   -------------------------------------------------------------------------- */

@keyframes jkt-intro-mark {
	0%   { opacity: 0; transform: scale(.9); }
	14%  { opacity: 1; transform: scale(1); }
	68%  { opacity: 1; transform: scale(1.01); }
	100% { opacity: 0; transform: scale(1.08); }
}

@keyframes jkt-rise-in {
	from { opacity: 0; transform: translateY(30px); }
	to   { opacity: 1; transform: translateY(0); }
}

@keyframes jkt-fade-in {
	from { opacity: 0; }
	to   { opacity: 1; }
}

body.home .jkt-hero__mark-img {
	animation: jkt-intro-mark 2.7s ease both;
}

/* The header is shared site-wide, so only the home page's logo waits. It fades
   in as the centre mark dissolves, which reads as the mark settling into the
   nav. The menu items are never hidden. */
body.home .jkt-header__brand {
	animation: jkt-fade-in .7s ease 2.5s both;
}

/* The handoff staggers two headline lines. The heading is one element here and
   wraps differently at every width, so it rises as a block — the handoff allows
   this where the markup is a single line. */
body.home .jkt-hero__intro {
	animation: jkt-rise-in .8s ease 2.55s both;
}

body.home .jkt-hero__index {
	animation: jkt-fade-in .8s ease 3.1s both;
}

/* Reduced motion: no sequence at all. The nav logo, headline and strip are
   simply there, and the centre mark never appears. */
@media (prefers-reduced-motion: reduce) {
	body.home .jkt-hero__mark {
		display: none;
	}

	body.home .jkt-header__brand,
	body.home .jkt-hero__intro,
	body.home .jkt-hero__index {
		animation: none;
	}
}

/* Firth of Forth coastline behind the timeline. The SVG is a plugin asset, so
   the image and its pulse live here; the layout sits in the Oxygen selector. */
@keyframes jkt-mapfade {
	0%, 100% { opacity: .1; }
	50%      { opacity: .32; }
}

.jkt-timeline__map {
	aspect-ratio: 1600 / 754;
	background-image: url("../img/forth.svg");
	background-repeat: no-repeat;
	background-position: center;
	background-size: 100% auto;
	animation: jkt-mapfade 12s ease-in-out infinite;
}

/* Interactive panels: only the active one shows, but only once the script is
   running. With JS off every panel stays readable instead of vanishing. */
.jkt-js .jkt-timeline__panel:not(.is-active),
.jkt-js .jkt-proc__caption:not(.is-active),
.jkt-js .jkt-certs__panel:not(.is-active) {
	display: none;
}

/* Crossfading photo backgrounds (the variety and factory sections). The layers
   stack, so only the active one may be opaque — but only once the script is
   running. With JS off the first frame stays visible on its own, rather than
   the section rendering as a flat navy block. */
.jkt-js .jkt-xfade__layer:not(.is-active) {
	opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
	.jkt-timeline__map { animation: none; opacity: .2; }
}

/* ==========================================================================
   [jkt_spec_table]
   ========================================================================== */

.jkt-spec {
	font-family: var(--jkt-font-mono);
}

.jkt-spec__head {
	font: 500 11px/1 var(--jkt-font-mono);
	letter-spacing: .14em;
	color: var(--jkt-coral);
	text-transform: uppercase;
	border-bottom: 1px solid var(--jkt-hair-strong);
	padding-bottom: 14px;
	margin: 0;
}

.jkt-spec__grid {
	display: grid;
	grid-template-columns: 1fr 1fr;
	column-gap: 56px;
}

.jkt-spec__row {
	display: grid;
	grid-template-columns: 120px 1fr;
	gap: 16px;
	padding: 12px 0;
	border-bottom: 1px solid var(--jkt-hair-subtle);
}

.jkt-spec__label {
	font: 500 10px/1.6 var(--jkt-font-mono);
	letter-spacing: .1em;
	color: var(--jkt-ice-label);
	text-transform: uppercase;
}

.jkt-spec__value {
	font: 400 12px/1.6 var(--jkt-font-mono);
	font-variant-numeric: tabular-nums;
	color: var(--jkt-ice);
}

/* ==========================================================================
   [jkt_seasonality]
   ========================================================================== */

.jkt-seas {
	font-family: var(--jkt-font-mono);
}

.jkt-seas__head {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 16px;
	margin-bottom: 16px;
}

.jkt-seas__title {
	font: 500 11px/1 var(--jkt-font-mono);
	letter-spacing: .14em;
	color: var(--jkt-ice);
	text-transform: uppercase;
}

.jkt-seas__legend {
	display: flex;
	gap: 16px;
	font: 400 10px/1 var(--jkt-font-mono);
	color: var(--jkt-ice-label);
	white-space: nowrap;
}

.jkt-seas__legend-item {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}

.jkt-seas__swatch {
	width: 8px;
	height: 8px;
	flex: none;
}
.jkt-seas__swatch--peak { background: var(--jkt-seas-peak); }
.jkt-seas__swatch--standard { background: var(--jkt-seas-standard); }
.jkt-seas__swatch--limited { background: var(--jkt-seas-limited); }

.jkt-seas__grid {
	display: grid;
	grid-template-columns: repeat(12, 1fr);
	gap: 5px;
}

.jkt-seas__col {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.jkt-seas__track {
	height: 64px;
	display: flex;
	align-items: flex-end;
	background: var(--jkt-seas-track);
}

.jkt-seas__fill {
	width: 100%;
	height: 100%;
	background: var(--jkt-seas-peak);
}
.jkt-seas__fill--peak {
	height: 100%;
	background: var(--jkt-seas-peak);
}
.jkt-seas__fill--standard {
	height: 100%;
	background: var(--jkt-seas-standard);
}
.jkt-seas__fill--limited {
	height: var(--jkt-seas-limited-h);
	background: var(--jkt-seas-limited);
}

.jkt-seas__month {
	font: 400 9px/1 var(--jkt-font-mono);
	color: var(--jkt-ice-label);
	text-align: center;
}

/* ==========================================================================
   [jkt_sourcing_map]
   ========================================================================== */

.jkt-map {
	font-family: var(--jkt-font-mono);
}

.jkt-map__head {
	font: 500 10px/1 var(--jkt-font-mono);
	letter-spacing: .14em;
	color: var(--jkt-ice-label);
	text-transform: uppercase;
	margin-bottom: 14px;
}

.jkt-map__frame {
	position: relative;
}

.jkt-map__img {
	width: 100%;
	display: block;
}

.jkt-map__dot {
	position: absolute;
	width: 7px;
	height: 7px;
	transform: translate(-50%, -50%);
	border-radius: 50%;
	background: var(--jkt-coral);
}
.jkt-map__dot--hub {
	width: 8px;
	height: 8px;
	border-radius: 0;
	background: var(--jkt-ice);
}

.jkt-map__label {
	position: absolute;
	font: 500 9px/1 var(--jkt-font-mono);
	letter-spacing: .08em;
	color: var(--jkt-ice);
	white-space: nowrap;
}

.jkt-map__note {
	font: 400 9px/1.6 var(--jkt-font-mono);
	color: var(--jkt-ice-fine);
	margin-top: 10px;
}

/* ==========================================================================
   [jkt_product_filters] — archive filter chips
   Active = coral text + coral border, inactive = hairline border, hover →
   border-color to coral (no other animation).
   ========================================================================== */

.jkt-filter {
	display: flex;
	flex-direction: column;
	gap: 16px;
}

.jkt-filter__row {
	display: flex;
	align-items: baseline;
	gap: 24px;
}

.jkt-filter__label {
	font: 500 10px/1 var(--jkt-font-mono);
	letter-spacing: .12em;
	color: var(--jkt-ice-label);
	width: 80px;
	flex: none;
}

.jkt-filter__chips {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

.jkt-filter__chip {
	font: 500 11px/1 var(--jkt-font-mono);
	letter-spacing: .1em;
	text-transform: uppercase;
	text-decoration: none;
	padding: 8px 14px;
	color: var(--jkt-ice-body);
	border: 1px solid var(--jkt-hair);
	transition: border-color .2s, color .2s;
}

.jkt-filter__chip:hover {
	border-color: var(--jkt-coral);
}

.jkt-filter__chip.is-active {
	color: var(--jkt-coral);
	border-color: var(--jkt-coral);
}

/* ==========================================================================
   Gravity Forms
   Forms 1-3 are embedded by ID via Oxygen's Gravity Forms element, so the
   markup and CSS are GF's own (the 2.9 "orbital" theme). That theme is driven
   entirely by custom properties, so the supported way to restyle it is to
   redefine those properties rather than out-specify its rules. Design values
   from design/round-2 README, Site pages: mono uppercase 10px labels at
   ice .55, inputs on ice .06 with a .3 hairline border, coral solid submit,
   no border radius anywhere.
   ========================================================================== */

/* GF emits a per-form inline <style> on #gform_wrapper_<id>[data-form-index]
   (specificity 1,2,0), so !important is required for these to land. */
.gform_wrapper.gform-theme,
.gform_wrapper {
	/* Brand: everything the theme derives from "primary" becomes coral. */
	--gf-color-primary: var(--jkt-coral) !important;
	--gf-color-primary-rgb: 240, 82, 47 !important;
	--gf-color-primary-darker: #D8451F !important;
	--gf-color-primary-lighter: #F4785C !important;
	--gf-color-primary-contrast: var(--jkt-white) !important;
	--gf-color-primary-contrast-rgb: 255, 255, 255 !important;

	/* Inputs: ice .06 fill, .3 hairline border, ice text. */
	--gf-ctrl-bg-color: rgba(238, 242, 245, .06) !important;
	--gf-ctrl-bg-color-hover: rgba(238, 242, 245, .06) !important;
	--gf-ctrl-bg-color-focus: rgba(238, 242, 245, .06) !important;
	--gf-ctrl-bg-color-error: rgba(238, 242, 245, .06) !important;
	--gf-ctrl-border-color: rgba(238, 242, 245, .3) !important;
	--gf-ctrl-border-color-hover: var(--jkt-coral) !important;
	--gf-ctrl-border-color-focus: var(--jkt-coral) !important;
	--gf-ctrl-color: var(--jkt-ice) !important;
	--gf-ctrl-color-hover: var(--jkt-ice) !important;
	--gf-ctrl-color-focus: var(--jkt-ice) !important;
	--gf-ctrl-label-color-primary: var(--jkt-ice-label) !important;
	--gf-ctrl-label-color-secondary: var(--jkt-ice-label) !important;
	--gf-ctrl-desc-color: var(--jkt-ice-fine) !important;

	/* Shape: no radius anywhere (design/README "NO border radius"). */
	--gf-ctrl-radius: 0 !important;
	--gf-ctrl-border-radius: 0 !important;
	--gf-ctrl-btn-radius: 0 !important;
	--gf-ctrl-btn-border-radius: 0 !important;
	--gf-ctrl-checkbox-check-radius: 0 !important;
	--gf-ctrl-file-btn-radius: 0 !important;

	/* Submit / next / previous sizing. */
	--gf-ctrl-btn-padding-x: 44px !important;
	--gf-ctrl-btn-padding-y: 18px !important;
	--gf-ctrl-btn-font-family: var(--jkt-font-mono) !important;
	--gf-ctrl-btn-font-size: 12px !important;
	--gf-ctrl-btn-font-weight: 500 !important;

	font-family: var(--jkt-font-body);
}

/* The page heading is supplied by the Oxygen layout, not the form. */
.gform_wrapper .gform_title,
.gform_wrapper .gform_description {
	display: none;
}

/*
 * Oxygen's native form CSS puts `display:grid; grid-template-columns:
 * repeat(12,1fr)` on .breakdance-form. Third-party embeds (Gravity Forms here)
 * reuse that wrapper class, so the whole form lands in column 1 and collapses
 * to ~176px. Third-party forms bring their own layout — take the grid off.
 */
.breakdance-form.breakdance-form--third-party {
	display: block;
}

/* Typography the theme's custom properties do not cover. */
.gform_wrapper.gform-theme .gfield_label,
.gform_wrapper.gform-theme legend.gfield_label,
.gform_wrapper .gfield_label {
	font: 500 10px/1 var(--jkt-font-mono);
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--jkt-ice-label);
}

.gform_wrapper.gform-theme .gform_button,
.gform_wrapper.gform-theme .gform_next_button,
.gform_wrapper.gform-theme .gform_previous_button,
.gform_wrapper .gform_button {
	border-radius: 0;
	font-family: var(--jkt-font-mono);
	font-size: 12px;
	font-weight: 500;
	letter-spacing: .14em;
	text-transform: uppercase;
	cursor: pointer;
}

.gform_wrapper.gform-theme .gform_button:hover,
.gform_wrapper.gform-theme .gform_next_button:hover {
	background-color: var(--jkt-ice);
	color: var(--jkt-navy);
}

.gform_wrapper input,
.gform_wrapper select,
.gform_wrapper textarea {
	border-radius: 0;
}

.gform_wrapper textarea {
	resize: vertical;
}

.gform_wrapper ::placeholder {
	color: rgba(238, 242, 245, .35);
}

/* Multi-step progress bar (form 1, the 6-step application). */
.gform_wrapper .gf_progressbar_wrapper {
	margin-bottom: 48px;
}

.gform_wrapper .gf_progressbar_title {
	font: 500 11px/1 var(--jkt-font-mono);
	letter-spacing: .14em;
	text-transform: uppercase;
	color: rgba(238, 242, 245, .7);
	margin-bottom: 12px;
}

/* GF appends a colour class (percentbar_blue etc.) chosen in the form
   settings — match it so the bar follows the design regardless of that. */
.gform_wrapper .gf_progressbar,
.gform_wrapper .gf_progressbar[class*="gf_progressbar_"] {
	height: 3px;
	background: rgba(238, 242, 245, .15);
	border-radius: 0;
	overflow: hidden;
}

.gform_wrapper .gf_progressbar_percentage,
.gform_wrapper .gf_progressbar_percentage[class*="percentbar_"] {
	height: 100%;
	background: var(--jkt-coral);
	border-radius: 0;
}

.gform_wrapper .gf_progressbar_percentage span {
	display: none;
}

.gform_wrapper .gform_confirmation_message {
	font: 400 17px/1.7 var(--jkt-font-body);
	color: var(--jkt-ice-body);
}

/* ==========================================================================
   Header states
   One header template (post 38) in two states, switched by CSS condition:
     default              solid --jkt-navy
     home / category hero transparent, floated over the hero
   The transparent state is triggered by WordPress's own `home` body class and
   by `:has(.jkt-cat__hero)` for Smoked/Fresh/Frozen, whose heroes pull up
   under the nav (margin-top:-113px). Keyed on the hero being present rather
   than on page IDs, so it survives slug changes.
   ========================================================================== */

body.home .jkt-header,
body:has(.jkt-cat__hero) .jkt-header {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	background-color: transparent;
	border-bottom-color: var(--jkt-hair-subtle);
}

/*
 * The admin bar is position:fixed and adds `html{margin-top:32px}`, which
 * shifts the page but NOT this header — it is absolutely positioned against
 * the initial containing block, so it stays at y=0 and the bar covers its
 * 28px top padding. That reads as "too close to the top" but only when logged
 * in; logged out, header and hero both sit at 0 exactly as the design has it.
 * Offset by the bar's own height so the two agree for admins. Inert for
 * visitors — no `admin-bar` class is output when logged out.
 */
body.admin-bar.home .jkt-header,
body.admin-bar:has(.jkt-cat__hero) .jkt-header {
	top: 32px;
}

/* WordPress switches the bar to 46px at this width (its own breakpoint). */
@media screen and (max-width: 782px) {
	body.admin-bar.home .jkt-header,
	body.admin-bar:has(.jkt-cat__hero) .jkt-header {
		top: 46px;
	}
}

/* ==========================================================================
   Responsive — spec grid collapses 2-col → 1-col on mobile
   ========================================================================== */

@media (max-width: 640px) {
	.jkt-spec__grid {
		grid-template-columns: 1fr;
		column-gap: 0;
	}
}

/* ==========================================================================
   [jkt_factory_slider] — 3-image factory slider
   --------------------------------------------------------------------------
   A native scroll-snap strip. It is a working slider with no JS at all: the
   viewport scrolls and snaps, and it takes keyboard focus. The arrows, dots
   and counter are enhancements, so they only appear once JS has flagged the
   document — otherwise they would be controls that do nothing.
   ========================================================================== */

.jkt-fslider {
	margin: 0;
	display: flex;
	flex-direction: column;
	gap: var(--jkt-space-2);
}

.jkt-fslider__viewport {
	display: block;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	border: 1px solid var(--jkt-hair-subtle);
	background: var(--jkt-navy-deep);
	/* Firefox + the standard property; WebKit handled below. */
	scrollbar-width: none;
}

.jkt-fslider__viewport::-webkit-scrollbar {
	display: none;
}

.jkt-fslider__viewport:focus-visible {
	outline: 1px solid var(--jkt-coral);
	outline-offset: 2px;
}

.jkt-fslider__track {
	display: flex;
}

.jkt-fslider__frame {
	flex: 0 0 100%;
	scroll-snap-align: start;
}

.jkt-fslider__img {
	display: block;
	width: 100%;
	aspect-ratio: 3 / 2;
	object-fit: cover;
}

.jkt-fslider__meta {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--jkt-space-2);
	font-family: var(--jkt-font-mono);
	font-size: 10px;
	font-weight: 400;
	line-height: 1;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--jkt-ice-fine);
}

.jkt-fslider__counter {
	flex: none;
	font-variant-numeric: tabular-nums;
	color: var(--jkt-ice-label);
}

.jkt-fslider__sep {
	margin: 0 .35em;
	opacity: .6;
}

/* Controls: enhancement only. */
.jkt-fslider__controls {
	display: none;
	align-items: center;
	gap: var(--jkt-space-2);
}

.jkt-js .jkt-fslider__controls {
	display: flex;
}

.jkt-fslider__arrow {
	appearance: none;
	margin: 0;
	padding: 0;
	width: 32px;
	height: 32px;
	flex: none;
	cursor: pointer;
	color: var(--jkt-ice);
	font-family: var(--jkt-font-mono);
	font-size: 13px;
	line-height: 1;
	background: transparent;
	border: 1px solid var(--jkt-hair-subtle);
	transition: color .2s ease, border-color .2s ease;
}

.jkt-fslider__arrow:hover:not(:disabled) {
	color: var(--jkt-coral);
	border-color: var(--jkt-coral);
}

.jkt-fslider__arrow:disabled {
	opacity: .3;
	cursor: default;
}

.jkt-fslider__dots {
	display: flex;
	align-items: center;
	gap: var(--jkt-space-1);
}

.jkt-fslider__dot {
	appearance: none;
	margin: 0;
	padding: 0;
	width: 7px;
	height: 7px;
	flex: none;
	cursor: pointer;
	border-radius: 50%;
	background: transparent;
	border: 1px solid var(--jkt-hair-strong);
	transition: background-color .2s ease, border-color .2s ease;
}

.jkt-fslider__dot:hover,
.jkt-fslider__dot.is-active {
	background: var(--jkt-coral);
	border-color: var(--jkt-coral);
}

/* ==========================================================================
   [jkt_range_counter] — "01 / 11" on the range shelf
   ========================================================================== */

.jkt-rangecount {
	font-family: var(--jkt-font-mono);
	font-size: 10px;
	font-weight: 400;
	line-height: 1;
	letter-spacing: .1em;
	color: var(--jkt-ice-label);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

.jkt-rangecount__sep {
	margin: 0 .35em;
	opacity: .6;
}

@media (prefers-reduced-motion: reduce) {
	.jkt-fslider__arrow,
	.jkt-fslider__dot {
		transition: none;
	}
}

/* ==========================================================================
   [jkt_social] — footer social links
   Outline marks that fill on hover. The frame is stroked at the same weight as
   the hairlines used everywhere else on the page, via non-scaling-stroke so
   the line stays exactly that width whatever size the icon is rendered at —
   scaling a 24-unit viewBox down to 20px would otherwise thin it to 0.83px.
   On hover the frame fills with the link colour (coral, interaction only) and
   the letterform flips to the page navy, knocking out of the solid shape the
   way the official marks do.
   ========================================================================== */

.jkt-social {
	display: flex;
	flex-direction: column;
	gap: var(--jkt-space-1);
}

.jkt-social__label {
	font-family: var(--jkt-font-mono);
	font-size: 10px;
	font-weight: 500;
	line-height: 1;
	letter-spacing: .1em;
	text-transform: uppercase;
	color: var(--jkt-ice-label);
}

.jkt-social__icons {
	display: flex;
	align-items: center;
	gap: var(--jkt-space-2);
}

.jkt-social__link {
	display: inline-flex;
	color: var(--jkt-ice-body);
	text-decoration: none;
	transition: color .2s ease;
}

.jkt-social__link:hover,
.jkt-social__link:focus-visible {
	color: var(--jkt-coral);
}

.jkt-social__icon {
	/* Matches --jkt-hair's 1px, a touch heavier so it holds at icon scale. */
	--jkt-social-stroke: 1.25;
	display: block;
	width: 20px;
	height: 20px;
	overflow: visible; /* The stroke sits on the shape edge, so don't clip it. */
}

/* Outline at rest. */
.jkt-social__frame,
.jkt-social__lens {
	fill: none;
	stroke: currentColor;
	stroke-width: var(--jkt-social-stroke);
	vector-effect: non-scaling-stroke;
	transition: fill .2s ease, stroke .2s ease;
}

/* The letterform (and Instagram's shutter dot) is solid at both states. */
.jkt-social__mark {
	fill: currentColor;
	stroke: none;
	transition: fill .2s ease;
}

/* Filled on hover, with the mark knocked out in the page colour. */
.jkt-social__link:hover .jkt-social__frame,
.jkt-social__link:focus-visible .jkt-social__frame {
	fill: currentColor;
}

.jkt-social__link:hover .jkt-social__mark,
.jkt-social__link:focus-visible .jkt-social__mark {
	fill: var(--jkt-navy);
}

.jkt-social__link:hover .jkt-social__lens,
.jkt-social__link:focus-visible .jkt-social__lens {
	stroke: var(--jkt-navy);
}

@media (prefers-reduced-motion: reduce) {
	.jkt-social__link,
	.jkt-social__frame,
	.jkt-social__lens,
	.jkt-social__mark {
		transition: none;
	}
}

/* ==========================================================================
   CONSENT BANNER (Complianz)
   ==========================================================================
   Colours, corners, position, button labels and behaviour are all set through
   Complianz's own settings — see BUILD_NOTES ("Compliance") for which is
   which. What is left here is what the plugin has no option for: typography,
   button geometry, and the z-index relationship with the mini-cart.

   Lives in jkt.css rather than its own file because the banner appears on
   every page, and jkt.css is already loaded on every page — a separate
   stylesheet would be a second request for ~2KB.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Typography — Complianz has a font-size option but no font-family
   -------------------------------------------------------------------------- */

.cmplz-cookiebanner,
.cmplz-manage-consent {
	font-family: var(--jkt-font-body);
}

.cmplz-cookiebanner .cmplz-title {
	font-family: var(--jkt-font-display);
	font-weight: 700;
	font-size: 20px;
	line-height: 1.2;
	color: var(--jkt-white, #fff);
}

.cmplz-cookiebanner .cmplz-message {
	font-size: 14px;
	line-height: 1.6;
	color: var(--jkt-ice-body);
}

.cmplz-cookiebanner .cmplz-divider {
	background-color: var(--jkt-hair-subtle);
}

/* Category rows in the preferences view. */
.cmplz-cookiebanner .cmplz-category-header .cmplz-category-title {
	font-family: var(--jkt-font-mono);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: .12em;
	text-transform: uppercase;
}

.cmplz-cookiebanner .cmplz-description {
	font-size: 13px;
	line-height: 1.55;
	color: var(--jkt-ice-fine);
}

/* --------------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------------
   Mono uppercase, square — the same button language as the mini-cart drawer.

   EQUAL PROMINENCE. Accept and Reject are given the same flex basis, so they
   are the same width, the same height, the same type and on the same row; the
   only difference is that Accept is filled coral and Reject is a full-strength
   ice hairline. Neither is a text link, neither is greyed, and rejecting is
   one click exactly like accepting — which is what PECR and the ICO's guidance
   on consent actually require. Do not "tidy" this by shrinking Reject.
   -------------------------------------------------------------------------- */

/*
 * Grid, not flex-wrap. Flex wrapping put "Manage preferences" up beside
 * "Accept all" and pushed "Reject optional" onto its own row below — which is
 * precisely the arrangement that must not happen, since it makes rejecting
 * look like the lesser, after-the-fact option. A two-column grid is
 * deterministic at every width: Accept and Reject always share row one at
 * exactly equal width, preferences always spans row two.
 *
 * .cmplz-buttons is doubled to reach (0,3,0): Complianz sets
 * `.cmplz-cookiebanner .cmplz-buttons { display: flex }` at (0,2,0) from a
 * stylesheet that loads after ours, so a single class would lose on source
 * order. Repeating the class is the least invasive way to outweigh it without
 * an !important or hard-coding the banner-id class.
 */
.cmplz-cookiebanner .cmplz-buttons.cmplz-buttons {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 8px;
}

.cmplz-cookiebanner .cmplz-buttons .cmplz-btn {
	font-family: var(--jkt-font-mono);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: .1em;
	text-transform: uppercase;
	padding: 12px 14px;
	min-height: 42px;
	transition: background-color .2s ease, color .2s ease, border-color .2s ease;
}

/* .cmplz-btn compound for weight — see the note on the grid rule above. */
.cmplz-cookiebanner .cmplz-buttons .cmplz-btn.cmplz-view-preferences,
.cmplz-cookiebanner .cmplz-buttons .cmplz-btn.cmplz-save-preferences {
	grid-column: 1 / -1;
}

/* Inverts to ice on hover — the same move as the mini-cart's primary button. */
.cmplz-cookiebanner .cmplz-buttons .cmplz-accept:hover {
	background-color: var(--jkt-ice);
	border-color: var(--jkt-ice);
	color: #16181c;
}

.cmplz-cookiebanner .cmplz-buttons .cmplz-deny:hover,
.cmplz-cookiebanner .cmplz-buttons .cmplz-view-preferences:hover,
.cmplz-cookiebanner .cmplz-buttons .cmplz-save-preferences:hover {
	border-color: var(--jkt-coral);
	color: var(--jkt-coral);
}

/* Policy links along the foot of the banner. */
.cmplz-cookiebanner a.cmplz-link,
.cmplz-cookiebanner .cmplz-links a,
.cmplz-cookiebanner .cmplz-documents a {
	font-family: var(--jkt-font-mono);
	font-size: 10px;
	letter-spacing: .1em;
	text-transform: uppercase;
	/* Colour comes from Complianz's own hyperlink option (set to coral). */
	text-decoration: none;
}

.cmplz-cookiebanner a.cmplz-link:hover,
.cmplz-cookiebanner .cmplz-links a:hover,
.cmplz-cookiebanner .cmplz-documents a:hover {
	color: var(--jkt-ice);
}

/* --------------------------------------------------------------------------
   The persistent "manage consent" control
   --------------------------------------------------------------------------
   Complianz parks this as a tab at the bottom-left. Kept deliberately quiet —
   a small mono label on a hairline — because it must be permanently reachable
   without competing with the shop.
   -------------------------------------------------------------------------- */

.cmplz-manage-consent {
	padding: 8px 14px !important;   /* plugin sets 15px inline-equivalent at higher specificity */
	background-color: var(--jkt-navy-deep) !important;
	border: 1px solid var(--jkt-hair);
	border-bottom: 0;
	color: var(--jkt-ice-label) !important;
	font-family: var(--jkt-font-mono);
	font-size: 10px;
	letter-spacing: .12em;
	text-transform: uppercase;
	transition: color .2s ease, background-color .2s ease;
}

.cmplz-manage-consent:hover {
	color: var(--jkt-coral) !important;
}

/* --------------------------------------------------------------------------
   Stacking against the mini-cart
   --------------------------------------------------------------------------
   Complianz ships the banner at z-index 99999. The mini-cart drawer is 9999
   and its overlay 9998, so out of the box an open basket is drawn UNDER the
   consent banner: the banner floats over the dimmed overlay, undimmed, and
   sits above a dialog marked aria-modal.

   Rather than raise the drawer above the consent banner — the banner should
   stay on top in the ordinary case, it is a legal gate — the banner is pushed
   below the overlay for exactly as long as the drawer is open. The drawer
   already sets .jkt-minicart-open on <html> when it opens and removes it on
   close, so this reverses itself.
   -------------------------------------------------------------------------- */

html.jkt-minicart-open .cmplz-cookiebanner,
html.jkt-minicart-open .cmplz-manage-consent {
	z-index: 9997;
}

/* --------------------------------------------------------------------------
   Narrow screens
   -------------------------------------------------------------------------- */

@media (max-width: 480px) {
	.cmplz-cookiebanner .cmplz-title { font-size: 18px; }
	.cmplz-cookiebanner .cmplz-message { font-size: 13px; }
	.cmplz-cookiebanner .cmplz-buttons .cmplz-btn { font-size: 10px; padding: 11px 10px; }
}

/* --------------------------------------------------------------------------
   Policy links in the footer legal line
   --------------------------------------------------------------------------
   Appended to the existing copyright text element rather than added as a new
   Oxygen node, so the footer template's structure and generated CSS are
   untouched. They inherit the legal line's mono/uppercase/10px treatment from
   .jkt-footer__legal; all that is needed is link colour and the hover.
   -------------------------------------------------------------------------- */

.jkt-footer__legal-link {
	color: var(--jkt-ice-label);
	text-decoration: none;
	border-bottom: 1px solid transparent;
	transition: color .2s ease, border-color .2s ease;
}

.jkt-footer__legal-link:hover,
.jkt-footer__legal-link:focus-visible {
	color: var(--jkt-coral);
	border-bottom-color: var(--jkt-coral);
}

/* --------------------------------------------------------------------------
   Dual-route CTA pair (Our Seafood pages)
   --------------------------------------------------------------------------
   The two ways to buy: the online shop for case quantities, the sales team for
   volume. Rendered INSIDE the existing intro paragraph on the Smoked / Fresh /
   Frozen pages, because appending to an existing Oxygen text node is a
   content-only edit — no new elements, no regenerated template CSS, no need to
   open the beta builder. See BUILD_NOTES ("Positioning").

   That placement is why everything here is an inline element. A <div> inside a
   <p> is invalid and browsers hoist it out of the paragraph, which would break
   the layout; a <span> set to flex is valid and behaves identically.

   Both routes are hairline, deliberately — neither channel is being pushed
   over the other. The shop route carries a coral border to mark it as the one
   that can be actioned on the spot.
   -------------------------------------------------------------------------- */

.jkt-route {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	margin-top: var(--jkt-space-2);
}

.jkt-route__link {
	display: inline-flex;
	align-items: center;
	padding: 13px 20px;
	border: 1px solid var(--jkt-hair);
	color: var(--jkt-ice);
	font-family: var(--jkt-font-mono);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: .1em;
	line-height: 1.2;
	text-transform: uppercase;
	text-decoration: none;
	transition: border-color .2s ease, color .2s ease;
}

/* The actionable route — bordered coral, not filled, so the pair stays even. */
.jkt-route__link--shop {
	border-color: var(--jkt-coral);
	color: var(--jkt-coral);
}

.jkt-route__link:hover,
.jkt-route__link:focus-visible {
	border-color: var(--jkt-coral);
	color: var(--jkt-coral);
}

@media (max-width: 600px) {
	.jkt-route { gap: 10px; }
	.jkt-route__link { width: 100%; justify-content: center; font-size: 10px; padding: 12px 14px; }
}

/* Inline prose links in body copy (Oxygen standfirsts, Woo short descriptions,
   category descriptions). Coral underline rather than a colour change, so the
   sentence still reads as a sentence but the link is unmistakably a link. */
.jkt-inline-link {
	color: inherit;
	text-decoration: underline;
	text-decoration-color: var(--jkt-coral);
	text-underline-offset: 3px;
	transition: color .2s ease;
}

.jkt-inline-link:hover,
.jkt-inline-link:focus-visible {
	color: var(--jkt-coral);
}

/* --------------------------------------------------------------------------
   Home "the range" slider — forward / back controls
   --------------------------------------------------------------------------
   The strip is an Oxygen Post Loop in Swiper mode. It could always be dragged;
   it had no controls and no affordance, so it read as a static row that
   happened to be cut off. Markup for these buttons is created by jkt.js
   against the existing Swiper instance — nothing is added to the page
   document. See BUILD_NOTES.

   Same button language as the mini-cart and the dual-route CTAs: square,
   hairline, ice, coral on hover.
   -------------------------------------------------------------------------- */

/* Keeps the section head at two flex children so its space-between still
   puts the intro left and everything actionable right. */
.jkt-range-actions {
	display: flex;
	align-items: center;
	gap: 16px;
	flex-shrink: 0;
}

.jkt-range-nav {
	display: flex;
	gap: 8px;
}

.jkt-range-nav[hidden] { display: none; }

.jkt-range-nav__btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 46px;
	height: 46px;
	padding: 0;
	background: transparent;
	border: 1px solid var(--jkt-hair);
	border-radius: 0;
	color: var(--jkt-ice);
	cursor: pointer;
	transition: border-color .2s ease, color .2s ease, opacity .2s ease;
}

.jkt-range-nav__btn:hover:not(:disabled),
.jkt-range-nav__btn:focus-visible:not(:disabled) {
	border-color: var(--jkt-coral);
	color: var(--jkt-coral);
}

.jkt-range-nav__btn:focus-visible {
	outline: 2px solid var(--jkt-coral);
	outline-offset: 2px;
}

/*
 * Disabled at the ends rather than hidden, so the header does not reflow as
 * the visitor pages through the strip.
 */
.jkt-range-nav__btn:disabled {
	opacity: .3;
	cursor: default;
}

.jkt-range-nav__icon { display: block; }

/* The drag always worked — say so. */
.jkt-home-range .swiper.jkt-range-grab { cursor: grab; }
.jkt-home-range .swiper.jkt-range-grab:active { cursor: grabbing; }

@media (max-width: 900px) {
	/* The head stacks at this width; keep the CTA and arrows on one line. */
	.jkt-range-actions { gap: 12px; }
	.jkt-range-nav__btn { width: 42px; height: 42px; }
}

/* --------------------------------------------------------------------------
   News cards — featured image, fallback, and internal rhythm
   --------------------------------------------------------------------------
   The card's image area was a static striped placeholder. It now carries the
   post's featured image, bound with Oxygen's own
   [breakdance_dynamic field='post_featured_image_url'] inside the EXISTING
   placeholder text node — a content-only template edit, no new elements.

   The stripes live on .jkt-news__media as a background-image, and that element
   is already position:relative with aspect-ratio 16/10. So the image is simply
   layered over it: when a post has a featured image it covers the stripes, and
   when it has none the shortcode resolves to an empty src, the image is hidden
   and the original placeholder shows through untouched. The fallback needs no
   separate markup and cannot drift out of sync.
   -------------------------------------------------------------------------- */

.jkt-news__img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	/*
	 * cover, not contain. These are editorial photographs — cropping is
	 * correct. The product cards use contain with a white mat because a pack
	 * shot must never be cut; that treatment would be wrong here.
	 */
	object-fit: cover;
	object-position: center;
	display: block;
}

/* No featured image: hide the image, leave the striped placeholder visible. */
.jkt-news__img[src=""],
.jkt-news__img:not([src]) {
	display: none;
}

/* Featured image present: the "PHOTO: TO COME" label would sit on top of it. */
.jkt-news__img:not([src=""]) ~ .jkt-ph__label {
	display: none;
}

/*
 * The date badge is position:absolute on .jkt-news__media and comes after the
 * image in source order, so it paints above the photo in both states. A stop
 * of shade keeps it legible over a light photograph without a hard chip.
 */
.jkt-news__media::after {
	content: "";
	position: absolute;
	top: 0;
	right: 0;
	left: 0;
	height: 96px;
	background: linear-gradient(to bottom, rgba(15, 24, 55, .55), rgba(15, 24, 55, 0));
	pointer-events: none;
}

/* --------------------------------------------------------------------------
   Internal spacing
   --------------------------------------------------------------------------
   The excerpt is a <p>, and nothing had reset its browser default margin — so
   15px was stacking on top of .jkt-news__body's 14px flex gap above the text
   and on top of its 32px bottom padding below it. That unreset UA margin, not
   a builder value, was the whole of the "excessive space" problem: there was no
   competing author rule to remove.

   Resetting it here leaves the builder's own gap and padding as the single
   owner of the card's rhythm, and brings the card in line with the product
   cards' tighter title-to-copy relationship.
   -------------------------------------------------------------------------- */

.jkt-news__body > *,
.jkt-news__text > p {
	margin-top: 0;
	margin-bottom: 0;
}

/* Multi-paragraph excerpts still need separating. */
.jkt-news__text > p + p {
	margin-top: 10px;
}

/*
 * Cards fill their grid cell so a short post and a long one end level. The
 * body grows to take up the slack rather than leaving the card bottom ragged.
 */
.jkt-news__card {
	height: 100%;
}

.jkt-news__body {
	flex-grow: 1;
}

/* --------------------------------------------------------------------------
   [jkt_contact_map] — location map, consent gate, directions CTA
   --------------------------------------------------------------------------
   The embed is blocked until marketing consent is given (Complianz's
   data-category / data-src-cmplz contract), so this has to look right in two
   states: the blocked placeholder Complianz injects, and the live map.

   The directions CTA is a plain link and is never gated — see the note in
   includes/shortcodes/contact-map.php.
   -------------------------------------------------------------------------- */

.jkt-findus {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 18px;
}

.jkt-findus__label {
	margin: 0;
	color: var(--jkt-coral);
	font-family: var(--jkt-font-mono);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: .14em;
	text-transform: uppercase;
}

.jkt-findus__frame {
	position: relative;
	width: 100%;
	height: var(--jkt-map-h, 430px);
	border: 1px solid var(--jkt-hair);
	background-color: var(--jkt-navy-deep);
	overflow: hidden;
}

.jkt-findus__embed {
	display: block;
	width: 100%;
	height: 100%;
	border: 0;
	/*
	 * Google's map is a bright white tile sheet dropped into a navy page. A
	 * light touch of desaturation settles it without making it hard to read —
	 * it still has to work as a map.
	 */
	filter: saturate(.85) contrast(.95);
}

/* An iframe with no src yet would otherwise reserve a blank white box. */
.jkt-findus__embed:not(.cmplz-activated) {
	visibility: hidden;
}

.jkt-findus__address {
	margin: 0;
	color: var(--jkt-ice-label);
	font-family: var(--jkt-font-mono);
	font-size: 11px;
	letter-spacing: .08em;
	text-transform: uppercase;
}

/* Coral primary CTA, same weight as the checkout button. */
.jkt-findus__cta {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	padding: 17px 40px;
	background-color: var(--jkt-coral);
	border: 1px solid var(--jkt-coral);
	color: #fff;
	font-family: var(--jkt-font-mono);
	font-size: 12px;
	font-weight: 500;
	letter-spacing: .12em;
	text-transform: uppercase;
	text-decoration: none;
	transition: background-color .2s ease, color .2s ease, border-color .2s ease;
}

.jkt-findus__cta:hover,
.jkt-findus__cta:focus-visible {
	background-color: var(--jkt-ice);
	border-color: var(--jkt-ice);
	color: #16181c;
}

.jkt-findus__cta:focus-visible {
	outline: 2px solid var(--jkt-ice);
	outline-offset: 3px;
}

/* --------------------------------------------------------------------------
   Blocked state — our own placeholder, not Complianz's

   Complianz only injects its "click to accept" notice into embeds its own
   output filter blocked. We ship the iframe pre-blocked (safer: it can never
   leak to Google), so that filter never touches it and no notice appears.
   This is that notice, in our own tokens.

   It sits as a sibling AFTER the iframe, so the general sibling combinator
   can retire it the moment Complianz activates the embed.
   -------------------------------------------------------------------------- */

.jkt-findus__blocked {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 14px;
	padding: 24px;
	text-align: center;
	background-color: var(--jkt-navy-deep);
	/* Same hairline weave as the other "to come" placeholders on the site. */
	background-image: repeating-linear-gradient(
		45deg,
		rgba(238, 242, 245, .05) 0 12px,
		rgba(238, 242, 245, .02) 12px 24px
	);
}

/* Consent given — Complianz adds .cmplz-activated to the iframe. */
.jkt-findus__embed.cmplz-activated ~ .jkt-findus__blocked {
	display: none;
}

.jkt-findus__blocked-label {
	margin: 0;
	color: var(--jkt-coral);
	font-family: var(--jkt-font-mono);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: .14em;
	text-transform: uppercase;
}

.jkt-findus__blocked-text {
	margin: 0;
	max-width: 46ch;
	color: var(--jkt-ice-body);
	font-family: var(--jkt-font-body);
	font-size: 14px;
	line-height: 1.6;
}

.jkt-findus__blocked-alt {
	margin: 0;
	color: var(--jkt-ice-label);
	font-family: var(--jkt-font-mono);
	font-size: 10px;
	letter-spacing: .1em;
	text-transform: uppercase;
}

/*
 * Deliberately the ice outline button, not the coral one. Coral is reserved
 * for the page's primary action, which here is Get directions — the thing
 * that works for everyone. Accepting cookies should not out-shout it.
 */
.jkt-findus__accept {
	appearance: none;
	padding: 13px 26px;
	background-color: transparent;
	border: 1px solid var(--jkt-ice);
	color: var(--jkt-ice);
	font-family: var(--jkt-font-mono);
	font-size: 11px;
	font-weight: 500;
	letter-spacing: .12em;
	text-transform: uppercase;
	cursor: pointer;
	transition: background-color .2s ease, color .2s ease;
}

.jkt-findus__accept:hover,
.jkt-findus__accept:focus-visible {
	background-color: var(--jkt-ice);
	color: #16181c;
}

.jkt-findus__accept:focus-visible {
	outline: 2px solid var(--jkt-ice);
	outline-offset: 3px;
}

@media (max-width: 700px) {
	.jkt-findus__frame { height: min(var(--jkt-map-h, 430px), 260px); }
	.jkt-findus__cta { width: 100%; justify-content: center; padding: 16px 20px; }
}
