/* 
==================================================================
PROJECT: TimeTrax Landing Page Stylings
AUTHOR: Senior Frontend Architect
RESPONSIBILITY: Global Typography, Brand Colors, Custom Overrides
==================================================================
*/

/* --- 1. DESIGN SYSTEM VARIABLES (CSS CUSTOM PROPERTIES) --- */
:root {
  /* Brand Color Palette - Updated to match mockup's warm corporate orange identity */
  --tt-primary: #F37021;          /* Corporate Orange - main action accent */
  --tt-primary-hover: #D95D16;    /* Darker Orange for hover states */
  --tt-primary-rgb: 243, 112, 33; /* RGB version of primary for alpha overlays */
  --tt-primary-hover-rgb: 217, 93, 22; /* RGB of --tt-primary-hover */

  /* ------------------------------------------------------------------
     BOOTSTRAP'S DEFAULT LINK COLOUR, REPOINTED AT THE BRAND.

     bootstrap.min.css styles the bare element:
         a { color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity,1)) }
     and ships --bs-link-color-rgb: 13,110,253 - Bootstrap blue. Nothing in
     this site ever overrode it, so EVERY anchor that did not match one of our
     own component rules rendered blue: in-body links, prose links, anything
     newly written. The components (nav, footer, buttons, cards, crumbs) set
     their own colour and are unaffected by this.

     Fixing it here, once, at the token level is the whole point: the
     alternative is chasing each blue anchor with another `.some-class a`
     rule for ever, and missing the next one. Hard rule 3 - use the tokens,
     never hard-code a colour - applies to Bootstrap's tokens too.

     Both variables are needed. Bootstrap's hover is
         a:hover { --bs-link-color-rgb: var(--bs-link-hover-color-rgb) }
     so setting only the first leaves hover blue.
     ------------------------------------------------------------------ */
  --bs-link-color-rgb: var(--tt-primary-rgb);
  --bs-link-hover-color-rgb: var(--tt-primary-hover-rgb);
  /* The non-rgb pair as well: the bare <a> rule uses the rgb form above, but
     Bootstrap's own components (.btn-link, .link-* utilities) read these. */
  --bs-link-color: var(--tt-primary);
  --bs-link-hover-color: var(--tt-primary-hover);
  
  --tt-secondary: #2D2D2D;        /* Deep Charcoal for high-contrast text and content */
  --tt-secondary-hover: #1A1A1A;  
  
  --tt-dark: #2B2B2D;             /* Slate Charcoal */
  --tt-dark-rgb: 43, 43, 45;
  --tt-dark-light: #3E3E42;       
  
  --tt-muted: #5A5A5C;            /* Perfect readability grey */
  --tt-light: #F9FAFB;            /* Content backing light white */
  --tt-border: #E5E7EB;           
  
  --tt-top-bar-bg: #1C1C1E;       /* Mockup's deep dark top-bar charcoal background */
  --tt-success: #10B981;
  --tt-alert: #F59E0B;

  /* Brand Orange Accent Duo (deep/light tints of the same corporate orange) */
  --tt-accent-deep: #C1440E;
  --tt-accent-deep-rgb: 193, 68, 14;
  --tt-accent-light: #FFC178;
  --tt-accent-light-rgb: 255, 193, 120;

  /* Signature Brand Fills - FLAT, no gradients anywhere on the site.
     These four names are kept (they are referenced from every stylesheet) but
     now resolve to solid brand colours. Do not put a gradient back in them. */
  --tt-gradient-primary: var(--tt-primary);
  --tt-gradient-vivid: var(--tt-primary);
  --tt-gradient-dark: #1C1C1E;
  --tt-gradient-mesh: none;

  /* Glassmorphism Surface System */
  --tt-glass-bg: rgba(255, 255, 255, 0.55);
  --tt-glass-bg-soft: rgba(255, 255, 255, 0.72);
  --tt-glass-border: rgba(255, 255, 255, 0.5);
  --tt-glass-blur: blur(20px) saturate(160%);
  --tt-glass-shadow: 0 8px 32px rgba(31, 38, 80, 0.1);
  --tt-glass-dark-bg: rgba(28, 28, 32, 0.55);
  --tt-glass-dark-border: rgba(255, 255, 255, 0.08);

  /* Spacing Grid (Consistent increments matching Bootstrap) */
  --tt-spacer-xs: 0.25rem;        /* 4px */
  --tt-spacer-sm: 0.5rem;         /* 8px */
  --tt-spacer-md: 1rem;           /* 16px */
  --tt-spacer-lg: 1.5rem;         /* 24px */
  --tt-spacer-xl: 3rem;           /* 48px */
  --tt-spacer-xxl: 5rem;          /* 80px */

  /* Unified section rhythm */
  --tt-section-space: clamp(3.2rem, 5vw, 5.2rem);
  --tt-section-space-tight: clamp(2.2rem, 3.5vw, 3.6rem);

  /* GAP BETWEEN THE FIXED HEADER AND THE FIRST FOLD.
     Every hero on the site clears the fixed header with a hard-coded pixel
     value; this is the breathing room added ON TOP of that clearance, and it
     is the ONLY thing that should sit between the two. It used to be a full
     --tt-section-space (51-83px) on the 54 .tt-hp-hero pages and a
     --tt-spacer-xl (48px) on the homepage, which read as a blank band under
     the navbar before the page had said anything.

     Section rhythm and fold clearance are different jobs. --tt-section-space
     separates two blocks of content that both have weight; nothing sits above
     a hero except the header, so the same value there is just empty screen at
     the most expensive point on the page. Change this token, not the
     individual calc()s - they all reference it. */
  --tt-fold-gap: 1.5rem;          /* 24px */

  /* Unified fluid type scale */
  --tt-fs-display: clamp(2.1rem, 4.2vw, 4.1rem);
  --tt-fs-h1: clamp(2rem, 3.9vw, 3.8rem);
  --tt-fs-h2: clamp(1.7rem, 2.8vw, 3rem);
  --tt-fs-h3: clamp(1.25rem, 2vw, 2rem);
  --tt-fs-h4: clamp(1.08rem, 1.55vw, 1.35rem);
  --tt-fs-body-lg: clamp(1rem, 1.35vw, 1.22rem);
  --tt-fs-body: clamp(0.95rem, 1.08vw, 1.06rem);
  --tt-fs-body-sm: clamp(0.84rem, 0.95vw, 0.95rem);

  /* Typography Hierarchy */
  --tt-font-primary: 'Inter', system-ui, -apple-system, sans-serif;
  --tt-font-secondary: 'Montserrat', system-ui, -apple-system, sans-serif;
  --tt-font-poppins: 'Poppins', system-ui, -apple-system, sans-serif;

  /* Transitions & Animation Curves */
  --tt-transition-fast: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  --tt-transition-standard: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  --tt-transition-spring: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
  
  /* Shadow Elevation Scales */
  --tt-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --tt-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -2px rgba(0, 0, 0, 0.08);
  --tt-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -4px rgba(0, 0, 0, 0.08);
  --tt-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
}

/* --- 2. BASE RESETS & NORMALIZATION --- */
html, body {
  font-family: var(--tt-font-primary);
  color: var(--tt-dark);
  background-color: #FCFAF8;
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  scroll-behavior: smooth;
  /* Fallback so #hash / nav-link jumps land BELOW the fixed header rather than
     tucked underneath it. script.js refines this to the exact measured header
     height (+12) once loaded; this covers the pre-JS window. */
  scroll-padding-top: 96px;
  /* `clip`, not `hidden`: both stop horizontal scrollbars, but `hidden`
     turns the element into a scroll container, which silently breaks
     `position: sticky` for ALL descendants (the mobile-app artwork freeze
     depends on it). `overflow-x: clip` clips the same overflow WITHOUT
     establishing a scroll container, so sticky keeps resolving against the
     viewport. Same reason .page-wrapper uses clip below. */
  overflow-x: clip;
}

/* Fixed ambient wash layer behind every glass surface on the page. Since
   gradients were removed site-wide --tt-gradient-mesh resolves to `none`, so
   this pseudo-element now paints nothing; it is kept as the hook the wash used
   to hang on. overflow-x: clip (not the Bootstrap .overflow-hidden utility that
   used to be in the markup) contains anything that bleeds past the edges while
   still allowing descendant position: sticky to work - see the note on
   html, body above. */
.page-wrapper {
  position: relative;
  overflow-x: clip;
}

.page-wrapper::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  background: var(--tt-gradient-mesh);
  pointer-events: none;
}

.main-header,
main,
.main-footer {
  position: relative;
  z-index: 1;
}

*, *:before, *:after {
  box-sizing: inherit;
}

/* Custom Scrollbar styling */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
::-webkit-scrollbar-track {
  background: var(--tt-light);
}
::-webkit-scrollbar-thumb {
  background: #CBD5E1;
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--tt-muted);
}

/* --- 3. TYPOGRAPHY CODES --- */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--tt-font-secondary);
  font-weight: 700;
  color: var(--tt-dark);
  line-height: 1.25;
  margin-top: 0;
  margin-bottom: 0.5em;
}

p {
  font-family: var(--tt-font-poppins);
  line-height: 1.625;
  color: var(--tt-muted);
  margin-top: 0;
  margin-bottom: 1.25rem;
}

/* Fluid responsive heading map */
h1 { font-size: var(--tt-fs-h1); }
h2 { font-size: var(--tt-fs-h2); }
h3 { font-size: var(--tt-fs-h3); }
h4 { font-size: var(--tt-fs-h4); }
h5 { font-size: clamp(1rem, 1.35vw, 1.18rem); }
h6 { font-size: clamp(0.9rem, 1vw, 1rem); }

/* Smooth body sizing baseline */
body,
p,
li,
a,
input,
textarea,
button {
  font-size: var(--tt-fs-body);
}

/* Focus Styling for Accessibility (WCAG compliant) */
a:focus-visible, 
button:focus-visible, 
input:focus-visible, 
select:focus-visible, 
textarea:focus-visible {
  outline: 3px solid rgba(var(--tt-primary-rgb), 0.5);
  outline-offset: 2px;
}

/* --- 4. GLOBAL SHARED CUSTOM UI COMPONENTS --- */

/* Primary Custom Action Button */
.btn-primary-custom {
  font-family: var(--tt-font-primary);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  color: #FFFFFF;
  background: var(--tt-gradient-primary);
  border: 1px solid transparent;
  padding: 0.75rem 1.75rem;
  border-radius: 10px;
  box-shadow: 0 8px 20px -6px rgba(var(--tt-primary-rgb), 0.45);
  transition: var(--tt-transition-fast);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  text-decoration: none;
  cursor: pointer;
}
.btn-primary-custom:hover {
  color: #FFFFFF;
  filter: brightness(1.06);
  transform: translateY(-2px);
  box-shadow: 0 14px 30px -6px rgba(var(--tt-primary-rgb), 0.55);
}
.btn-primary-custom:active {
  transform: translateY(0);
}

/* Secondary Custom Action Button */
.btn-secondary-custom {
  font-family: var(--tt-font-primary);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  color: var(--tt-dark);
  background-color: #FFFFFF;
  border: 1px solid var(--tt-border);
  padding: 0.75rem 1.75rem;
  border-radius: 8px;
  transition: var(--tt-transition-fast);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  text-decoration: none;
  cursor: pointer;
}
.btn-secondary-custom:hover {
  background-color: var(--tt-light);
  border-color: #CBD5E1;
  color: var(--tt-dark);
  transform: translateY(-1px);
}
.btn-secondary-custom:active {
  transform: translateY(0);
}

/* DARK FILL FOR A SECONDARY BUTTON - the hero's "View Case Studies"
   (index.html). Colour only: it rides on .btn-secondary-custom + .btn-hero-cta
   so its geometry stays identical to the Contact Sales button beside it.

   WHY NOT JUST USE .tt-btn-dark, which is already a black button? Because that
   one is a 999px pill with its own font-size, padding and min-height, and it
   is declared ~38,000 characters LATER in this file than .btn-hero-cta. Both
   are single-class selectors, so source order decides and .tt-btn-dark wins -
   the button would come out a pill, visibly mismatched against the 8px-radius
   primary next to it. This modifier overrides nothing but the three colour
   properties.

   #1C1C1E via --tt-gradient-dark is the SAME black as .tt-btn-dark, which is
   what the page's other "Explore Case Studies" button uses. The two CTAs point
   at the same place and should not be two different blacks. (The token is a
   solid colour despite the name - see hard rule 3; do not put a gradient back
   into it.)

   The :hover pair is required, not decoration: .btn-secondary-custom:hover
   repaints the background white-ish and would otherwise flash the button back
   to its light state on hover. */
.btn-secondary-custom.tt-btn-hero-dark {
  background-color: var(--tt-gradient-dark);
  border-color: var(--tt-gradient-dark);
  color: #FFFFFF;
}

.btn-secondary-custom.tt-btn-hero-dark:hover {
  background-color: var(--tt-gradient-dark);
  border-color: var(--tt-gradient-dark);
  color: #FFFFFF;
  filter: brightness(1.15);
}

/* Floating Animation Utility Helper */
@keyframes ttFloat {
  0% { transform: translateY(0px); }
  50% { transform: translateY(-8px); }
  100% { transform: translateY(0px); }
}

.animate-float {
  animation: ttFloat 4s ease-in-out infinite;
}

/* High visibility Section Titles Block */
.section-title-wrapper {
  margin-bottom: var(--tt-spacer-xl);
}
.section-title-wrapper .badge-tag {
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--tt-primary);
  background-color: rgba(var(--tt-primary-rgb), 0.1);
  padding: 0.35rem 0.85rem;
  border-radius: 50px;
  display: inline-block;
  margin-bottom: 0.75rem;
}
.section-title-wrapper .section-title {
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--tt-dark);
}
.section-title-wrapper .section-subtitle {
  font-size: 1.1rem;
  color: var(--tt-muted);
  max-width: 600px;
  margin: 0 auto;
}

/* Global fold spacing rhythm */
.tt-ticker-section,
.tt-modules-section,
.tt-testimonials-section,
.tt-awards-section,
.tt-integrations-section,
.tt-mobile-app-section,
.tt-contact-section {
  padding-bottom: var(--tt-section-space);
  padding-top: var(--tt-section-space);
}

/* Horizontal line custom separator */
.text-divider {
  border-top: 1px solid var(--tt-border);
  margin: var(--tt-spacer-xl) 0;
}

/* --- 5. TOP CONTACT STRIP & HEADER NAVIGATION --- */

/* Header Wrapper holding both Top Bar and Navbar */
.main-header {
  /* Declared directly (not left to Bootstrap's .fixed-top utility alone) so
     the header stays pinned to the top of the viewport regardless of that
     class - fixed-top is kept in the markup too, this is just belt-and-braces. */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  transition: var(--tt-transition-standard);
  z-index: 1030; /* Keep it above other elements but below modal alerts */
}

/* Top Contact Bar */
.tt-top-bar {
  background: var(--tt-gradient-dark);
  color: #FFFFFF;
  font-size: 0.85rem;
  font-family: var(--tt-font-poppins);
  font-weight: 400;
  letter-spacing: 0.01em;
  padding: 0.55rem 0;
  /* No max-height / overflow: hidden here any more. Those existed only to
     animate the strip's collapse on scroll, which it no longer does - and
     `overflow: hidden` would clip anything anchored to the strip, including
     the event registration popup that opens from the Register Now CTA. */
  transition: padding 0.35s ease;
}

.tt-top-bar a {
  /* A global `a { font-size: var(--tt-fs-body) }` rule elsewhere overrides
     whatever font-size is merely inherited from an ancestor - direct rules on
     the element itself always win over inheritance, regardless of ancestor
     specificity - so this has to be set explicitly here, not left to cascade
     down from .tt-top-bar. */
  font-family: var(--tt-font-poppins);
  font-size: 0.8rem;
  color: #FFFFFF;
  text-decoration: none;
  font-weight: 500;
  transition: var(--tt-transition-fast);
}

.tt-top-bar a:hover {
  color: var(--tt-primary);
}

.tt-top-bar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: relative;
}

.tt-top-bar-item {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Contact Badges Circles */
.tt-contact-icon {
  background-color: var(--tt-primary);
  color: #FFFFFF;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-right: 0.5rem;
  font-size: 0.7rem;
  flex-shrink: 0;
}

/* Webinar announcement - true-centered regardless of how wide the email/phone
   items on either side are, via absolute positioning rather than flex order. */
.tt-top-bar-webinar {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  align-items: center;
  gap: 0.55rem;
  white-space: nowrap;
  font-size: 0.82rem;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.88);
}

.tt-top-bar-webinar-icon {
  color: var(--tt-primary);
  font-size: 0.78rem;
}

.tt-top-bar-webinar-cta {
  font-size: 0.82rem;
  color: var(--tt-primary) !important;
  font-weight: 600 !important;
  text-decoration: underline !important;
  text-underline-offset: 2px;
}

.tt-top-bar-webinar-cta:hover {
  color: var(--tt-primary-hover) !important;
}

/* Main Navbar Wrapper - Glassmorphic frosted surface */
.tt-navbar {
  background-color: var(--tt-glass-bg-soft);
  backdrop-filter: var(--tt-glass-blur);
  -webkit-backdrop-filter: var(--tt-glass-blur);
  transition: var(--tt-transition-standard);
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--tt-border);
  box-shadow: none;
}

/* Brand Logo Layout styling */
.tt-logo-wrapper {
  display: flex;
  align-items: center;
  text-decoration: none;
  gap: 0.75rem;
}

.tt-logo-text-bold {
  font-family: var(--tt-font-secondary);
  font-weight: 800;
  font-size: 1.6rem;
  letter-spacing: -0.03em;
  color: var(--tt-dark);
  line-height: 1;
}

.tt-logo-text-orange {
  color: var(--tt-primary);
}

/* 44px, raised from 38px on 2026-09-21, and the number is picked to sit with
   the header CTAs rather than chosen for its own sake.

   .btn-book-call is font-size 0.95rem at Bootstrap's line-height 1.5 with
   0.55rem padding top and bottom, so it renders ~40px tall; .btn-login-header
   is ~42px with its 1px borders. At 38px the logo read as the smallest thing
   in a row it is supposed to anchor. At 44px it is the tallest element by a
   couple of pixels, which is what makes the row look aligned rather than
   merely centred - the navbar is already `align-items: center`, so the two
   were always on the same axis; the logo was just losing the size contest.

   CHANGE THE BUTTON PADDING OR FONT-SIZE AND THIS NUMBER IS STALE. */
.tt-header-logo {
  height: 44px;
  width: auto;
  display: block;
}

.tt-logo-sub {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #7A7A7C;
  line-height: 1;
  margin-top: 1px;
}

/* Custom Navbar Links alignment */
.tt-navbar .navbar-nav {
  gap: 1.75em;
}

.tt-navbar .nav-link {
  font-family: var(--tt-font-poppins);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  color: var(--tt-dark);
  padding: 0.5rem 0 !important;
  position: relative;
  transition: var(--tt-transition-fast);
}

.tt-navbar .nav-link:hover, 
.tt-navbar .nav-link.active {
  color: var(--tt-primary) !important;
}

/* Home active link underline indicator mimicking mockup */
.tt-navbar .nav-item {
  position: relative;
}

.tt-navbar .nav-link::after {
  content: '';
  position: absolute;
  width: 0;
  height: 2px;
  bottom: 0;
  left: 0;
  background-color: var(--tt-primary);
  transition: var(--tt-transition-fast);
}

.tt-navbar .nav-link:hover::after,
.tt-navbar .nav-link.active::after {
  width: 100%;
}

/* Action button inside navbar */
/* Contact Sales - takes over the bold gradient look the Login button used to have */
.btn-book-call {
  font-family: var(--tt-font-poppins);
  font-weight: 700;
  font-size: 0.95rem;
  background: var(--tt-gradient-primary);
  color: #FFFFFF !important;
  border: none;
  padding: 0.55rem 1.75rem;
  border-radius: 8px;
  box-shadow: 0 6px 14px -4px rgba(var(--tt-primary-rgb), 0.35);
  transition: var(--tt-transition-fast);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
}

.btn-book-call:hover {
  color: #FFFFFF !important;
  filter: brightness(1.06);
  transform: translateY(-1px);
  box-shadow: 0 10px 22px -4px rgba(var(--tt-primary-rgb), 0.45);
}

/* Login - same dark charcoal gradient as the top contact strip, not flat/solid black */
.btn-login-header {
  font-family: var(--tt-font-poppins);
  font-weight: 700;
  font-size: 0.95rem;
  background: var(--tt-gradient-dark);
  color: #FFFFFF !important;
  border: 1px solid rgba(255, 255, 255, 0.1);
  padding: 0.55rem 1.75rem;
  border-radius: 8px;
  box-shadow: 0 6px 14px -4px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  transition: var(--tt-transition-fast);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.btn-login-header:hover {
  color: #FFFFFF !important;
  filter: brightness(1.2);
  border-color: rgba(255, 255, 255, 0.22);
  transform: translateY(-1px);
  box-shadow: 0 10px 22px -4px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.12);
}

/* Mobile Toggler Customization */
.navbar-toggler {
  border: cubic-bezier(0.4, 0, 0.2, 1);
  padding: 0.25rem 0.5rem;
}

.navbar-toggler:focus {
  box-shadow: 0 0 0 3px rgba(var(--tt-primary-rgb), 0.25);
}

/* Slide Drawer on scroll behavior adjustments */
/* The contact strip STAYS in the sticky header - it only condenses.
   It carries the upcoming-webinar CTA (and the event registration popup that
   opens from it), so it has to remain reachable after the user has scrolled;
   a CTA that disappears the moment someone starts reading is not a CTA.

   This previously collapsed to max-height: 0 on scroll, shrinking the header
   by ~50px. Anything that positions against the header measures it live rather
   than assuming a fixed value (see script.js, benefits-showcase.js and
   mega-menu.js), so removing the shrink makes those calculations MORE stable,
   not less. Do not reintroduce a collapse here without re-checking them. */
.header-scrolled .tt-top-bar {
  padding-top: 0.3rem;
  padding-bottom: 0.3rem;
}

/* Solid, not frosted. The glass surface read as washed-out over busy content
   while scrolling; an opaque bar keeps the nav legible over every section.
   backdrop-filter stays on the base .tt-navbar rule but has nothing to do
   once the background is opaque. */
.header-scrolled .tt-navbar {
  background-color: #FFFFFF;
  border-bottom-color: rgba(16, 24, 40, 0.08);
  box-shadow: var(--tt-shadow-md);
  padding: 0.5rem 0;
}

/* Nav labels never break mid-item. "Become a Partner" and "Contact Us" are two
   and three words, and once the row got tight they wrapped onto three lines
   each and shoved the CTAs out of alignment. */
.tt-navbar .nav-link {
  white-space: nowrap;
}

/* Laptop squeeze (992-1199px): five longer nav labels plus two CTAs no longer
   fit at full size, so the row tightens rather than wrapping. Everything here
   is proportional trimming - nothing is hidden or moved. */
@media (min-width: 992px) and (max-width: 1199.98px) {
  .tt-navbar .navbar-nav {
    gap: 1.05em;
  }

  .tt-navbar .nav-link {
    font-size: 0.85rem;
  }

  .btn-book-call,
  .btn-login-header {
    font-size: 0.82rem;
    padding: 0.5rem 1rem;
  }

  /* Scaled with the desktop bump (38->44) so the laptop squeeze stays
     proportional: 33 was 87% of 38, 38 is 86% of 44. The buttons above shrink
     to 0.82rem/1rem padding here, so the logo tracks them down together. */
  .tt-header-logo {
    height: 38px;
  }

  .tt-navbar .navbar-nav {
    margin-left: 1rem !important;
    margin-right: 1rem !important;
  }
}

/* Between 1200 and 1400 the row still fits but with little air - ease the gap
   back in gradually instead of jumping straight to the full 1.75em. */
@media (min-width: 1200px) and (max-width: 1399.98px) {
  .tt-navbar .navbar-nav {
    gap: 1.35em;
  }

  .tt-navbar .nav-link {
    font-size: 0.9rem;
  }

  .btn-book-call,
  .btn-login-header {
    padding: 0.55rem 1.35rem;
  }
}

/* The drawer's brand mark is injected into the nav markup on every page, so it
   must be hidden by default - it only belongs in the mobile drawer. */
.tt-drawer-brand { display: none; }

/* ==================================================================
   MOBILE NAV — dark drawer sliding in from the right

   Bootstrap's collapse drops a white panel underneath the navbar, which
   over a light hero read as part of the page rather than as a menu. This
   turns the same markup into an off-canvas drawer: fixed to the right
   edge, dark, over a dimmed page.

   Driven by `body.tt-nav-open` (added on Bootstrap's show.bs.collapse and
   removed on hide.bs.collapse - see the drawer block in mega-menu.js), NOT
   by the `.show` class. Bootstrap adds `.show` only AFTER its height
   transition finishes, so animating off `.show` makes the drawer sit still
   for ~350ms and then jump. The events fire at the START of each direction,
   which is what a slide needs.

   Bootstrap's own height animation is neutralised below (display/height
   forced) because the drawer is a full-height fixed panel - there is no
   height to animate, only a transform.
   ================================================================== */
@media (max-width: 991.98px) {
  .tt-navbar .navbar-collapse,
  .tt-navbar .navbar-collapse.collapse:not(.show),
  .tt-navbar .navbar-collapse.collapsing {
    display: block !important;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(340px, 86vw);
    height: 100dvh;
    max-height: none;
    margin: 0;
    /* Enough top room to clear the contact strip and sit the drawer's own logo
       roughly level with the burger - the page header behind it is covered, so
       it does not need clearing in full. */
    padding: 4.5rem 1.15rem 1.75rem;
    background: var(--tt-gradient-dark);
    border-top: 0;
    border-left: 1px solid rgba(255, 255, 255, 0.09);
    box-shadow: -24px 0 60px -20px rgba(0, 0, 0, 0.55);
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Closed state. `visibility` keeps offscreen links out of the tab order. */
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 0.32s cubic-bezier(0.4, 0, 0.2, 1),
                visibility 0.32s linear;
  }

  body.tt-nav-open .tt-navbar .navbar-collapse {
    transform: translateX(0);
    visibility: visible;
  }

  /* Dimmed page behind the drawer. Sits BELOW the fixed header (z-index 1030)
     so the burger stays visible and tappable to close. */
  body.tt-nav-open::after {
    content: '';
    position: fixed;
    inset: 0;
    z-index: 1020;
    background: rgba(12, 12, 14, 0.55);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
  }

  body.tt-nav-open { overflow: hidden; }

  /* The drawer's own brand mark (injected by mega-menu.js). The page logo is
     behind the panel once it opens, so without this the drawer reads as an
     unlabelled dark sheet. */
  .tt-drawer-brand {
    display: block;
    padding-bottom: 0.85rem;
    margin-bottom: 0.35rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  }
  .tt-drawer-brand-img {
    display: block;
    width: 155px;
    max-width: 70%;
    height: auto;
  }

  /* The drawer is a descendant of .tt-navbar and comes AFTER the burger in the
     DOM, so by default it paints over it - leaving no way to close the menu.
     Lift ONLY the burger. The logo must stay under the drawer: it sits at the
     left edge, which the open drawer overlaps, and lifting it made the logo
     float on top of the dark panel. */
  .tt-navbar .navbar-toggler {
    position: relative;
    z-index: 2;
  }

  /* Burger flips to the brand colour while open, over the dark drawer edge. */
  body.tt-nav-open .navbar-toggler { color: var(--tt-primary); }

  .tt-navbar .navbar-nav {
    gap: 0;
    padding-left: 0;
  }

  /* Full-width rows with a real tap target instead of tight inline links. */
  .tt-navbar .nav-item {
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  }

  .tt-navbar .nav-link {
    align-items: center;
    color: rgba(255, 255, 255, 0.92);
    display: flex;
    justify-content: space-between;
    min-height: 48px;
    padding: 0.35rem 0.25rem !important;
  }

  /* Current page + keyboard focus only. `:hover` is deliberately NOT here -
     on a touch screen a tapped element keeps its :hover state until something
     else is tapped, so rows stayed highlighted with no pointer anywhere near
     them. Hover is re-added below, gated to devices that actually have one. */
  .tt-navbar .nav-link:focus-visible,
  .tt-navbar .nav-link.active { color: var(--tt-accent-light); }

  @media (hover: hover) {
    .tt-navbar .nav-link:hover { color: var(--tt-accent-light); }
  }

  .tt-navbar .nav-link::after {
    display: none; /* Hide hover underline style on mobile stacked items */
  }

  /* The caret sits at the far edge of the row, where a thumb expects it. */
  .tt-mega-caret {
    font-size: 0.75rem;
    margin-left: auto;
    opacity: 0.55;
  }

  .btn-book-call,
  .btn-login-header {
    width: 100%;
    margin-top: 0.5rem;
    justify-content: center;
    min-height: 46px;
  }

  .navbar-nav ~ .d-flex {
    flex-direction: column;
    padding-top: 0.9rem;
  }
}


/* --- 6. HERO SECTION STYLES (Fold 1) --- */

.tt-hero-section {
  /* 120px allocates for the stacked header + contact-strip heights; the gap on
     top of that is --tt-fold-gap, not --tt-spacer-xl (48px). */
  padding-top: calc(120px + var(--tt-fold-gap));
  padding-bottom: var(--tt-spacer-lg);
  background-color: transparent;
}

/* Ambient Radial Gels - these were soft radial glow blobs. Radial glows are
   gradients, so their fills are now `none` and they render nothing. The boxes
   are kept (the markup still references them) but paint no colour. */
.tt-hero-glow-1 {
  position: absolute;
  top: 5%;
  right: -8%;
  width: 620px;
  height: 620px;
  background: none;
  border-radius: 50%;
  filter: blur(10px);
  pointer-events: none;
  z-index: 1;
}

.tt-hero-glow-2 {
  position: absolute;
  bottom: -5%;
  left: -12%;
  width: 500px;
  height: 500px;
  background: none;
  border-radius: 50%;
  filter: blur(10px);
  pointer-events: none;
  z-index: 1;
}

/* Hero Typography Parameters - solid brand-orange accent for the focal point */
/* Hero kicker pill. Doubles as the semantic <h1> of the hero fold, so its
   default heading size/margins are reset and it is sized down to read as a
   small kicker badge above the big (now paragraph) headline. */
.tt-hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--tt-font-secondary);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  line-height: 1.2;
  text-transform: uppercase;
  color: var(--tt-primary);
  background: rgba(var(--tt-primary-rgb), 0.1);
  border: 1px solid rgba(var(--tt-primary-rgb), 0.22);
  padding: 0.4rem 0.9rem;
  border-radius: 8px;
  margin: 0 0 1.1rem;
}

.tt-hero-title {
  font-family: var(--tt-font-secondary);
  font-weight: 800;
  font-size: calc(2.2rem + 1.8vw); /* Responsive titles fluid boundary scale */
  letter-spacing: -0.03em;
  line-height: 1.15;
  color: var(--tt-dark);
}

/* index.html's hero headline ONLY (.tt-hero-lead is on that one page), held to
   a hard maximum of THREE LINES at every viewport.

   Sized in cqi - a percentage of THIS COLUMN's width - not vw, because the
   column width and the viewport width do not track each other here. The hero
   is col-lg-6, so crossing 992px HALVES the available width while a vw-based
   size simultaneously grows; that corner (a 456px column at the widest font
   the old rule produced) is where the headline blew out to five lines. Above
   1400px the .container caps at 1320px and stops widening, so a vw size also
   kept growing against a fixed box - the same bug at the other end.

   The 8.8cqi figure is MEASURED ON THIS PAGE, not guessed and not derived
   from a standalone test element - a standalone probe of the same string,
   font, weight and letter-spacing put the cliff at 0.1017 and was wrong by
   8%, which is enough to ship four lines. Measured against the real rendered
   .tt-hero-title, the wrap to a 4th line happens at 0.0942 x column width,
   and that ratio is identical at every column width from 288px (a 320px
   phone) to 696px (a tablet) - line count depends on the font-size:width
   ratio alone, which is what makes ONE cqi value cover every device.
   8.8cqi sits ~6.5% under that cliff.

   The 3.4rem maximum is not a safety guard, it is a layout fix: below 992px
   the hero column is full width (696px), so an uncapped ratio made the TABLET
   headline larger than the desktop one and put a 36% size jump either side of
   the 992px breakpoint. Capping both bands at 54.4px removes the inversion.
   A cap can only ever REDUCE the ratio, so it cannot add a line.

   Re-measure if the copy, the font or the column span changes: any of the
   three moves the cliff, and the margin here is only ~6%. The 1.5rem minimum
   never binds on a real device (8.8cqi is 25.3px on a 320px phone).

   text-wrap: balance is cosmetic here - measured at exactly the same cliff
   with and without it, so it evens the lines up without buying or costing a
   line. The plain font-size above is the pre-2022 fallback; browsers without
   container queries keep the old vw scale and may run to four lines. */
.tt-hero-lead {
  container-type: inline-size;
}
.tt-hero-lead .tt-hero-title {
  /* 0,2,0 - deliberately out-specifies the 0,1,0 .tt-hero-title override in
     the max-width 991.98px block below, so this holds at every breakpoint. */
  font-size: clamp(1.5rem, 8.8cqi, 3.4rem);
  text-wrap: balance;
}

/* Section-kit heroes (the 37 module pages, the 2 money pages, the 4 device
   pages and the 6 company/content pages) run a step smaller than index.html.
   Since the SEO h1 swap the big line is a <p>, not the heading, and at the old
   calc(2.2rem + 1.8vw) it out-shouted both the kicker and the CTA. index.html
   has no .tt-hp-hero, so its hero keeps the larger scale on purpose.
   NOTE: this is more specific than the plain .tt-hero-title rule in the
   max-width 991.98px block, so it needs its own override there - see below. */
.tt-hp-hero .tt-hero-title {
  font-size: calc(1.9rem + 1.4vw);
}

/* Body-copy link to a hub (money) page. These sit inside running prose on the
   module, device and article pages, so they must read as text that happens to
   be a link - same family, size and weight as the paragraph around them, marked
   only by the brand orange and an underline.

   `font: inherit` is the load-bearing part: Bootstrap's own anchor styling
   would otherwise leave these as its default link blue, which is the one colour
   on the site that belongs to nothing.

   Deliberately matches `.tt-ar-body a` in blog.css character for character, so
   a hub link in an article and one on a module page are indistinguishable. If
   you change one, change both. Specificity is a tie with that rule and blog.css
   loads later, which is harmless precisely because the values agree.

   This comment must run unbroken to the end. A comment terminator sat after
   the line above until 2026-08-27 and closed it four lines early. The
   remaining prose then became raw stylesheet text, CSS error recovery
   swallowed the `.tt-hub-link, .tt-rel-link` rule below into an invalid
   selector, and the whole declaration block was DROPPED - leaving all 234 of
   those links across 58 pages rendering as Bootstrap default blue with an
   underline. A stylesheet reports no syntax error; it silently stops
   applying the rule.

   `.tt-rel-link` is the same treatment for sibling-module and article links.
   Two class names rather than one so the two kinds of internal link can still
   be counted separately in an audit; they are deliberately indistinguishable
   on screen. */
.tt-hub-link,
.tt-rel-link {
  font: inherit;
  color: var(--tt-primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.tt-hub-link:hover,
.tt-hub-link:focus-visible,
.tt-rel-link:hover,
.tt-rel-link:focus-visible {
  color: var(--tt-primary-hover);
}

/* Accent phrase only - a black/orange mix reads more confident and on-brand
   than colouring the entire headline. */
.tt-hero-title-accent {
  color: var(--tt-primary);
}

.tt-hero-paragraph {
  font-family: var(--tt-font-primary);
  font-size: 1.15rem;
  font-weight: 450;
  line-height: 1.7;
  color: #555558;
}

/* Large consultancy cta button */
.btn-hero-cta {
  font-size: 1.05rem;
  padding: 1rem 2.25rem;
  border-radius: 8px;
}

/* ==================================================================
   AMBIENT GLOW DAMPING ON SMALL SCREENS

   NOTE: the glows themselves are gone - radial glows are gradients, and this
   site carries none, so the fills below resolve to `none`. This block is now
   inert and is kept only so the sizing rationale survives if a flat ambient
   treatment is ever added back. Original rationale:

   The decorative glows are sized in fixed pixels (620px, 500px, 480px).
   On a 375px-wide phone that is 130-165% of the viewport, so what reads
   as a soft corner glow on a desktop covers the entire screen on a
   phone - and it stacks with the fixed page mesh AND each section's own
   glow, which is why small screens looked washed in orange.

   Below 768px the glows are pulled back to viewport-relative sizes and
   their opacity is cut. Sizes stay proportional to the screen rather
   than absolute, so this holds at every phone width.
   ================================================================== */
@media (max-width: 767.98px) {
  .page-wrapper::before { opacity: 0.5; }

  .tt-hero-glow-1,
  .tt-hero-glow-2 { opacity: 0.45; }
  .tt-hero-glow-1 { width: min(420px, 80vw); height: min(420px, 80vw); }
  .tt-hero-glow-2 { width: min(340px, 65vw); height: min(340px, 65vw); }
}
/* The .tt-hp-* glows are damped in hrms-page.css instead - that file loads
   after this one, and a media query adds no specificity, so a rule here would
   simply lose to its base rule there. */

/* Hero CTA row on phones.
   The markup is `.tt-hero-actions.d-sm-flex ... .gap-3`, so below 576px the
   row is NOT a flex container - and `gap` only applies to flex/grid parents.
   The result was two stacked buttons rendering flush against each other with
   no separation at all. Making it an explicit column flex container here
   restores the gap and gives both buttons a single shared width, instead of
   each sizing to its own label. */
@media (max-width: 575.98px) {
  .tt-hero-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
  }
  .tt-hero-actions .btn-hero-cta {
    width: 100%;
    text-align: center;
  }
}

/* KEEP EACH HERO BUTTON'S LABEL ON ONE LINE, and let the ROW break instead.

   The labels grew on 2026-09-14 ("Hear from Our Clients" against the older
   "View Case Studies"), and between roughly 992px and 1200px the two buttons
   no longer fit side by side inside the hero's col-lg-6. Without this they
   did the worst available thing: kept sitting side by side and wrapped their
   own text, so each became a ragged two-line box of different height.

   nowrap forbids that; flex-wrap lets the second button drop to its own line
   when the row genuinely runs out of width. The phone rule above already
   stacks them deliberately and is unaffected - it sets an explicit column
   direction, which flex-wrap does not override. */
.tt-hero-actions {
  flex-wrap: wrap;
}

.tt-hero-actions .btn-hero-cta {
  white-space: nowrap;
}

/* Interactive Video layout */
.tt-hero-media-wrapper {
  z-index: 5;
}

/* Decorative visual glow directly placed behind video frame matching mockup ambient */
.tt-video-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 92%;
  height: 92%;
  background: var(--tt-gradient-vivid);
  opacity: 0.35;
  filter: blur(80px);
  z-index: -1;
  border-radius: 30px;
  pointer-events: none;
}

.tt-video-container {
  /* 16:9 to match the hero video's own 1920x1080. It used to be 16/10, which
     is a taller box than the footage: with object-fit:cover the video was
     scaled up to fill the height and ~10% of its width was cropped away
     (roughly 5% off each side), cutting into the animation's content. */
  aspect-ratio: 16 / 9;
  background-color: #2F2F32;
  box-shadow: 0 25px 60px -12px rgba(var(--tt-accent-deep-rgb), 0.25);
  border: 1px solid var(--tt-glass-border);
  display: flex;
  align-items: center;
  justify-content: center;
}

.tt-hero-video {
  width: 100%;
  height: 100%;
  /* contain, not cover: with the container now matching the video's ratio the
     two render identically, but if the video is ever swapped for a different
     aspect it will letterbox against the dark backplate rather than silently
     crop the new footage. */
  object-fit: contain;
  display: block;
}

/* Overlay play icon */
.tt-video-overlay-play {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.15); /* Light visual cover matching transparent screen */
  cursor: pointer;
  transition: var(--tt-transition-standard);
  z-index: 10;
}

.tt-video-overlay-play:hover {
  background-color: rgba(0, 0, 0, 0.25);
}

/* While the video is actually playing the overlay gets out of the way: no tint,
   no button, no pulse. It stays in the DOM and clickable so the whole frame
   still works as a pause target, and everything comes back on hover (with a
   pause glyph, swapped in by initHeroVideoController). */
.tt-video-overlay-play.is-playing {
  background-color: transparent;
}

.tt-video-overlay-play.is-playing .tt-play-btn,
.tt-video-overlay-play.is-playing .tt-pulse-ring {
  opacity: 0;
}

/* Stop the pulse keyframes rather than animating something invisible. */
.tt-video-overlay-play.is-playing .tt-pulse-ring {
  animation: none;
}

.tt-video-overlay-play.is-playing:hover .tt-play-btn {
  opacity: 1;
}

.tt-play-btn {
  background-color: #FFFFFF;
  color: var(--tt-primary);
  width: 80px;
  height: 80px;
  border-radius: 50%;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.6rem;
  padding-left: 6px; /* Balance the optical alignment of play triangular icon */
  box-shadow: 0 10px 25px -3px rgba(243, 112, 33, 0.4);
  transition: var(--tt-transition-spring);
  pointer-events: none; /* Let the parent click handler take charge */
}

.tt-video-overlay-play:hover .tt-play-btn {
  transform: scale(1.1);
  color: var(--tt-primary-hover);
  box-shadow: 0 15px 30px -3px rgba(243, 112, 33, 0.6);
}

/* Pulse animation standard */
.tt-pulse-ring {
  position: absolute;
  width: 80px;
  height: 80px;
  border: 2px solid #FFFFFF;
  border-radius: 50%;
  animation: ttPulse 2s cubic-bezier(0.24, 0, 0.38, 1) infinite;
  pointer-events: none;
}

@keyframes ttPulse {
  0% {
    transform: scale(1);
    opacity: 0.8;
  }
  100% {
    transform: scale(1.8);
    opacity: 0;
  }
}

/* Responsive Scaling Overrides for Hero Layout */
@media (max-width: 991.98px) {
  .tt-hero-section {
    padding-top: calc(90px + var(--tt-fold-gap));
    padding-bottom: var(--tt-spacer-md);
  }
  
  .tt-hero-title {
    font-size: calc(1.8rem + 1.5vw);
  }

  /* Must repeat the scoped selector: the rule above is only 0,1,0, so without
     this the desktop .tt-hp-hero .tt-hero-title would win below 992px. */
  .tt-hp-hero .tt-hero-title {
    font-size: calc(1.6rem + 1.2vw);
  }

  .tt-hero-paragraph {
    font-size: 1.05rem;
  }
  
  .tt-play-btn {
    width: 65px;
    height: 65px;
    font-size: 1.3rem;
  }
  
  .tt-pulse-ring {
    width: 65px;
    height: 65px;
  }
}


/* --- 7. BRAND LOGO TICKER SECTION --- */

.tt-ticker-section {
  /* Soft neutral band rather than white: it separates the light hero above
     from the dark HCM band below, and gives the (mostly white-background)
     client logos something to sit on. */
  background-color: #F4F5F7;
  padding: 1.9rem 0;
  overflow: hidden;
  position: relative;
  width: 100%;
  border-top: 1px solid rgba(16, 24, 40, 0.05);
  border-bottom: 1px solid rgba(16, 24, 40, 0.07);
}

/* Standing label on the left, scrolling logos on the right, split by a rule. */
.tt-ticker-layout {
  align-items: center;
  display: flex;
  gap: clamp(1.5rem, 3vw, 2.75rem);
}

.tt-ticker-label {
  border-right: 1px solid rgba(16, 24, 40, 0.14);
  flex: 0 0 auto;
  max-width: 250px;
  padding-right: clamp(1.5rem, 3vw, 2.75rem);
}

.tt-ticker-label-text {
  color: var(--tt-dark);
  font-family: var(--tt-font-secondary);
  font-size: clamp(1rem, 1.35vw, 1.3rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.3;
  margin: 0;
}

.tt-ticker-container {
  display: flex;
  flex: 1 1 auto;
  min-width: 0; /* Lets the flex item shrink so the track can overflow-hide */
  overflow: hidden;
  position: relative;
}

/* Fades live on the logo lane, not the section - they must start after the
   label's divider, otherwise they wash the label out too. */
.tt-ticker-container::before,
.tt-ticker-container::after {
  content: "";
  position: absolute;
  top: 0;
  width: 6%;
  height: 100%;
  z-index: 5;
  pointer-events: none;
}

.tt-ticker-container::before {
  left: 0;
  background: none;
}

.tt-ticker-container::after {
  right: 0;
  background: none;
}

.tt-ticker-track {
  display: flex;
  flex-wrap: nowrap; /* Enforce absolutely no wrapping of the track elements */
  width: max-content;
  /* Duration is a FUNCTION OF THE LOGO COUNT, not a constant. Each item is a
     fixed 200px, so the track is (count x 200px) wide and the animation moves
     it -50%; holding a steady px/sec therefore means scaling this with the
     count - 45s at the original 11 logos, i.e. 4.09s per logo. Add logos and
     leave this alone and the ticker scrolls FASTER, it does not run longer.
     18 logos/group (incl. RONIN + BYD) -> 18 x 4.09 = 74s.
     scripts\add-ticker-logos.ps1 prints the correct value on every run. */
  animation: ttTickerScroll 74s linear infinite;
  will-change: transform; /* Hint GPU for optimal rendering layers */
  position: relative;
  z-index: 1; /* Keep readable below container's shadow gradient overlay mask */
}

/* Pause on hover to allow users to interact with client references.

   GATED ON A REAL POINTER, and that gate is the whole point of the media
   query. A touch screen has no hover, so it SYNTHESISES one: tapping or
   swiping over the track applies :hover and leaves it applied until
   something else is tapped. Ungated, this rule froze the ticker on a phone
   the first time a finger crossed it, permanently, with nothing to undo it
   - reported repeatedly as "the ticker doesn't move on mobile".
   Same fix as .tt-mega-item:hover in mega-menu.css, same reason. */
@media (hover: hover) and (pointer: fine) {
  .tt-ticker-track:hover {
    animation-play-state: paused;
  }
}

.tt-ticker-item {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 200px; /* Fixed item width (independent of each logo's own aspect ratio) so the track's total
                       width is deterministic from first paint - prevents the transform's -50% end point
                       from shifting (and visibly jumping) as images finish loading asynchronously. */
  width: 200px;
  padding: 0 1.25rem;
}

.tt-ticker-logo {
  height: 68px !important; /* Larger, more visible sizing */
  width: auto !important; /* Restored width-auto so that different logo shapes layout naturally */
  max-width: 160px !important; /* Cap width to prevent oversized horizontal expansion */
  object-fit: contain !important;
  filter: grayscale(0%) contrast(1.05); /* Completely colorful and crisp corporate colors */
  opacity: 1 !important; /* Solid visibility as shown in screenshot crop */
  transition: var(--tt-transition-standard);
  display: block;
}

.tt-ticker-logo:hover {
  transform: scale(1.08); /* Sophisticated light scale layout zoom */
}

/* Glitchless Hardware Accelerated translation keyframes standard */
@keyframes ttTickerScroll {
  0% {
    transform: translate3d(0, 0, 0);
  }
  100% {
    transform: translate3d(-50%, 0, 0); /* Loop resets exactly on Group duplication point */
  }
}

@media (max-width: 767.98px) {
  .tt-ticker-section {
    padding: 1.5rem 0;
  }

  /* Side by side leaves the logo lane too narrow to read - stack the label
     above the strip and turn its divider into an underline. */
  .tt-ticker-layout {
    align-items: stretch;
    flex-direction: column;
    gap: 1rem;
  }

  .tt-ticker-label {
    border-bottom: 1px solid rgba(16, 24, 40, 0.12);
    border-right: 0;
    max-width: none;
    padding-bottom: 0.9rem;
    padding-right: 0;
    text-align: center;
  }

  .tt-ticker-logo {
    height: 48px !important; /* Balanced for mobile viewports */
    max-width: 120px !important;
  }

  .tt-ticker-item {
    flex: 0 0 140px;
    width: 140px;
    padding: 0 1rem;
  }
}


/* --- 8. WHY CHOOSE TIMETRAX (Fold 2) ------------------------------------

   History, because this slot has now turned over twice: the original inline
   benefits markup was replaced by the scroll-driven Benefits Showcase
   component (benefits-showcase.css + .js), and on 2026-09-16 that component
   was itself replaced by the static list below.

   NOTHING HERE MOVES, AND THAT IS THE SPECIFICATION. No script, no
   animation, no transition - not even on hover. The showcase spent 17.5 KB
   of CSS and 19 KB of JS crossfading three screenshots; this replaces both
   for +8 KB on a stylesheet already being fetched (most of it these
   comments, which gzip away), and no requests at all.

   IT LIVES IN style.css ON PURPOSE. A `why-choose.css` would have handed
   back one of the two requests the swap existed to remove, on the one page
   that carries the section. Do not split it out.

   The items are not links and get no hover state, deliberately: a pill that
   lights up under the cursor and then does nothing when clicked is a worse
   affordance than one that plainly is not interactive.
   ------------------------------------------------------------------------ */

/* THE FLAIR IS THE COMPOSITION, not an effect: a dot-grid texture behind,
   a white panel lifted off it on a soft shadow, and one orange rule. Three
   static layers. Nothing animates and nothing needs an illustration. */
/* WARMTH WITHOUT A GRADIENT, which hard rule 3 does not allow. Three flat
   tones sitting next to each other do the job: this warm off-white, the
   pure-white panel on top of it, and the orange-tinted dots between them.
   The panel stays #FFF precisely so it still lifts off the section - warm
   the panel too and the shadow is the only thing separating them. */
.tt-why-section {
  background: #FDFAF7;
  overflow: hidden;
  padding: var(--tt-section-space) 0;
  position: relative;
}

/* Dot-grid texture, edge-faded.

   DELIBERATELY THE SAME TECHNIQUE AND THE SAME NUMBERS as
   .tt-hcm-artwork-wrap::before - 1.3px dots on a 26px pitch, tinted with
   `color` so one declaration drives the dots, radially masked so it fades
   out before it reaches an edge and never ends in a hard rectangle. Match
   that rule if you retune this one; two different dot grids on one page
   read as a mistake.

   This is NOT a gradient under hard rule 3. Both functions here are
   hard-stop (1.3px -> transparent at 1.4px) and an alpha mask, which that
   rule explicitly exempts - there is no colour blend anywhere in it. */
.tt-why-section::before {
  background-image: radial-gradient(currentColor 1.3px, transparent 1.4px);
  background-size: 26px 26px;
  /* Orange-tinted rather than the HCM grid's blue-grey. The geometry is
     deliberately identical to that rule; only the tint differs, because
     this section is the warm one. */
  color: rgba(var(--tt-primary-rgb), 0.16);
  content: '';
  inset: 0;
  -webkit-mask-image: radial-gradient(68% 72% at 50% 50%, #000 0%, transparent 78%);
  mask-image: radial-gradient(68% 72% at 50% 50%, #000 0%, transparent 78%);
  pointer-events: none;
  position: absolute;
  z-index: 0;
}

/* Keep the panel above the texture layer. */
.tt-why-section .container {
  position: relative;
  z-index: 1;
}

/* The panel floats ON the texture - that lift is what the shadow is for,
   and it is the whole reason the dot grid reads as depth rather than as
   noise behind flat content.

   NO BORDER, on purpose: the hairline grid inside already draws a frame,
   and a second outline 3rem outside it reads as two boxes rather than a
   card containing a table. The shadow defines this edge instead. */
.tt-why-panel {
  background: #FFFFFF;
  border-radius: 24px;
  box-shadow: 0 18px 50px -24px rgba(16, 24, 40, 0.28);
  margin: 0 auto;
  max-width: 1120px;
  padding: clamp(2.4rem, 4vw, 3.6rem) clamp(1.1rem, 3vw, 3rem)
           clamp(2.6rem, 4vw, 3.6rem);
}

.tt-why-head {
  margin: 0 auto 2.4rem;
  max-width: 660px;
  text-align: center;
}

/* The one piece of brand punctuation, and it doubles as the separator
   between the heading block and the grid. Same 4px-tall rounded rule the
   modules section uses for .tt-modules-title-accent - reused rather than
   reinvented so the two sections read as one system. */
.tt-why-head::after {
  background: var(--tt-primary);
  border-radius: 999px;
  content: '';
  display: block;
  height: 4px;
  margin: 1.4rem auto 0;
  width: 46px;
}

.tt-why-heading {
  color: var(--tt-dark);
  font-family: var(--tt-font-secondary);
  font-size: clamp(1.7rem, 3vw, 2.7rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 0;
}

.tt-why-heading .tt-why-brand {
  color: var(--tt-primary);
}

.tt-why-subtext {
  color: var(--tt-muted);
  font-size: 1rem;
  line-height: 1.55;
  margin: 0.8rem 0 0;
}

/* ONE PANEL DIVIDED BY HAIRLINES, not nine floating cards.

   THE DIVIDERS ARE A 1px box-shadow RING ON EACH CELL, with gap: 0.
   Neighbouring rings land on exactly the same line and the same colour, so
   they read as one hairline rather than the 2px doubled edge that
   `border-right` + `border-bottom` always produces. Three reasons this
   beats the alternatives:

     - NO EMPTY-CELL HAZARD. The obvious version of this layout paints the
       grid container in the line colour and lets it show through a 1px
       gap. That works only while the item count divides exactly by the
       column count. At the 2-column breakpoint nine items leave one empty
       cell, and the container's background renders it as a SOLID GREY
       BLOCK. With rings on the cells there is nothing behind them to show.
     - It survives a tenth item, or an eighth, at any breakpoint - no
       `:nth-child(3n)` border resets to maintain, which is the usual cost
       of this layout and the usual source of a stray line on the last row.
     - The line lands on the device pixel grid at any zoom.

   The rounded outline is the CONTAINER'S own border, not the cells' rings:
   `overflow: hidden` clips those rings square at the corners, which would
   leave the four arcs unpainted.

   Hard rule 1 is about html / body / .page-wrapper, where `overflow:
   hidden` kills every `position: sticky`. On a self-contained block like
   this it is the correct tool.

   Explicit 3 / 2 / 1 columns rather than auto-fit + minmax: the nine
   labels differ enough in length that auto-fit resolves to an odd column
   count at some widths and orphans the last item. */
.tt-why-grid {
  /* One local token so the hairline colour is stated once - the cells'
     rings and this outer border must never drift apart.

     Brand-tinted rather than --tt-border (#E5E7EB), whose blue cast sat
     against the orange rather than with it. Kept at low alpha on purpose:
     at three columns this is a lot of line on screen, and anything
     stronger stops reading as a hairline and starts reading as a drawn
     orange table. */
  --tt-why-line: rgba(var(--tt-primary-rgb), 0.18);

  border: 1px solid var(--tt-why-line);
  border-radius: 16px;
  display: grid;
  gap: 0;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  list-style: none;
  margin: 0;
  overflow: hidden;
  padding: 0;
}

.tt-why-item {
  align-items: center;
  /* OPAQUE, AND IT MUST BE - this is what makes the single-alpha hairline
     work. Each cell's ring is drawn outside its own border box, i.e. over
     the neighbour. Painting in DOM order, cell 2's background covers cell
     1's ring and then cell 2's ring paints over cell 1's background, so
     exactly one ring survives on every shared edge. Give the cells a
     TRANSLUCENT fill instead and both rings show through the shared line
     at double alpha, which is the doubled-border artefact this technique
     exists to avoid. */
  background: #FFFFFF;
  box-shadow: 0 0 0 1px var(--tt-why-line);
  color: var(--tt-dark);
  display: flex;
  font-size: 1.02rem;
  font-weight: 500;
  /* Wide enough to clear the dot's halo, which is drawn outside its box
     and so is not counted by the gap. */
  gap: 1.05rem;
  /* LEFT-ALIGNED, so every dot in a column starts at the same x.
     Centring the dot+label pair was tried first and is wrong here: the
     nine labels differ in length, so each centred pair put its dot in a
     different place and the three columns of dots came out ragged. A
     column of bullets has to be a straight line. */
  justify-content: flex-start;
  line-height: 1.35;
  /* Roomier than the old card, because the cell now has no border of its
     own to define it - the whitespace is what separates one point from
     the next. */
  padding: 1.55rem 1.6rem;
  text-align: left;
}

/* A CSS circle, NOT a FontAwesome <i>. That stylesheet loads non-blocking,
   so a glyph here would be a zero-width box until it arrives and every
   label would jump sideways when it did. A ::before is painted with the
   first frame and costs no request.

   The halo is the detail that carries close up: a solid dot alone is
   flat, and a second ring at 13% alpha gives it a little depth for one
   extra declaration and no element. It is `box-shadow` spread rather than
   a border so the dot's own 10px box - and therefore the text baseline it
   sits against - does not move.

   `margin-left` pays for the halo on the left edge; the `gap` on the
   parent covers the right. Neither is optical fiddling - the halo is
   outside the border box, so no layout property accounts for it. */
.tt-why-item::before {
  background: var(--tt-primary);
  border-radius: 50%;
  box-shadow: 0 0 0 4px rgba(var(--tt-primary-rgb), 0.13);
  content: '';
  flex: 0 0 auto;
  height: 10px;
  margin-left: 4px;
  width: 10px;
}

/* Only the column count changes at each step. Do not introduce a `gap`
   here - the dividers are the cells' own rings, and any gap would open
   white channels through the panel and leave every hairline doubled. */
@media (max-width: 991.98px) {
  .tt-why-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 575.98px) {
  .tt-why-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .tt-why-item {
    font-size: 0.98rem;
    padding: 1.15rem 1.1rem;
  }
}


/* --- 8b. AI SEGMENT (index.html only) -----------------------------------

   A composite of the supplied artwork layers plus one CSS animation:
   comets travelling from each input icon into the TimeTrax AI ring. There
   is no GIF and no video - see the markup comment in index.html for why a
   GIF would have been the worse artefact even if this machine could make
   one.

   THE SECTION MUST STAY PURE WHITE. bg.jpg is the background artwork
   flattened onto white (there is no WebP encoder here and PNG was 10x the
   bytes - see scripts/build-ai-segment-bg.ps1). A JPEG has no alpha, so
   on any tinted background its white box shows as a rectangle.

   Lives in style.css rather than its own file even though only index.html
   uses it: that page had two requests removed when the benefits showcase
   went, and this would hand one straight back. ~4 KB uncompressed on the
   other 101 pages, most of it these comments, nearly all of it gone after
   gzip - which both server configs already apply.
   ------------------------------------------------------------------------ */

.tt-ai-section {
  background: #FFFFFF;
  padding: var(--tt-section-space) 0;
}

.tt-ai-head {
  margin: 0 auto 2.6rem;
  max-width: 720px;
  text-align: center;
}

.tt-ai-heading {
  color: var(--tt-dark);
  font-family: var(--tt-font-secondary);
  font-size: clamp(1.7rem, 3vw, 2.7rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 0;
}

.tt-ai-heading .tt-ai-brand {
  color: var(--tt-primary);
}

.tt-ai-copy {
  color: var(--tt-muted);
  font-size: 1rem;
  line-height: 1.6;
  margin: 0.9rem 0 0;
}

/* THE COORDINATE SYSTEM FOR EVERYTHING BELOW IS THE 2369x822 ARTWORK.
   The stage holds that ratio, every layer is positioned as a percentage
   of it, and the SVG shares the same viewBox - so the comets, the icons
   and the background stay registered to each other at every width with
   no breakpoints and no JS measurement. Change the ratio and every
   percentage below is wrong. */
.tt-ai-stage {
  aspect-ratio: 2369 / 822;
  margin: 0 auto;
  max-width: 1240px;
  position: relative;
  width: 100%;
}

.tt-ai-bg {
  height: 100%;
  inset: 0;
  position: absolute;
  width: 100%;
  z-index: 0;
}

.tt-ai-el {
  height: auto;
  position: absolute;
  z-index: 2;
}

/* Measured by template-matching each PNG inside "Full Image.png", not
   placed by eye. Left/top/width are percentages of 2369x822.
   The seven LEFT icons are not here: they exist only as flying tokens. */
.tt-ai-l7 { left:   8.675%; top:   8.577%; width:  8.864%; }
.tt-ai-l5 { left:  17.602%; top:  18.309%; width:  8.864%; }
.tt-ai-l6 { left:   3.651%; top:  20.255%; width:  8.780%; }
.tt-ai-l2 { left:   9.223%; top:  33.273%; width:  7.598%; }
.tt-ai-l4 { left:   3.482%; top:  44.708%; width:  7.640%; }
.tt-ai-l1 { left:  14.162%; top:  48.114%; width:  7.598%; }
.tt-ai-l3 { left:   8.210%; top:  61.131%; width:  7.598%; }
.tt-ai-mid { left: 41.072%; top: 28.710%; width: 16.420%; }
.tt-ai-r1  { left: 78.303%; top:  9.367%; width: 14.310%; }
.tt-ai-r2  { left: 79.612%; top: 31.630%; width: 19.249%; }
.tt-ai-r3  { left: 76.192%; top: 57.908%; width: 17.011%; }

/* ---- the one animation: pulses running the artwork's own lines ----

   THE PATHS ARE TRACED OUT OF bg.png, NOT AUTHORED. A ridge-follow seeds
   on each painted curve and walks it column by column, taking an
   ink-weighted centroid, so a pulse sits ON its line by construction. No
   hand-drawn Bezier can promise that, and every earlier version of this
   section was guessing at where the lines ran.

   FIVE LANES, NOT EIGHT. The trace finds eight curves at x=720; lanes 2
   and 6 are dropped as near-duplicates (their traces merge into the
   bundle approaching the ring) and lane 7 because its trace stops at
   x=774 and never reaches it - a pulse there would halt in mid-air.

   Seeding per TILE was tried first and abandoned: only 3 of the 7 tiles
   have a line leaving their right edge. The artwork's curves do not all
   originate at a tile, so the lines are found on their own terms instead.

   THE MOTION IS A PERCENTAGE OF THE WRAPPER'S OWN BOX, which is what
   makes it scale: the wrapper is sized as a percentage of the stage and
   the stage holds a fixed 2369/822 ratio, so every waypoint tracks at any
   width with no breakpoints and no JS measurement.

   Waypoints are resampled at EQUAL ARC LENGTH, 17 per lane. CSS
   interpolates transform linearly between keyframes, so too few stops
   reads as a polyline, and equal-t sampling makes the speed surge and
   ease. Equal distance plus linear is what gives constant speed.

   transform and opacity only - both compositor properties. An early
   version animated stroke-dashoffset on SVG paths, which repaints on the
   main thread every frame. */
.tt-ai-pulse {
  aspect-ratio: 1;
  opacity: 0;
  position: absolute;
  /* The wrapper is deliberately much larger than the visible dot. A bare
     18px dot would need translate offsets in the thousands of percent,
     since they are relative to the element's own box. */
  width: 4%;
  z-index: 1;
}

/* A TAPERED STREAK LYING ALONG ITS LINE, not a mark sitting on top of it.

   Both pseudo-elements are horizontal bars centred in the wrapper; the
   wrapper's own animated rotate() is what swings them onto the curve. The
   rotation angle at every waypoint is the path TANGENT, taken as a central
   difference over the traced points - so it is measured off the artwork's
   geometry exactly like the position is. Across the five lanes it runs
   from -33.8deg to +39.5deg, which is why an unrotated shape read as
   sitting on the line rather than travelling in it.

   THE TAPER IS A mask-image, AND THAT IS DELIBERATE UNDER HARD RULE 3.
   The fill is a flat brand colour; the fade to nothing at each end is an
   alpha mask, which that rule exempts by name. Do not reach for a
   background gradient to get the same look.

   ::before is the 26x6 halo and ::after the 20x3 core, in that order so
   the bright core paints over its own glow with no z-index. Sized in
   percentages of the wrapper so both scale with the stage.

   20px, not 26: at 26 the clearance check went to -2px, i.e. two streaks
   on converging lines just touched. At 20 it is +4px. */
.tt-ai-pulse::before,
.tt-ai-pulse::after {
  border-radius: 999px;
  content: '';
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 35%, #000 65%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 35%, #000 65%, transparent 100%);
}

.tt-ai-pulse::before {
  background: rgba(var(--tt-primary-rgb), 0.16);
  height: 6.33%;
  width: 27.40%;
}

.tt-ai-pulse::after {
  background: var(--tt-primary);
  height: 3.17%;
  width: 21.11%;
}

/* Wrapper centred on the first traced point of its line. */
.tt-ai-p1 { left:  25.607%; top:  21.852%; }
.tt-ai-p3 { left:  17.249%; top:  31.024%; }
.tt-ai-p5 { left:  25.607%; top:  60.234%; }
.tt-ai-p2 { left:  22.314%; top:  24.832%; }
.tt-ai-p4 { left:  19.781%; top:  47.095%; }

/* A CONTINUOUS STREAM, NOT TURN-TAKING. This is a structural change, not
   a tuning one.

   An earlier version emitted ONE dot per line at a time, each running a
   30% window and then resting - which capped the whole section at two
   dots in motion and read as a sequence of events rather than a flow.
   Now every line carries THREE dots a third of a cycle apart, all
   travelling continuously across the full 100% of the loop. Measured:

       dots visible at once            max 13 of 15
       frames with nothing visible     0 of 800
       minimum clearance               16 px, on an 8px dot
       spacing along a line            117-181 px

   Those numbers are the brief - a steady current with nothing colliding -
   and the generator checks them. Change the count, the spacing or the
   duration and re-run it.

   The 15 delays are spread evenly across the cycle and interleaved BY
   LINE as well as by phase, so the dots do not arrive in rank.

   All delays NEGATIVE, so the stream is already flowing on the first
   frame instead of filling up over the first ten seconds. */
.tt-ai-stage.is-live .tt-ai-pulse {
  animation-duration: 10s;
  animation-iteration-count: infinite;
  animation-name: ttAiPulseFade;
  animation-timing-function: linear;
}

.tt-ai-stage.is-live .tt-ai-p1 { animation-name: ttAiPulse-p1, ttAiPulseFade; }
.tt-ai-stage.is-live .tt-ai-p2 { animation-name: ttAiPulse-p2, ttAiPulseFade; }
.tt-ai-stage.is-live .tt-ai-p3 { animation-name: ttAiPulse-p3, ttAiPulseFade; }
.tt-ai-stage.is-live .tt-ai-p4 { animation-name: ttAiPulse-p4, ttAiPulseFade; }
.tt-ai-stage.is-live .tt-ai-p5 { animation-name: ttAiPulse-p5, ttAiPulseFade; }

.tt-ai-stage.is-live .tt-ai-p1.tt-ai-d1 { animation-delay: -0.000s, -0.000s; }
.tt-ai-stage.is-live .tt-ai-p3.tt-ai-d1 { animation-delay: -0.667s, -0.667s; }
.tt-ai-stage.is-live .tt-ai-p5.tt-ai-d1 { animation-delay: -1.333s, -1.333s; }
.tt-ai-stage.is-live .tt-ai-p2.tt-ai-d1 { animation-delay: -2.000s, -2.000s; }
.tt-ai-stage.is-live .tt-ai-p4.tt-ai-d1 { animation-delay: -2.667s, -2.667s; }
.tt-ai-stage.is-live .tt-ai-p1.tt-ai-d2 { animation-delay: -3.333s, -3.333s; }
.tt-ai-stage.is-live .tt-ai-p3.tt-ai-d2 { animation-delay: -4.000s, -4.000s; }
.tt-ai-stage.is-live .tt-ai-p5.tt-ai-d2 { animation-delay: -4.667s, -4.667s; }
.tt-ai-stage.is-live .tt-ai-p2.tt-ai-d2 { animation-delay: -5.333s, -5.333s; }
.tt-ai-stage.is-live .tt-ai-p4.tt-ai-d2 { animation-delay: -6.000s, -6.000s; }
.tt-ai-stage.is-live .tt-ai-p1.tt-ai-d3 { animation-delay: -6.667s, -6.667s; }
.tt-ai-stage.is-live .tt-ai-p3.tt-ai-d3 { animation-delay: -7.333s, -7.333s; }
.tt-ai-stage.is-live .tt-ai-p5.tt-ai-d3 { animation-delay: -8.000s, -8.000s; }
.tt-ai-stage.is-live .tt-ai-p2.tt-ai-d3 { animation-delay: -8.667s, -8.667s; }
.tt-ai-stage.is-live .tt-ai-p4.tt-ai-d3 { animation-delay: -9.333s, -9.333s; }

/* The dot travels for the WHOLE cycle now; the fade only softens the two
   ends, so the wrap from the end of a line back to its start is never
   seen. It holds at 0.55, not 1 - these are meant to read as light
   signals on the line, not as beads laid on top of it. */
@keyframes ttAiPulseFade {
  0%        { opacity: 0; }
  8%, 88%   { opacity: 0.55; }
  100%      { opacity: 0; }
}

@keyframes ttAiPulse-p1 {
   0.00% { transform: translate(0.00%, 0.00%) rotate(30.52deg); }
   6.25% { transform: translate(18.71%, 11.03%) rotate(36.15deg); }
  12.50% { transform: translate(36.83%, 26.90%) rotate(39.54deg); }
  18.75% { transform: translate(55.03%, 41.02%) rotate(25.29deg); }
  25.00% { transform: translate(77.30%, 46.02%) rotate(22.49deg); }
  31.25% { transform: translate(97.79%, 58.72%) rotate(31.82deg); }
  37.50% { transform: translate(118.27%, 71.45%) rotate(31.14deg); }
  43.75% { transform: translate(139.07%, 83.66%) rotate(28.30deg); }
  50.00% { transform: translate(160.68%, 94.28%) rotate(25.24deg); }
  56.25% { transform: translate(182.64%, 104.20%) rotate(21.60deg); }
  62.50% { transform: translate(205.47%, 112.02%) rotate(16.28deg); }
  68.75% { transform: translate(228.86%, 117.70%) rotate(12.66deg); }
  75.00% { transform: translate(252.48%, 122.57%) rotate(7.15deg); }
  81.25% { transform: translate(276.52%, 123.68%) rotate(3.20deg); }
  87.50% { transform: translate(300.55%, 125.26%) rotate(6.74deg); }
  93.75% { transform: translate(324.22%, 129.32%) rotate(6.95deg); }
  100.00% { transform: translate(348.25%, 131.07%) rotate(4.17deg); }
}

@keyframes ttAiPulse-p2 {
   0.00% { transform: translate(0.00%, 0.00%) rotate(18.36deg); }
   6.25% { transform: translate(27.41%, 9.10%) rotate(24.47deg); }
  12.50% { transform: translate(52.26%, 23.78%) rotate(29.57deg); }
  18.75% { transform: translate(77.75%, 37.66%) rotate(28.67deg); }
  25.00% { transform: translate(103.15%, 51.61%) rotate(29.50deg); }
  31.25% { transform: translate(128.21%, 66.22%) rotate(28.47deg); }
  37.50% { transform: translate(154.10%, 79.23%) rotate(25.87deg); }
  43.75% { transform: translate(180.39%, 91.52%) rotate(23.45deg); }
  50.00% { transform: translate(207.34%, 102.33%) rotate(29.57deg); }
  56.25% { transform: translate(230.04%, 119.69%) rotate(26.76deg); }
  62.50% { transform: translate(257.50%, 127.62%) rotate(12.32deg); }
  68.75% { transform: translate(286.21%, 131.96%) rotate(8.81deg); }
  75.00% { transform: translate(314.89%, 136.51%) rotate(7.35deg); }
  81.25% { transform: translate(343.78%, 139.38%) rotate(5.49deg); }
  87.50% { transform: translate(372.65%, 142.06%) rotate(4.87deg); }
  93.75% { transform: translate(401.57%, 144.30%) rotate(0.92deg); }
  100.00% { transform: translate(430.56%, 142.99%) rotate(-2.59deg); }
}

@keyframes ttAiPulse-p3 {
   0.00% { transform: translate(0.00%, 0.00%) rotate(15.56deg); }
   6.25% { transform: translate(34.37%, 9.57%) rotate(17.86deg); }
  12.50% { transform: translate(67.92%, 21.88%) rotate(21.77deg); }
  18.75% { transform: translate(100.82%, 36.11%) rotate(21.38deg); }
  25.00% { transform: translate(134.63%, 47.99%) rotate(19.82deg); }
  31.25% { transform: translate(167.89%, 60.28%) rotate(15.37deg); }
  37.50% { transform: translate(202.93%, 66.77%) rotate(13.01deg); }
  43.75% { transform: translate(237.36%, 76.33%) rotate(15.37deg); }
  50.00% { transform: translate(271.93%, 85.73%) rotate(10.71deg); }
  56.25% { transform: translate(307.39%, 89.57%) rotate(5.86deg); }
  62.50% { transform: translate(342.96%, 93.02%) rotate(4.45deg); }
  68.75% { transform: translate(378.73%, 95.12%) rotate(3.16deg); }
  75.00% { transform: translate(414.48%, 96.97%) rotate(-0.36deg); }
  81.25% { transform: translate(450.18%, 94.66%) rotate(-6.25deg); }
  87.50% { transform: translate(485.62%, 89.19%) rotate(-3.75deg); }
  93.75% { transform: translate(521.43%, 90.00%) rotate(0.07deg); }
  100.00% { transform: translate(557.20%, 89.28%) rotate(-1.15deg); }
}

@keyframes ttAiPulse-p4 {
   0.00% { transform: translate(0.00%, 0.00%) rotate(-2.86deg); }
   6.25% { transform: translate(31.48%, -1.57%) rotate(-3.80deg); }
  12.50% { transform: translate(62.80%, -4.17%) rotate(-5.23deg); }
  18.75% { transform: translate(93.99%, -7.29%) rotate(-5.40deg); }
  25.00% { transform: translate(125.39%, -10.09%) rotate(-6.87deg); }
  31.25% { transform: translate(156.55%, -14.83%) rotate(-8.36deg); }
  37.50% { transform: translate(187.69%, -19.24%) rotate(-4.96deg); }
  43.75% { transform: translate(216.85%, -20.06%) rotate(-1.69deg); }
  50.00% { transform: translate(246.32%, -20.97%) rotate(-7.39deg); }
  56.25% { transform: translate(277.07%, -27.87%) rotate(-17.63deg); }
  62.50% { transform: translate(305.97%, -39.93%) rotate(-13.96deg); }
  68.75% { transform: translate(336.99%, -42.76%) rotate(-2.54deg); }
  75.00% { transform: translate(368.50%, -42.70%) rotate(-3.75deg); }
  81.25% { transform: translate(399.70%, -46.87%) rotate(-7.07deg); }
  87.50% { transform: translate(430.96%, -50.44%) rotate(-1.82deg); }
  93.75% { transform: translate(462.41%, -48.86%) rotate(0.29deg); }
  100.00% { transform: translate(493.88%, -50.13%) rotate(-2.31deg); }
}

@keyframes ttAiPulse-p5 {
   0.00% { transform: translate(0.00%, 0.00%) rotate(7.50deg); }
   6.25% { transform: translate(22.78%, 3.00%) rotate(-7.26deg); }
  12.50% { transform: translate(44.14%, -5.63%) rotate(-27.91deg); }
  18.75% { transform: translate(63.38%, -18.51%) rotate(-33.80deg); }
  25.00% { transform: translate(82.64%, -31.40%) rotate(-32.24deg); }
  31.25% { transform: translate(102.54%, -43.21%) rotate(-29.38deg); }
  37.50% { transform: translate(122.96%, -54.09%) rotate(-26.92deg); }
  43.75% { transform: translate(143.83%, -64.16%) rotate(-23.53deg); }
  50.00% { transform: translate(165.39%, -72.57%) rotate(-19.56deg); }
  56.25% { transform: translate(187.43%, -79.66%) rotate(-15.62deg); }
  62.50% { transform: translate(209.96%, -85.03%) rotate(-12.00deg); }
  68.75% { transform: translate(232.72%, -89.28%) rotate(-6.88deg); }
  75.00% { transform: translate(255.84%, -90.56%) rotate(-3.31deg); }
  81.25% { transform: translate(278.95%, -91.96%) rotate(-2.07deg); }
  87.50% { transform: translate(302.01%, -92.24%) rotate(-1.44deg); }
  93.75% { transform: translate(325.12%, -93.12%) rotate(-2.87deg); }
  100.00% { transform: translate(348.25%, -94.55%) rotate(-3.55deg); }
}

/* For a vestibular trigger the movement itself is the problem, not its
   speed - so the pulses are removed outright rather than slowed. What is
   left is the complete static artwork, which loses nothing. */
@media (prefers-reduced-motion: reduce) {
  .tt-ai-stage.is-live .tt-ai-pulse {
    animation: none;
    opacity: 0;
  }
}


/* --- 8c. TIMETRAX AI LANDING PAGE (timetrax-ai.html only) ---------------

   Only the hero furniture lives here. Everything else on that page is the
   existing kit: .tt-hp-* for the sections and cards, and the .tt-ai-*
   component from section 8b for the flow diagram - the SAME markup, lifted
   out of index.html by scripts\build-timetrax-ai-page.ps1 so the two
   cannot drift apart.
   ------------------------------------------------------------------------ */

.tt-tai-hero {
  margin: 0 auto;
  max-width: 900px;
  text-align: center;
}

/* "30 Years of Excellence". The design floats this top-right of the hero;
   here it sits above the heading and centres, because a floated badge at
   phone width either overlaps the headline or has to be hidden - and this
   is a claim worth keeping on every screen. It is NOT invented: the site
   already says "now in its 30th year". */
.tt-tai-badge {
  align-items: baseline;
  background: var(--tt-primary);
  border-radius: 999px;
  color: #FFFFFF;
  display: inline-flex;
  font-size: 0.82rem;
  font-weight: 600;
  gap: 0.4rem;
  letter-spacing: 0.02em;
  margin: 0 0 1.25rem;
  padding: 0.4rem 1rem;
  text-transform: uppercase;
}

.tt-tai-badge span {
  font-family: var(--tt-font-secondary);
  font-size: 1.15rem;
  font-weight: 800;
  line-height: 1;
}

/* The claim line: an orange pill holding the product name, then the verb
   phrase in plain text beside it. Kept as ONE sentence so it reads
   correctly when the pill wraps onto its own line. */
.tt-tai-claim {
  align-items: center;
  color: var(--tt-dark);
  display: flex;
  flex-wrap: wrap;
  font-size: clamp(1rem, 1.6vw, 1.22rem);
  font-weight: 600;
  gap: 0.7rem;
  justify-content: center;
  margin: 0;
}

.tt-tai-claim-pill {
  align-items: center;
  background: var(--tt-primary);
  border-radius: 10px;
  color: #FFFFFF;
  display: inline-flex;
  font-weight: 700;
  gap: 0.5rem;
  padding: 0.5rem 0.95rem;
}

/* The icon reserves its own width so the pill does not resize when
   FontAwesome lands - it loads non-blocking site-wide, so an unsized <i>
   is a 0px box until it arrives. Same reason the app-login bar's glyph
   does it. */
.tt-tai-claim-pill i {
  font-size: 0.95em;
  width: 1em;
}

/* Three up, matching the design's 3x3. The shared .tt-hp-grid is content
   driven; these nine cards carry a name and nothing else, so they are
   given an explicit track count and a tighter card. */
.tt-tai-grid {
  gap: 1.1rem;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.tt-tai-card {
  align-items: center;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  justify-content: center;
  padding: 1.6rem 1rem;
  text-align: center;
}

@media (max-width: 767.98px) {
  .tt-tai-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 479.98px) {
  .tt-tai-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}


/* --- 9. MODULES FOLD SECTION (Fold 3) --- */

.tt-modules-section {
  background: rgba(246, 247, 249, 0.9);
}

.tt-modules-head {
  max-width: 640px;
  margin-bottom: 2.25rem;
}

.tt-modules-heading {
  font-family: var(--tt-font-secondary);
  font-weight: 500;
  font-size: clamp(1.6rem, 2.4vw, 2.5rem);
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--tt-dark);
}

.tt-modules-heading .fw-bold {
  color: var(--tt-primary);
}

.tt-modules-subtext {
  color: var(--tt-muted);
  font-size: 1rem;
  line-height: 1.55;
}

@media (max-width: 575.98px) {
  .tt-modules-head {
    margin-bottom: 1.5rem;
  }
}

.tt-modules-row {
  align-items: stretch;
  display: flex;
  gap: 1rem;
}

.tt-modules-row + .tt-modules-row {
  margin-top: 1.5rem;
}

.tt-modules-title-block {
  background: var(--tt-glass-bg-soft);
  backdrop-filter: var(--tt-glass-blur);
  -webkit-backdrop-filter: var(--tt-glass-blur);
  border: 1px solid var(--tt-glass-border);
  border-radius: 16px;
  box-shadow: var(--tt-glass-shadow);
  display: flex;
  min-width: 250px;
  padding: 1.5rem 1.25rem;
  width: 250px;
  position: relative;
  overflow: hidden;
}

/* Soft brand-orange glow in the corner, matching the ambient glow language used
   elsewhere on the site - a quiet way to give this block more presence without
   making it loud or playful. */
.tt-modules-title-block::before {
  content: '';
  position: absolute;
  top: -25%;
  right: -20%;
  width: 150px;
  height: 150px;
  background: none;
  border-radius: 50%;
  pointer-events: none;
  z-index: 0;
}

.tt-modules-title-block-right::before {
  right: auto;
  left: -20%;
}

.tt-modules-title-wrap {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-between;
  height: 100%;
  width: 100%;
  gap: 1.5rem;
}

.tt-modules-title-wrap-right {
  align-items: flex-end;
  text-align: right;
}

.tt-modules-title-group {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.7rem;
}

.tt-modules-title-wrap-right .tt-modules-title-group {
  align-items: flex-end;
}

.tt-modules-title-icon {
  align-items: center;
  background: var(--tt-gradient-primary);
  border-radius: 12px;
  box-shadow: 0 8px 18px -4px rgba(var(--tt-primary-rgb), 0.35);
  color: #FFFFFF;
  display: flex;
  font-size: 1.15rem;
  height: 46px;
  justify-content: center;
  width: 46px;
}

.tt-modules-title-accent {
  background: var(--tt-gradient-primary);
  border-radius: 999px;
  display: block;
  height: 4px;
  width: 42px;
}

.tt-modules-title {
  color: var(--tt-dark);
  font-family: var(--tt-font-secondary);
  font-size: 1.65rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 0;
}

.tt-modules-track-wrap {
  overflow: hidden;
  width: 100%;
}

/* Auto-scrolling track: width is content-driven (2x the real card set) and JS drives
   position via transform, so it can auto-play, pause on hover, and still respond to
   the prev/next arrows - the same technique used by the client-logo ticker. */
.tt-modules-track {
  display: flex;
  gap: 1rem;
  width: max-content;
  padding: 1px 0;
  will-change: transform;
}

.tt-module-card {
  background: var(--tt-glass-bg-soft);
  backdrop-filter: var(--tt-glass-blur);
  -webkit-backdrop-filter: var(--tt-glass-blur);
  border: 1px solid var(--tt-glass-border);
  border-radius: 14px;
  box-shadow: var(--tt-glass-shadow);
  flex: 0 0 260px;
  min-height: 224px;
  padding: 1.25rem 1.1rem;
  transition: var(--tt-transition-fast);
}

.tt-module-card:hover {
  border-color: rgba(var(--tt-primary-rgb), 0.4);
  box-shadow: var(--tt-shadow-md);
  transform: translateY(-4px);
}

.tt-module-icon {
  display: block;
  height: 42px;
  margin-bottom: 0.8rem;
  object-fit: contain;
  width: 42px;
}

.tt-module-title {
  color: #2E3135;
  font-family: var(--tt-font-primary);
  font-size: 1.02rem;
  font-weight: 700;
  line-height: 1.25;
  margin: 0 0 0.4rem;
}

.tt-module-desc {
  color: #666B72;
  display: -webkit-box;
  font-size: 0.95rem;
  line-height: 1.45;
  margin: 0 0 0.7rem;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

.tt-module-link {
  color: var(--tt-primary);
  font-size: 0.85rem;
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.tt-module-link:hover {
  color: var(--tt-primary-hover);
}

@media (max-width: 1199.98px) {
  .tt-modules-title {
    font-size: 1.55rem;
  }

  .tt-modules-title-block {
    min-width: 220px;
    width: 220px;
  }

  .tt-module-card {
    flex-basis: 220px;
  }
}

@media (max-width: 991.98px) {
  .tt-modules-row,
  .tt-modules-row-bottom {
    flex-direction: column;
  }

  .tt-modules-title-block,
  .tt-modules-title-block-right {
    min-width: unset;
    width: 100%;
  }

  .tt-modules-title-wrap,
  .tt-modules-title-wrap-right {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    text-align: left;
    gap: 1rem;
    width: 100%;
  }

  .tt-modules-title {
    font-size: 1.45rem;
  }

  .tt-module-card {
    flex-basis: 280px;
  }
}

@media (max-width: 575.98px) {
  .tt-modules-title {
    font-size: 1.35rem;
  }

  .tt-modules-title-block {
    padding: 1.25rem 1rem;
  }

  .tt-module-card {
    flex-basis: 250px;
    min-height: 208px;
    padding: 1rem;
  }

  .tt-module-icon {
    height: 36px;
    width: 36px;
  }

  .tt-module-title {
    font-size: 0.98rem;
  }

  .tt-module-desc {
    font-size: 0.9rem;
  }
}


/* --- 10. TESTIMONIALS SECTION (Fold 4) --- */

.tt-testimonials-section {
  background: rgba(243, 244, 246, 0.92);
  padding: calc(var(--tt-section-space) - 0.15rem) 0 calc(var(--tt-section-space) + 0.2rem);
  position: relative;
  overflow: hidden;
}

/* Ambient accent blobs for depth, consistent with the rest of the page */
.tt-testimonials-section::before,
.tt-testimonials-section::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  z-index: 0;
}

.tt-testimonials-section::before {
  top: -8%;
  right: -6%;
  width: 420px;
  height: 420px;
  background: none;
}

.tt-testimonials-section::after {
  bottom: -10%;
  left: -8%;
  width: 380px;
  height: 380px;
  background: none;
}

.tt-testimonials-section > .container {
  position: relative;
  z-index: 1;
}

.tt-testimonials-heading {
  color: var(--tt-dark);
  font-family: var(--tt-font-secondary);
  font-size: clamp(2rem, 2.7vw, 3.1rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.tt-testimonial-card {
  background: var(--tt-glass-bg-soft);
  border: 1px solid var(--tt-glass-border);
  border-radius: 24px;
  box-shadow: var(--tt-glass-shadow);
  padding: 1.6rem 1.7rem;
  transition: var(--tt-transition-standard), opacity 0.55s ease, transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);

  /* Scroll-reveal starting state (see .in-view rules + script.js initTestimonialsReveal) */
  opacity: 0;
  transform: translateY(28px);
}

.tt-testimonial-card:hover {
  border-color: rgba(var(--tt-primary-rgb), 0.25);
  box-shadow: 0 16px 34px -8px rgba(var(--tt-primary-rgb), 0.16), var(--tt-shadow-md);
  transform: translateY(-6px);
}

.tt-testimonials-section.in-view .tt-testimonial-card,
.tt-testimonials-section.in-view .tt-testimonials-video-wrap {
  opacity: 1;
  transform: translateY(0);
}

.tt-testimonials-section.in-view .tt-testimonial-card:hover {
  transform: translateY(-6px);
}

.tt-testimonial-featured { transition-delay: 0s; }
.tt-testimonials-video-wrap { transition-delay: 0.1s; }
.tt-testimonials-grid > .tt-testimonial-card:nth-child(1) { transition-delay: 0.2s; }
.tt-testimonials-grid > .tt-testimonial-card:nth-child(2) { transition-delay: 0.3s; }
.tt-testimonials-grid > .tt-testimonial-card:nth-child(3) { transition-delay: 0.4s; }
.tt-testimonials-grid > .tt-testimonial-card:nth-child(4) { transition-delay: 0.5s; }

@media (prefers-reduced-motion: reduce) {
  html, body {
    scroll-behavior: auto; /* honor the OS "reduce motion" setting for jumps */
  }

  .tt-testimonial-card,
  .tt-testimonials-video-wrap {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* Quote badge: small solid brand chip replacing a plain quotation mark, a common
   modern testimonial pattern that reads instantly without competing with the text. */
.tt-testimonial-quote-badge {
  align-items: center;
  background: var(--tt-gradient-primary);
  border-radius: 10px;
  box-shadow: 0 6px 14px -4px rgba(var(--tt-primary-rgb), 0.4);
  color: #FFFFFF;
  display: inline-flex;
  font-size: 0.85rem;
  height: 34px;
  justify-content: center;
  margin-bottom: 0.85rem;
  transition: var(--tt-transition-spring);
  width: 34px;
}

.tt-testimonial-card:hover .tt-testimonial-quote-badge {
  transform: rotate(-8deg) scale(1.08);
}

/* Star rating row - a quick, familiar signal of social proof */
.tt-testimonial-rating {
  color: var(--tt-primary);
  display: flex;
  font-size: 0.8rem;
  gap: 0.2rem;
  margin-bottom: 0.6rem;
}

.tt-testimonial-featured {
  margin-top: 0.8rem;
  min-height: 220px;
}

.tt-testimonial-quote {
  color: #3E434A;
  font-size: 1.02rem;
  line-height: 1.62;
  margin-bottom: 1.3rem;
}

.tt-testimonial-author {
  align-items: center;
  display: flex;
  gap: 0.8rem;
}

.tt-testimonial-logo {
  background: #F8FAFC;
  border: 1px solid rgba(229, 231, 235, 0.8);
  border-radius: 50%;
  height: 52px;
  object-fit: cover;
  padding: 2px;
  width: 52px;
}

.tt-testimonial-name {
  color: #24282F;
  font-family: var(--tt-font-primary);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.3;
  margin: 0;
}

.tt-testimonial-role {
  color: #59606B;
  font-size: 0.9rem;
  margin: 0.15rem 0 0;
}

.tt-testimonials-video-wrap {
  border-radius: 20px;
  height: 100%;
  overflow: hidden;
  position: relative;
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.55s ease, transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

.tt-testimonials-video-image {
  border-radius: 20px;
  display: block;
  min-height: 100%;
  object-fit: cover;
  width: 100%;
}

.tt-testimonials-play {
  align-items: center;
  background: rgba(255, 255, 255, 0.93);
  border: none;
  border-radius: 999px;
  color: var(--tt-primary);
  cursor: pointer;
  display: inline-flex;
  font-size: 2.1rem;
  height: 74px;
  justify-content: center;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  transition: var(--tt-transition-spring);
  width: 74px;
}

.tt-testimonials-play:hover {
  transform: translate(-50%, -50%) scale(1.06);
}

.tt-testimonials-grid {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: 1fr 1fr 1.1fr;
  grid-template-rows: auto auto;
}

.tt-testimonial-sm {
  min-height: 255px;
}

.tt-testimonial-lg {
  grid-column: 3;
  grid-row: 1 / span 2;
  min-height: 530px;
}

.tt-testimonial-wide {
  grid-column: 1 / span 2;
  min-height: 170px;
}

@media (max-width: 991.98px) {
  .tt-testimonials-section {
    padding: var(--tt-section-space-tight) 0;
  }

  .tt-testimonials-heading {
    font-size: clamp(1.65rem, 5vw, 2.3rem);
  }

  .tt-testimonials-grid {
    grid-template-columns: 1fr;
    grid-template-rows: auto;
  }

  .tt-testimonial-lg,
  .tt-testimonial-wide {
    grid-column: auto;
    grid-row: auto;
    min-height: auto;
  }

  .tt-testimonial-sm {
    min-height: auto;
  }

  .tt-testimonial-card {
    border-radius: 18px;
    padding: 1.2rem;
  }
}

@media (max-width: 575.98px) {
  .tt-testimonial-quote {
    font-size: 0.95rem;
  }

  .tt-testimonial-name {
    font-size: 1rem;
  }

  .tt-testimonial-role {
    font-size: 0.85rem;
  }

  .tt-testimonials-play {
    font-size: 1.4rem;
    height: 56px;
    width: 56px;
  }
}


/* --- 11. AWARDS SECTION (Fold 5) --- */

.tt-awards-section {
  background: rgba(243, 244, 246, 0.92);
  padding: calc(var(--tt-section-space) - 0.35rem) 0;
}

.tt-awards-heading {
  color: var(--tt-dark);
  font-size: clamp(1.9rem, 2.7vw, 3rem);
  letter-spacing: -0.02em;
  line-height: 1.14;
}

.tt-awards-copy {
  max-width: 320px;
}

.tt-awards-chip {
  background: var(--tt-gradient-primary);
  border-radius: 9px;
  color: #FFFFFF;
  display: inline-block;
  font-size: 1rem;
  font-weight: 700;
  padding: 0.45rem 0.8rem;
}

.tt-awards-text {
  color: #4E5560;
  font-size: var(--tt-fs-body-lg);
  line-height: 1.42;
}

.tt-awards-panel {
  background: var(--tt-glass-bg-soft);
  backdrop-filter: var(--tt-glass-blur);
  -webkit-backdrop-filter: var(--tt-glass-blur);
  border: 1px solid var(--tt-glass-border);
  border-radius: 22px;
  box-shadow: var(--tt-glass-shadow);
  display: flex;
  align-items: center;
  min-height: 210px;
  padding: 1.6rem 1.1rem;
  position: relative;
}

/* --- Awards auto-scroll marquee --- */
.tt-awards-marquee {
  overflow: hidden;
  position: relative;
  width: 100%;
  /* Soft edge fade so cards enter/exit smoothly */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 7%, #000 93%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, #000 7%, #000 93%, transparent 100%);
}

.tt-awards-marquee-track {
  display: flex;
  width: max-content;
  animation: ttAwardsScroll 45s linear infinite;
  will-change: transform;
}

/* Pointer-gated for the same reason as .tt-ticker-track:hover above: a
   synthesised touch :hover never lifts, so ungated this froze the awards
   strip on a phone the first time it was swiped over. */
@media (hover: hover) and (pointer: fine) {
  .tt-awards-marquee:hover .tt-awards-marquee-track {
    animation-play-state: paused;
  }
}

.tt-award-card {
  flex: 0 0 auto;      /* Fixed card box so the -50% loop point is deterministic before images load */
  width: 236px;
  height: 148px;
  margin: 0 0.55rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.9rem 1rem;
  background: #FFFFFF;
  border: 1px solid rgba(229, 231, 235, 0.9);
  border-radius: 14px;
  box-shadow: 0 4px 14px rgba(31, 38, 80, 0.06);
}

.tt-award-img {
  max-height: 100%;
  max-width: 100%;
  width: auto;
  object-fit: contain;
  display: block;
}

/* Glitchless hardware-accelerated loop; -50% lands exactly on the group duplication point */
@keyframes ttAwardsScroll {
  0% {
    transform: translate3d(0, 0, 0);
  }
  100% {
    transform: translate3d(-50%, 0, 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .tt-awards-marquee-track {
    animation: none;
  }
}


/* --- 12. INTEGRATIONS SECTION (Fold 6) --- */

.tt-integrations-section {
  background: rgba(243, 244, 246, 0.92);
  padding: calc(var(--tt-section-space) - 0.1rem) 0 calc(var(--tt-section-space) + 0.25rem);
  /* positioned so the run-out ::after below anchors to this section */
  position: relative;
}

/* Same dark-to-light "run-out" idea as the HCM freeze (see .tt-hcm-dark::after):
   this dark band is immediately followed by the light Mobile-App freeze, so when
   Mobile-App scrolls up into view the bottom of this dark box fills the top of the
   viewport as a hard black band (worst at zoom). Fade the bottom of the box to the
   Mobile-App page tone so that hand-off is a soft light edge, not a black line.
   Unlike HCM this section has no empty stage room - the hub artboard fills it - so
   the light is kept to the lowest strip (transparent until 78%), which the artboard
   sits above; it only tints the bottom padding, visible during the hand-off. */
/* (Bottom dark-to-light blend removed per request — the section now keeps its
   dark tone all the way to its edge instead of washing out to the Mobile-app
   color.) */

.tt-integrations-heading {
  color: #272C33;
  font-size: clamp(2rem, 2.8vw, 3rem);
  letter-spacing: -0.02em;
  line-height: 1.12;
}

.tt-integrations-copy {
  color: #555D67;
  font-size: var(--tt-fs-body-lg);
  line-height: 1.6;
  max-width: 590px;
}

.tt-integration-artboard {
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  max-width: 600px;
  position: relative;
  width: 100%;
}

/* Continuous ambient breathing glow, independent of the scroll-reveal transitions
   below (kept on its own element so the two animations never fight over transform). */
.tt-integration-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 74%;
  height: 74%;
  border-radius: 50%;
  background: none;
  z-index: 0;
  pointer-events: none;
  animation: ttIntegrationPulse 5s ease-in-out infinite;
}

@keyframes ttIntegrationPulse {
  0%, 100% { transform: translate(-50%, -50%) scale(0.92); opacity: 0.65; }
  50% { transform: translate(-50%, -50%) scale(1.08); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .tt-integration-glow {
    animation: none;
    transform: translate(-50%, -50%) scale(1);
    opacity: 0.85;
  }
}

.tt-integration-rings {
  background: rgba(var(--tt-accent-deep-rgb), 0.09);
  border-radius: 50%;
  height: 86%;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 86%;
}

.tt-integration-center {
  align-items: center;
  background: var(--tt-gradient-vivid);
  border-radius: 22px;
  box-shadow: 0 12px 30px -4px rgba(var(--tt-accent-deep-rgb), 0.4);
  display: flex;
  height: 120px;
  justify-content: center;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 120px;
  z-index: 4;
}

.tt-integration-center-mark {
  color: #FFFFFF;
  font-family: var(--tt-font-secondary);
  font-size: 3rem;
  font-weight: 800;
  line-height: 1;
}

/* White TimeTrax mark that sits inside the orange center tile (replaces the
   old "tt" text). The <picture> wrapper is stretched to fill the tile and
   centers the mark, so the logo stays dead-center regardless of the svg's
   intrinsic aspect ratio. */
.tt-integration-center picture {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
.tt-integration-center-logo {
  width: 58%;
  height: 58%;
  object-fit: contain;
}

/*
==================================================================
HEXAGON GEOMETRY: 6 nodes evenly spaced at exact 60-degree bearings
around the hub (top, upper-right, lower-right, bottom, lower-left,
upper-left) instead of ad-hoc percentage offsets, so every node sits
the same distance from center and every spoke points exactly at its
node - both scale together and stay symmetric at any artboard size.
==================================================================
*/

/* Spokes: a bar pinned at the hub, rotated to its node's bearing (0=up, clockwise).
   scaleY draws the bar outward on reveal without disturbing the rotation. */
/* THE BAR IS ANCHORED ABOVE THE CENTRE AND GROWS UPWARD, so rotate(0deg)
   really does point at 12 o'clock and rotate(N) is a true clockwise bearing -
   which is what the comment above has always claimed and what the node
   coordinates have always assumed.

   IT USED TO BE `top: 50%` + `transform-origin: top center`, which hangs the
   bar DOWNWARD: rotate(0deg) pointed at 6 o'clock while node 1 sat at 12. Every
   spoke was 180 degrees from its own logo.

   THAT BUG WAS INVISIBLE FOR AS LONG AS THERE WERE SIX NODES. A six-point set
   at 60deg spacing is symmetric under a 180deg rotation, so each spoke's tip
   still landed on *some* node - the wrong one, but a real one, and the figure
   looked correct. At five nodes and 72deg spacing that symmetry is gone
   (180 is not a multiple of 72), so every line pointed into the gap between two
   logos. Found 2026-09-21 after the ring went from six to five.

   `bottom: 50%` rather than `top: 12%` on purpose: it pins the bar's inner end
   to the exact centre no matter what `height` is, so changing the radius below
   cannot silently detach the spokes from the hub again.

   scaleY still grows the bar outward from the hub, because the origin is the
   end that sits at the centre. */
.tt-integration-spoke {
  position: absolute;
  bottom: 50%;
  left: 50%;
  width: 3px;
  height: 38%;
  background: rgba(var(--tt-primary-rgb), 0.45);
  transform-origin: bottom center;
  z-index: 1;
}

/* FIVE POSITIONS, 72deg APART, NUMBERED 1-5 CLOCKWISE FROM THE TOP.

   This was a hexagon until 2026-09-21, with classes named for compass
   bearings - tt-spoke-top, -upper-right, -bottom and so on. Mudad and Muqeem
   were dropped and Odoo added, taking the ring from six nodes to five, and
   direction names do not survive that: the node that used to be "bottom" is
   now at 144deg. Numbers do, which is why they are numbers now.

   IF THE COUNT CHANGES AGAIN, EVERY ANGLE AND EVERY PERCENTAGE BELOW IS
   STALE. They are not independent values - each is cos/sin of its own bearing
   on one circle, and a five-point set shares no member with a six-point set
   except the top. Recompute all of them together:

       bearing(i) = i * (360 / count)          i = 0 .. count-1
       left       = 50 + 38 * sin(bearing)
       top        = 50 - 38 * cos(bearing)

   38 is the spoke height above, as a percentage of the square artboard, so
   the nodes land exactly on the ends of their own spokes. Change one and
   change the other. */
.tt-spoke-1 { transform: translateX(-50%) rotate(0deg) scaleY(0); }
.tt-spoke-2 { transform: translateX(-50%) rotate(72deg) scaleY(0); }
.tt-spoke-3 { transform: translateX(-50%) rotate(144deg) scaleY(0); }
.tt-spoke-4 { transform: translateX(-50%) rotate(216deg) scaleY(0); }
.tt-spoke-5 { transform: translateX(-50%) rotate(288deg) scaleY(0); }

.tt-integration-node {
  align-items: center;
  background: var(--tt-glass-bg-soft);
  backdrop-filter: var(--tt-glass-blur);
  -webkit-backdrop-filter: var(--tt-glass-blur);
  border: 1px solid var(--tt-glass-border);
  border-radius: 22px;
  box-shadow: var(--tt-glass-shadow);
  display: flex;
  height: 88px;
  justify-content: center;
  padding: 0.75rem;
  position: absolute;
  width: 88px;
  z-index: 3;
  opacity: 1;
  transform: translate(-50%, -50%) scale(0.5);
}

.tt-integration-node img {
  max-height: 56px;
  max-width: 100%;
  object-fit: contain;
}

/* Node centers sit on the same 38%-radius circle as the spokes (cos/sin of the
   bearings above), positioned via their own center point + translate(-50%,-50%).
   Derived from the formula in the spoke comment - do not hand-tune one of
   these on its own, or a node drifts off the end of its spoke. */
.tt-node-1 { left: 50%;   top: 12%; }
.tt-node-2 { left: 86.1%; top: 38.3%; }
.tt-node-3 { left: 72.3%; top: 80.7%; }
.tt-node-4 { left: 27.7%; top: 80.7%; }
.tt-node-5 { left: 13.9%; top: 38.3%; }

/*
==================================================================
SCROLL REVEAL: staggered, clockwise from the top node, triggered
once via IntersectionObserver (see script.js) adding .in-view.
Respects prefers-reduced-motion (see media query below).
==================================================================
*/
.tt-integration-rings,
.tt-integration-center {
  opacity: 1;
  transform: translate(-50%, -50%) scale(0.6);
  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.tt-integration-spoke {
  transition: transform 0.45s ease-out;
  transition-delay: 0.15s;
}

.tt-integration-node {
  transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Each spoke leads its own node by 0.1s, and each pair follows the last by
   0.1s, so the ring draws itself clockwise from the top. Five pairs now, not
   six - the old cascade ran to 0.75s and now ends at 0.65s. */
.tt-node-1 { transition-delay: 0.25s; }
.tt-spoke-1 { transition-delay: 0.15s; }
.tt-node-2 { transition-delay: 0.35s; }
.tt-spoke-2 { transition-delay: 0.25s; }
.tt-node-3 { transition-delay: 0.45s; }
.tt-spoke-3 { transition-delay: 0.35s; }
.tt-node-4 { transition-delay: 0.55s; }
.tt-spoke-4 { transition-delay: 0.45s; }
.tt-node-5 { transition-delay: 0.65s; }
.tt-spoke-5 { transition-delay: 0.55s; }

.tt-integration-artboard.in-view .tt-integration-rings {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

.tt-integration-artboard.in-view .tt-integration-center {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

/* The SAME five bearings as the base rule, with scaleY(1). They have to agree
   exactly - a mismatch here does not fail, it makes the spoke swing round to a
   different angle as it grows. */
.tt-integration-artboard.in-view .tt-spoke-1 { transform: translateX(-50%) rotate(0deg) scaleY(1); }
.tt-integration-artboard.in-view .tt-spoke-2 { transform: translateX(-50%) rotate(72deg) scaleY(1); }
.tt-integration-artboard.in-view .tt-spoke-3 { transform: translateX(-50%) rotate(144deg) scaleY(1); }
.tt-integration-artboard.in-view .tt-spoke-4 { transform: translateX(-50%) rotate(216deg) scaleY(1); }
.tt-integration-artboard.in-view .tt-spoke-5 { transform: translateX(-50%) rotate(288deg) scaleY(1); }

.tt-integration-artboard.in-view .tt-integration-node {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .tt-integration-rings,
  .tt-integration-center,
  .tt-integration-spoke,
  .tt-integration-node {
    transition: none !important;
    opacity: 1 !important;
  }
  .tt-integration-rings,
  .tt-integration-center { transform: translate(-50%, -50%) scale(1) !important; }
  /* Third copy of the same five bearings. Keep all three in step. */
  .tt-spoke-1 { transform: translateX(-50%) rotate(0deg) scaleY(1) !important; }
  .tt-spoke-2 { transform: translateX(-50%) rotate(72deg) scaleY(1) !important; }
  .tt-spoke-3 { transform: translateX(-50%) rotate(144deg) scaleY(1) !important; }
  .tt-spoke-4 { transform: translateX(-50%) rotate(216deg) scaleY(1) !important; }
  .tt-spoke-5 { transform: translateX(-50%) rotate(288deg) scaleY(1) !important; }
  .tt-integration-node { transform: translate(-50%, -50%) scale(1) !important; }
}

@media (max-width: 991.98px) {
  .tt-awards-text,
  .tt-integrations-copy {
    font-size: 1.2rem;
  }

  .tt-awards-copy {
    max-width: unset;
  }

  .tt-awards-panel {
    min-height: auto;
    padding: 1rem 2rem 2rem;
  }

  .tt-integrations-section {
    padding: var(--tt-section-space-tight) 0;
  }

  .tt-integration-artboard {
    margin-top: 1rem;
    max-width: 480px;
  }
}

@media (max-width: 575.98px) {
  .tt-awards-panel {
    min-height: 170px;
    padding: 1.1rem 0.6rem;
  }

  .tt-award-card {
    width: 188px;
    height: 118px;
    margin: 0 0.4rem;
    padding: 0.7rem 0.8rem;
  }

  .tt-integration-artboard {
    max-width: 360px;
  }

  .tt-integration-center {
    border-radius: 16px;
    height: 92px;
    width: 92px;
  }

  .tt-integration-center-mark {
    font-size: 2.2rem;
  }

  .tt-integration-node {
    border-radius: 16px;
    height: 66px;
    width: 66px;
  }

  .tt-integration-node img {
    max-height: 40px;
  }
}


/* --- 13. MOBILE APP SHOWCASE SECTION (Fold 7) --- */

.tt-mobile-app-section {
  background: rgba(243, 244, 246, 0.92);
  padding: var(--tt-section-space) 0;
  position: relative;
  /* Same containment as the HCM freeze: keep the blooming phone/cards inside
     this section's box so they can't paint over neighbours at zoom / on release.
     `clip` keeps the inner position:sticky freeze working (not a scroll container);
     `isolate` keeps the pieces' positive z-index local so they never paint over
     the neighbouring sections' content. */
  overflow: clip;
  isolation: isolate;
}

.tt-mobile-app-section::before {
  content: '';
  position: absolute;
  top: 10%;
  left: 50%;
  transform: translateX(-50%);
  width: 70%;
  max-width: 900px;
  height: 320px;
  background: none;
  pointer-events: none;
  z-index: 0;
}

.tt-mobile-app-section .container {
  position: relative;
  z-index: 1;
}

.tt-mobile-app-head {
  max-width: 980px;
}

.tt-mobile-app-title {
  color: #2A2E33;
  font-family: var(--tt-font-secondary);
  font-size: var(--tt-fs-display);
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.15;
  margin: 0;
}

.tt-mobile-app-copy {
  color: #5A616C;
  font-size: var(--tt-fs-body-lg);
  line-height: 1.58;
  margin-left: auto;
  margin-right: auto;
  max-width: 900px;
}

.tt-btn-dark {
  align-items: center;
  background: var(--tt-gradient-dark);
  border: 1px solid #262A2F;
  border-radius: 999px;
  color: #FFFFFF;
  display: inline-flex;
  font-size: 1.02rem;
  font-weight: 700;
  justify-content: center;
  min-height: 52px;
  padding: 0.75rem 1.4rem;
  text-decoration: none;
  transition: var(--tt-transition-fast);
  box-shadow: 0 10px 24px -8px rgba(0, 0, 0, 0.35);
}

.tt-btn-dark:hover {
  filter: brightness(1.15);
  color: #FFFFFF;
  transform: translateY(-2px);
  box-shadow: 0 14px 30px -8px rgba(0, 0, 0, 0.45);
}

.tt-btn-consultancy {
  border-radius: 999px;
  min-height: 52px;
  padding: 0.75rem 1.5rem;
}

/*
==================================================================
MOBILE APP ARTWORK - now composed from 4 separate layers (mobile-1
through mobile-4, see index.html) instead of one flattened PNG, so
each piece can "bloom" into place independently on scroll. See
initMobileAppReveal in assets/js/script.js.
==================================================================
*/
/*
Freeze mechanism: plain CSS `position: sticky`, no GSAP, no JS-driven pin.
The stage (.tt-mobile-app-artwork-stage) reserves a tall scroll region and
the wrap (.tt-mobile-app-artwork-wrap) is `position: sticky` inside it, so as
the page scrolls the wrap holds still on screen ("freezes") for the whole
reserved distance, then releases when the stage's bottom passes. This ONLY
works because no ancestor clips overflow with `overflow: hidden` (which would
turn that ancestor into the sticky scroll container and stop the freeze from
ever engaging) - html/body and .page-wrapper deliberately use `overflow-x:
clip` instead of hidden for exactly this reason (see notes up top). A GSAP
position:fixed pin was tried earlier and repeatedly broke/duplicated layout
across the page; sticky is fully contained by normal document flow and can't
leak like that. assets/js/script.js's initStickyBloom only measures the real
header height (to set the sticky `top` offset) and drives the bloom progress;
it does NOT create the freeze. Mobile / reduced-motion drops out of the
@media block below and uses the free-scrub fallback (initFreeScrubBloom), no
freeze, no reserved scroll room.
*/
.tt-mobile-app-artwork-stage {
  position: relative;
}

.tt-mobile-app-artwork-wrap {
  margin: 0.6rem auto 0;
  position: relative;
  max-width: 1280px;
  width: 100%;
  aspect-ratio: 17 / 10;
}

@media (min-width: 992px) {
  .tt-mobile-app-artwork-stage {
    /* ~100vh for the phone's own scroll-in (Phase 1, before the wrap sticks)
       + ~130vh of frozen-hold room for the 3 cards to pop out one by one
       (Phase 2) - see initStickyBloom in assets/js/script.js. */
    height: 230vh;
  }

  .tt-mobile-app-artwork-wrap {
    position: sticky;
    /* JS (initStickyBloom) overwrites this inline with the measured fixed
       header height + 12px; 135px is a sane pre-JS/no-JS fallback. */
    top: 135px;
    margin: 0 auto;
    /* Same viewport-fit guard as the HCM freeze: cap width so the 17/10-derived
       height stays within the visible viewport at any zoom, so the phone + cards
       never spill past the freeze into the neighbouring sections. */
    width: min(100%, calc((100vh - 160px) * 17 / 10));
  }
}

@media (min-width: 992px) and (prefers-reduced-motion: reduce) {
  .tt-mobile-app-artwork-stage {
    height: auto;
  }

  .tt-mobile-app-artwork-wrap {
    position: relative;
    margin: 0.6rem auto 0;
  }
}

/* A clean, brand-toned glow drawn in CSS behind the artwork - the phone layer
   (mobile-2) still carries its own baked-in glow; this just extends that same
   color language a little further so the composition doesn't feel boxed in. */
.tt-mobile-app-artwork-glow {
  position: absolute;
  top: 46%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 62%;
  height: 66%;
  background: none;
  filter: blur(40px);
  z-index: 0;
  pointer-events: none;
}

/*
Bidirectional scroll-scrub, never a one-shot reveal - assets/js/script.js's
initMobileAppBloom writes a per-piece --tt-bloom-local (and a clamped twin,
--tt-bloom-clamped, for filter()) directly onto each piece; this file only
reads those. Two flavors depending on viewport:

- Desktop (initStickyBloom): the wrap is `position: sticky` inside the tall
  stage above, so it "freezes" on screen for the reserved scroll distance.
  The phone blooms in first during ordinary scroll (Phase 1, before the wrap
  sticks); the instant it sticks, the 3 cards bloom in one by one (Phase 2)
  from how far the stage has scrolled through the frozen region. Scrolling
  back up reverses it exactly - cards retract first, then, once unstuck, the
  phone un-blooms as it scrolls back off-screen.
- Mobile / reduced-motion (initFreeScrubBloom): the original single-phase
  version, all 4 pieces blooming in sequence purely from the wrap's own
  scroll position, no freeze, no reserved extra scroll room.

Default --tt-bloom-local is 1 (fully bloomed / correct static layout) so the
section still looks right with JS disabled; the script pulls it back only
once it starts measuring scroll position.
*/
.tt-mobile-app-piece {
  position: absolute;
  top: 50%;
  left: 50%;
  --bloom-x: 0px;
  --bloom-y: 0px;
  --tt-bloom-local: 1;
  --tt-bloom-clamped: 1;
  /* transform reads the raw (possibly slightly >1, spring-overshooting) value -
     harmless for translate/scale and is what gives each piece's pop its snap.
     filter reads the clamped twin: blur()/brightness()/saturate() are invalid
     with a negative input, which an overshoot would otherwise briefly produce. */
  transform:
    translate(-50%, -50%)
    translate(calc(var(--bloom-x) * (1 - var(--tt-bloom-local))), calc(var(--bloom-y) * (1 - var(--tt-bloom-local))))
    scale(calc(0.08 + 0.92 * var(--tt-bloom-local)));
  opacity: 1;
  will-change: transform;
  pointer-events: none;
}

.tt-mobile-app-piece img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 14px 28px rgba(0, 0, 0, 0.1));
}

/* Composition - percentages mirror the original flattened artwork's layout.
   --bloom-x/y = (phone SCREEN's middle - this piece's final position) in px,
   at the 1280px-wide desktop artwork size: every piece blooms outward FROM
   the middle of the phone's screen specifically (50%, 46% of the wrap - not
   the phone box's own geometric center, which sits a little lower/differently
   once the crop's object-position is accounted for), and each piece's z-index
   below the phone's means it visibly emerges from behind/under it. */
.tt-mapp-clock { top: 40%; left: 20%; width: 23%; z-index: 2; --bloom-x: 384px; --bloom-y: 45px; }
/* z-index 4 (above the phone's 3): the enlarged phone overlaps this card, so it
   sits ON TOP of the phone instead of being clipped behind it. */
.tt-mapp-payslip { top: 68%; left: 24%; width: 29%; z-index: 4; --bloom-x: 333px; --bloom-y: -166px; }
.tt-mapp-alerts { top: 44%; left: 80%; width: 27%; z-index: 2; --bloom-x: -384px; --bloom-y: 15px; }

/* mobile-2.png's own canvas is much wider/taller than the phone it depicts (it
   still carries its own baked-in glow blob around the phone), so sizing it by
   width % like the others scales all that empty canvas too, shrinking the
   actual phone to a fraction of the intended size. Instead: drive its size by
   height, and crop with object-fit so only the phone+hand region renders. */
.tt-mapp-phone {
  top: 52%;
  left: 50%;
  height: 138%;
  width: auto;
  aspect-ratio: 344 / 750;
  z-index: 3;
  --bloom-x: 0px;
  --bloom-y: 0px;
}

.tt-mapp-phone img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 41%;
}

@media (prefers-reduced-motion: reduce) {
  .tt-mobile-app-piece {
    --tt-bloom-local: 1 !important;
    --tt-bloom-clamped: 1 !important;
  }
}

@media (max-width: 575.98px) {
  .tt-mobile-app-artwork-wrap {
    aspect-ratio: 4 / 5;
  }

  /* Recomputed for the mobile layout's different final positions/aspect ratio */
  .tt-mapp-phone { height: 46%; width: auto; top: 42%; --bloom-x: 0px; --bloom-y: 0px; }
  .tt-mapp-clock { width: 34%; top: 16%; left: 22%; --bloom-x: 112px; --bloom-y: 170px; }
  .tt-mapp-alerts { width: 40%; top: 68%; left: 78%; --bloom-x: -112px; --bloom-y: -90px; }
  .tt-mapp-payslip { width: 42%; top: 88%; left: 30%; --bloom-x: 80px; --bloom-y: -190px; }
}

@media (max-width: 991.98px) {
  .tt-mobile-app-section {
    padding: var(--tt-section-space-tight) 0;
  }

  .tt-mobile-app-head {
    max-width: 760px;
  }

  .tt-mobile-app-copy {
    max-width: 700px;
  }
}

@media (max-width: 575.98px) {
  .tt-mobile-app-title {
    font-size: clamp(1.45rem, 7.8vw, 1.9rem);
  }

  .tt-mobile-app-copy {
    font-size: 0.98rem;
    line-height: 1.54;
  }

  .tt-mobile-app-cta {
    gap: 0.75rem;
  }

  .tt-btn-dark,
  .tt-btn-consultancy {
    min-height: 46px;
    width: 100%;
  }
}


/* --- 13b. HCM DASHBOARD SHOWCASE SECTION ---
   Same blooming-freeze technique as the Mobile App Showcase above (see the
   blooming-element-freeze-screen skill): a central dashboard screenshot with 8
   cards that bloom out of it one by one while the section is frozen via CSS
   position: sticky. All motion is driven by --tt-bloom-local/--tt-bloom-clamped
   per piece (written each frame by initHcmBloom in assets/js/script.js). */

.tt-hcm-section {
  /* Deeper than the near-white page so the white dashboard + card screenshots
     (and their shadows) read clearly against it - a flat near-white bg made them
     blend in. Medium warm greige with a faint brand tint keeps it on-brand. */
  background:
    #D2C6BB;
  padding: var(--tt-section-space) 0;
  position: relative;
  /* Contain the blooming cards (z-index 3-4) to this section's box so they can
     never paint over the next section during the freeze-release transition or at
     zoom. `clip` (not `hidden`) does NOT create a scroll container, so the inner
     position:sticky freeze keeps resolving against the viewport - see the sticky
     note at the top of this file. */
  overflow: clip;
  /* Establish a stacking context so the cards' positive z-index stays LOCAL to
     this section. Without it those z-index:4 cards live in the root stacking
     context and paint OVER the following Benefits section wherever the two boxes
     meet (the stuck wrap reaches the boundary on short viewports / zoom), so
     Benefits looked like it slid "under" this section. This is the real fix;
     overflow:clip alone only contains geometry, not paint order. */
  isolation: isolate;
}

.tt-hcm-head {
  max-width: 900px;
}

.tt-hcm-title {
  color: #2A2E33;
  font-family: var(--tt-font-secondary);
  font-size: var(--tt-fs-display);
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.15;
  margin: 0;
}

.tt-hcm-copy {
  color: #5A616C;
  font-size: var(--tt-fs-body-lg);
  line-height: 1.58;
  margin-left: auto;
  margin-right: auto;
  max-width: 760px;
}

.tt-hcm-artwork-stage {
  position: relative;
}

.tt-hcm-artwork-wrap {
  margin: 1rem auto 0;
  position: relative;
  max-width: 1400px;
  width: 100%;
  /* 16/9 (~787px tall at max width) so the whole composition fits inside the
     frozen viewport below the fixed header - a taller wrap would push the bottom
     cards off-screen during the freeze. */
  aspect-ratio: 16 / 9;
}

/* Brand-toned ambient glow behind the composition, same idea as the mobile-app
   section's glow (extends the warm brand color out from behind the dashboard). */
.tt-hcm-artwork-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 58%;
  height: 62%;
  background: none;
  filter: blur(46px);
  z-index: 0;
  pointer-events: none;
}

/* The freeze: pure CSS position: sticky, desktop only. The stage reserves a tall
   scroll region (100vh bloom-in + dwell for the 8 cards) and the wrap sticks
   inside it. Works only because no ancestor clips overflow with overflow:hidden -
   html/body/.page-wrapper use overflow-x: clip (see notes at top of this file). */
@media (min-width: 992px) {
  .tt-hcm-artwork-stage {
    height: 330vh;
    /* Short trailing run-out AFTER the sticky stage. The dashboard fades OUT in
       place at the tail of the freeze (applyTailFade in assets/js/script.js), so it
       no longer has to scroll a full viewport off before Benefits can enter - this
       run-out only has to cover the brief fade + the bg blend, which keeps the gap
       between the two sections small (a full 100vh here felt like too much dead
       space at 100% zoom). Faded to the Benefits tone by .tt-hcm-dark::after so
       Benefits rises in over clean light. Keep ~in sync with applyTailFade's fadeEnd
       (art must be gone by the time Benefits enters, i.e. run-out ≲ the fade-out
       distance). Reduced-motion has no freeze, so it resets this to 0 below. */
    margin-bottom: 55vh;
  }

  .tt-hcm-artwork-wrap {
    position: sticky;
    top: 135px; /* JS (initHcmBloom) overwrites inline with measured header height + 12px */
    margin: 0 auto;
    /* Cap the WIDTH so the 16/9-derived HEIGHT never exceeds the space visible
       below the sticky header. The freeze assumes the whole composition fits in
       the viewport; without this, zooming in shrinks the viewport's CSS-px
       height below the width-derived wrap height, so the bottom cards + dark
       band spill past the freeze and overlap the Benefits section. min() keeps
       normal (unzoomed) sizing untouched and only scales the art down when the
       viewport gets short. */
    width: min(100%, calc((100vh - 160px) * 16 / 9));
  }
}

@media (min-width: 992px) and (prefers-reduced-motion: reduce) {
  .tt-hcm-artwork-stage {
    height: auto;
    /* No freeze here, so no curtain either - drop the cover room (Benefits is not
       pinned under reduced motion, so it would otherwise be 100vh of dead space). */
    margin-bottom: 0;
  }

  .tt-hcm-artwork-wrap {
    position: relative;
    margin: 1rem auto 0;
  }
}

/*
Each piece collapses onto the dashboard center at bloom 0 and blooms out to its
resting spot at bloom 1. --bloom-x/y = (dashboard center - this card's final
center) in px at the 1400px-wide artwork size, so the card travels FROM behind
the dashboard TO its final position. Default --tt-bloom-local is 1 so the static
(JS-off) state is the finished composition. transform reads the raw springy
value; filter reads the clamped twin (blur/brightness/saturate reject negatives).
*/
.tt-hcm-piece {
  position: absolute;
  top: 50%;
  left: 50%;
  --bloom-x: 0px;
  --bloom-y: 0px;
  --tt-bloom-local: 1;
  --tt-bloom-clamped: 1;
  transform:
    translate(-50%, -50%)
    translate(calc(var(--bloom-x) * (1 - var(--tt-bloom-local))), calc(var(--bloom-y) * (1 - var(--tt-bloom-local))))
    scale(calc(0.08 + 0.92 * var(--tt-bloom-local)));
  opacity: var(--tt-bloom-local);
  filter:
    blur(calc(24px * (1 - var(--tt-bloom-clamped))))
    brightness(calc(1 + 0.4 * (1 - var(--tt-bloom-clamped))))
    saturate(calc(1 + 0.3 * (1 - var(--tt-bloom-clamped))));
  will-change: transform, opacity, filter;
  pointer-events: none;
}

.tt-hcm-piece img {
  display: block;
  width: 100%;
  height: auto;
}

/* Refined layered drop-shadow on the cards (tight contact + soft ambient) so the
   white panels lift crisply off the background. Per-variant overrides below tune
   the tint/strength. */
.tt-hcm-piece:not(.tt-hcm-dashboard) img {
  filter:
    drop-shadow(0 2px 4px rgba(16, 24, 40, 0.12))
    drop-shadow(0 18px 34px rgba(16, 24, 40, 0.2));
}

.tt-hcm-dashboard {
  width: 56%;
  z-index: 3;
}

/* Browser/app "window chrome": the dashboard screenshot sits inside a framed
   window (rounded corners, 1px border, layered shadow) with a slim chrome bar +
   3 traffic-light dots on top - reads as a live product window (modern SaaS). */
.tt-hcm-window {
  width: 100%;
  border-radius: 14px;
  overflow: hidden;
  background: #FFFFFF;
  border: 1px solid rgba(16, 24, 40, 0.1);
  box-shadow: 0 30px 70px -18px rgba(16, 24, 40, 0.3), 0 10px 26px -10px rgba(16, 24, 40, 0.16);
}

.tt-hcm-chrome {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 30px;
  padding: 0 14px;
  background: #ECEEF1;
  border-bottom: 1px solid rgba(16, 24, 40, 0.08);
}

.tt-hcm-chrome span {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  display: block;
}

.tt-hcm-chrome span:nth-child(1) { background: #FF5F57; }
.tt-hcm-chrome span:nth-child(2) { background: #FEBC2E; }
.tt-hcm-chrome span:nth-child(3) { background: #28C840; }

.tt-hcm-window img {
  border-radius: 0;
}

/* 3D tilt group: the WHOLE composition (dashboard + cards) tilts as one flat
   group in perspective for depth. Applied here, never on the sticky wrap (a
   transform there breaks position: sticky). No preserve-3d on purpose - the
   pieces use filter() for the bloom, which is incompatible with true 3D; the
   group is rendered flat then tilted, which keeps the bloom animation intact. */
.tt-hcm-plane {
  position: absolute;
  inset: 0;
  transform: perspective(1700px) rotateX(7deg) rotateY(-3deg) scale(0.97);
  transform-origin: 50% 45%;
}

/* --- Heading: eyebrow pill badge + solid accent word (both variants) --- */
.tt-hcm-eyebrow {
  display: inline-block;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.4rem 0.95rem;
  border-radius: 999px;
  margin-bottom: 1rem;
  color: var(--tt-primary);
  background: rgba(var(--tt-primary-rgb), 0.12);
  border: 1px solid rgba(var(--tt-primary-rgb), 0.3);
}

.tt-hcm-title-accent {
  color: var(--tt-primary);
}

/* Keep the heading/copy above the section's texture layers */
.tt-hcm-section .container {
  position: relative;
  z-index: 1;
}

/* --- Dot-grid texture behind the composition (both variants), edge-faded --- */
.tt-hcm-artwork-wrap::before {
  content: '';
  position: absolute;
  inset: -12% -8%;
  background-image: radial-gradient(currentColor 1.3px, transparent 1.4px);
  background-size: 26px 26px;
  color: rgba(16, 24, 40, 0.07);
  -webkit-mask-image: radial-gradient(72% 62% at 50% 46%, #000 0%, transparent 80%);
  mask-image: radial-gradient(72% 62% at 50% 46%, #000 0%, transparent 80%);
  z-index: 0;
  pointer-events: none;
}

/* ==================== DARK SPOTLIGHT BAND ==================== */
.tt-hcm-section.tt-hcm-dark {
  background:
    #0C0D11;
}

/* Light "run-out" at the very bottom of the dark HCM box. This section is a full
   dark band immediately followed by the light Benefits freeze; because the two
   sections tile, whenever Benefits scrolls up into view the bottom of THIS box
   still fills the top of the viewport - a hard black band that reads as "Benefits
   sliding under the dark section" (worst at browser zoom, where the shared
   transition viewport is a big fraction of the screen). Fading the last viewport
   of this box to the exact Benefits page tone makes that boundary seamless: what
   shows above Benefits during the hand-off is now light, not black. It sits at
   z-index 0 (behind the dashboard/cards, above the base background) so it only
   affects the empty bottom run-out, which is off-screen during the dashboard's
   own frozen bloom and only becomes visible as the composition scrolls away. */
.tt-hcm-dark::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* Flat light run-out. This was a multi-stop fade to the Benefits page tone;
     with gradients removed site-wide it is now a solid block of that same tone,
     sized to the part of the old ramp that was already fully opaque (the old
     box was 100vh and reached solid #FCFAF8 at 56%, i.e. the bottom ~44vh).
     The hand-off is therefore still LIGHT rather than a hard black band - the
     thing this element exists to prevent - but the boundary is now a clean edge
     instead of a fade. Do not restore the gradient. */
  height: 44vh;
  background: #FCFAF8;
  z-index: 0;
  pointer-events: none;
}

.tt-hcm-dark .tt-hcm-title { color: #F4F6FA; }
.tt-hcm-dark .tt-hcm-copy { color: #A9B0BD; }

.tt-hcm-dark .tt-hcm-eyebrow {
  color: #FFB985;
  background: rgba(var(--tt-primary-rgb), 0.16);
  border-color: rgba(var(--tt-primary-rgb), 0.42);
}

.tt-hcm-dark .tt-hcm-artwork-wrap::before {
  color: rgba(255, 255, 255, 0.07);
}

/* Bigger, warmer aurora glow behind the composition on the dark band */
.tt-hcm-dark .tt-hcm-artwork-glow {
  width: 72%;
  height: 74%;
  background: none;
  filter: blur(60px);
}

/* Cards need deeper, darker shadows to lift off the dark band */
.tt-hcm-dark .tt-hcm-piece:not(.tt-hcm-dashboard) img {
  filter:
    drop-shadow(0 2px 6px rgba(0, 0, 0, 0.45))
    drop-shadow(0 24px 44px rgba(0, 0, 0, 0.55));
}

.tt-hcm-dark .tt-hcm-window {
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow: 0 44px 96px -22px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.05);
}

/* --- Disable the 3D tilt where it doesn't belong --- */
@media (prefers-reduced-motion: reduce) {
  .tt-hcm-plane { transform: none; }
}

/* Card composition - 4 left, 4 right, staggered top to bottom. z-index 4 (ABOVE
   the dashboard's 3) so the cards layer on top of / overlap the dashboard as they
   bloom out, rather than tucking behind it. --bloom-x/y computed as
   1400*(0.5 - left%) , 787.5*(0.5 - top%) = the vector from a card's resting spot
   back to the dashboard center. */
.tt-hcm-card-1 { top: 13%; left: 21%; width: 22%; z-index: 4; --bloom-x: 406px; --bloom-y: 291px; }
.tt-hcm-card-2 { top: 13%; left: 79%; width: 23%; z-index: 4; --bloom-x: -406px; --bloom-y: 291px; }
.tt-hcm-card-3 { top: 37%; left: 18%; width: 22%; z-index: 4; --bloom-x: 448px; --bloom-y: 102px; }
.tt-hcm-card-4 { top: 37%; left: 82%; width: 22%; z-index: 4; --bloom-x: -448px; --bloom-y: 102px; }
.tt-hcm-card-5 { top: 63%; left: 18%; width: 21%; z-index: 4; --bloom-x: 448px; --bloom-y: -102px; }
.tt-hcm-card-6 { top: 63%; left: 82%; width: 23%; z-index: 4; --bloom-x: -448px; --bloom-y: -102px; }
.tt-hcm-card-7 { top: 87%; left: 22%; width: 23%; z-index: 4; --bloom-x: 392px; --bloom-y: -291px; }
.tt-hcm-card-8 { top: 87%; left: 78%; width: 23%; z-index: 4; --bloom-x: -392px; --bloom-y: -291px; }

@media (prefers-reduced-motion: reduce) {
  .tt-hcm-piece {
    --tt-bloom-local: 1 !important;
    --tt-bloom-clamped: 1 !important;
  }
}

@media (max-width: 991.98px) {
  .tt-hcm-section {
    padding: var(--tt-section-space-tight) 0;
  }

  /* No 3D tilt on small screens - it reads awkwardly and eats horizontal room. */
  .tt-hcm-plane {
    transform: none;
  }

  /* CURRENTLY UNREACHABLE: everything below re-lays out the bloom for narrow
     screens, but at this width the whole stage is now replaced by a flat image
     (see the TOUCH FALLBACK block further down). Kept rather than deleted so
     the tuned card positions are not lost if the fallback breakpoint is ever
     lowered back to phone-only. */

  /* Tablet/phone: taller wrap so the stacked cards have room without the freeze. */
  .tt-hcm-artwork-wrap {
    aspect-ratio: 3 / 4;
  }

  .tt-hcm-dashboard { width: 62%; }

  .tt-hcm-card-1 { top: 9%;  left: 20%; width: 34%; }
  .tt-hcm-card-2 { top: 9%;  left: 80%; width: 34%; }
  .tt-hcm-card-3 { top: 30%; left: 16%; width: 30%; }
  .tt-hcm-card-4 { top: 30%; left: 84%; width: 30%; }
  .tt-hcm-card-5 { top: 70%; left: 16%; width: 30%; }
  .tt-hcm-card-6 { top: 70%; left: 84%; width: 32%; }
  .tt-hcm-card-7 { top: 91%; left: 22%; width: 34%; }
  .tt-hcm-card-8 { top: 91%; left: 78%; width: 34%; }
}

/* --- TOUCH FALLBACK: flat image instead of the blooming stage ---
   Both bloom compositions place their cards with percentage offsets around a
   centre piece. Below the desktop breakpoint the stage is too narrow for the
   cards to clear each other and they overlap into a mess, so the animated
   stage is dropped entirely and a single flat image takes its place.
   991.98px matches the navbar's own breakpoint: desktop keeps the animation,
   tablet and phone both get the image. */
.tt-hcm-static,
.tt-mobile-app-static {
  display: none;
}

/* --- STATIC HCM DASHBOARD (index.html) ---
   index.html shows the flat dashboard shot on EVERY viewport; the animated
   blooming-freeze version is archived in index-old.html. The `tt-hcm-plain`
   modifier (a) forces the static image to show at desktop widths too and
   (b) removes the freeze-only run-out fade (.tt-hcm-dark::after) - without a
   freeze there is nothing to blend and a 100vh fade would wash the image.
   Placed AFTER .tt-hcm-dark::after so `content: none` wins on equal specificity. */
/* NO BORDER, RADIUS OR CARD SHADOW - the artwork is a TRANSPARENT device
   mockup now, not the rectangular screenshot this rule was written for.
   All three were right for a flat screenshot and are wrong here: a rounded
   box drawn around a cut-out tablet frames empty space and reads as a
   mistake. Put them back only if the image goes back to a full-bleed
   rectangle. */
.tt-hcm-plain .tt-hcm-static {
  display: block;
  height: auto;
  margin: 1.5rem auto 0;
  max-width: min(100%, 1040px);
}

/* --- STATIC MOBILE APP ARTWORK (index.html) ---
   Mirrors `tt-hcm-plain` exactly: the modifier forces the flat composite
   to show at desktop widths too, where the base rule hides it because it
   was only ever the below-992px fallback for the blooming stage.

   The stage is gone from the markup, so its 230vh reserved scroll region
   and sticky freeze go with it; the rules for it are left in place further
   up, orphaned, in case it is ever restored. */
.tt-mapp-plain .tt-mobile-app-static {
  display: block;
  height: auto;
  margin: 1.5rem auto 0;
  max-width: min(100%, 1040px);
}

.tt-hcm-plain::after {
  content: none;
}

@media (max-width: 991.98px) {
  .tt-hcm-artwork-stage,
  .tt-mobile-app-artwork-stage {
    display: none;
  }

  .tt-hcm-static,
  .tt-mobile-app-static {
    display: block;
    height: auto;
    margin: 0 auto;
    max-width: 100%;
  }

  /* Border/radius/shadow dropped here too - same reason as the desktop
     rule above: the artwork is a transparent mockup, not a screenshot. */
  .tt-hcm-static {
    margin-top: 0.5rem;
  }

  .tt-mobile-app-static {
    margin-top: 0.75rem;
  }
}

@media (max-width: 575.98px) {
  .tt-hcm-title {
    font-size: clamp(1.45rem, 7.8vw, 1.9rem);
  }

  .tt-hcm-copy {
    font-size: 0.98rem;
    line-height: 1.54;
  }
}


/* --- 14. CONTACT FORM SECTION (Fold 8) --- */

.tt-contact-section {
  background: rgba(243, 244, 246, 0.95);
  padding: var(--tt-section-space) 0;
}

.tt-contact-head {
  max-width: 980px;
}

.tt-contact-title {
  color: #2A2E33;
  font-size: var(--tt-fs-display);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.14;
}

.tt-contact-subtitle {
  color: #6A717C;
  font-size: var(--tt-fs-body-lg);
}

/* One flat, calm card instead of a loud gradient panel with a second glass
   card nested inside it - the brand orange now shows up only in accents
   (button, stat numbers, focus states), not as the whole background. */
.tt-contact-panel {
  background: #FFFFFF;
  border: 1px solid #E1E4E9;
  border-radius: 24px;
  padding: 2.5rem;
  box-shadow: 0 30px 60px -20px rgba(16, 24, 40, 0.18), 0 8px 24px -8px rgba(16, 24, 40, 0.1);
}

.tt-contact-form-wrap {
  color: var(--tt-dark);
  height: 100%;
}

.tt-contact-form-title {
  color: var(--tt-dark);
  font-size: var(--tt-fs-h4);
  font-weight: 600;
  line-height: 1.35;
  margin-bottom: 1.3rem;
}

.tt-contact-input {
  background: #FFFFFF;
  border: 2px solid #AEB4BD;
  border-radius: 10px;
  color: var(--tt-dark);
  font-size: 0.98rem;
  min-height: 52px;
  padding: 0.75rem 1rem;
  width: 100%;
}

.tt-contact-input::placeholder {
  color: #A6ADB8;
}

.tt-contact-input:focus {
  background: #FFFFFF;
  border-color: rgba(var(--tt-primary-rgb), 0.45);
  box-shadow: 0 0 0 3px rgba(var(--tt-primary-rgb), 0.12);
  outline: none;
}

.tt-contact-textarea {
  min-height: 160px;
  resize: none;
}

.tt-contact-submit {
  background: var(--tt-gradient-dark);
  border: 1px solid #262A2F;
  border-radius: 10px;
  color: #FFFFFF;
  display: inline-flex;
  align-items: center;
  font-size: 0.98rem;
  font-weight: 600;
  justify-content: center;
  min-height: 44px;
  transition: var(--tt-transition-fast);
  width: 100%;
}

.tt-contact-submit:hover {
  filter: brightness(1.15);
  border-color: #181B20;
}

.tt-contact-submit:disabled {
  cursor: default;
  opacity: 0.7;
  filter: none;
}

/* Web3Forms honeypot: off-screen, never display:none - bots skip fields that
   never render. Mirrors .tt-bc-hp on the booking form. */
.tt-contact-hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Submission result. Only ever set by the script AFTER Web3Forms accepts, so
   the visitor is never told a message was sent when it was not. */
.tt-contact-status {
  margin: 0.85rem 0 0;
  padding: 0.7rem 0.9rem;
  border-radius: 10px;
  font-size: 0.92rem;
  line-height: 1.5;
}
.tt-contact-status[hidden] { display: none; }
.tt-contact-status.is-busy {
  background: rgba(0, 0, 0, 0.04);
  color: var(--tt-muted);
}
.tt-contact-status.is-ok {
  background: rgba(16, 185, 129, 0.10);
  color: #0F6E52;
}
.tt-contact-status.is-err {
  background: rgba(220, 38, 38, 0.09);
  color: #A32020;
}

/* Its own clearly-different surface (not a near-white wash) so it reads as a
   distinct block from the white form side at a glance. */
.tt-contact-stats-card {
  background: rgba(var(--tt-accent-light-rgb), 0.4);
  border: 1px solid rgba(var(--tt-primary-rgb), 0.22);
  border-radius: 20px;
  padding: 2.25rem;
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  height: 100%;
  justify-content: center;
}

.tt-contact-stats-title {
  color: var(--tt-dark);
  font-size: var(--tt-fs-h3);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.25;
  margin-bottom: 0;
}

.tt-contact-stats-grid {
  column-gap: 1.5rem;
  display: grid;
  grid-template-columns: 1fr 1fr;
  row-gap: 1.75rem;
}

.tt-contact-stat-item h4 {
  font-family: var(--tt-font-secondary);
  font-size: clamp(2.4rem, 3.4vw, 3.4rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  margin: 0 0 0.25rem;
  color: var(--tt-primary);
}

.tt-contact-stat-item p {
  color: var(--tt-muted);
  font-size: 0.92rem;
  line-height: 1.3;
  margin: 0;
}


@media (max-width: 991.98px) {
  .tt-contact-panel {
    border-radius: 20px;
    padding: 1.5rem;
  }
}

@media (max-width: 575.98px) {
  .tt-contact-panel {
    border-radius: 16px;
    padding: 0.75rem;
  }

  .tt-contact-stats-card {
    border-radius: 16px;
    padding: 1.5rem;
  }

  .tt-contact-title {
    font-size: clamp(1.5rem, 7.8vw, 1.92rem);
  }

  .tt-contact-subtitle {
    font-size: 0.98rem;
    line-height: 1.52;
  }

  .tt-contact-input {
    min-height: 50px;
  }

  .tt-contact-textarea {
    min-height: 140px;
  }

  .tt-contact-submit {
    min-height: 44px;
  }

  .tt-contact-stats-title {
    font-size: clamp(1.35rem, 6.4vw, 1.8rem);
  }
}

@media (max-width: 430px) {
  .tt-testimonials-section,
  .tt-awards-section,
  .tt-integrations-section,
  .tt-mobile-app-section,
  .tt-contact-section {
    padding-top: 2.6rem;
    padding-bottom: 2.75rem;
  }

  .tt-testimonials-heading,
  .tt-awards-heading,
  .tt-integrations-heading,
  .tt-mobile-app-title,
  .tt-contact-title {
    font-size: clamp(1.46rem, 6.8vw, 1.9rem);
    letter-spacing: -0.015em;
  }

  .tt-testimonial-quote,
  .tt-awards-text,
  .tt-integrations-copy,
  .tt-mobile-app-copy,
  .tt-contact-subtitle {
    font-size: 0.96rem;
    line-height: 1.56;
  }
}

@media (max-width: 375px) {
  .tt-testimonials-section,
  .tt-awards-section,
  .tt-integrations-section,
  .tt-mobile-app-section,
  .tt-contact-section {
    padding-top: 2.35rem;
    padding-bottom: 2.5rem;
  }

  .tt-testimonials-heading,
  .tt-awards-heading,
  .tt-integrations-heading,
  .tt-mobile-app-title,
  .tt-contact-title {
    font-size: clamp(1.34rem, 6.4vw, 1.72rem);
    line-height: 1.16;
  }

  .tt-contact-submit {
    min-height: 44px;
  }
}


/* --- 15. FOOTER (Final Fold) --- */

.main-footer {
  background: var(--tt-gradient-dark);
  color: #FFFFFF;
  position: relative;
  overflow: hidden;
}

.main-footer::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    none;
  pointer-events: none;
  z-index: 0;
}

.main-footer > .container {
  position: relative;
  z-index: 1;
}

.tt-footer-brand-wrap {
  max-width: 390px;
}

.tt-footer-logo {
  align-items: center;
  color: #FFFFFF;
  display: inline-flex;
  gap: 0.6rem;
  text-decoration: none;
}

.tt-footer-logo-img {
  height: 48px;
  width: auto;
  display: block;
}

.tt-footer-logo-text {
  color: #FFFFFF;
  font-family: var(--tt-font-secondary);
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1;
}

.tt-footer-logo-text sup {
  font-size: 0.5em;
  top: -0.7em;
}

.tt-footer-logo-sub {
  color: #E8EAEE;
  font-size: 0.88rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  line-height: 1;
  margin-top: 0.15rem;
  text-transform: uppercase;
}

.tt-footer-copy {
  color: #ECEEF2;
  font-size: var(--tt-fs-body);
  line-height: 1.45;
  margin: 1rem 0 1.2rem;
}

/* ---- App store badges -------------------------------------------------
   The two SVGs are the LIGHT-background official badges (black artwork -
   "white" in their filenames means "for white backgrounds", not white ink),
   so they need a light plate to sit on inside this dark footer. Do not drop
   the plate without swapping in the dark-background badge artwork, or they
   vanish into the footer.

   NO FIXED HEIGHT on the plate. It used to be `height: 44px` around a 28px
   image, which left uneven dead space and made the plate look bigger than
   the badge it held. Padding alone now defines the plate, so it is snug and
   the two are identical.

   Both SVGs share a `0 0 144 47` viewBox, so setting only `height` on the
   image gives both badges the same rendered width automatically - which is
   what makes the pair read as deliberate rather than mismatched. If either
   asset is ever replaced, check the new viewBox still matches. */
.tt-store-badge {
  align-items: center;
  background: #FFFFFF;
  border-radius: 10px;
  display: inline-flex;
  justify-content: center;
  padding: 7px 12px;
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
  transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

.tt-store-badge:hover,
.tt-store-badge:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 10px 20px -4px rgba(0, 0, 0, 0.4);
}

.tt-store-badge:focus-visible {
  outline: 2px solid var(--tt-primary);
  outline-offset: 3px;
}

.tt-store-badge img {
  display: block;
  height: 30px;
  width: auto;
}

.tt-footer-heading {
  color: #FFFFFF;
  font-size: var(--tt-fs-h4);
  font-weight: 700;
  margin-bottom: 0.8rem;
}

.tt-footer-links li + li {
  margin-top: 0.24rem;
}

.tt-footer-links a {
  color: #F1F3F6;
  font-size: var(--tt-fs-body);
  text-decoration: none;
  transition: var(--tt-transition-fast);
}

.tt-footer-links a:hover {
  color: #FFFFFF;
}

.tt-footer-contact-item {
  align-items: center;
  display: flex;
  gap: 0.6rem;
  margin-bottom: 0.42rem;
}

.tt-footer-contact-item a {
  color: #F2F4F8;
  font-size: var(--tt-fs-body);
  text-decoration: none;
}

.tt-footer-icon-circle {
  border-radius: 50%;
  flex: 0 0 28px;
  height: 28px;
  width: 28px;
}

.tt-footer-address-item {
  margin-top: 0.45rem;
}

.tt-footer-city-row {
  align-items: center;
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.1rem;
}

.tt-footer-city-row span {
  color: #FFFFFF;
  font-size: var(--tt-fs-body);
  font-weight: 700;
}

.tt-footer-address-item p {
  color: #ECEEF2;
  font-size: var(--tt-fs-body);
  line-height: 1.32;
  margin: 0;
}

.tt-footer-flag {
  border-radius: 2px;
  display: block;
  height: 15px;
  width: 23px;
}

.tt-footer-bottom {
  border-top: 2px solid rgba(255, 255, 255, 0.8);
}

.tt-footer-bottom p {
  color: #ECEEF3;
  font-size: 1.12rem;
}

/* The Privacy Policy link in the copyright line. Without this it renders in
   Bootstrap's default anchor blue: the `color` on the <p> above is inherited,
   and Bootstrap's own `a { color: ... }` rule beats inheritance every time.
   Nothing else in the footer had this problem because every other link there
   carries a class that sets a colour.

   Scoped to `p a`, NOT `.tt-footer-bottom a` - the social icon row is also a
   child of .tt-footer-bottom, and a blanket rule would underline the icons
   and override their white-on-orange treatment.

   Values match `.tt-lp-foot-legal a` in landing-page.css, which is the same
   element doing the same job in the campaign-page footer. Keep the two in
   step: a visitor who lands on an ad page and then browses the organic site
   should not see the legal line change character. */
.tt-footer-bottom p a {
  color: rgba(255, 255, 255, 0.75);
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: var(--tt-transition-fast);
}

.tt-footer-bottom p a:hover,
.tt-footer-bottom p a:focus-visible {
  color: var(--tt-primary);
}

.tt-footer-social a {
  align-items: center;
  background: var(--tt-gradient-primary);
  border-radius: 50%;
  color: #FFFFFF;
  display: inline-flex;
  font-size: 0.95rem;
  height: 26px;
  justify-content: center;
  text-decoration: none;
  transition: var(--tt-transition-fast);
  width: 26px;
}

.tt-footer-social a:hover {
  background: #FFFFFF;
  color: var(--tt-primary);
}

@media (max-width: 1199.98px) {
  .tt-footer-logo-text {
    font-size: clamp(1.35rem, 2.2vw, 1.65rem);
  }

  .tt-footer-copy {
    font-size: var(--tt-fs-body);
  }
}

@media (max-width: 991.98px) {
  .tt-footer-col-addresses {
    margin-top: 0;
  }

  .tt-footer-bottom {
    border-top-width: 1px;
  }
}

@media (max-width: 575.98px) {
  .tt-footer-logo-sub {
    font-size: 0.72rem;
  }

  .tt-footer-heading {
    margin-bottom: 0.45rem;
  }

  /* Plate stays padding-driven here too - only the artwork scales. */
  .tt-store-badge {
    padding: 6px 10px;
    border-radius: 9px;
  }

  .tt-store-badge img {
    height: 26px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tt-store-badge { transition: none; }
  .tt-store-badge:hover,
  .tt-store-badge:focus-visible { transform: none; }
}

/* --- 16. POPPINS TYPOGRAPHY FOR FOOTER ---
   (Header items now declare font-family: var(--tt-font-poppins) directly on
   their own rules - .tt-top-bar, .tt-navbar .nav-link, .btn-book-call,
   .btn-login-header - so the !important override that used to live here for
   them was dead weight and has been removed.) */

/* Footer Entire Wrapper & All Typography Inside It */
.main-footer,
.main-footer h1,
.main-footer h2,
.main-footer h3,
.main-footer h4,
.main-footer h5,
.main-footer h6,
.main-footer p,
.main-footer a,
.main-footer li,
.main-footer span,
.main-footer div,
.tt-footer-logo-text,
.tt-footer-logo-sub,
.tt-footer-heading,
.tt-footer-links a,
.tt-footer-contact-item,
.tt-footer-address-item p,
.tt-footer-bottom p {
  font-family: var(--tt-font-poppins) !important;
}

/* 
==================================================================
17. ULTRA-MODERN AESTHETICS & INTERACTIVE SHINE EFFECTS
===================================================================*/

/* --- A. HOVER SHINE SWEEP ON ACTION BUTTONS ---

   A GLEAM WITHOUT A GRADIENT. This was a translucent linear-gradient bar
   sliding across the button; the no-gradient rule killed the fill and left
   the sweep invisible. It is rebuilt here as a FLAT translucent bar softened
   with `filter: blur()`.

   The blur is what does the work: a flat bar with hard edges reads as a
   sliding rectangle, but blurred it has no visible boundary and looks like
   light passing over the surface - the same effect as the old gradient,
   built from a solid colour. Do not swap the blur for a gradient.

   `overflow: hidden` on the button clips the bar (and its blur halo) to the
   button's own box, which is what keeps it looking like a reflection rather
   than a stray element. .btn-secondary-custom is in this list: it has a
   sweep pseudo-element but carried NO position/overflow of its own, so
   without this its bar would escape the button entirely. */
.btn-primary-custom,
.btn-book-call,
.btn-login-header,
.btn-secondary-custom,
.tt-contact-submit,
.tt-btn-dark,
.tt-btn-consultancy {
  position: relative !important;
  overflow: hidden !important;
}

/* Solid / dark buttons - a white gleam. */
.btn-primary-custom::after,
.btn-book-call::after,
.btn-login-header::after,
.tt-contact-submit::after,
.tt-btn-dark::after {
  content: '';
  position: absolute;
  top: 0;
  left: -50%;
  width: 30%;
  height: 100%;
  background: rgba(255, 255, 255, 0.3);
  filter: blur(6px);
  transform: skewX(-25deg);
  pointer-events: none;
  z-index: 1;
}

/* Light / outlined buttons - white would be invisible on a white face, so the
   gleam is a brand-orange tint instead. */
.btn-secondary-custom::after,
.tt-btn-consultancy::after {
  content: '';
  position: absolute;
  top: 0;
  left: -50%;
  width: 30%;
  height: 100%;
  background: rgba(var(--tt-primary-rgb), 0.22);
  filter: blur(6px);
  transform: skewX(-25deg);
  pointer-events: none;
  z-index: 1;
}

/* The transition lives on :hover only, deliberately: the gleam eases across on
   hover-in and resets instantly on hover-out, instead of sliding backwards. */
.btn-primary-custom:hover::after,
.btn-book-call:hover::after,
.btn-login-header:hover::after,
.tt-contact-submit:hover::after,
.tt-btn-dark:hover::after,
.btn-secondary-custom:hover::after,
.tt-btn-consultancy:hover::after {
  left: 150%;
  transition: left 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}

/* A sweep is decoration, not information - suppress it entirely rather than
   just shortening it. */
@media (prefers-reduced-motion: reduce) {
  .btn-primary-custom::after,
  .btn-book-call::after,
  .btn-login-header::after,
  .btn-secondary-custom::after,
  .tt-contact-submit::after,
  .tt-btn-dark::after,
  .tt-btn-consultancy::after {
    display: none;
  }
}

/* --- B. GLASSMORPHIC BACKPLATE & HOVER EFFECTS FOR CARDS --- */

/* Module Showcase Card - Interactive Left Stripe Indicator */
.tt-module-card {
  position: relative !important;
  overflow: hidden !important;
}

.tt-module-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 4px;
  height: 100%;
  background: var(--tt-gradient-vivid);
  opacity: 0;
  transition: var(--tt-transition-fast);
  z-index: 2;
}

.tt-module-card:hover::before {
  opacity: 1;
}

.tt-module-card:hover {
  border-color: rgba(var(--tt-primary-rgb), 0.35) !important;
  box-shadow: 0 10px 25px -4px rgba(243, 112, 33, 0.08), var(--tt-shadow-md) !important;
}

/* Testimonial Accent Background-Glow */
.tt-testimonial-card {
  position: relative;
  overflow: hidden;
  backdrop-filter: var(--tt-glass-blur);
  -webkit-backdrop-filter: var(--tt-glass-blur);
  background: var(--tt-glass-bg-soft) !important;
  border: 1px solid var(--tt-glass-border) !important;
}

.tt-testimonial-card:hover {
  border-color: rgba(var(--tt-primary-rgb), 0.22) !important;
  box-shadow: 0 12px 30px -4px rgba(0, 0, 0, 0.08), 0 0 20px rgba(var(--tt-accent-deep-rgb), 0.08) !important;
}

/* Underline link sliding-effect for "Learn More" in modules */
.tt-module-link {
  position: relative;
  text-decoration: none !important;
}

.tt-module-link::after {
  content: '';
  position: absolute;
  width: 100%;
  transform: scaleX(0.7);
  height: 2px;
  bottom: -2px;
  left: 0;
  background-color: var(--tt-primary);
  transform-origin: bottom left;
  transition: transform 0.25s ease-out;
}

.tt-module-link:hover::after {
  transform: scaleX(1);
}

/* --- C. CUSTOM INTEGRATIONS NETWORKS ACTIVE PULSE --- */
.tt-integration-node {
  transition: var(--tt-transition-spring) !important;
}

.tt-integration-node:hover {
  transform: translate(-50%, -50%) scale(1.16) rotate(1.5deg) !important;
  border-color: rgba(var(--tt-primary-rgb), 0.65) !important;
  box-shadow: 0 12px 28px -4px rgba(243, 112, 33, 0.22) !important;
}

/* --- D. CLIENT TICKER GLOW & POP INTERACTION --- */
.tt-ticker-logo {
  transition: var(--tt-transition-standard) !important;
}

.tt-ticker-logo:hover {
  transform: scale(1.1) !important;
  filter: drop-shadow(0 6px 16px rgba(243, 112, 33, 0.22)) !important;
}


/* ============================================================
   FAQ SECTION (accordion — brand orange, glassmorphism)
   ============================================================ */
.tt-faq-section {
  position: relative;
  overflow: hidden;
  background: rgba(243, 244, 246, 0.9);
}

.tt-faq-glow {
  position: absolute;
  width: 460px;
  height: 460px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 0;
}

.tt-faq-glow-a {
  top: -120px;
  left: -100px;
  background: none;
}

.tt-faq-glow-b {
  bottom: -140px;
  right: -110px;
  background: none;
}

.tt-faq-head {
  max-width: 720px;
}

.tt-faq-title {
  font-family: var(--tt-font-secondary);
  font-size: clamp(1.75rem, 3.4vw, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--tt-dark, #1a1a1a);
}

.tt-faq-subtitle {
  color: var(--tt-muted, #6b7280);
  font-size: 1.02rem;
}

/* Single column, at every width.

   This was a 2-column grid, which breaks the moment a question is opened:
   grid rows are shared, so an expanded <details> stretches its row and its
   partner leaves a tall empty gap beside it, with the rows below shunted
   down unevenly. `align-items: start` stops the sibling stretching but
   cannot decouple the row height - that is inherent to a row-based grid
   holding items that change height independently.

   A single column is also how a FAQ is actually read: scan the questions
   vertically, open one, read it in place. Do not put this back into columns
   without solving the shared-row-height problem first. */
.tt-faq-list {
  max-width: 820px;
  display: grid;
  grid-template-columns: 1fr;
  align-items: start;
  gap: 0.85rem;
}

.tt-faq-item {
  background: var(--tt-glass-bg-soft);
  backdrop-filter: var(--tt-glass-blur);
  -webkit-backdrop-filter: var(--tt-glass-blur);
  border: 1px solid var(--tt-glass-border);
  border-radius: 16px;
  box-shadow: var(--tt-glass-shadow);
  overflow: hidden;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

.tt-faq-item[open],
.tt-faq-item:hover {
  border-color: rgba(var(--tt-primary-rgb), 0.35);
  box-shadow: 0 14px 30px -6px rgba(var(--tt-primary-rgb), 0.2), var(--tt-shadow-md);
}

.tt-faq-question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.15rem 1.4rem;
  cursor: pointer;
  list-style: none;
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--tt-dark, #1a1a1a);
  user-select: none;
}

.tt-faq-question::-webkit-details-marker {
  display: none;
}

.tt-faq-chevron {
  flex-shrink: 0;
  color: var(--tt-primary);
  font-size: 0.9rem;
  transition: transform 0.3s ease;
}

.tt-faq-item[open] .tt-faq-chevron {
  transform: rotate(180deg);
}

.tt-faq-answer {
  padding: 0 1.4rem 1.25rem;
  color: var(--tt-muted, #4b5563);
  line-height: 1.6;
}

.tt-faq-answer p {
  margin: 0;
}

@media (prefers-reduced-motion: reduce) {
  .tt-faq-chevron,
  .tt-faq-item {
    transition: none;
  }
}

@media (max-width: 575px) {
  .tt-faq-question {
    padding: 1rem 1.1rem;
    font-size: 0.98rem;
  }
  .tt-faq-answer {
    padding: 0 1.1rem 1.1rem;
    font-size: 0.95rem;
  }
}






/* ==================================================================
   FOOTER REFINEMENT
   The footer had drifted: an 8-item link list, and contact details
   (phone/email) sharing a column with two office addresses while the
   other two offices sat in a fourth column. Four columns now each do
   one job - brand / quick links / how to reach us / where we are.
   ================================================================== */

/* All four offices together, one per row, so each can carry its complete
   address on a full-width line instead of being squeezed 2-up. This makes the
   footer taller; that is the trade being bought. */
.tt-footer-offices {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.85rem;
}
.tt-footer-office p {
  color: rgba(255, 255, 255, 0.72);
  font-size: 0.85rem;
  line-height: 1.5;
  margin: 0;
}
.tt-footer-office .tt-footer-city-row span { font-size: 0.95rem; }

/* The address type was running at full body size, which is what made the
   column feel like a wall of text. */
.tt-footer-address-item p { font-size: 0.85rem; line-height: 1.5; }

/* Bottom bar was set at 1.12rem - larger than the links above it. */
.tt-footer-bottom p { font-size: 0.9rem; }
.tt-footer-bottom { border-top-width: 1px; border-top-color: rgba(255, 255, 255, 0.16); }

.tt-footer-links li + li { margin-top: 0.15rem; }

/* ---------- Tablet ---------- */
@media (max-width: 991.98px) {
  .tt-footer-offices { gap: 0.8rem; }
}

/* ---------- Phones ----------
   Below 768px the columns stack. The paired columns (quick links + contact)
   stay side by side rather than becoming two near-empty full-width blocks;
   the offices are single-column at every width now. */
@media (max-width: 767.98px) {
  .tt-footer-brand-wrap { max-width: 34rem; }
  .tt-footer-heading { font-size: 1rem; }
}

@media (max-width: 575.98px) {
  .main-footer .row { --bs-gutter-y: 1.75rem; }
  .tt-footer-copy { margin-bottom: 1rem; }
  .tt-footer-bottom {
    text-align: center;
    gap: 0.9rem;
  }
  .tt-footer-social a { width: 30px; height: 30px; }  /* 26px is under the touch target */
}

/* ---------- Footer fixes: overflow + office density ----------
   1. "inquiry@efrotech.com" is a long unbroken token. In a half-width column
      on a phone it overflowed its box and clipped. The columns now go full
      width below 576px (markup), and the link is allowed to break as a last
      resort so no future address can clip either.
   2. The offices grid used to wrap every two words at desktop, because a
      col-lg-4 split 2-up leaves ~200px per office. It is one office per row
      now, at the full width of the column, so each complete address fits. */
.tt-footer-contact-item {
  align-items: flex-start;
  min-width: 0;
}
.tt-footer-contact-item a {
  min-width: 0;
  overflow-wrap: anywhere;
  line-height: 1.4;
}

.tt-footer-office p {
  font-size: 0.85rem;
  line-height: 1.5;
  text-wrap: pretty;      /* avoids a single orphan word on the last line */
}
.tt-footer-office .tt-footer-city-row {
  gap: 0.4rem;
  margin-bottom: 0.15rem;
}

/* "Our Offices" needs more air under it than the other two headings do. Each
   office is a two-line block (city row + wrapped address), so at the shared
   0.8rem the heading read as the first entry in the list rather than a label
   for it. Added here rather than on .tt-footer-heading so the three headings
   stay baseline-aligned across the columns. */
.tt-footer-offices {
  margin-top: 0.4rem;
}

/* ==================================================================
   FLOATING WHATSAPP CONTACT
   Present on every page, bottom-left (matching the placement already
   used on efrotech.com). Static markup rather than JS-injected so it
   works before/without scripts.

   z-index 1010 is deliberate: BELOW the fixed header (1030), the
   mobile drawer backdrop (1020), the album viewer (2200) and the
   webinar modal (2500). A floating button that sits on top of an open
   dialog is a bug, not a feature.
   ================================================================== */
.tt-wa-float {
  position: fixed;
  left: 1.25rem;
  bottom: 1.25rem;
  z-index: 1010;
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  height: 3.5rem;
  padding: 0 1.15rem 0 0;
  border-radius: 999px;
  background: #25D366;                 /* WhatsApp brand green - the one
                                          non-brand colour on the site, because
                                          the mark is only recognisable in it */
  color: #fff;
  font-family: var(--tt-font-primary);
  font-size: 0.92rem;
  font-weight: 600;
  text-decoration: none;
  box-shadow: 0 10px 28px -8px rgba(37, 211, 102, 0.55),
              0 2px 6px rgba(16, 24, 40, 0.18);
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
              box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
.tt-wa-float:hover,
.tt-wa-float:focus-visible {
  color: #fff;
  transform: translateY(-2px);
  box-shadow: 0 16px 34px -8px rgba(37, 211, 102, 0.65),
              0 3px 8px rgba(16, 24, 40, 0.22);
}
.tt-wa-float:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}

.tt-wa-float i {
  flex: 0 0 auto;
  width: 3.5rem;
  height: 3.5rem;
  display: grid;
  place-items: center;
  font-size: 1.75rem;
}

/* Phones: icon only. A labelled pill eats a third of a 375px screen and
   overlaps content the user is trying to read. */
@media (max-width: 767.98px) {
  .tt-wa-float {
    left: 1rem;
    bottom: 1rem;
    height: 3.25rem;
    padding: 0;
  }
  .tt-wa-float i { width: 3.25rem; height: 3.25rem; font-size: 1.6rem; }
  .tt-wa-label { display: none; }
}

/* Hidden while the mobile nav drawer is open - it would sit over the dimmed
   backdrop and compete with the menu. */
body.tt-nav-open .tt-wa-float { opacity: 0; pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
  .tt-wa-float { transition: none; }
  .tt-wa-float:hover { transform: none; }
}

/* ==================================================================
   LIVE CHAT WIDGET (Tawk.to) - stacking clamp

   The launcher lives bottom-RIGHT (set in the Tawk dashboard, not
   here) so it never collides with the WhatsApp pill bottom-left.

   Tawk injects its own elements with an INLINE z-index of 999999+,
   which would float the chat bubble over the webinar modal (2500),
   the gallery album viewer (2200) and the drawer backdrop (1020).
   Same rule as the WhatsApp float above: a floating button on top of
   an open dialog is a bug, not a feature.

   `!important` is REQUIRED and is a deliberate exception to the house
   style - an inline style set by third-party JS cannot be overridden
   any other way. Do not "tidy" it away.

   SCOPED TO DIRECT CHILDREN OF <body> ON PURPOSE. Tawk appends its
   containers there; everything it builds INSIDE them is its own
   business. An earlier version used a blanket [class^="tawk-"], which
   forced a stacking context onto every nested element of the widget
   and interfered with its own show/hide layering. Do not widen these
   selectors - clamp the container, never its internals.

   Nothing here touches `bottom`/`right` either: the launcher's corner
   is a Tawk dashboard setting, and fighting its inline positioning
   from CSS just breaks its open/close animation. The two floats sit
   in opposite corners, so there is no collision to correct.

   Belt and braces - assets/js/live-chat.js ALSO calls hideWidget()
   while a dialog is open, so the widget stays out of the way even if
   these hooks stop matching after a Tawk release.
   ================================================================== */
body > [id^="tawk"],
body > .tawk-min-container,
body > iframe[title*="chat" i],
body > div[class*="tawk"] {
  z-index: 1010 !important;
}

/* ===================================================================
   ANNOUNCEMENT BAR  (index.html only)
   Full-bleed band flush under the fixed header: edge-to-edge fill, one
   centred row, lockup + message.

   AS OF 2026-09-14 IT ANNOUNCES ITCN ASIA AND LINKS NOWHERE. It carries
   no button and the card is a <div>, not an <a> - both declined by the
   owner. scripts\set-announcement-bar-20260914.ps1 is the authoring
   source for the copy; edit it there, not in index.html.

   THE CLASS PREFIX STILL READS tt-app-bar, and that is now drift. The
   band was the app login route from 2026-08-28 (before that, a webinar
   registration strip - hence an even earlier tt-event-bar prefix). The
   prefix has been left alone because renaming it is a mechanical pass
   over this whole block plus the markup, worth doing on its own rather
   than folded into a copy change. Read "app-bar" as "the band under the
   header" until then.

   NOTHING LINKS TO app.etimetrax.com ANYWHERE ON THE SITE any more -
   this band and the navbar Login button were the only two routes and
   both were removed on 2026-09-14 by decision, not by accident. Do not
   "restore" either as a fix for the missing link.

   Part of the FOLD, not the sticky header - it scrolls away. It sits
   directly in <main>, above the first <section>, because 49 of the
   site's first sections are `d-flex`; as a child of one it became a
   flex sibling of the container and sat beside the hero.

   MARGIN, NOT PADDING, for the header clearance: the header is
   semi-transparent glass, so padding-top would run this band's fill up
   underneath it and tint the navbar.

   Two things carry the attention here, since the fill stays light: a
   soft orange tint and a SOLID button rather than an underlined link -
   underlined text reads as body copy at this scale. There is no longer
   a pulsing dot: that signalled a live/upcoming event, and on a
   standing login route it manufactures urgency that is not there.
   Flat fills only, orange is the sole accent (rule 3).
   =================================================================== */
#main-content > .tt-app-bar {
  /* --tt-header-exact-h is the header's real rendered height, published by
     initHeaderHeightVar() in script.js. NOT --tt-header-h: that is
     benefits-showcase.js's sticky freeze offset (header + 12px) and is scoped
     to .tt-benefits-showcase, so using it left a 12px gap under the header.
     The 70px fallback only applies for the moment before JS runs. */
  margin-top: var(--tt-header-exact-h, 70px);
  background-color: rgba(var(--tt-primary-rgb), 0.07);
  border-bottom: 1px solid rgba(var(--tt-primary-rgb), 0.18);
  font-family: var(--tt-font-poppins);
}

/* The section below no longer clears the header - this band does. */
#main-content > .tt-app-bar + section {
  padding-top: var(--tt-spacer-lg);
}

.tt-app-bar-card {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 0.7rem 0;
  /* 1rem, raised from 0.9rem on 2026-09-21. This is the band's base size and
     the message text inherits it, so the whole strip grows from this one
     value - do not set a font-size on .tt-app-bar-text as well. */
  font-size: 1rem;
  color: var(--tt-dark);
  text-decoration: none;
  transition: var(--tt-transition-standard);
}

.tt-app-bar-card:hover { color: var(--tt-dark); }

.tt-app-bar-label {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
  font-family: var(--tt-font-secondary);
  /* The "ITCN Asia" pill. 0.92rem, raised from 0.72rem on 2026-09-21 - at the
     old size the event name was the smallest text in the band despite being
     the thing the band is about. It stays below the CTA pill's weight because
     it is a label, not the action. */
  font-size: 0.92rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tt-primary);
}

/* Sign-in glyph in the lockup, replacing the old pulsing "live" dot.
   Width is reserved the same way the CTA icon reserves it below -
   FontAwesome loads non-blocking, so an unsized <i> is a 0px box until it
   lands and the lockup would visibly jump. */
.tt-app-bar-icon {
  display: inline-block;
  width: 1em;
  flex: none;
  text-align: center;
  /* Tracks the label above (0.8 -> 1rem with 0.72 -> 0.92) so the glyph does
     not shrink away beside the larger event name. The `width: 1em` above is
     relative to THIS size, so the reserved box grows with it and the lockup
     still cannot jump when FontAwesome lands. */
  font-size: 1rem;
}

.tt-app-bar-text {
  font-weight: 600;
  color: var(--tt-dark);
}

/* THE CTA PILL. A NEW CLASS, NOT .tt-app-bar-cta.

   That older class is documented as inert just below, and reusing it would
   have been an invisible bug rather than a shortcut: it is `display: none`
   at >= 992px by design, because back when the band pointed at the app it
   deferred to the navbar's real Login button on desktop and only appeared
   as a pill on mobile. This CTA has no such twin - it is the only way to
   the destination and must be visible at every width.

   It is a <span> inside the card, not a <button> or a nested <a>. The whole
   band is the link (see the note on the markup in
   scripts\set-announcement-bar-20260914.ps1), so this is the visible
   affordance for that one link - one element, one tab stop, no control
   nested inside another. */
.tt-app-bar-btn {
  align-items: center;
  background: var(--tt-primary);
  border-radius: 999px;
  color: #FFFFFF;
  display: inline-flex;
  flex: none;
  font-size: 0.82rem;
  font-weight: 700;
  gap: 0.4rem;
  line-height: 1;
  padding: 0.5rem 0.95rem;
  transition: background-color 0.2s var(--tt-ease, ease);
  white-space: nowrap;
}

/* Reserved width for the same reason .tt-app-bar-icon reserves it:
   FontAwesome loads non-blocking, so an unsized <i> is a 0px box until it
   arrives and the pill would visibly grow when it did. */
.tt-app-bar-btn i {
  display: inline-block;
  font-size: 0.75rem;
  text-align: center;
  width: 1em;
}

.tt-app-bar-card:hover .tt-app-bar-btn {
  background: var(--tt-primary-hover);
}

/* Pointer-gated: a touch screen keeps :hover applied after a tap until
   something else is tapped, so an ungated hover state sticks. Same rule the
   tickers and the mega-menu rows follow. */
@media (hover: hover) and (pointer: fine) {
  .tt-app-bar-card:hover .tt-app-bar-btn i {
    transform: translateX(2px);
  }
  .tt-app-bar-btn i { transition: transform 0.2s var(--tt-ease, ease); }
}

@media (prefers-reduced-motion: reduce) {
  .tt-app-bar-btn,
  .tt-app-bar-btn i { transition: none; }
  .tt-app-bar-card:hover .tt-app-bar-btn i { transform: none; }
}

/* Below 576px the label, message and pill stack; the message already has
   flex-basis:100% in the block further down, so the pill just needs to not
   be squeezed. */
@media (max-width: 575.98px) {
  .tt-app-bar-btn {
    font-size: 0.78rem;
    padding: 0.45rem 0.85rem;
  }
}

/* ===================================================================
   INERT AS OF 2026-09-14 - EVERYTHING FROM HERE TO THE END OF THE APP BAR
   BLOCK MATCHES NO MARKUP.

   The navbar Login button was removed site-wide and the app bar was stripped
   back to label + message, so .tt-app-bar-spacer, .tt-app-bar-trail,
   .tt-app-bar-trail-tip and .tt-app-bar-cta no longer exist in any page.
   See scripts\remove-header-login-20260914.ps1, which is that change and is
   also how it would be reversed.

   Kept on disk rather than deleted - same pattern as the archived webinar
   modal. The rules below still describe the arrangement accurately IF the
   three spans are ever restored; read them as a restore note, not as a
   description of the live page. The band centres itself without them because
   .tt-app-bar-card is already display:flex; justify-content:center.
   =================================================================== */

/* The right-hand affordance. TWO MODES, because the thing it refers to moves:

   >= 992px  the navbar is expanded and .btn-login-header is visible at the
             container's right edge. The in-band CTA is hidden entirely and a
             DASHED TRAIL runs from the end of the message across to that right
             edge, where it curves up and ends in an arrowhead aimed at the real
             button. Both the navbar and this band use Bootstrap's .container,
             so the two right edges line up at every width - that alignment is
             the whole trick, and it breaks if either one is ever moved out of
             .container.

             The trail replaced a small pinned "Log In" label with an up arrow.
             That version pointed at itself: a second control sitting under the
             first, so the arrow's target was ambiguous. A line that physically
             spans the gap says "the button is up there" without adding a rival
             thing to click.

   <  992px  navbar-expand-lg has collapsed the Login button into the burger
             drawer, where it is NOT visible. An up arrow would point at
             nothing, so below this breakpoint the affordance stays a solid
             pill with a RIGHT arrow and acts as its own button.

   The whole band remains an <a> to app.etimetrax.com in BOTH modes. Pointing
   at a control the visitor then has to go and find is an extra step; making
   the signpost itself clickable means the arrow is a hint, never a trap.

   Below 992px this is a real button, and an underlined link at 0.9rem would
   read as running text rather than as an action - hence the solid fill. */
.tt-app-bar-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  flex: none;
  margin-left: 0.35rem;
  padding: 0.45rem 1.1rem;
  border-radius: 999px;
  background-color: var(--tt-primary);
  color: #FFFFFF;
  font-size: 0.82rem;
  font-weight: 700;
  white-space: nowrap;
  transition: var(--tt-transition-standard);
}

/* The width is reserved rather than left to the glyph. FontAwesome loads
   non-blocking (see index.html), so before it lands this <i> is an empty inline
   box 0px wide and the pill would visibly widen when the icon appeared. 1em of
   its own font-size matches what FontAwesome sets it to, so nothing moves.
   inline-block is also what makes the hover nudge below actually work — a
   transform does nothing to a plain inline element. */
.tt-app-bar-cta i {
  display: inline-block;
  width: 1em;
  text-align: center;
  font-size: 0.72rem;
  transition: var(--tt-transition-standard);
}

.tt-app-bar-card:hover .tt-app-bar-cta {
  background-color: var(--tt-primary-hover);
  box-shadow: 0 6px 16px rgba(var(--tt-primary-rgb), 0.32);
}

.tt-app-bar-card:hover .tt-app-bar-cta i { transform: translateX(3px); }

/* THE POINTER. One short solid arc, a fixed 88x72 inline SVG, aimed at the
   Login button in the header above. Hidden by default and revealed only from
   992px, because below that the button it points at is inside the burger
   drawer - an arrow aimed at nothing is worse than no arrow.

   Decorative and aria-hidden, so nothing is announced twice; pointer-events are
   off so the band's own <a> keeps the whole width clickable.

   FIXED SIZE, NOT STRETCHED TO SPAN THE GAP. An earlier version ran a dashed
   line from the message across to a curve at the far end, so the pointer always
   touched the last word. It was replaced by this on 2026-09-01: solid and
   short reads as a drawn mark, where the long dashed connector read as a
   diagram. The fixed box is also what keeps the curve and the splayed head from
   distorting - stretching one SVG the full span needs non-scaling-stroke, a
   nested viewBox for the head, and a contrived final control point, which is
   three fragile things to hold a shape a fixed box holds for free. */
.tt-app-bar-trail { display: none; }

/* Below ~768px one row cannot hold lockup + message + button. The message
   drops to its own line rather than being squeezed to shreds. */
@media (max-width: 767.98px) {
  .tt-app-bar-card {
    flex-wrap: wrap;
    gap: 0.5rem 0.7rem;
    padding: 0.7rem 0;
    text-align: center;
    font-size: 0.85rem;
  }
  .tt-app-bar-text { flex-basis: 100%; order: 3; }
  .tt-app-bar-cta  { order: 4; margin-left: 0; }
}

/* POINTER MODE - see the two-mode note above. Back at 992px, which is exactly
   where navbar-expand-lg reveals the Login button being pointed at. It had been
   raised to 1200px while the pointer was a 204px curve on a stretched line,
   which crowded the message at the narrow end; at 124px it fits comfortably in
   the free space 992px leaves. */
@media (min-width: 992px) {
  .tt-app-bar-card { position: relative; }

  /* HOW THE LOCKUP STAYS CENTRED WHILE THE TRAIL REACHES THE RIGHT EDGE.
     The trail must absorb the free space on the right, or it cannot span a gap
     whose width changes with the viewport. On its own that shoves the lockup
     against the left edge. This invisible twin takes the same share on the
     left, so the free space splits equally and the lockup sits optically
     centred in the band - with no magic numbers and nothing absolutely
     positioned. Both must keep `flex: 1 1 0`; give either one `auto` as its
     basis and the balance breaks. */
  .tt-app-bar-spacer {
    display: block;
    flex: 1 1 0;
    min-width: 0;
  }

  /* The trail replaces it in this mode. Two controls one above the other made
     the arrow ambiguous about which one it meant. */
  .tt-app-bar-cta { display: none; }

  /* Holds no visible content of its own - it exists to claim the free space on
     the right (balancing the spacer, so the lockup stays centred) and to be the
     positioning context for the bow pinned to its right edge. */
  .tt-app-bar-trail {
    position: relative;
    display: block;
    flex: 1 1 0;
    min-width: 8rem;            /* the bow is 124px; never squeeze it */
    color: var(--tt-primary);
    pointer-events: none;
  }

  /* TAKEN OUT OF FLOW ON PURPOSE. As a flex item the 48px box would set the
     band's height - the band's own content is only ~20px - and leave a dead
     strip under the text. Absolute contributes nothing to layout.

     IT MUST NOT OVERFLOW UPWARD. The header is fixed and opaque, so anything
     rising past the band's top edge is painted over rather than clipped - it
     simply vanishes mid-stroke. The band is ~42px, so the arc has ~21px of
     headroom from the centre line and the SVG's drawing is centred in its box
     to stay inside it. Raising the arc means raising the band's padding first.

     top/margin-top rather than translateY(-50%), so the hover nudge below can
     own `transform` outright instead of having to restate the centring in it. */
  .tt-app-bar-trail-tip {
    position: absolute;
    /* PULLED LEFT TO SIT UNDER THE MIDDLE OF THE LOGIN BUTTON, rather than
       under its right corner. The arrowhead is drawn at x=80 of the 88-wide
       box, so the tip lands 8px inside this offset. .btn-login-header is
       ~100px wide (0.95rem/700 "Login" plus 1.75rem padding each side) and its
       right edge is the container's content edge, so its centre is ~50px in -
       which 40px here puts the tip on. This is the one knob: raise it to move
       the arrow further left, lower it to move right (0 was the old
       right-corner position, and -12px is the furthest right that stays inside
       the container's 12px gutter without risking a horizontal scrollbar). */
    right: 40px;
    top: 50%;
    margin-top: -24px;          /* half the SVG's 48px height */
    display: block;
    /* Set on the <svg>, not per-path, so the arc and its head can never drift
       to different strengths and show the join. */
    opacity: 0.72;
    transition: var(--tt-transition-standard);
  }

  .tt-app-bar-card:hover .tt-app-bar-trail-tip { transform: translateY(-3px); }
}

/* AFTER the min-width block on purpose: equal specificity, so source order is
   what lets this win. Placed before it, the trail tip's translateY would beat
   it and reduced-motion would be silently ignored. */
@media (prefers-reduced-motion: reduce) {
  .tt-app-bar-card:hover .tt-app-bar-cta i,
  .tt-app-bar-card:hover .tt-app-bar-trail-tip { transform: none; }
}
/* ===================================================================
   FOOTER CONTACT - keep phone and email on ONE line each
   The contact column is col-lg-3 (~250px inner at lg, ~216px at exactly
   992px). "inquiry@efrotech.com" fits that at full body size, so the old
   0.8rem shrink is gone - it only existed because the column used to be
   col-lg-2 (~196px) and the browser broke the address mid-word into
   "inquiry@efrotec / h.com". nowrap guarantees it can never break again:
   if the string ever grows it will visibly overflow rather than silently
   split an address in half. Phone sits on its own row below it.
   =================================================================== */
.tt-footer-contact-item a {
  white-space: nowrap;
}

.tt-footer-contact-item {
  gap: 0.5rem;
  margin-bottom: 0.55rem;
}

.tt-footer-contact-item .tt-footer-icon-circle {
  flex: 0 0 24px;
  width: 24px;
  height: 24px;
}
/* ===================================================================
   STATS BAND - "Unleashing the Power of Data with TimeTrax"
   Shared by index.html and the 37 Solutions module pages. Moved here from
   partner-page.css, which only become-a-partner.html loads - the markup
   would have rendered unstyled everywhere else. Renamed tt-pt-* ->
   tt-stats-* at the same time: a `pt` (partner) prefix on 38 non-partner
   pages was actively misleading.

   The same four figures also sit inside the contact card on
   contact-sales.html and become-a-partner.html. Keep them in step.
   Flat fills, orange for the numerals only (rule 3).
   =================================================================== */
.tt-stats-band {
  position: relative;
  padding: var(--tt-section-space-tight) 0;
  background: rgba(243, 244, 246, 0.9);
}

.tt-stats-band-title {
  font-family: var(--tt-font-poppins, var(--tt-font-primary));
  font-weight: 500;
  font-size: var(--tt-fs-h2);
  text-align: center;
  color: var(--tt-dark);
  margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
}

.tt-stats-band-title strong { font-weight: 800; }

.tt-stats-band-title .tt-stats-accent { color: var(--tt-primary); }

/* 1px gap + a background on the grid draws the hairline dividers between
   cells without a border on each one doubling up. */
.tt-stats-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: rgba(var(--tt-dark-rgb), 0.1);
  border-radius: 20px;
  overflow: hidden;
  border: 1px solid rgba(var(--tt-dark-rgb), 0.08);
  box-shadow: var(--tt-shadow-md);
}

.tt-stats-item {
  background: var(--tt-glass-bg-soft);
  backdrop-filter: var(--tt-glass-blur);
  -webkit-backdrop-filter: var(--tt-glass-blur);
  padding: clamp(1.25rem, 2.4vw, 2rem) 1rem;
  text-align: center;
}

.tt-stats-num {
  display: block;
  font-family: var(--tt-font-poppins, var(--tt-font-primary));
  font-weight: 800;
  font-size: clamp(1.9rem, 3.4vw, 3rem);
  line-height: 1.05;
  color: var(--tt-primary);
}

.tt-stats-label {
  display: block;
  margin-top: 0.35rem;
  font-size: var(--tt-fs-body-sm);
  font-weight: 500;
  color: var(--tt-muted);
}

@media (max-width: 767.98px) {
  .tt-stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}