/* Generetial — runtime layer.
 *
 * Hand-written, loaded after the Webflow stylesheets on every page. Holds the
 * things the Webflow export cannot express: the reveal guarantee, loading
 * skeletons, motion and focus accessibility, and CLS defences.
 *
 * Nothing in here restyles the design. It only fixes behaviour.
 */

/* ───────────────────────────────────────────────────────────────────────────
 * 1. THE REVEAL GUARANTEE  —  the most important rule in this file.
 *
 * Every page in the export ships its content wrapper collapsed inline:
 *
 *     <div style="width:100%;height:0px;opacity:0" class="main-wrapper">
 *
 * Webflow un-collapses it from an ix2 "page load" interaction, but that
 * interaction only exists in the exported data for the home page id. On the
 * other 29 pages nothing ever ran, so the wrapper stayed at height:0 and the
 * page rendered as an empty blue viewport — verified in production on
 * /services-01.html, /contact-01.html, /team-01.html and /the-agency-01.html.
 * js/page-reveal.js was a 700 ms setTimeout patched onto 5 of the 34 pages.
 *
 * A timer is the wrong tool: it makes visibility depend on winning a race
 * against 1.2 MB of webflow.js. These two declarations make *visible* the
 * default state at first paint, with no JavaScript involved at all — the page
 * cannot be blank even if every script on it fails to load.
 *
 * `!important` is required and deliberate: it is the only way a stylesheet can
 * beat an inline style attribute. The cost is that the wrapper can no longer be
 * animated on entry. That intro animation was already invisible on every page
 * (.preloader is display:none in the Webflow CSS), so nothing is lost — but if
 * one is ever wanted back, it has to be built as an animation that reveals from
 * a visible base, not one that un-collapses from zero.
 * ─────────────────────────────────────────────────────────────────────────── */
.main-wrapper {
  height: auto !important;
  opacity: 1 !important;
}

/* The preloader is display:none in the Webflow CSS, so it never shows. Pin it
 * closed so a stray inline style cannot resurrect it over the content. */
.preloader {
  display: none !important;
}

/* ───────────────────────────────────────────────────────────────────────────
 * 2. LOADING SKELETONS
 *
 * CMS-backed grids arrive one Firestore round-trip after paint. Without a
 * placeholder the visitor sees Webflow's "No items found" empty state flash
 * before the real content lands, which reads as a broken page.
 * ─────────────────────────────────────────────────────────────────────────── */
.gen8v-skeleton {
  position: relative;
  overflow: hidden;
  background: color-mix(in srgb, currentColor 8%, transparent);
  border-radius: 4px;
  min-height: 1em;
  color: transparent !important;
  user-select: none;
}

.gen8v-skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, currentColor 15%, transparent), transparent);
  animation: gen8v-shimmer 1.4s infinite;
}

@keyframes gen8v-shimmer {
  100% { transform: translateX(100%); }
}

.gen8v-skeleton-card { display: flex; flex-direction: column; gap: .75rem; }
.gen8v-skeleton-media { aspect-ratio: 3 / 2; width: 100%; }
.gen8v-skeleton-line { height: .85em; }
.gen8v-skeleton-line.is-short { width: 45%; }
.gen8v-skeleton-line.is-medium { width: 70%; }

/* While a container is loading, keep Webflow's empty state out of the way. */
[data-gen8v-state="loading"] .w-dyn-empty { display: none !important; }

/* ───────────────────────────────────────────────────────────────────────────
 * 3. CONTENT ERROR STATE
 *
 * The inline scripts used to paint `style="color:red"` error text into the
 * empty state. Give it a real, on-brand treatment instead.
 * ─────────────────────────────────────────────────────────────────────────── */
.gen8v-error {
  border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
  border-left: 3px solid #e9b2b8;
  background: color-mix(in srgb, currentColor 4%, transparent);
  padding: 1rem 1.25rem;
  border-radius: 4px;
}

.gen8v-error a,
.gen8v-inline-button {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* A real <button> styled as a link — content-grid.js needs a retry control, and
   a `javascript:` href is blocked by both the CSP and the sanitizer. */
.gen8v-inline-button {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

.gen8v-inline-button:hover { text-decoration-thickness: 2px; }

/* ───────────────────────────────────────────────────────────────────────────
 * 4. CLS DEFENCE — for ARTICLE BODY images only.
 *
 * Scope matters here. `data-gen8v-cms` is applied by js/sanitize-html.js, so it
 * lands only on images inside rich-text bodies, which the Webflow CSS does not
 * size — those genuinely shift the layout when they decode.
 *
 * It is deliberately NOT applied to the card images in the CMS grids. Checking
 * the Webflow stylesheet: .photo-blog, .photo-extra-small, .photo-item and
 * .photo-blog-01-* all set `height: 100%` plus a `max-height: Nvw`, so their box
 * is already reserved by the container and they do not shift. Forcing
 * aspect-ratio onto them would override that sizing and break the grid — this
 * rule would cause the problem it looks like it prevents. Do not widen the
 * selector to `.w-dyn-item img`.
 * ─────────────────────────────────────────────────────────────────────────── */
img[data-gen8v-cms] {
  aspect-ratio: 3 / 2;
  object-fit: cover;
  background: color-mix(in srgb, currentColor 6%, transparent);
}

img[data-gen8v-cms].is-square { aspect-ratio: 1 / 1; }

/* ───────────────────────────────────────────────────────────────────────────
 * 5. ACCESSIBILITY
 * ─────────────────────────────────────────────────────────────────────────── */

/* The export sets no visible focus style anywhere, so keyboard users have no
 * idea where they are. :focus-visible only shows it for keyboard navigation. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--color-01, #54e9dc);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Skip link — first tab stop, hidden until focused. */
.gen8v-skip-link {
  position: absolute;
  left: 1rem;
  top: -100px;
  z-index: 100000;
  background: var(--color-01, #54e9dc);
  color: var(--color-dark, #0e0e0e);
  font-family: Montserrat, sans-serif;
  font-weight: 700;
  font-size: 14px;
  letter-spacing: .04em;
  padding: .75rem 1.25rem;
  border-radius: 0 0 6px 6px;
  text-decoration: none;
  transition: top .15s ease-in;
}

.gen8v-skip-link:focus { top: 0; }

.gen8v-visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ───────────────────────────────────────────────────────────────────────────
 * 6. REDUCED MOTION
 *
 * This template is animation-heavy: marquees, parallax, scroll-driven scaling.
 * Visitors who have asked their OS for less motion currently get all of it.
 * ─────────────────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }

  .gen8v-skeleton::after { animation: none; }
  html { scroll-behavior: auto !important; }
}

/* ───────────────────────────────────────────────────────────────────────────
 * 7. FORM VALIDATION  (paired with js/lead-form.js)
 * ─────────────────────────────────────────────────────────────────────────── */
.gen8v-field-error {
  color: #c0392b;
  margin-top: .35rem;
  font-size: .8em;
  line-height: 1.4;
}

[aria-invalid="true"] {
  border-color: #c0392b !important;
}

form[aria-busy="true"] { cursor: progress; }

/* ───────────────────────────────────────────────────────────────────────────
 * 8. BLOG FILTER / SEARCH  (paired with js/blog-controls.js)
 * ─────────────────────────────────────────────────────────────────────────── */
.gen8v-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem 1rem;
  margin-bottom: 2.5rem;
}

/* Tints below derive from currentColor rather than hardcoding white: the blog
 * list section is dark-text-on-light while most others are the inverse, so a
 * white-tinted control would be invisible on exactly the page it is used on. */
.gen8v-search {
  flex: 1 1 14rem;
  min-width: 12rem;
  background: color-mix(in srgb, currentColor 7%, transparent);
  border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
  border-radius: 100px;
  color: inherit;
  font: inherit;
  font-size: .95rem;
  padding: .7rem 1.15rem;
}

.gen8v-search::placeholder { color: currentColor; opacity: .55; }

.gen8v-chip {
  appearance: none;
  cursor: pointer;
  background: transparent;
  border: 1px solid color-mix(in srgb, currentColor 32%, transparent);
  border-radius: 100px;
  color: inherit;
  font-family: Montserrat, sans-serif;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  padding: .6rem 1.1rem;
  transition: background-color .18s ease, border-color .18s ease, color .18s ease;
}

.gen8v-chip:hover { border-color: var(--color-01, #54e9dc); }

.gen8v-chip[aria-pressed="true"] {
  background: var(--color-01, #54e9dc);
  border-color: var(--color-01, #54e9dc);
  color: var(--color-dark, #0e0e0e);
}

.gen8v-result-count {
  flex-basis: 100%;
  font-size: .8rem;
  opacity: .7;
  margin: 0;
}

.gen8v-load-more {
  display: block;
  margin: 3rem auto 0;
  appearance: none;
  cursor: pointer;
  background: transparent;
  border: 1px solid currentColor;
  border-radius: 100px;
  color: inherit;
  font-family: Montserrat, sans-serif;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  padding: 1rem 2.25rem;
  transition: background-color .2s ease, color .2s ease;
}

.gen8v-load-more:hover {
  background: var(--color-01, #54e9dc);
  border-color: var(--color-01, #54e9dc);
  color: var(--color-dark, #0e0e0e);
}

.gen8v-load-more[hidden] { display: none; }

/* ───────────────────────────────────────────────────────────────────────────
 * 9. ARTICLE EXTRAS  (paired with js/article-extras.js)
 * ─────────────────────────────────────────────────────────────────────────── */
.gen8v-progress {
  position: fixed;
  top: 0; left: 0;
  height: 3px;
  width: 0;
  background: var(--color-01, #54e9dc);
  z-index: 99999;
  transition: width .1s linear;
  pointer-events: none;
}

.gen8v-meta-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1.25rem;
  margin-top: 1rem;
  font-size: .8rem;
  opacity: .75;
}

.gen8v-share {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-top: 2rem;
}

.gen8v-share-label {
  font-family: Montserrat, sans-serif;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  opacity: .6;
  margin-right: .35rem;
}

.gen8v-share a,
.gen8v-share button {
  appearance: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.35rem;
  height: 2.35rem;
  border: 1px solid currentColor;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  text-decoration: none;
  transition: background-color .18s ease, color .18s ease, border-color .18s ease;
}

.gen8v-share a:hover,
.gen8v-share button:hover {
  background: currentColor;
  border-color: currentColor;
}

.gen8v-share a:hover svg,
.gen8v-share button:hover svg { color: var(--color-light, #f7f7f7); mix-blend-mode: difference; }

.gen8v-share svg { width: 15px; height: 15px; fill: currentColor; }

.gen8v-share-toast {
  font-size: .78rem;
  opacity: 0;
  transition: opacity .2s ease;
}

.gen8v-share-toast.is-visible { opacity: .8; }

@media print {
  .gen8v-progress,
  .gen8v-share,
  .gen8v-controls,
  .gen8v-load-more { display: none !important; }
}

/* ───────────────────────────────────────────────────────────────────────────
 * 10. NO-JS FALLBACK INDEX  (written by scripts/build_seo.py)
 *
 * Inside <noscript> on the list pages. A visitor without JavaScript otherwise
 * gets Webflow's grey "No items found." box where the grid should be; this gives
 * them a real, navigable list of everything instead.
 * ─────────────────────────────────────────────────────────────────────────── */
.gen8v-fallback-index {
  max-width: 46rem;
  margin: 0 auto;
  padding: 3rem 1.5rem 4rem;
}

.gen8v-fallback-index h2 {
  font-family: Montserrat, sans-serif;
  font-size: 1.4rem;
  margin: 0 0 1.5rem;
}

.gen8v-fallback-index ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.25rem;
}

.gen8v-fallback-index li {
  border-top: 1px solid color-mix(in srgb, currentColor 18%, transparent);
  padding-top: 1.25rem;
}

.gen8v-fallback-index a {
  color: inherit;
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
}

.gen8v-fallback-index small { opacity: .72; }
