/* ═══════════════════════════════════════════════════════════════════════
   FIVENSON PAGE KIT — shared across every page
   Created 2026-08-02.

   Why this file exists: the same defects repeat on all 15 pages. Auditing
   them found 78 inline 100/120px paddings, 45 fixed 2-col grids and 19
   counter-rotation animations — all declared as INLINE styles, which no
   media query can reach. Hence the !important flags below: they are not
   laziness, they are the only way to override a style="" attribute.

   Fixing this per-page would be 15x the work and would drift out of sync.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. MOBILE SPACING SCALE ────────────────────────────────────────────
   Desktop 120px top/bottom on a 390px phone = ~4 full screens of empty
   space per page. Steps down at 900px and 560px. */
@media (max-width: 900px) {
  section[style] {
    padding-top: 64px !important;
    padding-bottom: 64px !important;
    padding-left: 20px !important;
    padding-right: 20px !important;
  }
}
@media (max-width: 560px) {
  section[style] {
    padding-top: 48px !important;
    padding-bottom: 48px !important;
    padding-left: 16px !important;
    padding-right: 16px !important;
  }
}

/* ── 2. FIXED GRIDS COLLAPSE ────────────────────────────────────────────
   "1fr 1fr" / "1.1fr 0.9fr" inline means the second column runs off a
   375px screen and gets clipped (Chris: "this is not responsive at all"). */
@media (max-width: 820px) {
  /* .fs-multicol is applied by the kit's JS, which PARSES each declared value
     and tags anything resolving to more than one track. That replaced an
     enumerated substring list which missed most real cases: an inventory found
     25+ distinct declarations across the 17 pages, and branding.html's
     `1.15fr 0.85fr` hero never collapsed, putting the hero visual on top of the
     copy at 375px.
     The literal selectors below are kept only as a no-JS fallback. */
  .fs-multicol,
  [style*="1fr 1fr"],
  [style*="1.1fr 0.9fr"],
  [style*="1.2fr"],
  [style*="0.9fr"] {
    grid-template-columns: 1fr !important;
    gap: 28px !important;
  }
}

/* ── 3. ORBIT LABELS THAT FLIP ──────────────────────────────────────────
   Chris: "fix this from flipping around, it looks glitchy."
   The orbit spins and each node counter-rotates to stay upright. A
   transform-based hover on the SAME element replaces that counter-rotation,
   so the parent's rotation shows through and the label turns upside down.
   Fix: never let hover touch transform on the counter-rotating element.

   The wrapping itself is done in JS (fivenson-page-kit.js -> fixOrbits) because
   it needs to move the position classes onto a new element. Once the animation
   lives on the wrapper, the page's own `.eco-node:hover { transform: scale(1.1)
   !important }` becomes harmless: it now scales an element that is not carrying
   the counter-rotation.

   NOTE: do NOT try to neutralise the page rule with `transform: none !important`
   — measured, that kills the counter-rotation too and flips the label to 90°,
   reproducing the exact bug. */
.eco-node-pos { position: absolute; }
.eco-node-pos > .eco-node { position: relative; top: auto; left: auto; right: auto; bottom: auto; }
.eco-node-pos:hover { z-index: 20; }

/* ── 3b. ORBIT RESTACK ON MOBILE ────────────────────────────────────────
   An orbit ring cannot survive a 390px screen. Measured before this rule:
     website-design  labels 9.2px after the wrapper's 0.5 scale (4/4 illegible)
     marketing       8 of 9 labels completely off screen at 375px
     branding        ring 902px wide at left -264px, 4 of 8 off screen
   All three passed BOTH the angle check and the position check while being
   unreadable or invisible, which is why containment + effective font size are
   now the assertions that decide `orbitLayoutOK`.

   Below 900px the ring is abandoned and the same nodes become a readable
   stack. `transform: none` matters: the .n1-.n8 classes carry
   translate(-50%,-50%), which is meaningless once the node is a grid cell but
   still drags the whole stack half its width off the left edge. */
@media (max-width: 900px) {
  .eco-orbit,
  .eco-orbit-1,
  .eco-orbit-2 {
    position: static !important;
    width: auto !important; height: auto !important;
    border: 0 !important; border-radius: 0 !important;
    transform: none !important; animation: none !important;
    display: grid !important;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px; margin: 0 0 14px !important;
  }
  .eco-node-pos {
    position: static !important;
    top: auto !important; left: auto !important; right: auto !important; bottom: auto !important;
    transform: none !important; animation: none !important;
  }
  /* the wrapper often scales the whole diagram down; that is what produced 9.2px text */
  .eco-wrapper {
    transform: none !important;
    min-height: 0 !important;
    display: block !important;
    margin-bottom: 0 !important;
  }
  .eco-center {
    position: static !important;
    width: auto !important; height: auto !important;
    max-width: 260px; margin: 0 auto 18px !important;
    border-radius: 20px !important; padding: 18px !important;
    font-size: 1.15rem !important;
  }
  .eco-center::after { display: none !important; }
  .eco-node { width: 100%; justify-content: center; white-space: normal !important; }
}

/* ── 4. MOBILE CTA PAIR ─────────────────────────────────────────────────
   Chris: "make sure we have start your project and the phone call button
   ... implement those on all the pages." */
.fs-mobile-cta { display: none; }
@media (max-width: 820px) {
  .fs-mobile-cta {
    display: flex; gap: 10px;
    position: fixed; left: 12px; right: 12px; bottom: 12px; z-index: 9997;
  }
  .fs-mobile-cta a {
    flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    padding: 14px 10px; border-radius: 999px; text-decoration: none;
    font: 800 .95rem/1.2 var(--font-heading, system-ui), system-ui, sans-serif;
    text-align: center;
  }
  .fs-mobile-cta .fs-cta-start {
    background: linear-gradient(135deg, #1A91E2 0%, #1078C1 100%); color: #fff;
    box-shadow: 0 10px 26px rgba(26,145,226,.38), inset 0 1px 0 rgba(255,255,255,.25);
  }
  .fs-mobile-cta .fs-cta-call {
    background: rgba(255,255,255,.96); color: #0A1C30;
    border: 1.5px solid rgba(26,145,226,.45);
    box-shadow: 0 10px 26px rgba(10,28,48,.16);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  }
  .fs-mobile-cta svg { width: 16px; height: 16px; flex: 0 0 16px; }
  /* keep the fixed bar from covering the end of the page */
  body { padding-bottom: 78px; }
}

/* ── 5. BUTTONS ─────────────────────────────────────────────────────────
   Chris: "the color is kind of clashing against this blue" and "the text
   should be centered". On dark/blue sections a blue button disappears;
   use the cyan-to-white treatment there instead. */
.btn-primary, .btn-secondary, .btn-cta-glow { text-align: center; }
.fs-on-blue .btn-primary,
.fs-on-blue .btn-cta-glow {
  background: #fff !important; color: #0A1C30 !important;
  box-shadow: 0 12px 30px rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.9) !important;
}
.fs-on-blue .btn-primary:hover,
.fs-on-blue .btn-cta-glow:hover {
  background: #E8F6FF !important; color: #0A1C30 !important;
}
@media (max-width: 560px) {
  .btn-primary, .btn-secondary, .btn-cta-glow {
    font-size: .92rem !important; line-height: 1.3;
    padding-left: 22px !important; padding-right: 22px !important;
  }
}

/* ── 6. READABILITY / TAP TARGETS ───────────────────────────────────────
   Sub-12px labels are unreadable on a phone; 18px-tall footer links miss
   every tap-target guideline. */
/* Tap targets, ALL widths. These live in the shared styles.css and were identical
   on all 17 pages: header nav links computed 20px tall and footer links 17px,
   well under any tap-target guideline. Fixed here rather than in styles.css so
   the change is scoped to the new pages and reversible by deleting the kit. */
.site-footer a, .footer-links a, .footer-inner a,
.nav-links > li > a, .mobile-nav-links a, .mobile-menu-logo {
  display: inline-block;
  padding-top: 8px; padding-bottom: 8px;
  min-height: 34px;
}
.nav-links > li > a { display: inline-flex; align-items: center; }

/* Compact media-player controls sat at 26-29px. Raised to the 32px floor with
   min-* only, so the player's own layout is not otherwise disturbed. */
.control-btn, .mute-btn, .speed-toggle-btn {
  min-width: 32px; min-height: 32px;
}

/* Shared-header defects, confirmed in styles.css and therefore identical on all
   17 pages:
     .header-cta   has a tablet variant `font-size: 0.7rem` = 11.2px, height 31px,
                   under both the 12px text floor and the 34px tap floor
     .logo         is `display: flex` wrapping a 24px-tall img, so the link itself
                   is a 24px tap target. The footer rule above cannot reach it. */
.site-header .header-cta { font-size: 12.5px; min-height: 38px; }
.site-header a.logo, .site-header .logo { min-height: 36px; align-items: center; }

@media (max-width: 900px) {
  .site-footer a, .footer-links a, .footer-inner a {
    padding-left: 2px; padding-right: 2px; min-height: 36px;
  }
}
/* Real content labels were under 12px at EVERY width, not just on phones: measured
   at 1440, .project-tag 10.9px, .narrator-status 10.4px, .ai-label /
   .playlist-label / .expert-region 11.5px. The earlier rule only ran <=560px.
   Deliberately EXCLUDED: .macbook-address and .awsmd-browser-address, which are
   fake browser URL bars inside device mockups and are small on purpose. */
.project-tag, .ai-label, .playlist-label, .tech-badge, .expert-region { font-size: 12px !important; }
.narrator-status { font-size: 12px !important; }

/* Footer link measured 25x34 = 850px2, under the 900px2 area floor. min-height
   alone could not fix it because the constraint was width. */
.site-footer a, .footer-links a, .footer-inner a { min-width: 46px; text-align: center; }

/* ── 7. PORTFOLIO CAROUSEL ──────────────────────────────────────────────
   Stacked cards ran ~5,300px (8 phone screens). One card at a time with
   snap + dots is ~2,600px. Desktop is untouched. */
#pf-dots { display: none; }
@media (max-width: 820px) {
  .awsmd-masonry-grid {
    display: flex !important;
    grid-template-columns: none !important;
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    gap: 14px; margin: 0 -5%; padding: 4px 5% 14px;
    scrollbar-width: none;
  }
  .awsmd-masonry-grid::-webkit-scrollbar { display: none; }
  .awsmd-masonry-grid > .awsmd-card {
    flex: 0 0 87%; scroll-snap-align: center;
    margin-top: 0 !important; height: 460px !important; min-height: 460px !important;
  }
  .awsmd-masonry-grid > .awsmd-card.hidden { display: none !important; }

  #pf-dots { display: flex; justify-content: center; align-items: center; gap: 7px; margin-top: 16px; }
  #pf-dots i {
    width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.25);
    transition: width .25s ease, background .25s ease; display: block;
  }
  #pf-dots i.on { background: #38C3E8; width: 22px; border-radius: 999px; }
}

/* ── 8. BACK TO TOP ─────────────────────────────────────────────────────
   Sits above the mobile CTA bar and the chat bubble so nothing overlaps. */
#fs-to-top {
  position: fixed; right: 22px; bottom: 104px; z-index: 9998;
  width: 46px; height: 46px; border: 0; border-radius: 50%; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, #1A91E2, #38C3E8); color: #fff;
  box-shadow: 0 8px 24px rgba(26,145,226,.35);
  opacity: 0; visibility: hidden; transform: translateY(10px);
  transition: opacity .28s ease, transform .28s ease, visibility .28s ease;
}
#fs-to-top.is-visible { opacity: 1; visibility: visible; transform: translateY(0); }
#fs-to-top:focus-visible { outline: 3px solid #0A1C30; outline-offset: 3px; }
@media (max-width: 820px) { #fs-to-top { bottom: 84px; right: 16px; width: 42px; height: 42px; } }

/* ── 9. IMAGE DIMENSION GUARDS ──────────────────────────────────────────
   width/height attributes were added to 372 images for CLS. They act as
   presentational hints, so when author CSS pins only ONE axis the attribute
   supplies the other and the image distorts. Measured examples:
     services.html  style="width:100%" (no height)  -> height 304 became 1024
     mobile logo    CSS height:40px  (no width)     -> width 246.6 became 300
   These guards are deliberately narrow — a blanket `img { height: auto }`
   would override legitimate CSS heights (e.g. the 120px award badges). */
img[style*="width:100%"]:not([style*="height"]),
img[style*="width: 100%"]:not([style*="height"]) { height: auto; }
.mobile-menu-logo img,
.logo img:not([style*="height"]) { width: auto; }

/* ── 10. MOTION ─────────────────────────────────────────────────────────*/
@media (prefers-reduced-motion: reduce) {
  #fs-to-top, #pf-dots i, .awsmd-masonry-grid { transition: none; scroll-behavior: auto; }
}

/* ── 11. REVEAL FLOORS ──────────────────────────────────────────────────
   `.reveal` content is gated behind a JS IntersectionObserver. These two
   floors mean it can never be permanently invisible to a real person:
   users who ask for reduced motion, and users without JS, see it outright.
   (The JS sweep in the kit handles the observer-never-fires case.) */
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* ── 12. HERO BACKGROUND VIDEO ──────────────────────────────────────────
   Decorative motion layer behind a hero. Sits at z-index 0, under the
   existing float/marquee decoration and well under `.hero .container`
   (z-index 2), so it can never intercept a click or cover the copy.

   Clip choice is NOT a taste call — it was measured. Compositing each
   clip's 95th-percentile-brightest pixel over each hero's background and
   scoring the result against the heading colour: the two white-background
   clips (brand-cube, brand-identity) fall to 4.1:1 on the near-black
   heroes at 50% and 6.7:1 at 35%, while the navy clips hold 15:1+. So a
   light clip only ever goes on the light hero. Per-page opacity is set
   inline from that table; every shipped pairing measures >= 13:1.

   `--fs-hero-video-op` keeps the tuned value in one place per page. */
.fs-hero-video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0; pointer-events: none;
  opacity: var(--fs-hero-video-op, 0.3);
}
/* Scrim: guards the copy against the brightest frame of the clip rather
   than the average one, and keeps the bottom edge from fighting the CTAs. */
.fs-hero-scrim {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    linear-gradient(180deg, var(--fs-scrim-top, rgba(10,15,26,.55)) 0%,
                            var(--fs-scrim-mid, rgba(10,15,26,.30)) 45%,
                            var(--fs-scrim-bot, rgba(10,15,26,.70)) 100%);
}
/* The clip is decoration. Respecting the motion preference leaves the
   poster frame in place, so the composition does not change — it stops. */
@media (prefers-reduced-motion: reduce) {
  .fs-hero-video { display: none; }
}
/* On a phone the hero is mostly type. Pull the clip back further so it
   reads as texture, and never let it become the loudest thing on screen. */
@media (max-width: 640px) {
  .fs-hero-video { opacity: calc(var(--fs-hero-video-op, 0.3) * 0.6); }
}

/* ── 13. HEADER PHONE STYLING GUARANTEES ─────────────────────────────────
   The header phone SVG has no width/height attributes and would scale to
   fill its container, causing layout breaks on pages that don't have
   individual styles for it. */
.header-phone {
  display: inline-flex !important;
  align-items: center !important;
  gap: 10px !important;
  text-decoration: none !important;
  color: var(--text-dark, #1E2328) !important;
  margin-right: 12px !important;
}
.fs-ph-badge {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  width: 34px !important;
  height: 34px !important;
  background: rgba(26, 145, 226, 0.08) !important;
  color: var(--accent, #1A91E2) !important;
  border-radius: 50% !important;
  flex-shrink: 0 !important;
}
.fs-ph-badge svg {
  width: 16px !important;
  height: 16px !important;
  display: block !important;
  stroke-width: 2.3 !important;
}
.header-phone {
  /* Ported verbatim from the live homepage (fivensonstudios.com), where this
     pill treatment already shipped. The kit still carried the older flat
     version, so the service pages rendered a bare icon+text while the homepage
     showed a bordered pill — the mismatch Chris spotted. */
  display: inline-flex; align-items: center; gap: 10px;
  padding: 6px 18px 6px 6px;
  border-radius: var(--radius-pill, 999px); text-decoration: none;
  background: linear-gradient(135deg, rgba(26,145,226,.07), rgba(56,195,232,.07));
  border: 1.5px solid rgba(26,145,226,.28);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 2px 10px rgba(26,145,226,.10);
  transition: transform .28s cubic-bezier(.16,1,.3,1), box-shadow .28s ease,
              border-color .28s ease, background .28s ease;
}
.header-phone:hover {
  transform: translateY(-2px); background: #fff;
  border-color: var(--accent, #1A91E2);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 8px 22px rgba(26,145,226,.26);
}
.header-phone:focus-visible { outline: 3px solid var(--accent, #1A91E2); outline-offset: 3px; }
.fs-ph-badge {
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; color: #fff;
  background: linear-gradient(135deg, var(--accent, #1A91E2) 0%, var(--accent-cyan, #38C3E8) 100%);
  box-shadow: 0 3px 10px rgba(26,145,226,.45);
  animation: fsPhRing 2.8s ease-out infinite;
}
.fs-ph-badge svg { width: 16px; height: 16px; }
.header-phone:hover .fs-ph-badge { transform: scale(1.06); }
.fs-ph-text { display: flex; flex-direction: column; line-height: 1.12; text-align: left; }
.fs-ph-kicker {
  /* Live uses .58rem, which measures 9.3px — below the 12px floor the kit
     enforces everywhere else, so this one value is deliberately larger than
     live. Everything else in this block matches production exactly. */
  font-size: 0.75rem; font-weight: 800; letter-spacing: .11em;
  text-transform: uppercase; color: var(--accent, #1A91E2);
}
.fs-ph-num {
  font-family: var(--font-heading, inherit); font-size: .98rem; font-weight: 800;
  color: #0A1C30; white-space: nowrap;
}
@keyframes fsPhRing {
  0%, 70%, 100% { box-shadow: 0 3px 10px rgba(26,145,226,.45), 0 0 0 0 rgba(26,145,226,.34); }
  35%           { box-shadow: 0 3px 10px rgba(26,145,226,.45), 0 0 0 8px rgba(26,145,226,0); }
}
@media (prefers-reduced-motion: reduce) { .fs-ph-badge { animation: none; } }
/* !important is REQUIRED here, not sloppiness. The "styling guarantees" block
   above sets `.header-phone { display: inline-flex !important }`, which beat a
   plain `display: none` and left the full desktop phone pill (189px) in the
   header on mobile. Combined with the compact call button and the menu button,
   .header-right measured 305px running to x=460 on a 375px screen — and since
   an ancestor clips rather than scrolls, the menu button and CTA were pushed
   off-screen and became UNREACHABLE, not merely ugly. */
@media (max-width: 1180px) { .header-phone { display: none !important; } }


/* ── 13. AT-A-GLANCE ANSWER CARDS ───────────────────────────────────────
   Promoted out of index.html so every service page can answer the buyer's
   first four questions (what / who / cost / how to start) directly under the
   hero, instead of making them scroll past the positioning copy to find out.

   Measured motivation: on website-design.html the deliverables sat 8.3 screens
   down and the price answer was an FAQ accordion at 19.5 screens, while the
   homepage answers all four inside screen 2. */
.fv-card {
  position: relative; background: rgba(255,255,255,0.85);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(26,145,226,0.12); border-radius: 20px;
  box-shadow: 0 10px 28px rgba(26,145,226,0.05); overflow: hidden;
  transition: transform .45s cubic-bezier(.16,1,.3,1),
              box-shadow .45s cubic-bezier(.16,1,.3,1), border-color .45s ease;
}
.fv-card::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-cyan) 100%);
  transform: scaleX(0); transform-origin: left;
  transition: transform .5s cubic-bezier(.16,1,.3,1); z-index: 3;
}
.fv-card:hover::before { transform: scaleX(1); }
.fv-card:hover {
  transform: translateY(-8px); border-color: rgba(26,145,226,0.35);
  box-shadow: 0 26px 52px rgba(26,145,226,0.14);
}
.fv-card::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(420px circle at 50% 0%, rgba(56,195,232,0.10), transparent 65%);
  opacity: 0; transition: opacity .45s ease;
}
.fv-card:hover::after { opacity: 1; }
.fv-card > * { position: relative; z-index: 2; }

.fv-glance { padding: 22px 24px; }
.fv-glance .fv-glance-label {
  font-family: var(--font-heading); font-size: .78rem; font-weight: 800;
  letter-spacing: .09em; text-transform: uppercase; color: var(--accent);
  margin-bottom: 8px; display: flex; align-items: center; gap: 8px;
}
.fv-glance .fv-glance-label::before {
  content: ""; width: 18px; height: 2px; border-radius: 2px;
  background: linear-gradient(90deg, var(--accent), var(--accent-cyan));
  transition: width .45s cubic-bezier(.16,1,.3,1);
}
.fv-glance:hover .fv-glance-label::before { width: 34px; }
.fv-glance p {
  margin: 0; color: var(--text-dark); font-size: 1.02rem;
  line-height: 1.55; font-weight: 600;
}
/* The answer strip itself — auto-fit so it is 4-up on desktop and stacks
   cleanly on a phone without a separate breakpoint. */
.fv-glance-grid {
  /* 210px let five cards sit side by side at 224px each, which measured 17
     characters per line — a third of the 45-75 comfortable reading measure, so
     the copy read as stacked word-fragments. 260px forces 4-up at ~285px. */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 20px;
}
@media (prefers-reduced-motion: reduce) {
  .fv-card, .fv-card::before, .fv-card::after { transition: none !important; }
}

/* ── 14. MID-PAGE CTA BAND ──────────────────────────────────────────────
   Measured on website-design: only 2 of 17 sections carried a conversion
   point (the hero and the closing section), leaving a 20.6-screen stretch
   where a convinced reader had nowhere to act. This is the band that goes
   between long content runs. */
.fs-midcta {
  background: linear-gradient(135deg, rgba(26,145,226,.06), rgba(56,195,232,.06));
  border-top: 1px solid rgba(26,145,226,.12);
  border-bottom: 1px solid rgba(26,145,226,.12);
  padding: 44px 5%;
}
.fs-midcta-inner {
  max-width: 1100px; margin: 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 28px; flex-wrap: wrap;
}
.fs-midcta-copy { flex: 1 1 380px; }
.fs-midcta-copy p:first-child {
  font-family: var(--font-heading, inherit); font-weight: 900;
  text-transform: uppercase; letter-spacing: -0.01em;
  font-size: clamp(1.25rem, 2.3vw, 1.7rem); line-height: 1.15;
  color: var(--text-dark, #1E2328); margin: 0 0 6px;
}
.fs-midcta-copy p:last-child {
  margin: 0; color: var(--text-mid, #5A6A7A); font-size: 1.02rem; line-height: 1.5;
}
.fs-midcta-actions { display: flex; gap: 12px; flex-wrap: wrap; }
@media (max-width: 640px) {
  .fs-midcta { padding: 34px 5%; }
  .fs-midcta-inner { flex-direction: column; align-items: flex-start; gap: 18px; }
  /* `flex: 1 1 380px` above sets flex-BASIS, which follows the container's main
     axis. In the desktop row that 380px is a sensible minimum WIDTH; the moment
     this becomes a column it silently turns into a 380px minimum HEIGHT. The
     copy is ~102px of text, so it rendered with ~278px of dead space between
     the headline and the buttons — the empty band Chris flagged. */
  .fs-midcta-copy { flex: 0 0 auto; }
  .fs-midcta-actions { width: 100%; }
  .fs-midcta-actions > * { flex: 1 1 100%; text-align: center; }
}

/* ── 15. LISTEN TO THIS PAGE ────────────────────────────────────────────
   A read-aloud control so the copy can be heard end to end and judged for
   flow rather than read on screen. Uses the browser's own speechSynthesis —
   no network, no key, no third-party player. It reads the page's headings
   and body copy in document order, skipping nav, footer and decoration. */
.fs-listen {
  /* back-to-top is 46px tall at bottom:104px, so it occupies up to 150px.
     Measured overlap at bottom:92px — this clears it with a 12px gap. */
  position: fixed; right: 22px; bottom: 162px; z-index: 60;
  display: inline-flex; align-items: center; gap: 9px;
  padding: 11px 18px 11px 13px; border-radius: 999px; cursor: pointer;
  font-family: var(--font-heading, inherit); font-weight: 800; font-size: .82rem;
  letter-spacing: .04em; text-transform: uppercase;
  color: #0A1C30; background: #fff;
  border: 1.5px solid rgba(26,145,226,.30);
  box-shadow: 0 6px 20px rgba(26,145,226,.20);
  transition: transform .25s cubic-bezier(.16,1,.3,1), box-shadow .25s ease, border-color .25s ease;
}
.fs-listen:hover { transform: translateY(-2px); border-color: var(--accent, #1A91E2);
  box-shadow: 0 10px 26px rgba(26,145,226,.30); }
.fs-listen:focus-visible { outline: 3px solid var(--accent, #1A91E2); outline-offset: 3px; }
.fs-listen svg { width: 15px; height: 15px; flex: 0 0 15px; }
.fs-listen .fs-listen-dot {
  width: 26px; height: 26px; flex: 0 0 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; color: #fff;
  background: linear-gradient(135deg, var(--accent, #1A91E2), var(--accent-cyan, #38C3E8));
}
.fs-listen[data-state="playing"] { border-color: var(--accent, #1A91E2); }
.fs-listen[data-state="playing"] .fs-listen-dot { animation: fsListenPulse 1.6s ease-out infinite; }
@keyframes fsListenPulse {
  0%,100% { box-shadow: 0 0 0 0 rgba(26,145,226,.40); }
  50%     { box-shadow: 0 0 0 7px rgba(26,145,226,0); }
}
@media (prefers-reduced-motion: reduce) {
  .fs-listen, .fs-listen[data-state="playing"] .fs-listen-dot { transition: none; animation: none; }
}
/* Sits above the back-to-top button; on a phone drop it beside, not over. */
@media (max-width: 640px) { .fs-listen { right: 14px; bottom: 150px; font-size: .75rem; padding: 9px 14px 9px 10px; } }

/* ── 16. SCROLLING REVIEW WALL ──────────────────────────────────────────
   Chris: "let's add some scrolling reviews so there's more than just these
   three. I have a bunch of scrolling reviews." Two rows drifting in opposite
   directions, each duplicated once so the loop has no visible seam.

   Pausing on hover matters: a review the reader is mid-sentence through must
   not slide away. Reduced-motion stops the drift entirely and lets the rows
   scroll manually instead. */
.fs-revwall { overflow: hidden; padding: 8px 0; }
.fs-revwall + .fs-revwall { margin-top: 18px; }
.fs-revwall-track {
  display: flex; gap: 20px; width: max-content;
  animation: fsRevScroll 90s linear infinite;
}
.fs-revwall:nth-of-type(even) .fs-revwall-track { animation-direction: reverse; }
.fs-revwall:hover .fs-revwall-track,
.fs-revwall:focus-within .fs-revwall-track { animation-play-state: paused; }
@keyframes fsRevScroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
.fs-rev-card {
  flex: 0 0 340px; margin: 0; padding: 24px 26px;
  background: #fff; border: 1px solid rgba(26,145,226,.14); border-radius: 18px;
  box-shadow: 0 8px 26px rgba(26,145,226,.06);
  display: flex; flex-direction: column;
}
.fs-rev-stars { color: #FBBF24; font-size: 1rem; letter-spacing: 2px; }
.fs-rev-card blockquote {
  margin: 12px 0 16px; padding: 0; border: 0;
  color: var(--text-dark, #1E2328); font-size: .99rem; line-height: 1.6;
}
.fs-rev-card figcaption {
  margin-top: auto; font-weight: 800; color: var(--text-mid, #5A6A7A); font-size: .92rem;
}
/* Fade the ends so cards enter and leave instead of being chopped off. */
.fs-revwall-mask {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .fs-revwall-track { animation: none; }
  .fs-revwall { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}
@media (max-width: 640px) {
  .fs-rev-card { flex-basis: 290px; padding: 20px 22px; }
  .fs-revwall-track { animation-duration: 65s; }
}

/* ── 17. EMBEDDED INTAKE FORM ───────────────────────────────────────────
   The hosted form is cross-origin and cannot report its height (its only
   postMessage is React's scheduler), so the frame height has to be set here.

   Two crops are applied via the wrapper:
     • top    — hides the form's own dark "Fivenson Studios" brand bar, which
                would be a second brand header on our own page
     • bottom — stops before the form's dark footer strip, which repeats the
                phone number already shown in the NAP block underneath

   Height is per-breakpoint because the form's four option cards sit 2-up on
   desktop and stack on a phone, so its natural height roughly doubles.
   Scrolling stays enabled: this is a MULTI-STEP form and a later step must
   never be unreachable if it runs taller than the box. */
.fs-formframe {
  overflow: hidden; margin: 0 auto;
  background: #fff; border: 1px solid rgba(26,145,226,0.14);
  border-radius: 22px; box-shadow: 0 14px 40px rgba(26,145,226,0.08);
  height: 545px;
}
.fs-formframe iframe {
  width: 100%; height: 1076px; border: 0; display: block;
  background: #fff; margin-top: -96px;
}
@media (max-width: 1023px) { .fs-formframe { height: 700px; } }
@media (max-width: 720px)  { .fs-formframe { height: 1020px; } }
@media (max-width: 480px)  { .fs-formframe { height: 1120px; } }

/* ── 18. NATIVE INTAKE FORM ─────────────────────────────────────────────
   Replaces the cross-origin iframe. Posts straight to Supervisor's public
   forms API, so submissions land in the same place the contact page's do and
   fire the same operator SMS + email chain — with none of the iframe's
   problems (no magic-number height crops, no duplicate brand bar, no
   scrollbar, and it inherits the page's own type and colour). */
.fs-form { display: grid; gap: 18px; }
.fs-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.fs-form-row[data-cols="3"] { grid-template-columns: repeat(3, 1fr); }
/* Three name/email/phone fields get too narrow to read their own placeholders
   below ~860px, so drop to 2-up before the single-column break. */
@media (max-width: 860px) { .fs-form-row[data-cols="3"] { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) { .fs-form-row, .fs-form-row[data-cols="3"] { grid-template-columns: 1fr; } }
.fs-field { display: flex; flex-direction: column; gap: 7px; }
.fs-field label {
  font-family: var(--font-heading, inherit); font-size: .82rem; font-weight: 800;
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-mid, #5A6A7A);
}
.fs-field .req { color: var(--accent, #1A91E2); }
.fs-field input, .fs-field select, .fs-field textarea {
  width: 100%; padding: 14px 16px; font: inherit; font-size: 1rem;
  color: var(--text-dark, #1E2328); background: #fff;
  border: 1.5px solid rgba(26,145,226,.22); border-radius: 12px;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.fs-field textarea { min-height: 132px; resize: vertical; }
.fs-field input:focus, .fs-field select:focus, .fs-field textarea:focus {
  outline: none; border-color: var(--accent, #1A91E2);
  box-shadow: 0 0 0 4px rgba(26,145,226,.12);
}
.fs-field[data-invalid] input, .fs-field[data-invalid] select, .fs-field[data-invalid] textarea {
  border-color: #D64545; box-shadow: 0 0 0 4px rgba(214,69,69,.10);
}
.fs-field-err { color: #C13434; font-size: .86rem; font-weight: 600; min-height: 0; }
.fs-consent {
  display: flex; gap: 11px; align-items: flex-start;
  font-size: .93rem; line-height: 1.5; color: var(--text-mid, #5A6A7A);
}
.fs-consent input { width: 19px; height: 19px; flex: 0 0 19px; margin-top: 2px; accent-color: var(--accent, #1A91E2); }
/* ── Multi-select service picker ──────────────────────────────────────────
   A real dropdown (Chris asked for a dropdown) that holds CHECKBOXES, so more
   than one service can be picked. A native <select multiple> was rejected: on
   mobile it renders as an unusable scrolling box, and on desktop it requires
   ctrl/cmd-click, which most visitors do not know. */
.fs-multi { position: relative; }
.fs-multi-btn {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 14px 16px; border-radius: 12px; cursor: pointer; text-align: left;
  border: 1.5px solid rgba(26,54,81,.16); background: #fff;
  font: inherit; font-size: 1rem; color: var(--text-dark, #1A3651);
}
.fs-multi-btn:hover { border-color: rgba(26,145,226,.45); }
.fs-multi-btn:focus-visible,
.fs-multi[data-open] .fs-multi-btn {
  outline: none; border-color: var(--accent, #1A91E2); box-shadow: 0 0 0 4px rgba(26,145,226,.12);
}
/* Placeholder state reads as unfilled, not as a chosen value. */
.fs-multi-text[data-empty] { color: #8A97A4; }
.fs-multi-caret { color: var(--accent, #1A91E2); font-size: .8rem; transition: transform .18s ease; }
.fs-multi[data-open] .fs-multi-caret { transform: rotate(180deg); }
.fs-multi-panel {
  position: absolute; z-index: 40; top: calc(100% + 6px); left: 0; right: 0;
  max-height: 264px; overflow-y: auto; padding: 6px;
  background: #fff; border: 1.5px solid rgba(26,145,226,.28); border-radius: 12px;
  box-shadow: 0 18px 44px rgba(26,54,81,.16);
}
/* These options ARE <label> elements sitting inside .fs-field, so they inherit
   the field-label treatment (uppercase, 800 weight, wide tracking). That is
   right for "PHONE *" and very wrong for a list of service names — it rendered
   "SEO & AI SEARCH (AEO/GEO)" as a shouting headline. Reset it explicitly. */
.fs-form .fs-multi-opt {
  display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 8px;
  cursor: pointer; color: var(--text-dark, #1A3651);
  font-family: var(--font-body, inherit);
  font-size: .96rem; font-weight: 500; line-height: 1.35;
  text-transform: none; letter-spacing: normal; margin: 0;
}
.fs-multi-opt:hover { background: rgba(26,145,226,.07); }
.fs-multi-opt input { width: 17px; height: 17px; flex: 0 0 17px; accent-color: var(--accent, #1A91E2); }
/* The picker sits in a grid row; on mobile the panel would otherwise be
   clipped by the card's rounded overflow. */
@media (max-width: 640px) { .fs-multi-panel { max-height: 232px; } }
.fs-field label .opt { font-weight: 600; text-transform: none; letter-spacing: 0; opacity: .7; }

/* Consent shown as fine print rather than a required checkbox. Small, but not
   below the kit's 12px legibility floor. */
.fs-form-fine {
  margin: 0; font-size: .8rem; line-height: 1.55; color: var(--text-mid, #5A6A7A); opacity: .85;
}
.fs-form-fine a { color: var(--accent, #1A91E2); font-weight: 700; }
.fs-form-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.fs-form button[type=submit] { min-width: 210px; }
.fs-form button[type=submit][disabled] { opacity: .6; cursor: not-allowed; }
.fs-form-note { color: var(--text-mid, #5A6A7A); font-size: .92rem; }
/* Result banner — success and failure both have to be visible, because a form
   that silently does nothing is how leads get lost. */
.fs-form-result { border-radius: 12px; padding: 16px 18px; font-weight: 600; line-height: 1.55; }
.fs-form-result[data-kind=ok]  { background: rgba(34,150,83,.09); border: 1px solid rgba(34,150,83,.30); color: #17693B; }
.fs-form-result[data-kind=err] { background: rgba(214,69,69,.07); border: 1px solid rgba(214,69,69,.30); color: #A72D2D; }

/* ── 19. MOBILE POLISH ────────────────────────────────────────────────────
   Measured fixes from a 375/390/414/768 audit, not guesses. This block loads
   after styles.css, so it wins without needing !important. */

/* Apple and WCAG both put the minimum touch target at ~44px. Measured on a
   375px screen these were the controls a thumb could miss. */
@media (max-width: 900px) {
  .mobile-menu-btn {
    min-width: 44px; min-height: 44px;      /* was 40x32 */
    align-items: center; justify-content: center;
  }
  .portfolio-filter-btn { min-height: 40px; }  /* was 35 */
}

/* 11.5px — under the 12px floor the rest of the kit holds itself to. These are
   the portfolio card URL bars, which are the proof a project is real, so they
   need to stay readable rather than being decorative chrome. */
.awsmd-browser-address { font-size: 12px; }

/* ── 20. MOBILE MENU OVERLAY ──────────────────────────────────────────────
   Measured at 375x667 with the menu open, the overlay was broken three ways
   at once and every one of them hid a real control:

     .mobile-menu-inner was 769px tall in a 667px viewport with
     overflow:visible — so "Call us" sat at y=718, entirely off-screen and
     unreachable, with nothing to scroll.

     The first item ("Our Services", top=50) sat UNDER the sticky header pill,
     because the header is z-index 1000 and the overlay is 999.

     .fs-mobile-cta (z 9997) and #fs-to-top (z 9998) both outrank the overlay,
     so the fixed bottom bar and the back-to-top button floated on top of the
     menu and covered its own two CTAs.

   The z-index values are deliberately left alone — the header SHOULD stay
   above the overlay so the close button remains reachable. What changes is
   that the menu now starts below it. */
@media (max-width: 900px) {
  .mobile-menu-overlay {
    padding-top: 90px;               /* clears the sticky header pill (bottom 86) */
    padding-bottom: 20px;
    overflow-y: auto;                /* never trap a control off-screen again */
    -webkit-overflow-scrolling: touch;
    /* The overlay centres its content vertically. Once the content is nearly
       as tall as the viewport that centring EATS the padding-top — measured on
       a 375x667 screen the first item still landed 8px under the header. Top
       alignment makes the padding mean what it says. */
    align-items: flex-start;
  }
  /* The overlay carries its OWN logo, which lands directly behind the sticky
     header's logo — a duplicate nobody can see. Hiding it buys back 84px,
     most of what the menu was overflowing by. */
  .mobile-menu-overlay .mobile-menu-logo { display: none; }
  /* 32px type on a 40px gap needed 769px to show 8 items in a 667px viewport.
     Tightened so the links AND both CTAs fit one screen. */
  .mobile-menu-inner { gap: 20px !important; }
  /* The links are LI children of .mobile-nav-links, NOT direct children of
     .mobile-menu-inner — a `> a` selector silently matches nothing here. */
  .mobile-menu-overlay .mobile-nav-links a { font-size: 1.5rem; }
}
/* A 667px-tall screen (iPhone SE/8) is the tightest case still in real use.
   At 1.5rem the last CTA finished 5px past the fold — it scrolled, so nothing
   was unreachable, but a menu that needs scrolling to reach "Call us" is a
   menu people do not call from. */
@media (max-width: 900px) and (max-height: 700px) {
  .mobile-menu-overlay .mobile-nav-links a { font-size: 1.3rem; }
  .mobile-menu-inner { gap: 14px !important; }
}

/* The fixed furniture must get out of the way while the menu is open,
   otherwise it covers the menu's own buttons. */
body:has(.mobile-menu-overlay.mobile-open) .fs-mobile-cta,
body:has(.mobile-menu-overlay.mobile-open) #fs-to-top {
  display: none !important;
}

/* ── 21. PERSISTENT TESTIMONIAL TICKER ────────────────────────────────────
   Chris: "is it possible to just have like a little testimonial just showing
   here that's kind of like scrolling with different ones that's consistent on
   the page?" — pointing at the lower-left corner.

   Hidden below 900px ON PURPOSE. The mobile CTA bar is already fixed to the
   bottom with body padding reserved for it, so a second fixed element in that
   corner would sit on top of the Call button. Social proof is worth less than
   a working phone number. */
.fs-tstrip {
  position: fixed; left: 20px; bottom: 20px; z-index: 9990;
  width: 320px; max-width: calc(100vw - 40px);
  background: rgba(255,255,255,.97);
  border: 1px solid rgba(26,145,226,.22);
  border-radius: 14px;
  box-shadow: 0 16px 40px rgba(10,28,48,.16);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  padding: 14px 16px 13px;
  opacity: 0; transform: translateY(10px);
  transition: opacity .45s ease, transform .45s cubic-bezier(.16,1,.3,1);
  pointer-events: none;
}
.fs-tstrip[data-shown] { opacity: 1; transform: translateY(0); pointer-events: auto; }
.fs-tstrip-stars { color: #F5A623; font-size: .82rem; letter-spacing: 1px; line-height: 1; }
.fs-tstrip-quote {
  margin: 7px 0 8px; font-size: .88rem; line-height: 1.5;
  color: var(--text-dark, #1E2328);
  transition: opacity .3s ease;
}
.fs-tstrip-name {
  font-family: var(--font-heading, inherit);
  font-size: .74rem; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: var(--accent, #1A91E2);
}
.fs-tstrip[data-fading] .fs-tstrip-quote,
.fs-tstrip[data-fading] .fs-tstrip-name { opacity: 0; }
.fs-tstrip-close {
  position: absolute; top: 6px; right: 8px;
  background: none; border: 0; cursor: pointer; line-height: 1;
  font-size: 1rem; color: #9AA7B4; padding: 4px;
}
.fs-tstrip-close:hover { color: #52606D; }
@media (max-width: 900px) { .fs-tstrip { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .fs-tstrip, .fs-tstrip-quote, .fs-tstrip-name { transition: none; }
}
