/*
Theme Name: Aleh Vik
Theme URI: https://alehvik.pl
Author: Aleh Vik
Description: One-page block theme for Aleh Vik construction & renovation services (Warsaw). Design tokens transcribed from 01-UI-SPEC.md.
Version: 1.0.0
Requires at least: 6.7
Tested up to: 7.1
Requires PHP: 8.1
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: alehvik
*/

/* --- A note on the spacing variables, learned the hard way ---------------
   theme.json names two of its spacing sizes "2xl" and "3xl", but WordPress
   runs preset slugs through kebab-casing before emitting them, so what
   actually reaches the page is --wp--preset--spacing--2-xl and --3-xl. The
   obvious spelling, --wp--preset--spacing--2xl, is simply undefined — and an
   undefined custom property does not fall back to the previous cascade
   winner, it makes the whole declaration invalid, so the property lands on
   its initial value. Nine rules here were written the obvious way and were
   silently computing to zero: every section on the page had no vertical
   padding at all on desktop, and the sticky mobile call bar was sitting on
   top of the footer because body's padding-bottom never applied.

   Nothing warns you about this. If a spacing value ever looks like it is
   being ignored, check the variable name against what global-styles-inline-css
   actually emits before looking anywhere else. Sizes xs/sm/md/lg/xl are
   unaffected — only slugs starting with a digit get rewritten.

   The fallbacks below exist so that a future rename fails visibly small
   rather than invisibly to zero. */
:root {
  --space-2xl: var(--wp--preset--spacing--2-xl, 48px);
  --space-3xl: var(--wp--preset--spacing--3-xl, 64px);
}

/* === Section rhythm (UI-SPEC 3xl/xl note: 64px desktop, 32px mobile) === */
.wp-block-group.section-rhythm {
  padding-top: var(--space-3xl);
  padding-bottom: var(--space-3xl);
}

@media (max-width: 640px) {
  .wp-block-group.section-rhythm {
    padding-top: var(--wp--preset--spacing--xl);
    padding-bottom: var(--wp--preset--spacing--xl);
  }
}

/* === Header contact (TRUST-01/02: click-to-call + WhatsApp, always visible) === */
.site-header__contact {
  gap: var(--wp--preset--spacing--md);
}

.site-header__phone a,
.site-header__whatsapp a {
  color: var(--wp--preset--color--text-on-dark);
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  transition: opacity 0.2s var(--ease-out-smooth);
}

.site-header__phone a:hover,
.site-header__whatsapp a:hover {
  opacity: 0.8;
}

@media (max-width: 480px) {
  .site-header__whatsapp {
    display: none; /* keep header tidy on very small phones; WhatsApp lives in the sticky bar */
  }
}

/* Mobile sticky call/WhatsApp bar — hidden on desktop, always reachable on
   mobile where 90%+ of this vertical's traffic lands (01-RESEARCH.md /
   FEATURES.md). Sits above any browser chrome, never covers the hero CTA.
   Specificity note: Gutenberg's own block-support layout class
   (.wp-block-buttons.is-layout-flex, two classes = 0,2,0) outranks a plain
   single-class .mobile-cta-bar rule (0,1,0) regardless of source order, so
   this section deliberately matches on two classes too. */
.wp-block-buttons.mobile-cta-bar {
  display: none;
}

@media (max-width: 767px) {
  .wp-block-buttons.mobile-cta-bar {
    display: flex;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 50;
    box-shadow: 0 -4px 16px rgb(30 41 59 / 0.15);
  }

  .mobile-cta-bar > .wp-block-button {
    flex: 1;
    margin: 0;
  }

  .mobile-cta-bar .wp-block-button__link {
    width: 100%;
    text-align: center;
    padding: var(--wp--preset--spacing--sm) 0;
    font-weight: 700;
    text-decoration: none;
    min-height: var(--space-2xl);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0;
  }

  .mobile-cta-bar__call .wp-block-button__link {
    background: var(--wp--preset--color--accent);
    color: var(--wp--preset--color--text-on-dark);
  }

  .mobile-cta-bar__whatsapp .wp-block-button__link {
    background: var(--wp--preset--color--contrast);
    color: var(--wp--preset--color--text-on-dark);
  }

  /* Never let the sticky bar hide page content behind it. */
  body {
    padding-bottom: var(--space-2xl);
  }
}

/* === Footer area statement + trust badges (TRUST-04/05) === */
.footer__area {
  opacity: 0.85;
  font-size: var(--wp--preset--font-size--label);
  margin-top: 0;
}

.footer__trust-slot {
  margin-top: var(--wp--preset--spacing--md);
  gap: var(--wp--preset--spacing--sm);
}

.trust-badge {
  display: inline-block;
  padding: var(--wp--preset--spacing--xs) var(--wp--preset--spacing--sm);
  border: 1px solid var(--wp--preset--color--text-on-dark);
  border-radius: 999px;
  font-size: var(--wp--preset--font-size--label);
  color: var(--wp--preset--color--text-on-dark);
  opacity: 0.9;
}

/* === Reviews (REV-01) === */
.reviews {
  background-color: var(--wp--preset--color--base);
}

.reviews-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--wp--preset--spacing--md);
}

@media (min-width: 640px) {
  .reviews-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .reviews-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.review-card {
  margin: 0;
  padding: var(--wp--preset--spacing--md);
  background: var(--wp--preset--color--surface);
  border: 1px solid var(--wp--preset--color--border);
  border-radius: 12px;
}

.review-card p {
  margin: 0 0 var(--wp--preset--spacing--sm) 0;
}

/* Every card is now as tall as the tallest review, so that the section — and
   the contact form below it — cannot change height as reviews rotate. That
   leaves slack in the short ones, so spend it: rating at the top, quote under
   it, signature pushed to the bottom edge. The uniform height then reads as a
   deliberate card rather than as padding nobody trimmed. */
.review-card {
  display: flex;
  flex-direction: column;
}

.review-card cite {
  margin-top: auto;
  padding-top: var(--wp--preset--spacing--sm);
}

.review-card cite {
  font-style: normal;
  font-weight: 700;
  font-size: var(--wp--preset--font-size--label);
}

/* === Fallback contact form (TRUST-03) === */
.contact-form-section {
  background-color: var(--wp--preset--color--surface);
}

.contact-form {
  max-width: 480px;
  margin: 0 auto;
}

.contact-form__honeypot {
  position: absolute;
  left: -9999px;
}

.contact-form label {
  display: block;
  font-weight: 700;
  font-size: var(--wp--preset--font-size--label);
  margin-bottom: var(--wp--preset--spacing--md);
}

.contact-form input,
.contact-form textarea {
  display: block;
  width: 100%;
  margin-top: var(--wp--preset--spacing--xs);
  padding: var(--wp--preset--spacing--sm);
  border: 1px solid var(--wp--preset--color--border);
  border-radius: 8px;
  font-size: var(--wp--preset--font-size--body);
  font-weight: 400;
  font-family: inherit;
  transition: border-color 0.2s var(--ease-out-smooth);
}

.contact-form input:focus,
.contact-form textarea:focus {
  border-color: var(--wp--preset--color--accent);
  outline: none;
}

.contact-form__submit {
  width: 100%;
  border: none;
}

.contact-form__status {
  text-align: center;
  min-height: 1.5em;
  font-weight: 700;
}

/* Footer margin reset (build-with-wordpress convention) */
.wp-site-blocks > footer {
  margin-block-start: 0;
}

/* === Hero ===
   The headline and CTA sit ON TOP of the work photo (core/cover), rather than
   stacked underneath it. The aspect-ratio rules that used to size a standalone
   <figure> are gone on purpose: core/cover positions its own background image
   absolutely, and an aspect-ratio here would fight it and letterbox the photo. */
.hero.wp-block-cover {
  padding: var(--space-3xl) var(--wp--preset--spacing--md);
}

/* On a phone the 9-word headline is taller than 78vh, and a fixed min-height
   would clip it top and bottom (content is centre-positioned). Let the cover
   grow to fit instead — the photo still fills whatever height results. */
@media (max-width: 767px) {
  .hero.wp-block-cover {
    min-height: 0 !important;
    padding-block: var(--space-2xl);
  }
  /* The h1 carries .has-display-font-size, whose font-size WordPress emits as
     !important — so re-point the preset variable itself rather than losing a
     specificity war with core. */
  .hero {
    --wp--preset--font-size--display: 30px;
  }
  .hero h1 { line-height: 1.2; }
}

@media (min-width: 768px) {
  .hero.wp-block-cover {
    padding: var(--space-3xl) var(--wp--preset--spacing--xl);
  }
}

/* Content column sits above the dim overlay.
   The horizontal gutter lives HERE, not on the cover: the cover is alignfull,
   and WordPress's own full-width rules zero its padding-inline at a
   specificity we would have to fight with !important. */
.hero .hero__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  padding-inline: var(--wp--preset--spacing--md);
}

@media (min-width: 768px) {
  .hero .hero__inner { padding-inline: var(--wp--preset--spacing--xl); }
}

/* A soft bottom-left gradient so the white headline keeps its contrast even
   over the brightest part of the photo. */
.hero.wp-block-cover::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(105deg, rgb(30 41 59 / 0.55) 0%, rgb(30 41 59 / 0.25) 45%, transparent 75%);
}

/* === Primary CTA — the only element permitted to carry the accent color === */
.hero-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--space-2xl);
  min-width: var(--space-2xl);
  padding: var(--wp--preset--spacing--sm) var(--wp--preset--spacing--lg);
  background-color: var(--wp--preset--color--accent);
  color: var(--wp--preset--color--text-on-dark);
  font-size: var(--wp--preset--font-size--body);
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  border-radius: 4px;
  border: none;
}

.hero-cta:hover {
  background-color: var(--wp--preset--color--accent);
  filter: brightness(0.92);
  color: var(--wp--preset--color--text-on-dark);
}

.hero-cta:focus-visible {
  outline: 2px solid var(--wp--preset--color--accent);
  outline-offset: 2px;
}

/* Hero slogan measure — wraps the ~9-word D-03 slogan to two lines on
   mobile without truncation (UI-SPEC long-text row), stays legible at
   the fluid display size on wider viewports. */
.hero h1 {
  max-width: 20ch;
}

@media (min-width: 640px) {
  .hero h1 {
    max-width: 34ch;
  }
}

@media (prefers-reduced-motion: reduce) {
  * ,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* === Motion — Apple-style easing, used everywhere something moves === */
:root {
  --ease-out-smooth: cubic-bezier(0.16, 1, 0.3, 1);
}

.hero__media img {
  transition: transform 0.4s var(--ease-out-smooth);
  will-change: transform;
}

/* Scroll reveal: fades/rises in once per element, then stays put. */
.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.6s var(--ease-out-smooth), transform 0.6s var(--ease-out-smooth);
}

.reveal.is-visible {
  opacity: 1;
  transform: none;
}

.service-card.reveal {
  transition-delay: calc(var(--card-index, 0) * 40ms);
}

/* === Services — compact grouped icon grid ===
   Owner asked for the service list to read as small icons grouped tightly
   together (app-icon style) rather than large photo cards, with the photo
   itself kept but de-emphasised (still present for CORE-03 + SEO, just not
   the dominant visual). Denser grid than a photo-card layout supports. */
.services {
  background-color: var(--wp--preset--color--base);
}

.services-grid.wp-block-query {
  margin: 0;
}

.services-grid .wp-block-post-template {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--wp--preset--spacing--md);
}

@media (min-width: 640px) and (max-width: 1023px) {
  .services-grid .wp-block-post-template {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 1024px) {
  .services-grid .wp-block-post-template {
    grid-template-columns: repeat(5, 1fr);
  }
}

.service-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  height: 100%;
  background-color: var(--wp--preset--color--surface);
  border: 1px solid var(--wp--preset--color--border);
  border-radius: 16px;
  padding: var(--wp--preset--spacing--lg) var(--wp--preset--spacing--sm);
  cursor: pointer;
  transition: transform 0.35s var(--ease-out-smooth), box-shadow 0.35s var(--ease-out-smooth),
    border-color 0.35s var(--ease-out-smooth);
}

.service-card:hover,
.service-card:focus-visible {
  transform: translateY(-4px) scale(1.03);
  box-shadow: 0 12px 24px -12px rgb(30 41 59 / 0.25);
  border-color: var(--wp--preset--color--accent);
  outline: none;
}

.service-card:active {
  transform: translateY(-1px) scale(1.01);
}

/* The icon is the primary visual now — accent color, generous size, grows
   slightly on hover for a "responds to you" feel. */
.service-card__icon {
  width: 56px;
  height: 56px;
  color: var(--wp--preset--color--accent);
  margin-bottom: var(--wp--preset--spacing--sm);
  transition: transform 0.35s var(--ease-out-smooth);
}

.service-card__icon svg {
  width: 100%;
  height: 100%;
}

.service-card:hover .service-card__icon {
  transform: scale(1.12);
}

/* The photo still exists in the DOM (CORE-03: every service has a photo,
   it's just not the card's dominant visual element anymore) — kept
   accessible, visually minimal. */
.service-card .wp-block-post-featured-image.service-card__photo--muted {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.service-card .wp-block-post-title {
  margin: 0;
  font-size: var(--wp--preset--font-size--label);
  font-weight: 700;
  line-height: 1.3;
}

/* Excerpt moves into the calculator modal on click instead of living on the
   compact tile — keeps the grouped-icon look uncluttered. */
.service-card .wp-block-post-excerpt {
  display: none;
}

/* === Calculator modal ===
   Client-side, rule-based placeholder for the Phase 3 AI price-estimate
   chat (CHAT-01..06) — clearly labelled as an approximate estimate. */
.calc-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
}

.calc-modal.is-open {
  display: block;
}

.calc-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgb(30 41 59 / 0.55);
  backdrop-filter: blur(2px);
  animation: calc-fade 0.3s var(--ease-out-smooth);
}

.calc-modal__panel {
  position: relative;
  z-index: 1;
  max-width: 420px;
  margin: 8vh auto;
  background: var(--wp--preset--color--surface);
  border-radius: 16px;
  padding: var(--wp--preset--spacing--lg);
  max-height: 84vh;
  overflow-y: auto;
  animation: calc-rise 0.4s var(--ease-out-smooth);
}

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

@keyframes calc-rise {
  from { opacity: 0; transform: translateY(24px) scale(0.97); }
  to { opacity: 1; transform: none; }
}

.calc-modal__close {
  position: absolute;
  top: var(--wp--preset--spacing--sm);
  right: var(--wp--preset--spacing--sm);
  width: 32px;
  height: 32px;
  border: none;
  background: var(--wp--preset--color--base);
  border-radius: 50%;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: var(--wp--preset--color--text);
  transition: background 0.2s var(--ease-out-smooth), transform 0.2s var(--ease-out-smooth);
}

.calc-modal__close:hover {
  background: var(--wp--preset--color--border);
  transform: scale(1.08);
}

.calc-modal__icon {
  width: 48px;
  height: 48px;
  color: var(--wp--preset--color--accent);
  margin-bottom: var(--wp--preset--spacing--sm);
}

.calc-modal__icon svg {
  width: 100%;
  height: 100%;
}

.calc-modal__title {
  margin: 0 0 var(--wp--preset--spacing--sm) 0;
  font-size: var(--wp--preset--font-size--heading);
}

.calc-modal__desc {
  margin: 0 0 var(--wp--preset--spacing--md) 0;
  color: var(--wp--preset--color--text);
}

.calc-field {
  display: block;
  font-size: var(--wp--preset--font-size--label);
  font-weight: 700;
  margin-bottom: var(--wp--preset--spacing--md);
}

.calc-field input,
.calc-field select {
  display: block;
  width: 100%;
  margin-top: var(--wp--preset--spacing--xs);
  padding: var(--wp--preset--spacing--sm);
  border: 1px solid var(--wp--preset--color--border);
  border-radius: 8px;
  font-size: var(--wp--preset--font-size--body);
  font-weight: 400;
  transition: border-color 0.2s var(--ease-out-smooth);
}

.calc-field input:focus,
.calc-field select:focus {
  border-color: var(--wp--preset--color--accent);
  outline: none;
}

.calc-modal__submit {
  width: 100%;
  border: none;
}

.calc-modal__result {
  margin-top: var(--wp--preset--spacing--md);
  padding-top: var(--wp--preset--spacing--md);
  border-top: 1px solid var(--wp--preset--color--border);
  animation: calc-rise 0.35s var(--ease-out-smooth);
}

.calc-modal__estimate {
  font-size: var(--wp--preset--font-size--heading);
  font-weight: 700;
  margin: 0 0 var(--wp--preset--spacing--xs) 0;
}

.calc-modal__disclaimer {
  font-size: var(--wp--preset--font-size--label);
  color: var(--wp--preset--color--text);
  opacity: 0.8;
  margin: 0 0 var(--wp--preset--spacing--md) 0;
}

.calc-modal__result .hero-cta {
  width: 100%;
}

body.calc-modal-open {
  overflow: hidden;
}

/* === AI chat view (CHAT-01/02) — shares the modal panel with the quick
   calculator; only one of the two is visible per session. === */
.calc-chat__messages {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--sm);
  max-height: 40vh;
  overflow-y: auto;
  margin-bottom: var(--wp--preset--spacing--md);
  padding-right: var(--wp--preset--spacing--xs);
}

.calc-chat__msg {
  padding: var(--wp--preset--spacing--sm) var(--wp--preset--spacing--md);
  border-radius: 14px;
  font-size: var(--wp--preset--font-size--body);
  max-width: 85%;
  white-space: pre-wrap;
}

.calc-chat__msg--assistant {
  background: var(--wp--preset--color--base);
  border: 1px solid var(--wp--preset--color--border);
  align-self: flex-start;
  border-bottom-left-radius: 4px;
}

.calc-chat__msg--user {
  background: var(--wp--preset--color--accent);
  color: var(--wp--preset--color--text-on-dark);
  align-self: flex-end;
  border-bottom-right-radius: 4px;
}

.calc-chat__disclaimer {
  font-size: var(--wp--preset--font-size--label);
  opacity: 0.75;
  margin: 0 0 var(--wp--preset--spacing--sm) 0;
}

.calc-chat__inputrow {
  display: flex;
  gap: var(--wp--preset--spacing--sm);
}

.calc-chat__input {
  flex: 1;
  padding: var(--wp--preset--spacing--sm);
  border: 1px solid var(--wp--preset--color--border);
  border-radius: 8px;
  font-size: var(--wp--preset--font-size--body);
  font-family: inherit;
}

.calc-chat__input:focus {
  border-color: var(--wp--preset--color--accent);
  outline: none;
}

.calc-chat__send {
  border: none;
  white-space: nowrap;
}

/* =========================================================================
   LIQUID GLASS LAYER
   -------------------------------------------------------------------------
   The work photo becomes the background of the WHOLE page, not just the hero,
   and every surface above it is frosted glass.

   Implementation note — why fixed pseudo-elements instead of
   `background-attachment: fixed` on <body>:
   iOS Safari does not honour `background-attachment: fixed` (it silently
   degrades to `scroll`), and on the desktop it forces a full-page repaint on
   every scroll frame. Two `position: fixed` layers behind the content give the
   same "photo stays put while content slides over it" effect, work on iOS, and
   are cheap to composite.

   Also note the photo never needs outpainting: because the layer is fixed and
   sized to the VIEWPORT (not the document), `cover` always fills the screen no
   matter how long the page grows. `background-position: center 28%` keeps the
   worker as the anchor near the top.
   ========================================================================= */

:root {
  /* Glass recipe. Two densities, per the readability lesson Apple learned in
     2026 when it had to ship a transparency slider and re-tint the nav icons:
     anything carrying text or navigation is DENSE; purely decorative panels
     stay light. */
  --glass-light-bg: rgb(255 255 255 / 0.12);
  --glass-light-border: rgb(255 255 255 / 0.25);
  --glass-dense-bg: rgb(18 20 24 / 0.64);
  --glass-dense-border: rgb(255 255 255 / 0.20);
  --glass-blur: blur(30px) saturate(150%);
  --glass-blur-strong: blur(36px) saturate(150%);
  --glass-radius: 24px;
  --glass-shadow: 0 18px 40px -20px rgb(0 0 0 / 0.65);
  --glass-highlight: inset 0 1px 0 0 rgb(255 255 255 / 0.35);
}

/* --- The page background: photo, then the darkening gradient --- */
html { background-color: #14161a; }

body {
  background-color: transparent !important;
  color: #F3F5F7;
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  background-image: var(--page-bg-image);
  background-size: cover;
  background-position: center 28%;
  background-repeat: no-repeat;
}

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgb(0 0 0 / 0.15) 0%,
    rgb(0 0 0 / 0.55) 50%,
    rgb(0 0 0 / 0.90) 100%
  );
}

/* Every section becomes a window onto that background. */
.wp-site-blocks,
main.wp-block-group,
.services,
.reviews,
.contact-form-section {
  background-color: transparent !important;
}

/* Light text everywhere, since the page is now dark throughout. */
main h1, main h2, main h3, main h4,
.services h2, .reviews h2, .contact-form-section h2 {
  color: #FFFFFF;
}

main p,
.contact-form label {
  color: #E7EAEE;
}

/* --- Hero: the shared background already carries the photo, so the cover's
   own image and dim layers are switched off rather than doubling it up (they
   would also scroll against the fixed layer and visibly mismatch). --- */
.hero.wp-block-cover { background-color: transparent !important; }
.hero.wp-block-cover > img.wp-block-cover__image-background,
.hero.wp-block-cover > .wp-block-cover__background { display: none; }
.hero.wp-block-cover::after { background: none; }

.hero h1 {
  text-shadow: 0 2px 24px rgb(0 0 0 / 0.45);
}

/* =========================================================================
   GLASS SURFACES
   ========================================================================= */

/* Header — navigation, so DENSE. */
.site-header.wp-block-group {
  position: sticky;
  top: 0;
  z-index: 60;
  background-color: var(--glass-dense-bg) !important;
  -webkit-backdrop-filter: var(--glass-blur-strong);
  backdrop-filter: var(--glass-blur-strong);
  border-bottom: 1px solid var(--glass-dense-border);
}

/* Service tiles.
   These carry text (the service name), so they follow the DENSE rule rather
   than the decorative one — and they are tinted DARK, not white. A white tint
   at 0.12 over a bright patch of the photo lands the white label at roughly
   1.5:1 contrast, far under the 4.5:1 minimum; the same blur over a dark tint
   keeps the glass character while holding the label readable no matter which
   part of the photo scrolls behind it. The light-glass tokens are kept above
   for genuinely decorative surfaces. */
.service-card {
  background-color: rgb(18 20 24 / 0.46) !important;
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-light-border);
  border-radius: var(--glass-radius);
  box-shadow: var(--glass-shadow), var(--glass-highlight);
  transition: transform 0.3s var(--ease-out-smooth),
              background-color 0.3s var(--ease-out-smooth),
              border-color 0.3s var(--ease-out-smooth),
              box-shadow 0.3s var(--ease-out-smooth);
}

.service-card .wp-block-post-title,
.service-card .wp-block-post-title a { color: #FFFFFF; }

.service-card:hover,
.service-card:focus-visible {
  background-color: rgb(28 32 38 / 0.62) !important;
  border-color: rgb(255 255 255 / 0.45);
  transform: translateY(-4px) scale(1.03);
  box-shadow: 0 24px 50px -22px rgb(0 0 0 / 0.75), var(--glass-highlight);
}

.service-card__icon { color: #FFFFFF; }
.service-card:hover .service-card__icon { color: var(--wp--preset--color--accent); }

/* Review cards — carry text, so denser than the service tiles. */
.review-card {
  background: var(--glass-dense-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-dense-border);
  border-radius: var(--glass-radius);
  box-shadow: var(--glass-shadow), var(--glass-highlight);
  color: #F3F5F7;
  padding: var(--wp--preset--spacing--lg);
}
.review-card p { color: #F3F5F7; }
.review-card cite { color: #FFFFFF; opacity: 0.8; }

/* The contact form is the most text-critical surface on the page — densest. */
.contact-form {
  background: rgb(18 20 24 / 0.70);
  -webkit-backdrop-filter: var(--glass-blur-strong);
  backdrop-filter: var(--glass-blur-strong);
  border: 1px solid var(--glass-dense-border);
  border-radius: var(--glass-radius);
  box-shadow: var(--glass-shadow), var(--glass-highlight);
  padding: var(--wp--preset--spacing--lg);
}

.contact-form input,
.contact-form textarea {
  background: rgb(255 255 255 / 0.10);
  border: 1px solid rgb(255 255 255 / 0.25);
  color: #FFFFFF;
  transition: background-color 0.25s var(--ease-out-smooth),
              border-color 0.25s var(--ease-out-smooth);
}
.contact-form input::placeholder,
.contact-form textarea::placeholder { color: rgb(255 255 255 / 0.55); }
.contact-form input:focus,
.contact-form textarea:focus {
  background: rgb(255 255 255 / 0.16);
  border-color: rgb(255 255 255 / 0.55);
}
.contact-form__status { color: #FFFFFF; }

/* Footer — dense, it is all text. */
.footer.wp-block-group {
  background-color: var(--glass-dense-bg) !important;
  -webkit-backdrop-filter: var(--glass-blur-strong);
  backdrop-filter: var(--glass-blur-strong);
  border-top: 1px solid var(--glass-dense-border);
}

/* =========================================================================
   TINTED GLASS BUTTONS
   Brand colour is kept, but as a translucent tint over blur rather than a
   flat fill — the button reads as coloured glass sitting on the photo.
   ========================================================================= */
.hero-cta,
.wp-block-button.is-style-fill > .wp-block-button__link.hero-cta {
  background-color: rgb(234 88 12 / 0.55) !important;
  -webkit-backdrop-filter: blur(20px) saturate(160%);
  backdrop-filter: blur(20px) saturate(160%);
  border: 1px solid rgb(255 255 255 / 0.35);
  border-radius: 16px;
  box-shadow: 0 10px 28px -12px rgb(0 0 0 / 0.7), var(--glass-highlight);
  color: #FFFFFF !important;
  transition: background-color 0.28s var(--ease-out-smooth),
              border-color 0.28s var(--ease-out-smooth),
              transform 0.28s var(--ease-out-smooth);
}

.hero-cta:hover,
.wp-block-button.is-style-fill > .wp-block-button__link.hero-cta:hover {
  background-color: rgb(234 88 12 / 0.75) !important;
  border-color: rgb(255 255 255 / 0.5);
  filter: none;
  transform: translateY(-2px);
}

.hero-cta:active { transform: translateY(0); }

/* Header phone / WhatsApp links */
.site-header__phone a,
.site-header__whatsapp a { color: #FFFFFF; }

/* Trust badges already were outlined pills — just soften onto the glass. */
.trust-badge {
  background: rgb(255 255 255 / 0.10);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  border-color: rgb(255 255 255 / 0.30);
}

/* Mobile sticky bar — tinted glass, same language as the buttons. */
@media (max-width: 767px) {
  .mobile-cta-bar__call .wp-block-button__link {
    background: rgb(234 88 12 / 0.72);
    -webkit-backdrop-filter: blur(24px) saturate(160%);
    backdrop-filter: blur(24px) saturate(160%);
    border-top: 1px solid rgb(255 255 255 / 0.28);
  }
  .mobile-cta-bar__whatsapp .wp-block-button__link {
    background: rgb(18 20 24 / 0.72);
    -webkit-backdrop-filter: blur(24px) saturate(160%);
    backdrop-filter: blur(24px) saturate(160%);
    border-top: 1px solid rgb(255 255 255 / 0.28);
  }
}

/* =========================================================================
   MODAL — same glass language (built in JS by buildModal()).
   ========================================================================= */
.calc-modal__panel {
  background: rgb(18 20 24 / 0.78) !important;
  -webkit-backdrop-filter: var(--glass-blur-strong);
  backdrop-filter: var(--glass-blur-strong);
  border: 1px solid var(--glass-dense-border);
  border-radius: var(--glass-radius);
  box-shadow: var(--glass-shadow), var(--glass-highlight);
  color: #F3F5F7;
}
.calc-modal__title,
.calc-modal__estimate { color: #FFFFFF; }
.calc-modal__desc,
.calc-modal__disclaimer,
.calc-field { color: #E7EAEE; }
.calc-field input,
.calc-field select,
.calc-chat__input {
  background: rgb(255 255 255 / 0.10);
  border: 1px solid rgb(255 255 255 / 0.25);
  color: #FFFFFF;
}
.calc-modal__close {
  background: rgb(255 255 255 / 0.12);
  color: #FFFFFF;
}
.calc-modal__close:hover { background: rgb(255 255 255 / 0.22); }
.calc-chat__msg--assistant {
  background: rgb(255 255 255 / 0.10);
  border-color: rgb(255 255 255 / 0.20);
  color: #F3F5F7;
}
.calc-modal__result { border-top-color: rgb(255 255 255 / 0.20); }

/* =========================================================================
   FALLBACKS
   ========================================================================= */

/* No backdrop-filter (older Firefox, some embedded browsers): translucent
   blur is impossible, so go opaque rather than shipping unreadable text over
   a photo. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header.wp-block-group,
  .footer.wp-block-group,
  .review-card,
  .contact-form,
  .calc-modal__panel { background-color: rgb(18 20 24 / 0.94) !important; }
  .service-card { background-color: rgb(38 42 48 / 0.92) !important; }
}

/* Respect the OS-level "reduce transparency" accessibility setting — the same
   control Apple added after the readability complaints. */
@media (prefers-reduced-transparency: reduce) {
  .site-header.wp-block-group,
  .footer.wp-block-group,
  .review-card,
  .contact-form,
  .calc-modal__panel { background-color: rgb(18 20 24 / 0.96) !important; }
  .service-card { background-color: rgb(38 42 48 / 0.94) !important; }
  .hero-cta,
  .wp-block-button.is-style-fill > .wp-block-button__link.hero-cta {
    background-color: var(--wp--preset--color--accent) !important;
  }
  body::after {
    background: linear-gradient(180deg, rgb(0 0 0 / 0.55) 0%, rgb(0 0 0 / 0.8) 50%, rgb(0 0 0 / 0.95) 100%);
  }
}

/* =========================================================================
   REVIEWS — source links, rating line, rotating cards
   ========================================================================= */

/* Source buttons. Same tinted-glass language as Zadzwoń/WhatsApp, but they must
   read as links OUT to another site, so each carries an icon, a label and a →,
   plus the usual affordances: pointer cursor, hover lift, visible focus ring. */
.reviews-sources {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--wp--preset--spacing--md);
  margin-bottom: var(--wp--preset--spacing--md);
}

.reviews-source-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 20px;
  border-radius: 999px;
  font-size: var(--wp--preset--font-size--body);
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
}

.reviews-source-btn__icon {
  display: inline-flex;
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}
.reviews-source-btn__icon svg { width: 100%; height: 100%; display: block; }

.reviews-source-btn__arrow {
  font-size: 18px;
  line-height: 1;
  transition: transform 0.25s var(--ease-out-smooth);
}
.reviews-source-btn:hover .reviews-source-btn__arrow { transform: translateX(3px); }

/* Instagram keeps its own identity instead of borrowing the orange. */
.hero-cta--ig,
a.hero-cta--ig {
  background-color: rgb(131 58 180 / 0.55) !important;
  border-color: rgb(255 255 255 / 0.35);
}
.hero-cta--ig:hover,
a.hero-cta--ig:hover { background-color: rgb(150 47 191 / 0.75) !important; }

.reviews-aggregate {
  text-align: center;
  margin: 0 0 var(--wp--preset--spacing--xl);
  opacity: 0.85;
  font-size: var(--wp--preset--font-size--label);
}

/* Rating row: stars, then the score in figures to their right.
   Qualified with p. deliberately — the glass layer above sets `.review-card p`
   (0,1,1), which outranks a bare class selector (0,1,0) whatever the source
   order, so an unqualified rule here would lose its colour to that one. */
.review-card p.review-card__rating {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 var(--wp--preset--spacing--sm);
  font-size: var(--wp--preset--font-size--label);
}

.review-card__stars {
  display: inline-flex;
  gap: 3px;
  line-height: 1;
}

.review-card__star {
  color: #FFC107;
  display: inline-block;
  transform-origin: 50% 60%;
}

.review-card__star--empty { color: rgb(255 255 255 / 0.22); }

.review-card__score {
  font-weight: 700;
  color: #FFFFFF;
  letter-spacing: 0.02em;
}

.reviews-empty { text-align: center; opacity: 0.8; }

/* --- Rotation and arrival ---------------------------------------------- */

/* Every review is rendered into the page — good for SEO and for anyone with
   JS off, who simply sees the whole list. JS then reveals one row at a time,
   and stages that row's arrival: the card rises into place, its stars light
   one after another, the score lands, the quote writes itself, the signature
   follows. Two seconds, front-loaded, then thirteen more of stillness to
   read in — the animation is there to win attention, not to spend it.

   Timings are composed from two custom properties so every element in a card
   inherits that card's place in the row: --card-i staggers the cards, and
   --star-i staggers the stars inside each one. */
.reviews-grid[data-reviews-rotator] .review-card {
  --card-delay: calc(var(--card-i, 0) * 120ms);
  transition:
    opacity 0.55s var(--ease-out-smooth) var(--card-delay),
    transform 0.55s var(--ease-out-smooth) var(--card-delay);
}

/* Applied by JS once it takes over; without it every card stays visible. */
.reviews-grid.is-rotating .review-card { display: none; }
.reviews-grid.is-rotating .review-card.is-current { display: flex; }

/* The grid reserves the height of the tallest review so the contact form
   below it cannot be shoved around. Centre the row inside that reservation
   rather than stretching it: a two-line review then stays a two-line card
   with air above and below it, instead of becoming one tall mostly-empty
   panel. The reserved space reads as section whitespace, which is fine; an
   oversized card does not. */
.reviews-grid.is-rotating { align-content: center; }

/* Used for a single frame while JS measures the tallest card. It reveals
   every card WITHOUT dropping .is-rotating, because the line clamp below
   hangs off that class — measuring with it removed would size the reservation
   to the unclamped text and reserve hundreds of pixels that nothing ever
   fills. */
.reviews-grid.is-measuring .review-card { display: flex !important; }

/* One review in the current set runs to fifteen lines where the rest take
   two to seven, and it alone was setting the height for everybody. Clamp the
   outliers and give them a way to open. Applied only under .is-rotating,
   which JS adds — so with JS off, or under prefers-reduced-motion, every
   review is shown in full and nothing needs a button to be readable. */
.reviews-grid.is-rotating .review-card__text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--review-clamp-lines, 7);
  line-clamp: var(--review-clamp-lines, 7);
  overflow: hidden;
}

.reviews-grid.is-rotating .review-card.is-expanded .review-card__text {
  display: block;
  -webkit-line-clamp: unset;
  line-clamp: unset;
  overflow: visible;
}

.review-card__more {
  align-self: flex-start;
  margin: 0 0 var(--wp--preset--spacing--sm);
  padding: 0;
  border: 0;
  background: none;
  color: var(--wp--preset--color--accent);
  font: inherit;
  font-size: var(--wp--preset--font-size--label);
  font-weight: 700;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.review-card__more:hover { color: #FFFFFF; }

/* Arriving: held just below and barely small, released a frame later. */
.reviews-grid.is-rotating .review-card.is-entering {
  opacity: 0;
  transform: translateY(16px) scale(0.985);
}

/* Leaving: drifts up as it goes, so the swap reads as one movement rather
   than a blink. No stagger on the way out — the row should clear together. */
.reviews-grid.is-rotating .review-card.is-leaving {
  opacity: 0;
  transform: translateY(-10px) scale(0.99);
  transition:
    opacity 0.35s var(--ease-out-smooth),
    transform 0.35s var(--ease-out-smooth);
}

/* Stars light one at a time, each with a small overshoot so it lands rather
   than fades. The delay chain puts them just after the card itself settles. */
.review-card__star {
  transition:
    opacity 0.3s var(--ease-out-smooth),
    transform 0.3s cubic-bezier(0.34, 1.4, 0.64, 1);
  transition-delay: calc(var(--card-delay, 0ms) + var(--star-i, 0) * 90ms + 140ms);
}
.review-card.is-entering .review-card__star {
  opacity: 0;
  transform: scale(0.4);
}

/* The score in figures follows the fifth star. */
.review-card__score {
  transition: opacity 0.35s var(--ease-out-smooth), transform 0.35s var(--ease-out-smooth);
  transition-delay: calc(var(--card-delay, 0ms) + 600ms);
}
.review-card.is-entering .review-card__score {
  opacity: 0;
  transform: translateX(-6px);
}

/* The un-written half of the quote holds its exact place in the layout while
   it waits — transparent, not hidden, so nothing below the card shifts as
   the text arrives and the whole quote stays readable to a screen reader
   that lands mid-animation. */
.review-card__pending { color: transparent; }

/* A caret, only while something is actually being written. */
.review-card.is-typing .review-card__typed::after {
  content: "";
  display: inline-block;
  width: 2px;
  height: 1em;
  margin-left: 1px;
  vertical-align: -0.15em;
  background: var(--wp--preset--color--accent);
  animation: alehvik-caret 0.75s steps(1, end) infinite;
}

@keyframes alehvik-caret {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* The signature waits for the quote to finish writing itself. */
.review-card cite {
  transition: opacity 0.4s var(--ease-out-smooth), transform 0.4s var(--ease-out-smooth);
}
.reviews-grid.is-rotating .review-card:not(.is-typed) cite {
  opacity: 0;
  transform: translateY(6px);
}

@media (prefers-reduced-motion: reduce) {
  .reviews-grid[data-reviews-rotator] .review-card,
  .review-card__star,
  .review-card__score,
  .review-card cite { transition: none; }
  .review-card.is-typing .review-card__typed::after { animation: none; }
}

.screen-reader-text {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Fixly's own recommendation badge in the footer trust row. It arrives as a
   bright yellow rectangle, which is the point — a trust mark works by being
   recognised, so it is left in its own colours rather than tinted into the
   glass palette. Only the interaction is ours. */
.fixly-badge {
  display: inline-flex;
  align-items: center;
  line-height: 0;
  border-radius: 10px;
  transition: transform 0.25s var(--ease-out-smooth), box-shadow 0.25s var(--ease-out-smooth);
}

.fixly-badge img {
  display: block;
  width: 178px;
  height: auto;
  border: 0;
  border-radius: 10px;
}

.fixly-badge:hover,
.fixly-badge:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 8px 20px -8px rgb(0 0 0 / 0.7);
}

@media (prefers-reduced-motion: reduce) {
  .fixly-badge { transition: none; }
  .fixly-badge:hover, .fixly-badge:focus-visible { transform: none; }
}
