@import url('tokens.css');

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--navy-900);
  background: var(--cream-50);
}
a { color: var(--burgundy-600); text-decoration: none; }
a:hover { color: var(--burgundy-700); }
::selection { background: var(--burgundy-100); }
img { max-width: 100%; display: block; }
h1, h2, h3, p, ul { margin: 0; }

.container { max-width: var(--content-max); margin: 0 auto; padding: 0 32px; }
.container-narrow { max-width: var(--content-max-narrow); margin: 0 auto; padding: 0 32px; }

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(255,255,255,0.94);
  backdrop-filter: saturate(1.2) blur(6px);
  -webkit-backdrop-filter: saturate(1.2) blur(6px);
  box-shadow: var(--shadow-header);
  padding: 0 32px;
  transition: transform 240ms ease, background-color 280ms ease, box-shadow 280ms ease,
              backdrop-filter 280ms ease;
}
.site-header--hidden { transform: translateY(-100%); }
.site-header--hidden:focus-within { transform: none; }
/* At the very top of the page (scroll 0) the header sheds its solid
   background so the hero's sky shows straight through behind it; it
   resolidifies the moment the user starts scrolling, via .site-header--transparent
   toggled in initHeaderAutoHide (js/main.js). Text/icon colors need their own
   on-dark override here since the navy default reads poorly over the sky photo. */
.site-header--transparent {
  background: transparent;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.site-header--transparent .site-header__tel,
.site-header--transparent .site-header__email,
.site-header--transparent .nav-dropdown__toggle,
.site-header--transparent .nav-dropdown__label,
/* :not(.nav-dropdown__menu a) - the dropdown popup itself always has a
   solid white background regardless of header state, so its links must
   stay dark; only the top-level bar links sit directly on the transparent
   header and need lightening. */
.site-header--transparent .site-nav a:not(.btn-primary):not(.nav-dropdown__menu a) {
  color: var(--color-text-on-dark);
}
.site-header--transparent .site-header__tel { border-left-color: rgba(242, 239, 233, 0.35); }
.site-header--transparent .site-nav a:not(.btn-primary):not(.nav-dropdown__menu a):hover,
.site-header--transparent .site-header__tel:hover,
.site-header--transparent .site-header__email:hover,
.site-header--transparent .nav-dropdown:hover .nav-dropdown__label,
.site-header--transparent .nav-dropdown:hover .nav-dropdown__toggle {
  color: var(--color-text-on-dark);
  opacity: 0.75;
}
.site-header__inner {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 20px 0;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  align-items: center;
  gap: 64px;
}
/* Shifted left by the same 45px as .site-header__logo-badge (see its own
   comment), so the phone/email keep their position relative to the logo
   even though they live in a different element - relative spacing between
   the icon/tel/email themselves is untouched since the whole group moves
   as one flex container. */
.site-header__left { display: flex; align-items: center; gap: 20px; margin-left: -45px; }
.site-header__brand { display: flex; align-items: center; justify-content: center; gap: 10px; color: var(--navy-900); width: 150px; flex-shrink: 0; }
.site-header__brand-icon-wrap {
  width: 60px;
  height: 60px;
  border-radius: var(--radius-pill);
  background: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  opacity: 0;
}
.site-header__brand-icon { height: 53px; width: 53px; object-fit: contain; display: block; }
/* Hamburger toggle for the collapsed mobile nav - only relevant/shown
   below 960px (see that media query), where it also gets its actual
   sizing/appearance. */
.site-header__menu-toggle { display: none; }
/* gap tightened from 30px: adding the "Områden" dropdown pushed the nav's
   total width past its available column width at normal desktop sizes,
   wrapping it onto a second line and throwing off the sticky header's
   height (see the header/logo coupling note - a taller header shifts the
   hero/logo-badge overlap it's tuned against). 10px keeps a single line
   at the site's max container width (1280px) and at a 1280px viewport
   itself, the tightest case a real (non-mobile) browser window hits before
   the ≤960px hamburger breakpoint takes over. */
.site-nav { display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; }
.site-nav a { font-size: 17px; font-weight: 500; color: var(--navy-900); }
.site-nav a:hover { color: var(--burgundy-600); }
.site-nav a.btn-primary,
.site-nav a.btn-primary:hover { color: #fff; }
.site-header__tel {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 17px;
  color: var(--navy-900);
  white-space: nowrap;
  padding-left: 20px;
  border-left: 1px solid var(--border-strong);
}
.site-header__tel:hover { color: var(--burgundy-600); }
.site-header__email {
  font-size: 16px;
  font-weight: 500;
  color: var(--ink-600);
  white-space: nowrap;
}
.site-header__email:hover { color: var(--burgundy-600); }

.nav-dropdown { position: relative; display: flex; align-items: center; gap: 2px; }
.nav-dropdown__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--navy-900);
  cursor: pointer;
}
.nav-dropdown__toggle svg { transition: transform var(--duration-fast) var(--ease-standard); }
.nav-dropdown:hover .nav-dropdown__toggle,
.nav-dropdown__toggle:hover { color: var(--burgundy-600); }
.nav-dropdown:hover .nav-dropdown__toggle svg,
.nav-dropdown--open .nav-dropdown__toggle svg { transform: rotate(180deg); }
.nav-dropdown--suppressed .nav-dropdown__toggle svg { transform: none !important; }
.nav-dropdown__menu {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 8px;
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card-hover);
  padding: 8px;
  min-width: 230px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity var(--duration-standard) var(--ease-standard),
              transform var(--duration-standard) var(--ease-standard),
              visibility var(--duration-standard) var(--ease-standard);
  z-index: 30;
}
.nav-dropdown:hover .nav-dropdown__menu,
.nav-dropdown--open .nav-dropdown__menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.nav-dropdown--suppressed .nav-dropdown__menu {
  opacity: 0 !important;
  visibility: hidden !important;
}
.nav-dropdown__menu a {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  font-size: 16px;
  font-weight: 500;
  color: var(--navy-900);
}
.nav-dropdown__menu a:hover { background: var(--cream-50); color: var(--burgundy-600); }

/* Trigger for dropdowns that have no page of their own to link to (unlike
   "Tjänster", which is both a link and a toggle) - styled to match
   .site-nav a exactly since the descendant selector there only targets
   real <a> elements. */
.nav-dropdown__label {
  font-family: var(--font-body);
  font-size: 17px;
  font-weight: 500;
  color: var(--navy-900);
  background: none;
  border: none;
  padding: 0;
  cursor: default;
}

/* Wider grid layout for the "Områden" menu (26 municipalities) - centered
   under its trigger rather than left-aligned like the short Tjänster list,
   since a 4-column grid this wide would otherwise overflow the nav on the
   right. Rules repeated (not just relying on the base .nav-dropdown__menu
   ones) because same-specificity selectors fall back to source order, and
   this block is declared after the base one specifically to win. */
.nav-dropdown__menu--areas {
  left: 50%;
  min-width: 620px;
  max-width: min(90vw, 760px);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px 8px;
  max-height: 70vh;
  overflow-y: auto;
  transform: translateX(-50%) translateY(-4px);
}
.nav-dropdown:hover .nav-dropdown__menu--areas,
.nav-dropdown--open .nav-dropdown__menu--areas {
  transform: translateX(-50%) translateY(0);
}

.site-header__logo-wrap {
  position: relative;
  z-index: 21;
  margin: -75px 0;
  padding: 0 32px;
  pointer-events: none;
  opacity: 1;
}
.site-header__logo-inner { max-width: var(--content-max); margin: 0 auto; }
/* left: -75px (was -30px) centers the badge over the hero title's left
   edge - measured via getBoundingClientRect: badge center was at x:125,
   title/lead left edge at x:80, so it needed to move left another 45px.
   .site-header__left carries the matching -45px shift for the header's
   phone/email. */
.site-header__logo-badge {
  display: block;
  position: relative;
  left: -75px;
  width: 150px;
  height: 150px;
  background: linear-gradient(to bottom, transparent 50%, var(--cream-50) 50%);
  pointer-events: auto;
  cursor: pointer;
}
.site-header__logo-circle {
  position: absolute;
  left: 33px;
  top: 18px;
  width: 112px;
  height: 112px;
  border-radius: 999px;
  background: var(--white);
}
.site-header__logo-img { position: relative; width: 150px; height: 150px; object-fit: contain; display: block; }
.site-header__logo-badge--dark { background: linear-gradient(to bottom, transparent 50%, var(--navy-900) 50%); }
/* The badge's dark half is a flat-color blend meant to melt into a solid
   navy section directly beneath it. Any page whose hero is photographic
   instead - the home page, and service pages using .service-hero--media -
   turns that flat fill into a visible rectangle over the photo, so those
   pages opt out via .has-photo-hero on <body>. Pages that still open on a
   flat navy hero (the service pages without a photo yet, integritetspolicy)
   omit the class and keep the blend. */
body.has-photo-hero .site-header__logo-badge--dark { background: none; }

/* ---------- Buttons ---------- */
.btn {
  font-family: var(--font-body);
  font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  letter-spacing: 0.01em;
  white-space: nowrap;
  flex-shrink: 0;
  transition: background var(--duration-standard) var(--ease-standard),
              border-color var(--duration-standard) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.btn-md { font-size: 15px; padding: 12px 22px; }
.btn-lg { font-size: 17px; padding: 16px 30px; }
.btn-block { width: 100%; }
.btn-primary { background: var(--burgundy-600); color: #fff; border-color: var(--burgundy-600); }
.btn-primary:hover { background: var(--burgundy-700); border-color: var(--burgundy-700); color: #fff; }

/* ---------- Form fields ---------- */
.field { display: flex; flex-direction: column; gap: 8px; }
.field__label { font-size: 13px; font-weight: 600; color: var(--navy-900); }
.field__optional { font-weight: 400; color: var(--ink-600); text-transform: none; letter-spacing: normal; }
.field input, .field select, .field textarea {
  font-family: var(--font-body);
  font-size: 15px;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--white);
  color: var(--navy-900);
  outline: none;
  transition: border-color var(--duration-fast) var(--ease-standard);
  width: 100%;
}
.field textarea { resize: vertical; min-height: 96px; line-height: 1.5; }
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--color-focus-ring); }
.field__error {
  display: none;
  font-size: 13px;
  font-weight: 600;
  color: var(--burgundy-600);
}
.field--invalid input,
.field--invalid select,
.field--invalid textarea { border-color: var(--burgundy-600); }
.field--invalid .field__error { display: block; }

.field--checkbox-row { display: flex; flex-direction: row; align-items: flex-start; gap: 10px; cursor: pointer; }
.field--checkbox-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin-top: 2px;
  accent-color: var(--burgundy-600);
}
.field--checkbox-row span { font-size: 14px; line-height: 1.5; color: var(--navy-700); }
.field--checkbox-row a { text-decoration: underline; }
.field--invalid .field--checkbox-row span { color: var(--burgundy-600); }

.field-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.form-success { font-size: 14px; color: var(--navy-600); margin: 0; }
.form-success[hidden] { display: none; }
.form-error { font-size: 14px; font-weight: 600; color: var(--burgundy-600); margin: 0; }
.form-error[hidden] { display: none; }

/* ---------- Eyebrow / shared bits ---------- */
.eyebrow {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--copper-500);
}
.image-placeholder {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--cream-100);
  border: 1px dashed var(--border-strong);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* A slot that used to be an .image-placeholder and now holds real
   photography. Same rounded, cropped frame - minus the dashed placeholder
   border - so the sizing rules keyed on the slot's own class (.about__image's
   aspect-ratio, .quote-card__media's radius override) keep working unchanged.
   The photo is set inline per element so relative paths resolve against the
   page rather than this stylesheet. */
.media-photo {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  background-color: var(--cream-100);
  background-size: cover;
  background-position: center;
}
.image-placeholder span {
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-600);
  font-weight: 600;
  text-align: center;
  padding: 0 16px;
}

/* ---------- Hero (home) ---------- */
/* Pulled up so the hero's box - and therefore its sky art - starts at the
   true top of the page, running behind the header instead of butting up
   against it. The header sits at z-index 20, well above the hero's own
   layers, so it still renders on top; only its background needs to go
   transparent (see .site-header--transparent) for the sky to show through.
   -175px = -(header height + 75px): this margin collapses with
   .site-header__logo-wrap's -75px bottom margin (the next sibling in
   between), and adjacent negative margins collapse to whichever is MORE
   negative, not their sum - so this has to out-negative -75px by the full
   150px height of that logo-wrap, PLUS however tall the header itself
   renders (logo-wrap's own top starts at header-height - 75px), to reach
   y:0. This is header-height-dependent and NOT self-updating: it was 167px
   against a 92px-tall header, and silently drifted 8px off (a visible strip
   of the page's cream background above the header) when a later change
   made the header's own text/logo bigger, growing it to 100px, without
   this being retuned to match. Verified via getBoundingClientRect at the
   header's CURRENT height, not guessed - if the header's height changes
   again, this needs to be re-measured, not just assumed. Reset to 0 for
   tablet/mobile below, where the header is a different height and this
   hasn't been extended yet. */
/* padding-bottom: 396px, not 88px - the .hero__art-frame fix above stopped
   cropping ~152px off the bottom of the roof photo, and the hero's box
   needed to grow to match or that extra height would eat into the sky
   instead of adding onto the bottom. Beyond just restoring the original
   sky, padding-bottom is tuned further so sky:roof lands at roughly 2:3 of
   the hero's total height (~40% sky) - since the roof's own height is
   fixed by viewport width (width / 2.2955, the photo's intrinsic ratio,
   completely unaffected by this padding), growing the hero via
   padding-bottom is what pushes that ratio toward more sky. Calibrated at
   a 1440px-wide viewport - desktop-only (tablet/mobile reset this padding
   to their own values below and never had any clipping to begin with).
   No padding-bottom compensation needed for the window crop: that crop is
   now handled by .hero__art-roof-wrap's own box size (see its comment),
   which is naturally exactly as tall as the visible roof at any viewport
   width, with nothing left over to compensate for. An earlier version of
   this crop used clip-path plus a padding-bottom reduction here, but that
   didn't actually work - see the wrap's comment for why.
   204px, not 396px: per request, moves the roof up by roughly half the gap
   between the stat row and the roofline (measured via pixel-scanning a
   screenshot at the 1440px reference: ~384px of sky at the column just past
   the text block's right edge, x:700 - the roofline is a diagonal, so this
   varies a lot by column, from already-overlapping near the roof's peak to
   much larger further left; x:700 was picked as roughly where "the gap"
   reads visually). Reducing padding-bottom by half that (192px) shortens
   the hero from the bottom only - top content (title, lead, stats) is
   unaffected since that's governed by padding-top, not this. */
.hero { background: var(--navy-900); padding: 220px 32px 204px; margin-top: -175px; position: relative; overflow: hidden; }

/* Background art: a static roof photo + an animated cloud layer. Sits
   behind .hero__grid via z-index; never affects hero height, so it cannot
   cause layout shift.
   .hero__art-frame used to be a rigid 702:508 unit scaled up with the
   standard JS-free "cover" trick (aspect-ratio + min-width/min-height:100%
   on a centered absolutely-positioned box) to guarantee it filled the hero
   at any viewport - originally so a clip-path traced sky stayed aligned
   with the roof photo's roofline. That clip-path is long gone (see the
   comment on .hero__sky below), and the cover trick had a real cost once
   removed: covering the whole hero on a wide viewport meant scaling the
   frame up until it was taller than the hero, centering it, and letting
   the overflow (clipped by .hero__art's overflow:hidden) run off equally
   top and bottom - which silently cropped ~150px off the BOTTOM of the
   roof photo on desktop. Filling the hero exactly (no distortion-driven
   overscale) shows the whole image, still edge-to-edge, at any width. */
.hero__art { position: absolute; inset: 0; overflow: hidden; z-index: 0; }
.hero__art-frame { position: absolute; inset: 0; }
/* No clip-path here on purpose: an earlier version clipped this gradient to
   a roofline traced from a different source photo than the one actually
   used underneath, so the two edges didn't line up - visible as a soft
   double-edge/haze right where sky met roof. The real roof photo below
   (.hero__art-roof-cutout) is a proper alpha-masked cutout, so its own
   silhouette is what defines the roofline now; this layer is just a plain
   full-frame sky backdrop behind it. */
.hero__sky {
  position: absolute;
  inset: 0;
  container-type: inline-size;
  overflow: hidden;
  background: linear-gradient(225deg, #35A9EA 0%, #63B7EF 28%, #93CAF3 56%, #B8DFF7 82%, #CDE9F9 100%);
}
/* Crops the bottom 13% of the source photo (measured against the image's
   own pixels - the two skylight windows' bottom edge sits at ~87% of the
   image height) so the roof tiles below the windows never render.
   This used to be a clip-path on the image itself, compensated by shrinking
   .hero's padding-bottom - but that doesn't actually work: clip-path only
   hides pixels, it doesn't shrink the image's own box, and that box is
   always bottom:0 (i.e. pinned to the hero's bottom edge) regardless of
   hero's height. So the 13%-tall hidden strip was always there, right above
   the hero's bottom edge, no matter how padding-bottom was tuned - visible
   as a stray gap of bare sky between the visible roof and the hero's edge.
   Cropping via a wrapper's own box size instead - aspect-ratio 2043/774.3
   is exactly 87% of the photo's natural 2043:890 height - means the
   WRAPPER (not the full image) is what's bottom:0 to the hero, so it's the
   wrapper's bottom that lines up with the hero's bottom, with nothing left
   over. The image itself renders at its normal full height inside the
   wrapper, top-aligned; only its overflowing bottom 13% gets clipped by
   the wrapper's own overflow:hidden. */
.hero__art-roof-wrap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  aspect-ratio: 2043 / 774.3;
  overflow: hidden;
}
.hero__art-roof-cutout {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: auto;
  display: block;
}
/* Every cloud shares this shape/animation; the per-cloud position, size,
   softness, opacity, and timing are authored as inline custom properties
   (same pattern the reveal stagger already uses via --reveal-delay) since
   they're per-instance data, not reusable tokens. */
.hero__cloud {
  position: absolute;
  top: var(--cloud-top, 0%);
  left: 0;
  width: var(--cloud-w, 30cqw);
  aspect-ratio: 5 / 2;
  filter: blur(var(--cloud-blur, 12px));
  opacity: var(--cloud-opacity, 1);
  animation: hero-cloud-drift var(--cloud-duration, 120s) linear infinite;
  animation-delay: var(--cloud-delay, 0s);
  will-change: transform;
}
.hero__cloud-puff { position: absolute; background: #fff; border-radius: 50%; }
.hero__sky-fade {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 34%;
  background: linear-gradient(to top, rgba(214, 236, 250, 0.85), rgba(214, 236, 250, 0));
  pointer-events: none;
}
/* Darkens the left side (where the headline/copy sit) so the existing
   on-dark text keeps its contrast over the photo; fades out toward the
   right where the opaque quote-card already has its own background. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(100deg, rgba(29, 42, 69, 0.86) 0%, rgba(29, 42, 69, 0.62) 42%, rgba(29, 42, 69, 0.22) 72%, rgba(29, 42, 69, 0) 100%);
  pointer-events: none;
}
@keyframes hero-cloud-drift {
  from { transform: translateX(112cqw) scaleX(var(--cloud-flip, 1)); }
  to { transform: translateX(-52cqw) scaleX(var(--cloud-flip, 1)); }
}
@media (prefers-reduced-motion: reduce) {
  .hero__cloud { animation: none; }
}
.hero__grid {
  position: relative;
  z-index: 2;
  max-width: var(--content-max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: 64px;
  align-items: start;
}
.hero__content { display: flex; flex-direction: column; gap: 20px; }
.hero__content .eyebrow { letter-spacing: 0.12em; }
.hero__title {
  font-family: var(--font-display);
  font-size: 67px;
  line-height: 1.08;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--color-text-on-dark);
  margin: 0;
}
/* --color-text-on-dark-secondary (a muted blue-gray) is tuned for sitting on
   flat navy - fine in the footer, but here it's close enough in hue to the
   sky photo that it washes out over the brighter/cloudier patches. Bumped to
   the same near-white used for the title, plus a soft shadow so it stays
   legible over bright clouds too, without adding a second background block
   on top of the hero scrim that already covers this whole area. */
.hero__lead {
  font-size: 17px;
  line-height: 1.65;
  color: var(--color-text-on-dark);
  text-shadow: 0 2px 16px rgba(29, 42, 69, 0.55), 0 1px 2px rgba(29, 42, 69, 0.45);
  max-width: 34em;
  margin: 0;
}
/* Light-surface chip: the only place .pill is used now is the process
   section's promo column (moved out of the dark hero), so it's styled for
   a white/cream background, not the on-dark translucent treatment it used
   to need. */
.pill-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
.pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 500;
  color: var(--navy-700);
  background: var(--cream-100);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  padding: 8px 16px;
}
/* Reusable phone/email pair, styled after .service-aside__phone/__email but
   gap-based rather than space-between - this sits in much wider columns
   (service-page body text, the process promo column) where space-between
   would strand "Ring direkt" far from the number. Used standalone (not
   inside a bordered card), under body copy on service pages and under the
   promo pills on the home page. */
.contact-lines { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
.contact-lines__phone {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  color: var(--navy-900);
}
.contact-lines__phone:hover { color: var(--burgundy-600); }
.contact-lines__phone span { font-family: var(--font-body); font-size: 13px; font-weight: 500; color: var(--ink-600); }
.contact-lines__email { font-size: 17px; color: var(--burgundy-600); }
.contact-lines__email:hover { text-decoration: underline; }

.hero__cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; }
.btn-secondary {
  background: transparent;
  color: var(--color-text-on-dark);
  border-color: rgba(242, 239, 233, 0.3);
}
.btn-secondary:hover { background: rgba(242, 239, 233, 0.08); color: var(--color-text-on-dark); }

.hero__form-wrap { width: 100%; }

.stat-strip__inner {
  display: flex;
  justify-content: flex-start;
  gap: 40px;
  flex-wrap: wrap;
}
.stat { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.stat__value { font-family: var(--font-display); font-size: 41px; font-weight: 600; line-height: 1; color: var(--color-text-on-dark); }
/* Same fix as .hero__lead above: --color-text-on-dark-secondary washes out
   over the sky photo, so bump to the primary on-dark color plus a soft
   shadow rather than leaving it muted. */
.stat__label {
  font-size: 13px;
  color: var(--color-text-on-dark);
  text-shadow: 0 2px 16px rgba(29, 42, 69, 0.55), 0 1px 2px rgba(29, 42, 69, 0.45);
}
.quote-card {
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 32px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.js-lead-form { display: flex; flex-direction: column; gap: 22px; }
.quote-card .field input,
.quote-card .field select,
.quote-card .field textarea {
  border-radius: var(--radius-md);
}
.quote-card .btn { border-radius: var(--radius-md); }
.quote-card__head { display: flex; flex-direction: column; gap: 4px; }
.quote-card__head h2 { font-family: var(--font-display); font-size: 21px; font-weight: 600; }
.quote-card__head p { font-size: 16px; color: var(--ink-600); }
.quote-card form.js-lead-form input:focus-visible,
.quote-card form.js-lead-form select:focus-visible,
.quote-card form.js-lead-form textarea:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}
.quote-card .field { gap: 6px; }
.quote-card .field__error { margin-top: 2px; }
.quote-card .field--checkbox-row input[type="checkbox"] { margin-top: 3px; }
.quote-card button[type="submit"] { transition: transform 120ms ease, box-shadow 200ms ease; }
.quote-card button[type="submit"]:hover {
  box-shadow: 0 4px 12px rgba(29, 42, 69, 0.18);
  transform: translateY(-1px);
}
.quote-card .form-success:not([hidden]),
.quote-card .form-error:not([hidden]) {
  animation: quote-card-msg-in 280ms ease-out;
}
@keyframes quote-card-msg-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ---------- About ---------- */
.about { background: var(--cream-50); padding: 88px 32px; border-top: 1px solid var(--border-subtle); }
.about__grid {
  max-width: var(--content-max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: 72px;
  align-items: start;
}
/* Used on the individual service pages, where this block is reused with the
   features/image column first and the text column second (mirrored from
   its original tjanster.html layout) - column widths swap along with the
   DOM order so the wider column still belongs to the features/image side. */
.about__grid--reverse { grid-template-columns: 1.1fr 0.9fr; }
.about__intro { display: flex; flex-direction: column; gap: 20px; position: sticky; top: 112px; }
.about__intro h2 {
  font-family: var(--font-display);
  font-size: 38px;
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.about__intro p { font-size: 17px; line-height: 1.7; color: var(--ink-600); }
.about__features { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.feature-card {
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: 28px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.feature-card__num { font-family: var(--font-display); font-size: 15px; font-weight: 600; color: var(--copper-500); }
.feature-card__title { font-family: var(--font-display); font-size: 19px; font-weight: 500; }
.feature-card__desc { font-size: 17px; line-height: 1.6; color: var(--ink-600); }
.about__image { grid-column: 1 / -1; aspect-ratio: 16/6; background-position: center top; }

/* ---------- Services ---------- */
.services { background: var(--white); padding: 88px 32px; border-top: 1px solid var(--border-subtle); }
.services__inner { max-width: var(--content-max); margin: 0 auto; display: flex; flex-direction: column; gap: 40px; }
.services__head { display: flex; justify-content: space-between; align-items: end; gap: 40px; flex-wrap: wrap; }
.services__head-left { display: flex; flex-direction: column; gap: 14px; max-width: 34em; }
.services__head-left h2 {
  font-family: var(--font-display);
  font-size: 38px;
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.services__head-right { font-size: 17px; color: var(--ink-600); margin: 0; max-width: 24em; }
.services__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; }
.service-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 28px;
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: box-shadow var(--duration-standard) var(--ease-standard), transform var(--duration-standard) var(--ease-standard);
}
.service-card:hover { box-shadow: var(--shadow-card-hover); transform: translateY(-2px); }
/* Sits behind the icon/body (z-index below, see those rules). Each card's
   own background-image (gradient + its own photo) is set inline in the
   HTML rather than here, so relative image paths resolve against the
   page, not this stylesheet.
   The photo is visible at rest, on every viewport. It used to be hidden
   behind clip-path: circle(0px at 52px 52px) and wiped open only on hover;
   that reveal is gone deliberately - the cards now show their photo from
   the start and hover only widens them. Because the text sits permanently
   on the photo, the on-dark colours below are the resting state rather
   than a hover override. */
.service-card__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-size: cover, cover;
  background-position: center, center;
}
.service-card__icon {
  position: relative;
  z-index: 1;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
.service-card__body { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 14px; flex: 1; }
.service-card__title { font-family: var(--font-display); font-size: 19px; font-weight: 500; color: #fff; transition: color 180ms ease-out; }
.service-card__desc { font-size: 17px; line-height: 1.55; color: rgba(255, 255, 255, 0.9); flex: 1; transition: color 180ms ease-out; }
.service-card__more { font-size: 14px; font-weight: 700; color: #fff; transition: color 180ms ease-out; }
.services__note { font-size: 17px; line-height: 1.7; color: var(--ink-600); text-align: center; max-width: 56em; align-self: center; }
.services__note strong { color: var(--navy-900); font-weight: 600; }

/* ---------- Reviews ---------- */
.reviews { background: var(--cream-50); padding: 88px 32px; border-top: 1px solid var(--border-subtle); }
.reviews__inner { max-width: var(--content-max); margin: 0 auto; display: flex; flex-direction: column; gap: 40px; }
.reviews__head { display: flex; flex-direction: column; gap: 14px; max-width: 34em; }
.reviews__head h2 {
  font-family: var(--font-display);
  font-size: 38px;
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.reviews__widget-slot:not(:empty) { margin-bottom: 8px; }
.reviews__grid { display: grid; grid-template-columns: 1.3fr 1fr 1fr; gap: 24px; }
.reviews__empty { grid-column: 1 / -1; aspect-ratio: 21 / 5; }
.review-card {
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.review-card__top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.review-card__stars { display: flex; gap: 2px; color: var(--copper-500); font-size: 15px; }
.review-card__tag {
  flex-shrink: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-600);
  background: var(--cream-100);
  border-radius: var(--radius-pill);
  padding: 4px 10px;
}
.review-card__quote { font-size: 17px; line-height: 1.6; color: var(--navy-900); font-style: italic; }
.review-card__meta { font-size: 14px; color: var(--ink-600); }
.review-card__meta strong { color: var(--navy-900); }
.reviews__disclaimer { font-size: 14px; color: var(--ink-600); }

/* ---------- Footer ---------- */
.site-footer { background: var(--navy-900); color: var(--color-text-on-dark); padding: 72px 32px 28px; }
.site-footer__grid { max-width: var(--content-max); margin: 0 auto; display: grid; grid-template-columns: 1.3fr 1fr 1fr 1fr; gap: 48px; }
.site-footer__brand { display: flex; flex-direction: column; gap: 16px; }
.site-footer__brand-row { display: flex; align-items: center; gap: 10px; }
.site-footer__logo-wrap { position: relative; display: block; width: 114px; height: 114px; flex-shrink: 0; }
.site-footer__logo-circle { position: absolute; left: 25px; top: 14px; width: 85px; height: 85px; border-radius: 999px; background: var(--white); }
.site-footer__logo-img { position: relative; width: 114px; height: 114px; object-fit: contain; display: block; }
.site-footer__brand-row span { font-family: var(--font-display); font-weight: 600; font-size: 19px; }
.site-footer__brand p { font-size: 17px; line-height: 1.65; color: var(--color-text-on-dark-secondary); max-width: 26em; }
.site-footer__col { display: flex; flex-direction: column; gap: 12px; }
.site-footer__col .label { font-family: var(--font-display); font-size: 15px; font-weight: 600; color: var(--color-text-on-dark); }
.site-footer__col a { font-size: 15px; color: var(--color-text-on-dark-secondary); }
.site-footer__col a:hover { color: var(--color-text-on-dark); }
.site-footer__phone { font-family: var(--font-display); font-size: 22px; font-weight: 600; color: var(--color-text-on-dark); }
.site-footer__phone:hover { color: var(--copper-500); }
.site-footer__addr { font-size: 17px; line-height: 1.6; color: var(--color-text-on-dark-secondary); }
.site-footer__hours { font-size: 13px; color: var(--color-text-on-dark-secondary); opacity: 0.75; }
.site-footer__bottom {
  max-width: var(--content-max);
  margin: 48px auto 0;
  border-top: 1px solid rgba(242,239,233,0.14);
  padding-top: 20px;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 24px;
  flex-wrap: wrap;
}
.site-footer__bottom p { font-size: 13px; color: var(--color-text-on-dark-secondary); }
.site-footer__credentials { display: flex; flex-direction: column; gap: 4px; }
.site-footer__social { display: flex; gap: 10px; align-items: center; flex-shrink: 0; }
.site-footer__social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  background: rgba(242, 239, 233, 0.08);
  color: var(--color-text-on-dark-secondary);
}
.site-footer__social-link:hover { background: rgba(242, 239, 233, 0.16); color: var(--color-text-on-dark); }

/* ---------- Service pages ---------- */
.service-hero { background: var(--navy-900); color: var(--color-text-on-dark); padding: 56px 32px 72px; }
.service-hero__inner { max-width: var(--content-max); margin: 0 auto; display: flex; flex-direction: column; gap: 20px; }
.service-hero__back { font-size: 14px; font-weight: 600; color: var(--color-text-on-dark-secondary); }
.service-hero__back:hover { color: var(--color-text-on-dark); }
.service-hero h1 { font-family: var(--font-display); font-size: 48px; line-height: 1.1; font-weight: 600; letter-spacing: -0.015em; }
.service-hero__lead { font-size: 19px; line-height: 1.65; color: var(--color-text-on-dark-secondary); max-width: 34em; }

/* Photo band behind a service hero. Opt-in via the --media modifier: the base
   .service-hero is shared with kontakt.html and integritetspolicy.html, which
   stay flat navy. Same art + scrim + raised-content layering as .hero on the
   home page; the page supplies its own photo through --service-hero-image.
   Scrim runs bottom-to-top rather than the home hero's 100deg left-to-right:
   this content is one bottom-anchored block spanning the full column width,
   not a left column beside an opaque card, so the darkening has to follow the
   text down instead of across - which also leaves the sky readable up top. */
.service-hero--media {
  position: relative;
  overflow: hidden;
  min-height: 440px;
  display: flex;
  align-items: flex-end;
}
/* The photo itself is set inline on the element (same idiom as
   .service-card__media on the home page) rather than piped in through a custom
   property: a relative url() inside a custom property resolves against the
   stylesheet that substitutes it, so 'assets/x.jpg' became '/css/assets/x.jpg'
   and 404'd. Declared inline it resolves against the document, as intended. */
.service-hero__art {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center;
}
.service-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to top, rgba(29, 42, 69, 0.94) 0%, rgba(29, 42, 69, 0.86) 30%, rgba(29, 42, 69, 0.62) 62%, rgba(29, 42, 69, 0.34) 100%);
}
.service-hero--media .service-hero__inner { position: relative; z-index: 2; width: 100%; }

.service-body { background: var(--cream-50); padding: 72px 32px; }
/* Same column treatment as .process__cta (minmax(0, 560px) + 1fr, 64px gap,
   content-max container) so the reused .quote-card renders at the exact
   same 560px width here as it does on the home page - the previous
   1.15fr/0.85fr split of a narrower 1024px container squeezed it to ~411px. */
.service-body__grid { max-width: var(--content-max); margin: 0 auto; display: grid; grid-template-columns: 1fr minmax(0, 560px); gap: 64px; align-items: start; }

.service-body__main { display: flex; flex-direction: column; gap: 20px; }
.service-body__main h2 { font-family: var(--font-display); font-size: 30px; line-height: 1.2; font-weight: 600; }
.service-body__main h3 { font-family: var(--font-display); font-size: 21px; line-height: 1.25; font-weight: 600; margin-top: 4px; }
.service-body__main p { font-size: 17px; line-height: 1.75; color: var(--ink-600); }
.service-points { margin: 8px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 12px; }
.service-points li { display: flex; gap: 12px; align-items: flex-start; font-size: 18px; line-height: 1.6; color: var(--navy-700); }
.service-points li::before {
  content: '';
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  margin-top: 9px;
  border-radius: 999px;
  background: var(--burgundy-600);
}

/* .service-aside is now just a sticky-positioning wrapper - the visible
   card (background/border/shadow/padding) is the reused .quote-card
   module nested inside it, so the two don't double up into a card-in-a-card.
   .service-aside__phone/__email are kept for kontakt.html, which still
   uses them directly inside .contact-info. */
.service-aside { position: sticky; top: 112px; }
.service-aside__phone { display: flex; align-items: center; justify-content: space-between; font-family: var(--font-display); font-size: 22px; font-weight: 600; color: var(--navy-900); }
.service-aside__phone:hover { color: var(--burgundy-600); }
.service-aside__phone span { font-family: var(--font-body); font-size: 13px; font-weight: 500; color: var(--ink-600); }
.service-aside__email { font-size: 17px; color: var(--burgundy-600); }
.service-aside__email:hover { text-decoration: underline; }

.projects { background: var(--white); padding: 72px 32px; border-top: 1px solid var(--border-subtle); }
.projects__inner { max-width: var(--content-max-narrow); margin: 0 auto; display: flex; flex-direction: column; gap: 28px; }
.projects__inner h2 { font-family: var(--font-display); font-size: 30px; line-height: 1.2; font-weight: 600; }
.projects__row-main { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; }
.projects__row-main > .image-placeholder { aspect-ratio: 16/10; }
.projects__stack { display: grid; grid-template-rows: 1fr 1fr; gap: 20px; }
.projects__row-grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; }
.projects__row-grid3 .image-placeholder { aspect-ratio: 4/3; }

.other-services { background: var(--cream-50); padding: 72px 32px; border-top: 1px solid var(--border-subtle); }
.other-services__inner { max-width: var(--content-max-narrow); margin: 0 auto; display: flex; flex-direction: column; gap: 24px; }
.other-services__inner h2 { font-family: var(--font-display); font-size: 30px; line-height: 1.2; font-weight: 600; }
.other-services__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
/* Area pages ("Våra tjänster") list all 4 services rather than the 3
   "other" ones a service page links to - this modifier keeps that one row
   at desktop widths without disturbing the base 3-column layout used
   elsewhere. Gated to the same breakpoint the base rule collapses at
   (see the ≤960px override below) so mobile still gets a single column. */
@media (min-width: 961px) {
  .other-services__grid--areas { grid-template-columns: repeat(4, 1fr); }
}
.other-service-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: 24px;
  transition: box-shadow var(--duration-standard) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.other-service-card:hover { box-shadow: var(--shadow-card-hover); }
.other-service-card h3 { font-family: var(--font-display); font-size: 19px; font-weight: 500; color: var(--navy-900); }
.other-service-card span { font-size: 14px; font-weight: 700; color: var(--burgundy-600); }

/* ---------- Process ("Så går det till") ---------- */
.process { background: var(--white); padding: 88px 32px; border-top: 1px solid var(--border-subtle); }
.process__inner { max-width: var(--content-max); margin: 0 auto; display: flex; flex-direction: column; gap: 48px; align-items: flex-start; }
.process__head { display: flex; flex-direction: column; gap: 14px; max-width: 34em; }
.process__head h2 { font-family: var(--font-display); font-size: 38px; line-height: 1.15; font-weight: 600; letter-spacing: -0.01em; }
/* grid-template-columns: minmax(0,560px) for the card, minmax(0,max-content)
   for the promo column - not two matched 560px tracks, and not 560px 1fr
   either (1fr greedily filled the rest of .process__inner's width, which
   pinned the card+promo pair to the left with a growing dead zone on the
   right at wide viewports). A flat 560px cap on the promo column clipped
   "precision och ansvar." onto its own extra line (it needs ~639px at this
   font-size) - max-content sizes the column to exactly what its widest line
   actually needs instead of a guessed number, so it can't under- or
   over-shoot. justify-content: center then centers the card+promo PAIR as a
   unit in the leftover space, rather than centering content within an
   already-full-width track. */
.process__cta { width: 100%; display: grid; grid-template-columns: minmax(0, 560px) minmax(0, max-content); justify-content: center; gap: 64px; align-items: center; }
.process__form-wrap { width: 100%; }
.process__promo { display: flex; flex-direction: column; gap: 24px; }
.process__promo-title {
  font-family: var(--font-display);
  font-size: 67px;
  line-height: 1.08;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--navy-900);
  margin: 0;
}
.process__promo-copy { display: flex; flex-direction: column; gap: 10px; }
.process__promo-kicker { font-family: var(--font-display); font-size: 19px; font-weight: 600; color: var(--navy-900); margin: 0; }
.process__promo-copy p:not(.process__promo-kicker) { font-size: 17px; line-height: 1.65; color: var(--ink-600); margin: 0; }

.step-list { position: relative; display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px; width: 100%; }
/* left/right: calc(12.5% - 12px), not a plain 12.5%, because .step-item is
   centered within its own grid column (see align-items: center below) -
   each badge's true center sits at half a column's width in from the
   column edge, and half of ((100% - 3*32px)/4) reduces to 12.5% - 12px.
   A plain 12.5% only holds if the 32px gap is ignored. */
.step-list::before {
  content: '';
  position: absolute;
  top: 23px;
  left: calc(12.5% - 12px);
  right: calc(12.5% - 12px);
  height: 1px;
  background: var(--border-strong);
  z-index: 0;
}
.step-item { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; }
.step-item__badge {
  width: 46px;
  height: 46px;
  border-radius: var(--radius-pill);
  background: var(--white);
  border: 1px solid var(--border-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 15px;
  color: var(--copper-500);
}
.step-item__title { font-family: var(--font-display); font-size: 19px; font-weight: 500; color: var(--navy-900); margin: 0; }
.step-item__desc { font-size: 17px; line-height: 1.6; color: var(--ink-600); margin: 0; }

/* ---------- Reference gallery ---------- */
.projects-section { background: var(--cream-50); padding: 88px 32px; border-top: 1px solid var(--border-subtle); }
.projects-section__inner { max-width: var(--content-max); margin: 0 auto; display: flex; flex-direction: column; gap: 40px; }
.projects-section__head { display: flex; flex-direction: column; gap: 14px; max-width: 34em; }
.projects-section__head h2 { font-family: var(--font-display); font-size: 38px; line-height: 1.15; font-weight: 600; letter-spacing: -0.01em; }
.projects-grid {
  display: flex;
  gap: 24px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  padding-bottom: 8px;
  margin: 0 -32px;
  padding-left: 32px;
  padding-right: 32px;
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) transparent;
}
.project-card {
  flex: 0 0 320px;
  scroll-snap-align: start;
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.project-card__media { position: relative; aspect-ratio: 4 / 3; }
.project-card__image { width: 100%; height: 100%; object-fit: cover; border-radius: 0; aspect-ratio: auto; }
.project-card__toggle {
  align-self: flex-start;
  margin: 12px 16px 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--burgundy-600);
  background: var(--cream-100);
  border: none;
  border-radius: var(--radius-pill);
  padding: 6px 14px;
  cursor: pointer;
  transition: background var(--duration-standard) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.project-card__toggle:hover { background: var(--cream-50); }
.project-card__body { padding: 16px; display: flex; flex-direction: column; gap: 4px; }
.project-card__type { font-family: var(--font-display); font-size: 17px; font-weight: 500; color: var(--navy-900); }
.project-card__location { font-size: 13px; font-weight: 600; color: var(--copper-500); text-transform: uppercase; letter-spacing: 0.04em; }
.project-card__desc { font-size: 16px; line-height: 1.55; color: var(--ink-600); }

/* ---------- FAQ ---------- */
.faq { background: var(--white); padding: 88px 32px; border-top: 1px solid var(--border-subtle); }
.faq__inner { max-width: var(--content-max-narrow); margin: 0 auto; display: flex; flex-direction: column; gap: 40px; }
.faq__head { display: flex; flex-direction: column; gap: 14px; max-width: 34em; }
.faq__head h2 { font-family: var(--font-display); font-size: 38px; line-height: 1.15; font-weight: 600; letter-spacing: -0.01em; }
.faq__list { display: flex; flex-direction: column; gap: 4px; }
.faq-item { border-bottom: 1px solid var(--border-subtle); }
.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 4px;
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 500;
  color: var(--navy-900);
  cursor: pointer;
  list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '+';
  flex-shrink: 0;
  font-family: var(--font-body);
  font-size: 22px;
  font-weight: 400;
  color: var(--burgundy-600);
  transition: transform var(--duration-standard) var(--ease-standard);
}
.faq-item[open] summary::after { transform: rotate(45deg); }
.faq-item summary:hover { color: var(--burgundy-600); }
.faq-item p { font-size: 17px; line-height: 1.7; color: var(--ink-600); margin: 0 4px 20px; max-width: 68ch; }
/* Compact FAQ's trailing "still have questions" prompt - index, tjanster.html
   and offert.html only; the full 8-question FAQ on kontakt.html has no CTA
   since the card next to it already is one. */
.faq__cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 20px;
  margin-top: 8px;
  padding-top: 28px;
  border-top: 1px solid var(--border-subtle);
}
.faq__cta p { font-family: var(--font-display); font-size: 21px; font-weight: 600; color: var(--navy-900); margin: 0; }

/* ---------- Nav active state ---------- */
.site-nav > a.is-active:not(.btn-primary),
.nav-dropdown > a.is-active { color: var(--burgundy-600); }
.nav-dropdown__menu a.is-active { color: var(--burgundy-600); background: var(--cream-50); }

/* ---------- Card with media (offertkort/kontaktkort) ----------
   Same .quote-card used everywhere else, plus an image strip on top.
   Padding moves from the card itself onto .quote-card__content so the
   media can sit flush with the card's edges/radius. */
.quote-card--with-media { padding: 0; gap: 0; }
.quote-card--with-media .quote-card__media { aspect-ratio: 16/10; border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
.quote-card--with-media .quote-card__content { padding: 32px; display: flex; flex-direction: column; gap: 24px; }

/* ---------- Page hero (tjanster.html/offert.html) ----------
   Same two-column shape as the home hero (.hero__grid) but without its
   photographic sky/roof art - just a flat navy section, reused module + a
   plain image-placeholder inside the card instead. */
.page-hero { background: var(--navy-900); padding: 96px 32px 88px; position: relative; overflow: hidden; }
.page-hero__inner {
  position: relative;
  z-index: 1;
  max-width: var(--content-max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 0.95fr 1.05fr;
  gap: 64px;
  align-items: center;
}
.page-hero__content { display: flex; flex-direction: column; gap: 20px; }
.page-hero__content .eyebrow { letter-spacing: 0.12em; }
.page-hero__content h1 {
  font-family: var(--font-display);
  font-size: 48px;
  line-height: 1.1;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--color-text-on-dark);
}
.page-hero__lead { font-size: 19px; line-height: 1.65; color: var(--color-text-on-dark-secondary); max-width: 34em; }

/* Ambient logo flyby (tjanster.html only): enters off the right edge,
   pauses once in the right-hand gutter (right of the heading), then
   crosses left - behind the text and offertkort - and exits off the left
   edge before looping. --page-hero-gutter is the navy margin outside the
   centered content-max container. --page-hero-pause-right defaults to 55%
   of the way across that gutter but is clamped so the logo's own scaled
   footprint (--page-hero-logo-half) never dips behind the opaque text
   column - without that clamp a wide logo on a narrower gutter would pause
   partly hidden behind the content instead of reading clearly on the navy
   background. Sits at z-index 0, behind .page-hero__inner's z-index 1, so
   it never competes with the heading or the form. prefers-reduced-motion is
   handled by the sitewide `*` override further down (collapses to a
   single, instant, already-offscreen frame) rather than repeated here. */
.page-hero {
  --page-hero-gutter: max(32px, calc((100vw - var(--content-max)) / 0.27));
  /* Half of the logo's rendered footprint at its paused scale: 126px width
     * 1.3 pause scale / 2. Update this alongside the width/height and the
     scale in the keyframes below if either changes. */
  --page-hero-logo-half: 82px;
  --page-hero-pause-right: clamp(calc(100vw - var(--page-hero-gutter) + var(--page-hero-logo-half)), calc(100vw - var(--page-hero-gutter) * 0.55), calc(100vw - 8px));
}
.page-hero__logo-fly { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.page-hero__logo-fly-img {
  position: absolute;
  top: 15%;
  left: 0;
  width: 126px;
  height: 126px;
  opacity: 0;
  animation: page-hero-logo-fly 7s cubic-bezier(0.2, 0.7, 0.3, 1) infinite;
  animation-delay: 0s;
}
@keyframes page-hero-logo-fly {
  0% { transform: translate(115vw, -850%) scale(5); opacity: 0; }
  8% { opacity: 1; }
  18% { transform: translate(var(--page-hero-pause-right), -50%) scale(1.3); opacity: 1; }
  32% { transform: translate(var(--page-hero-pause-right), -50%) scale(1.3); opacity: 1; }
  82% { opacity: 0; }
  90% { transform: translate(-40vw, -850%) scale(05); opacity: 0; }
  100% { transform: translate(-40vw, -50%) scale(0.5); opacity: 0; }
}

/* ---------- Closing CTA ---------- */
/* padding-bottom: 32px, not 48px - reduces the gap before the footer by
   1/3 (48 * 2/3). */
.closing-cta { background: var(--navy-900); padding: 48px 32px 32px; }
.closing-cta__inner { max-width: 820px; margin: 0 auto; display: flex; flex-direction: column; align-items: center; text-align: center; gap: 24px; }
.closing-cta__inner h2 {
  font-family: var(--font-display);
  font-size: 48px;
  line-height: 1.12;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--color-text-on-dark);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0.3em;
}
.closing-cta__inner p { font-size: 19px; line-height: 1.65; color: var(--color-text-on-dark-secondary); max-width: 42em; }
.closing-cta__buttons { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px; margin-top: 12px; }
.closing-cta__buttons .btn-lg { padding: 19px 38px; }

/* ---------- Rotating word (closing CTA) ----------
   All four words share one CSS Grid cell so the container auto-sizes to
   the widest word with no JS measurement. Visibility/position is driven
   by is-active/is-past/is-future classes toggled in main.js
   (initRotatingWord), one step ahead/behind like an odometer. */
/* text-align: center (not left): all four words share one grid cell sized
   to the widest word, so a shorter word left-aligned in that box sits flush
   to the box's left edge with empty box-space trailing it on the right -
   the box itself was centered by the parent h2's justify-content:center,
   but the word's actual visible text wasn't, making shorter words read as
   shifted left. Centering the text within the shared cell fixes that for
   every word regardless of length, with no JS width measurement needed. */
.rotating-word { position: relative; display: inline-grid; text-align: center; overflow: hidden; padding: 4px 0; }
.rotating-word__item {
  grid-area: 1 / 1;
  white-space: nowrap;
  color: var(--copper-500);
  font-weight: 700;
  opacity: 0;
  transform: translateY(60%);
  transition: transform 500ms var(--ease-standard), opacity 400ms var(--ease-standard);
}
.rotating-word__item.is-active { opacity: 1; transform: translateY(0); }
.rotating-word__item.is-past { opacity: 0; transform: translateY(-60%); }
.rotating-word__item.is-future { opacity: 0; transform: translateY(60%); }

/* ---------- Scroll reveal ----------
   Elements fade/rise into place the first time they cross the viewport
   (see js/main.js initScrollReveal). Scoped entirely inside the
   no-preference query so a prefers-reduced-motion visitor never has
   content sitting at opacity:0 waiting on JS. */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translateY(28px);
    transition: opacity 700ms var(--ease-standard), transform 700ms var(--ease-standard);
    transition-delay: var(--reveal-delay, 0ms);
  }
  .reveal--visible { opacity: 1; transform: translateY(0); }
}

/* ---------- Tactile feedback ----------
   :active press states for anything clickable, kept last in the
   cascade so they win over the :hover rules declared earlier. */
.btn:active { transform: scale(0.97); }
.service-card:active { transform: translateY(-1px) scale(0.99); }
.other-service-card:active { transform: scale(0.99); }
.project-card__toggle:active { transform: scale(0.95); }
.site-header__logo-badge { transition: transform var(--duration-standard) var(--ease-standard); }
.site-header__logo-badge:hover { transform: scale(1.03); }
.service-card__icon { transition: transform var(--duration-standard) var(--ease-standard), box-shadow var(--duration-standard) var(--ease-standard); }
.service-card:hover .service-card__icon { transform: scale(1.12) rotate(-4deg); box-shadow: 0 0 0 6px rgba(185, 139, 78, 0.22); }
/* Work-light hover (961-1199px + touch, real :hover/:has()): the hovered
   card's icon lights up (above) while its siblings recede slightly. The
   wider real-pointer breakpoint below replaces this with the JS-driven
   expand/reveal instead. */
.services__grid:has(.service-card:hover) .service-card:not(:hover) {
  opacity: 0.82;
  transition: opacity 220ms ease;
}
/* Expand-and-reveal hover, ported from a Claude Design prototype (imported
   via the design MCP, project "Tjanstekort"). Desktop + real pointer only -
   gated by (hover:hover) and (min-width:1200px) - so tablets, touch, and
   narrower desktop windows keep the plain grid + simple icon-lights-up
   treatment above, untouched. State is driven entirely by JS
   (initServiceCardHover in main.js) via a data-hover-state attribute
   ("active" | "inactive", absent = rest) instead of :hover/:has(),
   because pure CSS can't diff which cards actually changed: switching the
   hovered card directly from one to another means the real cursor crosses
   the gap between them, and for however many ms it's there, NO card
   matches :hover - so :has(.service-card:hover) on the grid briefly
   un-matches and re-matches, retriggering every sibling's transition even
   though their own flex-grow value never truly changes. The JS instead
   diffs previous vs. next state per card and only writes the attribute
   (and toggles the text-settle class below) on cards whose state actually
   differs - an uninvolved sibling gets zero DOM writes, not just a
   no-op transition.
   min-width raised from an earlier 212px to 256px so an inactive/shrunk
   card's description content-width never drops below ~200px - the same
   width rest itself already needs at the 1200px floor (its own worst
   case, 5 lines). That's what keeps the whole row's height constant
   through every hover state without an explicit fixed height: nothing
   ever needs to wrap past what rest already reserves room for (see the
   .service-card__desc min-height below). Enter (260ms) is slower than
   leave (160ms) on purpose - declared once on the resting rule (read when
   returning to rest or becoming a newly-inactive sibling) and again on
   [data-hover-state="active"] (read while transitioning into hover),
   since a CSS transition's duration comes from the state being
   transitioned TO. */
@media (hover: hover) and (min-width: 1200px) {
  .services__grid { display: flex; align-items: stretch; gap: 24px; }
  .service-card {
    flex-grow: 1;
    flex-basis: 0;
    min-width: 256px;
    transition: flex-grow 160ms ease-out, box-shadow 160ms ease-out, transform 160ms ease-out;
  }
  .service-card[data-hover-state="active"] {
    flex-grow: 1.7;
    box-shadow: 0 18px 40px rgba(16, 22, 38, 0.28);
    transform: translateY(-4px);
    transition: flex-grow 260ms ease-out, box-shadow 260ms ease-out, transform 260ms ease-out;
  }
  .service-card[data-hover-state="inactive"] { flex-grow: 0.78; }
  /* Reserves height for the longest title's and description's worst case
     at this breakpoint (2 lines / 5 lines - see the comment above): a
     constant so the row never needs to grow when a sibling shrinks. */
  .service-card__title { min-height: 46px; }
  .service-card__desc { min-height: 132px; }
  /* The photo and the on-dark text colours are resting state now (see the
     base rules above), so nothing here reveals or recolours anything - the
     only thing hover changes at this breakpoint is the card's width. This
     rule just cancels the icon's scale/rotate lift from the narrower
     breakpoint, which reads as noise next to the width animation. */
  .service-card[data-hover-state="active"] .service-card__icon {
    transform: none;
    box-shadow: none;
  }
  /* Text-settle fade: a transition entirely separate from the width/
     clip-path one above (own duration + a hold in between, toggled by JS
     adding/removing --settling on the body wrapper - see
     initServiceCardHover in main.js), so the title/description are fully
     invisible while they reflow into their new line-wrap instead of
     visibly jittering into place. Only touches cards whose state actually
     changed, same as the attribute above. Fade-in (170ms, on removal) is
     slower than fade-out (110ms, on entry) so the text eases back in
     rather than snapping. */
  .service-card__body { transition: opacity 170ms ease-out; }
  .service-card__body.service-card__body--settling {
    opacity: 0;
    transition: opacity 110ms ease-in;
  }
}

/* ---------- Sticky mobile CTA ---------- */
.sticky-cta {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  gap: 10px;
  padding: 12px 16px;
  background: var(--navy-900);
  box-shadow: 0 -8px 24px rgba(29, 42, 69, 0.24);
}
.sticky-cta.sticky-cta--hidden { transform: translateY(100%); }
.sticky-cta .btn { flex: 1; }

/* ---------- Anchor scroll offset (sticky header clearance) ----------
   Applied to every section id that is (or could reasonably become) an
   anchor-link target, not just #offert, so the sticky header never covers
   a jump-to destination. */
#offert,
#om-oss,
#tjanster,
#recensioner,
#sa-gar-det-till,
#referenser,
#faq,
#kontakt { scroll-margin-top: 130px; }

/* ---------- Responsive ---------- */
@media (min-width: 961px) {
  .service-hero { padding-top: 104px; }
  #offert,
  #om-oss,
  #tjanster,
  #recensioner,
  #sa-gar-det-till,
  #referenser,
  #faq,
  #kontakt { scroll-margin-top: 170px; }
}

@media (max-width: 960px) {
  /* The -92px pull-up above is calibrated to the desktop header's height;
     tablet/mobile headers are taller and haven't been fitted for this
     effect yet, so leave the hero sitting in normal flow here. */
  .hero { margin-top: 0; }
  /* cqw-based drift covers fewer actual pixels on a narrow mobile viewport,
     so at the original desktop durations (118-236s) the clouds read as
     static. Scaling each cloud's own duration down keeps their relative
     parallax speeds intact while making the motion actually visible. */
  .hero__cloud { animation-duration: calc(var(--cloud-duration, 120s) / 3); }
  .site-header { padding: 0; }
  .site-header__logo-wrap { display: none; }
  .site-header__brand { width: auto; justify-content: flex-start; }
  .site-header__brand-icon-wrap { opacity: 1; }
  .about__grid, .service-body__grid, .process__cta, .page-hero__inner { grid-template-columns: 1fr; }
  .about__intro, .service-aside { position: static; }
  /* Heading/lead read before the card on small screens, same order the
     home hero already uses (text markup precedes the form there too). */
  .page-hero__content { order: -1; }
  /* kontakt.html only: card goes above the FAQ on mobile (spec: "kortet överst"). */
  .service-aside--first-mobile { order: -1; }
  .services__grid { grid-template-columns: repeat(2, 1fr); }
  .reviews__grid { grid-template-columns: 1fr; }
  .site-footer__grid { grid-template-columns: 1fr 1fr; }
  .form-row-2 { grid-template-columns: 1fr; }
  .projects__row-main { grid-template-columns: 1fr; }
  .projects__row-grid3 { grid-template-columns: 1fr 1fr; }
  .other-services__grid { grid-template-columns: 1fr; }
  /* Mobile header collapses everything except the logo and "Få offert"
     into a hamburger toggle (.site-header__menu-toggle, added only in the
     HTML at this breakpoint's viewport - hidden via display:none above
     960px). .site-header__left and .site-nav go display:contents, which
     promotes their children (brand link, tel, email, dropdown, plain nav
     links, the CTA) to be direct flex items of .site-header__inner without
     touching any markup - each can then be ordered/shown independently.
     Low order (1-3) keeps the logo + CTA + toggle on the first row; order
     10+ collapses phone/email/nav links into a full-width stack that's
     hidden until .site-header--menu-open (toggled in main.js) reveals it. */
  .site-header__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px 16px;
    padding: 16px 24px;
  }
  .site-header__left { display: contents; }
  .site-nav { display: contents; }
  .site-header__brand { order: 1; }
  .site-nav > .btn-primary { order: 2; }
  .site-header__menu-toggle {
    order: 3;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    background: var(--white);
    color: var(--navy-900);
    flex-shrink: 0;
    cursor: pointer;
  }
  .site-header__tel,
  .site-header__email,
  .site-nav > .nav-dropdown,
  .site-nav > a:not(.btn-primary) {
    display: none;
    width: 100%;
    padding: 14px 0;
    border-top: 1px solid var(--border-subtle);
  }
  .site-header__tel { order: 10; padding-left: 0; border-left: none; }
  .site-header__email { order: 11; }
  .site-nav > .nav-dropdown { order: 12; }
  .site-nav > a:not(.btn-primary) { order: 13; }
  .site-header--menu-open .site-header__tel,
  .site-header--menu-open .site-header__email,
  .site-header--menu-open .site-nav > .nav-dropdown,
  .site-header--menu-open .site-nav > a:not(.btn-primary) {
    display: block;
  }
  /* The desktop mega-menu is centered/absolutely-positioned and 4 columns
     wide - collapse it back into the stacked mobile panel instead, since
     .nav-dropdown itself already goes full-width/in-flow at this breakpoint. */
  .nav-dropdown__menu--areas {
    position: static;
    left: auto;
    transform: none !important;
    min-width: 0;
    max-width: none;
    grid-template-columns: repeat(2, 1fr);
    box-shadow: none;
    border: none;
    padding: 8px 0 0;
    margin-top: 0;
    max-height: none;
    overflow: visible;
  }
  .hero { padding: 96px 32px 56px; }
  .hero__grid { grid-template-columns: 1fr; gap: 40px; }
  /* .hero__art-frame needs no override here anymore - it fills the hero
     exactly at every width now (see its base rule), which is exactly the
     "roof at its own natural proportions, no inflated overscale" behavior
     this breakpoint used to have to opt into on its own. */
  /* Scale the roof up slightly so the chimneys clear the fold instead of
     being cropped right at the top of the visible band. Anchored to the
     bottom so the roofline itself doesn't shift, only its top edge rises. */
  .hero__art-roof-cutout {
    transform: scale(1.15);
    transform-origin: 50% 100%;
  }
  .stat-strip__inner { gap: 40px; }
  .step-list { grid-template-columns: repeat(2, 1fr); }
  .step-list::before { display: none; }
  .step-item:nth-child(even) { margin-top: 0; }
  .projects-grid .project-card { flex-basis: 260px; }
  .sticky-cta { display: flex; }
}

@media (max-width: 640px) {
  .hero { padding: 20px 20px 130px; }
  .hero__grid { gap: 6px; }
  .hero__content { gap: 12px; }
  .hero__cta-row { margin-top: 4px; }
  .hero__title { font-size: 43px; }
  .process__promo-title { font-size: 43px; }
  .hero__cta-row .btn { flex: 1; }
  .stat-strip { padding: 24px 20px; }
  .stat-strip__inner { gap: 24px; }
  .stat__value { font-size: 28px; }
  .about, .services, .reviews, .service-body, .projects, .other-services { padding-left: 20px; padding-right: 20px; }
  .service-hero { padding: 40px 20px 48px; }
  .service-hero h1 { font-size: 32px; }
  /* Narrow viewports crop the photo hard toward its centre, so a 440px band
     buys mostly empty lakt grid - trade height back for a tighter frame. */
  .service-hero--media { min-height: 340px; }
  .services__grid { grid-template-columns: 1fr; }
  .projects__row-grid3 { grid-template-columns: 1fr; }
  .site-footer__grid { grid-template-columns: 1fr; }
  .site-footer { padding: 48px 20px 24px; }
  .process, .projects-section, .faq, .closing-cta { padding-left: 20px; padding-right: 20px; }
  .closing-cta__inner h2 { font-size: 32px; }
  .step-list { grid-template-columns: 1fr; }
  .projects-grid { margin: 0 -20px; padding-left: 20px; padding-right: 20px; }
  .projects-grid .project-card { flex-basis: 82vw; }
  .closing-cta__buttons .btn { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .service-card:hover, .other-service-card:hover { transform: none; }
}
