/**
 * Hostaan.fi Custom Styles
 * Sandbox-inspired design (Demo 27/36 aesthetic)
 * Font: Manrope
 * Colors: Soft charcoal, warm gray, vibrant blue
 */

/* ===== CSS Variables ===== */
:root {
  /* Hostaan Brand Color Palette (Warm Blue) */
  /* Colors extracted from robot SVGs and brand guidelines */
  --primary: #004275;         /* Hostaan Deep Blue (from current website) */
  --primary-hover: #003560;
  --blue-light: #27AAE1;      /* Robot Light Blue (from SVG) */
  --blue-dark: #047BC1;       /* Robot Dark Blue (from SVG) */
  --accent: #2E9900;          /* Hostaan Green (can be changed) */
  --soft-blue: #e8f4fc;       /* Light blue tint for backgrounds */
  
  /* Text Colors */
  --text-dark: #004275;       /* Headings - matches primary */
  --text-body: #343f52;       /* Body text - darker for readability */
  --text-light: #6c7a8a;      /* Muted text - slightly warmer */
  
  /* Backgrounds */
  --bg-white: #ffffff;
  --bg-soft: #fefefe;
  --bg-pale: #f6f8fa;         /* Slightly cooler */
  --bg-blue-light: #e8f4fc;   /* Light blue background */
  
  /* Accent Colors */
  --orange-dark: #F7941E;     /* Dark orange from robot_maintenance.svg */
  
  /* Gradients */
  --gradient-hero: linear-gradient(135deg, #e8f4fc 0%, #ffffff 100%);  /* Light blue to white */
  --gradient-blue: linear-gradient(135deg, #047BC1 0%, #27AAE1 100%);  /* Robot blues */
  
  /* Shadows */
  --shadow-sm: 0 0.25rem 1.25rem rgba(30,34,40,0.04);
  --shadow-md: 0 0.5rem 1.5rem rgba(30,34,40,0.06);
  --shadow-lg: 0 1rem 3rem rgba(30,34,40,0.08);
  --shadow-hover: 0 1.25rem 3.5rem rgba(30,34,40,0.12);
  
  /* Border radius */
  --radius-sm: 0.4rem;
  --radius-md: 0.6rem;
  --radius-lg: 0.8rem;
  --radius-xl: 1rem;
  
  /* Bootstrap default card border-radius override (+50% from 0.2rem) */
  --bs-card-border-radius: 0.3rem;
  --bs-border-radius: 0.3rem;
  
  /* Spacing */
  --section-padding: 7rem 0;
}

/* ===== Reduced motion (WCAG 2.3.3) =====
 * Site-wide, deliberately generic rather than touching every individual
 * @keyframes/transition rule below (there are dozens - hero robots,
 * scroll-reveal fades, marquees, hover lifts, etc.). Near-zero duration
 * instead of animation:none so every animation still reaches its real
 * end state instantly (scroll-reveal content still becomes visible,
 * just without the animated transition) - nothing is left permanently
 * hidden or mid-transition for visitors with this OS-level preference
 * set. No visual change at all for anyone without it set. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

.cta-gradient-robot.play img {
  animation-play-state: running;
}
.hero-image-wrapper.play img {
  animation-play-state: running;
}

/* ===== Brand Primary Overrides for Utilities ===== */
.bg-primary { background-color: var(--text-dark) !important; }
.text-primary { color: var(--text-dark) !important; }
.border-primary { border-color: var(--text-dark) !important; }

/* Pagination — default link colour + active/hover override */
.pagination .page-link {
  color: var(--text-dark);
  border-color: rgba(164, 174, 198, 0.3);
}

.pagination .page-item.active .page-link,
.pagination .page-link:focus,
.pagination .page-link:hover {
  background-color: var(--text-dark);
  border-color: var(--text-dark);
  color: #fff;
}

/* Progress bar primary */
.progress-bar.bg-primary,
.progress-bar {
  background-color: var(--text-dark) !important;
}

/* ===== Alerts — Hostaan brand style =====
 * Soft tinted background + coloured left border accent + matching text.
 * Left border identifies severity at a glance without aggressive colour fills.
 */
.alert {
  border: none;
  border-left: 4px solid;
  border-radius: var(--radius-sm);
  padding: 1rem 1.25rem;
}

.alert-primary {
  background: rgba(0, 66, 117, 0.07);
  border-left-color: var(--primary);
  color: var(--primary);
}

.alert-info {
  background: rgba(4, 123, 193, 0.08);
  border-left-color: var(--blue-dark);
  color: var(--blue-dark);
}

.alert-success {
  background: rgba(25, 135, 84, 0.08);
  border-left-color: #198754;
  color: #145c38;
}

.alert-danger {
  background: rgba(220, 53, 69, 0.08);
  border-left-color: #dc3545;
  color: #a12030;
}

.alert-warning {
  background: rgba(247, 148, 30, 0.1);
  border-left-color: var(--orange-dark);
  color: #8a5800;
}

.alert-secondary {
  background: rgba(108, 117, 125, 0.08);
  border-left-color: rgba(108, 117, 125, 0.5);
  color: var(--text-body);
}

/* List group active */
.list-group-item.active {
  background-color: var(--text-dark) !important;
  border-color: var(--text-dark) !important;
}

.hero.hero-sunset .carousel-item {
  min-height: 440px; /* Match /webhotellit hero height */
}

/* ===== Section Spacing ===== */
.py-6 {
  padding-top: 5rem !important;
  padding-bottom: 5rem !important;
}

.py-5 {
  padding-top: 4rem !important;
  padding-bottom: 4rem !important;
}

/* ===== Typography System ===== */
/* 
 * TYPOGRAPHY RULES:
 * 1. All heading styles defined here apply to /styles AND /blocks automatically
 * 2. Never override font-weight, letter-spacing, or line-height with additional classes
 * 3. Use semantic HTML: <h1> for page title, <h2> for sections, <h3> for subsections
 * 4. For non-heading decorative text (eyebrows), use <p> with utility class
 * 
 * HEADING HIERARCHY:
 * H1 - Page title / Hero headline (once per page, SEO critical)
 * H2 - Section headings ("Miksi Hostaan?", "Vertaile paketteja")
 * H3 - Subsection headings (Product names, feature titles)
 * H4 - Small headings (Service box titles, card headings)
 * H5 - Micro headings (FAQ questions, labels)
 * H6 - Reserved for edge cases
 */

/* Keeps any same-page anchor jump (plain href="#id" clicks, browser back/
 * forward restoring a #hash URL, and Element.scrollIntoView() calls like
 * initHashCollapse() in main.js) from landing with its target's top edge
 * hidden under the fixed .navbar. 100px comfortably clears the largest
 * navbar height in use (90px on body.no-hero pages) with a little to spare;
 * scroll-padding-top is honored by both native anchor navigation and
 * scrollIntoView() in every current browser, so this is a single global
 * fix rather than something to repeat per anchor. */
html {
  scroll-padding-top: 100px;
}

body {
  font-family: 'Manrope', sans-serif;
  font-size: 1.125rem;        /* 18px - regular paragraph size */
  line-height: 1.7;
  color: var(--text-body);
  background-color: var(--bg-white);
  -webkit-font-smoothing: antialiased;
  /* See the --eb-h comment on .navbar above - same variable, same fallback. */
  padding-top: var(--eb-h, 0px);
}

/* Pages without hero need padding */
body.no-hero {
  padding-top: calc(90px + var(--eb-h, 0px));
}

@media (max-width: 1199px) {
  body.no-hero { padding-top: calc(80px + var(--eb-h, 0px)); }
}

@media (max-width: 991px) {
  body.no-hero { padding-top: calc(70px + var(--eb-h, 0px)); }
}

/* Base heading styles - applies to ALL headings */
h1, h2, h3, h4, h5, h6 {
  font-weight: 700;
  line-height: 1.25;
  margin-bottom: 1rem;
  color: var(--text-dark);
  letter-spacing: -0.015em;
}

/* H1 - Hero / Page Title */
h1 { 
  font-family: 'Raleway', sans-serif;  /* Raleway for impact */
  font-size: 3.125rem;  /* 50px */
  font-weight: 900;     /* Black weight */
  /* Mild vertical gradient: darker blue at bottom -> slightly lighter at top */
  background: linear-gradient(
    to top,
    #004275 0%,      /* Darkest blue at bottom */
    #005a9d 100%     /* Slightly lighter at top */
  );
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  /* Fallback for browsers without gradient text support */
  color: #004275;
}

/* Badges and spans inside gradient headings need their fill reset
 * or the parent's -webkit-text-fill-color: transparent bleeds in */
h1 .badge, h2 .badge, h3 .badge,
h1 .btn, h2 .btn, h3 .btn {
  -webkit-text-fill-color: initial;
  color: white;
}

/* H2 - Section Headings */
h2 { 
  font-family: 'Raleway', sans-serif;
  font-size: 3rem;      /* 48px */
  font-weight: 700;     /* Bold weight */
}

/* H3 - Subsection Headings */
h3 { 
  font-family: 'Raleway', sans-serif;
  font-size: 2.375rem;  /* 38px */
  font-weight: 900;     /* Black weight */
  letter-spacing: -0.01em;
}

/* H4 - Small Headings */
h4 { 
  font-family: 'Raleway', sans-serif;
  font-size: 1.75rem;   /* 28px -- visibly distinct from H5 */
  font-weight: 700;     /* Bold weight */
}

/* H5 - Micro Headings */
h5 {
  font-family: 'Raleway', sans-serif;
  font-size: 1.5rem;    /* 24px - same as H4 */
  font-weight: 700;     /* Bold weight */
}

/* H6 - Micro Labels */
h6 {
  font-family: 'Raleway', sans-serif;
  font-size: 1.125rem;  /* 18px */
  font-weight: 700;     /* Bold weight */
}

p.lead {
  font-size: 1.25rem;         /* 20px - slightly larger than regular */
  font-weight: 700;           /* Bold to distinguish from regular */
  color: var(--text-body);
  line-height: 1.6;
}

/* ===== Links ===== */
a {
  color: var(--text-dark);
  text-decoration: underline;
  text-decoration-color: rgba(0, 66, 117, 0.3);
  text-underline-offset: 2px;
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}

a:hover {
  color: var(--blue-dark);
  text-decoration-color: var(--blue-dark);
}

/* Muted text - same size as regular, but light color */
.text-muted {
  color: var(--text-light) !important;  /* Light gray color */
  /* Font size inherited from body (1.125rem = 18px) */
}

/* Small text */
.small, small {
  font-size: 0.875rem;        /* 14px - smaller than regular */
}

/* ===== Buttons ===== */
.btn {
  font-weight: 700;
  border-radius: 0.4rem; /* Default 8px radius */
  padding: 0.8rem 1.8rem;
  transition: all 0.25s ease-in-out;
  box-shadow: none;
  letter-spacing: -0.01em;
  font-size: 1rem;
  /* Use transparent border instead of none -- outline variants need the border box */
  border: 1px solid transparent;
}

/* Unified hover lift -- applies to all button types for visual consistency */
.btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 0.25rem 0.75rem rgba(30, 34, 40, 0.15);
}

/* Outline button overrides -- Bootstrap sets border-color but our base removes border */
.btn-outline-primary {
  color: var(--primary);
  border-color: var(--primary) !important;
  background: transparent;
}
.btn-outline-primary:hover {
  background-color: var(--primary);
  border-color: var(--primary) !important;
  color: white;
}
.btn-outline-secondary { border-color: #6c757d !important; }
.btn-outline-success    { border-color: #198754 !important; }
.btn-outline-danger     { border-color: #dc3545 !important; }
.btn-outline-warning    { border-color: #ffc107 !important; }
.btn-outline-info       { border-color: #0dcaf0 !important; }
.btn-outline-dark       { border-color: #212529 !important; }

.btn.rounded {
  border-radius: 0.4rem !important; /* Sandbox uses 8px border-radius, not fully rounded */
}

.btn-primary {
  background-color: var(--primary);
  border-color: var(--primary);
  color: white;
}

.btn-primary:hover {
  background-color: var(--primary-hover);
  border-color: var(--primary-hover);
  transform: translateY(-2px);
  box-shadow: 0 0.25rem 0.75rem rgba(30, 34, 40, 0.15);
  color: white;
}

.btn-light {
  background: white;
  color: var(--text-dark);
  border: 1px solid rgba(164, 174, 198, 0.2);
}

.btn-light:hover {
  background: white;
  color: var(--primary);
  border-color: rgba(164, 174, 198, 0.4);
  transform: translateY(-2px);
}

/* Button size overrides -- Bootstrap's btn-sm/btn-lg are overridden by our base .btn padding */
.btn-lg {
  padding: 1rem 2.5rem;
  font-size: 1.125rem;
  border-radius: 0.5rem;
}

.btn-sm {
  padding: 0.4rem 1rem;
  font-size: 0.875rem;
  border-radius: 0.35rem;
}

/* Button with icon (Sandbox style) */
.btn-icon {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.btn-icon i {
  font-size: 1.1rem;
  /* Icon animation removed for unified hover effect */
}

/* ===== Navigation (Sticky with scroll effect) ===== */
.navbar {
  background: transparent; /* Transparent at top */
  backdrop-filter: none;
  padding: 1.25rem 0;
  box-shadow: none; /* No shadow at top */
  border-bottom: 1px solid transparent; /* No border at top */
  position: fixed;
  /* --eb-h is set by initEmergencyBanner() (main.js) only while the sitewide
     emergency banner (components/emergency-banner.html) is active - pushes
     the navbar down by exactly the banner's own rendered height, which
     varies as the banner wraps to more lines on narrow viewports. Falls
     back to 0px (i.e. no change at all) whenever the banner is absent. */
  top: var(--eb-h, 0px);
  left: 0;
  right: 0;
  z-index: 1000;
  transition: all 0.3s ease;
}

/* Navbar when scrolled (floating state) */
.navbar.navbar-scrolled {
  background: rgba(255, 255, 255, 0.98);
  backdrop-filter: blur(10px);
  box-shadow: 0 2px 8px rgba(30, 34, 40, 0.06);
  border-bottom: 1px solid rgba(164, 174, 198, 0.15);
  padding: 0.9rem 0; /* Slightly more compact when scrolled */
}

/* Logo shrinks slightly when scrolled - responsive */
.navbar.navbar-scrolled .navbar-brand img {
  height: 34px;
}

/* Tablet: scrolled state */
@media (max-width: 1199px) {
  .navbar.navbar-scrolled {
    padding: 0.8rem 0;
  }
  
  .navbar.navbar-scrolled .navbar-brand img {
    height: 30px;
  }
}

/* Mobile: scrolled state */
@media (max-width: 991px) {
  .navbar.navbar-scrolled {
    padding: 0.65rem 0;
  }
  
  .navbar.navbar-scrolled .navbar-brand img {
    height: 26px;
  }
}

/* Logo - scales with viewport */
.navbar-brand img {
  height: 38px; /* Larger logo on desktop */
  width: auto;
  transition: height 0.3s ease;
}

/* Nav links - scale with viewport */
.nav-link {
  color: var(--text-dark);
  font-weight: 700;
  padding: 0.5rem 1.25rem !important;
  font-size: 1.05rem; /* Slightly larger on desktop */
  transition: all 0.3s ease;
}
.navbar .nav-link.dropdown-toggle { color: var(--text-dark) !important; }
.navbar .nav-link { color: var(--text-dark) !important; }

/* Improve nav visibility when navbar is transparent */
.navbar:not(.navbar-scrolled) .nav-link {
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5); /* Subtle white shadow for contrast */
}

.navbar:not(.navbar-scrolled) .navbar-toggler {
  border-color: var(--text-dark);
}

/* Tablet: Medium size */
@media (max-width: 1199px) {
  .navbar {
    padding: 1rem 0;
  }
  
  .navbar-brand img {
    height: 34px;
  }
  
  .nav-link {
    font-size: 1rem;
  }
}

/* Mobile: Compact size + scrollable */
@media (max-width: 991px) {
  .navbar {
    padding: 0.75rem 0;
    max-height: 100vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  
  .navbar-brand img {
    height: 28px;
  }
  
  .nav-link {
    font-size: 0.95rem;
  }
  
  .navbar-collapse {
    max-height: calc(100vh - 70px);
    overflow-y: auto;
    /* Mobile menu always has background (even when navbar is transparent) */
    background: rgba(255, 255, 255, 0.98);
    backdrop-filter: blur(10px);
    margin-left: -1rem;
    margin-right: -1rem;
    padding: 1rem;
    border-top: 1px solid rgba(164, 174, 198, 0.15);
    margin-top: 0.75rem;
  }
}

.nav-link:hover { color: var(--blue-dark); }
.nav-link.active { color: var(--text-dark); }
.navbar .dropdown-toggle.active { color: var(--text-dark) !important; }

/* Language Switcher Dropdown */
.language-select .nav-link {
  font-size: 1rem;
  font-weight: 700;
  padding: 0.5rem 1.25rem !important;
}

.language-select .flag-icon {
  width: 18px;
  height: 12px;
  display: inline-block;
  margin-right: 0.4rem;
  vertical-align: middle;
}

/* Match mega menu styling exactly */
.language-select .dropdown-menu {
  min-width: 160px;
  padding: 1rem;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  border: none; /* No border like mega menu */
  background: white;
  margin-top: -2px; /* Overlap for hover gap fix */
  padding-top: 1rem; /* Minimal padding - no subheading needed */
  /* Fade-up animation like mega menu */
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.3s ease, transform 0.3s ease;
  pointer-events: none;
}

.language-select .dropdown-menu.show {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.language-select .dropdown-item {
  padding: 0.5rem 1.25rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text-dark);
  border-radius: var(--radius-sm);
  transition: background-color 0.2s ease;
  display: flex;
  align-items: center;
}

.language-select .dropdown-item:hover {
  background-color: var(--bg-pale); /* Match mega menu hover */
  color: var(--text-dark);
}

.language-select .dropdown-item.active {
  background-color: var(--bg-pale);
  color: var(--text-dark);
  font-weight: 600;
  border-radius: var(--radius-sm);
}

/* Login Button in Navbar - Inherit base button styles */
.navbar .btn-sm {
  padding: 0.5rem 1.25rem;
  font-size: 1rem;
  white-space: nowrap;
}

/* Mobile adjustments for language and login */
@media (max-width: 991px) {
  /* Language switcher - flatten like mega menu */
  .language-select .nav-link {
    pointer-events: auto;
    cursor: pointer;
    padding-bottom: 0.25rem !important;
  }
  
  /* Hide flag and language code, show heading text instead */
  .language-select .nav-link .flag-icon,
  .language-select .nav-link:not([aria-label])::before {
    display: none;
  }
  
  .language-select .nav-link::before {
    content: attr(aria-label); /* Show "Kieli" or "Language" */
    font-weight: 700;
    color: var(--text-dark);
  }
  
  .language-select .dropdown-toggle::after {
    display: inline-block;
  }
  
  .language-select .dropdown-menu {
    display: none !important;
    position: static !important;
    width: 100%;
    box-shadow: none;
    background: transparent;
    padding: 0;
    margin: 0;
    opacity: 1 !important;
    transform: none !important;
  }
  
  .language-select .dropdown-menu.show {
    display: block !important;
  }

  .language-select .dropdown-item {
    padding: 0.75rem 1rem;
    padding-left: 2rem; /* Indent like mega menu */
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-body);
    background: transparent;
    border-bottom: 1px solid rgba(164, 174, 198, 0.08);
  }
  
  .language-select .dropdown-item:last-child {
    border-bottom: none;
  }
  
  .language-select .dropdown-item:hover {
    background: var(--bg-pale);
    color: var(--primary);
  }
  
  .language-select .dropdown-item.active {
    color: var(--primary);
  }
  
  /* Login button */
  .nav-item:has(.btn-primary) {
    margin-top: 0.5rem;
  }
  
  .navbar .btn-primary {
    width: 100%;
    text-align: center;
    margin-left: 0 !important;
    padding: 0.75rem 1rem;
  }
}

/* Dropdown toggle arrow */
.nav-link.dropdown-toggle::after {
  margin-left: 0.4em;
  vertical-align: 0.15em;
  border-top: 0.3em solid;
  border-right: 0.3em solid transparent;
  border-left: 0.3em solid transparent;
}

/* ===== Mega Menu ===== */
.mega-menu-item {
  position: static;
}

.mega-menu {
  position: absolute;
  left: 50% !important;
  transform: translateX(-50%);
  width: 100%;
  max-width: 1140px; /* Container width */
  border: none;
  border-radius: var(--radius-lg);
  background: white;
  box-shadow: 0 0.5rem 2rem rgba(30, 34, 40, 0.15);
  padding: 2rem;
  margin-top: -2px; /* Overlap parent to prevent gap */
  padding-top: 2.5rem; /* Compensate for overlap */
  
  /* Fade-up animation */
  opacity: 0;
  transition: opacity 0.3s ease, transform 0.3s ease;
  display: none;
}

.mega-menu.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  display: block;
  animation: megaMenuFadeUp 0.3s ease forwards;
}

@keyframes megaMenuFadeUp {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
}

/* Mega menu headings */
.mega-menu-heading {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #1e2228;  /* Black instead of blue */
  margin-bottom: 1rem;
}

/* Mega menu list */
.mega-menu-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.mega-menu-list li {
  margin-bottom: 0.4rem;
}

/* Mega menu links */
.mega-menu-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1.25rem;
  color: #1e2228;  /* Black instead of blue */
  text-decoration: none;
  font-size: 1rem;
  font-weight: 700;
  font-family: 'Manrope', sans-serif;
  transition: color 0.2s ease;
  border-radius: var(--radius-sm);
}

.mega-menu-link:hover {
  color: var(--blue-dark);  /* Robot dark blue on hover */
  background: var(--bg-pale);
}

.mega-menu-link i {
  font-size: 1.1rem;
  color: #1e2228;  /* Black icons */
  flex-shrink: 0;
  width: 20px;
}

.mega-menu-link:hover i {
  color: var(--blue-dark);  /* Blue icons on hover */
}

.mega-menu-link span {
  flex: 1;
}

/* "Katso kaikki ratkaisut" - small link below the Ratkaisut mega menu's
   4 solution cards, pointing to the /ratkaisut hub page. */
.mega-menu-view-all {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.9rem;
  font-weight: 700;
  font-family: 'Manrope', sans-serif;
  color: var(--blue-dark);
  text-decoration: none;
  transition: color 0.2s ease;
}

.mega-menu-view-all:hover {
  color: var(--primary);
  text-decoration: underline;
}

.mega-menu-view-all i {
  font-size: 0.85rem;
}

/* Link without icon */
.mega-menu-link.no-icon {
  padding-left: 1.25rem;
}

/* Mega menu column divider */
.mega-menu-col {
  border-right: 1px solid rgba(164, 174, 198, 0.15);
  padding-right: 1.5rem;
  padding-left: 1.5rem;
}

.mega-menu-col:first-child {
  padding-left: 0;
}

.mega-menu-col:last-child {
  border-right: none;
  padding-right: 0;
}

/* Non-divider columns (no border) */
.mega-menu .col-lg-3:not(.mega-menu-col),
.mega-menu .col-md-3:not(.mega-menu-col) {
  padding-left: 1.5rem;
}

/* Mega menu description text */
.mega-menu-desc {
  font-size: 0.95rem;
  color: var(--text-body);
  line-height: 1.7;
  margin-bottom: 1.5rem;
  padding: 0 1.25rem;
}

/* Mega menu promo box */
.mega-menu-promo {
  background: var(--bg-pale);
  padding: 1.5rem;
  border-radius: var(--radius-md);
  text-align: center;
}

.mega-menu-promo-img {
  max-width: 100px;
  height: auto;
  margin: 0 auto;
}

.mega-menu-promo h6 {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text-dark);
  margin-bottom: 0.5rem;
}

/* Mobile: Flatten mega menu to single list */
@media (max-width: 991px) {
  /* Mobile section toggles remain tappable with a visible disclosure arrow. */
  .mega-menu-item .dropdown-toggle {
    pointer-events: auto;
    cursor: pointer;
  }
  
  .mega-menu-item .dropdown-toggle::after {
    display: inline-block;
  }
  
  /* Keep opened mobile sections in normal flow. */
  .mega-menu {
    position: static !important;
    transform: none !important;
    /* Kill the megaMenuFadeUp animation — it applies translateX(-50%) which
     * displaces the menu 50% left for ~0.3s, causing the visible flash. */
    animation: none !important;
    transition: none !important;
    width: 100%;
    max-width: 100%;
    padding: 0 !important;
    margin: 0 !important;
    box-shadow: none !important;
    border: none !important;
    border-radius: 0 !important;
    background: transparent !important;
    opacity: 1 !important;
    display: none !important;
  }

  .mega-menu.show {
    display: block !important;
  }
  
  .mega-menu .row {
    display: block;
  }
  
  /* Stack all columns */
  .mega-menu-col,
  .mega-menu .col-md-3,
  .mega-menu .col-md-4,
  .mega-menu .col-lg-3,
  .mega-menu .col-lg-4 {
    border: none;
    padding: 0;
    margin: 0;
  }
  
  /* Hide headings, descriptions, promos, and buttons on mobile */
  .mega-menu-heading,
  .mega-menu-desc,
  .mega-menu-promo {
    display: none;
  }
  
  /* Simplify links to match main nav style */
  .mega-menu-link {
    padding: 0.75rem 1rem;
    padding-left: 2rem; /* Indent submenu items */
    font-size: 0.95rem;
    font-weight: 600; /* Slightly lighter than main nav */
    color: var(--text-body);
    background: transparent;
    border-radius: 0;
  }
  
  .mega-menu-link:hover {
    background: var(--bg-pale);
    color: var(--primary);
  }
  
  .mega-menu-link i {
    display: none;
  }
  
  .mega-menu-list {
    margin-bottom: 0;
  }
  
  .mega-menu-list li {
    margin-bottom: 0;
    border-bottom: 1px solid rgba(164, 174, 198, 0.08);
  }
  
  .mega-menu-list li:last-child {
    border-bottom: none;
  }
}

/* ===== Hero Section (Refined) ===== */
.hero {
  background: linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(246, 247, 249, 1) 100%);
  padding-top: 130px; /* Space for transparent navbar (90px header + 40px extra) */
  padding-bottom: 3rem;
  position: relative;
  overflow: hidden;
  margin-top: 0;
}

/* Homepage hero: sunset background, rounded corners, page width */
.hero.hero-sunset {
  background: transparent; /* Let the carousel card provide the background */
}

.hero.hero-sunset .carousel-inner {
  max-width: 1320px; /* Page width (container-xl) */
  margin: -40px auto 0; /* Raise carousel 40px */
  border-radius: var(--radius-xl);
  overflow: hidden;
  padding: 2rem 0;
  background: linear-gradient(180deg, #f6f8fa 0%, #ffffff 100%);
  position: relative;
}

/* Animated sunset gradient - rises on page load */
.hero.hero-sunset .carousel-inner::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    ellipse 130% 90% at 50% 110%,
    rgba(255, 160, 60, 0.65) 0%,
    rgba(255, 120, 40, 0.45) 15%,
    rgba(255, 180, 100, 0.3) 30%,
    rgba(39, 170, 225, 0.2) 50%,
    rgba(4, 123, 193, 0.08) 70%,
    transparent 100%
  );
  transform-origin: bottom center;
  animation: sunsetRise 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  z-index: 0;
  pointer-events: none;
}

/* Match /webhotellit hero content padding */
.hero.hero-sunset .container {
  padding-left: 3rem;
  padding-right: 2rem;
}

/* hero-image-style inside hero-sunset: same positioning as carousel-inner */
.hero.hero-sunset:has(.hero-image-style) {
  padding-bottom: 0;
}

.hero.hero-sunset .hero-image-style {
  margin: -40px auto 0;
}

/* When hero-image-style is inside hero-sunset, neutralize the carousel-inner
   styling that hero-sunset normally applies (prevent double background/radius) */
.hero.hero-sunset .hero-image-style .carousel-inner {
  max-width: none;
  margin: 0;
  border-radius: 0;
  background: none;
  padding: 0;
}

.hero.hero-sunset .hero-image-style .carousel-inner::before {
  display: none;
}

/* ===== Page-width rounded hero variants (non-carousel) =====
 * All three share: proper top spacing for body.no-hero (90px navbar offset),
 * page-width container with rounded corners, and min-height matching
 * the full-width parallax hero (~280px visible content area).
 * Text overrides ensure headings are visible (not transparent gradient).
 */

/* --- Shared layout for all page-width rounded heroes ---
 * These mirror hero-image-style: section-level element that starts
 * right below the navbar (body.no-hero provides 90px offset).
 * The container is the rounded visual block, same width/corners as
 * hero-image-style (max-width 1320px, radius-xl).
 * padding: 4rem 0 matches hero-image-style exactly.
 */
.hero-sunset-simple,
.hero-pale-simple,
.hero-image-simple {
  background: transparent;
  padding: 0 0 2rem; /* No top pad — body.no-hero handles it */
}

.hero-sunset-simple .container,
.hero-pale-simple .container,
.hero-image-simple .container {
  max-width: 1320px;
  border-radius: var(--radius-xl);
  /* Match hero-image-style internal padding: 4rem vertical */
  padding: 4rem 3rem;
  /* Min-height matches hero-image-style visible content area */
  min-height: 340px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
}

/* Force visible dark text (override gradient h1 in all rounded heroes) */
.hero-sunset-simple h1,
.hero-pale-simple h1 {
  background: none;
  -webkit-background-clip: unset;
  -webkit-text-fill-color: var(--text-dark);
  background-clip: unset;
  color: var(--text-dark);
}

.hero-sunset-simple .lead,
.hero-pale-simple .lead {
  color: var(--text-body);
}

.hero-sunset-simple .eyebrow,
.hero-pale-simple .eyebrow,
.hero-image-simple .eyebrow {
  position: relative;
  z-index: 1;
}

/* --- 1.2 Sunset variant --- */
.hero-sunset-simple .container {
  background: linear-gradient(180deg, #f6f8fa 0%, #ffffff 100%);
  overflow: hidden;
}

/* Sunset gradient overlay (static — no animation) */
.hero-sunset-simple .container::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    ellipse 130% 90% at 50% 110%,
    rgba(255, 160, 60, 0.55) 0%,
    rgba(255, 120, 40, 0.35) 15%,
    rgba(255, 180, 100, 0.22) 30%,
    rgba(39, 170, 225, 0.15) 50%,
    rgba(4, 123, 193, 0.06) 70%,
    transparent 100%
  );
  z-index: 0;
  pointer-events: none;
}

.hero-sunset-simple .container > * {
  position: relative;
  z-index: 1;
}

/* --- 1.3 Pale variant --- */
.hero-pale-simple .container {
  background: var(--bg-pale);
}

/* --- 1.5 Background image variant (page-width rounded) --- */
.hero-image-simple .container {
  overflow: hidden;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

/* Dark tint overlay */
.hero-image-simple .container::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 30, 60, 0.55);
  pointer-events: none;
  z-index: 0;
}

.hero-image-simple .container > * {
  position: relative;
  z-index: 1;
}

/* White text for image hero */
.hero-image-simple h1 {
  background: none;
  -webkit-background-clip: unset;
  -webkit-text-fill-color: white;
  background-clip: unset;
  color: white;
  text-shadow: 0 2px 14px rgba(0,0,0,0.5);
}

.hero-image-simple .lead {
  color: rgba(255,255,255,0.85);
  text-shadow: 0 1px 8px rgba(0,0,0,0.4);
}

.hero-image-simple .eyebrow {
  color: rgba(255,255,255,0.65) !important;
  text-shadow: 0 1px 4px rgba(0,0,0,0.4);
}

/* Responsive: reduce min-height + padding on smaller screens */
@media (max-width: 991px) {
  .hero-sunset-simple .container,
  .hero-pale-simple .container,
  .hero-image-simple .container {
    min-height: 280px;
    padding: 3rem 2rem;
  }
}

@media (max-width: 767px) {
  .hero-sunset-simple .container,
  .hero-pale-simple .container,
  .hero-image-simple .container {
    min-height: 240px;
    padding: 2.5rem 1.5rem;
  }
}

/* Small hero (non-carousel pages like webhosting, contact) */
.hero-small {
  padding-top: 180px; /* More space for header overlap (90px header + 90px content) */
  padding-bottom: 2rem;
}

/* Adjust hero padding for different screen sizes */
@media (max-width: 1199px) {
  .hero { padding-top: 120px; } /* 80px header + 40px */
  .hero-small { padding-top: 160px; } /* 80px header + 80px content */
}

@media (max-width: 991px) {
  .hero { padding-top: 110px; } /* 70px header + 40px */
  .hero-small { padding-top: 140px; } /* 70px header + 70px content */
}

/* Hero Carousel */
.hero .carousel-inner {
  min-height: 500px; /* Fixed height to prevent vertical jumping */
  position: relative;
  overflow: hidden; /* Hide slides outside viewport */
}

.hero .carousel-item {
  min-height: 500px; /* Match carousel-inner height */
  display: flex;
  align-items: center;
  -webkit-transition: -webkit-transform 0.6s ease-in-out;
  transition: transform 0.6s ease-in-out;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  perspective: 1000px;
}

/* Default positioning for inactive items */
.hero .carousel-item {
  display: none;
}

.hero .carousel-item.active,
.hero .carousel-item-next,
.hero .carousel-item-prev {
  display: flex;
}

/* Next slide positioning (coming from right) */
.hero .carousel-item-next:not(.carousel-item-start) {
  transform: translateX(100%);
}

/* Active slide moving out to left when going forward */
.hero .carousel-item.active.carousel-item-start {
  transform: translateX(-100%);
}

/* Previous slide positioning (coming from left) */
.hero .carousel-item-prev:not(.carousel-item-end) {
  transform: translateX(-100%);
}

/* Active slide moving out to right when going backward */
.hero .carousel-item.active.carousel-item-end {
  transform: translateX(100%);
}

/* Centered position for active and transitioning slides */
.hero .carousel-item-next.carousel-item-start,
.hero .carousel-item-prev.carousel-item-end,
.hero .carousel-item.active:not(.carousel-item-start):not(.carousel-item-end) {
  transform: translateX(0);
}

/* Carousel Indicators (Dots) */
.hero .carousel-indicators {
  bottom: 1.5rem;
  margin-bottom: 0;
}

.hero .carousel-indicators [data-bs-target] {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: rgba(52, 63, 82, 0.3);
  border: none;
  margin: 0 4px;
  transition: all 0.3s ease;
}

.hero .carousel-indicators [data-bs-target].active {
  background-color: #343f52;
  width: 10px;
  height: 10px;
}

.hero .container { position: relative; z-index: 1; }

.hero h1,
.hero-image-carousel h1 { margin-bottom: 1rem; animation: fadeInUp 0.7s ease-out; }
.hero .lead,
.hero-image-carousel .lead { margin-bottom: 2rem; animation: fadeInUp 0.7s ease-out 0.1s backwards; }
.hero .btn,
.hero-image-carousel .btn { 
  animation: fadeInUp 0.7s ease-out 0.2s backwards;
  /* Ensure hover effect works in carousel */
  position: relative;
  z-index: 2;
}

.hero-robot {
  max-width: 260px; /* 50% larger than previous 175px */
  width: 100%;
  height: auto;
  animation: float 4s ease-in-out infinite;
  filter: drop-shadow(0 20px 40px rgba(0,0,0,0.08));
}
/* Slightly smaller for robot_hello on homepage */
.hero-robot[src*="robot_hello"] {
  max-width: 234px; /* 10% smaller than 260px */
}

/* Special sizing for rocket (needs to be larger) */
.hero-robot[src*="rocket"] {
  max-width: 350px; /* 100% larger than base size for rocket */
}

/* ===== BLOCK 1.1c: Sunset Carousel with Big Robots =====
 * Same sunset card carousel but with 2.15-style oversized robots
 * clipped at the bottom of the carousel-inner area.
 * Text stays left-aligned like 2.15, robots peek from bottom-right.
 */
.hero-sunset-bigbot .carousel-inner {
  min-height: 520px;
  padding-bottom: 4rem;
}

.hero-sunset-bigbot .carousel-item {
  min-height: 520px;
  position: relative;
}

/* Text left-aligned (not centered) like 2.15 */
.hero-sunset-bigbot .container {
  padding-left: 3rem;
  padding-right: 2rem;
}

/* Constrain text column so it doesn't overlap robot */
.hero-sunset-bigbot .col-lg-6:first-child {
  position: relative;
  z-index: 2;
}

/* Robot wrapper - positioned at bottom-right of carousel item */
.hero-robot-big-wrap {
  position: relative;
  height: 380px;
  overflow: visible;
}

.hero-robot-big {
  position: absolute;
  bottom: -260px; /* Push hello/thumbs down more */
  right: -40px;
  width: 550px;
  max-width: none;
  height: auto;
  filter: drop-shadow(0 20px 40px rgba(0,0,0,0.08));
  pointer-events: none;
}

/* Rocket: higher and bigger */
.hero-robot-big[src*="rocket"] {
  width: 720px;
  right: -100px;
  bottom: -200px; /* Higher than others */
}

/* Hello robot slightly smaller, pushed down */
.hero-robot-big[src*="robot_hello"] {
  width: 480px;
  right: -20px;
  bottom: -270px;
}

/* Thumbs up pushed down */
.hero-robot-big[src*="thumbs_up"] {
  bottom: -270px;
}

/* Tablet - follow 2.15 style: text left, robot right, same height */
@media (max-width: 991px) {
  .hero-sunset-bigbot .carousel-inner,
  .hero-sunset-bigbot .carousel-item {
    min-height: 480px;
  }
  .hero-sunset-bigbot .col-lg-6:first-child {
    max-width: 60%;
    flex: 0 0 60%;
  }
  .hero-robot-big-wrap {
    position: absolute;
    bottom: 0;
    right: 0;
    width: 42%;
    height: 100%;
  }
  .hero-robot-big {
    width: 400px;
    bottom: -220px;
    right: -30px;
  }
  .hero-robot-big[src*="rocket"] {
    width: 500px;
    right: -70px;
    bottom: -180px;
  }
  .hero-robot-big[src*="robot_hello"] {
    width: 360px;
    right: -10px;
    bottom: -230px;
  }
  .hero-robot-big[src*="thumbs_up"] {
    bottom: -230px;
  }
  .hero-sunset-bigbot .container {
    padding-left: 1.5rem;
    padding-right: 1rem;
  }
}

/* Mobile - robot lurks from right edge */
@media (max-width: 768px) {
  .hero-sunset-bigbot .carousel-inner,
  .hero-sunset-bigbot .carousel-item {
    min-height: 440px;
  }
  .hero-sunset-bigbot .col-lg-6:first-child {
    max-width: 65%;
    flex: 0 0 65%;
  }
  .hero-robot-big-wrap {
    position: absolute;
    bottom: 0;
    right: -150px;
    height: 350px;
    width: 450px;
  }
  .hero-robot-big {
    width: 400px;
    bottom: -260px;
    right: 0;
  }
  .hero-robot-big[src*="rocket"] {
    width: 440px;
    right: -20px;
    bottom: -220px;
  }
  .hero-robot-big[src*="robot_hello"] {
    width: 360px;
    right: 10px;
    bottom: -270px;
  }
}

/* ===== BLOCK 2.15b: Hero Image Carousel =====
 * Carousel inside .hero-image-style. Each slide has its own
 * large robot via .hero-carousel-robot, clipped by section overflow:hidden.
 * Shorter than 2.15 - robots show less.
 */
.hero-image-carousel {
  min-height: 420px;
}

.hero-image-carousel .carousel-inner {
  min-height: 380px;
  overflow: visible; /* Let section clip instead */
}

.hero-image-carousel .carousel-item {
  min-height: 380px;
  display: none;
}

.hero-image-carousel .carousel-item.active,
.hero-image-carousel .carousel-item-next,
.hero-image-carousel .carousel-item-prev {
  display: flex;
  align-items: center;
}

/* Horizontal slide transitions */
.hero-image-carousel .carousel-item-next:not(.carousel-item-start) { transform: translateX(100%); }
.hero-image-carousel .carousel-item.active.carousel-item-start { transform: translateX(-100%); }
.hero-image-carousel .carousel-item-prev:not(.carousel-item-end) { transform: translateX(-100%); }
.hero-image-carousel .carousel-item.active.carousel-item-end { transform: translateX(100%); }
.hero-image-carousel .carousel-item-next.carousel-item-start,
.hero-image-carousel .carousel-item-prev.carousel-item-end,
.hero-image-carousel .carousel-item.active:not(.carousel-item-start):not(.carousel-item-end) { transform: translateX(0); }

.hero-image-carousel .carousel-item {
  -webkit-transition: -webkit-transform 0.6s ease-in-out;
  transition: transform 0.6s ease-in-out;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

/* Carousel indicators */
.hero-image-carousel .carousel-indicators {
  bottom: 1rem;
  margin-bottom: 0;
  z-index: 5;
}

.hero-image-carousel .carousel-indicators [data-bs-target] {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: rgba(52, 63, 82, 0.3);
  border: none;
  margin: 0 4px;
}

.hero-image-carousel .carousel-indicators [data-bs-target].active {
  background-color: #343f52;
}

/* ===== Blocks page: WYSIWYG demo separator =====
 * /blocks shows each block completely unwrapped (no surrounding .container)
 * so full-bleed/dark/rounded blocks render at true page width, exactly as
 * they would on a real page - the old /blocks-old nested every demo inside
 * a shared .container, which silently capped "full width" blocks at 1320px
 * and never actually showed their real edge-to-edge rendering.
 * Structure per block:
 *   <div class="blocks-demo-item" data-block-category="X">
 *     <div class="container"><div class="alert alert-secondary blocks-demo-label">Name - description</div></div>
 *     <section class="[real block classes]">...real block markup...</section>
 *   </div>
 * The label is a real Bootstrap alert (not custom-styled text) per the
 * "utilize /styles as-is" rule - .blocks-demo-label only adds the spacing
 * alert-secondary doesn't have by default.
 */
.blocks-demo-item {
  margin-top: 3.5rem;
  background: white;
}
.blocks-demo-item-first {
  margin-top: 0;
}
.blocks-category-intro {
  /* Only one category is ever visible at a time (JS toggles display:none
     on the rest), so this always sits directly under the tabs nav -
     no top margin needed regardless of DOM position. */
  margin-top: 0;
  margin-bottom: 2rem;
}
.blocks-demo-label {
  margin-bottom: 2rem !important; /* wins over the mb-0 utility also present on these alerts */
}

/* Robot wrapper for carousel slides
 * Uses same absolute positioning as 2.15's .hero-image-wrapper:
 * position absolute, spanning full carousel item height, so bottom
 * values match 2.15 exactly.
 */
.hero-image-carousel .carousel-item {
  position: relative; /* Positioning context for robot wrapper */
  /* Per-slide safe zone: clip left/right to item bounds so elements
     (robots, decorations) from adjacent slides never overlap during
     horizontal transitions. Extend top/bottom infinitely so elements
     peeking from below the card remain visible.
     Works for all breakpoints, both forward and backward transitions. */
  clip-path: inset(-9999px 0 -9999px 0);
}

.hero-carousel-robot {
  position: absolute;
  bottom: 0;
  top: 0;
  right: 0;
  width: 50%; /* Right half - match 2.15 */
  overflow: visible;
  pointer-events: none;
}

.hero-carousel-robot img {
  position: absolute;
  bottom: -600px; /* Match 2.15 final position */
  right: -50px;
  width: 650px; /* Match 2.15 */
  max-width: none;
  height: auto;
  pointer-events: none;
  filter: drop-shadow(0 20px 40px rgba(0,0,0,0.08));
}

/* Hello: exact 2.15 match */
.hero-carousel-robot img[src*="robot_hello"] {
  width: 650px;
  right: -50px;
  bottom: -600px;
}

/* Rocket: bigger, positioned higher to show more */
.hero-carousel-robot img[src*="rocket"] {
  width: 800px;
  right: -80px;
  bottom: -250px; /* 250px lower from 0 */
}

/* Brainstorm (domain search slide): 20% smaller than the generic/hello size
 * at both the largest and smallest tiers - bottom/right scaled by the same
 * 0.8 factor so the crop position looks the same, just smaller. */
.hero-carousel-robot img[src*="robot_brainstorm"] {
  width: 520px; /* 650 * 0.8 */
  right: -40px; /* -50 * 0.8 */
  bottom: -480px; /* -600 * 0.8 */
}

/* Mirrored variant - robot on LEFT side */
.hero-carousel-robot-left {
  right: auto;
  left: 0;
}

.hero-carousel-robot-left img {
  right: auto;
  left: -80px;
}

.hero-carousel-robot-left img[src*="maintenance"] {
  width: 650px;
  left: -100px;
  bottom: -520px;
}

/* Maintenance slide: text needs left padding to avoid overlap with left robot */
.hero-image-carousel .order-lg-2 {
  padding-left: 2rem;
}

/* ===== Hero Levitate Robot variant =====
 * Full-body robot in normal document flow (not absolute/bleeding off the
 * section like .hero-carousel-robot/.hero-image-wrapper above), gently
 * floating forever - a modernized version of the pre-2.x hero's small
 * floating robot (see the old .hero-robot + `float` keyframe). Use inside
 * a .col-lg-6 in place of .hero-carousel-robot/.hero-image-wrapper.
 */
/* Absolutely positioned within the carousel item (Bootstrap's own
 * .carousel-item is position:relative) and clipped by the section's own
 * overflow:hidden if it doesn't fit - exactly like .hero-carousel-robot's
 * big bleeding robots do. This is what keeps a multi-slide hero's height
 * driven purely by .hero-image-carousel's own min-height (see that class)
 * instead of by whichever slide happens to have the tallest content: an
 * overlay sitting on top of this robot (.hero-price-badge-overlap,
 * .hero-logo-cluster-overlap) never stretches the section, no matter how
 * far it visually rises above the robot, because none of this participates
 * in normal document flow. An earlier in-flow version of this class (no
 * absolute positioning) briefly existed as .hero-levitate-robot-v2 during
 * development on /webhotellit - folded back into this class once confirmed
 * there; don't reintroduce a separate v2. */
.hero-levitate-robot {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: visible;
  pointer-events: none;
}
.hero-levitate-robot img {
  width: 100%;
  max-width: 300px;
  height: auto;
  filter: drop-shadow(0 24px 40px rgba(0,66,117,0.12));
  animation: heroLevitate 4.5s ease-in-out infinite;
}
@keyframes heroLevitate {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-16px); }
}

/* Price badge overlapping a .hero-levitate-robot in the same carousel item
 * (positioned relative to the item, same containing block as the robot
 * wrapper) - scaled down from its normal 243px so it reads as an accent, not
 * a second hero image competing with the robot. */
.hero-price-badge-overlap {
  position: absolute;
  right: 8%;
  bottom: 8%;
  z-index: 3;
  transform: scale(0.72);
  transform-origin: bottom right;
}

@media (max-width: 991px) {
  .hero-price-badge-overlap {
    right: 2%;
    bottom: 4%;
    transform: scale(0.55);
  }
}

/* Same idea as .hero-price-badge-overlap above, but for a small overlapping
 * cluster of product logo chips (e.g. Google Workspace + Microsoft 365 +
 * Hostaan over a "cloud/email" hero slide's robot) instead of a single price
 * badge. Arranged as a staggered vertical pyramid - Google at the base,
 * Microsoft 365 raised above it, Hostaan highest and centered - taller and
 * narrower than a simple side-by-side row so it reads as one compact
 * cluster. Anchored with `bottom` only (never `top`) at every breakpoint so
 * shrinking the viewport just scales it in place instead of relocating it
 * upward over the robot's eyes. `right` is kept the same at every breakpoint
 * (close to the column's right edge) - an earlier desktop-only `right: 27%`
 * shifted the cluster in over the robot's face and read worse than the
 * smaller-viewport position, so that value was dropped in favor of this one
 * everywhere. */
.hero-logo-cluster-overlap {
  position: absolute;
  right: 6%;
  bottom: 10%;
  z-index: 3;
  width: 160px;
  height: 180px;
}
.hero-logo-chip {
  position: absolute;
  border-radius: 1.5rem;
  background: white;
  box-shadow: 0 14px 40px rgba(0,45,99,0.18);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}
.hero-logo-chip img { width: 100%; height: 100%; object-fit: contain; }
.hero-logo-chip-1 { width: 112px; height: 112px; left: 0; bottom: 0; z-index: 2; }
.hero-logo-chip-2 { width: 92px; height: 92px; right: 0; bottom: 32px; z-index: 2; }
.hero-logo-chip-3 { width: 78px; height: 78px; left: 50%; top: 0; transform: translateX(-50%); z-index: 3; }

@media (max-width: 991px) {
  .hero-logo-cluster-overlap {
    bottom: 4%;
    transform: scale(0.65);
    transform-origin: bottom right;
  }
}

/* ===== Hero Carousel Photo variant =====
 * For a .cropped-device-shot (real UI screenshot framed in a photographed
 * device) used as a carousel slide's image instead of a robot. Markup pairs
 * this class WITH .hero-carousel-robot (e.g. class="hero-carousel-robot
 * hero-carousel-photo") so it inherits that class's absolute positioning +
 * every responsive breakpoint override for free - only the img's own
 * size/position differ here, same as how robot_hello/rocket each get their
 * own [src*=...] override on top of the shared base. This is what makes it
 * scale up and bleed under the rounded hero corner at desktop, then shrink
 * back down at tablet/mobile, instead of sitting static and pushing the
 * text column down like a plain flow image would.
 */
.hero-carousel-photo img {
  bottom: 0;
  right: -130px;
  width: 700px; /* wider aspect ratio than a robot silhouette, so this reads bigger than the same nominal width would on a robot */
}

/* Shrink earlier than the shared tablet breakpoint below - at this width the
   column is already narrow enough that the full-size photo starts crowding
   the text before .hero-carousel-robot's own 991px tablet tier kicks in. */
@media (max-width: 1300px) {
  .hero-carousel-photo img {
    width: 560px;
    bottom: 58px; /* keeps the image's vertical center in place as it shrinks from 700px */
    right: -40px;
  }
}

/* Tablet (max-width: 991px) - match 2.15 tablet */
@media (max-width: 991px) {
  .hero-image-carousel {
    padding: 4rem 0;
  }
  .hero-image-carousel,
  .hero-image-carousel .carousel-inner,
  .hero-image-carousel .carousel-item {
    min-height: 380px;
  }
  /* Text constrained like 2.15 (slides 1 & 3) */
  .hero-image-carousel .col-lg-6:first-child {
    max-width: 60%;
    flex: 0 0 60%;
  }
  /* Slide 2 mirrored: full width, text pushed right to clear left robot */
  .hero-image-carousel .carousel-item .col-lg-6.order-lg-2 {
    max-width: 100%;
    flex: 0 0 100%;
    padding-left: 28%;
    padding-right: 1rem;
  }
  .hero-carousel-robot {
    width: 42%; /* Match 2.15 */
  }
  .hero-carousel-robot img {
    width: 420px;
    bottom: -430px; /* Match 2.15 tablet */
    right: -40px;
  }
  .hero-carousel-robot img[src*="robot_hello"] {
    width: 420px;
    right: -40px;
    bottom: -430px;
  }
  .hero-carousel-robot img[src*="rocket"] {
    width: 520px;
    right: -50px;
    bottom: -130px; /* 300px higher than hello (-430+300) */
  }
  /* Maintenance on left - match mobile half-showing style */
  .hero-carousel-robot-left {
    position: absolute;
    bottom: 0;
    top: auto;
    right: auto;
    left: -230px;
    width: 500px;
    height: 380px;
  }
  .hero-carousel-robot-left img {
    position: absolute;
    left: 0;
    right: auto;
  }
  .hero-carousel-robot-left img[src*="maintenance"] {
    width: 500px;
    bottom: -460px;
  }
  .hero-carousel-photo img {
    width: 420px;
    bottom: 106px; /* keeps the image's vertical center in place as it shrinks */
    right: -20px;
  }
  .hero-image-carousel .container {
    padding-left: 1rem;
    padding-right: 1rem;
  }
}

/* Mobile (max-width: 768px) - match 2.15 mobile */
@media (max-width: 768px) {
  .hero-image-carousel,
  .hero-image-carousel .carousel-inner,
  .hero-image-carousel .carousel-item {
    min-height: 380px;
  }
  .hero-image-carousel {
    padding: 4rem 0;
  }
  /* Slides 1 & 3: text constrained like 2.15 */
  .hero-image-carousel .col-lg-6:first-child {
    max-width: 65%;
    flex: 0 0 65%;
  }
  /* Slide 2 mirrored: full width, text pushed right */
  .hero-image-carousel .carousel-item .col-lg-6.order-lg-2 {
    max-width: 100%;
    flex: 0 0 100%;
    padding-left: 24%;
    padding-right: 1rem;
  }
  /* Robot lurks from bottom-right - match 2.15 */
  .hero-carousel-robot {
    position: absolute;
    bottom: 0;
    top: auto;
    left: auto;
    right: -230px; /* Match 2.15: -250px but slightly less to show more */
    width: 550px;
    height: 400px;
  }
  .hero-carousel-robot img {
    position: absolute;
    bottom: -510px; /* Match 2.15 */
    right: auto;
    left: 0;
    width: 550px;
    opacity: 1;
  }
  .hero-carousel-robot img[src*="robot_hello"] {
    width: 550px;
    bottom: -510px;
  }
  .hero-carousel-robot img[src*="rocket"] {
    width: 620px;
    bottom: -210px; /* 300px higher than hello (-510+300) */
    left: -30px;
  }
  /* Brainstorm: 20% smaller than the generic/hello size here too */
  .hero-carousel-robot img[src*="robot_brainstorm"] {
    width: 440px; /* 550 * 0.8 */
    bottom: -408px; /* -510 * 0.8 */
  }
  /* Maintenance on left */
  .hero-carousel-robot-left {
    right: auto;
    left: -280px;
    width: 550px;
  }
  .hero-carousel-robot-left img {
    left: 0;
    right: auto;
  }
  .hero-carousel-robot-left img[src*="maintenance"] {
    width: 550px;
    bottom: -480px;
  }
  .hero-carousel-photo {
    right: -140px; /* Still lurking, but shows more of the screenshot than a robot silhouette would need to */
    width: 480px;
  }
  .hero-carousel-photo img {
    left: auto;
    right: 0;
    width: 380px; /* a bit bigger than before */
    bottom: 133px; /* keeps the image's vertical center in place */
  }
  .hero-image-carousel .container {
    padding-left: 1rem;
    padding-right: 1rem;
  }
}

/* Small phones (max-width: 575px) - the 768px tier above still gives the
 * text column only 65% width and lets each slide's image bleed off-screen
 * absolutely. That was tuned by resizing a desktop browser down to ~576px
 * and never actually checked below it - on a real narrow phone (iPhone 15
 * ~390px, most Android phones ~360-412px) the text column and the
 * bleeding image physically overlap.
 *
 * Switched to a standard stacked mobile hero instead: text runs full
 * width at the top in normal reading order (eyebrow, heading, lead, then
 * the CTA right after it, so the CTA stays attached to its own paragraph
 * instead of being pulled away from it), and each slide's image follows
 * in NORMAL DOCUMENT FLOW below - centered and height-capped instead of
 * absolutely bled/overlaid. Flow layout can't overlap the text no matter
 * how long the FI vs EN heading/lead happens to run.
 *
 * (An "image stays behind as a background, CTA pinned near the bottom in
 * front of it" version was considered too, closer to how big
 * photographic consumer hero banners work - dropped because it needs a
 * fixed vertical gap tuned per slide AND per language to stop the image
 * either hiding under the text or crowding the CTA, which is exactly the
 * kind of fragile pixel-guessing that broke this breakpoint the first
 * time. This flow version has no such failure mode. Revisit only if a
 * more art-directed mobile hero is wanted later.)
 */
@media (max-width: 575px) {
  /* Zeroing the column's own grid gutter (below) without also zeroing the
   * row's matching negative margin left only ~4px of visible edge margin
   * (container's 16px minus the row's -12px) - net it to zero here and
   * put the ONLY left/right margin on .container, sized to match plain
   * Bootstrap's default gutter (0.75rem/side) that every other, non-hero
   * section on the site relies on with no override of its own. */
  .hero-image-carousel .carousel-item .row {
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
  .hero-image-carousel .container {
    padding-left: .75rem !important;
    padding-right: .75rem !important;
  }
  .hero-image-carousel .col-lg-6:first-child,
  .hero-image-carousel .carousel-item .col-lg-6.order-lg-2,
  .hero-image-carousel .col-lg-6:last-child {
    max-width: 100% !important;
    flex: 0 0 100% !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  .hero-image-carousel .col-lg-6:last-child {
    margin-top: .5rem;
  }

  /* .carousel-item.active (etc.) is still display:flex; align-items:center
   * from the desktop side-by-side layout further up. Keep it flex, but
   * align content to the top so the text column sits flush at the top
   * of whatever height the item ends up with - needed below now that a
   * min-height is back (see next rule): centering would split any extra
   * room above AND below the text, which is exactly the bug being fixed
   * (dead background space between the image and the section's edge). */
  .hero-image-carousel .carousel-item.active,
  .hero-image-carousel .carousel-item-next,
  .hero-image-carousel .carousel-item-prev {
    align-items: flex-start !important;
  }

  /* The robot needs real vertical canvas to bleed into below the text -
   * a min-height floor gives it that room (same role min-height plays
   * on desktop), safe now that align-items:flex-start (above) keeps the
   * text pinned to the top instead of centered into the extra space. */
  .hero-image-carousel,
  .hero-image-carousel .carousel-inner,
  .hero-image-carousel .carousel-item {
    min-height: 440px !important; /* was 400px - a bit shorter than desktop, per feedback */
  }

  /* Text column renders above the robot layer (z-index), since the robot
   * is now allowed to rise up behind the heading/CTA rather than being
   * confined to its own fenced-off crop window below the text - per
   * feedback, overlap there is fine as long as the buttons stay legible
   * (Bootstrap's own solid button background already guarantees that). */
  .hero-image-carousel .col-lg-6:first-child,
  .hero-image-carousel .carousel-item .col-lg-6.order-lg-2 {
    position: relative;
    z-index: 2;
  }
  .hero-image-carousel .col-lg-6:last-child {
    margin-top: 0;
  }
  /* The domain-search slide's TLD marquee sits at the bottom of the text
   * column - the same area the robot's bottom-anchored bleed concentrates
   * in, especially once this slide's extra content (search field +
   * marquee) makes the item taller than the others and the robot's
   * bottom:0-anchored wrapper grows to match. The ancestor column above
   * already carries the higher z-index, but give the marquee (and its
   * wrapper) their own explicit position+z-index too, higher again, so
   * the pills unambiguously win regardless of any nested-stacking-context
   * subtlety rather than relying only on inheriting the column's. */
  .hero-image-carousel .domain-tld-live,
  .hero-image-carousel .domain-tld-live-marquee,
  .hero-image-carousel .tld-marquee {
    position: relative !important;
    z-index: 3 !important;
  }

  /* Robots (not the device photo, see below): reproduce the EXACT desktop
   * mechanism instead of a flow-fitted crop window - a wrapper spanning
   * the full carousel-item height (top:0 AND bottom:0, not bottom-only)
   * gives the image a known, fixed canvas to bleed against, and a large
   * negative `bottom` offset on the image (sized to the section's own
   * 4rem/64px padding) pushes it past that padding so it gets clipped
   * flush at the section's TRUE outer edge by .hero-image-style's
   * overflow:hidden - not at some smaller inner box, which is what left
   * a visible gap of background before the real edge. Centered instead
   * of right-anchored since text is now full-width above it, not beside
   * it. */
  .hero-carousel-robot,
  .hero-carousel-robot-left {
    position: absolute !important;
    left: 0 !important;
    right: 0 !important;
    top: 0 !important;
    bottom: 0 !important;
    width: 100% !important;
    height: auto !important;
    overflow: visible !important;
    display: block !important;
    z-index: 1;
    pointer-events: none;
  }
  .hero-carousel-robot img,
  .hero-carousel-robot-left img {
    position: absolute !important;
    left: 50% !important;
    right: auto !important;
    top: auto !important;
    /* -64px (the section's own 4rem padding) turned out far too small
     * relative to how tall these images actually render at 360px wide -
     * almost the whole character stayed visible, rising well up into the
     * text instead of just peeking from below. -564px (500px further)
     * was the first correction; raised 100px higher again per feedback
     * to -464px. */
    bottom: -464px !important;
    width: 360px !important; /* 240 * 1.5, per feedback */
    max-width: 98% !important; /* safety net for <=340px-wide phones (iPhone SE/5) so this can't overflow the viewport sideways */
    height: auto !important;
    margin: 0;
    filter: drop-shadow(0 14px 24px rgba(0, 0, 0, 0.08));
    animation: heroCarouselMobileRise 1s ease-out both;
  }
  /* Rocket: already "bigger, positioned higher to show more" on desktop
   * (-250px vs the other robots' -600px) - needs a proportionally
   * smaller push-down here too. -364px (300px further) was the first
   * correction; raised 50px higher again per feedback to -314px. */
  .hero-carousel-robot img[src*="rocket"] {
    bottom: -314px !important;
  }
  /* Hello (slide 1): shifted right instead of centered so the antenna
   * doesn't rise up through the left-aligned heading/lead text - still
   * self-centers on that point via the shared entrance animation's
   * translateX(-50%). Confirmed working, kept independent of the
   * min-height/height-parity change that was reverted alongside it. */
  .hero-carousel-robot img[src*="robot_hello"] {
    left: 78% !important;
  }
  @keyframes heroCarouselMobileRise {
    0% { transform: translateX(-50%) translateY(60px); opacity: 0; }
    100% { transform: translateX(-50%) translateY(0); opacity: 1; }
  }

  /* Device screenshot (kotisivukone slide): its wrapper already carries
   * BOTH .hero-carousel-robot and .hero-carousel-photo (see the markup
   * comment above .hero-carousel-photo img's desktop rule), so it
   * inherits the exact same absolute/bleed mechanism as the robots above
   * for free - same zero-flow-footprint (fixing the height-parity issue
   * a flow-based version had) and the same cropped/peeking look, just
   * with its own width/offset tuned to this image's much wider (1174x685)
   * aspect ratio instead of the robots' tall-character one. */
  .hero-carousel-photo img {
    left: 50% !important;
    bottom: -190px !important; /* -90px was too high; 100px further down per feedback */
    width: 360px !important;
  }
  /* /sahkoposti's photo slide is a different image (cropped_macbook_webmail.png,
   * 1000x510 - flatter than kotisivukone's 1174x685), so at the same 360px
   * width its natural height (~184px) is shorter - the shared -190px offset
   * above would push almost the whole thing out of view. Scaled to leave the
   * same ~20px peek kotisivukone gets. */
  .hero-carousel-photo img[src*="webmail"] {
    bottom: -165px !important;
  }

  /* CTA buttons (slides 1/3/4/5's button pairs - slide 2's domain-search
   * row already uses regular-size buttons and is unaffected): shrink
   * from btn-lg down to plain .btn size so both buttons fit on one line
   * instead of wrapping. Overriding the Bootstrap 5 CSS custom
   * properties that .btn-lg sets (rather than the HTML class) keeps this
   * scoped to the hero at this breakpoint only - desktop/tablet keep the
   * real btn-lg size. */
  .hero-image-carousel .btn-lg {
    --bs-btn-padding-y: 0.375rem;
    --bs-btn-padding-x: 0.75rem;
    --bs-btn-font-size: 1rem;
  }
  .hero-image-carousel .d-flex.gap-3 {
    gap: 0.5rem !important;
  }
}

/* ===== Eyebrow Text (Non-Heading Decorative Text) =====
 * USAGE: <p class="eyebrow">Label text</p>
 * RULE: Use <p> tag, NOT heading tags (eyebrows are decorative, not structural)
 * PURPOSE: Small uppercase labels above headings ("Kenelle?", "Miksi?")
 * VARIANTS:
 *   .eyebrow (default gray)
 *   .eyebrow-blue (blue - for emphasis)
 *   .eyebrow-dot (with dot before text)
 *   .eyebrow-line (with line before text)
 */
.eyebrow {
  font-size: 0.8125rem;        /* 13px */
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.15em;       /* Wider for modern look */
  color: var(--text-light);     /* Default gray */
  margin-bottom: 0.75rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* Blue eyebrow - use for emphasis (Orbit-style) */
.eyebrow-blue {
  color: var(--blue-light);     /* Light blue from robot SVG */
}

/* Eyebrow with dot accent */
.eyebrow-dot::before {
  content: '';
  width: 8px;
  height: 8px;
  background: var(--blue-light);
  border-radius: 50%;
}

/* Eyebrow with line accent */
.eyebrow-line::before {
  content: '';
  width: 30px;
  height: 2px;
  background: var(--blue-light);
}

/* Service Box / "What We Do" */
.service-box {
  padding: 2.5rem 2rem;
  border-radius: var(--radius-lg);
  background: var(--bg-white);
  box-shadow: var(--shadow-sm);
  transition: all 0.3s ease;
  height: 100%;
  border: 1px solid transparent;
  text-align: left;
}

.service-box:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-hover);
  border-color: rgba(63, 120, 224, 0.1);
}

.service-icon {
  width: 50px;
  height: 50px;
  margin-bottom: 1.25rem;
}

.service-box h4 { margin-bottom: 0.75rem; }
.service-box p { margin-bottom: 0; }

/* Case Studies / Projects */
.project-card {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-md);
  margin-bottom: 2rem;
  transition: transform 0.3s ease;
  background: white;
}

.project-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-hover); }

.project-img-wrapper {
  height: 220px;
  overflow: hidden;
  background-color: var(--bg-pale);
}

.project-img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; }
.project-card:hover .project-img { transform: scale(1.05); }

.project-details { padding: 1.5rem; }
.project-category { font-size: 0.75rem; text-transform: uppercase; color: var(--primary); font-weight: 700; margin-bottom: 0.25rem; }
.project-title { font-size: 1.15rem; margin-bottom: 0; }

/* Pricing Cards (Refined) */
.pricing-card {
  background: white;
  border-radius: var(--radius-lg);
  padding: 3rem 2rem;
  box-shadow: var(--shadow-md);
  transition: all 0.3s ease;
  border: 1px solid rgba(164, 174, 198, 0.3);  /* Gray border by default */
  text-align: center;
}

.pricing-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-hover); }
.pricing-card.featured { border: 2px solid var(--primary); position: relative; overflow: hidden; }
.pricing-price { font-size: 3rem; font-weight: 800; color: var(--text-dark); margin: 1.5rem 0 0.5rem; line-height: 1; }
.pricing-features li { padding: 0.5rem 0; }

/* Stats */
.counter-wrapper { text-align: center; padding: 2rem 0; }
.counter { font-size: 2.5rem; font-weight: 800; color: var(--primary); line-height: 1; margin-bottom: 0.5rem; }
.counter-label { font-size: 0.9rem; color: var(--text-body); font-weight: 600; }

/* ========================================
 * FOOTER
 * ========================================
 * Dark footer with 4-column layout
 * Inspired by Sandbox demo14/demo27
 * All typography uses base styles (NO font-size overrides)
 */

/* ----- Footer Container ----- */
.footer {
  background: linear-gradient(180deg, #003560 0%, var(--text-dark) 100%);  /* Subtle gradient */
  color: white;
  padding: 5rem 0 2rem;
}

/* ----- Footer Logo ----- */
.footer-logo {
  max-width: 140px;
  margin-bottom: 1.5rem;
  /* No filter - using dedicated footer SVG with white/light blue */
}

.footer-tagline {
  color: rgba(255, 255, 255, 0.6);
  margin-bottom: 0;
  /* Font size inherited from body (17px) */
}

/* ----- Footer Headings ----- */
.footer h6 {
  color: white;
  margin-bottom: 1.5rem;
  /* Size/weight inherited from H6: 18px ExtraBold */
}

/* A linked footer heading (e.g. "Ratkaisut" -> /ratkaisut) must not fall back
   to the browser's default blue/underlined link color. */
.footer h6 a {
  color: inherit;
  text-decoration: none;
  transition: color 0.3s ease;
}
.footer h6 a:hover {
  color: rgba(255, 255, 255, 0.75);
}

/* ----- Footer Links ----- */
.footer-links {
  list-style: none;
  padding: 0;
  margin: 0;
}

.footer-links li {
  margin-bottom: 0.5rem;
  line-height: 1.4;
}

.footer-links a {
  color: rgba(255, 255, 255, 0.6);
  text-decoration: none;
  transition: color 0.3s ease;
  /* Font size inherited from body (17px) */
}

.footer-links a:hover {
  color: white;
}

/* ----- Contact Info ----- */
.footer-contact {
  color: rgba(255, 255, 255, 0.6);
}

.footer-contact a {
  color: rgba(255, 255, 255, 0.6);
  text-decoration: none;
  transition: color 0.3s ease;
}

.footer-contact a:hover {
  color: white;
}

/* ----- Social Icons ----- */
.social-icons {
  display: flex;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.social-icon {
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 50%;
  color: white;
  text-decoration: none;
  transition: all 0.3s ease;
  font-size: 0.875rem;
}

.social-icon:hover {
  background: rgba(255, 255, 255, 0.15);
  transform: translateY(-3px);
  color: white;
}

/* ----- Footer Bottom Bar ----- */
.footer-bottom {
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  margin-top: 4rem;
  padding-top: 2rem;
}

.footer-bottom p {
  color: rgba(255, 255, 255, 0.5);
  margin-bottom: 0;
}

.footer-bottom-links {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.footer-bottom-links li {
  margin-bottom: 0;
}

.footer-bottom-links a {
  color: rgba(255, 255, 255, 0.5);
  text-decoration: none;
  transition: color 0.3s ease;
}

.footer-bottom-links a:hover {
  color: rgba(255, 255, 255, 0.8);
}

/* Accessibility: Skip to content */
.skip-to-content {
  position: absolute;
  top: -100px;
  left: 10px;
  background: var(--primary);
  color: white;
  padding: 10px 15px;
  text-decoration: none;
  z-index: 2000; /* Above navbar */
  transition: top 0.3s ease;
  border-radius: 4px;
  font-weight: 600;
}
.skip-to-content:focus {
  top: 10px;
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* Animations */
@keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
@keyframes sunsetRise {
  0% {
    opacity: 0;
    transform: scaleY(0.1) translateY(40%);
  }
  40% {
    opacity: 0.8;
  }
  100% {
    opacity: 1;
    transform: scaleY(1) translateY(0);
  }
}
.fade-in-section { opacity: 0; transform: translateY(30px); transition: opacity 0.8s ease-out, transform 0.8s ease-out; }
.fade-in-section.is-visible { opacity: 1; transform: translateY(0); }

/* Disable fade-in animation for carousel items */
.carousel-item h1,
.carousel-item .lead,
.carousel-item .btn {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
}

/* Responsive */
@media (max-width: 991px) {
  body { padding-top: 70px; }
  h1 { font-size: 2.5rem; }
  h2 { font-size: 2.25rem; }  /* 36px, down from 48px - h1 already scales here, h2/h3 didn't */
  h3 { font-size: 1.875rem; } /* 30px, down from 38px */

  /* Tablet: Fixed carousel height */
  .hero .carousel-inner {
    min-height: 600px;
  }
  .hero .carousel-item {
    min-height: 600px;
  }
  
  /* Smaller robots on tablet (70% of desktop) */
  .hero-robot {
    max-width: 182px; /* 70% of 260px */
  }
  .hero-robot[src*="rocket"] {
    max-width: 245px; /* 70% of 350px */
  }
}

@media (max-width: 768px) {
  h1 { font-size: 2rem; }
  h2 { font-size: 1.75rem; }  /* 28px, down from 48px - still clearly bigger than body/lead text */
  h3 { font-size: 1.5rem; }   /* 24px, down from 38px */
  .hero { text-align: center; padding: 1rem 0 2rem; }
  
  /* Mobile: Fixed carousel height (taller to prevent jumping) */
  .hero .carousel-inner {
    min-height: 650px;
  }
  .hero .carousel-item {
    min-height: 650px;
  }
  
  /* Smaller robots on mobile (70% of desktop, except rocket stays same) */
  .hero-robot {
    margin-top: 2rem;
    max-width: 182px; /* 70% of 260px */
  }
  .hero-robot[src*="rocket"] {
    max-width: 350px; /* Keep rocket at full size on mobile */
  }
}

/* Utilities */
.text-primary { color: var(--primary) !important; }
.bg-pale { background-color: var(--bg-pale); }
.bg-soft { background-color: var(--bg-soft); }

/* ========================================
 * CONTENT BLOCKS - ORGANIZED BY BLOCK TYPE
 * ========================================
 * 
 * RULES:
 * - Each block has its own section with clear header comment
 * - Only visual/layout CSS here (NO typography font-size overrides)
 * - Typography uses Bootstrap classes in HTML
 * - Structure: padding, margin, borders, shadows, backgrounds, transitions
 */

/* ===== BLOCK: Feature Cards (3-column grid with icons) ===== */
.feature-card {
  padding: 2rem;
  text-align: center;
  transition: transform 0.3s ease;
}

.feature-card:hover {
  transform: translateY(-5px);
}

.feature-icon {
  width: 120px;
  height: 120px;
  margin: 0 auto 1.5rem;
}

/* ===== BLOCK: Pricing Cards (3-column comparison) ===== */
.pricing-card-v2 {
  background: white;
  border-radius: var(--radius-lg);
  padding: 3rem 2rem;
  box-shadow: var(--shadow-md);
  transition: all 0.3s ease;
  border: 1px solid rgba(164, 174, 198, 0.3); /* Gray border for all cards */
  text-align: center;
  height: 100%;
}

.pricing-card-v2:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-hover);
}

.pricing-card-v2.featured {
  border: 2px solid var(--primary);
  position: relative;
  overflow: hidden;
}

.pricing-badge {
  position: absolute;
  top: 1rem;
  right: 1rem;
  background: var(--primary);
  color: white;
  padding: 0.375rem 0.875rem;
  border-radius: 2rem;
}

.pricing-price-display {
  margin: 2rem 0 1rem;
}

.pricing-amount {
  font-size: 3rem;
  font-weight: 800;
  line-height: 1;
  color: var(--text-dark);
}

.pricing-features-list {
  list-style: none;
  padding: 0;
  margin: 2rem 0;
  text-align: left;
}

.pricing-features-list li {
  padding: 0.625rem 0;
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}

.pricing-features-list svg {
  flex-shrink: 0;
  margin-top: 0.25rem;
}

/* ===== BLOCK: CTA Banner (full-width call-to-action) ===== */
.cta-banner {
  background: linear-gradient(135deg, #003560 0%, var(--primary) 50%, var(--primary-hover) 100%);
  border-radius: var(--radius-lg);
  padding: 4rem 3rem;
  text-align: center;
  color: white;
  position: relative;
  overflow: hidden;
}

.cta-banner::before {
  content: '';
  position: absolute;
  top: -50%;
  right: -10%;
  width: 300px;
  height: 300px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 50%;
  pointer-events: none;
}

.cta-banner::after {
  content: '';
  position: absolute;
  bottom: -30%;
  left: -5%;
  width: 200px;
  height: 200px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 50%;
  pointer-events: none;
}

.cta-banner > * {
  position: relative;
  z-index: 1;
}

/* ===== BLOCK: Stats Row (key numbers) ===== */
.stats-item {
  padding: 1.5rem;
}

.stats-number {
  font-size: 3.5rem;
  font-weight: 800;
  line-height: 1;
  color: var(--primary);
  margin-bottom: 0.5rem;
}

.stats-label {
  color: var(--text-body);
  margin-bottom: 0;
  /* Font size inherited from body (17px) */
}

/* ===== BLOCK: Testimonial Cards ===== */
.testimonial-card {
  background: white;
  padding: 2.5rem 2rem;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: all 0.3s ease;
  height: 100%;
  border: 1px solid transparent;
}

.testimonial-card:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-hover);
  border-color: rgba(92, 119, 255, 0.1);
}

.testimonial-stars {
  font-size: 1.25rem;
}

.testimonial-quote {
  font-style: italic;
  /* Font size inherited from body (17px) */
}

.testimonial-author h6 {
  /* Inherits H6 styles: 18px ExtraBold */
}

/* ===== BLOCK: Trust Badges / Logo Grid ===== */
.trust-badge {
  padding: 1rem;
  transition: opacity 0.3s ease;
}

.trust-badge:hover {
  opacity: 0.7;
}

.trust-badge-placeholder {
  padding: 2rem 1rem;
  background: rgba(52, 63, 82, 0.03);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 80px;
}

/* Logo marquee */
.logo-marquee {
  overflow: hidden;
  position: relative;
  display: flex;
  gap: 0;
  /* Fade edges for smooth scroll */
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, black 6%, black 94%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, black 6%, black 94%, transparent 100%);
}

.logo-marquee-track {
  display: flex;
  gap: 2.5rem;
  align-items: center;
  flex-shrink: 0;
  animation: logoMarquee 26s linear infinite;
}
.logo-item {
  padding: 0 1.25rem;
  display: flex;
  align-items: center;
}

.logo-item img {
  height: 64px;
  width: auto;
  opacity: 0.9;
  filter: grayscale(5%);
  transition: opacity 0.3s ease;
  display: block;
}

.logo-item img:hover { opacity: 1; filter: none; }
.logo-item img[alt="UpCloud"] { margin: 0; padding: 0; }
.logo-item img[alt="Hetzner"] {
  height: 52px;
  padding: 0 0.15rem;
}

.logo-item img[alt="Nginx"] {
  height: 83px; /* +30% */
  transform: scale(1.08);
}

.logo-item img[alt="Plesk"] {
  height: 90px; /* +40% */
  transform: scale(1.06);
}

@keyframes logoMarquee {
  from { transform: translateX(0); }
  to { transform: translateX(-100%); }
}

/* ===== BLOCK: FAQ Accordion (Bootstrap override) ===== */
.accordion-item {
  border: none;
  border-bottom: 1px solid rgba(164, 174, 198, 0.15);
  background: transparent;
}

.accordion-item:last-child {
  border-bottom: none;
}

.accordion-button {
  background: transparent;
  border: none;
  box-shadow: none;
  padding: 1.5rem 0;
  width: 100%;  /* Fill horizontal space */
  /* H6 styling for questions */
  font-family: 'Raleway', sans-serif;
  font-size: 1.25rem;  /* Bigger than default */
  font-weight: 700;
  color: var(--text-dark);
}

.accordion-button:not(.collapsed) {
  background: transparent;
  color: var(--primary);
  box-shadow: none;
}

.accordion-button:focus {
  box-shadow: none;
  border-color: transparent;
}

.accordion-button::after {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23343f52'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}

.accordion-body {
  padding: 0 0 1.5rem 0;
  /* Body text inherits paragraph style: 17px */
}

/* ===== BLOCK: Stats Panel with Icon (Orbit-inspired) =====
 * 4-column stats with rounded colored icons
 * Usage: <div class="stat-panel">...</div>
 */
.stat-panel {
  text-align: center;
  padding: 2rem 1rem;
}

.stat-icon {
  width: 70px;
  height: 70px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 1.25rem;
  font-size: 1.75rem;
  color: white;
}

/* Icon color variants */
.stat-icon-blue { background: var(--blue-light); }
.stat-icon-dark { background: var(--blue-dark); }
.stat-icon-primary { background: var(--primary); }
.stat-icon-green { background: var(--accent); }

.stat-number {
  font-size: 2.5rem;
  font-weight: 800;
  line-height: 1;
  color: var(--text-dark);
  margin-bottom: 0.5rem;
}

.stat-label {
  color: var(--text-light);
  margin-bottom: 0;
}

/* Stat Panel as a clickable card (contact methods on /yhteystiedot etc.) -
   same icon/label proportions as the plain stat-panel above, wrapped in a
   soft-card-style clickable surface so tel:/mailto:/chat/modal triggers
   read as one obviously-clickable tile instead of bare centered text. */
.stat-panel-link {
  background: white;
  border-radius: 1.5rem;
  padding: 2rem 1.25rem;
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.05);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
  color: inherit;
  cursor: pointer;
}

.stat-panel-link:hover,
.stat-panel-link:focus-visible {
  transform: translateY(-4px);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.09);
  color: inherit;
}

/* Stat "number" reused for non-numeric contact values (phone/email/etc.) -
   smaller and word-breaking so a long email address or number never
   overflows the narrow card column like the old plain-text version did. */
.stat-number-text {
  font-size: 1.35rem;
  font-weight: 700;
  word-break: break-word;
}

/* ===== BLOCK: Icon Text Row (Sandbox-inspired) =====
 * Icon aligned left with text - "Can I cancel?" style
 * Usage: <div class="icon-text-item">...</div>
 */
.icon-text-item {
  display: flex;
  gap: 1.25rem;
  padding: 1.5rem 0;
  border-bottom: 1px solid rgba(164, 174, 198, 0.15);
}

.icon-text-item:last-child {
  border-bottom: none;
}

.icon-text-icon {
  width: 48px;
  height: 48px;
  min-width: 48px;
  border-radius: 50%;
  background: var(--soft-blue);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  color: var(--blue-dark);
}

.icon-text-content h5 {
  margin-bottom: 0.5rem;
}

.icon-text-content p {
  margin-bottom: 0;
  color: var(--text-body);
}

/* ===== BLOCK: Simple CTA (Sandbox-inspired) =====
 * Clean, minimal CTA with bold heading + button
 * Usage: <div class="cta-simple">...</div>
 */
.cta-simple {
  background: var(--bg-pale);
  border-radius: var(--radius-lg);
  padding: 3rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.5rem;
}

@media (max-width: 768px) {
  .cta-simple {
    flex-direction: column;
    text-align: center;
  }
}

.cta-simple h3 {
  margin-bottom: 0;
}

/* ===== BLOCK: Price Display (Sandbox-inspired) =====
 * Large euro amount with small cents
 * Usage: <div class="price-display">...</div>
 */
.price-display {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  line-height: 1;
  letter-spacing: -0.03em; /* Tighter spacing */
}

.price-currency {
  font-family: 'Montserrat', sans-serif;
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text-dark);
  margin-top: 0.5rem;
}

.price-amount {
  font-family: 'Montserrat', sans-serif;
  font-size: 4rem;
  font-weight: 800;
  color: var(--text-dark);
  letter-spacing: -0.04em; /* Even tighter for big numbers */
}

.price-cents {
  font-family: 'Montserrat', sans-serif;
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text-dark);
  margin-top: 0.5rem;
}

.price-period {
  font-size: 1rem;
  color: var(--text-light);
  margin-left: 0.25rem;
  margin-top: auto;
  margin-bottom: 0.75rem;
}

/* ===== BLOCK: Service Grid (Orbit-inspired) =====
 * 6-panel services grid with icon and text
 */
.service-grid-item {
  background: white;
  border-radius: var(--radius-lg);
  padding: 2rem;
  box-shadow: var(--shadow-sm);
  transition: all 0.3s ease;
  height: 100%;
  border: 1px solid transparent;
}

.service-grid-item:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-hover);
  border-color: rgba(39, 170, 225, 0.2); /* blue-light tint */
}

.service-grid-icon {
  width: auto;
  height: auto;
  border-radius: 0;
  background: transparent;
  font-size: 3rem;
  color: var(--blue-dark);
  margin-bottom: 1rem;
}

/* ===== Background Utility: Light Blue =====
 * Use for sections that need light blue background (Orbit-style)
 */
.bg-blue-light {
  background: var(--bg-blue-light);
}

/* Light blue gradient hero background */
.bg-gradient-blue {
  background: var(--gradient-hero);
}

/* ===== BLOCK: Gradient CTA (Sandbox-inspired "Sunset" style) =====
 * Radial gradient background - warm orange fading to blue then white
 * Usage: <section class="cta-gradient">...</section>
 */
.cta-gradient {
  position: relative;
  padding: 6rem 0;
  overflow: hidden;
  /* Multi-stop radial gradient: orange center -> blue -> pale edges */
  background: radial-gradient(
    ellipse 80% 100% at 50% 120%,
    rgba(255, 180, 100, 0.4) 0%,
    rgba(255, 140, 60, 0.25) 20%,
    rgba(39, 170, 225, 0.2) 45%,
    rgba(4, 123, 193, 0.1) 65%,
    rgba(246, 248, 250, 0.8) 100%
  ),
  linear-gradient(180deg, #f6f8fa 0%, #ffffff 100%);
}

.cta-gradient .container {
  position: relative;
  z-index: 1;
}

.cta-gradient h2 {
  font-size: 3.5rem;
  font-weight: 700;
  margin-bottom: 1.5rem;
}

.cta-gradient .lead {
  font-size: 1.25rem;
  max-width: 600px;
  margin: 0 auto 2rem;
}

@media (max-width: 768px) {
  .cta-gradient h2 {
    font-size: 2.5rem;
  }
  .cta-gradient {
    padding: 4rem 0;
  }
}

/* Robot positioned absolutely within the gradient section */
.cta-gradient {
  position: relative; /* For absolute positioning of robot */
  padding-top: 3rem; /* Less top padding - content higher */
  padding-bottom: 280px; /* More space below button for robot */
}

.cta-gradient-robot {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 400px;
  height: 280px; /* Match bottom padding */
  overflow: visible; /* Allow robot to rise without clipping */
  z-index: 0; /* Keep behind content */
  pointer-events: none; /* Don't block clicks */
}

.cta-gradient-robot img {
  position: absolute;
  bottom: -320px; /* Much lower - only top of head peeks */
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 400px;
  animation: robotRise 3s ease-out forwards;
  animation-play-state: paused;
  opacity: 0;
}

/* Taller CTA variant (used on /webhotellit) */
.cta-gradient.cta-gradient-tall {
  padding-bottom: 280px; /* Match standard CTA */
}

.cta-gradient.cta-gradient-tall .cta-gradient-robot {
  height: 280px; /* Match standard CTA */
  overflow: visible; /* Allow robot to slide up without clipping */
  z-index: 0;
}

.cta-gradient.cta-gradient-tall .cta-gradient-robot img {
  bottom: -320px; /* Match standard CTA */
  animation: robotRise 3s ease-out forwards;
}

@keyframes robotRise {
  0% {
    bottom: -450px;
    opacity: 0;
  }
  100% {
    bottom: -320px; /* Final position - much lower */
    opacity: 0.9;
  }
}

/* robotRiseTall unused after matching standard CTA */

/* Raise-150 variant: rises 150px higher than the standard tall-CTA peek
   (-170px final instead of -320px). The tall-CTA rule above sets `animation`
   as a shorthand (which resets animation-name too) at higher specificity
   than a plain .cta-gradient-robot.raise-150 selector, so this needs the
   same .cta-gradient.cta-gradient-tall ancestor prefix to actually win. */
.cta-gradient.cta-gradient-tall .cta-gradient-robot.raise-150 img {
  animation-name: robotRiseRaised150;
}
@keyframes robotRiseRaised150 {
  0% { bottom: -450px; opacity: 0; }
  100% { bottom: -170px; opacity: 0.9; }
}

/* Left-aligned robot variant */
.cta-gradient-robot.left {
  left: 0;
  transform: none;
}

.cta-gradient-robot.left img {
  left: -50px;
  transform: none;
}

/* Right-aligned robot variant - mirror of .left */
.cta-gradient-robot.right {
  left: auto;
  right: 0;
  transform: none;
}

.cta-gradient-robot.right img {
  left: auto;
  right: -50px;
  transform: none;
}

/* Big-lift variant - 2x size, rises ~500px higher than the standard peek so
   it's actually visible instead of just the top sliver of its head. Pair
   with .cta-gradient-tall.compact to reclaim some of the extra bottom
   padding the standard peek needed but this variant doesn't. */
.cta-gradient-robot.big-lift {
  width: 700px;
  right: -120px; /* push further right so it clears this CTA's centered text instead of overlapping it */
}
.cta-gradient-robot.big-lift img {
  max-width: 700px;
  animation-name: robotRiseBig;
}
@keyframes robotRiseBig {
  0% { bottom: -50px; opacity: 0; }
  100% { bottom: 80px; opacity: 0.9; }
}
/* Slightly bigger on large screens, smaller on narrow ones - narrow viewports
   have less horizontal room before the centered text and the robot collide. */
@media (min-width: 1400px) {
  .cta-gradient-robot.big-lift { width: 780px; }
  .cta-gradient-robot.big-lift img { max-width: 780px; }
}
@media (max-width: 991px) {
  .cta-gradient-robot.big-lift { width: 560px; right: -90px; }
  .cta-gradient-robot.big-lift img { max-width: 560px; }
}
@media (max-width: 768px) {
  .cta-gradient-robot.big-lift { width: 400px; right: -60px; }
  .cta-gradient-robot.big-lift img { max-width: 400px; }
}
.cta-gradient-tall.compact {
  padding-bottom: 196px; /* 30% less than the standard 280px */
}

/* Raise-150 variant - stops the standard peek 150px higher than usual.
 * Use on a .left/.right robot when the CTA's own copy is short enough that
 * the standard low peek would overlap the button row underneath it. */
.cta-gradient-robot.raise-150 img {
  animation-name: robotRiseRaised150;
}
@keyframes robotRiseRaised150 {
  0% { bottom: -450px; opacity: 0; }
  100% { bottom: -170px; opacity: 0.9; }
}

/* Raise-300 variant - stops the standard peek 300px higher than usual
 * (150px higher still than .raise-150). */
.cta-gradient-robot.raise-300 img {
  animation-name: robotRiseRaised300;
}
@keyframes robotRiseRaised300 {
  0% { bottom: -450px; opacity: 0; }
  100% { bottom: -20px; opacity: 0.9; }
}

/* Raise-500 variant - stops the standard peek 500px higher than usual
 * (200px higher still than .raise-300). */
.cta-gradient-robot.raise-500 img {
  animation-name: robotRiseRaised500;
}
@keyframes robotRiseRaised500 {
  0% { bottom: -450px; opacity: 0; }
  100% { bottom: 180px; opacity: 0.9; }
}

/* ===== Animated Counter Numbers =====
 * For stats blocks - numbers count up on scroll
 */
.stat-number[data-count] {
  /* Base styling already defined in .stat-number */
}

/* ===== BLOCK: Hero Image Style (Sandbox demo8-inspired) =====
 * Hero with robot/image, sunset gradient on whole area, floating panel
 * Usage: <section class="hero-image-style">...</section>
 */
.hero-image-style {
  /* Base background - sunset gradient animates in via ::before */
  background: linear-gradient(180deg, #f6f8fa 0%, #ffffff 100%);
  padding: 4rem 0;
  margin: 0 auto; /* Center the section */
  max-width: 1320px; /* Page width - matches Bootstrap container-xl */
  position: relative;
  overflow: hidden; /* Clip robot rising */
  border-radius: var(--radius-xl); /* Rounded corners */
}

/* Animated sunset gradient - rises on page load */
.hero-image-style::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    ellipse 130% 90% at 50% 110%,
    rgba(255, 160, 60, 0.65) 0%,
    rgba(255, 120, 40, 0.45) 15%,
    rgba(255, 180, 100, 0.3) 30%,
    rgba(39, 170, 225, 0.2) 50%,
    rgba(4, 123, 193, 0.08) 70%,
    transparent 100%
  );
  transform-origin: bottom center;
  animation: sunsetRise 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  z-index: 0;
  pointer-events: none;
}

.hero-image-style .container {
  max-width: 1320px; /* Match homepage hero width */
  padding-left: 3rem;
  padding-right: 2rem;
  position: relative;
  z-index: 2; /* Above robot */
}

/* Image wrapper - full height of section, no padding constraints */
/* IMPORTANT - two independent hero-image systems share this one wrapper:
 * 1. Robot SVGs (img/robots/*.svg, plain <img>, no attribute selector) -
 *    THIS rule + heroRobotRise below. Tall character shape, consistent
 *    proportions across the whole family.
 * 2. "illustration_*" SVGs (img/illustrations/illustration_*.svg only -
 *    NOT the same as img/illustrations/undraw_*.svg used elsewhere on the
 *    site, those aren't hero images) - see the separate
 *    `.hero-image-wrapper img[src*="illustration_"]` rule + heroIllustration
 *    keyframe further down this file. Wildly inconsistent aspect ratios
 *    (square/landscape/tall-narrow - checked all 5 files directly), so it
 *    deliberately uses its own width/reveal-height baseline rather than the
 *    robot system's character-specific tuning - confirmed intentional, not
 *    accidental duplication, do not merge the two.
 *
 * Consequence: a raise-N/lower-N modifier class written for ONE system
 * (different animation-name, different keyframe, different base bottom
 * value) does nothing on a hero using the OTHER system - confirmed by a
 * real bug (2026-09-30): /managed-cloud and /reseller-cloud use
 * illustration_* heroes, but were given the robot system's own raise-200/
 * lower-200 modifier classes, which only match plain `img` (no
 * illustration_ in src) and so silently never applied. Before adding a
 * new raise-N/lower-N variant, check which system the target hero
 * actually uses (grep the page's own <img src=...> in its
 * .hero-image-wrapper) - the illustration system's own modifiers are
 * named illustration-raise-N/illustration-lower-N specifically to avoid
 * this ambiguity going forward (see illustration-lower-250 below - add a
 * matching illustration-raise-N the same way if a page needs one, no
 * raise variant exists yet since none has needed it); the robot system's
 * older modifiers
 * (raise-100/150/180, lower-100/200) predate that convention and were
 * left as-is rather than renamed (would touch every page using them).
 */
.hero-image-wrapper {
  position: absolute;
  bottom: 0;
  top: 0; /* Full height from top to bottom */
  right: 0;
  width: 50%; /* Right half of section */
  display: flex;
  justify-content: flex-end;
  align-items: flex-end;
  overflow: visible; /* Allow robot to be visible throughout */
  pointer-events: none;
}

/* Hide the circle element - sunset is now on whole section */
.hero-image-circle {
  display: none;
}

.hero-image-wrapper img {
  position: absolute;
  bottom: -900px; /* Start position - 100px higher */
  right: -50px;
  width: 650px;
  max-width: none;
  height: auto;
  z-index: 1;
  opacity: 0;
  animation: heroRobotRise 3s ease-out forwards;
  animation-play-state: paused;
}

/* Faster reveal variant - being tried on /sahkoposti's hero first before
   considering it as the new site-wide default. */
.hero-image-wrapper.fast-reveal img {
  animation-duration: 1.5s; /* 2x faster than the 3s default */
}

@keyframes heroRobotRise {
  0% {
    bottom: -900px;
    opacity: 0;
  }
  100% {
    bottom: -600px;
    opacity: 1;
  }
}

/* Raise-100 variant: same rise, stops 100px higher (-500px final instead of
   -600px). Usage: <div class="hero-image-wrapper raise-100"> */
.hero-image-wrapper.raise-100 img {
  animation-name: heroRobotRiseRaised100;
}

@keyframes heroRobotRiseRaised100 {
  0% {
    bottom: -900px;
    opacity: 0;
  }
  100% {
    bottom: -500px;
    opacity: 1;
  }
}

/* Raise-150 variant: same rise, stops 150px higher (-450px final instead of
   -600px). Usage: <div class="hero-image-wrapper raise-150"> */
.hero-image-wrapper.raise-150 img {
  animation-name: heroRobotRiseRaised150;
}

@keyframes heroRobotRiseRaised150 {
  0% {
    bottom: -900px;
    opacity: 0;
  }
  100% {
    bottom: -450px;
    opacity: 1;
  }
}

/* Raise-180 variant: same rise, stops 180px higher (-420px final instead of
   -600px) - 30px more than raise-150. Usage: <div class="hero-image-wrapper raise-180"> */
.hero-image-wrapper.raise-180 img {
  animation-name: heroRobotRiseRaised180;
}

/* Lower-100 variant: same rise, stops 100px lower (-700px final instead of
   -600px). Usage: <div class="hero-image-wrapper lower-100"> */
.hero-image-wrapper.lower-100 img {
  animation-name: heroRobotRiseLowered100;
}

@keyframes heroRobotRiseLowered100 {
  0% {
    bottom: -900px;
    opacity: 0;
  }
  100% {
    bottom: -700px;
    opacity: 1;
  }
}

/* Lower-200 variant: same rise, stops 200px lower (-800px final instead of
   -600px). Usage: <div class="hero-image-wrapper lower-200"> */
.hero-image-wrapper.lower-200 img {
  animation-name: heroRobotRiseLowered200;
}

@keyframes heroRobotRiseLowered200 {
  0% {
    bottom: -900px;
    opacity: 0;
  }
  100% {
    bottom: -800px;
    opacity: 1;
  }
}

/* Raise-200 variant: same rise, stops 200px higher (-400px final instead of
   -600px). Usage: <div class="hero-image-wrapper raise-200"> */
.hero-image-wrapper.raise-200 img {
  animation-name: heroRobotRiseRaised200;
}

@keyframes heroRobotRiseRaised200 {
  0% {
    bottom: -900px;
    opacity: 0;
  }
  100% {
    bottom: -400px;
    opacity: 1;
  }
}

@keyframes heroRobotRiseRaised180 {
  0% {
    bottom: -900px;
    opacity: 0;
  }
  100% {
    bottom: -420px;
    opacity: 1;
  }
}

/* Floating stat panel */
.floating-panel {
  position: absolute;
  background: white;
  border-radius: var(--radius-lg);
  padding: 1.25rem 1.5rem;
  box-shadow: var(--shadow-lg);
  display: flex;
  align-items: center;
  gap: 1rem;
  z-index: 2;
}

.floating-panel-icon {
  width: 50px;
  height: 50px;
  background: var(--soft-blue);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  color: var(--blue-dark);
}

.floating-panel-content {
  text-align: left;
}

.floating-panel-number {
  font-size: 1.75rem;
  font-weight: 800;
  color: var(--text-dark);
  line-height: 1;
  margin-bottom: 0.25rem;
}

.floating-panel-label {
  font-size: 0.875rem;
  color: var(--text-light);
  margin: 0;
}

/* Position variants - positioned within wrapper */
.floating-panel.top-right {
  top: -5%;
  right: 4%;
  bottom: auto;
}

.floating-panel.bottom-left {
  bottom: 2%;
  left: 5%;
}

/* Tablet: Robot scaled, text constrained, panel visible */
@media (max-width: 991px) {
  .floating-panel {
    display: flex; /* Keep panel visible on tablet */
    transform: scale(0.85); /* Slightly smaller */
  }
  .floating-panel.top-right {
    top: 2%;
    right: 2%;
  }
  .hero-image-circle {
    display: none;
  }
  /* Constrain text column so it doesn't overlap robot */
  .hero-image-style .col-lg-6:first-child {
    max-width: 60%;
    flex: 0 0 60%;
  }
  .hero-image-style {
    padding: 4rem 0; /* Match desktop */
  }
  .hero-image-wrapper {
    width: 42%;
  }
  .hero-image-wrapper img {
    width: 420px;
    right: -40px;
    bottom: -700px; /* Start lower */
  }
  @keyframes heroRobotRise {
    0% { bottom: -700px; opacity: 0; }
    100% { bottom: -430px; opacity: 1; }
  }
  .hero-image-style .container {
    padding-left: 1rem;
    padding-right: 1rem;
  }
}

/* Mobile: Large robot lurks from bottom-right, half visible */
@media (max-width: 768px) {
  .floating-panel {
    display: flex; /* Keep panel visible on mobile too */
    transform: scale(0.8);
  }
  .floating-panel.top-right {
    top: 2%;
    right: auto; /* Override - wrapper is off-screen right */
    left: 10px; /* Position in visible area of wrapper */
  }
  /* Constrain text so it doesn't overlap right-side robot */
  .hero-image-style .col-lg-6:first-child {
    max-width: 65%;
    flex: 0 0 65%;
  }
  .hero-image-style {
    padding: 4rem 0; /* Match desktop */
  }
  .hero-image-wrapper {
    position: absolute;
    bottom: 0;
    top: auto;
    left: auto;
    right: -250px; /* Half of 550px hidden off-screen right */
    transform: none;
    width: 550px;
    height: 400px;
    overflow: visible;
  }
  .hero-image-wrapper img {
    position: absolute;
    bottom: -600px; /* Start very low */
    right: auto;
    left: 0;
    transform: none;
    width: 550px; /* Nearly desktop size */
    opacity: 0;
    animation: heroRobotLurk 3s ease-out forwards;
    animation-play-state: paused;
  }
  @keyframes heroRobotLurk {
    0% { bottom: -600px; opacity: 0; }
    100% { bottom: -510px; opacity: 1; }
  }
  .hero-image-style .container {
    padding-left: 1rem;
    padding-right: 1rem;
  }
}

/* Small phones (max-width: 575px) - same reasoning/scale factor (0.72)
 * as .hero-carousel-robot's own small-phone tier above: the 768px
 * values were tuned for tablet-width phones and stay too large all the
 * way down to real small-phone widths. */
@media (max-width: 575px) {
  .hero-image-wrapper {
    right: -180px; /* -250 * 0.72 */
    width: 396px;  /* 550 * 0.72 */
    height: 288px; /* 400 * 0.72 */
  }
  .hero-image-wrapper img {
    width: 396px;
    animation: heroRobotLurkSmall 3s ease-out forwards;
  }
  @keyframes heroRobotLurkSmall {
    0% { bottom: -432px; opacity: 0; }  /* -600 * 0.72 */
    100% { bottom: -367px; opacity: 1; } /* -510 * 0.72 */
  }
}

/* Left-image mirror of Hero — Image Style, Single Card (see /blocks: "every
 * single-card use of this hero puts the image on the right... ask for a
 * left-image variant here if/when a page actually needs one" - needed for
 * /asiakaskokemuksia). Markup keeps the SAME column order as the standard
 * right-image hero (text column first, image column second) so the
 * existing tablet/mobile ":first-child" text-width rules above keep
 * working unchanged - only add `order-lg-2`/`order-lg-1` on the two
 * columns in the HTML to flip the visual side at desktop widths.
 * `.hero-image-wrapper-left` (combined with `.hero-image-wrapper` on the
 * same element) overrides just the side-specific positioning; sizing and
 * the rise animation are identical to the right-hand variant and stay
 * inherited from `.hero-image-wrapper` as-is. */
.hero-image-wrapper-left {
  right: auto;
  left: 0;
  justify-content: flex-start;
}
.hero-image-wrapper-left img {
  right: auto;
  left: -50px;
}

@media (max-width: 991px) {
  /* Text is still :first-child in the DOM (order-lg-* doesn't apply below
     lg), so the shared rule above already narrows it correctly - just
     push it to the right edge of the row instead of the left, to sit on
     the opposite side from the now left-anchored robot. */
  .hero-image-style-left .col-lg-6:first-child {
    margin-left: auto;
  }
  .hero-image-wrapper-left img {
    right: auto;
    left: -40px;
  }
}

@media (max-width: 768px) {
  .hero-image-style-left .col-lg-6:first-child {
    margin-left: auto;
  }
  .hero-image-wrapper-left {
    right: auto;
    left: -250px;
  }
  .hero-image-wrapper-left img {
    right: 0;
    left: auto;
  }
}

@media (max-width: 575px) {
  .hero-image-wrapper-left {
    right: auto;
    left: -180px; /* mirrors .hero-image-wrapper's own small-phone -180px */
  }
  .hero-image-wrapper-left img {
    right: 0;
    left: auto;
  }
}

/* ===== BLOCK: Trusted By Section (Sandbox demo8-inspired) =====
 * "Trusted by over X clients" with serif heading
 */
.trusted-section {
  padding: 5rem 0;
  background: white;
}

.trusted-section h3 {
  /* Uses DM Serif Display from h3 defaults */
  margin-bottom: 1.5rem;
}

/* ===== BLOCK: Who Are We / What We Do (Sandbox demo8-inspired) =====
 * Text+image section with tag list
 */
.about-section {
  padding: 5rem 0;
  padding-left: 3rem;  /* More left padding */
  background: var(--bg-pale);
  border-radius: var(--radius-xl);
  overflow: visible; /* Allow robot to overflow */
}

.about-section-image {
  border-radius: var(--radius-lg);
  overflow: visible;
  text-align: center; /* Center robot */
  padding-right: 0;
}

.about-section-image img {
  max-width: 1040px; /* 100% bigger - giant rocket robot */
  height: auto;
  display: inline-block;
  margin-left: -100px; /* Allow overflow left */
  margin-right: -100px; /* Allow overflow right */
}

/* Tag list */
.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

.tag-item {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: white;
  padding: 0.5rem 1rem;
  border-radius: 50px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text-body);
  box-shadow: var(--shadow-sm);
}

.tag-item i {
  color: var(--blue-dark);
}

/* ===== BLOCK: Single Testimonial (Sandbox demo8-inspired) =====
 * Large centered testimonial with quote
 */
.testimonial-single {
  padding: 5rem 0;
  background: white;
}

.testimonial-single-card {
  background: var(--bg-pale);
  border-radius: var(--radius-xl);
  padding: 3rem;
  text-align: center;
  max-width: 800px;
  margin: 0 auto;
}

.testimonial-single-quote {
  font-family: 'DM Serif Display', serif;
  font-size: 1.75rem;
  line-height: 1.5;
  color: var(--text-dark);
  margin-bottom: 2rem;
  padding-top: 2.5rem;  /* More space for quote mark */
  position: relative;
}

.testimonial-single-quote::before {
  content: '"';
  font-size: 4rem;
  color: var(--blue-light);
  position: absolute;
  top: 0;  /* Higher position - no overlap */
  left: 50%;
  transform: translateX(-50%);
  opacity: 0.3;
  line-height: 1;
}

.testimonial-single-author {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
}

.testimonial-single-avatar {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--blue-light);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  color: white;
  font-weight: 700;
}

.testimonial-single-info h6 {
  margin-bottom: 0.25rem;
}

.testimonial-single-info p {
  font-size: 0.875rem;
  color: var(--text-light);
  margin: 0;
}

/* ===== BLOCK: FAQ Rounded Cards (Sandbox demo8-inspired) =====
 * FAQ with rounded card style in 2 columns
 */
.faq-cards {
  padding: 5rem 0;
}

.faq-cards-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
}

@media (max-width: 768px) {
  .faq-cards-grid {
    grid-template-columns: 1fr;
  }
}

.faq-card {
  background: white;
  border-radius: var(--radius-lg);
  padding: 1.5rem 2rem;
  box-shadow: var(--shadow-sm);
  border: 1px solid transparent;
  transition: all 0.3s ease;
  height: fit-content;
}

.faq-card:hover {
  box-shadow: var(--shadow-md);
  border-color: rgba(39, 170, 225, 0.2);
}

.faq-card-question {
  font-family: 'Raleway', sans-serif;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--text-dark);
  margin-bottom: 0.75rem;
  display: flex;
  align-items: flex-start;
  gap: 1rem;
}

.faq-card-question i {
  color: var(--blue-dark);
  font-size: 3rem; /* Match common features icon size */
  flex-shrink: 0;
  margin-top: -0.2rem;
}

.faq-card-answer {
  color: var(--text-body);
  margin: 0;
  padding-left: 2.25rem; /* Align with question text */
}

/* ===== Text Blocks (2.23, 2.24) ===== */
.quote-highlight {
  border-left: 4px solid var(--blue-dark);
  padding: 0.75rem 1rem;
  background: rgba(39, 170, 225, 0.06);
  font-style: italic;
  margin: 1.25rem 0;
}

.text-block-figure img {
  max-width: 440px;
}

.text-block-list li {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin-bottom: 0.6rem;
  color: var(--text-body);
}

.text-block-list i {
  color: var(--blue-dark);
  font-size: 1.1rem;
  margin-top: 0.2rem;
}

/* ===== Underline Effect (Sandbox demo10-inspired) =====
 * SVG/CSS underline for emphasis on words
 * Usage: <span class="underline-effect">word</span>
 */
.underline-effect {
  position: relative;
  display: inline-block;
}

.underline-effect::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 10px;
  /* Arc shape (like frown/unhappy mouth) - curves down in middle */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 10'%3E%3Cpath d='M2,2 Q50,14 98,2' stroke='%2327AAE1' stroke-width='3' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 100% 100%;
  opacity: 0.6;
}

/* ===== Text Animation: Marker Underline =====
 * SVG hand-drawn red marker stroke under a word (Sandbox demo27-inspired)
 * Usage: <span class="marker-underline">word</span>
 * Animates from left to right via clip-path reveal.
 *
 * NOTE: Spans inside h1 need explicit gradient text rendering because
 * h1 uses -webkit-text-fill-color: transparent + background-clip: text.
 * Using display:inline-block inside such h1 breaks the parent's clipping,
 * so we re-declare the gradient on the span itself.
 */
.marker-underline {
  position: relative;
  display: inline-block;
  white-space: nowrap;
  /* Re-declare h1 gradient so text is visible */
  background: linear-gradient(to top, #004275 0%, #005a9d 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.marker-underline::after {
  content: '';
  position: absolute;
  left: -2%;
  right: -2%;
  bottom: -1px;
  height: 10px;
  /* Smooth shallow arc instead of a wavy marker stroke */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 10'%3E%3Cpath d='M4,4 Q100,9 196,4' stroke='%23dc3545' stroke-width='3' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 100% 100%;
  pointer-events: none;
  /* Start fully clipped (hidden) */
  clip-path: inset(0 100% 0 0);
}

/* Animate via clip-path: reveals fixed SVG from left to right */
.carousel-item.active .marker-underline::after,
.marker-underline.animate::after {
  animation: markerDraw 1s 0.6s ease-out forwards;
}

@keyframes markerDraw {
  0%   { clip-path: inset(0 100% 0 0); } /* Fully hidden */
  100% { clip-path: inset(0 0% 0 0); }   /* Fully revealed - single smooth stroke */
}

/* ===== Text Animation: Light Speed In Left =====
 * Word slides in from the left with skew (animate.css lightSpeedInLeft)
 * Usage: <span class="light-speed-in">word</span>
 * Same gradient text fix as marker-underline for h1 compatibility.
 */
.light-speed-in {
  display: inline-block;
  opacity: 0;
  transform: translate3d(-100%, 0, 0) skewX(30deg);
  will-change: transform, opacity;
  /* Re-declare h1 gradient so text is visible */
  background: linear-gradient(to top, #004275 0%, #005a9d 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.carousel-item.active .light-speed-in {
  animation: lightSpeedInLeft 1s 0.5s ease-out both;
}

/* Non-carousel single-card heroes (.hero-image-style without the
   .hero-image-carousel modifier) have no .carousel-item.active ancestor to
   trigger the rule above, so this word would otherwise sit permanently
   hidden at its 0% keyframe state - auto-play it directly instead. */
.hero-image-style:not(.hero-image-carousel) .light-speed-in {
  animation: lightSpeedInLeft 1s 0.5s ease-out both;
}

@keyframes lightSpeedInLeft {
  0% {
    transform: translate3d(-100%, 0, 0) skewX(30deg);
    opacity: 0;
  }
  60% {
    transform: skewX(-20deg);
    opacity: 1;
  }
  80% {
    transform: skewX(5deg);
    opacity: 1;
  }
  100% {
    transform: translate3d(0, 0, 0);
    opacity: 1;
  }
}

/* ===== Text Animation: Fade-In-Zoom (Sandbox demo13-inspired) =====
 * Whole heading scales up from a slight zoom-out while fading in.
 * Usage: <h1 class="fade-in-zoom">...</h1>
 * Auto-plays on paint, same as marker-underline/light-speed-in above -- the
 * generic [data-anim] scroll-reveal system is explicitly documented as unfit
 * for above-the-fold hero content (its IntersectionObserver adds a visible
 * flash-of-blank delay), so hero text effects always animate immediately via
 * a plain forwards-filling keyframe instead.
 */
.fade-in-zoom {
  opacity: 0;
  transform: scale(0.85);
  animation: heroFadeInZoom 0.8s ease forwards;
}

@keyframes heroFadeInZoom {
  to { opacity: 1; transform: scale(1); }
}

/* ===== Text Animation: Sequential Entrance (Sandbox demo15-inspired) =====
 * Heading fades down into place, then the lead paragraph fades in from the
 * left, then the button row fades up -- delays are baked into each class so
 * no extra stagger class is needed.
 * Usage: <h1 class="entrance-fade-down">, <p class="entrance-fade-left">,
 * <div class="entrance-fade-up"> (buttons wrapper).
 */
.entrance-fade-down {
  opacity: 0;
  transform: translateY(-26px);
  animation: heroFadeDown 0.7s ease forwards;
}

.entrance-fade-left {
  opacity: 0;
  transform: translateX(-30px);
  animation: heroFadeLeftIn 0.7s 0.25s ease forwards;
}

.entrance-fade-up {
  opacity: 0;
  transform: translateY(24px);
  animation: heroFadeUpIn 0.7s 0.5s ease forwards;
}

@keyframes heroFadeDown   { to { opacity: 1; transform: translateY(0); } }
@keyframes heroFadeLeftIn { to { opacity: 1; transform: translateX(0); } }
@keyframes heroFadeUpIn   { to { opacity: 1; transform: translateY(0); } }

/* Carousel slides (.hero-image-carousel) already give every h1/.lead their
 * own generic fadeInUp entrance (see that class's own rules near the top of
 * this file) - those selectors (.hero-image-carousel h1 etc.) are more
 * specific than a bare .entrance-fade-down class, so they'd silently win and
 * this sequential effect would never actually play on a carousel slide.
 * Re-declare it here at matching specificity so a slide that opts in (e.g.
 * the front page's domain-search slide) gets the real down/left/up sequence
 * instead of the generic one-shot fade. */
.hero-image-carousel .entrance-fade-down { animation: heroFadeDown 0.7s ease forwards; }
.hero-image-carousel .entrance-fade-left { animation: heroFadeLeftIn 0.7s 0.25s ease forwards; }
.hero-image-carousel .entrance-fade-up   { animation: heroFadeUpIn 0.7s 0.5s ease forwards; }

/* ===== Text Animation: Word Swap (Sandbox demo21-inspired) =====
 * Like .typewriter (cycles through 3-4 words in place), but swaps the word
 * instantly instead of typing it character-by-character, colors it with the
 * lighter brand blue, and replays a short fade-in-down each time it changes.
 * Usage: <span class="word-swap" data-words='["word1","word2","word3"]'>word1</span>
 * JS: initWordSwap() in main.js. Same h1-gradient-clip re-declare as
 * .marker-underline/.light-speed-in -- h1 uses -webkit-text-fill-color:
 * transparent + background-clip: text, so a plain color here would be
 * invisible without overriding both.
 */
.word-swap {
  display: inline-block;
  color: var(--blue-light);
  background: none;
  -webkit-text-fill-color: var(--blue-light);
}

.word-swap.word-swap-play {
  animation: wordSwapFadeDown 0.5s ease;
}

@keyframes wordSwapFadeDown {
  from { opacity: 0; transform: translateY(-14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ===== BLOCK: Text + Image (2.4) Robot Sizing ===== */
.text-image-robot {
  max-width: 420px;  /* Bigger robot */
  margin-left: auto;
  margin-right: 2rem;  /* Position slightly right */
}

/* ===== SVG/CSS Clouds (Sandbox demo26-inspired) ===== */
.cloud-decoration {
  position: absolute;
  pointer-events: none;
  z-index: 0;
}

.cloud-left {
  left: -5%;
  top: 10%;
  width: 200px;
  height: 120px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 100'%3E%3Cellipse cx='70' cy='70' rx='50' ry='30' fill='%23e8f4fc'/%3E%3Cellipse cx='110' cy='60' rx='60' ry='35' fill='%23e8f4fc'/%3E%3Cellipse cx='150' cy='70' rx='40' ry='25' fill='%23e8f4fc'/%3E%3C/svg%3E") no-repeat center;
  background-size: contain;
  opacity: 0.8;
}

.cloud-right {
  right: -3%;
  top: 20%;
  width: 180px;
  height: 100px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 90'%3E%3Cellipse cx='60' cy='60' rx='45' ry='25' fill='%23e8f4fc'/%3E%3Cellipse cx='100' cy='50' rx='55' ry='30' fill='%23e8f4fc'/%3E%3Cellipse cx='140' cy='60' rx='35' ry='20' fill='%23e8f4fc'/%3E%3C/svg%3E") no-repeat center;
  background-size: contain;
  opacity: 0.7;
}

.cloud-bottom {
  bottom: 0;
  left: 0;
  right: 0;
  height: 80px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 80' preserveAspectRatio='none'%3E%3Cpath d='M0,80 L0,50 Q100,20 200,40 T400,35 T600,45 T800,30 T1000,40 T1200,35 L1200,80 Z' fill='%23ffffff'/%3E%3C/svg%3E") no-repeat bottom;
  background-size: 100% 100%;
}

/* Section with cloud decorations */
.section-clouds {
  position: relative;
  overflow: hidden;
}

/* ===== BLOCK: Pricing Cards v3 (Sandbox demo26-inspired) =====
 * Price at top, plan name under it, color accent bar on top
 * Usage: <div class="pricing-card-v3">...</div>
 */
.pricing-card-v3 {
  background: white;
  border-radius: var(--radius-lg);
  padding: 0;
  box-shadow: var(--shadow-md);
  transition: all 0.3s ease;
  text-align: center;
  height: 100%;
  overflow: hidden;
}

.pricing-card-v3:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-hover);
}

/* Color accent bar at top */
.pricing-card-v3-accent {
  height: 6px;
  background: var(--blue-light); /* Default light blue */
}

.pricing-card-v3-accent.accent-orange {
  background: linear-gradient(90deg, #F7941E 0%, #FFB347 100%);
}

.pricing-card-v3-accent.accent-blue {
  background: linear-gradient(90deg, var(--blue-dark) 0%, var(--blue-light) 100%);
}

.pricing-card-v3-accent.accent-primary {
  background: linear-gradient(90deg, var(--primary) 0%, var(--blue-dark) 100%);
}

.pricing-card-v3-body {
  padding: 1.5rem 2rem 2rem;
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* Large price at top */
.pricing-card-v3-price {
  margin-bottom: 0.5rem;
}

.pricing-card-v3-price .amount {
  font-family: 'Montserrat', sans-serif;
  font-size: 3.5rem;
  font-weight: 800;
  color: var(--text-dark);
  line-height: 1;
}

.pricing-card-v3-price .currency {
  font-family: 'Montserrat', sans-serif;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--text-dark);
  vertical-align: super;
  margin-left: 0.15rem;
}

.pricing-card-v3-price .period {
  font-size: 0.9rem;
  color: var(--text-light);
  display: block;
  margin-top: 0.25rem;
}

/* ===== Left-edge pill ribbon =====
 * In-flow element inside .pricing-card-v3-body, before the plan name.
 * Negative left margin pushes it flush with the card's left edge.
 * Cards without a ribbon use .pricing-card-v3-ribbon-spacer for alignment.
 */
.pricing-card-v3-ribbon {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  margin-left: -2rem;
  margin-bottom: 0.5rem;
  padding: 0.35rem 1rem 0.35rem 1.2rem;
  font-size: 0.8rem;
  font-weight: 700;
  color: white;
  line-height: 1.2;
  border-radius: 0 50px 50px 0;
  box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}

.pricing-card-v3-ribbon.ribbon-popular { background: var(--blue-dark); }
/* Was unused (plain red, no animation) - repurposed for a live TLD discount
 * ribbon: dark orange plus a modest, continuously-running glow pulse (a
 * quieter cousin of .price-badge-round's looping badgePulse ring, sized for
 * a rectangular ribbon rather than a circle). */
.pricing-card-v3-ribbon.ribbon-sale {
  background: var(--orange-dark);
  animation: tldSalePulse 2.2s ease-out infinite;
}
/* Grows outward and fades, then holds (no shrink-back) - same shape as
 * .price-badge-round's badgePulse, just smaller and a single ring. White
 * reads clearly on both the dark-orange ribbon and the dark-blue sale card. */
@keyframes tldSalePulse {
  0%   { box-shadow: 0 0 0 0 rgba(255,255,255,0.6); }
  65%  { box-shadow: 0 0 0 8px rgba(255,255,255,0); }
  100% { box-shadow: 0 0 0 8px rgba(255,255,255,0); }
}
/* For a featured card that's also the dark variant (e.g. SmartWoo's middle
 * plan) - the default .ribbon-popular's semi-transparent white treatment on
 * dark (see .pricing-card-v3-dark .ribbon-popular below) reads fine but is
 * low-contrast; orange stands out more against the dark card background. */
.pricing-card-v3-ribbon.ribbon-popular-orange { background: var(--orange-dark); color: white; }

/* Spacer for cards without ribbon (keeps plan names aligned) */
.pricing-card-v3-ribbon-spacer {
  min-height: 1.35rem; /* Match ribbon height */
  margin-bottom: 0.5rem;
}

/* Plan name (h3-sized, fixed 2-line height) */
.pricing-card-v3-name {
  font-family: 'Raleway', sans-serif;
  font-size: 1.75rem;
  font-weight: 800;
  color: var(--text-dark);
  margin-bottom: 0.25rem;
  min-height: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Power level indicator (lightning icons) */
.pricing-power {
  display: flex;
  justify-content: center;
  gap: 0.2rem;
  margin-bottom: 0.75rem;
}

.pricing-power i {
  font-size: 1rem;
  color: rgba(164, 174, 198, 0.35); /* Dim = off */
}

.pricing-power i.on {
  color: var(--blue-dark); /* Lit = on */
}

/* Description (fixed 3-line height, bold) */
.pricing-card-v3-desc {
  font-weight: 700;
  color: var(--text-body);
  min-height: 4.8rem; /* ~3 lines at body font size */
  margin-bottom: 0.5rem; /* Reduced: tighter gap to toggle button below */
}

/* Features list */
.pricing-card-v3-features {
  list-style: none;
  padding: 0;
  margin: 0 0 1.5rem 0;
  text-align: left;
  flex-grow: 1; /* Push button to bottom */
}

.pricing-card-v3-features li {
  padding: 0.5rem 0;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  color: var(--text-body);
}

.pricing-card-v3-features li i {
  color: var(--blue-dark);
  font-size: 1rem;
}

/* Dual button row — vertical by default (narrow cards), horizontal on XXL/mobile */
.pricing-card-v3-buttons {
  display: flex;
  flex-direction: column; /* Stacked by default (prevents wrapping at LG/MD) */
  gap: 0.5rem;
  margin-top: auto;
  margin-bottom: 0.5rem; /* Breathing room above card bottom edge */
}

.pricing-card-v3-buttons .btn {
  white-space: nowrap;
}

/* Full-width cards on mobile: buttons fit side-by-side */
@media (max-width: 575px) {
  .pricing-card-v3-buttons { flex-direction: row; }
  .pricing-card-v3-buttons .btn { flex: 1; }
}

/* Extra-wide cards on XXL: buttons fit side-by-side */
@media (min-width: 1400px) {
  .pricing-card-v3-buttons { flex-direction: row; }
  .pricing-card-v3-buttons .btn { flex: 1; }
  .pricing-card-v3-buttons .btn-primary { flex: 1.2; }
}

/* Non-featured cards: subtle border */
.pricing-card-v3:not(.featured) {
  border: 1px solid rgba(164, 174, 198, 0.22);
}

/* Featured variant */
.pricing-card-v3.featured {
  border: 2px solid var(--blue-dark);
}

/* Equal accent bar height across all cards for visual consistency */
.pricing-card-v3.featured .pricing-card-v3-accent {
  height: 6px; /* Same as non-featured */
}

/* Bigger power/level indicator icons (+50%) */
.pricing-power i { font-size: 1.95rem; }

/* Stars variant (replaces lightning bolts) */
.pricing-power-stars { display: flex; justify-content: center; gap: 0.2rem; margin-bottom: 0.75rem; }
.pricing-power-stars i { font-size: 1.95rem; color: rgba(164,174,198,0.30); }
.pricing-power-stars i.on { color: #f5a623; }

/* Bars variant (signal-strength style, 3rd card variation) */
.pricing-power-bars { display: flex; justify-content: center; gap: 0.35rem; margin-bottom: 0.75rem; align-items: flex-end; }
.pricing-power-bar { width: 9px; border-radius: 3px 3px 0 0; background: rgba(164,174,198,0.25); }
.pricing-power-bar.on { background: var(--primary); }
.pricing-power-bar:nth-child(1) { height: 10px; }
.pricing-power-bar:nth-child(2) { height: 17px; }
.pricing-power-bar:nth-child(3) { height: 26px; }

/* Inline old price (strikethrough inside .period) */
.pricing-card-v3-price .period s {
  font-weight: 700;
  color: var(--text-light);
  margin-right: 0.25rem;
}

/* Sale accent on the amount */
.pricing-card-v3-price .amount.sale {
  color: #dc3545;
}

/* ===== Performance spec blocks (2x2 grid, circle icon style) ===== */
.pricing-specs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

/* 3-column variant -- for plans with 3 highlighted specs instead of 4
 * (Nextcloud, Google Workspace, Microsoft 365 product pages). */
.pricing-specs.pricing-specs-3 {
  grid-template-columns: repeat(3, 1fr);
}

.pricing-spec {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0.35rem 0.25rem;
  gap: 0.2rem;
  background: none;
  border: none;
  border-radius: 0;
}

/* Circle disc inside spec (replaces inline icon) */
.pricing-spec-disc {
  width: 40px; height: 40px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.1rem;
  margin-bottom: 0.2rem;
  flex-shrink: 0;
}

.psd-blue   { background: rgba(4,123,193,0.11);  color: var(--blue-dark); }
.psd-purple { background: rgba(130,80,220,0.11); color: #8250dc; }
.psd-green  { background: rgba(46,153,0,0.11);   color: #1a7a00; }
.psd-orange { background: rgba(247,148,30,0.11); color: var(--orange-dark); }

.pricing-spec-value {
  font-family: 'Montserrat', sans-serif;
  font-size: 1.25rem;
  font-weight: 800;
  color: var(--text-dark);
  line-height: 1.1;
}

.pricing-spec-label {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--text-light);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1;
}

/* ===== BLOCK: Pricing Cards v3 — Dark/Inverted Variant (testing) =====
 * Dark navy background with white text. Use for visual A/B comparison.
 * Apply .pricing-card-v3-dark alongside/instead of .pricing-card-v3.
 */
.pricing-card-v3-dark {
  background: linear-gradient(160deg, #002455 0%, var(--primary) 60%, #003060 100%);
  border-radius: var(--radius-lg);
  padding: 0;
  box-shadow: 0 8px 35px rgba(0, 25, 60, 0.35);
  transition: all 0.3s ease;
  text-align: center;
  height: 100%;
  overflow: hidden;
}

.pricing-card-v3-dark:hover {
  transform: translateY(-5px);
  box-shadow: 0 16px 55px rgba(0, 25, 60, 0.50);
}

/* Slightly lighter accent bar for dark cards */
.pricing-card-v3-dark .pricing-card-v3-accent {
  height: 6px;
  background: rgba(255,255,255,0.15);
}
.pricing-card-v3-dark .pricing-card-v3-accent.accent-blue {
  background: linear-gradient(90deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0.10) 100%);
}
.pricing-card-v3-dark .pricing-card-v3-accent.accent-primary {
  background: linear-gradient(90deg, var(--blue-light) 0%, rgba(255,255,255,0.15) 100%);
}
.pricing-card-v3-dark .pricing-card-v3-accent.accent-orange {
  background: linear-gradient(90deg, #F7941E 0%, #FFB347 100%);
}

/* Sale-card price accent - scoped to .tld-sale-card so it doesn't touch the
 * plain white price used by "most popular" dark cards (SmartWP/SmartWoo)
 * that aren't about a discount. Used on /verkkotunnukset's on-sale TLD
 * cards; the amount/currency turn orange instead of white. */
.pricing-card-v3-dark.tld-sale-card .pricing-card-v3-price .amount,
.pricing-card-v3-dark.tld-sale-card .pricing-card-v3-price .currency {
  color: #FFB347;
  -webkit-text-fill-color: #FFB347;
}

/* Dark card text overrides */
.pricing-card-v3-dark .pricing-card-v3-price .amount { color: white; -webkit-text-fill-color: white; }
.pricing-card-v3-dark .pricing-card-v3-price .currency { color: white; -webkit-text-fill-color: white; }
.pricing-card-v3-dark .pricing-card-v3-price .period { color: rgba(255,255,255,0.60); }
.pricing-card-v3-dark .pricing-card-v3-name { color: white; -webkit-text-fill-color: white; }
.pricing-card-v3-dark .pricing-card-v3-desc { color: rgba(255,255,255,0.80); }
.pricing-card-v3-dark .pricing-card-v3-features li {
  color: rgba(255,255,255,0.80);
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.pricing-card-v3-dark .pricing-card-v3-features li i { color: var(--blue-light); }

/* White circle discs for dark card feature list (matches light card .stat-icon-gray style) */
.pricing-card-v3-dark .pricing-card-v3-features li .check-disc {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px; height: 22px; min-width: 22px;
  border-radius: 50%;
  background: rgba(255,255,255,0.18);
  color: white;
  font-size: 0.7rem;
  flex-shrink: 0;
}
/* Real pages use .stat-icon-gray (not .check-disc above, which only ever
   existed in the /blocks demo) for feature bullets, matching the light-card
   markup exactly. .stat-icon-gray's own base rule forces a dark icon color
   with !important for the light-card case, so the override here must also
   be !important - a plain higher-specificity rule would lose to it. */
.pricing-card-v3-dark .pricing-card-v3-features .stat-icon-gray {
  background: rgba(255,255,255,0.18) !important;
  color: white !important;
  -webkit-text-fill-color: white !important;
}

/* .feat-em (first-3 emphasized bullets) hardcodes a light-card look (dark
   navy disc + dark navy text) with no dark-card equivalent - on a dark card
   this rendered as an unreadable dark-on-dark disc and dark grey text. */
.pricing-card-v3-dark .pricing-card-v3-features li.feat-em .stat-icon-gray {
  background: rgba(255,255,255,0.95) !important;
  color: var(--primary) !important;
  -webkit-text-fill-color: var(--primary) !important;
}
.pricing-card-v3-dark .pricing-card-v3-features li.feat-em > span:last-child {
  color: white;
}

.pricing-card-v3-dark .pricing-spec-value { color: white; -webkit-text-fill-color: white; }
.pricing-card-v3-dark .pricing-spec-label { color: rgba(255,255,255,0.55); }
.pricing-card-v3-dark .pricing-power i.on { color: var(--blue-light); }
.pricing-card-v3-dark .pricing-power-stars i.on { color: #fbbf24; }

/* Spec discs: lighter on dark background */
.pricing-card-v3-dark .psd-blue   { background: rgba(39,170,225,0.18); color: var(--blue-light); }
.pricing-card-v3-dark .psd-purple { background: rgba(180,120,255,0.18); color: #c084fc; }
.pricing-card-v3-dark .psd-green  { background: rgba(74,222,128,0.18); color: #4ade80; }
.pricing-card-v3-dark .psd-orange { background: rgba(251,191,36,0.18); color: #fbbf24; }

/* Ribbon stays readable */
.pricing-card-v3-dark .pricing-card-v3-ribbon.ribbon-popular {
  background: rgba(255,255,255,0.18);
  color: white;
}

/* Featured dark card: bright border */
.pricing-card-v3-dark.featured {
  border: 2px solid rgba(255,255,255,0.35);
}

/* Buttons: ghost light for secondary, white solid for CTA */
.pricing-card-v3-dark .btn-light {
  background: rgba(255,255,255,0.10);
  color: white;
  border: 1px solid rgba(255,255,255,0.28);
}
.pricing-card-v3-dark .btn-light:hover {
  background: rgba(255,255,255,0.20);
}
.pricing-card-v3-dark .btn-primary {
  background: white;
  color: var(--primary);
  border-color: white;
}
.pricing-card-v3-dark .btn-primary:hover {
  background: rgba(255,255,255,0.90);
  color: var(--primary);
}

/* "Muut ominaisuudet" toggle button: the base .specs-toggle-label/-circle
   styles use --text-light (a mid grey) and a barely-there grey circle, both
   too low-contrast on the dark card's navy gradient background. */
.pricing-card-v3-dark .specs-toggle-label { color: rgba(255,255,255,0.65); }
.pricing-card-v3-dark .specs-toggle-circle { background: rgba(255,255,255,0.15); color: rgba(255,255,255,0.85); }
.pricing-card-v3-dark .pricing-specs-toggle:hover .specs-toggle-circle { background: rgba(255,255,255,0.28); color: white; }

/* ===== BLOCK: CTA banner with domain search — Dark/inverted variant ===== */
.cta-dark-domain-input::placeholder {
  color: rgba(255, 255, 255, 0.80);
  opacity: 1;
}

/* ===== BLOCK: Domain TLD search widgets (styled dropdown + live matcher) =====
 * Two experimental variants of TLD selection, both built on the same
 * components/domain-tld-dropdown-items.html data:
 * - .domain-tld-dropdown: styled Bootstrap 5 dropdown replacing a plain
 *   <select>, with a type-to-filter box (168 TLDs is a lot to scroll).
 * - .domain-tld-live: single merged "domain.tld" input, JS-matched live
 *   against the same data (see js/domain-search.js).
 */
.domain-tld-toggle {
  min-width: 140px;
  text-align: left;
  border-color: var(--primary);
  color: var(--primary);
}
.domain-tld-toggle:hover,
.domain-tld-toggle:focus,
.domain-tld-toggle.show {
  background: var(--primary);
  border-color: var(--primary);
  color: white;
}

/* Disabled submit on the live matcher must read as "off", not just a dimmer
 * version of its normal color (Bootstrap's default :disabled is just reduced
 * opacity on the same btn-primary/btn-light color, which still looks fully
 * clickable) - same neutral gray treatment regardless of theme. */
.domain-tld-live-submit:disabled {
  background: #ced4da !important;
  border-color: #ced4da !important;
  color: #868e96 !important;
  opacity: 1;
}
.domain-tld-menu {
  max-height: 320px;
  overflow-y: auto;
  min-width: 220px;
  border-radius: var(--radius-md);
  /* Bootstrap's dropdown positioning keeps this in the input-group's own
     stacking context; the section it lives in must not clip (overflow:hidden)
     or set a lower stacking context, or the open menu renders behind the
     next section instead of above it. */
  z-index: 1060;
}
.domain-tld-item {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.9rem;
}
.domain-tld-item .tld-item-name {
  font-size: 1.05rem;
}
.domain-tld-item .tld-item-price {
  color: var(--text-light);
  font-size: 0.95em;
  margin-left: auto;
  text-align: right;
}
.domain-tld-item .tld-item-price.tld-item-price-has-discount {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}
.domain-tld-item .tld-item-price-discount {
  color: #dc3545;
  font-weight: 700;
}
.domain-tld-item .tld-item-price-regular {
  color: var(--text-light);
  font-size: 0.8em;
}
.domain-tld-item.active,
.domain-tld-item:active {
  background-color: var(--soft-blue);
  color: var(--text-dark);
}
.domain-tld-item.active .tld-item-price,
.domain-tld-item.active .tld-item-price-regular {
  color: var(--text-dark);
}

.domain-tld-live-message:empty {
  display: none;
}

/* Live-matcher confirmed pill: discount gets a brighter accent + a one-shot
 * pulse (same ring technique as .price-badge-round's badgePulse, but firing
 * once instead of looping - the pill is re-rendered on every keystroke, so
 * the animation restarts naturally each time it (re)appears). */
.tld-live-pill-discount {
  border-color: rgba(74, 222, 128, 0.55) !important;
  animation: tldDiscountPulse 0.9s ease-out 1;
}
.tld-live-pill-discount .tld-item-price-discount,
.tld-live-pill-discount .tld-item-price {
  color: #4ade80;
  font-weight: 700;
}
.tld-live-pill-discount .tld-item-price-regular {
  color: rgba(255, 255, 255, 0.60);
  font-weight: 400;
}
@keyframes tldDiscountPulse {
  0%   { box-shadow: 0 0 0 0 rgba(74, 222, 128, 0.45); }
  70%  { box-shadow: 0 0 0 14px rgba(74, 222, 128, 0); }
  100% { box-shadow: 0 0 0 14px rgba(74, 222, 128, 0); }
}

/* Full-TLD-list marquee (dark live-matcher block only) - same mechanism as
 * .tld-marquee/.tld-marquee-track (see the Domain Search blocks further
 * down), just a slower loop since it now carries 150+ items instead of a
 * handful. */
.tld-marquee-full .tld-marquee-track {
  animation-duration: 195s; /* 150s, slowed 30% per feedback */
}
.tag-item-dark.tag-item-discount {
  border-color: rgba(74, 222, 128, 0.55);
}
.tag-item-dark.tag-item-discount .tld-price {
  color: #4ade80;
  font-weight: 700;
}
.tag-item.tag-item-discount,
.sec-tech-pill.tag-item-discount {
  border-color: rgba(220, 53, 69, 0.35);
  background: rgba(220, 53, 69, 0.06);
}
.tag-item.tag-item-discount .tld-price,
.sec-tech-pill.tag-item-discount .tld-price {
  color: #dc3545;
  font-weight: 700;
}

/* Live-matcher confirmed pill, light theme (reuses .sec-tech-pill as the base). */
.tld-live-pill-discount-light {
  animation: tldDiscountPulse 0.9s ease-out 1;
}
.tld-live-pill-discount-light .tld-item-price-discount,
.tld-live-pill-discount-light .tld-item-price {
  color: #dc3545;
  font-weight: 700;
}

/* Clickable marquee pills (live-matcher only - see js/domain-search.js): picking
 * one sets that TLD straight into the domain field, keeping any label already
 * typed. Hovering already pauses the whole track (.tld-marquee:hover rule further
 * up); this just makes the individual pill's own affordance/feedback clear. */
.domain-tld-marquee-item {
  cursor: pointer;
  transition: transform 0.15s ease;
}
.domain-tld-marquee-item:hover,
.domain-tld-marquee-item:focus-visible {
  transform: translateY(-2px);
}
.tag-item-dark.domain-tld-marquee-item:hover {
  background: rgba(255, 255, 255, 0.26);
  border-color: rgba(255, 255, 255, 0.55);
}
.sec-tech-pill.domain-tld-marquee-item:hover {
  background: rgba(4, 123, 193, 0.16);
}

.domain-tld-extra-actions p {
  max-width: 480px;
  margin-left: auto;
  margin-right: auto;
}

/* Inverted section: dark blue background around dark cards */
.pricing-section-dark {
  background: linear-gradient(180deg, #070d1a 0%, #0d1a2e 100%);
}
.pricing-section-dark h2,
.pricing-section-dark .h2 { color: white; -webkit-text-fill-color: white; }
.pricing-section-dark .eyebrow { color: rgba(255,255,255,0.60) !important; }
.pricing-section-dark .btn-sm.btn-light {
  background: rgba(255,255,255,0.12);
  color: rgba(255,255,255,0.85);
  border: 1px solid rgba(255,255,255,0.20);
}
.pricing-section-dark .btn-sm.btn-light:hover {
  background: rgba(255,255,255,0.22);
}

/* ===== Small pentagon tag overlay on pricing card (demo/testing) =====
 * Position the column as relative, then place .price-tag-card-overlay
 * absolutely inside it to float the tag over the card's top-right corner.
 */
.price-tag-card-col {
  position: relative;
}

.price-tag-card-overlay {
  position: absolute;
  top: -12px;
  right: 0;
  width: 140px;
  padding: 1.5rem 1rem 2.5rem;
  background: linear-gradient(160deg, #002455 0%, var(--primary) 60%, #003d6b 100%);
  display: flex; flex-direction: column;
  align-items: center; justify-content: flex-start;
  color: white; text-align: center;
  clip-path: polygon(0 0, 100% 0, 100% 78%, 50% 100%, 0 78%);
  z-index: 5;
  box-shadow: -4px 4px 20px rgba(0,30,70,0.28);
}

/* Horizontal dashed line on card overlay tag */
.price-tag-card-overlay::before {
  content: '';
  position: absolute;
  top: 14px;
  left: 12%; right: 12%;
  height: 0;
  border-top: 1.5px dashed rgba(255,255,255,0.40);
}

.price-tag-card-overlay .pb-label  { font-size: 0.65rem; font-weight: 700; opacity: 0.88; }
.price-tag-card-overlay .pb-price-row {
  display: flex; align-items: flex-start; justify-content: center; line-height: 1;
}
.price-tag-card-overlay .pb-currency {
  font-family: 'Montserrat', sans-serif;
  font-size: 0.75rem; font-weight: 700; color: white; -webkit-text-fill-color: white;
  padding-top: 0.28rem;
}
.price-tag-card-overlay .pb-amount {
  font-family: 'Montserrat', sans-serif;
  font-size: 2rem; font-weight: 900; line-height: 1;
  color: white; -webkit-text-fill-color: white;
  letter-spacing: -0.03em;
}
.price-tag-card-overlay .pb-period  { font-size: 0.6rem; opacity: 0.75; }
.price-tag-card-overlay .pb-product { font-size: 0.6rem; font-weight: 600; opacity: 0.85; margin-top: 0.15rem; }

/* ===== Typewriter Effect (Sandbox demo36-inspired) =====
 * Rotating text with typing/erasing animation
 * Usage: <span class="typewriter" data-words='["word1", "word2", "word3"]'>word1</span>
 */
.typewriter-wrapper {
  display: inline;
}

.typewriter {
  display: inline-block;
  border-right: 3px solid var(--blue-dark);  /* Cursor */
  animation: typewriterBlink 0.7s infinite;
  padding-right: 2px;
}

@keyframes typewriterBlink {
  0%, 50% { border-color: var(--blue-dark); }
  51%, 100% { border-color: transparent; }
}

/* Wrapper for the static + dynamic text */
.typewriter-text {
  display: inline;
}

.typewriter-static {
  /* Static part of the text */
}

.typewriter-dynamic {
  /* The changing word - color accent */
  color: var(--blue-dark);
  font-weight: inherit;
}

/* ===== BLOCK: Domain Search (2.28 variants) =====
 * Search bar: Bootstrap input-group-lg + form-control-lg + form-select + btn-lg
 * TLD pills: reuses .tag-item from 2.17, .tag-item-dark for dark backgrounds
 * Custom CSS: oversized pill-shaped search bar, card (C), gradient (D), two-col (B)
 */

/* Oversized pill-shaped search bar (wraps Bootstrap input-group) */
.domain-search-bar .input-group {
  border-radius: 50px;
  overflow: hidden;
  box-shadow: var(--shadow-md);
}

.domain-search-bar .form-control {
  border: none;
  padding: 1rem 1.5rem;
  font-size: 1.125rem;
}

.domain-search-bar .form-control:focus {
  box-shadow: none;
}

/* TLD dropdown - constrained width, no left border */
.domain-search-tld {
  flex: 0 0 auto !important;
  width: 100px !important;
  border: none;
  border-left: 1px solid rgba(164, 174, 198, 0.2);
  border-radius: 0;
  padding: 1rem 0.5rem 1rem 0.75rem;
  font-size: 1.125rem;
  font-weight: 700;
  color: var(--text-dark);
  background-color: var(--bg-pale);
}

.domain-search-tld:focus {
  box-shadow: none;
  border-color: rgba(164, 174, 198, 0.4);
}

/* Search button inside the input-group */
.domain-search-bar .btn {
  padding: 1rem 2rem;
  font-size: 1.125rem;
  border-radius: 0;
  white-space: nowrap;
}

/* Disable rise-on-hover for buttons inside search bar */
.domain-search-bar .btn:hover {
  transform: none;
  box-shadow: none;
  opacity: 0.9;
}

/* ===== BLOCK: AI Idea Chat =====
 * Prompt entry that hands off to the Hostaan chat widget (see
 * js/hostaan-chat-block.js). Deliberately un-boxed - full container width,
 * same input/button/pill rounding as the Domain Search Live Matcher
 * (.domain-tld-live, plain Bootstrap form-control-lg/btn-lg, no pill/capsule
 * treatment) so this block reads as one of the family, not a one-off card.
 */
/* Prompt pills reuse .sec-tech-pill's light-blue look (see "Salamannopea
 * WordPress palvelinpuolen välimuistilla") on a <button> instead of a
 * <span>, so add button-reset + hover/focus feedback on top of it. */
.ai-idea-pill {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.ai-idea-pill:hover,
.ai-idea-pill:focus-visible {
  background: rgba(4, 123, 193, 0.16);
  border-color: rgba(4, 123, 193, 0.35);
}

.ai-idea-feedback {
  min-height: 1.5rem;
}

/* One-shot horizontal colour sweep across the heading on scroll-in - stays
 * dark-blue at rest, a warm-to-blue "sunset" band rolls left to right once.
 * Reuses the generic [data-anim].anim-play trigger (main.js IntersectionObserver);
 * this selector's higher specificity overrides the plain fade-in-up animation
 * with a combined list (fade-in-up + colour roll), and .anim-play's single
 * `running` value applies to both per the CSS animation-play-state spec. */
h2.ai-idea-heading-roll[data-anim] {
  background: linear-gradient(
    90deg,
    var(--text-dark) 0%,
    var(--text-dark) 40%,
    #ff9d5c 48%,
    #27aae1 52%,
    var(--text-dark) 60%,
    var(--text-dark) 100%
  );
  background-size: 280% 100%;
  background-position: 200% 0;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: var(--text-dark);
  animation: scrollFadeInUp 0.75s ease forwards, aiIdeaColorRoll 1.7s ease-out 0.2s forwards;
}

@keyframes aiIdeaColorRoll {
  0%   { background-position: 200% 0; }
  100% { background-position: -60% 0; }
}

/* Promo-block brand marks (Nextcloud / Google Workspace / Microsoft 365
 * product pages) -- fluid sizing via clamp() so each scales down smoothly
 * on narrower viewports instead of a fixed px width. */
.gw-promo-logo {
  width: clamp(140px, 18vw, 252px);
  height: auto;
}

.m365-promo-logo {
  width: clamp(130px, 16vw, 238px);
  height: auto;
}

.nc-promo-logo {
  width: clamp(220px, 32vw, 520px);
  height: auto;
}

.nc-promo-logo-label {
  font-size: clamp(1.5rem, 3vw, 3rem);
}

/* Gemini AI switcher tab (Google Workspace) -- logo mark standing in for a
 * product screenshot, sized to feel as substantial as the sibling tabs' images. */
.gw-ai-logo {
  width: clamp(160px, 20vw, 240px);
  height: auto;
}

/* Make tag-item and tag-item-dark work as links (clickable pills) */
a.tag-item {
  text-decoration: none;
  color: var(--text-body);
  transition: all 0.2s ease;
}

a.tag-item:hover {
  background: var(--soft-blue);
  color: var(--text-dark);
  text-decoration: none;
}

/* Tag items on dark background (for 2.28a) */
.tag-item-dark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.28);
  padding: 0.5rem 1rem;
  border-radius: 50px;
  font-size: 0.9rem;
  font-weight: 600;
  color: white;
  text-decoration: none;
  transition: all 0.2s ease;
}

.tag-item-dark:hover {
  background: rgba(255,255,255,0.26);
  border-color: rgba(255,255,255,0.55);
  color: white;
  text-decoration: none;
}

.tag-item-dark .tld-price {
  opacity: 0.7;
  font-weight: 400;
}

/* Domain Search Variant B: Two-column layout */
.domain-search-two-col {
  background: white;
  border: 1px solid rgba(164, 174, 198, 0.2);
  box-shadow: var(--shadow-md);
  align-items: flex-start;
}

.domain-search-two-col-text {
  flex: 0 0 auto;
  max-width: 320px;
}

.domain-search-two-col-form {
  flex: 1;
  min-width: 280px;
}

/* Domain Search Variant C: Featured card with orange accent */
.domain-search-feature-card {
  background: white;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-md);
  border: 2px solid var(--blue-dark);
}

.domain-search-feature-accent {
  height: 8px;
  background: linear-gradient(90deg, #F7941E 0%, #FFB347 100%);
}

.domain-search-feature-body {
  padding: 2.5rem 2rem;
}

/* Domain Search Variant D: Sunset gradient background */
.domain-search-gradient {
  position: relative;
  padding: 5rem 0 4rem;
  overflow: hidden;
  background: radial-gradient(
    ellipse 80% 100% at 50% 120%,
    rgba(255, 180, 100, 0.25) 0%,
    rgba(255, 140, 60, 0.15) 20%,
    rgba(39, 170, 225, 0.12) 45%,
    rgba(246, 248, 250, 0.8) 100%
  ),
  linear-gradient(180deg, #f6f8fa 0%, #ffffff 100%);
}

/* Domain Search Compact: reduced padding variant */
.domain-search-feature-card-compact .domain-search-feature-body {
  padding: 1.5rem 2rem;
}

/* TLD marquee (infinite scroll pills) */
.tld-marquee {
  overflow: hidden;
  position: relative;
  display: flex;
  gap: 0;
  /* A couple of px of breathing room top/bottom so a hovered/clickable pill's
     translateY(-2px) rise (see .domain-tld-marquee-item:hover) doesn't get
     clipped by this container's own overflow:hidden. */
  padding: 4px 0;
  margin: -4px 0;
  /* Fade edges */
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%);
}

.tld-marquee-track {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-shrink: 0;
  animation: tldMarquee 30s linear infinite;
  /* Pre-promote to its own compositor layer before the animation starts -
     without this, some browsers show a one-frame hitch/flash exactly at the
     loop seam (when the -100% reset happens) on very long-running tracks. */
  will-change: transform;
  backface-visibility: hidden;
}

/* Duplicate track needs gap from first */
.tld-marquee-track + .tld-marquee-track {
  margin-left: 0.75rem;
}

@keyframes tldMarquee {
  from { transform: translateX(0); }
  to { transform: translateX(-100%); }
}

/* Pause on hover */
.tld-marquee:hover .tld-marquee-track {
  animation-play-state: paused;
}

/* Responsive: stack search bar on mobile */
@media (max-width: 576px) {
  .domain-search-bar .input-group {
    border-radius: var(--radius-lg);
  }
  .domain-search-bar .form-control {
    padding: 0.75rem 1rem;
  }
  .domain-search-bar .btn {
    padding: 0.75rem 1.25rem;
  }
  .domain-search-tld {
    width: 80px !important;
  }
  .domain-search-two-col-text {
    max-width: 100%;
  }
  .domain-search-feature-card-compact .domain-search-feature-body {
    padding: 1rem;
  }
}

/* ===== Content Image Frame =====
 * Rounded bordered container for content-page images (screenshots, SVGs etc.).
 * overflow:hidden clips the image to the rounded corners -- the "partly hidden"
 * effect at the corners. Use on a wrapper <div> around the <img>.
 * The [data-anim] scroll animation system handles fadeInUp on scroll.
 */
.content-frame {
  border-radius: var(--radius-xl);
  border: 1px solid rgba(164, 174, 198, 0.25);
  box-shadow: 0 4px 20px rgba(0, 66, 117, 0.09), 0 1px 5px rgba(0, 0, 0, 0.04);
  overflow: hidden;
}

.content-frame img {
  display: block;
  width: 100%;
}

/* SVG variant: scale up slightly so illustration is cropped by rounded corners */
.content-frame-svg img {
  width: 115%;
  margin: -2% -7.5%;
}

/* ===== BLOCK: Solutions Tab Switcher (3.1) =====
 * Vertical pill nav (desktop, sticky card) / horizontal pills (mobile).
 * Panels use position:absolute (hidden) / position:relative (active) for
 * reliable cross-fade without CSS grid overlap quirks.
 * JS: initSolutionsSwitcher() in main.js.
 */

/* Single-column layout: nav (horizontal) on top, content below */
.solutions-switcher {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

/* Horizontal pill nav in a pale card -- buttons centered within full-width card */
.solutions-nav {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0.375rem;
  background: var(--bg-pale);
  border-radius: var(--radius-xl);
  padding: 0.4rem;
  justify-content: center;
}

/* Each pill button — auto-width in a row, reset browser defaults */
.solutions-nav-btn {
  appearance: none;
  -webkit-appearance: none;
  outline: none;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1.25rem;
  flex: 0 0 auto;
  text-align: left;
  border: none;
  border-radius: var(--radius-lg);
  background: transparent;
  cursor: pointer;
  font-family: 'Manrope', sans-serif;
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--text-dark);
  line-height: 1.3;
  transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}

.solutions-nav-btn:hover:not(.active) {
  background: white;
  box-shadow: 0 1px 8px rgba(0, 66, 117, 0.08);
}

/* The outline:none above removed all keyboard focus visibility with
 * nothing to replace it (WCAG 2.4.7) - :focus-visible only fires for
 * keyboard/programmatic focus, not mouse clicks, so mouse users see no
 * visual change at all; keyboard users get a clear ring. */
.solutions-nav-btn:focus-visible {
  box-shadow: 0 0 0 0.2rem rgba(4, 123, 193, 0.4), 0 1px 8px rgba(0, 66, 117, 0.08);
}

.solutions-nav-btn.active {
  background: var(--primary);
  color: white;
  box-shadow: 0 3px 14px rgba(0, 66, 117, 0.22);
}

/* Icon bubble */
.solutions-nav-icon {
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: 8px;
  background: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  color: var(--blue-dark);
  box-shadow: 0 1px 4px rgba(0, 66, 117, 0.1);
  transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}

.solutions-nav-btn.active .solutions-nav-icon {
  background: rgba(255, 255, 255, 0.2);
  color: white;
  box-shadow: none;
}

/* Chevron hidden for horizontal; kept for possible vertical variant */
.solutions-nav-arrow { display: none; }

/* ---- Content area ----
 * Active panel: position:relative (in-flow, gives container its height).
 * Hidden panels: position:absolute (out of flow, invisible, overlaps).
 */
.solutions-content {
  position: relative;
  /* Smooth height animation when switching panels of different heights */
  transition: min-height 0.4s ease;
}

.solutions-panel {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.55s ease, visibility 0.55s ease;
}

.solutions-panel.active {
  position: relative;
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* ---- Screenshot images ----
 * Use class="screenshot-img" + data-animate-on-view="fade-in-up" on <img>.
 * The existing IntersectionObserver in main.js adds .play when visible.
 */
.screenshot-img {
  display: block;
  width: 100%;
  border-radius: var(--radius-xl);
  border: 1px solid rgba(164, 174, 198, 0.25);
  box-shadow: 0 4px 20px rgba(0, 66, 117, 0.09), 0 1px 5px rgba(0, 0, 0, 0.04);
  /* Start state: invisible and slightly below */
  opacity: 0;
  transform: translateY(22px);
  /* 50% slower than original 0.6s */
  animation: screenshotReveal 0.9s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
  animation-play-state: paused;
}

.screenshot-img.play {
  animation-play-state: running;
}

/* Scale modifier -- shrinks a .screenshot-img to 80% width, centered.
 * Use when a screenshot's native proportions make full column width too big. */
.screenshot-img-80 {
  width: 80%;
  margin: 0 auto;
}

/* Scale modifier -- shrinks a .screenshot-img to 70% width, centered (30%
 * smaller) - for tab/illustration images noticeably too large at full width. */
.screenshot-img-70 {
  width: 70%;
  margin: 0 auto;
}

/* Flat modifier -- removes the border/shadow "framed screenshot" look, for
 * images that are already a device mockup cropped to a transparent/white
 * background (the border+shadow combo double-frames them oddly). Combine
 * with .screenshot-img-80 for a smaller size: class="screenshot-img
 * screenshot-img-flat screenshot-img-80". */
.screenshot-img-flat {
  border: none;
  box-shadow: none;
}

/* 4:3 crop modifier -- for a screenshot whose native proportions are too
 * square/tall next to its text column (e.g. a near-1:1 module screenshot in
 * a col-lg-7). object-fit:cover crops rather than squashing, object-position
 * biases toward the top of the source image since that's usually where the
 * most identifying UI (header/hero of the screenshotted page) sits. Combine
 * with .screenshot-img-flat/.screenshot-img-80 same as any other modifier. */
.screenshot-img-4x3 {
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: top center;
}

/* ---- Layered screenshot pair ----
 * Composes two flat app screenshots into one visual: a "back" shot in normal
 * flow, and a "front" shot absolutely positioned over it, offset right and
 * down. Both images keep the .screenshot-img border/shadow/reveal animation -
 * add class="screenshot-img screenshot-pair-back" (or -front) to each <img>,
 * both with data-animate-on-view="screenshot" so the switcher's re-trigger
 * loop (which iterates all .screenshot-img in the panel) animates both.
 * Wrap the pair in <div class="screenshot-pair">.
 */
.screenshot-pair {
  position: relative;
  padding-bottom: 20%;
}

.screenshot-pair-back.screenshot-img {
  width: 78%;
}

.screenshot-pair-front.screenshot-img {
  position: absolute;
  top: 32%;
  left: 26%;
  width: 66%;
  z-index: 2;
  box-shadow: 0 10px 30px rgba(0, 66, 117, 0.16), 0 2px 8px rgba(0, 0, 0, 0.06);
}

@media (max-width: 575px) {
  .screenshot-pair-front.screenshot-img {
    top: 26%;
    left: 22%;
    width: 66%;
  }
}

/* Mirrored variant -- leans the pair the other way (back image right-aligned,
 * front peeking out to the bottom-left instead of bottom-right). Use on a
 * second .screenshot-pair on the same page for visual variety. */
.screenshot-pair.screenshot-pair-mirror .screenshot-pair-back.screenshot-img {
  margin-left: auto;
}

.screenshot-pair.screenshot-pair-mirror .screenshot-pair-front.screenshot-img {
  left: auto;
  right: 26%;
}

@media (max-width: 575px) {
  .screenshot-pair.screenshot-pair-mirror .screenshot-pair-front.screenshot-img {
    right: 22%;
  }
}

/* ---- Cropped device screenshots ----
 * PNGs with a transparent background showing an app UI already framed
 * inside a real device (iMac/MacBook/iPhone) via marketing photography -
 * not a raw browser screenshot. Naming convention: prefix the filename with
 * "cropped_" (e.g. cropped_webmail_devices.png) so these stay recognizable
 * by filename alone regardless of which img/ subfolder they end up in.
 * Rules: no border, no box-shadow - the device chrome in the image IS the
 * frame already, adding either doubles it up. max-width caps display size
 * near half the source file's native width (these tend to arrive ~1000px
 * wide) to keep detail sharp rather than stretching. White/bg-pale sections
 * only - the device renders were shot for a light background and read
 * poorly against dark ones.
 */
.cropped-device-shot {
  display: block;
  width: 100%;
  max-width: 500px;
  height: auto;
  margin: 0 auto;
}

/* Larger variant - for a screenshot that's the main visual of its block
   rather than a supporting one alongside a full text column. */
.cropped-device-shot-lg {
  max-width: 520px; /* was 650px - scaled down 20% for a sharper look */
}

@keyframes screenshotReveal {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ===== Scroll Animation System =====
 * Usage: <element data-anim="fade-in-up"> or data-anim="fade-in"
 * JS (initScrollAnimations in main.js) adds .anim-play when element enters viewport.
 * Use .anim-d1 ... .anim-d5 for staggered delays.
 * Apply to section headings, panels, cards -- keep animations subtle.
 * Do NOT apply to above-the-fold hero content (observer fires immediately but causes
 * a brief invisible state before firing).
 */
[data-anim="fade-in"] {
  opacity: 0;
  animation: scrollFadeIn 0.75s ease forwards;
  animation-play-state: paused;
}

[data-anim="fade-in-up"] {
  opacity: 0;
  transform: translateY(28px);
  animation: scrollFadeInUp 0.75s ease forwards;
  animation-play-state: paused;
}

[data-anim].anim-play {
  animation-play-state: running;
}

@keyframes scrollFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes scrollFadeInUp {
  from { opacity: 0; transform: translateY(28px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Stagger delay utilities -- pair with data-anim elements */
.anim-d1 { animation-delay: 0.1s; }
.anim-d2 { animation-delay: 0.2s; }
.anim-d3 { animation-delay: 0.3s; }
.anim-d4 { animation-delay: 0.4s; }
.anim-d5 { animation-delay: 0.5s; }

/* Eyebrow color variants for use on coloured card backgrounds */
.eyebrow-teal   { color: #0d998c; }
.eyebrow-violet { color: #8250dc; }
.eyebrow-orange { color: var(--orange-dark); }

/* Cloud infra logos display (Panel 4: Pilvi-infra) */
.cloud-logos-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;  /* Brought logos closer together (was 3rem) */
  padding: 3rem 2rem;
  background: white;
  border-radius: var(--radius-xl);
  border: 1px solid rgba(164, 174, 198, 0.2);
  box-shadow: 0 4px 20px rgba(0, 66, 117, 0.07), 0 1px 4px rgba(0, 0, 0, 0.04);
  /* +25% taller than original 280px */
  min-height: 350px;
  opacity: 0;
  animation: screenshotReveal 0.9s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
  animation-play-state: paused;
}

.cloud-logos-display.play {
  animation-play-state: running;
}

.cloud-logos-display img {
  /* 100% bigger: 180px -> 360px wide, 70px -> 140px tall */
  max-width: 360px;
  max-height: 140px;
  object-fit: contain;
  opacity: 0.85;
  transition: opacity 0.2s;
}

.cloud-logos-display img:hover { opacity: 1; }

.cloud-logos-divider {
  width: 45%;
  height: 1px;
  background: rgba(164, 174, 198, 0.25);
}

/* Row modifier -- for 3+ app/client logos side by side instead of the
 * default stacked-with-divider layout (e.g. POP3/IMAP mail client logos). */
.cloud-logos-display.cloud-logos-row {
  flex-direction: row;
  flex-wrap: wrap;
  gap: 2.5rem;
}

.cloud-logos-display.cloud-logos-row img {
  max-width: 140px;
  max-height: 100px;
}

/* Small inline provider-logo badges - white rounded background, thin black
 * border, sit inline right after a paragraph (e.g. /managed-cloud's
 * "differentiators" block, "Valitse infrastruktuurisi..."). Deliberately
 * simpler/smaller than .cloud-logos-display above (that one's a large
 * standalone showcase panel; this is a compact inline pair). Sized down
 * from an initial 3x pass per feedback (too large): logo -20%, padding
 * -50%, border nudged up slightly from 1px. */
.infra-logo-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.infra-logo-badge {
  display: inline-flex;
  align-items: center;
  background: white;
  border: 1.5px solid #000;
  border-radius: var(--radius-lg);
  padding: 0.9rem 2.25rem;
}
.infra-logo-badge img {
  height: 67px;
  width: auto;
  display: block;
}

/* SVG illustration display -- no border, no shadow, floats freely.
 * Use for vector illustrations in solutions panels.
 * Add class="solutions-img-svg" + data-animate-on-view="screenshot"
 * so the panel-switch re-trigger logic catches it alongside screenshots.
 */
.solutions-img-svg {
  display: block;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  opacity: 0;
  animation: screenshotReveal 0.9s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
  animation-play-state: paused;
}

.solutions-img-svg.play {
  animation-play-state: running;
}

/* Scoped 30%-smaller modifier for a whole switcher's images (raster
 * screenshots and .solutions-img-svg illustrations alike) - e.g.
 * /kotisivukone's "Yksi tyokalu, monta tapaa kaytta" switcher. More
 * specific than .screenshot-img's own width:100% / .solutions-img-svg's
 * own max-width, so this wins without needing !important. */
.solutions-switcher-scaled-70 .solutions-panel img {
  max-width: 70%;
  margin-left: auto;
  margin-right: auto;
  display: block;
}

/* Placeholder image box (used in /blocks demo when no real screenshot) */
.solutions-img-placeholder {
  background: linear-gradient(135deg, var(--bg-pale) 0%, var(--soft-blue) 100%);
  border-radius: var(--radius-xl);
  min-height: 300px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}

.solutions-img-placeholder::before {
  content: '';
  position: absolute;
  width: 160px;
  height: 160px;
  border-radius: 50%;
  background: rgba(0, 66, 117, 0.05);
  top: -40px;
  right: -40px;
}

.solutions-img-placeholder::after {
  content: '';
  position: absolute;
  width: 90px;
  height: 90px;
  border-radius: 50%;
  background: rgba(39, 170, 225, 0.08);
  bottom: -15px;
  left: -15px;
}

.solutions-img-robot {
  width: 190px;
  position: relative;
  z-index: 1;
}


/* Mobile: same horizontal layout, just smaller buttons */
@media (max-width: 575px) {
  .solutions-nav-btn {
    padding: 0.6rem 1rem;
    font-size: 0.875rem;
    gap: 0.5rem;
  }

  .solutions-nav-icon {
    width: 28px;
    height: 28px;
    font-size: 0.875rem;
  }

  /* Mobile panels: instant switch, no fade (position:absolute not reliable when parent has no height) */
  .solutions-panel {
    display: none;
    position: static;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }

  .solutions-panel.active {
    display: block;
    position: static;
  }
}

/* ===== Illustration override: all illustration_*.svg in hero-image-wrapper =====
 * Covers all illustrations used as hero images (illustration_server_room,
 * illustration_digital_agency, etc.). They are square or landscape-oriented
 * SVGs that need different sizing and keyframe values than tall robot SVGs.
 * The wrapper, trigger (data-animate-on-view) and play mechanism are identical
 * to /webhotellit - only image size and animation bottom values differ.
 *
 * IMPORTANT: override the full `animation` shorthand (not just animation-name)
 * to ensure the correct keyframe is used. The keyframe is defined globally
 * to avoid @keyframes-inside-@media scoping issues across browsers.
 *
 * This is a genuinely separate system from the robot one (base rule,
 * ~line 3136) - deliberately different width/reveal baseline, confirmed
 * intentional (not accidental duplication) since the 5 illustration_*
 * files have wildly inconsistent aspect ratios, unlike the robot family's
 * consistent tall-character shape. See that rule's own comment for the
 * real bug this split caused (2026-09-30) and the resulting naming
 * convention (illustration-raise-N/illustration-lower-N here, vs plain
 * raise-N/lower-N for robots) - check which system a hero actually uses
 * before adding a new modifier.
 */
.hero-image-wrapper img[src*="illustration_"] {
  width: 520px;
  right: 0;
  /* Override the full shorthand so heroIllustration is used,
     not heroRobotRise. animation-play-state is set separately and is unaffected. */
  animation: heroIllustration 3s ease-out forwards;
}

/* Keyframe defined globally (not inside @media) to avoid browser scoping quirks.
 * Start: -600px (fully below section - same as robot start).
 * End:   -100px (~80% of illustration visible above the clipped section bottom).
 * Works for square (500x500) and landscape (425x368) illustrations alike.
 */
@keyframes heroIllustration {
  0%   { bottom: -600px; opacity: 0; }
  100% { bottom: -100px; opacity: 1; }
}

/* Tablet: only adjust sizing; keyframe animation values stay the same */
@media (max-width: 991px) {
  .hero-image-wrapper img[src*="illustration_"] {
    width: 380px;
    right: -20px;
  }
}

/* Mobile: lurk from bottom-right (same as robot behavior) */
@media (max-width: 768px) {
  .hero-image-wrapper img[src*="illustration_"] {
    width: 420px;
    left: 0;
    right: auto;
  }
}

/* Lower-150 variant: same illustration hero, ends 150px further down (only
 * at the widest/default breakpoint - the two @media blocks above already
 * set their own tighter sizing for tablet/mobile and are left as-is).
 * Usage: <div class="hero-image-wrapper lower-150"> */
@media (min-width: 992px) {
  .hero-image-wrapper.lower-150 img[src*="illustration_"] {
    animation-name: heroIllustrationLower150;
  }
}
@keyframes heroIllustrationLower150 {
  0%   { bottom: -600px; opacity: 0; }
  100% { bottom: -250px; opacity: 1; }
}

/* Lower-250 variant - same illustration hero, ends 250px further down than
 * the default -100px (see /managed-cloud). Usage:
 * <div class="hero-image-wrapper illustration-lower-250"> - named
 * differently from the robot system's own .lower-N/.raise-N (used by
 * heroRobotRise, a completely different keyframe/start value) to avoid
 * ambiguity between the two hero image systems sharing a class name. */
@media (min-width: 992px) {
  .hero-image-wrapper.illustration-lower-250 img[src*="illustration_"] {
    animation-name: heroIllustrationLower250;
  }
}
@keyframes heroIllustrationLower250 {
  0%   { bottom: -600px; opacity: 0; }
  100% { bottom: -350px; opacity: 1; }
}

/* Raise-100 variant - same illustration hero, ends 100px higher than the
 * default -100px (see /reseller-cloud). Usage:
 * <div class="hero-image-wrapper illustration-raise-100"> */
@media (min-width: 992px) {
  .hero-image-wrapper.illustration-raise-100 img[src*="illustration_"] {
    animation-name: heroIllustrationRaise100;
  }
}
@keyframes heroIllustrationRaise100 {
  0%   { bottom: -600px; opacity: 0; }
  100% { bottom: 0px; opacity: 1; }
}

/* .img-fluid only sets max-width:100% (never grows an image smaller than its
 * own intrinsic size up to fill the column) - most illustration_*.svg files
 * declare a large enough intrinsic width for that not to matter, but
 * illustration_server_work.svg declares a small 170px intrinsic width, so it
 * rendered far smaller than its sibling illustrations in the same layout (see
 * /ratkaisut). This forces it to fill most of the column like the others -
 * 100% read as too large next to its siblings, so it's 80%, centered. */
.illustration-fill { width: 80%; display: block; margin: 0 auto; }

/* ===== BLOCK: Feature Items - Left Icon Grid =====
 * demo4/demo18 style: icon with soft colored background on the left,
 * heading + text on the right, no card border or background.
 * Grid: col-xl-4 col-md-6 = 3 per row on XL, 2 on LG/MD, 1 on mobile.
 * Usage: <div class="feature-item"> inside a Bootstrap column.
 */
.feature-item {
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
}

.feature-item-icon {
  width: 52px;
  height: 52px;
  min-width: 52px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.625rem;
  flex-shrink: 0;
}

/* Soft-tinted icon color variants for feature-item-icon and product-card icons */
.icon-soft-blue    { background: rgba(39, 170, 225, 0.15); color: var(--blue-dark); }
.icon-soft-primary { background: rgba(0, 66, 117, 0.10);  color: var(--primary); }
.icon-soft-green   { background: rgba(46, 153, 0, 0.13);  color: #1a7a00; }
.icon-soft-orange  { background: rgba(247, 148, 30, 0.15); color: var(--orange-dark); }
.icon-soft-violet  { background: rgba(130, 80, 220, 0.12); color: #8250dc; }
.icon-soft-teal    { background: rgba(20, 175, 160, 0.13); color: #0d998c; }

/* ===== BLOCK: Star Rating (see /asiakaskokemuksia) ===== */
.star-rating { display: inline-flex; gap: 0.25rem; color: var(--orange-dark); font-size: 1.4rem; }
.star-rating-lg { font-size: 2.5rem; gap: 0.35rem; }
.star-rating i { vertical-align: middle; }

/* Gradient-yellow-to-orange star fill (icon-font glyphs need the
   background-clip text trick, same technique used for gradient headings
   elsewhere) - shared by the /frontpage2 review-badge stars and the
   /asiakaskokemuksia hero stars so both read as the same "star rating"
   language across the site. */
.star-rating i.on,
.review-badge-stars i.on {
  background: linear-gradient(180deg, #ffd54f 0%, #ff9800 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

/* ===== BLOCK: Review Rating Banner (see /frontpage2) =====
 * Slim strip under the hero, one badge per review platform (Google,
 * Facebook). Stars reuse the same icon+.on treatment as .pricing-power/
 * .pricing-power-stars (uis-star, the filled Unicons Solid icon, instead
 * of uil-bolt) rather than literal "★★★★★" text, per project owner.
 * .review-rating-banner-inner is a plain flex-wrap row with no
 * breakpoint-specific CSS: two badges fit one line on desktop, and wrap
 * to their own line each on narrow viewports purely from running out of
 * horizontal room - no media query needed. */
.review-rating-banner-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  row-gap: 0.75rem;
  column-gap: 2.5rem;
}

/* Pill badge - white background, shadow-only border (no hard line) so the
   pill shape reads softer, per project owner. Each platform's logo sits
   directly in front of its stars. Now also used as a clickable <a> (each
   pill links out to its own review/result page) - the link-reset rule
   below keeps that from looking like a text link. */
.review-badge-pill {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: nowrap;
  background: white;
  border: none;
  box-shadow: 0 2px 10px rgba(15, 23, 42, 0.09);
  border-radius: 2rem;
  padding: 0.4rem 1rem;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}

a.review-badge-pill,
a.review-badge-pill:hover {
  color: inherit;
  text-decoration: none;
}

a.review-badge-pill:hover {
  box-shadow: 0 4px 16px rgba(15, 23, 42, 0.14);
  transform: translateY(-1px);
}

.review-badge-logo {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

/* Bigger version for the /asiakaskokemuksia Trustmary illustration -
   G/F logos as a visual next to "Älä usko meitä - usko asiakkaitamme". */
.review-badge-logo.review-logo-lg {
  width: 76px;
  height: 76px;
}

/* Wordmark logos (e.g. WP-opas) aren't square - scale by height only so
   the logo isn't squished into the base 20x20 square. */
.review-badge-logo.review-badge-logo-wide {
  width: auto;
  height: 18px;
}

.review-badge-stars {
  display: inline-flex;
  gap: 0.2rem;
  font-size: 1.35rem;
  color: rgba(164, 174, 198, 0.30);
  flex-shrink: 0;
}

/* Small gold trophy icon inside a pill (WP-opas "Voittaja ... 2025 & 2026",
   Core Web Vitals badge) - same gradient-fill technique as .star-rating/
   .review-badge-stars i.on above, just sized down to sit inline with the
   pill's text instead of as a standalone icon. */
.review-badge-trophy {
  font-size: 0.95rem;
  background: linear-gradient(180deg, #ffd54f 0%, #ff9800 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  flex-shrink: 0;
}

/* Individual star reveal (fadeInDown-style, staggered one after another)
 * instead of the whole row fading in at once. Gated on the same
 * .anim-play class main.js's initScrollAnimations() already adds to the
 * ancestor .review-rating-banner-inner (data-anim="fade-in-up") -
 * animation-play-state stays paused until then, same pattern the
 * [data-anim] system itself uses. */
.review-badge-stars i {
  display: inline-block;
  opacity: 0;
  transform: translateY(-12px);
  animation: reviewStarFadeInDown 0.45s ease forwards;
  animation-play-state: paused;
}
/* Generic [data-anim].anim-play, not the specific .review-rating-banner-inner
   class - .review-badge-pill now also appears standalone (e.g.
   /asiakaskokemuksia's Google-reviews pill) under a plain data-anim
   wrapper with a different class name; scoping to one specific ancestor
   class silently left those stars stuck at opacity:0 forever since
   animation-play-state never left "paused". */
[data-anim].anim-play .review-badge-stars i {
  animation-play-state: running;
}
.review-badge-stars i:nth-child(1) { animation-delay: 0.15s; }
.review-badge-stars i:nth-child(2) { animation-delay: 0.27s; }
.review-badge-stars i:nth-child(3) { animation-delay: 0.39s; }
.review-badge-stars i:nth-child(4) { animation-delay: 0.51s; }
.review-badge-stars i:nth-child(5) { animation-delay: 0.63s; }

@keyframes reviewStarFadeInDown {
  from { opacity: 0; transform: translateY(-12px); }
  to   { opacity: 1; transform: translateY(0); }
}

.review-badge-score {
  font-weight: 700;
  font-size: 1rem;
  white-space: nowrap;
}

.review-badge-lead { white-space: nowrap; }

/* Stats Panel spacing override (see /frontpage2) - halves the section's
 * own .py-6 (5rem -> 2.5rem) and each .stat-panel's padding (2rem -> 1rem
 * top/bottom) for this specific placement only. Scoped to
 * .stats-banner-compact rather than editing .py-6/.stat-panel directly,
 * since both are shared utility/block classes used elsewhere at their
 * normal spacing. */
section.stats-banner-compact.py-6 {
  padding-top: 2.5rem !important;
  padding-bottom: 2.5rem !important;
}

.stats-banner-compact .stat-panel {
  padding-top: 1rem;
  padding-bottom: 1rem;
}

/* Generic "boxed panel" section: same rounded-corners, contained-to-
 * .container-width treatment as hero-sunset-simple/product-suite-glow-boxed,
 * but keeps the section's OWN existing background instead of swapping in a
 * photo - move whatever background the section already had (a bg-pale
 * class, or a bespoke rule) from the <section> onto its .container along
 * with this. Usage: add .section-boxed to the <section>. */
.section-boxed > .container {
  /* Match hero-sunset-simple's .container exactly: flat max-width instead
   * of Bootstrap's stepped breakpoints, so this box is precisely as wide as
   * the hero at every viewport size, not narrower at md/lg per feedback -
   * fine if that means full viewport width below 1320px, same as the hero. */
  max-width: 1320px;
  overflow: hidden;
  border-radius: var(--radius-xl);
  padding: 3.5rem 3rem;
}

/* Stats banner: same light-blue tint as before (moved here from the
 * .stats-banner-compact.py-6 rule above, since the background now lives on
 * the boxed .container, not the full-width section), tighter padding to
 * match this block's already-compact spirit (see the halved py-6 above). */
.stats-banner-compact.section-boxed > .container {
  background: rgba(39, 170, 225, 0.06);
  padding: 2rem;
}

/* Narrow phones: with no col-md-6/col-lg-3 match below 768px, the 4 stat
 * panels fall back to Bootstrap's content-sized flex default instead of
 * an even grid - force a real 2-per-row layout and scale each panel's
 * icon/number/label down so both columns fit without wrapping oddly. */
@media (max-width: 575px) {
  .stats-banner-compact .row > .col-lg-3 {
    flex: 0 0 50% !important;
    max-width: 50% !important;
  }
  .stats-banner-compact.section-boxed > .container {
    padding: 1.25rem 1rem;
  }
  .stats-banner-compact .stat-panel {
    padding: 0.5rem 0.25rem;
  }
  .stats-banner-compact .stat-icon {
    width: 48px;
    height: 48px;
    font-size: 1.15rem;
    margin-bottom: 0.6rem;
  }
  .stats-banner-compact .stat-number {
    font-size: 1.6rem;
    margin-bottom: 0.25rem;
  }
  .stats-banner-compact .stat-label {
    font-size: 0.8rem;
  }
}

/* ===== BLOCK: Product Suite background photo (see /frontpage2) =====
 * "Kaikki mitä tarvitset, yhdeltä kumppanilta" background. Two earlier
 * CSS-only attempts (soft gradient blobs, then hand-drawn radial-gradient
 * wave lines) didn't get the fluid abstract-wave-art look the project
 * owner wanted - true flowing bezier-curve line art isn't something plain
 * CSS gradients can draw, so this now uses the supplied photo
 * (img/backgrounds/bg_waveflow_red.jpg) as a real background image
 * instead. A top-down white fade keeps the heading/lead/domain-search
 * area (the top of the section) readable; the photo shows fully behind
 * the product grid further down since every tile there is an opaque
 * white .soft-card anyway. Static (no animation) - it's a single photo,
 * not a repeatable tile. */
.product-suite-glow {
  position: relative;
  overflow: hidden;
}
.product-suite-glow > .container {
  position: relative;
  z-index: 1;
}
.product-suite-glow-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to bottom, rgba(255,255,255,0.92) 0%, rgba(255,255,255,0.55) 22%, rgba(255,255,255,0.15) 42%, rgba(255,255,255,0) 60%),
    url('/img/backgrounds/bg_waveflow_red.jpg');
  background-size: cover, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
}

/* Boxed variant: background + rounded corners scoped to the .container's
 * own width (like hero-sunset-simple), instead of spanning the full
 * viewport - trying this on home.html's "Kaikki mitä tarvitset" block per
 * feedback. .product-suite-glow-bg moves to be the .container's own first
 * child (not a sibling before it) so its inset:0 sizes to the container's
 * box - .container is already position:relative (rule above), so no other
 * change is needed there for the bg to size correctly. The real content
 * needs its own stacking-context wrapper (.product-suite-glow-boxed-content)
 * because a plain, non-positioned sibling paints BELOW a position:absolute
 * z-index:0 sibling in CSS stacking order - without it, the photo would
 * cover the heading/cards instead of sitting behind them. */
.product-suite-glow-boxed > .container {
  /* Match hero-sunset-simple's .container exactly: flat max-width instead
   * of Bootstrap's stepped breakpoints, so this box is precisely as wide as
   * the hero at every viewport size, not narrower at md/lg per feedback -
   * fine if that means full viewport width below 1320px, same as the hero. */
  max-width: 1320px;
  overflow: hidden;
  border-radius: var(--radius-xl);
  padding: 3.5rem 3rem;
}
.product-suite-glow-boxed .product-suite-glow-bg {
  border-radius: inherit;
}
.product-suite-glow-boxed-content {
  position: relative;
  z-index: 1;
}

/* Second try, same block, per project owner ("let's try another background
   too") - bg_waveflow_blue.jpg is a dark navy photo rather than a light
   pastel one, so instead of a white top-fade this variant tints slightly
   darker at the top (insurance in case a lighter wave streak lands under
   the heading) and flips the heading/eyebrow/lead and domain-search input
   to the same light-on-dark treatment already used elsewhere on dark
   sections (see .domain-cta-dark-bg's "Middle CTA: domain search" block on
   /yllapidetty-wordpress for the same .cta-dark-domain-input pattern). */
.product-suite-glow-blue-bg {
  background-image:
    linear-gradient(to bottom, rgba(2,8,30,0.45) 0%, rgba(2,8,30,0.1) 35%, transparent 55%),
    url('/img/backgrounds/bg_waveflow_blue.jpg');
  background-size: cover, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
}
.product-suite-glow-blue .eyebrow { color: rgba(255,255,255,0.65) !important; }
.product-suite-glow-blue h2 { color: white; -webkit-text-fill-color: white; }
.product-suite-glow-blue .lead { color: rgba(255,255,255,0.85); }
/* Extends the same light-on-dark treatment to a plain h3 sub-heading and
 * .text-muted intro paragraph - needed on /managed-cloud and
 * /reseller-cloud's pricing sections, which use h3 (not h2) for the
 * per-provider "UpCloud"/"Hetzner" heading below the shared h2 intro. */
.product-suite-glow-blue h3 { color: white; -webkit-text-fill-color: white; }
.product-suite-glow-blue .text-muted { color: rgba(255,255,255,0.75) !important; }

/* Third try - bg_waveflow_blue2.jpg's wave shapes sit in the bottom third
   of the photo (unlike red/blue, which have interest through the middle),
   so "center" would crop the actual waves out on tall viewports - anchored
   to "center bottom" instead so they stay in frame. Light image (like red),
   same white top-fade treatment. */
.product-suite-glow-blue2-bg {
  background-image:
    linear-gradient(to bottom, rgba(255,255,255,0.92) 0%, rgba(255,255,255,0.55) 22%, rgba(255,255,255,0.15) 42%, rgba(255,255,255,0) 60%),
    url('/img/backgrounds/bg_waveflow_blue2.jpg');
  background-size: cover, cover;
  background-position: center, center bottom;
  background-repeat: no-repeat, no-repeat;
}

/* Fourth try - bg_waveflow_rainbow.webp is a genuinely transparent PNG-turned-
   WebP (thin gradient wire lines, no filled background) exported from a
   design tool, so unlike the photo variants above it needs its own solid
   background-color for the transparent areas to composite against - a light
   blue reads better than plain white (tested both), giving the block a
   distinct identity of its own rather than blending into the page. */
.product-suite-glow-rainbow-bg {
  background-color: #dbeeff;
  background-image:
    linear-gradient(to bottom, rgba(219,238,255,0.6) 0%, rgba(219,238,255,0.15) 30%, rgba(219,238,255,0) 55%),
    url('/img/backgrounds/bg_waveflow_rainbow.webp');
  background-size: cover, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
}

/* On-dark-glow: for an eyebrow/heading pair sitting far enough down a tall
   product-suite-glow section that the white top-fade has fully dissipated,
   exposing the raw (here, purple-toned) photo underneath - home.html's
   "Vai aloitetaanko näistä?" second heading group, below "Supernopeat
   WordPress-webhotellit"'s cards. Same light-on-dark convention as
   .product-suite-glow-blue. */
.on-dark-glow .eyebrow {
  color: rgba(255, 255, 255, 0.75);
}
.on-dark-glow .eyebrow-dot::before {
  background: rgba(255, 255, 255, 0.75);
}
.on-dark-glow h2 {
  color: #ffffff;
  -webkit-text-fill-color: #ffffff;
}

/* ===== BLOCK: Server Map with Pinned Locations (see /palvelinymparisto) =====
 * Percentage-based left/top on .server-map-pin keeps every pin locked to its
 * real spot on the map image at any viewport width, since the image itself
 * scales fluidly (width:100%) without cropping - the % coordinates always
 * refer to the same point on the image regardless of its rendered size.
 *
 * Redesigned 2026-09-23: teardrop map-pointer marker (was a plain dot -
 * per feedback a "map pointer" reads better than a small balloon), city
 * name/provider/detail shown in a popover on hover/focus only (was an
 * always-visible text label next to every dot, per feedback that's too
 * busy at this many pins) - color-coded to the real provider (UpCloud
 * blue / Hetzner red, matching Hetzner's own brand red) instead of an
 * arbitrary primary/secondary split, plus a two-tone "both" marker for
 * Helsinki, which has one Hetzner DC and two UpCloud zones in the same
 * city.
 *
 * .server-map-pin has NO transform - its own left/top:0 corner sits
 * exactly on the % coordinate. The marker's negative margin (classic
 * CSS-pin technique: a rotated square with one square corner, sized so
 * its point lands on the anchor) does the offsetting instead, so the
 * popover (anchored to the same .server-map-pin box) can position off
 * that same real coordinate without re-deriving an offset. */
.server-map-wrap { position: relative; }
.server-map-wrap img { width: 100%; display: block; }
.server-map-pin {
  position: absolute;
  z-index: 1;
}
.server-map-pin-marker {
  position: absolute;
  left: 0;
  top: 0;
  width: 26px;
  height: 26px;
  margin: -26px 0 0 -13px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  border: 2px solid #ffffff;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
  cursor: pointer;
}
.server-map-pin-marker::after {
  content: '';
  position: absolute;
  top: 6px;
  left: 6px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #ffffff;
}
.server-map-pin-marker.provider-upcloud { background: var(--blue-dark); }
.server-map-pin-marker.provider-hetzner { background: #d50c2d; }
.server-map-pin-marker.provider-both {
  background: linear-gradient(135deg, var(--blue-dark) 50%, #d50c2d 50%);
}
.server-map-pin-popover {
  position: absolute;
  bottom: 32px;
  left: 0;
  min-width: 170px;
  padding: 0.55rem 0.85rem;
  border-radius: var(--radius-sm, 0.4rem);
  background: #ffffff;
  box-shadow: var(--shadow-md);
  text-align: left;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translate(-50%, 4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 5;
}
.server-map-pin:hover .server-map-pin-popover,
.server-map-pin:focus-within .server-map-pin-popover,
.server-map-pin-marker:focus + .server-map-pin-popover {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(-50%, 0);
}
.server-map-pin-popover::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #ffffff;
}
.server-map-pin-popover-city {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 1rem;
  font-weight: 700;
  color: var(--text-dark);
  margin-bottom: 0.2rem;
}
.server-map-pin-popover-city-flag {
  width: 16px;
  height: 11px;
  flex-shrink: 0;
  border-radius: 2px;
}
/* Stacked lines, not a <ul> - a real list adds default browser
   bullet/indent padding that fights the "no spacing between lines" ask;
   the small round dot below is a controlled custom marker instead, not a
   browser list-style bullet, so it doesn't reintroduce that problem. */
.server-map-pin-popover-loc {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.35;
  margin: 0;
  padding: 0;
}
.server-map-pin-popover-loc.provider-upcloud { color: var(--blue-dark); }
.server-map-pin-popover-loc.provider-hetzner { color: #d50c2d; }

/* Small round bullet in front of each location line - reuses currentColor
   so it automatically matches whichever provider color the line above
   already sets, no separate per-provider color rule needed. */
.server-map-pin-popover-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
  display: inline-block;
}
.icon-soft-dark    { background: rgba(255, 255, 255, 0.14); color: #cfeaff; }

/* Original near-black navy gradient for the main domain-search CTA block
   ("Tilaa X nyt") - kept as-is per feedback; only its "already have a
   domain" sub-block below gets the lighter treatment (.domain-cta-subblock-bg). */
.domain-cta-dark-bg {
  background: linear-gradient(180deg, #002455 0%, var(--primary) 60%, #003060 100%);
  position: relative;
}

/* Lighter navy gradient for domain-tld-extra-actions' dark sub-block only
   ("Tilaus onnistuu myös ilman uutta verkkotunnusta") - reuses --blue-dark
   for the middle stop instead of --primary. */
.domain-cta-subblock-bg {
  background: linear-gradient(180deg, #0a3d70 0%, var(--blue-dark) 60%, #0f4f8c 100%);
  position: relative;
}

/* ===== BLOCK: Product Cards (solution cards with CTA button) =====
 * White card on bg-pale section. Colored icon via .feature-item-icon + icon-soft-*.
 * flex-direction: column keeps the button pinned to the bottom of each card.
 * Grid: col-xl-4 col-md-6 = 3 per row on XL, 2 on LG/MD, 1 on mobile.
 * Usage: <div class="product-card"> inside a Bootstrap column.
 */
.product-card {
  background: white;
  border-radius: var(--radius-lg);
  padding: 2rem;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.3s ease, transform 0.3s ease;
  height: 100%;
  display: flex;
  flex-direction: column;
  border: 1px solid rgba(164, 174, 198, 0.15);
}

.product-card:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-hover);
  border-color: rgba(39, 170, 225, 0.2);
}

/* ===== BLOCK: Project Cards (icon-figure top + text body + inline button) =====
 * demo11 "Case Studies" style: top colored band with large white icon, then
 * card body with heading, text, and a normal-width inline button.
 * No full-width button, no pinned-bottom button -- button sits naturally after text.
 * Grid: col-xl-4 col-md-6 = 3 per row on XL, 2 on LG/MD, 1 on mobile.
 * Usage: <div class="project-card"> inside a Bootstrap column.
 */
.project-card {
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid rgba(164, 174, 198, 0.2);
  background: white;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.3s ease, transform 0.3s ease;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.project-card:hover {
  box-shadow: var(--shadow-hover);
  transform: translateY(-4px);
}

/* Top figure: colored band with a large centered icon */
.project-card-figure {
  width: 100%;
  height: 150px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.project-card-figure i {
  font-size: 3.75rem;
  color: rgba(255, 255, 255, 0.92);
}

.project-card-body {
  padding: 1.5rem;
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Card figure background gradient variants */
.card-bg-primary { background: linear-gradient(135deg, var(--primary)    0%, var(--blue-dark)  100%); }
.card-bg-blue    { background: linear-gradient(135deg, var(--blue-dark)  0%, var(--blue-light) 100%); }
.card-bg-teal    { background: linear-gradient(135deg, #0d998c           0%, #14afa0           100%); }
.card-bg-orange  { background: linear-gradient(135deg, var(--orange-dark) 0%, #FFB347           100%); }
.card-bg-violet  { background: linear-gradient(135deg, #6635c8           0%, #8250dc           100%); }
.card-bg-green   { background: linear-gradient(135deg, #1a7a00           0%, var(--accent)     100%); }

/* ===== BLOCK: Target Group Cards (demo16 "Latest Projects" style) =====
 * Used on /ratkaisut: pastel gradient background, robot peeking from bottom-right.
 * Text constrained to left area so robot stays visible.
 * Grid: col-lg-6 = 2 per row on desktop, 1 on mobile.
 * Usage: <div class="target-card {{ card_color }}"> inside a Bootstrap column.
 */
.target-card {
  border-radius: var(--radius-lg);
  overflow: hidden;
  position: relative;
  padding: 2.5rem;
  /* No min-height: cards size to their content */
  display: flex;
  flex-direction: column;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  box-shadow: var(--shadow-sm);
}

.target-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
}

/* Text wrapper: constrained width so robot has room on right */
.target-card-content {
  max-width: 62%;
  position: relative;
  z-index: 1;
}

/* Illustration floats from bottom-right, partially clipped by overflow:hidden.
 * Illustrations are larger than robots to give more visual breathing room.
 * Landscape illustrations (425x368) at 260px display width ~225px tall. */
.target-card-illustration {
  position: absolute;
  bottom: -30px;
  right: -20px;
  width: 260px;
  height: auto;
  opacity: 0.9;
  pointer-events: none;
}

/* Lighter, demo16-inspired pastel background variants */
.target-card-blue   { background: linear-gradient(135deg, #f0f8fe 0%, #e4f3fd 100%); }
.target-card-teal   { background: linear-gradient(135deg, #f0fbf9 0%, #e3f8f3 100%); }
.target-card-violet { background: linear-gradient(135deg, #f7f3fd 0%, #ede5fb 100%); }
.target-card-orange { background: linear-gradient(135deg, #fefaf2 0%, #fdf3e2 100%); }

/* Brand-color variants (second set on /ratkaisut) */
.target-card-brand-light   { background: linear-gradient(135deg, var(--soft-blue) 0%, #cde7f5 100%); }
.target-card-brand-primary { background: linear-gradient(135deg, var(--primary) 0%, #005fa8 100%); }
.target-card-brand-sunset  {
  background: radial-gradient(
    ellipse 130% 90% at 50% 110%,
    rgba(255, 160, 60, 0.7) 0%, rgba(255, 120, 40, 0.5) 15%,
    rgba(39, 170, 225, 0.25) 50%, transparent 100%
  ), linear-gradient(135deg, #f6f8fa 0%, #eaf5fc 100%);
}
.target-card-brand-dark    { background: linear-gradient(135deg, #1e2343 0%, #2b3365 100%); }

/* Dark-card text overrides: headings, body text, eyebrow, button */
.target-card-brand-primary .target-card-content h4,
.target-card-brand-dark .target-card-content h4    { color: white; }
.target-card-brand-primary .target-card-content p,
.target-card-brand-dark .target-card-content p     { color: rgba(255, 255, 255, 0.82); }
.target-card-brand-primary .target-card-content [class*="eyebrow"],
.target-card-brand-dark .target-card-content [class*="eyebrow"] {
  color: rgba(255, 255, 255, 0.65) !important;
}
.target-card-brand-primary .btn-light,
.target-card-brand-dark .btn-light {
  background: rgba(255, 255, 255, 0.95);
  color: var(--primary);
  border: none;
}

/* Mobile: show full-width text, fade illustration */
@media (max-width: 767px) {
  .target-card-content {
    max-width: 100%;
  }
  .target-card-illustration {
    opacity: 0.2;
    width: 180px;
  }
}

/* ===== BLOCK: Product Tiles (demo14 OUR PROJECTS style) =====
 * No card background or border -- icon square at top, text below.
 * Grid: col-xl-4 col-md-6 = 3 per row on XL, 2 on LG/MD, 1 on mobile.
 * Usage: <div class="product-tile"> inside a Bootstrap column.
 * card_color class (card-bg-*) applies gradient to .product-tile-icon.
 */
.product-tile-icon {
  width: 88px;
  height: 88px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1.25rem;
  font-size: 2.5rem;
}

.product-tile-icon i {
  color: rgba(255, 255, 255, 0.92);
}

/* ===== Hero Carousel Navigation Arrows =====
 * Subtle arrow-only buttons (no circle). Only shown on XL screens where there
 * is enough space to place them without overlapping slide content.
 */
.hero-image-carousel .carousel-control-prev,
.hero-image-carousel .carousel-control-next {
  width: 36px;
  height: 36px;
  background: none;
  border-radius: 0;
  box-shadow: none;
  top: 50%;
  transform: translateY(-50%);
  bottom: auto;
  opacity: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 5;
  transition: opacity 0.2s ease;
}

.hero-image-carousel .carousel-control-prev { left: 0.25rem; }
.hero-image-carousel .carousel-control-next { right: 0.25rem; }

.hero-image-carousel .carousel-control-prev:hover,
.hero-image-carousel .carousel-control-next:hover { opacity: 0.75; }

.hero-image-carousel .carousel-control-prev .uil,
.hero-image-carousel .carousel-control-next .uil {
  font-size: 2rem;
  color: rgba(255, 255, 255, 0.9);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.22);
  line-height: 1;
}

/* Show on LG and XL screens (992px+) -- hide on tablet/mobile */
@media (max-width: 991px) {
  .hero-image-carousel .carousel-control-prev,
  .hero-image-carousel .carousel-control-next {
    display: none;
  }
}

/* ===== Full-width target-card variant =====
 * Cards in a col-12 column are much wider so the illustration and content width are adjusted.
 * No min-height override: the card sizes to its content.
 */
.col-12 .target-card-illustration {
  width: 340px;
}

.col-12 .target-card-content {
  max-width: 55%;
}

/* ===== BLOCK: Product Visual Cards (large figure + watermark icon) =====
 * Replaces product-tile for a more visual appearance:
 * top figure area (200px) with gradient background, centered icon, and a
 * large semi-transparent watermark icon in the corner.
 * Grid: col-xl-4 col-md-6 = 3/2/1. Row gap: g-4 gy-5.
 */
.product-visual-card {
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid rgba(164, 174, 198, 0.2);
  background: white;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.3s ease, transform 0.3s ease;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.product-visual-card:hover {
  box-shadow: var(--shadow-hover);
  transform: translateY(-4px);
}

.product-visual-figure {
  position: relative;
  width: 100%;
  height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
}

/* Centered icon, solid and visible (2x larger than previous) */
.product-visual-figure .pv-icon {
  position: relative;
  z-index: 2;
  font-size: 7rem;
  color: rgba(255, 255, 255, 0.92);
}

/* Large watermark: same icon scaled up, semi-transparent, bottom-right corner (2x) */
.product-visual-figure .pv-watermark {
  position: absolute;
  right: -30px;
  bottom: -30px;
  font-size: 18rem;
  color: rgba(255, 255, 255, 0.10);
  line-height: 1;
  z-index: 1;
  pointer-events: none;
}

.product-visual-body {
  padding: 1.5rem;
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* ===== BLOCK: Why Choose (demo19 vertical tabs style) =====
 * Left column: numbered vertical tabs. Right column: content panels.
 * Mobile: tabs collapse to horizontal row at top, content below.
 */
.why-choose-nav {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.why-choose-tab {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 1.1rem 1.25rem;
  border: none;
  border-left: 3px solid rgba(164, 174, 198, 0.2);
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease;
  font-family: 'Manrope', sans-serif;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.why-choose-tab:hover:not(.active) {
  background: rgba(0, 66, 117, 0.03);
  border-left-color: rgba(0, 66, 117, 0.3);
}

.why-choose-tab.active {
  border-left-color: var(--primary);
  background: rgba(0, 66, 117, 0.05);
}

.why-choose-number {
  font-size: 1.625rem;
  font-weight: 900;
  font-family: 'Raleway', sans-serif;
  color: rgba(164, 174, 198, 0.45);
  min-width: 2.25rem;
  line-height: 1;
  transition: color 0.2s;
}

.why-choose-tab.active .why-choose-number {
  color: var(--primary);
}

.why-choose-label {
  font-weight: 700;
  color: var(--text-body);
  transition: color 0.2s;
}

.why-choose-tab.active .why-choose-label {
  color: var(--text-dark);
}

/* Content panels: hidden by default, shown when active */
.why-choose-content {
  position: relative;
}

/* Base: hidden, NO animation (animation is declared only on .active to prevent
 * fill-mode:both from keeping non-active panels at opacity:0) */
.why-choose-panel {
  display: none;
  animation: none;
}

/* Mobile: horizontal tab list + full-width content */
@media (max-width: 991px) {
  .why-choose-nav {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 2rem;
    border-left: none;
  }
  .why-choose-tab {
    border-left: none;
    border-bottom: 2px solid rgba(164, 174, 198, 0.2);
    border-radius: var(--radius-sm);
    padding: 0.6rem 1rem;
    background: var(--bg-pale);
    flex: 0 0 auto;
  }
  .why-choose-tab.active {
    background: var(--primary);
    border-color: var(--primary);
  }
  .why-choose-tab.active .why-choose-number,
  .why-choose-tab.active .why-choose-label {
    color: white;
  }
  .why-choose-number { font-size: 0.875rem; min-width: auto; }
  .why-choose-label { font-size: 0.9rem; }
}

/* ===== BLOCK: Sticky Feature Section (demo29 style) =====
 * Desktop: robot stays fixed while features scroll right.
 * The robot "lurks" from the bottom of the viewport -- only the upper body
 * is visible, lower body extends below the section edge and is covered by
 * the next section's solid background.
 * Mobile: sticky column is hidden (d-none d-lg-block); content stacks.
 *
 * IMPORTANT: the parent row must NOT have align-items-start or align-items-center.
 * Default stretch allows the left column to match the content column's full height,
 * which gives the sticky element room to scroll within.
 */
.sticky-feature-image {
  position: sticky;
  top: 90px;               /* just below the fixed navbar */
  height: calc(100vh - 90px);  /* fill from just below navbar to viewport bottom */
  display: flex;
  align-items: flex-start; /* robot top aligns with the intro text level */
  padding-top: 60px;       /* matches section's py-6 padding, placing robot at eyebrow level */
  overflow: visible;       /* next section's solid bg covers any bottom overflow */
  pointer-events: none;
}

/* Large robot image -- fixed size, no img-fluid.
 * margin-bottom: -120px makes the robot visually extend 120px below the sticky
 * section's boundary. The next section's solid bg-pale background covers that
 * portion, creating the "peeking from behind" effect. */
.sticky-feature-robot {
  display: block;
  width: 680px;
  max-width: none;
  margin-bottom: -120px;
  filter: drop-shadow(0 20px 40px rgba(0, 66, 117, 0.07));
  pointer-events: none;
}

/* Scale down robot on LG screens (col-lg-5 is narrower) */
@media (max-width: 1399px) {
  .sticky-feature-robot { width: 540px; }
}
@media (max-width: 1199px) {
  .sticky-feature-robot { width: 440px; }
}

/* Each feature row with a subtle separator line */
.sticky-feature-item {
  padding-bottom: 2.5rem;
  margin-bottom: 2.5rem;
  border-bottom: 1px solid rgba(164, 174, 198, 0.15);
}

.sticky-feature-item:last-child {
  border-bottom: none;
  margin-bottom: 0;
  padding-bottom: 0;
}

/* Fix: product-visual-body buttons stretch to full width in a flex column.
 * Apply align-self: flex-start so the button only takes its natural width. */
.product-visual-body .btn {
  align-self: flex-start;
}

/* Why-choose active panel: fade + rise animation when becoming visible */
.why-choose-panel.active {
  display: block;
  animation: scrollFadeInUp 0.35s ease both;
}

/* ===== BLOCK: Parallax Background Section =====
 * Full-width section with a fixed background image and a dark overlay.
 * The background-attachment:fixed creates the parallax scroll effect.
 * All text inside must be white/light -- use .text-white utilities.
 * Usage: <section class="section-bg-parallax py-6"> + container > .section-bg-parallax-content
 */
.section-bg-parallax {
  position: relative;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-attachment: fixed; /* parallax */
}

/* Dark tinted overlay — removed per design request; re-add with lighter opacity if text is unreadable */
.section-bg-parallax::before {
  content: none;
  display: none;
}

/* Opt-in tint modifier - being tried on /verkkokaupat's "Etsitkö
 * yritysratkaisuja?" section first. Lighter than .hero-image-simple's
 * 0.55 dark tint so the photo still reads clearly behind the text. */
.section-bg-parallax.tint::before {
  content: '';
  display: block;
  position: absolute;
  inset: 0;
  background: rgba(0, 30, 60, 0.35);
  pointer-events: none;
  z-index: 0;
}

/* Content sits above overlay */
.section-bg-parallax .container {
  position: relative;
  z-index: 1;
}

/* Text colour overrides for dark backgrounds */
.section-bg-parallax h2,
.section-bg-parallax h3 {
  color: white;
  -webkit-text-fill-color: white;  /* override gradient text on headings */
  /* Text shadow makes white headings stand out from light areas of background photo */
  text-shadow:
    0 2px 14px rgba(0, 0, 0, 0.65),
    0 0 35px rgba(0, 0, 0, 0.40),
    0 1px 4px rgba(0, 0, 0, 0.70);
}

.section-bg-parallax .eyebrow {
  color: rgba(255, 255, 255, 0.65) !important;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.55);
}

.section-bg-parallax .lead {
  color: rgba(255, 255, 255, 0.85);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.55), 0 0 20px rgba(0, 0, 0, 0.30);
}

.section-bg-parallax p:not(.eyebrow):not(.lead) {
  color: rgba(255, 255, 255, 0.80);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.55), 0 0 20px rgba(0, 0, 0, 0.30);
}

/* .check-list defaults to --text-body (dark) with a barely-visible border,
   both unreadable directly on a photo background with no dark overlay. */
.section-bg-parallax .check-list li {
  color: rgba(255, 255, 255, 0.90);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.55), 0 0 20px rgba(0, 0, 0, 0.30);
  border-bottom-color: rgba(255, 255, 255, 0.18);
}

/* Mobile: background-attachment:fixed often doesn't work on iOS */
@media (max-width: 767px) {
  .section-bg-parallax {
    background-attachment: scroll;
  }
}

/* The conflicting old mobile block was removed.
 * Mobile handling for mega-solution-cards is consolidated in the
 * block near .mega-solution-card desktop rules, further below. */

/* ===== BLOCK: Dark Glow Section (Google One inspired) =====
 * White background fades to near-black as the section scrolls into view.
 * A single soft blue/violet radial glow animates gently behind true-black panels.
 * The heading sits inside a black pill with a cycling rainbow glow shadow.
 * .is-dark is toggled by initDarkGlowSection() in main.js (IntersectionObserver).
 * Usage: <section class="dark-glow-section py-6">
 *   <div class="glow-canvas"></div>
 *   <div class="container">
 *     <span class="glow-heading-pill"><h2 class="typewriter-once" data-text="...">...</h2></span>
 *     ...
 *   </div>
 * </section>
 */
.dark-glow-section {
  position: relative;
  background: white;
  transition: background 1.2s cubic-bezier(0.25, 0, 0.25, 1);
  overflow: hidden;
  z-index: 0;
}

.dark-glow-section.is-dark { background: #070d1a; }

/* Boxed variant (see /frontpage2's AI Idea Chat block) - background sized
   like the page-width rounded hero (.hero-image-style: max-width 1320px,
   centered, rounded corners) instead of spanning the full viewport width.
   overflow:hidden already on .dark-glow-section clips glow-canvas to the
   rounded corners here too. */
.dark-glow-section.dark-glow-boxed {
  max-width: 1320px;
  margin: 0 auto;
  border-radius: var(--radius-xl);
}

/* Single centered blue-violet glow — appears on .is-dark, moves gently */
.dark-glow-section .glow-canvas {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    ellipse 80% 65% at 50% 68%,
    rgba(4, 123, 193, 0.58) 0%,
    rgba(80, 30, 160, 0.22) 45%,
    transparent 72%
  );
  opacity: 0;
  transition: opacity 1.4s ease;
  pointer-events: none;
  z-index: 0;
  animation: glowMove 16s ease-in-out infinite;
}

.dark-glow-section.is-dark .glow-canvas { opacity: 1; }

@keyframes glowMove {
  0%   { transform: translate(  0%,   0%) scale(1.00); }
  12%  { transform: translate(  5%,  -4%) scale(1.04); }
  25%  { transform: translate(  3%,  -8%) scale(1.07); }
  37%  { transform: translate( -3%,  -4%) scale(1.02); }
  50%  { transform: translate( -5%,   0%) scale(0.97); }
  62%  { transform: translate( -3%,   5%) scale(0.94); }
  75%  { transform: translate(  3%,   8%) scale(0.97); }
  87%  { transform: translate(  5%,   4%) scale(1.01); }
  100% { transform: translate(  0%,   0%) scale(1.00); }
}

/* Content above canvas */
.dark-glow-section .container { position: relative; z-index: 1; }

/* Headings + body transition light → white */
.dark-glow-section h2,
.dark-glow-section h3 { transition: color 1.2s ease, -webkit-text-fill-color 1.2s ease; }
.dark-glow-section.is-dark h2,
.dark-glow-section.is-dark h3 { color: white !important; -webkit-text-fill-color: white !important; }

.dark-glow-section .lead { transition: color 1.2s ease; }
.dark-glow-section.is-dark .lead { color: rgba(255,255,255,0.75); }

.dark-glow-section .eyebrow { transition: color 1.2s ease; }
.dark-glow-section.is-dark .eyebrow { color: rgba(255,255,255,0.60) !important; }

/* Heading pill: transparent on light bg, black + rainbow glow when dark */
.glow-heading-pill {
  display: inline-block;
  background: transparent;
  border-radius: 999px;
  padding: 0.75rem 2.25rem;
  transition: background 1.2s ease;
}

.dark-glow-section.is-dark .glow-heading-pill {
  background: #000;
  animation: pillGlow 7s linear infinite;
}

@keyframes pillGlow {
  0%   { box-shadow: 0 0 28px 8px rgba(6,182,212,0.62),  0 0 60px 18px rgba(6,182,212,0.20); }
  25%  { box-shadow: 0 0 28px 8px rgba(129,140,248,0.62), 0 0 60px 18px rgba(129,140,248,0.20); }
  50%  { box-shadow: 0 0 28px 8px rgba(236,72,153,0.62),  0 0 60px 18px rgba(236,72,153,0.20); }
  75%  { box-shadow: 0 0 28px 8px rgba(245,158,11,0.62),  0 0 60px 18px rgba(245,158,11,0.20); }
  100% { box-shadow: 0 0 28px 8px rgba(6,182,212,0.62),  0 0 60px 18px rgba(6,182,212,0.20); }
}

/* Glow panels: light bg → true black */
.glow-panel {
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid rgba(164, 174, 198, 0.15);
  border-radius: 1.25rem;
  padding: 2rem;
  height: 100%;
  transition: background 1.2s ease, border-color 1.2s ease, box-shadow 0.3s ease;
  /* Flex column so lurk image is pushed to bottom */
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}

.dark-glow-section.is-dark .glow-panel {
  background: #000;
  border-color: rgba(255, 255, 255, 0.07);
}

.dark-glow-section.is-dark .glow-panel:hover {
  border-color: rgba(39, 170, 225, 0.38);
  box-shadow: 0 0 28px rgba(4, 123, 193, 0.22);
}

.glow-panel h4 { transition: color 1.2s ease, -webkit-text-fill-color 1.2s ease; }
.dark-glow-section.is-dark .glow-panel h4 { color: white !important; -webkit-text-fill-color: white !important; }

.glow-panel p { transition: color 1.2s ease; }
.dark-glow-section.is-dark .glow-panel p { color: rgba(255,255,255,0.75); }

.glow-panel .eyebrow { transition: color 1.2s ease; }
.dark-glow-section.is-dark .glow-panel .eyebrow { color: rgba(255,255,255,0.55) !important; }

.glow-panel-icon {
  width: 52px; height: 52px;
  border-radius: 0.75rem;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.75rem;
  margin-bottom: 1.25rem;
  flex-shrink: 0;
}

/* Image lurking from the bottom of a glow-panel card.
 * Appears only on .is-dark. Use margin: auto -2rem -2rem to extend to card edges.
 * Gradient overlay fades the top of the image into the black card background.
 */
.glow-panel-lurk {
  /* Push to bottom of flex card via margin-top: auto; extend to all card edges */
  margin-top: auto;
  margin-left: -2rem;
  margin-right: -2rem;
  margin-bottom: -2rem;
  height: 250px; /* Was 150px — increased so more image is visible at card bottom */
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  border-radius: 0 0 1.25rem 1.25rem;
  opacity: 0;
  transition: opacity 1.2s ease;
}

.dark-glow-section.is-dark .glow-panel-lurk { opacity: 1; }

.glow-panel-lurk img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top; /* show top of screenshot */
  display: block;
}

/* Robot SVG: 15% smaller (408px), top anchored so head/shoulders visible in lurk window */
.glow-panel-lurk img.lurk-svg {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  height: 408px;   /* 15% smaller than 480px */
  width: auto;
  object-fit: initial;
}

/* Card 1 (robot): top:0px — shows maximum of the robot head area */
.glow-panel-card-1 .glow-panel-lurk img.lurk-svg {
  top: 0;
  bottom: auto;
}

/* Card 2 (Smart Updates): 10% smaller view; start from top of screenshot */
.glow-panel-card-2 .glow-panel-lurk { height: 225px; }
.glow-panel-card-2 .glow-panel-lurk img { object-position: center top; }

/* Card 3 (monitoring screenshot): screenshot-frame style with grey rounded border */
.glow-panel-card-3 .glow-panel-lurk {
  height: 185px;
  margin-left: 0;
  margin-right: 0;
  border-radius: var(--radius-md);
  border: 1px solid rgba(164,174,198,0.28);
  box-shadow: 0 2px 12px rgba(0,0,0,0.18);
}
.glow-panel-card-3 .glow-panel-lurk img {
  object-fit: cover;
  object-position: center top;
  border-radius: var(--radius-md);
}

/* Gradient: fades image into black at BOTTOM, less aggressive so more head is visible */
.glow-panel-lurk::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, #000 0%, rgba(0,0,0,0.55) 20%, transparent 48%);
  pointer-events: none;
}

/* Typewriter-once blinking cursor (used with .typewriter-once + initTypewriterOnce JS) */
.tw-cursor {
  display: inline;
  animation: cursorBlink 0.9s step-end infinite;
  font-weight: 300;
  margin-left: 1px;
}

.dark-glow-section.is-dark .tw-cursor { color: rgba(255,255,255,0.80); }

/* AI Idea Chat block reused inside .dark-glow-section (see /frontpage2) -
 * .ai-idea-pill (built on .sec-tech-pill's light-blue-on-white look) and
 * the status/feedback text need their own dark-mode variant, same
 * transition-based pattern as the rest of this block. */
.dark-glow-section .sec-tech-pill,
.dark-glow-section .ai-idea-feedback {
  transition: background-color 1.2s ease, border-color 1.2s ease, color 1.2s ease;
}
.dark-glow-section.is-dark .sec-tech-pill {
  background: rgba(255,255,255,0.08);
  color: rgba(255,255,255,0.85);
  border-color: rgba(255,255,255,0.18);
}
.dark-glow-section.is-dark .ai-idea-pill:hover,
.dark-glow-section.is-dark .ai-idea-pill:focus-visible {
  background: rgba(255,255,255,0.16);
  border-color: rgba(255,255,255,0.35);
}
.dark-glow-section.is-dark .ai-idea-feedback.text-muted { color: rgba(255,255,255,0.65) !important; }

@keyframes cursorBlink {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0; }
}

/* ===== BLOCK: Soft Grid Section (DrawKit / Sandbox demo30 inspired) =====
 * Pale section with a radial accent glow, white soft-card elements, and
 * optional dark accent card variants.
 */
.soft-grid-section {
  position: relative;
  overflow: hidden;
}

/* Radial glow from bottom center */
.soft-grid-section::before {
  content: '';
  position: absolute;
  bottom: -120px; left: 50%;
  transform: translateX(-50%);
  width: 900px; height: 500px;
  background: radial-gradient(ellipse at center, rgba(4, 123, 193, 0.08) 0%, transparent 70%);
  pointer-events: none; z-index: 0;
}

.soft-grid-section .container { position: relative; z-index: 1; }

/* White soft card */
.soft-card {
  background: white;
  border-radius: 1.5rem;
  padding: 2rem;
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.05);
  height: 100%;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

/* Blue top accent bar, same gradient as .pricing-card-v3-accent.accent-blue.
   Confirmed as the standard look for two named blocks (see /blocks):
   "Grid — Compact Link Cards" and "Card — Featured Path + Secondary Paths"
   (its secondary cards only, not the featured card itself). Deliberately
   NOT used on "Grid — Product Suite (Photo Background)" or its dark
   variant - those already sit on a photo/gradient background where the
   accent bar doesn't read well. Opt-in via this modifier rather than
   changing .soft-card itself, since plain .soft-card is also used for
   several other block types (testimonial spotlight, generic icon-content
   grids, etc.) that don't get this treatment. Also gets the same thin gray
   border as .product-card (the established "card with a CTA button" border
   convention sitewide - rgba(164,174,198,*)) - plain .soft-card relies on
   shadow alone, but these more strongly-styled cards read better with both. */
.soft-card-accent-blue {
  position: relative;
  overflow: hidden;
  padding-top: 2.5rem;
  border: 1px solid rgba(164, 174, 198, 0.15);
}
.soft-card-accent-blue::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 6px;
  background: linear-gradient(90deg, var(--blue-dark) 0%, var(--blue-light) 100%);
}

.soft-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.09);
}

/* Partner logo grid (/kumppanit) - .soft-card as the base, just a centered
   logo image per card, no name/heading text (matches how the old site's
   partner page displayed the logos - link only, no visible company name).
   display:flex here needs !important - the card link also carries Bootstrap's
   .d-block utility (for the <a> to fill its grid column), which is itself
   !important and would otherwise win over this and break the centering. */
.partner-logo-card {
  display: flex !important;
  align-items: center;
  justify-content: center;
  padding: 1.75rem;
  min-height: 140px;
}
.partner-logo-card img {
  max-width: 100%;
  max-height: 80px;
  width: auto;
  height: auto;
}

.soft-card-icon {
  width: 52px; height: 52px;
  border-radius: 1rem;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.75rem;
  margin-bottom: 1.25rem;
}

/* /nopeus (speed comparison) - horizontal bar-chart rows built on top of
   Bootstrap's .progress/.progress-bar (kept for semantics/role), heavily
   restyled: tall rounded pill track, gradient fill, bold value label.
   Width animates in via the site's existing [data-anim].anim-play scroll
   trigger (see main.js initScrollAnimations()) - no extra JS needed, just
   set --bar-w inline per row. */
.speed-bar-row { margin-bottom: 1.5rem; }
.speed-bar-row:last-child { margin-bottom: 0; }
.speed-bar-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.5rem;
  font-weight: 600;
}
.speed-bar-label .name { display: flex; align-items: center; }
.speed-bar-label .value { font-weight: 700; color: var(--blue-dark); }
.speed-bar-row.speed-bar-winner .speed-bar-label .value { color: var(--orange-dark); }
.speed-bar-row.speed-bar-winner .speed-bar-label .name { font-size: 1.1rem; }

.speed-bar-track {
  height: 16px;
  border-radius: 999px;
  background: var(--bg-pale);
  overflow: hidden;
}
.speed-bar-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--blue-dark) 0%, var(--blue-light) 100%);
  transition: width 1.2s cubic-bezier(.16,1,.3,1);
}
.speed-bar-row.speed-bar-winner .speed-bar-fill {
  background: linear-gradient(90deg, #F7941E 0%, #FFB347 100%);
}
[data-anim].anim-play .speed-bar-fill {
  width: var(--bar-w, 0%);
}

.speed-bar-winner-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--orange-dark);
  background: rgba(247, 148, 30, 0.12);
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  margin-left: 0.6rem;
}
.speed-bar-second-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--blue-dark);
  background: rgba(4, 123, 193, 0.1);
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  margin-left: 0.6rem;
}

/* Provider not yet measured - placeholder row in a speed-bar list (used on
   /nopeus while some providers in the comparison method are still pending
   real data). Muted fill + badge, never a winner/second. */
.speed-bar-row.speed-bar-pending { opacity: 0.55; }
.speed-bar-row.speed-bar-pending .speed-bar-fill {
  background: repeating-linear-gradient(45deg, var(--text-dim, #57626f) 0, var(--text-dim, #57626f) 6px, transparent 6px, transparent 12px);
}
.speed-bar-pending-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-dim, #57626f);
  background: rgba(87, 98, 111, 0.12);
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  margin-left: 0.6rem;
}

/* Small asterisk-style footnote under a speed-bar chart or stat. */
.speed-bar-footnote {
  font-size: 0.75rem;
  color: var(--text-dim, #57626f);
  margin: 1rem 0 0;
}

/* /nopeus hero only - bigger than the carousel's own .hero-levitate-robot
   img (300px cap), which stays untouched since other pages depend on that
   smaller carousel-safe size. Keeps the same heroLevitate float. */
.hero-levitate-robot-lg img {
  max-width: 420px;
}

/* Dark accent card */
.soft-card-dark {
  background: #0e1525;
  border-radius: 1.5rem;
  padding: 2rem;
  height: 100%;
  position: relative; overflow: hidden;
  transition: transform 0.25s ease;
}

.soft-card-dark:hover { transform: translateY(-4px); }

/* Ambient glow inside dark card */
.soft-card-dark::before {
  content: '';
  position: absolute;
  bottom: -60px; right: -40px;
  width: 200px; height: 200px;
  background: radial-gradient(circle, rgba(4, 123, 193, 0.3), transparent 70%);
  pointer-events: none;
}

.soft-card-dark h4,
.soft-card-dark h5 {
  color: white !important;
  -webkit-text-fill-color: white !important;
}

.soft-card-dark p { color: rgba(255,255,255,0.72); }
.soft-card-dark .eyebrow { color: rgba(255,255,255,0.5) !important; }

/* ===== BLOCK: Product Tile Centered (Google One / verkkokaupat style) =====
 * Centered card with soft warm background, pill tag, icon badge, centered text.
 */
.product-tile-centered {
  border-radius: 1.5rem;
  padding: 2.5rem 2rem;
  text-align: center;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.product-tile-centered:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 30px rgba(0,0,0,0.08);
}

.product-tile-centered .ptc-icon {
  width: 60px; height: 60px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.875rem;
  margin: 0 auto 1.25rem;
}

.product-tile-centered .ptc-pill {
  display: inline-block;
  border-radius: 2rem;
  padding: 0.2rem 0.875rem;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  margin-bottom: 0.875rem;
}

.product-tile-centered p { flex: 1; }

/* Warm soft-background variants for product-tile-centered */
.ptc-warm   { background: #fdf6ee; }
.ptc-cool   { background: #eef4ff; }
.ptc-sage   { background: #eef8f4; }
.ptc-lavend { background: #f4eeff; }

.ptc-warm   .ptc-icon { background: rgba(247,148,30,0.14); color: var(--orange-dark); }
.ptc-cool   .ptc-icon { background: rgba(4,123,193,0.12); color: var(--blue-dark); }
.ptc-sage   .ptc-icon { background: rgba(46,153,0,0.12); color: #1a7a00; }
.ptc-lavend .ptc-icon { background: rgba(130,80,220,0.12); color: #8250dc; }

.ptc-warm   .ptc-pill { background: rgba(247,148,30,0.14); color: var(--orange-dark); }
.ptc-cool   .ptc-pill { background: rgba(4,123,193,0.12); color: var(--blue-dark); }
.ptc-sage   .ptc-pill { background: rgba(46,153,0,0.12); color: #1a7a00; }
.ptc-lavend .ptc-pill { background: rgba(130,80,220,0.12); color: #8250dc; }

/* Single warm-beige variant — simplified, non-cycling (verkkokaupat) */
.ptc-single { background: #f7f4ef; }
.ptc-single .ptc-icon { background: rgba(4,123,193,0.10); color: var(--blue-dark); }
.ptc-single .ptc-pill { background: rgba(4,123,193,0.10); color: var(--blue-dark); }

/* Sharper text colours for all centered tiles */
.product-tile-centered h4 { color: var(--text-dark); }
.product-tile-centered p  { color: var(--text-body); }

/* ===== BLOCK: Mini Brand Card (digitoimistot / ratkaisut mega-menu style) =====
 * Small card with brand gradient backgrounds, icon positioned bottom-right.
 * Uses existing target-card-brand-* gradient backgrounds.
 */
.mini-brand-card {
  border-radius: var(--radius-lg);
  padding: 1.75rem;
  position: relative;
  overflow: hidden;
  height: 100%;
  display: flex;
  flex-direction: column;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
  box-shadow: var(--shadow-sm);
}

.mini-brand-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-hover);
}

.mini-brand-card-icon {
  position: absolute;
  bottom: -20px; right: -10px;
  font-size: 10rem;       /* 2× bigger than original 5.5rem */
  opacity: 0.15;
  line-height: 1;
  pointer-events: none;
}

/* Dark cards: use white icon so it’s visible against the dark gradient */
.target-card-brand-primary .mini-brand-card-icon,
.target-card-brand-dark .mini-brand-card-icon {
  color: white;
  opacity: 0.32;
}

.mini-brand-card-body { position: relative; z-index: 1; flex: 1; }

/* Text on light cards */
.target-card-brand-light .mini-brand-card-body h4,
.target-card-brand-sunset .mini-brand-card-body h4 { color: var(--text-dark); }
.target-card-brand-light .mini-brand-card-body p,
.target-card-brand-sunset .mini-brand-card-body p { color: var(--text-body); }

/* Text on dark cards */
.target-card-brand-primary .mini-brand-card-body h4,
.target-card-brand-dark .mini-brand-card-body h4 {
  color: white !important; -webkit-text-fill-color: white !important;
}
.target-card-brand-primary .mini-brand-card-body p,
.target-card-brand-dark .mini-brand-card-body p { color: rgba(255,255,255,0.82); }
.target-card-brand-primary .mini-brand-card-body .eyebrow,
.target-card-brand-dark .mini-brand-card-body .eyebrow { color: rgba(255,255,255,0.6) !important; }

/* Eyebrow accent on light / warm cards */
.target-card-brand-light .mini-brand-card-body .eyebrow,
.target-card-brand-sunset .mini-brand-card-body .eyebrow { color: var(--blue-dark) !important; }

/* ===== BLOCK: Why Choose Numbered (home page simple version) =====
 * Three-column decorative numbered items. No JS / tabs — pure CSS.
 */
.why-num-badge {
  font-size: 3.5rem;
  font-weight: 900;
  line-height: 1;
  font-family: 'Raleway', sans-serif;
  color: transparent;
  -webkit-text-stroke: 2px rgba(4, 123, 193, 0.22);
  display: block;
  margin-bottom: 0.75rem;
}

.why-num-item h4 { color: var(--text-dark); margin-bottom: 0.5rem; }
.why-num-item p  { color: var(--text-body); }

/* ===== BLOCK: Why Choose Circle Numbers (orbit-style numbered circles) =====
 * Replaces decorative text-stroke numbers with stat-icon circles containing 1/2/3.
 */
.why-num-circle {
  font-family: 'Raleway', sans-serif;
  font-size: 1.5rem;
  font-weight: 800;
  color: white;
  -webkit-text-fill-color: white;
  /* Inherits: stat-icon sizing (70px × 70px, border-radius:50%) + margin:0 auto 1.25rem */
}

/* Stat icon light / gray variants (for disc icons in check lists, price cards) */
.stat-icon-light { background: rgba(4,123,193,0.10); color: var(--blue-dark) !important; -webkit-text-fill-color: var(--blue-dark) !important; }
.stat-icon-gray  { background: rgba(164,174,198,0.18); color: var(--text-body) !important; -webkit-text-fill-color: var(--text-body) !important; }

/* Extra-small stat-icon variant (28px) for inline use */
.stat-icon-xs {
  width: 28px; height: 28px; min-width: 28px;
  font-size: 0.875rem;
  margin: 0;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}

/* ===== BLOCK: Price Badge (standalone product price callout) =====
 * Two variants: round (circular) and tag (bookmark clip-path shape).
 */
/* ===== Price Badges (size corrected: +50%) ===== */
/* One-directional outward pulse: ring starts from badge edge, grows and fades out, then pauses */
@keyframes badgePulse {
  0%   { box-shadow: 0 0 0 9px rgba(255,255,255,0.11), 0 0 0 18px rgba(4,123,193,0.07), 0 0 0 0px rgba(4,123,193,0.40), 0 12px 50px rgba(0,66,117,0.22); }
  65%  { box-shadow: 0 0 0 9px rgba(255,255,255,0.11), 0 0 0 18px rgba(4,123,193,0.07), 0 0 0 55px rgba(4,123,193,0.00), 0 12px 50px rgba(0,66,117,0.22); }
  100% { box-shadow: 0 0 0 9px rgba(255,255,255,0.11), 0 0 0 18px rgba(4,123,193,0.07), 0 0 0 55px rgba(4,123,193,0.00), 0 12px 50px rgba(0,66,117,0.22); }
}

.price-badge-round {
  width: 243px; height: 243px;
  border-radius: 50%;
  background: linear-gradient(145deg, #002455 0%, var(--primary) 60%, var(--blue-dark) 100%);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  color: white; text-align: center;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  animation: badgePulse 3.5s ease-out infinite;
}

/* WordPress watermark icon in badge bottom-left — 2× larger */
.price-badge-bg-icon {
  position: absolute;
  bottom: -20px;
  left: -10px;
  font-size: 180px;
  color: rgba(255,255,255,0.10);
  pointer-events: none;
  line-height: 1;
  z-index: 0;
}

/* Ensure badge text is above the background icon */
.price-badge-round > *:not(.price-badge-bg-icon) { position: relative; z-index: 1; }

.price-badge-round.pb-blue-dark { background: var(--blue-dark); }
.price-badge-round.pb-orange    { background: linear-gradient(135deg, var(--orange-dark), #fbbf24); }

.price-badge-round .pb-label   { font-size: 1.05rem; font-weight: 700; opacity: 0.90; }

/* Flex row holding currency + amount inline */
.price-badge-round .pb-price-row {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  line-height: 1;
}

/* Smaller superscript-style euro sign matching card style */
.price-badge-round .pb-currency {
  font-family: 'Montserrat', sans-serif;
  font-size: 1.25rem;
  font-weight: 700;
  color: white;
  -webkit-text-fill-color: white;
  padding-top: 0.45rem; /* Superscript alignment against 3.5rem amount */
  letter-spacing: 0;
}

.price-badge-round .pb-amount {
  font-family: 'Montserrat', sans-serif;
  font-size: 3.5rem; font-weight: 900; line-height: 1; /* Same as pricing-card-v3 cards */
  color: white; -webkit-text-fill-color: white;
  letter-spacing: -0.04em;
}
.price-badge-round .pb-period  { font-size: 0.95rem; opacity: 0.80; }
.price-badge-round .pb-product { font-size: 1rem; font-weight: 700; margin-top: 0.35rem; opacity: 0.90; }

/* Minimal variant — just a big price + period, no label/product text. For
   badges that overlay a hero where the product name is already stated in
   the surrounding heading/paragraph (e.g. /webhotellit Park slide), so the
   badge itself doesn't need to repeat it. */
.price-badge-round.pb-minimal .pb-label,
.price-badge-round.pb-minimal .pb-product { display: none; }
.price-badge-round.pb-minimal .pb-amount { font-size: 6.5rem; }
.price-badge-round.pb-minimal .pb-currency { font-size: 2.1rem; padding-top: 1rem; }
.price-badge-round.pb-minimal .pb-period { font-size: 1.1rem; margin-top: 0.25rem; }

/* Tag-shaped price badge — dark blue with subtle gradient, tear-off ticket top */
.price-badge-tag {
  width: 240px;
  /* Dark primary matching round badge, subtle gradient for depth */
  background: linear-gradient(160deg, #002455 0%, var(--primary) 60%, #003d6b 100%);
  display: flex; flex-direction: column;
  align-items: center; justify-content: flex-start;
  padding: 2.5rem 1.5rem 4rem;
  color: white; text-align: center;
  position: relative;
  clip-path: polygon(0 0, 100% 0, 100% 78%, 50% 100%, 0 78%);
  flex-shrink: 0;
}

/* Thick white horizontal line at top of tag */
.price-badge-tag::before {
  content: '';
  position: absolute;
  top: 18px;
  left: 0; right: 0;
  height: 3px;
  background: rgba(255,255,255,0.65);
}

/* Thin light-blue accent line just below the white line */
.price-badge-tag::after {
  content: '';
  position: absolute;
  top: 26px;
  left: 0; right: 0;
  height: 1.5px;
  background: rgba(100,200,255,0.45);
}

/* Flex row holding currency + amount inline (same as round badge) */
.price-badge-tag .pb-price-row {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  line-height: 1;
}

/* Smaller superscript-style euro sign */
.price-badge-tag .pb-currency {
  font-family: 'Montserrat', sans-serif;
  font-size: 1.25rem;
  font-weight: 700;
  color: white;
  -webkit-text-fill-color: white;
  padding-top: 0.45rem;
  letter-spacing: 0;
}

.price-badge-tag .pb-amount {
  font-family: 'Montserrat', sans-serif;
  font-size: 3.5rem; font-weight: 900; line-height: 1; /* Matched to cards + round badge */
  color: white; -webkit-text-fill-color: white;
  letter-spacing: -0.03em;
}
.price-badge-tag .pb-label  { font-size: 1.05rem; font-weight: 700; opacity: 0.90; }
.price-badge-tag .pb-period { font-size: 0.95rem; opacity: 0.80; }
.price-badge-tag .pb-product { font-size: 1rem; font-weight: 700; opacity: 0.90; margin-top: 0.25rem; }

/* ===== BLOCK: Immersive Solutions (Hostinger-style full-width rounded container) =====
 * Full-width rounded-corner block: image half (cropped left), content half right.
 * Use with container-xl for slightly wider than standard container.
 */
.solutions-immersive-wrap {
  border-radius: var(--radius-xl);
  overflow: hidden;
  background: var(--bg-pale);
}

/* Clean-slate immersive media: absolute-fill approach — 100% reliable */
.solutions-immersive-media {
  overflow: hidden;
  position: relative;
  min-height: 300px; /* visible floor on all screen sizes */
}

.solutions-immersive-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.solutions-immersive-content { padding: 3.5rem; }

@media (max-width: 991px) {
  .solutions-immersive-content { padding: 2rem; }
  .solutions-immersive-media   { min-height: 260px; }
}

/* ===== BLOCK: Feature Icon Illustration =====
 * Two stacked outline Unicons icons — no background rectangle.
 * Primary: large dark blue. Behind: same/related icon, very light blue-grey.
 * Usage: <div class="feat-icon-stack">
 *           <i class="uil uil-BEHIND feat-icon-behind"></i>
 *           <i class="uil uil-PRIMARY feat-icon-primary"></i>
 *        </div>
 */
.feat-icon-stack {
  position: relative;
  width: 260px; height: 260px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}

/* Primary icon: large dark blue outline */
.feat-icon-primary {
  position: relative; z-index: 2;
  font-size: 9rem;
  color: var(--blue-dark);
  line-height: 1;
}

/* Behind icon: very large, very light blue-grey (watermark effect matching badge size) */
.feat-icon-behind {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  font-size: 18rem;
  color: rgba(164, 174, 198, 0.18);
  line-height: 1;
  pointer-events: none;
}

/* WordPress badge at bottom-right of feat-icon-stack — 2× larger, closer to center (partly overlapping primary icon) */
.feat-icon-badge-wp {
  position: absolute;
  bottom: 10px; right: 10px;  /* Closer to center, overlaps primary icon corner */
  width: 80px; height: 80px;   /* Larger to fit 2× icon */
  border-radius: 50%;
  background: white;
  display: flex; align-items: center; justify-content: center;
  font-size: 3.5rem;           /* 2× the original 1.875rem */
  color: var(--blue-dark);
  box-shadow: 0 3px 18px rgba(0,66,117,0.22);
  z-index: 3;
  border: 2.5px solid rgba(4,123,193,0.14);
}

/* Icon-only shield badge variant (no text) */
.feat-badge-shield {
  justify-content: center;
  padding: 3rem 1.5rem 5.5rem;
}

/* Shield primary icon: same size as feat-icon-primary (9rem) */
.feat-badge-shield .feat-shield-main {
  position: relative; z-index: 2;
  font-size: 9rem;
  color: rgba(255,255,255,0.92);
  line-height: 1;
}

/* Large transparent shield behind: 15rem, positioned like circle-badge background icon */
.feat-badge-shield .feat-shield-behind {
  position: absolute;
  bottom: -20px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 15rem;
  color: rgba(255,255,255,0.11);
  line-height: 1;
  pointer-events: none;
}

/* Security technology pill badges (ModSecurity, fail2ban, Imunify+) */
.sec-tech-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.sec-tech-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: rgba(4,123,193,0.08);
  color: var(--blue-dark);
  border: 1px solid rgba(4,123,193,0.15);
  border-radius: 2rem;
  padding: 0.3rem 0.875rem;
  font-size: 0.875rem;
  font-weight: 600;
}

.sec-tech-pill i { font-size: 1rem; }

/* ===== BLOCK: Feature Badge Tag =====
 * Pentagon-shaped badge that promotes a technology/feature (not a price).
 * Reuses .price-badge-tag shape. Content: logo + name + tagline.
 */
.feat-badge-tag {
  /* Inherits .price-badge-tag structural/shape properties */
  padding-top: 3.5rem;
}

.feat-badge-tag .feat-logo {
  width: 60px; height: auto;
  filter: brightness(0) invert(1);
  opacity: 0.92;
  margin-bottom: 0.5rem;
  display: block;
  flex-shrink: 0;
}

.feat-badge-tag .feat-icon-badge {
  font-size: 3.5rem;
  color: rgba(255,255,255,0.85);
  line-height: 1;
  margin-bottom: 0.5rem;
}

.feat-badge-tag .feat-name {
  font-family: 'Raleway', sans-serif;
  font-size: 1.1rem; font-weight: 800;
  color: white; -webkit-text-fill-color: white;
  line-height: 1.2;
}

.feat-badge-tag .feat-tagline {
  font-size: 0.82rem;
  color: rgba(255,255,255,0.82);
  margin-top: 0.35rem;
  line-height: 1.4;
}

/* ===== BLOCK: Webhotellivertailu Comparison Table (Google One inspired) =====
 * Sticky header with plan columns. Feature rows alternate pale/white.
 * Check / cross icons replace boolean values.
 */
.compare-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.compare-table {
  width: 100%;
  min-width: 680px;
  border-collapse: collapse;
  background: white;
}

.compare-table thead th {
  position: sticky;
  top: 80px; /* Below fixed navbar */
  background: white;
  z-index: 2;
  padding: 1.5rem 1.25rem 1rem;
  text-align: center;
  border-bottom: 2px solid rgba(164,174,198,0.18);
}

.compare-table thead .th-feature { text-align: left; }

.compare-table thead .th-plan { min-width: 190px; }

.compare-table thead .th-plan.featured {
  background: linear-gradient(180deg, rgba(0,66,117,0.04) 0%, white 100%);
  border-top: 3px solid var(--primary);
}

.compare-plan-name {
  font-family: 'Raleway', sans-serif;
  font-size: 1.125rem;
  font-weight: 800;
  color: var(--text-dark);
  display: block;
}

.compare-plan-price {
  font-family: 'Montserrat', sans-serif;
  font-size: 1.75rem;
  font-weight: 900;
  color: var(--primary);
  display: block;
  line-height: 1;
  margin: 0.25rem 0;
}

.compare-plan-period { font-size: 0.8rem; color: var(--text-light); }

.compare-table tbody tr td {
  padding: 0.875rem 1.25rem;
  vertical-align: middle;
  font-size: 1rem;
  border-bottom: 1px solid rgba(164,174,198,0.10);
}

.compare-table tbody tr td:first-child { font-weight: 600; color: var(--text-dark); text-align: left; }
.compare-table tbody tr td:not(:first-child) { text-align: center; color: var(--text-body); }

.compare-table tbody tr:nth-child(odd) td { background: rgba(246,247,249,0.5); }

.compare-table tbody .section-header td {
  background: var(--bg-pale) !important;
  font-family: 'Raleway', sans-serif;
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-light);
  padding: 0.625rem 1.25rem;
}

.compare-check  { color: var(--accent); font-size: 1.25rem; }
.compare-cross  { color: rgba(164,174,198,0.55); font-size: 1.1rem; }
.compare-feat   { color: var(--primary); font-size: 1.25rem; }

/* ===== BLOCK: Gradient Background Section (demo30 "What makes us different?" style) =====
 * Soft diagonal purple-blue gradient with a radial glow accent under the focal element.
 * Used for "Mikä tekee meistä erilaisia?" on /suunnittelijat.
 */
.gradient-bg-section {
  position: relative;
  background: linear-gradient(145deg, #eef3ff 0%, #e5edff 32%, #eff0ff 62%, #f5eeff 85%, #fff0fb 100%);
  overflow: hidden;
}

/* Radial glow from center-top toward the focal image */
.gradient-bg-section::before {
  content: '';
  position: absolute;
  top: 8%; left: 50%;
  transform: translateX(-50%);
  width: 820px; height: 620px;
  background: radial-gradient(ellipse at center, rgba(4,123,193,0.10) 0%, transparent 68%);
  pointer-events: none;
  z-index: 0;
}

.gradient-bg-section .container { position: relative; z-index: 1; }

/* ===== BLOCK: Check List (Have Perfect Control / demo7 style) =====
 * Icon bullet list for 2-col content blocks.
 */
.check-list { list-style: none; padding: 0; margin: 0; }

.check-list li {
  display: flex;
  gap: 0.875rem;
  align-items: flex-start;
  padding: 0.5rem 0;
  color: var(--text-body);
  border-bottom: 1px solid rgba(164,174,198,0.12);
}

.check-list li:last-child { border-bottom: none; }

.check-list-icon {
  flex-shrink: 0;
  width: 22px; height: 22px;
  margin-top: 2px;
  border-radius: 50%;
  background: var(--primary);
  display: flex; align-items: center; justify-content: center;
  color: white;
  font-size: 0.75rem;
}

/* ===== Per-card Technical Specifications Toggle =====
 * Positioned between the plan description and the feature list.
 * Centered circle button (grey) with animated up/down arrow + small label below.
 * One click opens/closes ALL three plan cards simultaneously (custom JS).
 */
.pricing-specs-toggle {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  border: none;
  background: none;
  cursor: pointer;
  padding: 0.5rem 0 0.25rem;
  margin: 0.25rem 0 0.5rem;
  gap: 0.4rem;
  text-decoration: none;
}

/* Grey circle background — same neutral tint used in spec discs */
.specs-toggle-circle {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(164, 174, 198, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  color: var(--text-light);
  transition: background 0.2s ease, color 0.2s ease;
  flex-shrink: 0;
}

.pricing-specs-toggle:hover .specs-toggle-circle {
  background: rgba(164, 174, 198, 0.32);
  color: var(--text-body);
}

/* Small label — same style as spec grid labels (CPU/RAM/SSD/LIIKENNE) */
.specs-toggle-label {
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--text-light);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1;
  text-align: center;
}

/* Arrow icon: rotates 180° when expanded */
.pricing-specs-toggle .toggle-arrow {
  transition: transform 0.25s ease;
  display: block;
  line-height: 1;
}

.pricing-specs-toggle[aria-expanded="true"] .toggle-arrow {
  transform: rotate(180deg);
}

/* Emphasized feature bullets — white bg, dark blue disc, lead-colour text, no divider */
.pricing-card-v3-features li.feat-em {
  background: none;
  border-radius: 0;
  padding: 0.5rem 0;
  margin-bottom: 0;
}

/* Disc: dark primary blue with white checkmark */
.pricing-card-v3-features li.feat-em .stat-icon-gray {
  background: var(--primary) !important;
  color: white !important;
  -webkit-text-fill-color: white !important;
  flex-shrink: 0;
}

/* Text: lead colour (var(--text-body) = #343f52, matches p.lead) + bold weight */
.pricing-card-v3-features li.feat-em > span:last-child {
  color: var(--text-body);
  font-weight: 700;
}

/* ===== .swp-features: custom max-height animation (replaces Bootstrap collapse) =====
 * JS toggles .show (open) and .is-closing (close in progress).
 * Container animates height via max-height; items stagger via nth-child animations.
 */
.swp-features {
  max-height: 0;
  overflow: hidden;
  flex-grow: 0;    /* Don't claim flex space when collapsed */
  margin: 0;
  transition: max-height 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}

.swp-features.show {
  max-height: 920px;  /* Tall enough for 15 list items */
  margin-bottom: 1rem;
}

/* Items start invisible so they fade in, not pop in */
.swp-features li { opacity: 0; }

/* Opening: stagger items in, top-to-bottom */
.swp-features.show:not(.is-closing) li {
  animation: swpItemIn 0.25s ease both;
}
.swp-features.show:not(.is-closing) li:nth-child(1)  { animation-delay: 0.04s; }
.swp-features.show:not(.is-closing) li:nth-child(2)  { animation-delay: 0.08s; }
.swp-features.show:not(.is-closing) li:nth-child(3)  { animation-delay: 0.12s; }
.swp-features.show:not(.is-closing) li:nth-child(4)  { animation-delay: 0.16s; }
.swp-features.show:not(.is-closing) li:nth-child(5)  { animation-delay: 0.19s; }
.swp-features.show:not(.is-closing) li:nth-child(6)  { animation-delay: 0.22s; }
.swp-features.show:not(.is-closing) li:nth-child(7)  { animation-delay: 0.24s; }
.swp-features.show:not(.is-closing) li:nth-child(8)  { animation-delay: 0.26s; }
.swp-features.show:not(.is-closing) li:nth-child(9)  { animation-delay: 0.27s; }
.swp-features.show:not(.is-closing) li:nth-child(10) { animation-delay: 0.28s; }
.swp-features.show:not(.is-closing) li:nth-child(11) { animation-delay: 0.29s; }
.swp-features.show:not(.is-closing) li:nth-child(12) { animation-delay: 0.30s; }
.swp-features.show:not(.is-closing) li:nth-child(13) { animation-delay: 0.31s; }
.swp-features.show:not(.is-closing) li:nth-child(14) { animation-delay: 0.32s; }
.swp-features.show:not(.is-closing) li:nth-child(15) { animation-delay: 0.33s; }

/* Closing: items fade out in reverse order (bottom-to-top = nth-last-child) */
.swp-features.is-closing li {
  animation: swpItemOut 0.15s ease both;
}
.swp-features.is-closing li:nth-last-child(1)  { animation-delay: 0s; }
.swp-features.is-closing li:nth-last-child(2)  { animation-delay: 0.02s; }
.swp-features.is-closing li:nth-last-child(3)  { animation-delay: 0.04s; }
.swp-features.is-closing li:nth-last-child(4)  { animation-delay: 0.06s; }
.swp-features.is-closing li:nth-last-child(5)  { animation-delay: 0.08s; }
.swp-features.is-closing li:nth-last-child(6)  { animation-delay: 0.10s; }
.swp-features.is-closing li:nth-last-child(7)  { animation-delay: 0.12s; }
.swp-features.is-closing li:nth-last-child(8)  { animation-delay: 0.13s; }
.swp-features.is-closing li:nth-last-child(9)  { animation-delay: 0.14s; }
.swp-features.is-closing li:nth-last-child(10) { animation-delay: 0.15s; }
.swp-features.is-closing li:nth-last-child(11) { animation-delay: 0.16s; }
.swp-features.is-closing li:nth-last-child(12) { animation-delay: 0.17s; }
.swp-features.is-closing li:nth-last-child(13) { animation-delay: 0.18s; }
.swp-features.is-closing li:nth-last-child(14) { animation-delay: 0.19s; }
.swp-features.is-closing li:nth-last-child(15) { animation-delay: 0.20s; }

@keyframes swpItemIn {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes swpItemOut {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(4px); }
}

/* ===== BLOCK: Article Content (long-form reading content) =====
 * Narrower reading column, comfortable paragraph spacing, semantic headings.
 * .document-content variant: legal/agreement styling with numbered sections.
 * Usage: <article class="article-content"> or <article class="article-content document-content">
 */
.article-content {
  max-width: 800px;
  margin: 0 auto;
}

/* Comfortable reading spacing */
.article-content p {
  margin-bottom: 1.25rem;
  line-height: 1.8;
}

.article-content p.lead {
  margin-bottom: 1.75rem;
}

/* Heading spacing within article body */
.article-content h2 {
  margin-top: 3rem;
  margin-bottom: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid rgba(164, 174, 198, 0.18);
}

.article-content h3 {
  margin-top: 2.25rem;
  margin-bottom: 0.75rem;
}

.article-content h4 {
  margin-top: 1.75rem;
  margin-bottom: 0.5rem;
}

/* Lists inside articles */
.article-content ul,
.article-content ol {
  margin-bottom: 1.25rem;
  padding-left: 1.5rem;
}

.article-content li {
  margin-bottom: 0.5rem;
  line-height: 1.7;
}

/* Links inside articles: underlined for discoverability */
.article-content a {
  color: var(--blue-dark);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.article-content a:hover {
  color: var(--primary);
}

/* === Document variant: legal/agreement styling === */

/* Document meta: date, version info */
.document-meta {
  padding: 1rem 1.25rem;
  background: var(--bg-pale);
  border-radius: var(--radius-md);
  margin-bottom: 2rem;
}

.document-meta p {
  margin-bottom: 0.25rem;
  line-height: 1.5;
}

/* Table of contents navigation */
.document-toc {
  padding: 1.5rem;
  background: white;
  border: 1px solid rgba(164, 174, 198, 0.18);
  border-radius: var(--radius-lg);
  margin-bottom: 2.5rem;
}

.document-toc h5 {
  margin-bottom: 0.75rem;
}

.document-toc ol {
  margin: 0;
  padding-left: 1.25rem;
  list-style-type: decimal;
}

.document-toc li {
  margin-bottom: 0.35rem;
}

.document-toc a {
  text-decoration: none;
  color: var(--text-body);
  transition: color 0.2s;
}

.document-toc a:hover {
  color: var(--blue-dark);
  text-decoration: underline;
}

/* Print-friendly: hide nav, show full content */
@media print {
  .article-content { max-width: 100%; }
  .document-toc { break-inside: avoid; }
  .navbar, .footer, .cta-gradient { display: none !important; }
}

/* ===== BLOCK: Blog Cards (Sandbox/Zoner inspired) =====
 * Featured card: half-image half-text horizontal layout.
 * Grid cards: vertical image-on-top cards with category pill and date.
 */
.blog-card-featured {
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: white;
  box-shadow: var(--shadow-md);
  transition: box-shadow 0.3s ease;
}

.blog-card-featured:hover {
  box-shadow: var(--shadow-hover);
}

.blog-card-featured-img {
  overflow: hidden;
  min-height: 380px;
}

.blog-card-featured-img img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

.blog-card-featured-body {
  padding: 2.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Grid article card */
.blog-card {
  display: block;
  background: white;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: all 0.3s ease;
  text-decoration: none;
  color: inherit;
  height: 100%;
}

.blog-card:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-hover);
  color: inherit;
  text-decoration: none;
}

.blog-card-img {
  overflow: hidden;
  height: 220px;
}

.blog-card-img img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.4s ease;
}

.blog-card:hover .blog-card-img img {
  transform: scale(1.05);
}

.blog-card-body {
  padding: 1.5rem;
}

/* Category pill */
.blog-card-category {
  display: inline-block;
  padding: 0.2rem 0.75rem;
  border-radius: 50px;
  background: rgba(4, 123, 193, 0.10);
  color: var(--blue-dark);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.blog-card-date {
  font-size: 0.8rem;
  color: var(--text-light);
}

@media (max-width: 991px) {
  .blog-card-featured-img { min-height: 260px; }
  .blog-card-featured-body { padding: 1.5rem; }
}

/* === Document sidebar: sticky TOC + meta for legal pages === */
.document-sidebar {
  position: sticky;
  top: 100px; /* Below fixed navbar */
  background: var(--bg-pale);
  border-radius: var(--radius-lg);
  padding: 1.5rem;
}

.document-sidebar h5 {
  margin-bottom: 0.75rem;
}

.document-sidebar h6 {
  margin-bottom: 0.5rem;
}

.document-sidebar-toc {
  margin: 0;
  padding-left: 1.25rem;
  list-style-type: decimal;
}

.document-sidebar-toc li {
  margin-bottom: 0.35rem;
}

.document-sidebar-toc a {
  text-decoration: none;
  color: var(--text-body);
  transition: color 0.2s;
}

.document-sidebar-toc a:hover {
  color: var(--blue-dark);
  text-decoration: underline;
}

/* Print: hide sidebar, full-width content */
@media print {
  .document-sidebar { display: none !important; }
}

/* END OF CONTENT BLOCKS */

/* @property enables smooth keyframe animation of the conic-gradient angle
 * (Chrome/Edge 85+, Firefox 128+, Safari 15.4+). */
@property --mega-card-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

/* Stacks two+ .mega-solution-card promos vertically in one mega-menu column
   (Tuotteet's Nextcloud + Kumppanit cards) - each card keeps its own natural
   height instead of every card trying to be 100% of the stack at once. */
.mega-promo-stack {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  height: 100%;
}
.mega-promo-stack .mega-solution-card {
  height: auto;
  min-height: 150px;
  flex: 1 1 auto;
}

/* ===== BLOCK: Mega Menu Solution Cards (Ratkaisut + Teknologia + featured promos) =====
 * Uses target-card-brand-* colour classes (matching /ratkaisut page blocks exactly).
 * Hover: one-revolution sunset-gradient spinning border, no lift.
 */
.mega-solution-card {
  border-radius: var(--radius-lg);
  overflow: hidden;
  position: relative;
  padding: 1.5rem 1.75rem;
  min-height: 190px;
  display: flex;
  flex-direction: column;
  height: 100%;
  transition: box-shadow 0.2s ease;
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  color: inherit;
}

/* Hover: lift shadow only. Gradient border is handled by ::after below. */
.mega-solution-card:hover {
  box-shadow: var(--shadow-md);
  color: inherit;
  text-decoration: none;
}

/* Gradient border ring: sunset-orange ⇔ robot-blue, spins one full turn on hover.
 * Uses the mask xor trick to show only the 2px border area. */
.mega-solution-card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  padding: 2px;
  background: conic-gradient(
    from var(--mega-card-angle),
    transparent 0deg,
    var(--orange-dark) 90deg,
    rgba(39, 170, 225, 0.65) 180deg,
    var(--orange-dark) 270deg,
    transparent 360deg
  );
  -webkit-mask:
    linear-gradient(#fff 0 0) content-box,
    linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#fff 0 0) content-box,
    linear-gradient(#fff 0 0);
  mask-composite: exclude;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

/* On hover: fade border in and spin one full revolution (0.4 s) */
.mega-solution-card:hover::after {
  opacity: 1;
  animation: megaCardBorderSpin 0.4s linear 1 forwards;
}

@keyframes megaCardBorderSpin {
  from { --mega-card-angle: 0deg; }
  to   { --mega-card-angle: 360deg; }
}

/* Text column: fills card height; button (in promo cards) is anchored to bottom */
.mega-solution-content {
  max-width: 68%;
  position: relative;
  z-index: 1;
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Button (promo cards only): pinned to bottom of the content column */
.mega-solution-content .btn {
  margin-top: auto;
  align-self: flex-start;
}

/* Eyebrow dot: currentColor so it is always visible against the card background
 * (e.g. white-ish on dark brand-primary/brand-dark cards). */
.mega-solution-content .eyebrow-dot::before {
  background: currentColor;
  flex-shrink: 0;
}

/* === Dark card text overrides (matching target-card-content rules) === */
.target-card-brand-primary .mega-solution-content h6,
.target-card-brand-dark .mega-solution-content h6 { color: white; }

.target-card-brand-primary .mega-solution-content p,
.target-card-brand-dark .mega-solution-content p { color: rgba(255, 255, 255, 0.82); }

.target-card-brand-primary .mega-solution-content [class*="eyebrow"],
.target-card-brand-dark .mega-solution-content [class*="eyebrow"] {
  color: rgba(255, 255, 255, 0.65) !important;
}

/* === SVG illustration (Ratkaisut cards) === */
.mega-solution-illus {
  position: absolute;
  bottom: -12px;
  right: -10px;
  width: 145px;
  height: auto;
  opacity: 0.88;
  pointer-events: none;
}

.target-card-brand-primary .mega-solution-illus {
  width: 128px;
  bottom: -22px;
}
.target-card-brand-dark .mega-solution-illus {
  width: 155px;
  bottom: -4px;
}

/* === Icon watermark (Teknologia + promo cards) === */
.mega-solution-icon {
  position: absolute;
  bottom: -10px;
  right: -10px;
  font-size: 7.5rem;
  line-height: 1;
  opacity: 0.12;
  color: var(--primary);
  pointer-events: none;
  z-index: 0;
}

.target-card-brand-primary .mega-solution-icon,
.target-card-brand-dark .mega-solution-icon {
  color: white;
  opacity: 0.15;
}

/* === Photo watermark (Hostaan plugin Teknologia card only) - a real
 * product screenshot (iMac mockup, 800x700), same bleed technique as
 * .mega-solution-illus above: full image, no artificial background-size
 * crop window - just an oversized <img> positioned so ~30% of its width
 * bleeds past the card's own right edge and gets clipped by
 * .mega-solution-card's overflow:hidden, the same "cut by the mega menu
 * item's own background" look the SVG illustrations and icon watermarks
 * already have. -72px = 30% of the 240px display width. */
/* Fixed height + object-fit:cover on all three photo watermarks below -
 * the three source images have different native aspect ratios (plugin
 * 800x700, monitoring 2416x1432, devtools 1000x569), so width + height:auto
 * alone rendered each at a different height and made them look randomly
 * placed relative to each other despite identical bottom/right offsets.
 * A shared fixed box (240x210, cropped via object-fit) makes all three
 * occupy the exact same footprint. */
.mega-plugin-photo {
  position: absolute;
  bottom: -55px;
  right: -97px;
  width: 240px;
  height: 210px;
  object-fit: cover;
  object-position: top left;
  pointer-events: none;
  z-index: 0;
}

/* Same box as .mega-plugin-photo, 30px higher - "Ennakoiva valvonta" card,
 * real monitoring dashboard screenshot (2416x1432, no device frame),
 * localized via localizedImg(). Rounded corners so its top-left edge
 * (the corner NOT bled off/clipped by the card) blends with the card's
 * own rounded corner instead of showing a hard rectangle. */
.mega-monitoring-photo {
  position: absolute;
  bottom: 10px;
  right: -72px;
  width: 168px;
  height: 147px;
  object-fit: cover;
  object-position: top left;
  border-radius: var(--radius-lg);
  pointer-events: none;
  z-index: 0;
}

/* Same box as .mega-plugin-photo, 30px higher - "Graafisesti tai
 * komentorivillä" (Kehittäjille) card, the same MacBook + WP Toolkit
 * mockup used on /kehittajille's own demo panel (1000x569). */
.mega-devtools-photo {
  position: absolute;
  bottom: 10px;
  right: -52px;
  width: 168px;
  height: 147px;
  object-fit: cover;
  object-position: top left;
  pointer-events: none;
  z-index: 0;
}

/* Gold trophy ("price cup") next to the heading, Resurssit mega menu speed
 * study card only. FIX: the glyph an icon-font <i> actually paints lives on
 * its ::before (see [class^="uil-"]:before in the self-hosted Unicons CSS)
 * - background/background-clip set on the <i> element itself has no text
 * run to clip against, and color:transparent still inherits down to
 * ::before, which is why the icon rendered fully invisible. The gradient-
 * clip must target ::before directly. (.review-badge-trophy elsewhere has
 * this same latent bug - not fixed here since nobody's reported it broken
 * there yet, but the fix is identical if it ever is.) */
.mega-speed-trophy-icon::before {
  background: linear-gradient(180deg, #ffd54f 0%, #ff9800 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

/* === Mini bar chart (Resurssit mega menu, speed study promo card) ===
 * 3-row version of /nopeus and the front page's .speed-bar-row, sized to
 * fit inside the narrow .mega-solution-card instead of the full-size
 * soft-card version - track fills the content column's full width
 * (.mega-speed-bar-track has no explicit width, so it already stretches
 * to fit .mega-solution-content's 68% column), just thicker/bolder than
 * the first pass so it reads properly at the card's actual size. Colors
 * assume the dark card background it's always used on
 * (.target-card-brand-dark) - no light-mode variant needed. */
.mega-speed-bars {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.mega-speed-bar-label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.8rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.75);
  margin-bottom: 0.25rem;
}
.mega-speed-bar-row.is-winner .mega-speed-bar-label {
  color: white;
}
.mega-speed-bar-track {
  height: 8px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.15);
  overflow: hidden;
}
.mega-speed-bar-fill {
  height: 100%;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.45);
}
.mega-speed-bar-row.is-winner .mega-speed-bar-fill {
  background: var(--orange-dark);
}

/* === Gutter reset for row.g-3 card columns === */
.mega-menu .mega-card-col {
  padding-left: calc(var(--bs-gutter-x, 1rem) * 0.5) !important;
  padding-right: calc(var(--bs-gutter-x, 1rem) * 0.5) !important;
}

/* === Mobile ===
 * <a> cards flatten to indented nav links. <div> promos are hidden.
 * BUG FIX: dark brand cards have white text overrides (specificity 0,3,0 + !important).
 * Using element type 'a' raises to (0,3,1), which wins over the dark-card rule. */
@media (max-width: 991px) {
  a.mega-solution-card {
    background: transparent !important;
    box-shadow: none;
    border-radius: 0;
    min-height: 0;
    padding: 0.5rem 1rem 0.5rem 2rem; /* match .nav-link vertical padding */
    height: auto;
    position: static;
    display: block;
    transition: none;
    border-bottom: 1px solid rgba(164, 174, 198, 0.08) !important;
  }

  /* Last solution card in a mega-card-col group: no bottom border */
  .mega-card-col:last-of-type a.mega-solution-card { border-bottom: none !important; }
  .mega-promo-stack a.mega-solution-card:last-child { border-bottom: none !important; }

  a.mega-solution-card:hover {
    background: var(--bg-pale) !important;
    box-shadow: none;
    color: var(--primary);
  }

  /* Hide gradient border ring on mobile */
  a.mega-solution-card::after { display: none; }

  /* <div> promos are decorative — hidden on mobile */
  div.mega-solution-card { display: none; }

  /* BUG FIX: .mega-promo-stack .mega-solution-card (2 classes, desktop rule
   * above) outranks the plain a.mega-solution-card reset above (1 class + 1
   * type) despite being inside this same media query, because specificity
   * is compared before source order. Result: the Nextcloud/Kumppanit promo
   * cards kept their desktop min-height:150px + flex:1 1 auto after every
   * visible child (h6/paragraph/button/icon) was hidden, leaving a tall
   * empty box under just the flattened eyebrow text. Re-declare with one
   * extra type selector to win specificity and actually collapse the box. */
  .mega-promo-stack a.mega-solution-card {
    min-height: 0;
    flex: none;
    height: auto;
  }
  .mega-promo-stack {
    display: block;
    gap: 0;
    height: auto;
  }

  .mega-solution-illus,
  .mega-solution-icon,
  .mega-plugin-photo,
  .mega-monitoring-photo,
  .mega-devtools-photo { display: none; }

  .mega-solution-content {
    max-width: 100%;
    flex: none;
    display: block;
  }

  /* Show only eyebrow; hide h6, paragraph, button */
  a.mega-solution-card .mega-solution-content h6,
  a.mega-solution-card .mega-solution-content p:not(.eyebrow),
  a.mega-solution-card .mega-solution-content .btn { display: none; }

  /* Eyebrow as readable nav link text (overrides dark-card white-text rule) */
  a.mega-solution-card .mega-solution-content .eyebrow {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-body) !important;
    text-transform: none;
    letter-spacing: 0;
    margin-top: 0;
    margin-bottom: 0;
  }
  a.mega-solution-card .mega-solution-content .eyebrow-dot::before { display: none; }

  .mega-menu .mega-card-col {
    padding-left: 0 !important;
    padding-right: 0 !important;
    margin-top: 0 !important;
  }

  /* Fix 1: row.g-3 adds negative margin-top to the row and positive margin-top to each
   * column child. When rows are display:block on mobile this causes visual artifacts
   * (text appearing to render outside the viewport on initial hover). Reset them. */
  .mega-menu .row.g-3 {
    margin-top: 0 !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
  }
  .mega-menu .row.g-3 > * {
    margin-top: 0 !important;
  }

  /* Fix 2: non-mega-menu-col columns (Nextcloud promo, company promo) have desktop
   * padding-left: 1.5rem from a higher-specificity rule. Use !important to override. */
  .mega-menu .col-lg-3:not(.mega-menu-col),
  .mega-menu .col-md-3:not(.mega-menu-col) {
    padding-left: 0 !important;
    padding-right: 0 !important;
    margin: 0 !important;
  }

  /* "Katso kaikki ratkaisut" (mega-menu-view-all) had no mobile treatment
   * at all - flatten it to the same indented nav-link style every other
   * mega-menu item gets on mobile, instead of keeping its centered
   * desktop button look. */
  .mega-menu-view-all-wrap {
    text-align: left !important;
    margin-top: 0 !important;
  }
  .mega-menu-view-all {
    display: block;
    padding: 0.75rem 1rem;
    padding-left: 2rem;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-body);
    background: transparent;
    border-radius: 0;
  }
  .mega-menu-view-all:hover {
    background: var(--bg-pale);
    color: var(--primary);
    /* The desktop :hover rule (line ~813) sets text-decoration:underline -
     * CSS cascades per-property, not per-rule, so redeclaring color/
     * background here doesn't cancel it; needs its own override. */
    text-decoration: none;
  }
  .mega-menu-view-all i {
    display: none;
  }
}

/* ===== BLOCK: DNS Compat Logo Trio (small "put together" logo cluster) =====
 * Compact alternative to a photo for a two-column teaser block, when the
 * content is "works with these 3 products" rather than a single image.
 * Reuses each product's own promo-logo asset (see /google-workspace,
 * /microsoft-365, /nextcloud "Promo: text left, logo right" blocks) at a
 * much smaller, card-mounted size instead of their hero-scale originals. */
.dns-compat-logos {
  display: flex;
  gap: 1.25rem;
  justify-content: center;
  flex-wrap: wrap;
}
.dns-compat-logo-item {
  background: white;
  border-radius: 1.25rem;
  box-shadow: 0 10px 30px rgba(0,45,99,0.08);
  padding: 1.75rem 1.25rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  width: 150px;
}
.dns-compat-logo-item img {
  height: 44px;
  width: auto;
  max-width: 100%;
}
.dns-compat-logo-item span {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-body);
  text-align: center;
}
.dns-compat-logo-item {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.dns-compat-logo-item:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.09);
}

/* ===== BLOCK: Toggle Collapse Button (text swaps open/closed, chevron flips) =====
 * Generic Bootstrap-collapse trigger button pattern: two label spans, only
 * one visible at a time based on the button's own aria-expanded state
 * (which Bootstrap's collapse JS sets automatically - no custom JS needed). */
.toggle-collapse-btn .toggle-label-open,
.toggle-collapse-btn .toggle-label-closed { display: none; }
.toggle-collapse-btn[aria-expanded="false"] .toggle-label-open { display: inline; }
.toggle-collapse-btn[aria-expanded="true"] .toggle-label-closed { display: inline; }
.toggle-collapse-btn .uil-angle-down { transition: transform 0.25s ease; }
.toggle-collapse-btn[aria-expanded="true"] .uil-angle-down { transform: rotate(180deg); }

/* Bootstrap's :focus ring/border only lands on the .form-control itself, so
 * a joined input-group (icon prefix with border-end-0, input with
 * border-start-0) looks cut off right before the icon's rounded left corner
 * when focused. Move the ring to the whole group instead, matching
 * Bootstrap's own default focus colors exactly (see .form-control:focus in
 * bootstrap.min.css) so it reads as one seamless pill again. */
.tld-price-search-group:focus-within {
  border-radius: var(--bs-border-radius-lg, 0.5rem);
  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.tld-price-search-group:focus-within .input-group-text {
  border-color: #86b7fe;
}
.tld-price-search-group .form-control:focus {
  box-shadow: none;
}

/* Login modal input-groups (email/user/lock icon prefixes, and the server
 * picker's icon+dropdown-toggle) - same "focus ring covers the whole group,
 * not just the focused child" fix as .tld-price-search-group above. */
.login-modal-input-group:focus-within,
.login-server-picker:focus-within {
  border-radius: var(--bs-border-radius, 0.375rem);
  box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.login-modal-input-group:focus-within .input-group-text,
.login-server-picker:focus-within .input-group-text {
  border-color: #86b7fe;
}
.login-modal-input-group .form-control:focus,
.login-server-picker .login-server-toggle:focus {
  box-shadow: none;
}

/* Server picker toggle: deliberately NOT the branded blue-outline
 * .domain-tld-toggle hover (background flips to solid blue, text to
 * white) - that reads as a strong color-inverting button here, when the
 * brief was "look and feel like the other input fields". Neutral,
 * subtle, input-like instead - no color inversion, no layout shift. */
.login-server-toggle {
  border-color: #ced4da;
  color: #212529;
  background: #fff;
  /* .btn's sitewide padding (0.8rem 1.8rem, meant for standalone CTAs) is
   * why this was visibly taller than the .form-control/.input-group-text
   * siblings next to it - match their padding/weight instead. */
  padding: 0.375rem 0.75rem;
  font-weight: 400;
}
.login-server-toggle:hover,
.login-server-toggle:focus,
.login-server-toggle.show {
  background: #f1f3f5;
  border-color: #ced4da;
  color: #212529;
  /* Cancel the sitewide .btn:hover lift (transform + shadow) - this reads
   * as a form control, not a CTA, so it should stay put on hover. */
  transform: none;
  box-shadow: none;
}

/* ===== BLOCK: TLD Price Table (/verkkotunnukset full price list) =====
 * Plain, compact table - only 2 columns (name/price) so it never needs
 * horizontal scrolling even on mobile. Multiple instances sit side by side
 * in a responsive grid to use desktop width without one very long list. */
.tld-price-table { font-size: 0.9rem; }
.tld-price-table td { padding: 0.35rem 0.25rem; border-color: rgba(164,174,198,0.18); }
.tld-price-table-was { color: var(--text-light); text-decoration: line-through; font-size: 0.8em; margin-left: 0.25rem; }

/* Price pill - every row's price sits in the same rounded-pill shape used by
 * the domain-search TLD pills elsewhere, for visual consistency. Plain rows
 * get a quiet, pale-blue pill; an on-sale row's pill turns a stronger blue
 * and pulses continuously (unlike the one-shot tldDiscountPulse the search
 * pills use, which only fires once per re-render) to keep drawing the eye. */
.tld-price-pill {
  display: inline-block;
  background: rgba(4,123,193,0.08);
  color: var(--blue-dark);
  font-weight: 600;
  padding: 0.25rem 0.75rem;
  border-radius: 50px;
}
.tld-price-pill-sale {
  background: rgba(4,123,193,0.16);
  border: 1px solid rgba(4,123,193,0.45);
  color: #dc3545;
  font-weight: 700;
  animation: tldPriceListSalePulse 1.8s ease-out infinite;
}
@keyframes tldPriceListSalePulse {
  0%   { box-shadow: 0 0 0 0 rgba(4,123,193,0.45); }
  70%  { box-shadow: 0 0 0 10px rgba(4,123,193,0); }
  100% { box-shadow: 0 0 0 10px rgba(4,123,193,0); }
}

/* Row hover, via Bootstrap's own .table-hover (added on the <table> in the
 * template) rather than a hand-rolled tr:hover background-color - Bootstrap
 * paints striped/hover backgrounds on each cell via an inset box-shadow
 * driven by the --bs-table-accent-bg variable (see bootstrap.min.css), which
 * sits ON TOP of a plain background-color set directly on the <tr> and hid
 * it completely - the previous custom rule here never visibly did anything.
 * .table-hover already sets that same variable correctly on hover, in a way
 * that composes properly with .table-striped; overriding the color it uses
 * is enough. */
.tld-price-table { --bs-table-hover-bg: rgba(4,123,193,0.12); }
.tld-price-table tbody tr:hover .tld-price-name { font-weight: 700; }

/* The .tld is plain text (a link/button there wasn't noticeably clickable,
 * and its hover tooltip didn't look good) - the whole row is the
 * hoverable/clickable affordance instead. A "Tarkista" button sits right
 * after the name, hidden until the row is hovered - display:none/inline-
 * block, no transition, so it simply appears rather than sliding or fading
 * in. Click behavior comes for free from initTldSelectButtons() via
 * data-tld-select. On touch devices (no hover) it's always shown instead,
 * since there's no hover state to reveal it there.
 *
 * A version of this button centered against the table's own rendered width
 * (rather than sitting after the name) was tried twice more - once
 * absolutely positioned against the row, once via JS tracking the hovered
 * row - and both misbehaved (wrong position, or visibly jumping/tracking
 * the cursor). This simple, content-anchored version is the one that's
 * actually worked reliably; don't reintroduce either of those. */
.tld-price-row-check {
  display: none;
  margin-left: 0.5rem;
  vertical-align: middle;
}
.tld-price-table tbody tr:hover .tld-price-row-check {
  display: inline-block;
}
@media (hover: none) {
  .tld-price-row-check {
    display: inline-block;
  }
}

/* ===== BLOCK: TLD Scroll Row (/verkkotunnukset popular-extensions strip) =====
 * Small cards in a native horizontally-scrollable row (overflow-x, no JS
 * carousel needed) - mirrors Hostinger's "choose from popular domains" strip.
 * Each card's button is wired up by initTldSelectButtons() in main.js. */
.tld-scroll-row {
  display: flex;
  gap: 1rem;
  overflow-x: auto;
  padding: 0.25rem 0.25rem 0.75rem;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}
.tld-scroll-card {
  flex: 0 0 140px;
  background: white;
  border-radius: 1rem;
  box-shadow: 0 6px 20px rgba(0,45,99,0.08);
  padding: 1.25rem 1rem;
  text-align: center;
}
.tld-scroll-card-name {
  font-family: 'Montserrat', sans-serif;
  font-weight: 800;
  font-size: 1.25rem;
  color: var(--primary);
  margin-bottom: 0.35rem;
}
.tld-scroll-card-price {
  font-size: 0.9rem;
  color: var(--text-light);
  margin-bottom: 0.75rem;
}

/* On-sale variant - same dark blue gradient as the big featured .tld-sale-card
 * (.pricing-card-v3-dark), orange price, and a small pulsing "Tarjous" pill
 * above the name so a discounted TLD stands out from the plain white cards
 * around it in the scroll strip. */
.tld-scroll-card.tld-scroll-card-sale {
  background: linear-gradient(160deg, #002455 0%, var(--primary) 60%, #003060 100%);
  box-shadow: 0 10px 28px rgba(0,45,99,0.2);
}
.tld-scroll-card-sale .tld-scroll-card-name { color: white; }
.tld-scroll-card-sale .tld-scroll-card-price { color: #FFB347; font-weight: 700; }
.tld-scroll-card-sale-badge {
  display: inline-block;
  background: var(--orange-dark);
  color: white;
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-radius: 999px;
  padding: 0.15rem 0.55rem;
  margin-bottom: 0.5rem;
  animation: tldSalePulse 2.2s ease-out infinite;
}

/* .tld-scroll-row's wrapper - adds prev/next arrow buttons either side of the
   native scroll container (see initTldScrollArrows() in main.js). The row
   itself still supports drag/swipe scroll unchanged - these are just an
   additional click/tap affordance, matching carousel-control conventions. */
.tld-scroll-wrap {
  position: relative;
  padding: 0 2.75rem;
}

/* Heading/CTA rows above and below a .tld-scroll-wrap (see
   components/articles-teaser.html) - same 2.75rem horizontal padding as
   the wrap itself, so the heading/button line up with the card content
   below/above instead of the scroll-arrow gutter. */
.tld-scroll-heading {
  padding: 0 2.75rem;
  margin-top: 1rem;
}
.tld-scroll-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: white;
  box-shadow: 0 6px 20px rgba(0,45,99,0.15);
  color: var(--blue-dark);
  font-size: 1.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  transition: background 0.2s ease, color 0.2s ease;
}
.tld-scroll-arrow:hover { background: var(--primary); color: white; }
.tld-scroll-arrow-prev { left: 0; }
.tld-scroll-arrow-next { right: 0; }

/* Rounded-corner container for the full TLD price table - a plain <table>
   ignores border-radius on its own cells/borders, so the radius + clip has
   to live on this wrapping div instead. */
.tld-price-table-wrap {
  border: 1px solid rgba(164,174,198,0.25);
  border-radius: 1rem;
  overflow: hidden;
}
.tld-price-table-wrap .table { margin-bottom: 0; }

/* ===== BLOCK: Cookie Table (/evastekaytanto) =====
 * Generated from resources/data/shared/cookie-categories.php via the
 * cookieCategories() Twig function - never hand-write this table again,
 * edit the PHP data instead. Same rounded card-wrap convention as
 * .tld-price-table-wrap, smaller text for responsivity on a 5-column
 * table (name/category/purpose/duration/status). */
.cookie-table-wrap {
  border: 1px solid rgba(164,174,198,0.25);
  border-radius: 1rem;
  overflow: hidden;
}
.cookie-table {
  font-size: 0.85rem;
  margin-bottom: 0;
}
.cookie-table th {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-light);
  background: var(--bg-pale);
  border-bottom-width: 1px;
  white-space: nowrap;
}
.cookie-table td {
  border-color: rgba(164, 174, 198, 0.18);
  vertical-align: top;
}
.cookie-table code {
  font-size: 0.8em;
  color: var(--text-dark);
  background: var(--soft-blue);
  padding: 0.1rem 0.35rem;
  border-radius: 0.3rem;
  white-space: nowrap;
}
.cookie-table-category {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  white-space: nowrap;
}
.cookie-table-category-necessary { background: var(--soft-blue); color: var(--primary); }
.cookie-table-category-preferences { background: rgba(39, 170, 225, 0.14); color: var(--blue-dark); }
.cookie-table-category-analytics { background: rgba(46, 153, 0, 0.12); color: var(--accent); }
.cookie-table-category-marketing { background: rgba(247, 148, 30, 0.16); color: var(--orange-dark); }
.cookie-table-status {
  font-size: 0.7rem;
  font-weight: 700;
  white-space: nowrap;
}
.cookie-table-status-active { color: var(--accent); }
.cookie-table-status-planned { color: var(--text-light); }

/* Card fallback below the md breakpoint (see evastekaytanto.html's
 * d-none d-md-block / d-md-none pair) - same data, same font-size, just
 * stacked instead of squeezed into a 6-column table on a narrow screen. */
.cookie-card {
  border: 1px solid rgba(164, 174, 198, 0.25);
  border-radius: 0.8rem;
  padding: 1rem 1.1rem;
  margin-bottom: 0.75rem;
  font-size: 0.85rem;
}
.cookie-card:last-child { margin-bottom: 0; }
.cookie-card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}
.cookie-card-name code { font-size: 0.8em; }

/* ===== BLOCK: Hero Image Wrapper - Screenshot bleed variant (/hostaan-plugin) =====
 * For a wide real screenshot (not a robot SVG, not a device-mockup crop).
 * Positioned absolutely within .hero-image-style (that section is already
 * position:relative + overflow:hidden + border-radius, same containing
 * block the robot heroes bleed into) and pushed past the section's own
 * right edge via a negative `right` offset on the img, so part of it is
 * clipped under the section's rounded border - a horizontal version of
 * the robots' vertical bleed.
 *
 * FIX: an earlier version gave the WRAPPER itself overflow:hidden at the
 * tablet/mobile tiers, to stop the image visually covering the text
 * column. But the wrapper's own box (a percentage of the section) is
 * narrower than the fixed-px image at those tiers, so overflow:hidden on
 * the wrapper clipped real image content off its LEFT edge too - a
 * "porthole" crop that never happens on the robot heroes (they only ever
 * rely on the outer SECTION's overflow:hidden, never their own wrapper's).
 * Fixed by giving the text column its own stacking order (z-index:3, see
 * below) so it always paints above the image regardless of overlap, and
 * removing overflow:hidden from the wrapper entirely - only the section's
 * own clipping (of the right-side bleed) ever applies now, so the image
 * is never internally cropped on its left, at any viewport width. */
.hero-image-style-shot .col-lg-6:first-child {
  position: relative;
  z-index: 3;
}
.hero-image-wrapper-shot {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 56%;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  overflow: visible;
  pointer-events: none;
}
.hero-image-wrapper-shot img {
  position: relative;
  right: -100px;
  width: 680px;
  max-width: none;
  opacity: 0;
  transform: translateX(24px);
  animation: heroShotSlideIn 1s ease-out forwards;
  animation-play-state: paused;
}
.hero-image-wrapper-shot.play img {
  animation-play-state: running;
}
@keyframes heroShotSlideIn {
  0% { opacity: 0; transform: translateX(24px); }
  100% { opacity: 1; transform: translateX(0); }
}

@media (max-width: 991px) {
  .hero-image-style-shot .col-lg-6:first-child {
    max-width: 58%;
    flex: 0 0 58%;
  }
  .hero-image-wrapper-shot { width: 42%; }
  .hero-image-wrapper-shot img { width: 480px; right: -120px; }
}

/* Mobile: same breakpoint (768px) as .hero-carousel-robot-left's own
 * mobile tier on the front page (mirrored - that one bleeds left, this
 * bleeds right) - the screenshot stays partially visible under the
 * section's rounded border at every viewport size instead of collapsing
 * to a static full-width block below the text the way this used to. */
@media (max-width: 768px) {
  .hero-image-style-shot .col-lg-6:first-child {
    max-width: 62%;
    flex: 0 0 62%;
  }
  .hero-image-wrapper-shot {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    width: 55%;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    overflow: visible;
  }
  .hero-image-wrapper-shot img {
    position: relative;
    width: 380px;
    right: -90px;
    animation: none;
    opacity: 1;
    transform: none;
  }
}

@media (max-width: 575px) {
  .hero-image-wrapper-shot img {
    width: 274px; /* 380 * 0.72, same scale factor as the other hero variants */
    right: -65px; /* -90 * 0.72 */
  }
}

/* ===== BLOCK: Nginx Logo Lockup (/hostaan-plugin) =====
 * Emblem + wordmark side by side as one tight unit instead of stacked with
 * a large gap between them - reads as a single combined logo. */
.nginx-logo-lockup {
  display: inline-flex;
  align-items: center;
  gap: 0.85rem;
}
.nginx-logo-lockup img:first-child { width: 154px; height: auto; } /* emblem, +10% */
.nginx-logo-lockup img:last-child { width: 240px; height: auto; } /* wordmark, +20% */

/* ============================================
   TEAM PHOTO CLUSTER — real staff photos, floating and
   individually hoverable, no names/captions. The point is
   purely "real humans work here", used on /asiakaspalvelu and
   /yhteystiedot.

   Redesigned 2026-09-23: the mouse-parallax (JS setting --px/
   --py on mousemove) read as jagged/jittery rather than nice,
   per feedback - removed entirely (js/main.js's
   initTeamPhotoCluster() is gone). Each photo now just drifts
   gently in its own small loop (teamPhotoDrift, several
   waypoints rather than a straight up/down bob, for a more
   organic feel), independently timed per photo via
   --float-duration/--float-delay so they're never in sync.
   Sized up ~20% and pulled closer together (especially
   horizontally) per feedback.

   Two-element structure per photo is kept even without the JS:
   .team-photo-item (position + float, absolute) wraps
   .team-photo <img> (hover scale-up, its own separate
   transform) - still worth keeping apart so a hover-triggered
   scale can never fight the ambient drift for the same
   property.
   ============================================ */
.team-photo-cluster {
  position: relative;
  width: 100%;
  max-width: 400px;
  height: 380px;
  margin: 0 auto;
}
.team-photo-cluster .team-photo-item {
  position: absolute;
  animation: teamPhotoDrift ease-in-out infinite;
  animation-delay: var(--float-delay, 0s);
  animation-duration: var(--float-duration, 7s);
  will-change: transform;
}
.team-photo-cluster .team-photo {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  border: 4px solid #ffffff;
  box-shadow: var(--shadow-md);
  cursor: pointer;
  transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.3s ease;
}
.team-photo-cluster .team-photo:hover {
  transform: scale(1.14);
  box-shadow: 0 0.9rem 1.75rem rgba(30, 34, 40, 0.18);
}
@keyframes teamPhotoDrift {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(5px, -7px); }
  50%  { transform: translate(-4px, -11px); }
  75%  { transform: translate(-6px, -3px); }
  100% { transform: translate(0, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .team-photo-cluster .team-photo-item,
  .ai-team-avatar-wrap { animation: none !important; }
}
.team-photo-cluster .team-photo-item-1 { width: 202px; height: 202px; top: 0; left: 34px; z-index: 3; --float-duration: 7.5s; --float-delay: 0s; }
.team-photo-cluster .team-photo-item-2 { width: 154px; height: 154px; top: 12px; right: 24px; z-index: 2; --float-duration: 6.5s; --float-delay: -1.4s; }
.team-photo-cluster .team-photo-item-3 { width: 168px; height: 168px; bottom: 24px; left: 10px; z-index: 2; --float-duration: 8s; --float-delay: -2.8s; }
.team-photo-cluster .team-photo-item-4 { width: 125px; height: 125px; bottom: 0; left: 168px; z-index: 4; --float-duration: 7s; --float-delay: -3.6s; }
.team-photo-cluster .team-photo-item-5 { width: 110px; height: 110px; bottom: 50px; right: 14px; z-index: 1; --float-duration: 7.8s; --float-delay: -0.7s; }
@media (max-width: 767px) {
  .team-photo-cluster { height: 320px; max-width: 340px; }
  .team-photo-cluster .team-photo-item-1 { width: 156px; height: 156px; }
  .team-photo-cluster .team-photo-item-2 { width: 120px; height: 120px; }
  .team-photo-cluster .team-photo-item-3 { width: 130px; height: 130px; }
  .team-photo-cluster .team-photo-item-4 { width: 98px; height: 98px; }
  .team-photo-cluster .team-photo-item-5 { width: 86px; height: 86px; }
}

/* ============================================
   AI TEAM — Aada (AI phone) + Hostaan Agentti (AI chat),
   at the top of /asiakaspalvelu's AI support section, above
   the 3-panel grid. Visually distinct from real staff photos
   (gradient ring + AI badge).

   Redesigned 2026-09-23: was a single shared absolutely-
   positioned cluster (like the human photos) with names/roles
   in a separate row below - made the two hard to align
   vertically under their own avatar, per feedback. Now each
   member is its own self-contained flex column (avatar + name
   + role stacked together), so the caption is always directly
   under its own avatar with no alignment bookkeeping needed.
   Still floats, via the same teamPhotoDrift keyframe as the
   human photos - transform on a normally-flowed flex item
   doesn't affect its layout position, so no absolute
   positioning is required to float in place.

   .ai-team-avatar-wrap is the outer, unclipped element the
   badge is positioned against (a badge near the edge of a
   circle gets sliced in half if it's a child of the
   overflow:hidden circle itself - confirmed the hard way).
   ============================================ */
.ai-team-row {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 3.5rem;
}
.ai-team-member { text-align: center; }
.ai-team-avatar-wrap {
  position: relative;
  width: 160px;
  height: 160px;
  margin: 0 auto;
  animation: teamPhotoDrift ease-in-out infinite;
  animation-delay: var(--float-delay, 0s);
  animation-duration: var(--float-duration, 7s);
  will-change: transform;
}
.ai-team-avatar {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  background: linear-gradient(135deg, var(--blue-light), var(--blue-dark) 60%, #001a33);
  border: 4px solid rgba(255,255,255,0.9);
  box-shadow: var(--shadow-md);
}
.ai-team-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 12%;
  transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.ai-team-avatar-wrap:hover .ai-team-avatar img {
  transform: scale(1.14);
}
/* robot_hello.svg is a tall full-body illustration (viewBox 200x350).
   object-fit:cover here is width-constrained (0.8x), so a scale BELOW 1
   on top of that shrinks the image smaller than the frame instead of
   zooming in, pulling the bottom edge up (since transform-origin is
   "top") and exposing empty gradient background where the torso should
   still be visible - that was the "torso cropped before the circle
   crops it" bug. Fixed by scaling UP instead (zooming in never exposes
   a gap). No horizontal shift - the illustration's eye-line already
   sits on the viewBox's horizontal center once cover-fit is applied, so
   centering it keeps the robot centered instead of pushed to one side
   (per feedback), and a smaller scale keeps more of it in frame. Hover
   keeps the same origin and bumps the scale further (not a bare
   `scale()`, which would replace this base transform rather than
   extend it - that mismatch is why Aada and the robot once hovered
   differently). */
.ai-team-avatar-robot img {
  transform: scale(1.05);
  transform-origin: top center;
}
.ai-team-avatar-wrap:hover .ai-team-avatar.ai-team-avatar-robot img {
  transform: scale(1.18);
}
.ai-team-badge {
  position: absolute;
  bottom: 0.25rem;
  right: 0.25rem;
  background: var(--blue-dark);
  color: #ffffff;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  border: 2px solid #ffffff;
  z-index: 2;
}
/* ===== BLOCK: Articles / News (/tiedotteet, /blogi, and their {slug} detail
 * pages) - category + date use plain Bootstrap .badge.rounded-pill + a
 * .text-muted.small date string (see /styles - no bespoke pill/tab CSS
 * here, matches the rest of the site's "default Bootstrap 5 components,
 * brand color overrides only" convention). */

/* Article row - image left, content right, one per row. The shared base
 * card for /tiedotteet's listing (one column, full-width) and /blogi's
 * "secondary" column (narrower, still image-left) - deliberately the same
 * component so both sections read as built from the same DNA, per
 * feedback, even though /blogi additionally layers a featured hero + a
 * compact thumbnail column around it. */
.article-row {
  display: flex;
  gap: 1.5rem;
  align-items: stretch;
  background: white;
  border-radius: 1.25rem;
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.05);
  padding: 1.25rem;
  text-decoration: none;
  color: inherit;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.article-row:hover {
  transform: translateY(-3px);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.09);
  color: inherit;
}
.article-row-image {
  flex: 0 0 240px;
  border-radius: 0.85rem;
  overflow: hidden;
}
.article-row-image img {
  width: 100%;
  height: 100%;
  min-height: 160px;
  object-fit: cover;
}
.article-row-content {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.article-row-content h4,
.article-row-content h5 { margin-bottom: 0.5rem; }
.article-row-content p {
  color: var(--text-light);
  margin-bottom: 0.75rem;
}
/* Rendered as a real .btn.btn-light now (see /styles - no custom button
   look) - this just pins it to the bottom of the card's flex column. */
.article-row-readmore {
  margin-top: auto;
}

@media (max-width: 575px) {
  .article-row { flex-direction: column; }
  .article-row-image { flex-basis: auto; height: 160px; }
}

/* Detail page body - plain prose, one type per block (see loadArticle()'s
   body schema in vhosts/hostaan/index.php). */
.article-body { font-size: 1.05rem; line-height: 1.75; }
.article-body p { margin-bottom: 1.25rem; }
.article-body h2,
.article-body h3,
.article-body h4 { margin-top: 2rem; margin-bottom: 1rem; line-height: 1.3; }
.article-body ul,
.article-body ol { margin-bottom: 1.25rem; padding-left: 1.5rem; }
.article-body li { margin-bottom: 0.5rem; }
.article-body a { text-decoration: underline; text-underline-offset: 0.15em; }
.article-body pre.alert-secondary {
  display: block;
  margin: 1.5rem 0;
  padding: 1rem 1.25rem;
  overflow-x: auto;
  white-space: pre;
  line-height: 1.5;
  border-left: 4px solid rgba(108, 117, 125, 0.5);
  border-radius: var(--radius-lg);
  font-size: 0.9rem;
}
.article-body pre code {
  display: block;
  color: inherit;
  white-space: inherit;
  overflow-wrap: normal;
  font-size: inherit;
}
.article-body :not(pre) > code {
  padding: 0.1em 0.3em;
  border-radius: var(--radius-sm);
  background: rgba(108, 117, 125, 0.1);
  color: var(--text-dark);
  overflow-wrap: anywhere;
}
.article-body img {
  max-width: 100%;
  height: auto;
  border-radius: 1rem;
  margin-bottom: 1.25rem;
}
.article-body table { display: block; max-width: 100%; overflow-x: auto; margin-bottom: 1.5rem; }
.article-body th,
.article-body td { padding: 0.6rem; border: 1px solid rgba(164, 174, 198, 0.3); }
.article-body blockquote {
  border-left: 4px solid var(--primary);
  padding: 0.25rem 0 0.25rem 1.25rem;
  margin: 0 0 1.25rem;
  font-style: italic;
  color: var(--text-light);
}
.article-hero-image {
  width: 100%;
  max-height: 420px;
  object-fit: cover;
  border-radius: 1.25rem;
  margin-bottom: 2rem;
}

/* Article scroll-strip card - reuses .tld-scroll-wrap/.tld-scroll-row/
   .tld-scroll-arrow (see initTldScrollArrows() in main.js, generalized to
   also pick up .article-scroll-card for its width calc) with a wider,
   vertical (image-on-top) card instead of TLD's small centered chip. */
.article-scroll-card {
  flex: 0 0 300px;
  text-align: left;
  padding: 0;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  display: block;
}
.article-scroll-card:hover { color: inherit; }
.article-scroll-card-image { height: 160px; overflow: hidden; }
.article-scroll-card-image img { width: 100%; height: 100%; object-fit: cover; }
.article-scroll-card-body { padding: 1.1rem 1.25rem 1.35rem; }
.article-scroll-card-body h5 {
  margin-bottom: 0.4rem;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.article-scroll-card-body p {
  margin-bottom: 0;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* Mobile: the horizontal scroll strip only ever showed ~1.5 cards at a
   time on a narrow viewport (dull, per feedback) - below 576px, article
   scroll strips (.article-scroll-wrap, the marker class added alongside
   .tld-scroll-wrap specifically for article content - the TLD-price
   scroll strip elsewhere keeps its normal horizontal behaviour
   unaffected) become a plain vertical stack instead, capped to the
   first 4 cards so the section doesn't run unboundedly long. */
@media (max-width: 575px) {
  .article-scroll-wrap { padding: 0; }
  .article-scroll-wrap .tld-scroll-arrow { display: none; }
  .article-scroll-wrap .article-scroll-row {
    flex-direction: column;
    overflow-x: visible;
  }
  .article-scroll-wrap .article-scroll-card {
    flex: 1 1 auto;
    width: 100%;
  }
  .article-scroll-wrap .article-scroll-card:nth-child(n+5) {
    display: none;
  }
}

@media (max-width: 767px) {
  .ai-team-row { gap: 2rem; }
  .ai-team-avatar-wrap { width: 128px; height: 128px; }
}

/* ===== BLOCK: Sitewide Emergency Banner (components/emergency-banner.html) =====
 * Floating bar fixed above the header, on every page - see
 * resources/data/hostaan.fi/emergency-alert.php for how to activate it.
 * Its rendered height (which changes as it wraps to more lines on narrow
 * viewports) is measured and pushed into --eb-h by initEmergencyBanner()
 * (main.js), which the .navbar/body rules above read to shift everything
 * below it down by exactly that amount. */
.emergency-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1001;
  color: #ffffff;
  font-weight: 600;
  font-size: 0.9rem;
}
.emergency-banner-danger  { background: #c62839; }
.emergency-banner-warning { background: #b8790f; }
.emergency-banner-info    { background: var(--blue-dark); }

.emergency-banner-inner {
  max-width: 1320px;
  margin: 0 auto;
  padding: 0.65rem 3rem 0.65rem 1.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  text-align: center;
  position: relative;
}

.emergency-banner-icon {
  color: #ffd166;
  font-size: 1.1rem;
  flex-shrink: 0;
}

.emergency-banner-link {
  color: #ffffff;
  text-decoration: underline;
  font-weight: 700;
  white-space: nowrap;
}
.emergency-banner-link:hover { color: #ffffff; }

.emergency-banner-close {
  position: absolute;
  right: 0.6rem;
  top: 50%;
  transform: translateY(-50%);
  background: transparent;
  border: none;
  color: #ffffff;
  font-size: 1.5rem;
  line-height: 1;
  opacity: 0.8;
  padding: 0.25rem;
  cursor: pointer;
}
.emergency-banner-close:hover { opacity: 1; }

@media (max-width: 575px) {
  .emergency-banner-inner {
    font-size: 0.85rem;
    padding: 0.6rem 2.5rem 0.6rem 1rem;
  }
}

/* ===== BLOCK: Reviews (components/review-card.html, review-marquee.html,
 * review-spotlight.html) - replaces the Trustmary widget's free-tier limits
 * with hand-curated review data (resources/data/hostaan.fi/reviews-*.php),
 * see doc/ai-memory/feature-reviews-block-2026-09-29.md. */
.review-card {
  background: white;
  border-radius: 1.25rem;
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.05);
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  /* No explicit height: 100% here on purpose - that was the actual bug
     behind "Näytä lisää" not growing the card. A flex item's automatic
     min-height (which lets it grow past its stretched cross-size to fit
     its own content) only applies when height is auto; an explicit
     height:100% overrides that protection and pins the card to whatever
     pixel value it resolved to at first paint, clipping anything that
     grows afterward (the expanded text, and the now-pushed-down source
     link). align-items: stretch (the marquee track's default) still
     equalizes card heights within a row at initial render; growing one
     card's content afterward now genuinely grows the row instead of
     being clipped by it - fixes review-marquee.html, review-spotlight.html
     and the Bootstrap carousel (review-carousel.html) at once, since none
     of those ancestors have an explicit height that would otherwise still
     have capped it even with this fixed. */
}
.review-card-header {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-bottom: 1rem;
}
.review-avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}
.review-name { font-weight: 700; }
.review-card .star-rating { font-size: 0.8rem; }
/* Facebook shows a binary "Suosittelee" (recommends) rating, not stars -
   its own recommend icon repeated 5x mimics the star row's shape. The
   icon has no rounding of its own (unlike Google's star glyph), hence the
   small rounded frame around each one. */
.review-recommend-row { display: inline-flex; gap: 0.25rem; }
.review-recommend-icon {
  width: 1rem;
  height: 1rem;
  border-radius: 0.25rem;
  background: rgba(24, 119, 242, 0.08);
  padding: 0.15rem;
}
.review-text {
  white-space: pre-line;
  color: var(--text-body);
  flex: 1 1 auto;
  font-size: 0.95rem;
  font-weight: 500;
}
.review-text-clamped {
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
}
.review-expand-btn {
  align-self: flex-start;
  text-decoration: none;
  font-weight: 600;
  margin-bottom: 0.75rem;
}
.review-source {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--text-light);
  text-decoration: none;
  margin-top: auto;
  padding-top: 0.75rem;
  border-top: 1px solid rgba(164, 174, 198, 0.15);
}
.review-source:hover { color: var(--primary); }

/* Marquee - two duplicate tracks, same continuous-loop technique as
   .logo-marquee, just with review cards instead of small logo chips
   (slower duration, wider fixed card width). Paused on hover so a
   visitor can actually read/click a card mid-scroll. */
.review-marquee {
  overflow: hidden;
  position: relative;
  display: flex;
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, black 4%, black 96%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, black 4%, black 96%, transparent 100%);
}
.review-marquee-track {
  display: flex;
  gap: 1.5rem;
  padding: 0.5rem 0.75rem 1.5rem;
  flex-shrink: 0;
  animation: reviewMarquee 90s linear infinite;
}
.review-marquee:hover .review-marquee-track {
  animation-play-state: paused;
}
/* Opposite scroll direction, same speed/easing - lets a Google-only and a
   Facebook-only marquee on the same page scroll toward each other rather
   than both drifting the same way. */
.review-marquee-reverse .review-marquee-track {
  animation-direction: reverse;
}
.review-marquee .review-card {
  flex: 0 0 340px;
  width: 340px;
}
@keyframes reviewMarquee {
  from { transform: translateX(0); }
  to { transform: translateX(-100%); }
}

/* Spotlight - single bigger review, centered. */
.review-spotlight {
  max-width: 560px;
  margin: 0 auto;
}
.review-spotlight .review-card {
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.09);
}
.review-spotlight .review-avatar { width: 64px; height: 64px; }
.review-spotlight .review-text { font-size: 1.1rem; }

/* Featured-reviews carousel (review-carousel.html) - same subtle
   arrow-only prev/next pattern as .hero-image-carousel's own arrows, but
   recolored for a light background (the hero's version is white text
   with a dark text-shadow, meant for a dark/photo hero backdrop) and
   positioned just outside the centered review-spotlight card rather than
   flush against the section edge. */
.review-featured-carousel {
  position: relative;
}
/* Bootstrap's .carousel-inner clips vertically too (needed for the
   horizontal slide-masking effect) - without breathing room here, the
   review-spotlight card's own box-shadow gets cropped top/bottom since
   the clipped area was exactly the card's height with no margin. */
.review-featured-carousel .carousel-inner {
  padding: 1.25rem 0;
  margin: -1.25rem 0;
}
/* Round grey buttons (not the bare arrow-only hero style - that's too
   subtle against this section's plain background) with the chevron
   centered inside, same positioning approach as the hero carousel. */
.review-featured-carousel .carousel-control-prev,
.review-featured-carousel .carousel-control-next {
  width: 44px;
  height: 44px;
  background-color: rgba(52, 63, 82, 0.08);
  border-radius: 50%;
  box-shadow: none;
  top: 45%;
  transform: translateY(-50%);
  bottom: auto;
  opacity: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 5;
  transition: background-color 0.2s ease;
}
.review-featured-carousel .carousel-control-prev { left: -3rem; }
.review-featured-carousel .carousel-control-next { right: -3rem; }
.review-featured-carousel .carousel-control-prev:hover,
.review-featured-carousel .carousel-control-next:hover {
  background-color: rgba(52, 63, 82, 0.16);
}
.review-featured-carousel .carousel-control-prev .uil,
.review-featured-carousel .carousel-control-next .uil {
  font-size: 1.5rem;
  color: var(--text-body);
  line-height: 1;
}
@media (max-width: 991px) {
  .review-featured-carousel .carousel-control-prev,
  .review-featured-carousel .carousel-control-next {
    display: none;
  }
}
/* Round dot indicators, same recipe as .hero-image-carousel's own. */
.review-featured-carousel .carousel-indicators {
  position: static;
  margin-top: 1.5rem;
}
.review-featured-carousel .carousel-indicators [data-bs-target] {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: rgba(52, 63, 82, 0.3);
  border: none;
  margin: 0 4px;
}
.review-featured-carousel .carousel-indicators [data-bs-target].active {
  background-color: #343f52;
}

@media (max-width: 575px) {
  .review-marquee-track { animation-duration: 60s; }
  .review-marquee .review-card { flex-basis: 280px; width: 280px; }
}

/* Keep the featured blog headline readable without filling a mobile screen. */
@media (max-width: 991.98px) {
  .blog-featured-title {
    font-size: clamp(1.5rem, 5vw, 2rem);
    line-height: 1.2;
    overflow-wrap: break-word;
  }
}

/* ===== Generic instant-filter (see js/instant-filter.js) =====
 * [hidden] already means display:none per the HTML spec, but a defensive
 * explicit rule guards against anything with equal/higher specificity
 * (e.g. a flex/grid child class) accidentally winning instead. Used by
 * /tuki's search and /blogi's search. */
[hidden] {
  display: none !important;
}

/* Search box - built on the site's own .form-control, not a bespoke input,
 * so it inherits the same focus ring/border/radius as every other text
 * input on the site. Used by /tuki and /blogi's search. */
.filter-search {
  position: relative;
  width: min(100%, 680px);
  margin-top: 1.5rem;
}

.filter-search-icon {
  position: absolute;
  top: 50%;
  left: 1rem;
  transform: translateY(-50%);
  color: var(--blue-dark);
  font-size: 1.2rem;
  pointer-events: none;
}

.filter-search input.form-control {
  padding-left: 2.75rem;
}

.filter-search-clear {
  position: absolute;
  top: 50%;
  right: 0.5rem;
  transform: translateY(-50%);
  padding: 0.35rem 0.7rem;
  border: 0;
  border-radius: 0.45rem;
  background: var(--bg-pale);
  color: var(--blue-dark);
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
}

.filter-search-clear:hover {
  background: rgba(4, 123, 193, 0.12);
}

/* Keep author metadata compact, including inside article typography. */
.article-author .article-author-name {
  font-size: 1rem;
  font-weight: 700;
  line-height: 1.4;
  margin-top: 0;
  color: var(--bs-heading-color, #034b7b);
}
.article-author .article-author-bio {
  font-size: 0.875rem;
  line-height: 1.65;
}

/* News placeholders use the original mascot on the hero sunset gradient.
   Title artwork remains unchanged for social sharing and other card layouts. */
.article-row-image.article-row-placeholder {
  position: relative;
  align-self: stretch;
  min-height: 240px;
  background: radial-gradient(ellipse at 50% 120%, #ffc5a6 0%, #ffe8ce 28%, transparent 62%), linear-gradient(180deg, #fff 10%, #dff2f9 100%);
}
.article-row-image.article-row-placeholder img {
  position: absolute;
  inset: 0 auto 0 0;
  display: block;
  width: 100%;
  max-width: none;
  height: 100%;
  min-height: 0;
  padding: 1rem;
  object-fit: contain;
}
@media (max-width: 575px) {
  .article-row-image.article-row-placeholder {
    width: 100%;
    height: auto;
    aspect-ratio: 1;
    min-height: 0;
  }
}

.article-scroll-card-image img.article-thumbnail-placeholder { object-fit: contain; background: #fff; }
