/**
 * Faltec X — Home motion layer (desktop motion system, approved 2026-09-28).
 *
 * Layered onto the approved static design; the final state of every effect is
 * the static composition.
 * - Ownership: CSS-owned entrances apply under html.fx-motion (.fx-m-seq,
 *   .fx-m-statement); GSAP-owned states (Hero entrance, heading reveals,
 *   magnetic CTAs) apply under html.fx-intro / html.fx-gsap. No element has
 *   both kinds of hook.
 * - Content is never hidden with JavaScript off or with reduced motion.
 * - Services stack: CSS sticky plus scroll-driven recede (progressive).
 * - Reduced motion: the static design, instantly.
 * Only transform, translate, scale, opacity and clip-path are animated.
 */

:root {
	--fx-motion-ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* =========================================================
   Level 3 — micro-interactions
   ========================================================= */

/* Services index rows: one shared treatment for all five rows. Rows 2–5 are
   links; row 1 (Web Design Adelaide, this page) is not, but responds the same. */
.elementor .fx-service-row :is(h2, h3),
.elementor .fx-service-row p {
	transition: translate 0.4s var(--fx-motion-ease), color 0.3s ease;
}

.elementor .fx-service-row::after {
	content: '';
	position: absolute;
	inset-inline: 0;
	inset-block-end: -1px;
	height: 1px;
	background: var(--fx-grad-signal);
	transform: scaleX(0);
	transform-origin: left center;
	transition: transform 0.6s var(--fx-motion-ease);
}

.elementor .fx-service-row > p:last-child {
	transition: translate 0.4s var(--fx-motion-ease), background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}

.elementor .fx-service-row:is(:hover, :focus-visible) :is(h2, h3) {
	translate: 0.375rem 0;
}

.elementor .fx-service-row:is(:hover, :focus-visible) > p:nth-child(2) {
	color: var(--fx-link-water);
}

.elementor .fx-service-row:is(:hover, :focus-visible)::after {
	transform: scaleX(1);
}

.elementor .fx-service-row:is(:hover, :focus-visible) > p:last-child {
	translate: 0.25rem 0;
	border-color: transparent;
	background-color: var(--fx-accent-blue);
	color: var(--fx-navy);
}

.elementor a.fx-service-row:focus-visible {
	outline-offset: 0.5rem;
}

/* Pricing — Business card only: light moving over the surface. Two layers
   follow the pointer (eased position from fx-motion.js as --fx-px/--fx-py):
   a soft blue-white specular core and a wide, faint blue falloff, screened
   onto the unchanged resting background. Drawn behind the content; the card's
   chamfer (clip-path) and overflow clip it. The card lifts like the others
   (shared .fx-card hover). Keyboard focus inside the card shows a static,
   gentler light. */
.elementor .fx-card-featured.fx-card {
	position: relative;
	isolation: isolate;
}

.elementor .fx-card-featured.fx-card::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	pointer-events: none;
	background:
		radial-gradient(
			14rem circle at var(--fx-px, 50%) var(--fx-py, 0%),
			rgba(225, 244, 255, 0.17) 0%,
			rgba(160, 220, 255, 0.07) 45%,
			rgba(160, 220, 255, 0) 100%
		),
		radial-gradient(
			34rem circle at var(--fx-px, 50%) var(--fx-py, 0%),
			rgba(0, 157, 255, 0.1) 0%,
			rgba(0, 157, 255, 0.035) 50%,
			rgba(0, 157, 255, 0) 100%
		);
	mix-blend-mode: screen;
	opacity: 0;
	transition: opacity 0.6s var(--fx-motion-ease);
}

@media (hover: hover) and (pointer: fine) {
	.elementor .fx-card-featured.fx-card:hover::before {
		opacity: 1;
	}
}

.elementor .fx-card-featured.fx-card:has(:focus-visible)::before {
	--fx-px: 50%;
	--fx-py: 0%;
	opacity: 0.7;
}

/* FAQ: the icon ring responds before the item opens (closed items only; an
   open item keeps its neutral gradient ring). */
.elementor .fx-faq-item:not([open]) summary:is(:hover, :focus-visible) .fx-faq-icon {
	border-color: var(--fx-accent-blue);
	color: var(--fx-accent-blue);
}

/* Touch screens: no hover movement. */
@media (hover: none) {
	.elementor .fx-service-row:hover :is(h2, h3),
	.elementor .fx-service-row:hover > p:last-child {
		translate: none;
	}
}

/* =========================================================
   Stage 1 — GSAP-owned states (see js/modules/)
   ========================================================= */

/* Hero guard: html.fx-intro (added by the head guard, motion-allowed visitors
   only) hides the Hero copy until the entrance takes over (.fx-intro-run).
   Failsafe: the copy shows by itself after 0.95s, and the head guard's timer
   releases the class after ~0.85s from first paint. The lead and the
   supporting paragraph (the Hero's largest text blocks, so its LCP) are never
   hidden: they are present from first paint; only the supporting paragraph's
   short hairline (--fx-hl, its scaleX) is held for the entrance to draw. */
@keyframes fx-intro-failsafe {
	to { opacity: 1; }
}

.fx-intro:not(.fx-intro-run) .elementor .fx-m-hero > :not(.fx-lead, .fx-hairline-top) {
	opacity: 0;
	animation: fx-intro-failsafe 0.01s linear 0.95s forwards;
}

@keyframes fx-intro-failsafe-hairline {
	to { --fx-hl: 1; }
}

.fx-intro:not(.fx-intro-run) .elementor .fx-m-hero > .fx-hairline-top {
	--fx-hl: 0;
	animation: fx-intro-failsafe-hairline 0.01s linear 0.95s forwards;
}

/* Heading reveals: kept invisible (layout unchanged) until the reveal runs.
   Opacity, not visibility, so the heading stays in the accessibility tree
   (screen-reader heading navigation finds headings below the fold). */
.fx-gsap .elementor .fx-r-wait {
	opacity: 0;
}

/* Split words (fx-split.js). Masked words clip their rising inner text; the
   clip region extends past the word box so glyphs and italics are never cut
   in the final position. */
.elementor .fx-w,
.elementor .fx-w-in {
	display: inline-block;
}

.elementor .fx-w-mask {
	clip-path: inset(-0.25em -0.2em -0.12em -0.2em);
}

/* B. Signal reveal: the travelling light. */
.elementor .fx-r-signal {
	position: relative;
}

.elementor .fx-signal-bar {
	position: absolute;
	inset-block: -0.12em -0.18em;
	inset-inline-start: 0;
	width: 2px;
	pointer-events: none;
	background: linear-gradient(180deg, var(--fx-secondary), var(--fx-accent-blue));
	box-shadow: 0 0 14px 1px rgba(0, 157, 255, 0.45);
	will-change: transform;
}

.elementor .fx-section-dark .fx-signal-bar {
	background: linear-gradient(180deg, var(--fx-accent), var(--fx-accent-blue));
	box-shadow: 0 0 16px 1px rgba(0, 246, 255, 0.5);
}

/* Magnetic CTAs: the offset is written every frame, so `translate` must not
   transition (other button transitions are kept). */
.elementor .fx-magnetic.fx-mag-on {
	transition: box-shadow 0.3s var(--fx-ease-out), color 0.3s, background-color 0.45s var(--fx-ease-out);
}

.elementor .fx-mag-label {
	display: inline-block;
}

/* =========================================================
   Level 2 — CSS-owned entrances (only under html.fx-motion)
   ========================================================= */

.fx-motion .elementor .fx-m-seq > * {
	transition: opacity 0.8s var(--fx-motion-ease), transform 0.9s var(--fx-motion-ease);
}

.fx-motion .elementor .fx-m-seq:not(.is-in) > * {
	opacity: 0;
	transform: translateY(16px);
}

/* Sequenced groups: 70ms apart, reading order. */
.fx-motion .elementor .fx-m-seq > :nth-child(2) { transition-delay: 0.07s; }
.fx-motion .elementor .fx-m-seq > :nth-child(3) { transition-delay: 0.14s; }
.fx-motion .elementor .fx-m-seq > :nth-child(4) { transition-delay: 0.21s; }
.fx-motion .elementor .fx-m-seq > :nth-child(5) { transition-delay: 0.28s; }
.fx-motion .elementor .fx-m-seq > :nth-child(6) { transition-delay: 0.35s; }

/* Pricing cards: the entrance (opacity/transform, Business card lands last)
   and the shared hover lift + elevation (translate/box-shadow, all three
   cards) keep separate timings, so hover eases in and out smoothly and never
   inherits the entrance stagger. */
.elementor .fx-price-grid .fx-card {
	transition: translate 0.5s var(--fx-motion-ease), box-shadow 0.5s var(--fx-motion-ease);
}

/* Business card elevation: its chamfer (clip-path) would cut a box-shadow
   into stray corner shapes, so the card carries none. Instead its wrapper
   (.fx-card-lift) casts a drop-shadow that follows the chamfered outline and
   the lift, matching the perceived elevation of the other two cards. */
.elementor .fx-price-grid .fx-card-featured:is(:hover, :focus-visible) {
	box-shadow: none;
}

.elementor .fx-card-lift {
	filter: none;
	transition: filter 0.5s var(--fx-motion-ease);
}

.elementor .fx-card-lift:has(> .fx-card:is(:hover, :focus-visible)) {
	filter: drop-shadow(0 18px 22px rgba(34, 48, 63, 0.16));
}

.fx-motion .elementor .fx-price-grid.fx-m-seq > .fx-card {
	transition:
		opacity 0.8s var(--fx-motion-ease) var(--fx-seq-delay, 0s),
		transform 0.9s var(--fx-motion-ease) var(--fx-seq-delay, 0s),
		translate 0.5s var(--fx-motion-ease),
		box-shadow 0.5s var(--fx-motion-ease);
}

.fx-motion .elementor .fx-price-grid.fx-m-seq > .fx-card-lift {
	transition:
		opacity 0.8s var(--fx-motion-ease) var(--fx-seq-delay, 0s),
		transform 0.9s var(--fx-motion-ease) var(--fx-seq-delay, 0s),
		filter 0.5s var(--fx-motion-ease);
}

.fx-motion .elementor .fx-price-grid.fx-m-seq > :nth-child(2) { --fx-seq-delay: 0.16s; }
.fx-motion .elementor .fx-price-grid.fx-m-seq > :nth-child(3) { --fx-seq-delay: 0.08s; }

/* Approach questions (.fx-m-wipe): each question row — its hairline and text
   together — wipes in from the left, in reading order. Numbers stay static. */
.fx-motion .elementor .fx-m-wipe > * {
	transition: clip-path 0.6s var(--fx-motion-ease) var(--fx-d, 0s);
}

.fx-motion .elementor .fx-m-wipe:not(.is-in) > * {
	clip-path: inset(0 100% 0 0);
}

.fx-motion .elementor .fx-m-wipe.is-in > * {
	clip-path: inset(0 0 0 0);
}

/* Services list (.fx-m-list): each title rises through its own mask, then its
   description and arrow appear; rows in reading order. The entrance uses
   `transform`, clip-path and opacity, so the hover (`translate`, colours)
   keeps its own timings and is never delayed. */
.fx-motion .elementor .fx-m-list .fx-service-row :is(h2, h3) {
	transition:
		translate 0.4s var(--fx-motion-ease),
		color 0.3s ease,
		transform 0.8s var(--fx-motion-ease) var(--fx-d, 0s),
		clip-path 0.8s var(--fx-motion-ease) var(--fx-d, 0s);
}

.fx-motion .elementor .fx-m-list:not(.is-in) .fx-service-row :is(h2, h3) {
	transform: translateY(0.45em);
	clip-path: inset(0 -0.1em 100% -0.1em);
}

.fx-motion .elementor .fx-m-list.is-in .fx-service-row :is(h2, h3) {
	clip-path: inset(-0.15em -0.1em -0.2em -0.1em);
}

.fx-motion .elementor .fx-m-list .fx-service-row > p:nth-child(2) {
	transition:
		translate 0.4s var(--fx-motion-ease),
		color 0.3s ease,
		opacity 0.5s var(--fx-motion-ease) calc(var(--fx-d, 0s) + 0.12s);
}

.fx-motion .elementor .fx-m-list .fx-service-row > p:last-child {
	transition:
		translate 0.4s var(--fx-motion-ease),
		background-color 0.3s ease,
		border-color 0.3s ease,
		color 0.3s ease,
		opacity 0.5s var(--fx-motion-ease) calc(var(--fx-d, 0s) + 0.12s);
}

.fx-motion .elementor .fx-m-list:not(.is-in) .fx-service-row > p {
	opacity: 0;
}

/* Inclusions (.fx-m-build): each row — divider and text — builds in from
   the left; as it lands its gradient dot activates with one restrained ring.
   The final clip leaves room for the ring. The note box stays static. */
.fx-motion .elementor .fx-m-build > * {
	transition: clip-path 0.5s var(--fx-motion-ease) var(--fx-d, 0s);
}

.fx-motion .elementor .fx-m-build:not(.is-in) > * {
	clip-path: inset(0 100% 0 0);
}

.fx-motion .elementor .fx-m-build.is-in > * {
	clip-path: inset(-0.75rem -0.75rem -0.75rem -0.75rem);
}

.fx-motion .elementor .fx-m-build .fx-dot {
	position: relative;
	transition: transform 0.45s var(--fx-motion-ease) calc(var(--fx-d, 0s) + 0.25s);
}

.fx-motion .elementor .fx-m-build:not(.is-in) .fx-dot {
	transform: scale(0.4);
}

@keyframes fx-dot-ring {
	from { opacity: 0.55; transform: scale(1); }
	to { opacity: 0; transform: scale(2.6); }
}

.fx-motion .elementor .fx-m-build .fx-dot::after {
	content: '';
	position: absolute;
	inset: 0;
	border-radius: 50%;
	border: 1px solid var(--fx-accent-blue);
	opacity: 0;
	pointer-events: none;
}

.fx-motion .elementor .fx-m-build.is-in .fx-dot::after {
	animation: fx-dot-ring 0.7s ease-out calc(var(--fx-d, 0s) + 0.3s) 1 both;
}

/* Reading-order stagger for the three groups above. */
.fx-motion .elementor .fx-m-wipe > :nth-child(2) { --fx-d: 0.07s; }
.fx-motion .elementor .fx-m-wipe > :nth-child(3) { --fx-d: 0.14s; }
.fx-motion .elementor .fx-m-wipe > :nth-child(4) { --fx-d: 0.21s; }
.fx-motion .elementor .fx-m-wipe > :nth-child(5) { --fx-d: 0.28s; }
.fx-motion .elementor .fx-m-wipe > :nth-child(6) { --fx-d: 0.35s; }

.fx-motion .elementor .fx-m-list > :nth-child(2) { --fx-d: 0.08s; }
.fx-motion .elementor .fx-m-list > :nth-child(3) { --fx-d: 0.16s; }
.fx-motion .elementor .fx-m-list > :nth-child(4) { --fx-d: 0.24s; }
.fx-motion .elementor .fx-m-list > :nth-child(5) { --fx-d: 0.32s; }

.fx-motion .elementor .fx-m-build > :nth-child(2) { --fx-d: 0.06s; }
.fx-motion .elementor .fx-m-build > :nth-child(3) { --fx-d: 0.12s; }
.fx-motion .elementor .fx-m-build > :nth-child(4) { --fx-d: 0.18s; }
.fx-motion .elementor .fx-m-build > :nth-child(5) { --fx-d: 0.24s; }
.fx-motion .elementor .fx-m-build > :nth-child(6) { --fx-d: 0.3s; }
.fx-motion .elementor .fx-m-build > :nth-child(7) { --fx-d: 0.36s; }
.fx-motion .elementor .fx-m-build > :nth-child(8) { --fx-d: 0.42s; }
.fx-motion .elementor .fx-m-build > :nth-child(9) { --fx-d: 0.48s; }
.fx-motion .elementor .fx-m-build > :nth-child(10) { --fx-d: 0.54s; }

/* Process scroll progression (fx-process.js): a 1px track + cyan → blue fill
   above each step (scaleX = --fx-fill), and quiet → strong → settled step
   numbers. Present only while the module runs (.fx-p-on). The pseudo-element
   insets override Elementor's own .e-con::before overlay rule. */
.elementor .fx-process.fx-p-on > * {
	position: relative;
}

.elementor .fx-process.fx-p-on > *::before,
.elementor .fx-process.fx-p-on > *::after {
	content: '';
	position: absolute;
	top: var(--fx-rail-top, -1.75rem);
	right: 0;
	bottom: auto;
	left: 0;
	width: auto;
	height: var(--fx-rail-h, 1px);
	pointer-events: none;
}

.elementor .fx-process.fx-p-on > *::before {
	background: rgba(34, 48, 63, 0.1);
}

.elementor .fx-process.fx-p-on > *::after {
	background: linear-gradient(90deg, var(--fx-accent) 0%, var(--fx-accent-blue) 100%);
	transform: scaleX(var(--fx-fill, 0));
	transform-origin: 0 50%;
}

.elementor .fx-process.fx-p-live > *::after {
	will-change: transform;
}

/* Inline progression steps: phrases (<span>) inside a paragraph, e.g. the
   Website Redesign assessment scan. Instead of a rail, a signal underline
   fills along each phrase (continuous across line breaks) over a faint
   baseline, and the phrase firms up to navy as it activates. Inactive
   phrases keep the paragraph's own colour, so contrast never drops. */
.elementor .fx-process.fx-p-on > span::before,
.elementor .fx-process.fx-p-on > span::after {
	content: none;
}

.elementor .fx-process.fx-p-on > span {
	padding-bottom: 0.1em;
	background-image:
		linear-gradient(90deg, var(--fx-accent) 0%, var(--fx-accent-blue) 100%),
		linear-gradient(var(--fx-hairline-strong), var(--fx-hairline-strong));
	background-size: calc(var(--fx-fill, 0) * 100%) 2px, 100% 1px;
	background-position: 0 100%, 0 100%;
	background-repeat: no-repeat;
	transition: color 0.45s var(--fx-motion-ease);
}

.elementor .fx-process.fx-p-on > span:is(.is-active, .is-done) {
	color: var(--fx-navy);
}

/* Optional content dimming for upcoming steps (set per grid; unset on Home,
   where titles and body copy are never dimmed). */
.elementor .fx-process.fx-p-on > * > :not(:first-child) {
	transition: opacity 0.5s var(--fx-motion-ease), translate 0.5s var(--fx-motion-ease);
}

.elementor .fx-process.fx-p-on > .is-upcoming > :not(:first-child) {
	opacity: var(--fx-p-dim, 1);
	translate: 0 var(--fx-p-shift, 0);
}

.elementor .fx-process.fx-p-on > * > :first-child {
	transition: opacity 0.45s var(--fx-motion-ease);
}

.elementor .fx-process.fx-p-on > .is-upcoming > :first-child {
	opacity: var(--fx-p-num-up, 0.25);
}

.elementor .fx-process.fx-p-on > .is-done > :first-child {
	opacity: var(--fx-p-num-done, 0.55);
}

/* Adelaide statement: revealed top to bottom as one element. */
.fx-motion .elementor .fx-m-statement {
	clip-path: inset(-0.25em -0.25em -0.35em -0.25em);
	transition: clip-path 1s var(--fx-motion-ease), transform 1s var(--fx-motion-ease);
}

.fx-motion .elementor .fx-m-statement:not(.is-in) {
	clip-path: inset(-0.25em -0.25em 100% -0.25em);
	transform: translateY(0.4em);
}

/* Photo-ready background layers: slow parallax (desktop, scroll-driven CSS). */
@keyframes fx-parallax {
	from { translate: 0 calc(-1 * var(--fx-par, 6%)); }
	to { translate: 0 var(--fx-par, 6%); }
}

@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) and (min-width: 1025px) {
		.elementor .fx-m-parallax {
			animation: fx-parallax linear both;
			animation-timeline: view();
		}
	}
}

/* =========================================================
   Level 1 — Services settling stack (desktop, CSS only)
   ========================================================= */

@keyframes fx-recede {
	to { transform: scale(0.98); }
}

@media (prefers-reduced-motion: no-preference) and (min-width: 1025px) {
	.elementor .fx-stack-deck > .fx-stack-card {
		position: sticky;
		transform-origin: 50% 0;
	}

	/* All three cards settle at the same point below the header, so each one
	   slides up over the last and card 3 finally covers cards 1 and 2
	   completely (fx-motion.js keeps card 3 at least as tall as the cards it
	   covers, and makes the three release together). */
	.elementor .fx-stack-deck > .fx-stack-card {
		top: 5.5rem;
	}

	/* The hold: once card 3 has settled, the completed stack stays together
	   for a short stretch before the section releases it. The spacer is an
	   empty block after the cards (zero height in the static design). */
	.elementor .fx-stack-deck > :not(.fx-stack-card) {
		height: 18vh;
	}

	/* Tightened 2026-10-05: a short framed hold, then the next section arrives
	   right below the settled card instead of after a long empty dark band. */
	.elementor .fx-section:has(> .fx-container .fx-stack-deck:not(.fx-stack-off)) {
		padding-block-end: 3.5rem !important;
	}

	/* Short screens (fx-motion.js): the last card too tall to be held in full
	   view → no hold; a covered card too tall → the approved static layout
	   (normal flow). */
	.elementor .fx-stack-deck.fx-stack-nohold > :not(.fx-stack-card) {
		height: 0;
	}

	.elementor .fx-stack-deck.fx-stack-off > .fx-stack-card {
		position: relative;
		top: auto;
		animation: none;
	}

	.elementor .fx-stack-deck.fx-stack-off > :not(.fx-stack-card) {
		height: 0;
	}

	@supports (animation-timeline: view()) and (timeline-scope: --fx-a) {
		.elementor .fx-stack-deck {
			timeline-scope: --fx-card-2, --fx-card-3;
		}

		.elementor .fx-stack-deck > .fx-stack-card:nth-child(2) { view-timeline-name: --fx-card-2; }
		.elementor .fx-stack-deck > .fx-stack-card:nth-child(3) { view-timeline-name: --fx-card-3; }

		.elementor .fx-stack-deck > .fx-stack-card:nth-child(1) {
			animation: fx-recede linear both;
			animation-timeline: --fx-card-2;
			animation-range: entry 20% contain 0%;
		}

		.elementor .fx-stack-deck > .fx-stack-card:nth-child(2) {
			animation: fx-recede linear both;
			animation-timeline: --fx-card-3;
			animation-range: entry 20% contain 0%;
		}
	}
}

/* =========================================================
   Reduced motion: the approved static design, instantly.
   ========================================================= */

@media (prefers-reduced-motion: reduce) {
	.fx-intro .elementor .fx-m-hero > *,
	.fx-gsap .elementor .fx-r-wait {
		opacity: 1 !important;
		visibility: visible !important;
		animation: none !important;
	}

	.fx-motion .elementor .fx-m-seq > *,
	.fx-motion .elementor .fx-m-statement,
	.fx-motion .elementor :is(.fx-m-wipe, .fx-m-build) > *,
	.fx-motion .elementor .fx-m-list .fx-service-row :is(h2, h3, p),
	.fx-motion .elementor .fx-m-build .fx-dot {
		opacity: 1 !important;
		transform: none !important;
		clip-path: none !important;
		transition: none !important;
	}

	.fx-motion .elementor .fx-m-build .fx-dot::after {
		animation: none !important;
	}
}

/* Continuity line entrance (.fx-m-care, marked .is-in by fx-motion.js): the
   line draws once, then all status dots come on together (concurrent, not a
   sequence) with two restrained rings. Nothing loops. */
.fx-motion .elementor .fx-m-care::after {
	transition: transform 1.2s var(--fx-motion-ease);
}

.fx-motion .elementor .fx-m-care:not(.is-in)::after {
	transform: scaleX(0);
}

.fx-motion .elementor .fx-m-care > *::before {
	transition: transform 0.5s var(--fx-motion-ease) 0.55s, opacity 0.5s var(--fx-motion-ease) 0.55s;
}

.fx-motion .elementor .fx-m-care:not(.is-in) > *::before {
	opacity: 0.35;
	transform: scale(0.5);
}

.fx-motion .elementor .fx-m-care.is-in > *::after {
	animation: fx-dot-ring 1s ease-out 0.8s 2 both;
}

@media (max-width: 1024px) {
	.fx-motion .elementor .fx-m-care:not(.is-in)::after {
		transform: scaleY(0);
	}
}
