/*
 * Rules the prototype's design system does not contain.
 *
 * globals.css is carried over from the prototype unedited, so anything the
 * prototype never had to draw is missing from it. Keeping those rules in a
 * separate file means a future refresh of globals.css cannot silently drop them,
 * and it stays obvious which part of the design is ours rather than theirs.
 *
 * Loaded after globals.css.
 */

/* ---------------------------------------------------------------------------
 * Card whose target page does not exist yet
 *
 * sk_card_link() (inc/templates.php) renders such a card as a <div> instead of
 * an <a>, so it cannot link into a 404. Most of the design system already
 * scopes its hover to `a.` and needs nothing here; .countertop-card-link does
 * not, so a plain <div> would still lift and brighten under the cursor and
 * promise a click it cannot deliver. Loaded after globals.css, same
 * specificity, so this wins.
 *
 * Temporary by construction: these rules stop matching anything the moment the
 * catalogue pages exist.
 * ------------------------------------------------------------------------- */

.sk-card-inert {
  cursor: default;
}

.sk-card-inert:hover,
.sk-card-inert:focus-visible {
  background: rgba(249, 246, 240, 0.68);
  transform: none;
}

/* ---------------------------------------------------------------------------
 * Language switcher
 *
 * The prototype ships ACTIVE_LOCALES = ["ru"], so LanguageSwitcher returns null
 * and globals.css has no .lang-switcher rule at all — the markup rendered as a
 * run of unstyled text, "ETEestiRUРусский…". The live site publishes four
 * languages, so it has to be drawn.
 *
 * The markup already says how it was meant to look: each item carries a
 * two-letter code plus a .lang-switcher-full span with the language's own name.
 * Two letters are what fits a header that already holds seven links; the full
 * name stays in the accessibility tree, where a screen reader reads "Eesti"
 * rather than spelling out E-T.
 * ------------------------------------------------------------------------- */

.lang-switcher {
  display: flex;
  align-items: center;
  gap: 2px;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
}

.lang-switcher-item {
  /*
   * 24×24 is the smallest tap target Lighthouse and WCAG 2.5.8 accept. Two
   * letters at 0.72rem with 4px padding came to ~22px on phones and the last
   * item (FI) failed the target-size audit on every page (2026-09-04 runs).
   */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
  padding: 4px 7px;
  border-radius: 4px;
  color: #8a8781;
  text-decoration: none;
  transition: color 0.18s ease, background-color 0.18s ease;
}

.lang-switcher-item:hover,
.lang-switcher-item:focus-visible {
  color: #3f3d3a;
  background: rgba(63, 61, 58, 0.08);
}

.lang-switcher-item.is-current {
  color: #3f3d3a;
  background: rgba(63, 61, 58, 0.1);
}

.site-header--dark .lang-switcher-item {
  color: rgba(241, 238, 232, 0.58);
}

.site-header--dark .lang-switcher-item:hover,
.site-header--dark .lang-switcher-item:focus-visible {
  color: var(--paper, #f1eee8);
  background: rgba(241, 238, 232, 0.1);
}

.site-header--dark .lang-switcher-item.is-current {
  color: var(--paper, #f1eee8);
  background: rgba(241, 238, 232, 0.14);
}

/* Present to assistive technology, absent from the layout. */
.lang-switcher-full {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 900px) {
  /* The header collapses to logo, switcher and the burger; four codes still fit
     where seven nav links do not. */
  .lang-switcher {
    gap: 0;
    font-size: 0.72rem;
  }

  .lang-switcher-item {
    padding: 4px 5px;
  }
}

/* ---------------------------------------------------------------------------
 * Blocks arriving on scroll
 *
 * Owner's decision, 2026-09-01. The prototype has no such effect — there is not
 * one IntersectionObserver in its sources — so this is an addition to the
 * approved design rather than part of the port, which is why it lives here and
 * not in globals.css.
 *
 * Written without JavaScript on purpose. The usual way to do this is to start a
 * block at opacity 0 and have a script reveal it, which makes the page depend on
 * that script to be readable at all: until it runs, the visitor sees nothing,
 * and on a slow phone that is a slower page, not a nicer one. A scroll-driven
 * CSS animation has no such moment — the browser drives it, and where the
 * browser does not know the feature the @supports guard leaves every block
 * plainly visible. Nothing to fall back to, nothing to break.
 *
 * A block already on screen when the page opens is shown finished, not
 * animated: its entry range is behind the scroll position, so the animation sits
 * at its end. That is what keeps the first screen instant.
 *
 * The exclusions are not taste, they are correctness. An element with a
 * transform becomes the containing block for `position: fixed` descendants and
 * changes the frame a `position: sticky` descendant sticks to — the same defect
 * that made the shortlist panel land 7203px down the page before it was moved to
 * the footer. The list below is every fixed or sticky element the design system
 * places inside <main>, taken by grepping globals.css for `position: fixed` and
 * `position: sticky`; re-derive it the same way if the design system changes.
 * ------------------------------------------------------------------------- */

/*
 * Why two rules of the design system are overridden here.
 *
 * globals.css clips the decorative background layers of #content and .home-story
 * with `overflow: hidden`. That also, as a side effect nobody asked for, makes
 * each of them a scroll container — and a scroll-driven animation measures
 * against the nearest scroll container, not the window. Every block on every
 * page sat inside one of these two, so the timeline never moved and the effect
 * was invisible. Measured in the browser, not deduced.
 *
 * `overflow: clip` clips exactly the same pixels and is not scrollable, which is
 * what these two rules actually wanted. Verified by comparing every section's
 * position and size before and after on the home page and on /materjalid/:
 * identical to the pixel, page height unchanged.
 *
 * One deliberate consequence beyond the animation: `position: sticky` inside a
 * non-scrolling `overflow: hidden` box cannot work either, so the design's
 * sticky navigation was dead — in the prototype too. It starts working here.
 * That is a departure from the prototype's rendering, and it is a departure
 * towards what the design plainly intends.
 */
#content,
.home-story {
  overflow: clip;
}

/*
 * The same arrival, driven by assets/js/block-reveal.js, for browsers that lack
 * the CSS feature below — Firefox, as of 2026.
 *
 * Scoped to .sk-reveal-js, a class only that script adds, and .sk-reveal-pending,
 * which it only puts on blocks that are below the fold. So: no script, nothing
 * hidden; script blocked or thrown, nothing hidden; first screen, nothing hidden.
 */
html.sk-reveal-js #main section.sk-reveal-pending {
  opacity: 0;
  transform: translateY(18px);
}

html.sk-reveal-js #main section.sk-reveal-pending.sk-reveal-in {
  opacity: 1;
  transform: none;
  transition: opacity 620ms ease, transform 620ms ease;
}

@media (prefers-reduced-motion: reduce) {
  html.sk-reveal-js #main section.sk-reveal-pending {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

@keyframes sk-block-enter {
  from {
    opacity: 0;
    transform: translateY(18px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    #main section {
      animation: sk-block-enter linear both;

      /* Not decoration: the `animation` shorthand above sets duration to 0s
       * when it is omitted, and a zero-length animation finishes at its first
       * frame — every block sat in its final state and nothing ever moved.
       * `auto` is what makes the animation span its scroll range instead of a
       * stretch of time. Verified in the browser, not assumed. */
      animation-duration: auto;
      animation-timeline: view();
      animation-range: entry 0% entry 75%;
    }

    /* A section inside a section is a subdivision of a block that is already
     * arriving, not a block of its own — the catalogue's collection groups are
     * six such sections holding hundreds of swatches between them, and animating
     * each one turns a calm page into a flicker. Only top-level blocks arrive. */
    #main section section {
      animation: none;
    }

    #main section:has(dialog),
    #main section:has(.hardware-section-nav),
    #main section:has(.hardware-jump-nav),
    #main section:has(.process-intro),
    #main section:has(.faq-catalog aside) {
      animation: none;
    }
  }
}

/* ---------------------------------------------------------------------------
 * The enquiry form as Contact Form 7 renders it
 *
 * globals.css styles the prototype's <form>: a <label> per field, the caption
 * in a <span>, the control right after it. CF7 keeps that structure but wraps
 * every control in <span class="wpcf7-form-control-wrap">, which the caption
 * rule `.estimate-form label > span` then also matches — the input inherits a
 * 0.78rem caption size (globals.css sets `input { font: inherit }`). The first
 * rule hands the wrap the label's own font and colour back.
 *
 * The submit is an <input type="submit">, not the prototype's <button>, and
 * the front page's field rule `.estimate-form input:not([type="file"])`
 * catches it — dark field styling on what should be the light button. The
 * `.button--light` values are restated at higher specificity.
 *
 * The drop zone is built by the upload plugin's script with its own light
 * stylesheet; the rules below put it in the form's dark palette. Selectors
 * are the plugin's; a plugin update that renames them shows up as a light
 * box on a dark form, not as a broken page.
 * ------------------------------------------------------------------------- */

.estimate-form label > .wpcf7-form-control-wrap {
  display: block;
  color: inherit;
  font-size: inherit;
}

.estimate-form .file-field {
  display: grid;
  gap: 8px;
}

.estimate-form .file-field > span {
  color: #dfddd8;
  font-size: 0.78rem;
}

.estimate-form .file-field small {
  margin-left: 6px;
  color: #aaa59d;
  font-size: inherit;
}

.estimate-form input[type="submit"].button,
.home-page .estimate-form input[type="submit"].button {
  width: auto;
  min-height: 48px;
  border: 1px solid var(--paper);
  border-radius: 0;
  padding: 0.78rem 1.25rem;
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
}

.estimate-form input[type="submit"].button:hover,
.home-page .estimate-form input[type="submit"].button:hover {
  border-color: var(--stone);
  background: var(--stone);
}

.estimate-form .wpcf7-spinner {
  margin: 0 0 0 14px;
}

.estimate-form .wpcf7-not-valid-tip {
  display: block;
  margin-top: 6px;
  color: #e3a98c;
  font-size: 0.74rem;
}

/*
 * CF7's stylesheet addresses the summary as `.wpcf7 form .wpcf7-response-output`
 * and recolours its border per state (amber for invalid, green for sent), so
 * the selector here carries the same weight plus the form class, and the
 * states are restated in the form's own palette.
 */
.wpcf7 form.estimate-form .wpcf7-response-output {
  margin: 0;
  border: 1px solid rgba(241, 238, 232, 0.42);
  padding: 12px 14px;
  color: var(--paper);
  font-size: 0.82rem;
}

.wpcf7 form.estimate-form.invalid .wpcf7-response-output,
.wpcf7 form.estimate-form.unaccepted .wpcf7-response-output,
.wpcf7 form.estimate-form.failed .wpcf7-response-output,
.wpcf7 form.estimate-form.aborted .wpcf7-response-output,
.wpcf7 form.estimate-form.spam .wpcf7-response-output {
  border-color: #e3a98c;
}

.wpcf7 form.estimate-form.sent .wpcf7-response-output {
  border-color: var(--paper);
}

.estimate-form .codedropz-upload-handler {
  margin: 0;
  border: 1px dashed rgba(241, 238, 232, 0.42);
  border-radius: 0;
  background: transparent;
}

.estimate-form .codedropz-upload-inner {
  padding: 22px 16px;
}

.estimate-form .codedropz-upload-inner h3 {
  margin: 0 0 6px;
  color: var(--paper);
  font-size: 0.9rem;
  font-weight: 500;
}

.estimate-form .codedropz-upload-inner > span {
  color: #aaa59d;
  font-size: 0.78rem;
}

.estimate-form .codedropz-upload-inner .codedropz-btn-wrap a.cd-upload-btn {
  display: inline-block;
  margin-left: 6px;
  border: 1px solid rgba(241, 238, 232, 0.42);
  border-radius: 0;
  padding: 6px 11px;
  background: transparent;
  color: var(--paper);
  font-size: 0.78rem;
  text-decoration: none;
}

.estimate-form .dnd-upload-status .dnd-upload-details .name,
.estimate-form .dnd-upload-status .dnd-upload-details .name span,
.estimate-form .dnd-upload-counter {
  color: #c8c4bd;
}

.estimate-form .dnd-upload-status .dnd-upload-details .dnd-progress-bar {
  background: rgba(241, 238, 232, 0.2);
}

.estimate-form .dnd-upload-status .dnd-upload-details .dnd-progress-bar span {
  background: var(--paper);
}

/* -------------------------------------------------------------------------
 * Map plate — the contacts map before it is asked for
 *
 * A stored picture of the location stands where the Google embed used to load
 * on sight. It carries the same grayscale treatment as the embed, so the click
 * swaps one for the other without the page changing colour.
 * ---------------------------------------------------------------------- */

.map-plate {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: inherit;
  overflow: hidden;
  background: #d8d4cc;
}

/* The <picture> has to be given a box of its own. It is an inline element by
   default and takes no height, so the image's height: 100% resolved against
   nothing and the plate rendered as an empty beige rectangle with a button
   floating in it — the markup and the file were both correct and the map was
   simply not there. Found by looking at the rendered page, which is the only
   place this shows. */
.map-plate picture {
  position: absolute;
  inset: 0;
  display: block;
}

/* No filter here. The picture is already in the site's two tones, applied when
   it was built (deploy/build-map-image.py); running the embed's grayscale over
   it a second time was what washed the streets out to the page background. The
   Google frame keeps the filter, because it arrives in Google's colours. */
.map-plate__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Below the middle, not on it: the pin is drawn at the centre of the picture,
   and a button placed there covered the one thing the plate exists to show. */
.map-plate__button {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, 46px);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 14px 26px;
  border-radius: 999px;
  background: var(--ink, #222324);
  color: var(--paper, #f3eee6);
  font-size: 15px;
  letter-spacing: 0.02em;
  text-decoration: none;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.22);
  transition: background 160ms ease, transform 160ms ease;
}

.map-plate__button:hover,
.map-plate__button:focus-visible {
  background: var(--blue, #3f617f);
  transform: translate(-50%, 44px);
}

.map-plate__note {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 10px 16px;
  background: rgba(243, 238, 230, 0.94);
  color: var(--ink, #222324);
  font-size: 12px;
  letter-spacing: 0.02em;
  text-align: center;
}

/* Two class names, not one, and display governed here rather than by the
   hidden attribute. globals.css still carries ".contacts-map__frame iframe"
   and ".footer-map iframe" from the old embed markup, both { display: block },
   and a class-plus-element selector outranks the [hidden] rule the browser
   supplies. The empty frame was therefore laid over the whole plate: the map
   and the button were rendered, correctly sized and fully loaded, and covered
   by a transparent iframe. Nothing in the HTML or the image was wrong, which
   is why every check passed and only the screenshot showed it. */
.map-plate .map-plate__frame {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  filter: grayscale(1) contrast(0.94);
}

.map-plate.is-loaded .map-plate__frame {
  display: block;
}

/* The footer plate stands where an iframe with a fixed height used to, and
   that height was set on the iframe, not on its container. */
.footer-map .map-plate {
  height: 250px;
}

@media (max-width: 900px) {
  .footer-map .map-plate {
    height: 220px;
  }
}

/* Once the embed is in, the picture and its furniture are out of the way —
   hidden from the screen and from assistive technology alike. */
.map-plate.is-loaded :where(picture, .map-plate__button, .map-plate__note) {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .map-plate__button {
    transition: none;
  }

  .map-plate__button:hover,
  .map-plate__button:focus-visible {
    transform: translate(-50%, 46px);
  }
}

/* ---------------------------------------------------------------------------
 * Price request — /hinnaparing/
 *
 * The page carries two classes: `contacts-page`, because it reuses that page's
 * hero, numbered steps, bordered grid and form card, and those rules are scoped
 * to it in globals.css; and `price-page` for what differs. globals.css ships
 * from the prototype unedited, so widening its selector lists is not an option —
 * borrowing the class and overriding here is.
 *
 * Three things differ: the photograph behind the page, the two note paragraphs
 * the contacts page has no shape for, and the price figure inside each cell of
 * the grid.
 * ------------------------------------------------------------------------- */

/* The canvas photograph. Both have to be named: --page-canvas-image draws the
   fixed backdrop behind the whole page, and .contacts-hero paints the salon
   over the top of it with a rule of its own. */
.price-page {
  --page-canvas-image: url("/images/portfolio/gerli/gerli-1.jpg");
  --page-canvas-position: center 52%;
}

.price-page .contacts-hero {
  background:
    linear-gradient(90deg, rgba(12, 14, 15, 0.93) 0%, rgba(12, 14, 15, 0.74) 44%, rgba(12, 14, 15, 0.3) 74%, rgba(12, 14, 15, 0.2) 100%),
    linear-gradient(180deg, rgba(12, 14, 15, 0.12), rgba(12, 14, 15, 0.5)),
    url("/images/portfolio/gerli/gerli-1.jpg") center 52% / cover no-repeat;
}

/* A paragraph that qualifies the block above it: the instruction for a visitor
   with no floor plan, and the sentence saying what moves a kitchen between
   price levels. Indented against a rule so it reads as an aside and not as the
   next chapter. */
:where(.price-page, .price-examples-page, .finland-page) .price-note {
  max-width: 760px;
  margin-top: clamp(38px, 5vw, 60px);
  border-left: 1px solid rgba(243, 238, 230, 0.28);
  padding-left: clamp(20px, 2.5vw, 32px);
  color: rgba(243, 238, 230, 0.74);
  font-size: 0.95rem;
  line-height: 1.75;
}

:where(.price-page, .price-examples-page, .finland-page) .price-note strong {
  color: rgba(243, 238, 230, 0.94);
  font-weight: 580;
}

:where(.price-page, .price-examples-page, .finland-page) .price-note--after {
  margin-top: clamp(30px, 4vw, 46px);
}

/* The figure is the reason the cell exists, so it is set as a number and not as
   another line of prose. .contacts-label above it stays the small caption. */
:where(.price-page, .price-examples-page, .finland-page) .price-level__range {
  margin-top: 10px;
  color: var(--contacts-paper);
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: 1.15;
}

:where(.price-page, .price-examples-page, .finland-page) .contacts-details__grid article > p:last-child {
  margin-top: 18px;
}

/* The questions sit on a panel, the way a chapter of copy does everywhere else
   on the dark canvas: the accordion is drawn in --ink and --muted, which are
   readable on paper and invisible on the backdrop. Same declarations as the
   panel list in globals.css, which is not ours to extend. */
:where(.price-page, .price-examples-page, .finland-page) .price-faq {
  padding-block: 0 clamp(95px, 11vw, 160px);
}

:where(.price-page, .price-examples-page, .finland-page) #content > section.price-faq > .shell {
  border: 1px solid var(--material-panel-line);
  border-radius: var(--radius-panel);
  padding: clamp(26px, 4vw, 52px);
  background: var(--material-panel-strong);
  color: var(--ink);
  box-shadow: 0 18px 52px rgba(28, 24, 21, 0.09);
  backdrop-filter: blur(12px);
}

:where(.price-page, .price-examples-page, .finland-page) .price-faq__heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: clamp(26px, 3vw, 40px);
}

:where(.price-page, .price-examples-page, .finland-page) .price-faq__heading h2 {
  color: var(--ink);
}

/* ---------------------------------------------------------------------------
 * Heading grids: a grid item may shrink below its contents
 *
 * assets/js/editorial-heading-rules.js measures a heading's last visual line
 * and writes the width to the ::after that draws the rule beneath it — as an
 * inline pixel value, on a block-level pseudo-element. A grid item's default
 * `min-width: auto` then counts that pixel width towards the item's minimum, so
 * the column grows to hold it, the heading stops wrapping, the next measurement
 * reads the now-longer single line, and the track grows again. It settles with
 * the heading laid out at its unwrapped width — on a phone, past the edge of
 * the screen.
 *
 * `min-width: 0` is the ordinary remedy: it lets the item shrink to its track,
 * the heading wraps, and the script's next pass measures the wrapped last line.
 * Nothing here changes a heading short enough never to have inflated its track,
 * which is every heading on /kontakt/ — the loop needs a heading long enough to
 * overflow before it can start.
 * ------------------------------------------------------------------------- */

.contacts-section-heading > *,
.contacts-hero__layout > *,
.contacts-message__layout > * {
  min-width: 0;
}

/* ---------------------------------------------------------------------------
 * Cookie consent — the banner
 *
 * inc/consent.php prints it hidden on every page; assets/js/consent.js shows
 * it once. Fixed to the bottom of the viewport so it shifts nothing when it
 * appears, and drawn in the page's own two tones so it reads as part of the
 * site rather than a plugin's overlay. One column below 640px, the buttons
 * side by side above it.
 *
 * The [hidden] rule is written out because a class selector outranks the
 * browser's own [hidden] { display: none } — the same fault the map plate's
 * iframe had, see .map-plate .map-plate__frame above.
 *
 * Above the material shortlist (z-index 90, bottom right on catalogue pages)
 * by ten: the banner is answered once and gone, the shortlist stays.
 * ------------------------------------------------------------------------- */

.sk-consent {
  position: fixed;
  right: 16px;
  bottom: 16px;
  left: 16px;
  z-index: 100;
  display: flex;
  justify-content: flex-start;
  pointer-events: none;
}

.sk-consent[hidden] {
  display: none;
}

.sk-consent__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 6px 28px;
  align-items: center;
  width: min(100%, 720px);
  padding: 18px 22px;
  border: 1px solid var(--line, rgba(34, 35, 36, 0.18));
  border-radius: var(--radius-media, 16px);
  background: var(--paper, #f3eee6);
  color: var(--ink, #222324);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.18);
  pointer-events: auto;
}

.sk-consent__title {
  grid-column: 1;
  margin: 0;
  font-size: 0.95rem;
  font-weight: 620;
  letter-spacing: -0.012em;
}

.sk-consent__text {
  grid-column: 1;
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--muted, #68605a);
}

.sk-consent__link {
  margin-left: 4px;
  color: var(--ink, #222324);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.sk-consent__link:hover,
.sk-consent__link:focus-visible {
  color: var(--blue, #3f617f);
}

.sk-consent__actions {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  gap: 10px;
}

.sk-consent__button {
  min-height: 44px;
  padding: 0.6rem 1.15rem;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 620;
  white-space: nowrap;
}

.sk-consent__button--quiet {
  border-color: var(--line, rgba(34, 35, 36, 0.18));
  background: transparent;
  color: var(--ink, #222324);
}

.sk-consent__button--quiet:hover,
.sk-consent__button--quiet:focus-visible {
  border-color: var(--ink, #222324);
  background: transparent;
}

@media (max-width: 640px) {
  .sk-consent {
    right: 10px;
    bottom: 10px;
    left: 10px;
  }

  .sk-consent__inner {
    grid-template-columns: minmax(0, 1fr);
    gap: 6px;
    padding: 16px 18px;
  }

  .sk-consent__actions {
    grid-column: 1;
    grid-row: auto;
    flex-wrap: wrap;
    margin-top: 8px;
  }

  .sk-consent__button {
    flex: 1 1 auto;
  }
}

/* ---------------------------------------------------------------------------
 * Views the theme has not taken over: the old site's Elementor pages, the
 * blog's posts, archives and search. body.sk-legacy is set by the same test
 * that drives the asset allowlist (inc/setup.php). The content is printed as
 * it is; this only gives it room under the sticky header, a reading measure
 * for prose, and a heading scale below the ported pages' hero titles. No
 * colours are set: the old site's own stylesheet still loads on these pages
 * and decides the ground.
 * ------------------------------------------------------------------------ */

.sk-legacy .legacy-article {
  padding-block: clamp(48px, 6vw, 88px) clamp(72px, 8vw, 120px);
}

.sk-legacy .legacy-article--prose {
  max-width: 74ch;
  margin-inline: auto;
}

.sk-legacy .legacy-article--prose > h1 {
  font-size: clamp(2rem, 3.4vw, 2.9rem);
  line-height: 1.1;
  margin-bottom: clamp(20px, 2.4vw, 32px);
}

.sk-legacy .legacy-article--prose h2 {
  max-width: none;
  font-size: clamp(1.45rem, 2.2vw, 1.9rem);
  line-height: 1.2;
  margin-top: clamp(32px, 3.6vw, 48px);
  margin-bottom: 0.6em;
}

.sk-legacy .legacy-article--prose h3 {
  font-size: clamp(1.15rem, 1.6vw, 1.35rem);
  line-height: 1.3;
  margin-top: 1.8em;
  margin-bottom: 0.5em;
}

.sk-legacy .legacy-article--prose p,
.sk-legacy .legacy-article--prose li {
  font-size: 1.05rem;
  line-height: 1.65;
}

.sk-legacy .legacy-article--prose p + p {
  margin-top: 1em;
}

.sk-legacy .legacy-article--prose ul,
.sk-legacy .legacy-article--prose ol {
  margin-block: 1em;
  padding-left: 1.4em;
}

.sk-legacy .legacy-article--prose li + li {
  margin-top: 0.4em;
}

.sk-legacy .legacy-article--prose img {
  height: auto;
  margin-block: clamp(24px, 3vw, 40px);
}

.sk-legacy .legacy-article--prose a:not(.wp-block-button__link) {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.sk-legacy .legacy-article__meta {
  margin-bottom: 0.6em;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  opacity: 0.7;
}

.sk-legacy .legacy-article__back {
  margin-top: clamp(40px, 5vw, 64px);
}

.sk-legacy .legacy-list__item + .legacy-list__item {
  margin-top: clamp(36px, 4vw, 56px);
  padding-top: clamp(28px, 3vw, 40px);
  border-top: 1px solid rgba(128, 128, 128, 0.3);
}

.sk-legacy .legacy-list__item h2 {
  margin-top: 0;
}

.sk-legacy .legacy-list .pagination,
.sk-legacy .legacy-list .nav-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6em;
  margin-top: clamp(40px, 5vw, 64px);
}

/* The editorial rule under headings belongs to the ported pages' sections;
 * on an Elementor page or a post it would underline every heading. */
.sk-legacy .legacy-article section :where(h2, h3)::after,
.sk-legacy .legacy-article :where(h2, h3)::after {
  content: none;
}

/* The legal row under the footer grid: the privacy policy and the way back
 * into the consent banner (footer.php). Same tone as .footer-contacts, one
 * step quieter; the button is dressed as a link because it acts like one. */
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px 24px;
  padding-block: 0 26px;
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.78rem;
}

.footer-legal__link {
  color: inherit;
  text-decoration: none;
  transition: color 0.2s ease;
}

.footer-legal__button {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
}

.footer-legal__link:hover,
.footer-legal__link:focus-visible {
  color: #fff;
}

/* ---------------------------------------------------------------------------
 * Price examples — /hinnanaidised/ (2026-09-05)
 *
 * Same two-class pattern as /hinnaparing/: contacts-page for the hero and the
 * headings, price-examples-page for its own canvas and the project cards.
 * The cards are the page: photograph, name, the material lines the project
 * page already publishes, and the figure. Two to a row and not three, so the
 * figure and the material list have room to be read, not scanned.
 * ------------------------------------------------------------------------- */

.price-examples-page {
  --page-canvas-image: url("/images/portfolio/norman/norman-1.jpg");
  --page-canvas-position: center 48%;
}

.price-examples-page .contacts-hero {
  background:
    linear-gradient(90deg, rgba(12, 14, 15, 0.93) 0%, rgba(12, 14, 15, 0.74) 44%, rgba(12, 14, 15, 0.3) 74%, rgba(12, 14, 15, 0.2) 100%),
    linear-gradient(180deg, rgba(12, 14, 15, 0.12), rgba(12, 14, 15, 0.5)),
    url("/images/portfolio/norman/norman-1.jpg") center 48% / cover no-repeat;
}

.price-examples-page .price-examples__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(34px, 4vw, 58px) clamp(18px, 2.5vw, 34px);
  margin-top: clamp(30px, 4vw, 48px);
}

@media (max-width: 720px) {
  .price-examples-page .price-examples__grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.price-examples-page .price-example {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.price-examples-page .price-example figure {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--stone);
}

.price-examples-page .price-example img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.price-examples-page .price-example h2 {
  padding-top: 18px;
  font-size: clamp(1.25rem, 1.9vw, 1.7rem);
  font-weight: 620;
}

/* The figure first, in the size of a heading: it is what the visitor came for.
   The material lines under it are the project page's own, in the caption
   size the contacts grid uses for its labels. */
.price-examples-page .price-example__figure {
  margin-top: 8px;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: 1.15;
  color: var(--contacts-paper);
}

.price-examples-page .price-example__meta {
  margin-top: 6px;
  color: rgba(243, 238, 230, 0.74);
  font-size: 0.95rem;
}

.price-examples-page .price-example dl {
  margin-top: 16px;
  display: grid;
  grid-template-columns: minmax(0, 9em) minmax(0, 1fr);
  gap: 6px 14px;
  font-size: 0.92rem;
}

.price-examples-page .price-example dt {
  color: rgba(243, 238, 230, 0.6);
}

.price-examples-page .price-example dd {
  margin: 0;
  color: rgba(243, 238, 230, 0.88);
}

.price-examples-page .price-example dd a {
  color: inherit;
}

.price-examples-page .price-example .text-link {
  margin-top: auto;
  padding-top: 18px;
  align-self: flex-start;
}

/* ---------------------------------------------------------------------------
 * Finnish landing — /keittio-virosta/ (2026-09-05)
 *
 * The hero, the numbered steps, the bordered grid, the form card and the FAQ
 * panel are all borrowed; the page adds only its canvas and the strip of three
 * project photographs, which reuses the portfolio card as it is.
 * ------------------------------------------------------------------------- */

.finland-page {
  --page-canvas-image: url("/images/portfolio/karima/karima-1.jpg");
  --page-canvas-position: center 50%;
}

.finland-page .contacts-hero {
  background:
    linear-gradient(90deg, rgba(12, 14, 15, 0.93) 0%, rgba(12, 14, 15, 0.74) 44%, rgba(12, 14, 15, 0.3) 74%, rgba(12, 14, 15, 0.2) 100%),
    linear-gradient(180deg, rgba(12, 14, 15, 0.12), rgba(12, 14, 15, 0.5)),
    url("/images/portfolio/karima/karima-1.jpg") center 50% / cover no-repeat;
}

/* Four steps, not three: the contacts grid is three to a row and would leave
   "Tootmine" alone on a second line. Four across on a wide screen, two by two
   on a tablet, one below the width where globals.css stacks the steps. */
.finland-page .contacts-prep__steps {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 1100px) {
  .finland-page .contacts-prep__steps {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {
  .finland-page .contacts-prep__steps {
    grid-template-columns: minmax(0, 1fr);
  }
}

.finland-page .finland-projects .portfolio-grid {
  margin-top: clamp(30px, 4vw, 48px);
}

/* The strip sits under the section's own <h2>, so the card names are <h3>;
   the portfolio rule styles an h2 and is repeated here for the h3. */
.finland-page .finland-projects .portfolio-card-link h3 {
  padding-top: 15px;
  font-size: clamp(1.15rem, 1.7vw, 1.55rem);
  font-weight: 620;
  color: var(--contacts-paper);
}

.finland-page .finland-projects .text-link {
  display: inline-block;
  margin-top: clamp(22px, 3vw, 32px);
}

/* ---------------------------------------------------------------------------
 * Front-page hero: the rotating opening frames
 *
 * The prototype's hero is one still photograph, and globals.css still describes
 * it — .hero-visual img keeps its object-fit and its 51%/58% framing, and every
 * slide is that same img inside a wrapper. Only what the still frame never
 * needed is here: the crossfade and the slow zoom.
 *
 * Motion lives on the <img> and opacity on the wrapper, deliberately. Putting
 * both on one element means the transform resets the moment the slide stops
 * being active, and the reset is visible through the fade. .is-leaving keeps
 * the animation alive until the fade is over; assets/js/hero-slideshow.js
 * removes it then, and the two timings are written in both files.
 * ------------------------------------------------------------------------ */

.hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.4s ease-in-out;
}

.hero-slide.is-active {
  opacity: 1;
}

/* The first slide is the LCP element and is painted before any script runs, so
   its zoom starts with the page. `forwards` holds the end of the movement:
   without it the frame would jump back at the end of the animation, seconds
   before the slide is replaced. */
.hero-slide.is-active img,
.hero-slide.is-leaving img {
  animation: sk-hero-zoom-in 11s ease-out forwards;
  transform-origin: 51% center;
  will-change: transform;
}

/* Alternating direction, so a visitor who watches several frames does not see
   the same movement repeated. Even slides pull back instead of pushing in. */
.hero-slide:nth-child(even).is-active img,
.hero-slide:nth-child(even).is-leaving img {
  animation-name: sk-hero-zoom-out;
}

@keyframes sk-hero-zoom-in {
  from { transform: scale(1); }
  to   { transform: scale(1.08); }
}

@keyframes sk-hero-zoom-out {
  from { transform: scale(1.08); }
  to   { transform: scale(1); }
}

@media (max-width: 760px) {
  .hero-slide.is-active img,
  .hero-slide.is-leaving img {
    transform-origin: 58% center;
  }
}

/* Asked for stillness: no zoom, no crossfade. The script does not rotate the
   slides either, so what remains is the single frame the markup ships with. */
@media (prefers-reduced-motion: reduce) {
  .hero-slide {
    transition: none;
  }

  .hero-slide.is-active img,
  .hero-slide.is-leaving img {
    animation: none;
  }
}
