/* === AOS v2.3.4 vendored ============================================
   Inlined verbatim from src/vendor/aos/aos.css. Inlining (rather than
   `@import url(...)`) keeps style.css as a single CSS request and
   sidesteps any asset-hashing rewrite concerns on the @import URL.
   The src/vendor/aos/aos.css file is still committed for traceability
   but is not loaded at runtime.
   ==================================================================== */
[data-aos][data-aos][data-aos-duration="50"],body[data-aos-duration="50"] [data-aos]{transition-duration:50ms}[data-aos][data-aos][data-aos-delay="50"],body[data-aos-delay="50"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="50"].aos-animate,body[data-aos-delay="50"] [data-aos].aos-animate{transition-delay:50ms}[data-aos][data-aos][data-aos-duration="100"],body[data-aos-duration="100"] [data-aos]{transition-duration:.1s}[data-aos][data-aos][data-aos-delay="100"],body[data-aos-delay="100"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="100"].aos-animate,body[data-aos-delay="100"] [data-aos].aos-animate{transition-delay:.1s}[data-aos][data-aos][data-aos-duration="150"],body[data-aos-duration="150"] [data-aos]{transition-duration:.15s}[data-aos][data-aos][data-aos-delay="150"],body[data-aos-delay="150"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="150"].aos-animate,body[data-aos-delay="150"] [data-aos].aos-animate{transition-delay:.15s}[data-aos][data-aos][data-aos-duration="200"],body[data-aos-duration="200"] [data-aos]{transition-duration:.2s}[data-aos][data-aos][data-aos-delay="200"],body[data-aos-delay="200"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="200"].aos-animate,body[data-aos-delay="200"] [data-aos].aos-animate{transition-delay:.2s}[data-aos][data-aos][data-aos-duration="250"],body[data-aos-duration="250"] [data-aos]{transition-duration:.25s}[data-aos][data-aos][data-aos-delay="250"],body[data-aos-delay="250"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="250"].aos-animate,body[data-aos-delay="250"] [data-aos].aos-animate{transition-delay:.25s}[data-aos][data-aos][data-aos-duration="300"],body[data-aos-duration="300"] [data-aos]{transition-duration:.3s}[data-aos][data-aos][data-aos-delay="300"],body[data-aos-delay="300"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="300"].aos-animate,body[data-aos-delay="300"] [data-aos].aos-animate{transition-delay:.3s}[data-aos][data-aos][data-aos-duration="350"],body[data-aos-duration="350"] [data-aos]{transition-duration:.35s}[data-aos][data-aos][data-aos-delay="350"],body[data-aos-delay="350"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="350"].aos-animate,body[data-aos-delay="350"] [data-aos].aos-animate{transition-delay:.35s}[data-aos][data-aos][data-aos-duration="400"],body[data-aos-duration="400"] [data-aos]{transition-duration:.4s}[data-aos][data-aos][data-aos-delay="400"],body[data-aos-delay="400"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="400"].aos-animate,body[data-aos-delay="400"] [data-aos].aos-animate{transition-delay:.4s}[data-aos][data-aos][data-aos-duration="450"],body[data-aos-duration="450"] [data-aos]{transition-duration:.45s}[data-aos][data-aos][data-aos-delay="450"],body[data-aos-delay="450"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="450"].aos-animate,body[data-aos-delay="450"] [data-aos].aos-animate{transition-delay:.45s}[data-aos][data-aos][data-aos-duration="500"],body[data-aos-duration="500"] [data-aos]{transition-duration:.5s}[data-aos][data-aos][data-aos-delay="500"],body[data-aos-delay="500"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="500"].aos-animate,body[data-aos-delay="500"] [data-aos].aos-animate{transition-delay:.5s}[data-aos][data-aos][data-aos-duration="550"],body[data-aos-duration="550"] [data-aos]{transition-duration:.55s}[data-aos][data-aos][data-aos-delay="550"],body[data-aos-delay="550"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="550"].aos-animate,body[data-aos-delay="550"] [data-aos].aos-animate{transition-delay:.55s}[data-aos][data-aos][data-aos-duration="600"],body[data-aos-duration="600"] [data-aos]{transition-duration:.6s}[data-aos][data-aos][data-aos-delay="600"],body[data-aos-delay="600"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="600"].aos-animate,body[data-aos-delay="600"] [data-aos].aos-animate{transition-delay:.6s}[data-aos][data-aos][data-aos-duration="650"],body[data-aos-duration="650"] [data-aos]{transition-duration:.65s}[data-aos][data-aos][data-aos-delay="650"],body[data-aos-delay="650"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="650"].aos-animate,body[data-aos-delay="650"] [data-aos].aos-animate{transition-delay:.65s}[data-aos][data-aos][data-aos-duration="700"],body[data-aos-duration="700"] [data-aos]{transition-duration:.7s}[data-aos][data-aos][data-aos-delay="700"],body[data-aos-delay="700"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="700"].aos-animate,body[data-aos-delay="700"] [data-aos].aos-animate{transition-delay:.7s}[data-aos][data-aos][data-aos-duration="750"],body[data-aos-duration="750"] [data-aos]{transition-duration:.75s}[data-aos][data-aos][data-aos-delay="750"],body[data-aos-delay="750"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="750"].aos-animate,body[data-aos-delay="750"] [data-aos].aos-animate{transition-delay:.75s}[data-aos][data-aos][data-aos-duration="800"],body[data-aos-duration="800"] [data-aos]{transition-duration:.8s}[data-aos][data-aos][data-aos-delay="800"],body[data-aos-delay="800"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="800"].aos-animate,body[data-aos-delay="800"] [data-aos].aos-animate{transition-delay:.8s}[data-aos][data-aos][data-aos-duration="850"],body[data-aos-duration="850"] [data-aos]{transition-duration:.85s}[data-aos][data-aos][data-aos-delay="850"],body[data-aos-delay="850"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="850"].aos-animate,body[data-aos-delay="850"] [data-aos].aos-animate{transition-delay:.85s}[data-aos][data-aos][data-aos-duration="900"],body[data-aos-duration="900"] [data-aos]{transition-duration:.9s}[data-aos][data-aos][data-aos-delay="900"],body[data-aos-delay="900"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="900"].aos-animate,body[data-aos-delay="900"] [data-aos].aos-animate{transition-delay:.9s}[data-aos][data-aos][data-aos-duration="950"],body[data-aos-duration="950"] [data-aos]{transition-duration:.95s}[data-aos][data-aos][data-aos-delay="950"],body[data-aos-delay="950"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="950"].aos-animate,body[data-aos-delay="950"] [data-aos].aos-animate{transition-delay:.95s}[data-aos][data-aos][data-aos-duration="1000"],body[data-aos-duration="1000"] [data-aos]{transition-duration:1s}[data-aos][data-aos][data-aos-delay="1000"],body[data-aos-delay="1000"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1000"].aos-animate,body[data-aos-delay="1000"] [data-aos].aos-animate{transition-delay:1s}[data-aos][data-aos][data-aos-duration="1050"],body[data-aos-duration="1050"] [data-aos]{transition-duration:1.05s}[data-aos][data-aos][data-aos-delay="1050"],body[data-aos-delay="1050"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1050"].aos-animate,body[data-aos-delay="1050"] [data-aos].aos-animate{transition-delay:1.05s}[data-aos][data-aos][data-aos-duration="1100"],body[data-aos-duration="1100"] [data-aos]{transition-duration:1.1s}[data-aos][data-aos][data-aos-delay="1100"],body[data-aos-delay="1100"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1100"].aos-animate,body[data-aos-delay="1100"] [data-aos].aos-animate{transition-delay:1.1s}[data-aos][data-aos][data-aos-duration="1150"],body[data-aos-duration="1150"] [data-aos]{transition-duration:1.15s}[data-aos][data-aos][data-aos-delay="1150"],body[data-aos-delay="1150"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1150"].aos-animate,body[data-aos-delay="1150"] [data-aos].aos-animate{transition-delay:1.15s}[data-aos][data-aos][data-aos-duration="1200"],body[data-aos-duration="1200"] [data-aos]{transition-duration:1.2s}[data-aos][data-aos][data-aos-delay="1200"],body[data-aos-delay="1200"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1200"].aos-animate,body[data-aos-delay="1200"] [data-aos].aos-animate{transition-delay:1.2s}[data-aos][data-aos][data-aos-duration="1250"],body[data-aos-duration="1250"] [data-aos]{transition-duration:1.25s}[data-aos][data-aos][data-aos-delay="1250"],body[data-aos-delay="1250"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1250"].aos-animate,body[data-aos-delay="1250"] [data-aos].aos-animate{transition-delay:1.25s}[data-aos][data-aos][data-aos-duration="1300"],body[data-aos-duration="1300"] [data-aos]{transition-duration:1.3s}[data-aos][data-aos][data-aos-delay="1300"],body[data-aos-delay="1300"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1300"].aos-animate,body[data-aos-delay="1300"] [data-aos].aos-animate{transition-delay:1.3s}[data-aos][data-aos][data-aos-duration="1350"],body[data-aos-duration="1350"] [data-aos]{transition-duration:1.35s}[data-aos][data-aos][data-aos-delay="1350"],body[data-aos-delay="1350"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1350"].aos-animate,body[data-aos-delay="1350"] [data-aos].aos-animate{transition-delay:1.35s}[data-aos][data-aos][data-aos-duration="1400"],body[data-aos-duration="1400"] [data-aos]{transition-duration:1.4s}[data-aos][data-aos][data-aos-delay="1400"],body[data-aos-delay="1400"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1400"].aos-animate,body[data-aos-delay="1400"] [data-aos].aos-animate{transition-delay:1.4s}[data-aos][data-aos][data-aos-duration="1450"],body[data-aos-duration="1450"] [data-aos]{transition-duration:1.45s}[data-aos][data-aos][data-aos-delay="1450"],body[data-aos-delay="1450"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1450"].aos-animate,body[data-aos-delay="1450"] [data-aos].aos-animate{transition-delay:1.45s}[data-aos][data-aos][data-aos-duration="1500"],body[data-aos-duration="1500"] [data-aos]{transition-duration:1.5s}[data-aos][data-aos][data-aos-delay="1500"],body[data-aos-delay="1500"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1500"].aos-animate,body[data-aos-delay="1500"] [data-aos].aos-animate{transition-delay:1.5s}[data-aos][data-aos][data-aos-duration="1550"],body[data-aos-duration="1550"] [data-aos]{transition-duration:1.55s}[data-aos][data-aos][data-aos-delay="1550"],body[data-aos-delay="1550"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1550"].aos-animate,body[data-aos-delay="1550"] [data-aos].aos-animate{transition-delay:1.55s}[data-aos][data-aos][data-aos-duration="1600"],body[data-aos-duration="1600"] [data-aos]{transition-duration:1.6s}[data-aos][data-aos][data-aos-delay="1600"],body[data-aos-delay="1600"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1600"].aos-animate,body[data-aos-delay="1600"] [data-aos].aos-animate{transition-delay:1.6s}[data-aos][data-aos][data-aos-duration="1650"],body[data-aos-duration="1650"] [data-aos]{transition-duration:1.65s}[data-aos][data-aos][data-aos-delay="1650"],body[data-aos-delay="1650"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1650"].aos-animate,body[data-aos-delay="1650"] [data-aos].aos-animate{transition-delay:1.65s}[data-aos][data-aos][data-aos-duration="1700"],body[data-aos-duration="1700"] [data-aos]{transition-duration:1.7s}[data-aos][data-aos][data-aos-delay="1700"],body[data-aos-delay="1700"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1700"].aos-animate,body[data-aos-delay="1700"] [data-aos].aos-animate{transition-delay:1.7s}[data-aos][data-aos][data-aos-duration="1750"],body[data-aos-duration="1750"] [data-aos]{transition-duration:1.75s}[data-aos][data-aos][data-aos-delay="1750"],body[data-aos-delay="1750"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1750"].aos-animate,body[data-aos-delay="1750"] [data-aos].aos-animate{transition-delay:1.75s}[data-aos][data-aos][data-aos-duration="1800"],body[data-aos-duration="1800"] [data-aos]{transition-duration:1.8s}[data-aos][data-aos][data-aos-delay="1800"],body[data-aos-delay="1800"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1800"].aos-animate,body[data-aos-delay="1800"] [data-aos].aos-animate{transition-delay:1.8s}[data-aos][data-aos][data-aos-duration="1850"],body[data-aos-duration="1850"] [data-aos]{transition-duration:1.85s}[data-aos][data-aos][data-aos-delay="1850"],body[data-aos-delay="1850"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1850"].aos-animate,body[data-aos-delay="1850"] [data-aos].aos-animate{transition-delay:1.85s}[data-aos][data-aos][data-aos-duration="1900"],body[data-aos-duration="1900"] [data-aos]{transition-duration:1.9s}[data-aos][data-aos][data-aos-delay="1900"],body[data-aos-delay="1900"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1900"].aos-animate,body[data-aos-delay="1900"] [data-aos].aos-animate{transition-delay:1.9s}[data-aos][data-aos][data-aos-duration="1950"],body[data-aos-duration="1950"] [data-aos]{transition-duration:1.95s}[data-aos][data-aos][data-aos-delay="1950"],body[data-aos-delay="1950"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="1950"].aos-animate,body[data-aos-delay="1950"] [data-aos].aos-animate{transition-delay:1.95s}[data-aos][data-aos][data-aos-duration="2000"],body[data-aos-duration="2000"] [data-aos]{transition-duration:2s}[data-aos][data-aos][data-aos-delay="2000"],body[data-aos-delay="2000"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2000"].aos-animate,body[data-aos-delay="2000"] [data-aos].aos-animate{transition-delay:2s}[data-aos][data-aos][data-aos-duration="2050"],body[data-aos-duration="2050"] [data-aos]{transition-duration:2.05s}[data-aos][data-aos][data-aos-delay="2050"],body[data-aos-delay="2050"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2050"].aos-animate,body[data-aos-delay="2050"] [data-aos].aos-animate{transition-delay:2.05s}[data-aos][data-aos][data-aos-duration="2100"],body[data-aos-duration="2100"] [data-aos]{transition-duration:2.1s}[data-aos][data-aos][data-aos-delay="2100"],body[data-aos-delay="2100"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2100"].aos-animate,body[data-aos-delay="2100"] [data-aos].aos-animate{transition-delay:2.1s}[data-aos][data-aos][data-aos-duration="2150"],body[data-aos-duration="2150"] [data-aos]{transition-duration:2.15s}[data-aos][data-aos][data-aos-delay="2150"],body[data-aos-delay="2150"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2150"].aos-animate,body[data-aos-delay="2150"] [data-aos].aos-animate{transition-delay:2.15s}[data-aos][data-aos][data-aos-duration="2200"],body[data-aos-duration="2200"] [data-aos]{transition-duration:2.2s}[data-aos][data-aos][data-aos-delay="2200"],body[data-aos-delay="2200"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2200"].aos-animate,body[data-aos-delay="2200"] [data-aos].aos-animate{transition-delay:2.2s}[data-aos][data-aos][data-aos-duration="2250"],body[data-aos-duration="2250"] [data-aos]{transition-duration:2.25s}[data-aos][data-aos][data-aos-delay="2250"],body[data-aos-delay="2250"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2250"].aos-animate,body[data-aos-delay="2250"] [data-aos].aos-animate{transition-delay:2.25s}[data-aos][data-aos][data-aos-duration="2300"],body[data-aos-duration="2300"] [data-aos]{transition-duration:2.3s}[data-aos][data-aos][data-aos-delay="2300"],body[data-aos-delay="2300"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2300"].aos-animate,body[data-aos-delay="2300"] [data-aos].aos-animate{transition-delay:2.3s}[data-aos][data-aos][data-aos-duration="2350"],body[data-aos-duration="2350"] [data-aos]{transition-duration:2.35s}[data-aos][data-aos][data-aos-delay="2350"],body[data-aos-delay="2350"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2350"].aos-animate,body[data-aos-delay="2350"] [data-aos].aos-animate{transition-delay:2.35s}[data-aos][data-aos][data-aos-duration="2400"],body[data-aos-duration="2400"] [data-aos]{transition-duration:2.4s}[data-aos][data-aos][data-aos-delay="2400"],body[data-aos-delay="2400"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2400"].aos-animate,body[data-aos-delay="2400"] [data-aos].aos-animate{transition-delay:2.4s}[data-aos][data-aos][data-aos-duration="2450"],body[data-aos-duration="2450"] [data-aos]{transition-duration:2.45s}[data-aos][data-aos][data-aos-delay="2450"],body[data-aos-delay="2450"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2450"].aos-animate,body[data-aos-delay="2450"] [data-aos].aos-animate{transition-delay:2.45s}[data-aos][data-aos][data-aos-duration="2500"],body[data-aos-duration="2500"] [data-aos]{transition-duration:2.5s}[data-aos][data-aos][data-aos-delay="2500"],body[data-aos-delay="2500"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2500"].aos-animate,body[data-aos-delay="2500"] [data-aos].aos-animate{transition-delay:2.5s}[data-aos][data-aos][data-aos-duration="2550"],body[data-aos-duration="2550"] [data-aos]{transition-duration:2.55s}[data-aos][data-aos][data-aos-delay="2550"],body[data-aos-delay="2550"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2550"].aos-animate,body[data-aos-delay="2550"] [data-aos].aos-animate{transition-delay:2.55s}[data-aos][data-aos][data-aos-duration="2600"],body[data-aos-duration="2600"] [data-aos]{transition-duration:2.6s}[data-aos][data-aos][data-aos-delay="2600"],body[data-aos-delay="2600"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2600"].aos-animate,body[data-aos-delay="2600"] [data-aos].aos-animate{transition-delay:2.6s}[data-aos][data-aos][data-aos-duration="2650"],body[data-aos-duration="2650"] [data-aos]{transition-duration:2.65s}[data-aos][data-aos][data-aos-delay="2650"],body[data-aos-delay="2650"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2650"].aos-animate,body[data-aos-delay="2650"] [data-aos].aos-animate{transition-delay:2.65s}[data-aos][data-aos][data-aos-duration="2700"],body[data-aos-duration="2700"] [data-aos]{transition-duration:2.7s}[data-aos][data-aos][data-aos-delay="2700"],body[data-aos-delay="2700"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2700"].aos-animate,body[data-aos-delay="2700"] [data-aos].aos-animate{transition-delay:2.7s}[data-aos][data-aos][data-aos-duration="2750"],body[data-aos-duration="2750"] [data-aos]{transition-duration:2.75s}[data-aos][data-aos][data-aos-delay="2750"],body[data-aos-delay="2750"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2750"].aos-animate,body[data-aos-delay="2750"] [data-aos].aos-animate{transition-delay:2.75s}[data-aos][data-aos][data-aos-duration="2800"],body[data-aos-duration="2800"] [data-aos]{transition-duration:2.8s}[data-aos][data-aos][data-aos-delay="2800"],body[data-aos-delay="2800"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2800"].aos-animate,body[data-aos-delay="2800"] [data-aos].aos-animate{transition-delay:2.8s}[data-aos][data-aos][data-aos-duration="2850"],body[data-aos-duration="2850"] [data-aos]{transition-duration:2.85s}[data-aos][data-aos][data-aos-delay="2850"],body[data-aos-delay="2850"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2850"].aos-animate,body[data-aos-delay="2850"] [data-aos].aos-animate{transition-delay:2.85s}[data-aos][data-aos][data-aos-duration="2900"],body[data-aos-duration="2900"] [data-aos]{transition-duration:2.9s}[data-aos][data-aos][data-aos-delay="2900"],body[data-aos-delay="2900"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2900"].aos-animate,body[data-aos-delay="2900"] [data-aos].aos-animate{transition-delay:2.9s}[data-aos][data-aos][data-aos-duration="2950"],body[data-aos-duration="2950"] [data-aos]{transition-duration:2.95s}[data-aos][data-aos][data-aos-delay="2950"],body[data-aos-delay="2950"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="2950"].aos-animate,body[data-aos-delay="2950"] [data-aos].aos-animate{transition-delay:2.95s}[data-aos][data-aos][data-aos-duration="3000"],body[data-aos-duration="3000"] [data-aos]{transition-duration:3s}[data-aos][data-aos][data-aos-delay="3000"],body[data-aos-delay="3000"] [data-aos]{transition-delay:0}[data-aos][data-aos][data-aos-delay="3000"].aos-animate,body[data-aos-delay="3000"] [data-aos].aos-animate{transition-delay:3s}[data-aos][data-aos][data-aos-easing=linear],body[data-aos-easing=linear] [data-aos]{transition-timing-function:cubic-bezier(.25,.25,.75,.75)}[data-aos][data-aos][data-aos-easing=ease],body[data-aos-easing=ease] [data-aos]{transition-timing-function:ease}[data-aos][data-aos][data-aos-easing=ease-in],body[data-aos-easing=ease-in] [data-aos]{transition-timing-function:ease-in}[data-aos][data-aos][data-aos-easing=ease-out],body[data-aos-easing=ease-out] [data-aos]{transition-timing-function:ease-out}[data-aos][data-aos][data-aos-easing=ease-in-out],body[data-aos-easing=ease-in-out] [data-aos]{transition-timing-function:ease-in-out}[data-aos][data-aos][data-aos-easing=ease-in-back],body[data-aos-easing=ease-in-back] [data-aos]{transition-timing-function:cubic-bezier(.6,-.28,.735,.045)}[data-aos][data-aos][data-aos-easing=ease-out-back],body[data-aos-easing=ease-out-back] [data-aos]{transition-timing-function:cubic-bezier(.175,.885,.32,1.275)}[data-aos][data-aos][data-aos-easing=ease-in-out-back],body[data-aos-easing=ease-in-out-back] [data-aos]{transition-timing-function:cubic-bezier(.68,-.55,.265,1.55)}[data-aos][data-aos][data-aos-easing=ease-in-sine],body[data-aos-easing=ease-in-sine] [data-aos]{transition-timing-function:cubic-bezier(.47,0,.745,.715)}[data-aos][data-aos][data-aos-easing=ease-out-sine],body[data-aos-easing=ease-out-sine] [data-aos]{transition-timing-function:cubic-bezier(.39,.575,.565,1)}[data-aos][data-aos][data-aos-easing=ease-in-out-sine],body[data-aos-easing=ease-in-out-sine] [data-aos]{transition-timing-function:cubic-bezier(.445,.05,.55,.95)}[data-aos][data-aos][data-aos-easing=ease-in-quad],body[data-aos-easing=ease-in-quad] [data-aos]{transition-timing-function:cubic-bezier(.55,.085,.68,.53)}[data-aos][data-aos][data-aos-easing=ease-out-quad],body[data-aos-easing=ease-out-quad] [data-aos]{transition-timing-function:cubic-bezier(.25,.46,.45,.94)}[data-aos][data-aos][data-aos-easing=ease-in-out-quad],body[data-aos-easing=ease-in-out-quad] [data-aos]{transition-timing-function:cubic-bezier(.455,.03,.515,.955)}[data-aos][data-aos][data-aos-easing=ease-in-cubic],body[data-aos-easing=ease-in-cubic] [data-aos]{transition-timing-function:cubic-bezier(.55,.085,.68,.53)}[data-aos][data-aos][data-aos-easing=ease-out-cubic],body[data-aos-easing=ease-out-cubic] [data-aos]{transition-timing-function:cubic-bezier(.25,.46,.45,.94)}[data-aos][data-aos][data-aos-easing=ease-in-out-cubic],body[data-aos-easing=ease-in-out-cubic] [data-aos]{transition-timing-function:cubic-bezier(.455,.03,.515,.955)}[data-aos][data-aos][data-aos-easing=ease-in-quart],body[data-aos-easing=ease-in-quart] [data-aos]{transition-timing-function:cubic-bezier(.55,.085,.68,.53)}[data-aos][data-aos][data-aos-easing=ease-out-quart],body[data-aos-easing=ease-out-quart] [data-aos]{transition-timing-function:cubic-bezier(.25,.46,.45,.94)}[data-aos][data-aos][data-aos-easing=ease-in-out-quart],body[data-aos-easing=ease-in-out-quart] [data-aos]{transition-timing-function:cubic-bezier(.455,.03,.515,.955)}[data-aos^=fade][data-aos^=fade]{opacity:0;transition-property:opacity,transform}[data-aos^=fade][data-aos^=fade].aos-animate{opacity:1;transform:translateZ(0)}[data-aos=fade-up]{transform:translate3d(0,100px,0)}[data-aos=fade-down]{transform:translate3d(0,-100px,0)}[data-aos=fade-right]{transform:translate3d(-100px,0,0)}[data-aos=fade-left]{transform:translate3d(100px,0,0)}[data-aos=fade-up-right]{transform:translate3d(-100px,100px,0)}[data-aos=fade-up-left]{transform:translate3d(100px,100px,0)}[data-aos=fade-down-right]{transform:translate3d(-100px,-100px,0)}[data-aos=fade-down-left]{transform:translate3d(100px,-100px,0)}[data-aos^=zoom][data-aos^=zoom]{opacity:0;transition-property:opacity,transform}[data-aos^=zoom][data-aos^=zoom].aos-animate{opacity:1;transform:translateZ(0) scale(1)}[data-aos=zoom-in]{transform:scale(.6)}[data-aos=zoom-in-up]{transform:translate3d(0,100px,0) scale(.6)}[data-aos=zoom-in-down]{transform:translate3d(0,-100px,0) scale(.6)}[data-aos=zoom-in-right]{transform:translate3d(-100px,0,0) scale(.6)}[data-aos=zoom-in-left]{transform:translate3d(100px,0,0) scale(.6)}[data-aos=zoom-out]{transform:scale(1.2)}[data-aos=zoom-out-up]{transform:translate3d(0,100px,0) scale(1.2)}[data-aos=zoom-out-down]{transform:translate3d(0,-100px,0) scale(1.2)}[data-aos=zoom-out-right]{transform:translate3d(-100px,0,0) scale(1.2)}[data-aos=zoom-out-left]{transform:translate3d(100px,0,0) scale(1.2)}[data-aos^=slide][data-aos^=slide]{transition-property:transform}[data-aos^=slide][data-aos^=slide].aos-animate{transform:translateZ(0)}[data-aos=slide-up]{transform:translate3d(0,100%,0)}[data-aos=slide-down]{transform:translate3d(0,-100%,0)}[data-aos=slide-right]{transform:translate3d(-100%,0,0)}[data-aos=slide-left]{transform:translate3d(100%,0,0)}[data-aos^=flip][data-aos^=flip]{backface-visibility:hidden;transition-property:transform}[data-aos=flip-left]{transform:perspective(2500px) rotateY(-100deg)}[data-aos=flip-left].aos-animate{transform:perspective(2500px) rotateY(0)}[data-aos=flip-right]{transform:perspective(2500px) rotateY(100deg)}[data-aos=flip-right].aos-animate{transform:perspective(2500px) rotateY(0)}[data-aos=flip-up]{transform:perspective(2500px) rotateX(-100deg)}[data-aos=flip-up].aos-animate{transform:perspective(2500px) rotateX(0)}[data-aos=flip-down]{transform:perspective(2500px) rotateX(100deg)}[data-aos=flip-down].aos-animate{transform:perspective(2500px) rotateX(0)}
/* === end AOS v2.3.4 ================================================== */

:root {
  /* ------------------------------------------------------------------
     Brand 9 — locked, hex-verified against archive/v2/docs/vc-1-0-1/theme.json
     ------------------------------------------------------------------ */
  --color-purple:        #663399;
  --color-purple-shade:  #4a2574;
  --color-purple-tint:   #8c6bb1;

  --color-green:         #2D5C4F;
  --color-green-shade:   #1a3a34;
  --color-green-tint:    #5a9f95;

  --color-rust:          #9D5C3A;
  --color-rust-shade:    #6b3f27;   /* "rust-deep" */
  --color-rust-tint:     #c78a66;

  /* ------------------------------------------------------------------
     Light-mode neutrals — warm linen 4-step layered system
     Cards float UP from body bg (linen-card is lighter than body)
     ------------------------------------------------------------------ */
  --color-bg-body:       #F5EFE6;
  --color-bg-alt:        #E6DECF;
  --color-bg-card:       #FAF6ED;
  --color-text-body:     #1F1A1D;
  --color-text-on-dark:  #FFFFFF;

  /* ------------------------------------------------------------------
     Semantic role tokens
     ------------------------------------------------------------------ */
  --color-cta-bg:        var(--color-rust);
  --color-cta-text:      var(--color-text-on-dark);
  --color-link-inline:   var(--color-rust-shade);
  --color-focus-light:   var(--color-rust);
  --color-focus-dark:    var(--color-text-on-dark);

  /* ------------------------------------------------------------------
     Type stack
     ------------------------------------------------------------------ */
  --font-display: 'Fraunces', 'Times New Roman', serif;
  --font-body:    'Inter', system-ui, -apple-system, sans-serif;

  --weight-body:    400;
  --weight-strong:  600;
  --weight-display: 500;

  /* Type scale (perfect fourth, 1.333) */
  --text-50:  0.75rem;    /* 12px */
  --text-100: 1rem;       /* 16px — body */
  --text-200: 1.333rem;
  --text-300: 1.777rem;
  --text-400: 2.369rem;
  --text-500: 3.157rem;   /* hero headlines */
  --text-600: 4.209rem;

  /* ------------------------------------------------------------------
     Spacing scale
     ------------------------------------------------------------------ */
  --space-0:   0;
  --space-4:   0.25rem;
  --space-8:   0.5rem;
  --space-12:  0.75rem;
  --space-16:  1rem;
  --space-20:  1.25rem;    /* hero spacer thickness */
  --space-24:  1.5rem;
  --space-32:  2rem;
  --space-48:  3rem;
  --space-64:  4rem;
  --space-96:  6rem;
  --space-128: 8rem;

  /* ------------------------------------------------------------------
     Layout tokens
     Sitewide content column. Fluid via clamp(): 1200px floor wins
     below ~1500px viewport (matches the previous static breakpoint
     trigger); 80vw growth between ~1500px and ~2000px viewports;
     1600px ceiling at ~2000px+ (so 1920px ~= 1536, 4K caps at 1600).
     All max-width: var(--content-max-width) consumers (header,
     footer, mobile-panel, home services / use-cases / portfolio
     inners) recalculate automatically as the viewport widens. The
     hero content-edge alignment formula at .hero--home .hero__panel
     --one also reads this token so left-panel copy stays flush with
     band edges. Tunables: floor preserves below-trigger behavior;
     slope (80vw) sets growth rate; ceiling caps the column on
     ultrawide displays.
     ------------------------------------------------------------------ */
  --content-max-width: clamp(1200px, 80vw, 1600px);

  /* ------------------------------------------------------------------
     Motion tokens
     No per-element easing or duration overrides; rhythm is sitewide.
     ------------------------------------------------------------------ */
  --ease-standard: cubic-bezier(0.22, 1, 0.36, 1);

  --duration-hover:   150ms;
  --duration-hover-l: 200ms;
  --duration-reveal:  600ms;
  --duration-fade:    400ms;
  --duration-logo-leg:   700ms;
  --duration-logo-total: 1100ms;
}

/* ------------------------------------------------------------------
   Dark-mode override hooks — light ships v3, dark is a follow-up.
   Token contract is preserved so per-component rules need no changes.
   ------------------------------------------------------------------ */
:root[data-theme="dark"] {
  /* Filled in at dark-mode follow-up. */
}

/* ------------------------------------------------------------------
   Self-hosted fonts — Latin-subset variable WOFF2
   Subsetter: scripts/subset-fonts.js (run via npm run subset:fonts).
   Source TTFs live in src/fonts/source/ (gitignored).
   ------------------------------------------------------------------ */
@font-face {
  font-family: 'Fraunces';
  src: url('/fonts/fraunces-var.559e2d69b3.woff2') format('woff2-variations'),
       url('/fonts/fraunces-var.559e2d69b3.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  font-style: normal;
}

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.4d4fef069c.woff2') format('woff2-variations'),
       url('/fonts/inter-var.4d4fef069c.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  font-style: normal;
}

/* ------------------------------------------------------------------
   Body baseline (chrome owns the page surface)
   ------------------------------------------------------------------ */
body {
  font-family: var(--font-body);
  color: var(--color-text-body);
  background: var(--color-bg-body);
  margin: 0;
  transition: opacity var(--duration-fade) var(--ease-standard);
}

/* ------------------------------------------------------------------
   Page fade-in (sitewide).
   `<html class="is-loading">` is emitted server-side from base.njk to
   block paint of <body>. site.js removes the class on DOMContentLoaded,
   triggering the opacity transition above. AOS init waits for the
   resulting body `transitionend` event (see spec.md > Motion System).
   Reduced motion: body renders at opacity 1 immediately, no transition.
   ------------------------------------------------------------------ */
.is-loading body {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .is-loading body { opacity: 1; }
  body { transition: none; }
}

/* ------------------------------------------------------------------
   Site header (sticky)
   ------------------------------------------------------------------ */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--color-bg-body);
  padding: var(--space-16) var(--space-24);
  transition:
    background-color var(--duration-hover-l) var(--ease-standard),
    padding var(--duration-hover-l) var(--ease-standard),
    box-shadow var(--duration-hover-l) var(--ease-standard);
}

/* Scrolled state. Set server-side on no-hero pages (privacy, 404).
   Set by the IntersectionObserver in site.js once .hero-sentinel exits the
   viewport on pages with hasHero: true. Padding compresses from
   --space-16 (16px) to --space-12 (12px) -- a 25% reduction, the nearest
   token below the 80% spec target. Background swaps to linen-alt
   (the next-darker neutral) so the scrolled header reads as a distinct
   chrome layer above the body bg without competing with linen-card
   surfaces inside content. Shadow is the lightest tier of the shared
   shadow language. */
.site-header--scrolled {
  background-color: var(--color-bg-alt);
  padding-block: var(--space-12);
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.06);
}

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

.site-header__inner {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-24);
}

.site-header__logo-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-12);
  color: inherit;
  text-decoration: none;
}

.logo--header {
  width: 40px;
  height: 40px;
  display: block;
  flex: 0 0 auto;
}

/* Wordmark to the right of the header logo. Fraunces display face so it
   reads as the brand mark, not body chrome. Sized as the primary brand
   text alongside the logo. Hidden below 768px because the mobile menu
   is the primary navigation at narrow widths and the wordmark would
   crowd the hamburger. Lives inside .site-header__logo-link so hovering
   the wordmark also triggers the logo's hover-split (the rule is scoped
   to the parent link, see the .logo--header hover block below). */
.site-header__wordmark {
  font-family: var(--font-display);
  font-size: 2.25rem;
  font-weight: var(--weight-display);
  color: var(--color-text-body);
  line-height: 1;
  letter-spacing: 0.01em;
}

@media (max-width: 767.98px) {
  .site-header__wordmark {
    display: none;
  }
}

/* Two-fill logo: left V + right wedge are foreground; middle V valley
   carves out by matching the surface behind. Source SVG had this as
   white fill on the middle path. Solid white outline comes from the
   feMorphology filter inside the SVG (logo.njk). */
.logo {
  --logo-fg: var(--color-text-body);
  --logo-bg: var(--color-bg-body);
  overflow: visible;             /* let the filter render outside the viewBox */
}
.logo__polygon {
  fill: var(--logo-fg);
  stroke: var(--logo-fg);
  vector-effect: non-scaling-stroke;
  stroke-linejoin: miter;
}
/* v-right is the only polygon with a visible outline. Stroke is set in
   viewBox units (scaling) so the inset outline keeps its proportion to
   the halo at every display size (header 40px, hero 180-320px). The
   stroke is centered on the path edge; clip-path in logo.njk clips the
   outer half away, so only the inner half reads as the inset outline.
   Base (hero) values: halo radius=3 (logo.njk), stroke-width=5 here
   (inner-visible 2.5 vbox-units). Asymmetry rationale: the white halo
   wraps the entire 3-polygon silhouette; the black inset only renders
   along v-right's outer arc (where v-right meets v-left and c-tail
   the inset is invisible against the neighbouring black-filled
   polygons). Equal per-pixel thickness reads as visually unequal
   because the halo covers more total length, so the halo is
   intentionally tuned thicker than the inset's inner-visible width to
   balance the read. */
.logo .logo__polygon--v-right {
  fill: var(--logo-bg);
  vector-effect: none;
  stroke-width: 5;
}
/* Header variant: halo radius=4 (logo.njk) + stroke-width=8 here
   (inner-visible 4 vbox-units). At the small 40px header display the
   halo/inset asymmetry is less perceptible, so the two outlines run at
   matched 4-unit thickness; both also need the heavier weight to
   survive sub-pixel rendering at that size. */
.logo--header .logo__polygon--v-right { stroke-width: 8; }
.logo .logo__polygon--v-left  { stroke: none; }
.logo .logo__polygon--c-tail  { stroke: none; }

.site-header__cta {
  background: var(--color-cta-bg);
  color: var(--color-cta-text);
  padding: var(--space-12) var(--space-24);
  text-decoration: none;
  font-weight: var(--weight-strong);
  font-family: var(--font-body);
}

.site-header__hamburger {
  display: none;
  background: none;
  border: 0;
  padding: var(--space-8);
  cursor: pointer;
  color: inherit;
}

.site-header__hamburger-icon svg {
  width: 24px;
  height: 24px;
  display: block;
}

.hero-sentinel {
  height: 1px;
  width: 100%;
  pointer-events: none;
}

/* ------------------------------------------------------------------
   Primary nav
   ------------------------------------------------------------------ */
.site-nav__list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  gap: var(--space-24);
}

.site-nav__link {
  color: var(--color-text-body);
  text-decoration: none;
  font-weight: var(--weight-body);
  font-family: var(--font-body);
  padding: var(--space-8) 0;
  transition: color var(--duration-hover) var(--ease-standard);
}

/* Reserve the bold-state width on header nav links via an invisible
   ::after pseudo so hover/focus weight changes do not shift adjacent
   links. Mechanism: each link becomes a column-stacked inline-flex
   container with two flex items -- the visible text (anonymous text
   run, normal weight at rest, bold on hover) and the ::after pseudo
   (always bold, height: 0, visibility: hidden). The link's width
   tracks the wider of the two = the bold pseudo's width = constant
   across rest/hover/current-page states. align-items: center
   horizontally centers the visible text within that reserved width.
   Mobile menu links keep their existing block layout -- the column
   stack has no neighbour to shift, so the pseudo is unnecessary and
   would only add complexity. */
.site-nav--header .site-nav__link {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
}

.site-nav--header .site-nav__link::after {
  content: attr(data-text);
  font-weight: var(--weight-strong);
  height: 0;
  visibility: hidden;
  overflow: hidden;
  pointer-events: none;
  user-select: none;
}

/* Hover + keyboard-focus shift to rust-deep + bold. Color matches the
   inline-link emphasis convention (--color-link-inline); the weight
   bump amplifies the hover affordance beyond color alone. Color
   transition fades; font-weight changes instantly (font-weight is not
   transitioned). No reduced-motion gate (neither property is motion).
   Width does NOT shift because the link reserves the bold width at
   rest via the .site-nav--header .site-nav__link::after pseudo above. */
.site-nav__link:hover,
.site-nav__link:focus-visible {
  color: var(--color-rust-shade);
  font-weight: var(--weight-strong);
}

/* Current-page link: bolded weight + 3px rust-deep border-bottom as the
   visible indicator. The border renders regardless of motion preference.
   Negative bottom inset on the padding keeps the link's vertical rhythm
   matching siblings (3px gain is offset by 3px less bottom padding). */
.site-nav__link[aria-current="page"] {
  font-weight: var(--weight-strong);
  border-bottom: 3px solid var(--color-rust-shade);
  padding-bottom: calc(var(--space-8) - 3px);
}

@media (max-width: 767.98px) {
  .site-nav--header {
    display: none;
  }

  .site-header__cta {
    display: none;
  }

  .site-header__hamburger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* ------------------------------------------------------------------
   Mobile menu — Sprint 2 Item 5
   Implements docs/spec.md > Site Chrome & Navigation > Mobile Menu
   + docs/spec.md > Accessibility > Mobile Menu Focus Trap.

   Slide-down panel anchored to the header, surfaced in linen-card.
   JS handles open/close via .site-header--menu-open on <header>;
   focus trap, Esc dismiss, click-link-closes-then-navigates all in
   site.js > initMobileMenu().

   Hamburger trapezoid trio rotates to an X via per-bar transforms.
   Reduced motion: panel transition becomes instant; hamburger bars
   skip rotation transitions (final X state still renders).
   ------------------------------------------------------------------ */
.site-header__mobile-panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: var(--color-bg-card);
  color: var(--color-text-body);
  padding: var(--space-24);
  box-shadow: 0 4px 12px rgb(0 0 0 / 0.08);
  transform: translateY(-110%);
  transition: transform 280ms var(--ease-standard);
  z-index: -1;
  visibility: hidden;
}

.site-header--menu-open .site-header__mobile-panel {
  transform: translateY(0);
  visibility: visible;
}

.site-header__mobile-panel-inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  max-width: var(--content-max-width);
  margin: 0 auto;
}

.site-nav--mobile .site-nav__list {
  flex-direction: column;
  gap: var(--space-16);
}

.site-nav--mobile .site-nav__link {
  display: block;
  font-size: var(--text-200);
  padding: var(--space-12) 0;
}

.site-header__mobile-cta {
  background: var(--color-cta-bg);
  color: var(--color-cta-text);
  padding: var(--space-12) var(--space-24);
  text-decoration: none;
  font-weight: var(--weight-strong);
  font-family: var(--font-body);
  text-align: center;
  align-self: stretch;
}

/* Desktop: panel never renders. Hamburger is display:none above 768px,
   but the panel could still be visible from a resize. Force-hide it. */
@media (min-width: 768px) {
  .site-header__mobile-panel {
    display: none;
  }
}

/* Hamburger bars share a transition; specific transforms fire when
   header has .site-header--menu-open. Top bar rotates +45deg + drops
   to middle; middle bar fades; bottom bar rotates -45deg + lifts. */
.site-header__hamburger-bar {
  transform-origin: center;
  transition:
    transform var(--duration-hover-l) var(--ease-standard),
    opacity var(--duration-hover-l) var(--ease-standard);
}

.site-header--menu-open .site-header__hamburger-bar--top {
  transform: translateY(5.5px) rotate(45deg);
}

.site-header--menu-open .site-header__hamburger-bar--mid {
  opacity: 0;
}

.site-header--menu-open .site-header__hamburger-bar--bot {
  transform: translateY(-5.5px) rotate(-45deg);
}

@media (prefers-reduced-motion: reduce) {
  .site-header__mobile-panel {
    transition: none;
  }

  .site-header__hamburger-bar {
    transition: none;
  }
}

/* ------------------------------------------------------------------
   Site footer
   ------------------------------------------------------------------ */
.site-footer {
  position: relative;
  overflow: hidden;
  background: var(--color-bg-body);
  color: var(--color-text-body);
  padding: var(--space-64) var(--space-24) var(--space-48);
  border-top: 1px solid var(--color-bg-alt);
}

/* Footer between-columns dot-grid (Sprint 5 Iteration 1 item 7).
   Sibling of the closing-CTA texture (.cta-block::after) — same
   32px lattice / 1.5px radii / 20deg rotation, but rust-deep dots
   on the linen footer bg instead of white on rust (matches the
   retired home-partnership treatment's rust-deep tone). Sits in the
   whitespace between .site-footer__main (left) and the right-
   justified .site-footer__logo-link, behind footer content. The
   band is right-of-centre so it lands in the inter-column gap and
   clears the right-justified logo (item 4). Hidden < 768px where the
   footer collapses to a single column (no between-columns gap).
   reference-reactions.md > Icons / texture (tying device echoed
   lower). */
.site-footer::after {
  content: "";
  position: absolute;
  inset: -15% auto -15% auto;
  left: 58%;
  width: min(340px, 26%);
  background-image: radial-gradient(circle, rgba(107, 63, 39, 0.20) 1.5px, transparent 1.5px);
  background-size: 32px 32px;
  transform: rotate(20deg);
  pointer-events: none;
  z-index: 0;
}

@media (max-width: 767.98px) {
  .site-footer::after {
    display: none;
  }
}

/* Footer is a two-column flex row at desktop: left column = main
   content stack (tagline, contact, nav, copyright), right column =
   logo sized to fill the row vertically. align-items: stretch lets
   the logo column match the main column's natural height; the logo
   inside then takes height: 100% to fill that space. At narrow widths
   the row collapses to a single column stack with the logo placed
   below the main content (column-reverse would put logo above; we
   keep it below as a footer-closing brand mark). */
.site-footer__inner {
  position: relative;
  z-index: 1;
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: var(--space-48);
}

.site-footer__main {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  flex: 1;
  min-width: 0;
}

.site-footer__tagline {
  font-family: var(--font-display);
  font-size: var(--text-300);
  font-weight: var(--weight-display);
  color: var(--color-rust-shade);
  margin: 0;
}

/* Logo column. flex item with align-items: stretch (inherited from
   parent) means it spans the row's full height; inner flex aligns
   the logo to the right edge so it mirrors the left content's left
   alignment about the centered .site-footer__inner (Sprint 5
   Iteration 1 item 4). .site-footer horizontal padding is symmetric
   (--space-24 both sides), so the logo's right inset from the
   viewport equals the left content's left inset. Logo size override
   below scales the SVG to that height. */
.site-footer__logo-link {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-shrink: 0;
  color: inherit;
  text-decoration: none;
  line-height: 0;
}

/* Footer logo fills the row vertically. The header variant of the
   logo defaults to 40px square; here we override to track the parent
   link's stretched height. SVG viewBox is square-ish (399.43 x 399),
   so width: auto produces a near-square logo at whatever the row
   resolves to. */
.site-footer__logo-link .logo--header {
  width: auto;
  height: 100%;
  display: block;
}

@media (max-width: 767.98px) {
  .site-footer__inner {
    flex-direction: column;
    gap: var(--space-32);
  }

  /* At narrow widths the row no longer drives the logo's height, so
     give it a concrete size. 96px keeps the brand mark visible without
     dominating the stack. */
  .site-footer__logo-link {
    align-self: flex-start;
    /* Single-column stack: logo sits below the main content,
       left-aligned. Reset the desktop right-justify so the mobile
       footer is unchanged (Sprint 5 Iteration 1 item 4). */
    justify-content: flex-start;
  }

  .site-footer__logo-link .logo--header {
    height: 96px;
    width: 96px;
  }
}

.site-footer__contact {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-16);
}

.site-footer__contact a {
  color: var(--color-link-inline);
  text-decoration: none;
}

/* Footer link hover -- underline on :hover and :focus-visible. No
   transition on text-decoration (the affordance is instant on hover
   and instant off on unhover). Underline color inherits the link's
   own color. Applies to contact links + nav links. */
.site-footer__contact a:hover,
.site-footer__contact a:focus-visible,
.site-footer__nav a:hover,
.site-footer__nav a:focus-visible {
  text-decoration: underline;
}

.site-footer__linkedin {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.site-footer__linkedin svg {
  width: 24px;
  height: 24px;
  fill: var(--color-text-body);
}

.site-footer__nav ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
}

.site-footer__nav a {
  color: var(--color-link-inline);
  text-decoration: none;
}

@media (min-width: 768px) {
  .site-footer__nav ul {
    flex-direction: row;
    flex-wrap: wrap;
  }
}

.site-footer__copyright {
  color: var(--color-text-body);
  opacity: 0.75;
  font-size: var(--text-50);
  margin: 0;
}

/* ------------------------------------------------------------------
   Hero — asymmetric two-panel color block
   Grid is 6fr 4fr (never percentages). Spacer is 20px linen on top,
   bottom, and between panels. Mobile stacks 45vh / 55vh with a
   horizontal between-spacer at the seam.
   ------------------------------------------------------------------ */
.hero {
  display: grid;
  grid-template-columns: 6fr 4fr;
  position: relative;
  isolation: isolate;
  /* Scale with viewport height between 60vh and 80vh, but hard-cap at
     720px so the hero (and the about family photo inside its right
     panel) stops growing on very tall viewports. Without the cap, a
     1920x1200 viewport pushed the panel to ~960px and the cover-fit
     crop ate the top of the family heads. min() on both bounds avoids
     the case where min-height could exceed max-height on a >1200px
     viewport. */
  min-height: min(60vh, 720px);
  max-height: min(80vh, 720px);
}

.hero__panel {
  display: flex;
  align-items: center;
  position: relative;
  z-index: 0;
  padding: var(--space-48) var(--space-32);
}

.hero__panel--purple          { background: var(--color-purple); color: var(--color-text-on-dark); }
.hero__panel--green           { background: var(--color-green);  color: var(--color-text-on-dark); }
.hero__panel--rust            { background: var(--color-rust);   color: var(--color-text-on-dark); }
.hero__panel--theme-secondary { background: #000000;             color: var(--color-text-on-dark); }
.hero__panel--family-photo    { background: var(--color-bg-card); color: var(--color-text-body); }
.hero__panel--case-specific   { background: var(--color-bg-card); color: var(--color-text-body); }

.hero__copy {
  max-width: 36ch;
}

.hero__headline {
  font-family: var(--font-display);
  font-size: var(--text-500);
  font-weight: var(--weight-display);
  line-height: 1.05;
  margin: 0 0 var(--space-16);
}

.hero__subline {
  font-family: var(--font-body);
  font-size: var(--text-200);
  line-height: 1.4;
  margin: 0;
  opacity: 0.92;
}

.hero__spacer {
  position: absolute;
  background: var(--color-bg-body);
  z-index: 1;
  /* Spacers are purely decorative (colored gap between panels).
     Without pointer-events: none they absorb clicks in the top 20px /
     bottom 20px / between-edge zones, which blocks the /work hero
     carousel sibling buttons from receiving clicks. Other Group C
     heroes happened to have only static images in the right panel
     so the defect never manifested. Sprint 7 Story 4 surfacing. */
  pointer-events: none;
}

.hero__spacer--top {
  top: 0;
  left: 0;
  right: 0;
  height: var(--space-20);
}

.hero__spacer--bottom {
  bottom: 0;
  left: 0;
  right: 0;
  height: var(--space-20);
}

.hero__spacer--between {
  top: var(--space-20);
  bottom: var(--space-20);
  left: calc(60% - (var(--space-20) / 2));
  width: var(--space-20);
}

@media (max-width: 767px) {
  .hero {
    grid-template-columns: 1fr;
    grid-template-rows: 45vh 55vh;
    max-height: none;
  }

  .hero__spacer--between {
    top: 45vh;
    bottom: auto;
    left: 0;
    right: 0;
    width: auto;
    height: var(--space-20);
    transform: translateY(calc(var(--space-20) / -2));
  }
}

/* ------------------------------------------------------------------
   Sitewide hero dot-grid texture (Sprint 3 Iter 2 + 4 origin; promoted
   to sitewide in Sprint 4 polish per Jason).
   - Left panel: dot-grid pseudo in the right portion of the panel,
     rotated 20deg to align with the V-right slope of the logo. Renders
     on every two-panel hero (home, about, services, partnership, and
     future heroes). Hidden below 768px.
   - Right panel: dot-grid continuation, 12 columns x 32px = 384px,
     anchored to the right panel's left edge so the lattice reads as a
     continuation of the left panel's texture across the 20px linen
     spacer. EXCLUDED from .hero--about (right panel is the family
     photo; dots over photo would clash and the photo overlays the
     pseudo regardless).
   - Static; no animation; no reduced-motion gate needed.
   ------------------------------------------------------------------ */
.hero--two-panel .hero__panel--one {
  /* Clip the rotated dot-grid pseudo back to panel edges. The pseudo
     is oversized + rotated 20deg, so its bounding box overhangs the
     panel; overflow: hidden hides the overhang. */
  overflow: hidden;
}

.hero--two-panel .hero__panel--one::after {
  content: "";
  position: absolute;
  /* Inflated bounding box (~15% overhang each direction) so the 20deg
     rotation below does not leave triangular blank corners inside the
     visible texture area. */
  inset: -15% -15% -15% auto;
  width: 60%;
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.20) 1.5px, transparent 1.5px);
  background-size: 32px 32px;
  /* Rotate to match the V-right slope of the logo (~20deg clockwise
     from vertical). transform-origin defaults to center, which keeps
     the rotated pattern visually balanced inside the inflated box. */
  transform: rotate(20deg);
  pointer-events: none;
  z-index: 0;
}

.hero--two-panel .hero__panel--one .hero__copy {
  position: relative;
  z-index: 1;
}

@media (max-width: 767.98px) {
  .hero--two-panel .hero__panel--one::after {
    display: none;
  }
}

/* Right-panel dot continuation. Applies sitewide. On heroes where the
   right panel has content above it (the home logo, the about family
   photo), that content sits at z-index >= 1 so the dot pseudo (z-index
   0) renders behind. The /about family photo is absolutely positioned
   with object-fit cover, so it visually covers the dots; the CSS
   contract still applies them for consistency with other heroes. */
.hero--two-panel .hero__panel--two {
  overflow: hidden;
}

.hero--two-panel .hero__panel--two::before {
  content: "";
  position: absolute;
  inset: -15% auto -15% 0;
  width: 384px;
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.20) 1.5px, transparent 1.5px);
  background-size: 32px 32px;
  /* Phase-shift the lattice by the 20px spacer width so the dot
     columns line up as if the spacer gap weren't breaking the
     pattern. */
  background-position: -20px 0;
  transform: rotate(20deg);
  pointer-events: none;
  z-index: 0;
}

.hero--home .hero__panel--two .logo--hero {
  position: relative;
  z-index: 1;
}
/* .hero--about's photo wrapper gets z-index alongside its own
   position: absolute (rule below); do NOT add `position: relative`
   here — it would override the absolute positioning and let the
   intrinsic image size dictate the panel height (Sprint 4 polish bug). */

@media (max-width: 767.98px) {
  .hero--two-panel .hero__panel--two::before {
    display: none;
  }
}

/* ------------------------------------------------------------------
   Sitewide hero left-panel content-edge alignment + H1 wrap tuning.
   - At >=1200px: left panel's padding-left is the max of the standard
     hero padding and (viewport - content-max-width) / 2, so the H1 +
     sub-line align flush with the sitewide content column's left edge
     (matches the bento / services bands / etc. below the hero).
   - At >=1024px: hero__copy widens to 500px so display-font H1s wrap
     at natural phrase boundaries instead of mid-phrase. Base
     .hero__copy max-width of 36ch is measured in Inter (~324px) which
     forces awkward wraps for the Fraunces display headlines. 500px
     suits every locked tagline currently used in heroes (home boundary,
     about supporting, services boundary, partnership). If a future
     hero needs a different wrap calibration, override under
     .hero--<variant> .hero__copy.
   - Was originally per-page (.hero--home / --services / --partnership)
     and duplicated 3-4×; promoted to sitewide in Sprint 4 polish per
     Jason ("standard across all pages, use home as the standard").
   ------------------------------------------------------------------ */
@media (min-width: 1200px) {
  .hero--two-panel .hero__panel--one {
    padding-left: max(var(--space-32), calc((100vw - var(--content-max-width)) / 2));
  }
}

@media (min-width: 1024px) {
  .hero--two-panel .hero__copy {
    max-width: 530px;
  }
}

/* ------------------------------------------------------------------
   About hero (Sprint 4 Item 1)
   - Rust 60% left + family photo 40% right (Group A right-panel
     contract; PRD-locked sacred anchor).
   - Photo wrapper is absolutely positioned to fill the right panel
     regardless of intrinsic image dimensions; the inner <picture> +
     <img> use object-fit: cover so the image crops to the panel
     rather than pushing it.
   - Left panel opts into the content-edge alignment idiom at
     >=1200px so the H1 + sub-line align with the sitewide content
     column once sections 2-4 land below the hero (items 2 + 3).
   - No decorative motion added; the hero is static visuals.
   ------------------------------------------------------------------ */
.hero--about .hero__panel--two {
  /* Override the base hero__panel padding so the photo fills the
     panel edge-to-edge; otherwise the absolutely-positioned wrapper
     inherits a positioned context but the visual feels inset. */
  padding: 0;
  overflow: hidden;
}

.hero--about .hero__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  /* z-index keeps the photo above the .hero--two-panel right-panel
     ::before dot-grid (which sits at z-index: 0). */
  z-index: 1;
}

.hero--about .hero__photo picture,
.hero--about .hero__photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Bias the focal point upward so when the cover-fit crops the
     portrait photo vertically, the family heads stay above the
     crop line. Default `center` (50%) anchored the middle, which
     ate the top of heads at taller viewports. */
  object-position: center 30%;
}

/* Content-edge alignment + hero__copy 500px wrap-tuning inherited
   sitewide from .hero--two-panel rules above. */

/* ------------------------------------------------------------------
   Sitewide .section primitive.
   - Apply to any below-hero section. Modifiers --body / --alt drive
     the background; --compact swaps to lighter vertical padding for
     floor-service / teaser sections.
   - .section__inner clamps content to var(--content-max-width) so
     section frames align edge-for-edge across the whole site (home
     grids, page bento, page prose, payment-form cards).
   - .section__heading / __subheading / __lede / __paragraph are the
     shared typography for H2 / H3 / lede paragraph / body paragraph.
     Specialized per-page rules (pull-quotes, examples lists, the
     bento grid, payment-form cards, etc.) live in their own per-page
     blocks below and stack on top of these via additional classes
     on the same elements.
   - Was extracted in Sprint 4 polish after per-section blocks on
     /about, /services, /partnership duplicated this frame across 17+
     places and the home __partnership section drifted to 720px while
     everything else moved to var(--content-max-width). Single source
     of truth from now on.
   ------------------------------------------------------------------ */
.section {
  padding: var(--space-96) var(--space-32);
}
.section--body { background: var(--color-bg-body); }
.section--alt  { background: var(--color-bg-alt); }
.section--compact {
  padding: var(--space-64) var(--space-32);
}

.section__inner {
  max-width: var(--content-max-width);
  margin-inline: auto;
}

.section__heading {
  font-family: var(--font-display);
  font-size: var(--text-400);
  font-weight: var(--weight-display);
  line-height: 1.15;
  color: var(--color-text-body);
  margin: 0 0 var(--space-32);
}

.section__subheading {
  font-family: var(--font-display);
  font-size: var(--text-300);
  font-weight: var(--weight-display);
  line-height: 1.2;
  color: var(--color-text-body);
  margin: 0 0 var(--space-16);
}

.section__lede {
  font-family: var(--font-body);
  font-size: var(--text-200);
  font-weight: var(--weight-body);
  line-height: 1.6;
  color: var(--color-text-body);
  margin: 0 0 var(--space-48);
}

.section__paragraph {
  font-family: var(--font-body);
  font-size: var(--text-200);
  font-weight: var(--weight-body);
  line-height: 1.6;
  color: var(--color-text-body);
  margin: 0 0 var(--space-24);
}

.section__paragraph:last-child {
  margin-bottom: 0;
}

@media (max-width: 767px) {
  .section {
    padding: var(--space-64) var(--space-24);
  }
  .section--compact {
    padding: var(--space-48) var(--space-24);
  }
  .section__heading {
    font-size: var(--text-300);
    margin-bottom: var(--space-24);
  }
  .section__paragraph,
  .section__lede {
    font-size: var(--text-100);
  }
}

/* ------------------------------------------------------------------
   Privacy /privacy/ (Sprint 6 Item 1)
   Quiet, functional page: no color-block hero. The .section
   section--body + .section__inner frame is inherited; only the quiet
   header (Fraunces title + Inter sub-line + small rust accent rule)
   and the inter-section rhythm are privacy-scoped. All six sections
   sit on body bg inside a single .section so the short page reads as
   one column rather than six 96px-padded bands. H2s reuse
   .section__subheading; paragraphs reuse .section__paragraph.
   ------------------------------------------------------------------ */
.privacy__header {
  margin-bottom: var(--space-64);
}
.privacy__title {
  font-family: var(--font-display);
  font-size: var(--text-500);
  font-weight: var(--weight-display);
  line-height: 1.1;
  color: var(--color-text-body);
  margin: 0 0 var(--space-16);
}
.privacy__subline {
  font-family: var(--font-body);
  font-size: var(--text-200);
  line-height: 1.6;
  color: var(--color-text-body);
  margin: 0 0 var(--space-24);
  max-width: 52ch;
}
.privacy__rule {
  display: block;
  width: var(--space-64);
  height: 3px;
  background: var(--color-rust);
}
.privacy__section + .privacy__section {
  margin-top: var(--space-48);
}
.privacy__updated {
  font-family: var(--font-body);
  font-size: var(--text-100);
  color: var(--color-text-body);
  margin: var(--space-24) 0 0;
}

@media (max-width: 767px) {
  .privacy__title { font-size: var(--text-400); }
  .privacy__header { margin-bottom: var(--space-48); }
  .privacy__section + .privacy__section { margin-top: var(--space-32); }
}

/* ------------------------------------------------------------------
   About Section 1: bio bento (Sprint 4 Item 2)
   - 2x3 CSS grid; middle row spans full width.
   - 5 tiles in PRD-locked positions: tl / tr / middle (wide) / bl / br.
   - Tile surfaces are linen-card (var(--color-bg-card)); the bento
     itself sits on body bg (var(--color-bg-body)) so the linen-card
     floats UP against the body bg around it. That contrast IS the
     visual division between tiles -- the bento is NOT a whitespace-
     only division (motion hard-avoid).
   - Square corners + token-driven gap. Inlined per spec; no component.
   - No motion attached; sitewide AOS rhythm only if added later.
   - Mobile (<768px): tiles stack to a single column in PRD order.
   ------------------------------------------------------------------ */
/* Frame inherited from .section section--body + .section__inner. */

.about-bio-bento__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-areas:
    "tl tr"
    "middle middle"
    "bl br"
    "network network";
  gap: var(--space-24);
}

.about-bio-bento__tile--tl      { grid-area: tl; }
.about-bio-bento__tile--tr      { grid-area: tr; }
.about-bio-bento__tile--middle  { grid-area: middle; }
.about-bio-bento__tile--bl      { grid-area: bl; }
.about-bio-bento__tile--br      { grid-area: br; }
.about-bio-bento__tile--network { grid-area: network; }

.about-bio-bento__tile {
  background: var(--color-bg-card);
  color: var(--color-text-body);
  padding: var(--space-32);
  /* Square corners. Matches sitewide gallery / bento lean (use-case
     tiles, work tiles). */
  border-radius: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
}

.about-bio-bento__heading {
  font-family: var(--font-display);
  font-size: var(--text-300);
  font-weight: var(--weight-display);
  line-height: 1.15;
  margin: 0;
}

.about-bio-bento__body {
  font-family: var(--font-body);
  font-size: var(--text-100);
  font-weight: var(--weight-body);
  line-height: 1.55;
  margin: 0;
  /* Keep tile prose readable; bento tiles are scannable, not long-form. */
  max-width: 60ch;
}

.about-bio-bento__years {
  /* The locked years-claim span. No visual treatment beyond inheriting
     the tile body; placeholder hook for future emphasis if needed. */
  font-weight: var(--weight-strong);
}

/* The middle wide tile carries more prose than its neighbours; give it
   a touch more breathing room without disrupting the four square
   tiles' rhythm. */
.about-bio-bento__tile--middle .about-bio-bento__heading,
.about-bio-bento__tile--network .about-bio-bento__heading {
  font-size: var(--text-400);
}

/* The middle + network tiles span both columns; let their prose span the
   full width of the wide tile as one block rather than sitting in a 60ch
   measure on the left with the right half empty. */
.about-bio-bento__tile--middle .about-bio-bento__body,
.about-bio-bento__tile--network .about-bio-bento__body {
  max-width: none;
}

@media (max-width: 767px) {
  .about-bio-bento__grid {
    grid-template-columns: 1fr;
    grid-template-areas:
      "tl"
      "tr"
      "middle"
      "bl"
      "br"
      "network";
    gap: var(--space-16);
  }
  .about-bio-bento__heading {
    font-size: var(--text-200);
  }
  .about-bio-bento__tile--middle .about-bio-bento__heading,
  .about-bio-bento__tile--network .about-bio-bento__heading {
    font-size: var(--text-300);
  }
}

/* ------------------------------------------------------------------
   About section 2 — Philosophy (alt bg).
   3 beats stacked vertically; pull-quote rendered as inline emphasis
   (rust-deep Fraunces) on the same alt bg, no separate surface.
   Mirrors the home page's alt-bg section rhythm
   (.home__usecases / .home__partnership).
   Sprint 4 item 3.
   ------------------------------------------------------------------ */
/* Frame inherited from .section section--alt + .section__inner;
   H2 uses .section__heading; beat sub-heads use .section__subheading. */

.about-philosophy__beats {
  display: flex;
  flex-direction: column;
  gap: var(--space-48);
  margin: 0 0 var(--space-64);
}

.about-philosophy__beat {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}

.about-philosophy__beat-heading {
  font-family: var(--font-display);
  font-size: var(--text-300);
  font-weight: var(--weight-display);
  line-height: 1.15;
  margin: 0;
}

.about-philosophy__beat-body {
  font-family: var(--font-body);
  font-size: var(--text-200);
  font-weight: var(--weight-body);
  line-height: 1.55;
  margin: 0;
  color: var(--color-text-body);
}

/* Pull-quote = inline emphasis treatment per CLAUDE.md rust accent rule
   (rust-deep --color-rust-shade for inline emphasis on linen). Large
   Fraunces, italics, no separate surface. Sits at the end of the
   philosophy section after the 3 beats. */
.about-philosophy__pullquote {
  font-family: var(--font-display);
  font-size: var(--text-400);
  font-weight: var(--weight-display);
  font-style: italic;
  line-height: 1.2;
  color: var(--color-rust-shade);
  margin: 0;
  max-width: 24ch;
}

@media (max-width: 767px) {
  .about-philosophy__beats {
    gap: var(--space-32);
    margin-bottom: var(--space-48);
  }
  .about-philosophy__beat-heading {
    font-size: var(--text-200);
  }
  .about-philosophy__beat-body {
    font-size: var(--text-100);
  }
  .about-philosophy__pullquote {
    font-size: var(--text-300);
  }
}

/* About section 3 — Partnership philosophy: pure shared primitives, no
   per-page rules needed. Markup uses .section section--body +
   .section__inner + .section__heading + .section__paragraph. */

/* ------------------------------------------------------------------
   About section 4 — Closing CTA wrapper (alt bg sandwich).
   The .cta-block include carries its own rust surface; this wrapper
   sits on alt bg so the rust card is framed by alt-bg above and
   below in document flow. Mirrors home's `.home__cta` pattern without
   the dot-grid texture (that was a home-only flourish).
   Sprint 4 item 3.
   ------------------------------------------------------------------ */
.about__cta {
  background: var(--color-bg-alt);
}

/* ------------------------------------------------------------------
   Services hero — purple-left / theme-secondary-right.
   Per-page scope via .hero--services. Right panel carries a hand-drawn
   brand-color illustration (Group C type 3 = photo) that fills the
   panel edge-to-edge via object-fit: cover, same mechanic as the
   /about family photo. The #000 theme-secondary background is the
   fallback ground behind the opaque image. Content-edge alignment +
   hero__copy 500px wrap-tuning inherited from the sitewide
   .hero--two-panel rules above. Sprint 4 item 4; image Sprint 7 Story 2.
   ------------------------------------------------------------------ */
.hero--services .hero__panel--two {
  /* Drop the base panel padding so the illustration fills the panel
     edge-to-edge; matches the .hero--about photo treatment. */
  padding: 0;
  overflow: hidden;
}

.hero--services .hero__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  /* Above the .hero--two-panel right-panel ::before dot-grid (z-index 0). */
  z-index: 1;
}

.hero--services .hero__photo picture,
.hero--services .hero__photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Center crop -- the composition is abstract and balanced, so no
     focal-point bias is needed (unlike the /about family photo). */
  object-position: center center;
}

/* ------------------------------------------------------------------
   Services Section 1 — Intro (body bg).
   Single paragraph framing the four bands. Centered content column,
   constrained max-width for readable measure. Starts the page's
   alternating bg rhythm: body bg here, alt bg on the Bespoke
   applications section below.
   Sprint 4 item 4.
   ------------------------------------------------------------------ */
/* Services Section 1 — Intro: pure shared primitives, no per-page rules
   needed. Markup uses .section section--body + .section__inner +
   .section__paragraph. */

/* ------------------------------------------------------------------
   Services Section 2 — Bespoke applications (alt bg).
   Lead service, deepest section. H2 + 3 paragraphs of framing prose,
   an examples list (3+ concrete project types), and the locked
   "$5K-$15K bespoke build range" v1 carry-forward phrase as a
   callout below the list. The price-band span carries no special
   visual treatment beyond inheriting the paragraph; it exists so the
   locked phrase is greppable as a single token.
   Sprint 4 item 4.
   ------------------------------------------------------------------ */
/* Frame + H2 + paragraphs inherited from .section. Examples list +
   price callout below stay per-page. */

.services-bespoke__examples {
  list-style: none;
  margin: 0 0 var(--space-32);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
}

.services-bespoke__example {
  font-family: var(--font-body);
  font-size: var(--text-200);
  font-weight: var(--weight-body);
  line-height: 1.55;
  color: var(--color-text-body);
  /* Hanging-indent feel via a left border in rust-shade, the same
     accent that flags inline emphasis on linen elsewhere. */
  border-left: 3px solid var(--color-rust-shade);
  padding-left: var(--space-16);
}

.services-bespoke__example-name {
  font-weight: var(--weight-strong);
  color: var(--color-rust-shade);
}

.services-bespoke__price,
.services-automation__price,
.services-ai__price,
.services-webmobile__price {
  font-family: var(--font-body);
  font-size: var(--text-200);
  font-weight: var(--weight-body);
  line-height: 1.55;
  color: var(--color-text-body);
  margin: 0;
}

.services-bespoke__price-band,
.services-automation__price-band,
.services-ai__price-band,
.services-webmobile__price-band {
  font-weight: var(--weight-strong);
}

@media (max-width: 767px) {
  .services-bespoke__example,
  .services-bespoke__price,
  .services-automation__price,
  .services-ai__price,
  .services-webmobile__price {
    font-size: var(--text-100);
  }
}

/* ------------------------------------------------------------------
   Services Section 3 — Workflow automation (body bg).
   Mid-depth: H2, 2 paragraphs, 3-item examples list. Same internal
   rhythm and example treatment as the bespoke section so the four
   bands read as a series. Body bg sandwiches between the bespoke
   section (alt) and the AI section (alt) below.
   Sprint 4 item 5.
   ------------------------------------------------------------------ */
/* Frame + H2 + paragraphs inherited from .section. Examples list +
   example treatment below stay per-page. */

.services-automation__examples {
  list-style: none;
  /* Bottom margin matches the bespoke examples list so the price callout
     below sits in the same gap. */
  margin: 0 0 var(--space-32);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
}

.services-automation__example {
  font-family: var(--font-body);
  font-size: var(--text-200);
  font-weight: var(--weight-body);
  line-height: 1.55;
  color: var(--color-text-body);
  border-left: 3px solid var(--color-rust-shade);
  padding-left: var(--space-16);
}

.services-automation__example-name {
  font-weight: var(--weight-strong);
  color: var(--color-rust-shade);
}

@media (max-width: 767px) {
  .services-automation__example {
    font-size: var(--text-100);
  }
}

/* ------------------------------------------------------------------
   Services Section 4 — AI integration (alt bg).
   Carries the locked differentiator tagline ("AI is good. AI with a
   human is better.") as an inline rust-deep pull-quote between the
   prose and the examples list. Mirrors the About philosophy
   pull-quote idiom (Sprint 4 item 3): large Fraunces, italic,
   --color-rust-shade on alt bg. No separate surface, so it does not
   compete with the closing CTA's rust card lower on the page. The
   phrase is wider than the About pull-quote ("Tell you what you
   need to hear."), so the max-width is tuned wider here.
   Sprint 4 item 5.
   ------------------------------------------------------------------ */
/* Frame + H2 + paragraphs inherited from .section. Pull-quote +
   examples list stay per-page. */

.services-ai__pullquote {
  font-family: var(--font-display);
  font-size: var(--text-400);
  font-weight: var(--weight-display);
  font-style: italic;
  line-height: 1.2;
  color: var(--color-rust-shade);
  margin: 0 0 var(--space-48);
  max-width: 36ch;
}

.services-ai__examples-heading {
  font-family: var(--font-display);
  font-size: var(--text-300);
  font-weight: var(--weight-display);
  line-height: 1.15;
  margin: 0 0 var(--space-24);
}

.services-ai__examples {
  list-style: none;
  /* Bottom margin matches the bespoke examples list so the price callout
     below sits in the same gap. */
  margin: 0 0 var(--space-32);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
}

.services-ai__example {
  font-family: var(--font-body);
  font-size: var(--text-200);
  font-weight: var(--weight-body);
  line-height: 1.55;
  color: var(--color-text-body);
  border-left: 3px solid var(--color-rust-shade);
  padding-left: var(--space-16);
}

.services-ai__example-name {
  font-weight: var(--weight-strong);
  color: var(--color-rust-shade);
}

@media (max-width: 767px) {
  .services-ai__example {
    font-size: var(--text-100);
  }
  .services-ai__pullquote {
    font-size: var(--text-300);
  }
}

/* ------------------------------------------------------------------
   Services Section 5 — Web and mobile development (body bg).
   Floor-service treatment, intentionally lighter than sections 2-4:
   lighter vertical padding (--space-64 vs --space-96), no examples
   list, no examples-heading, just H2 + two short paragraphs. Reads
   as a baseline capability rather than a deep service narrative.
   Sprint 4 item 5.
   ------------------------------------------------------------------ */
/* Services Section 5 — Web and mobile (floor service): uses
   .section--compact for lighter vertical padding. Otherwise pure shared
   primitives, no per-page rules. */

/* ------------------------------------------------------------------
   Services Section 6 — Pricing and ongoing support (alt bg).
   Consolidated band: bespoke range, retainer, hosting, payment-models
   teaser. Four stacked subsections share the same internal rhythm
   (h3 + body paragraph + price callout) so all three pricing lines
   read with one consistent typographic treatment. The price callout
   uses Fraunces medium at --text-300 so the number is the visual
   anchor of each subsection without shouting.

   Section anchor id="pricing" is the locked cross-link target for
   /partnership/'s payment-forms teaser (Item 9).

   The payment-models teaser variant carries no price callout (it is
   not a price line) and reuses the body paragraph for its inline
   link to /partnership/. The link picks up the main p a inline-link
   cascade (rust-deep) without any per-section override.
   Sprint 4 item 6.
   ------------------------------------------------------------------ */
/* Frame + H2 + subheadings + body inherited from .section
   (.section__heading / __subheading / __paragraph). Only the per-page
   spacing rhythm + the rust-shade price callout stay. */

.services-pricing__subsection {
  margin: 0 0 var(--space-48);
}

.services-pricing__subsection:last-child {
  margin-bottom: 0;
}

/* Price callout: Fraunces at --text-300, rust-shade fill so the
   pricing line reads as the visual anchor of each subsection. All
   three pricing lines (bespoke range, retainer, hosting) share this
   treatment so they scan as a series. The teaser subsection has no
   __price element so this rule only matches the three pricing ones. */
.services-pricing__price {
  font-family: var(--font-display);
  font-size: var(--text-300);
  font-weight: var(--weight-display);
  line-height: 1.3;
  color: var(--color-rust-shade);
  margin: 0;
}

@media (max-width: 767px) {
  .services-pricing__price {
    font-size: var(--text-200);
  }
}

/* ------------------------------------------------------------------
   Services section 7 — Closing CTA wrapper (body bg sandwich).
   The .cta-block include carries its own rust surface; this wrapper
   sits on body bg so the rust card is framed by body-bg above and
   below in document flow. Section 6 above is alt bg, so this wrapper
   keeps the alternating bg rhythm going through the close of the page.
   Mirrors .about__cta with the wrapper bg flipped (alt there because
   /about's Section 3 was body; body here because /services Section 6
   is alt).
   Sprint 4 item 6.
   ------------------------------------------------------------------ */
.services__cta {
  background: var(--color-bg-body);
}

/* ------------------------------------------------------------------
   Partnership hero — green-left / theme-secondary-right.
   Per-page scope via .hero--partnership. Right panel carries a hand-drawn
   brand-color illustration (Group C type 3 = photo) that fills the
   panel edge-to-edge via object-fit: cover, same mechanic as the
   /services and /about photos. The #000 theme-secondary background is
   the fallback ground behind the opaque image. Content-edge alignment +
   hero__copy 500px wrap-tuning inherited from the sitewide
   .hero--two-panel rules above. Sprint 4 item 7; image Sprint 7 Story 3.
   ------------------------------------------------------------------ */
.hero--partnership .hero__panel--two {
  /* Drop the base panel padding so the illustration fills the panel
     edge-to-edge; matches the .hero--services photo treatment. */
  padding: 0;
  overflow: hidden;
}

.hero--partnership .hero__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  /* Above the .hero--two-panel right-panel ::before dot-grid (z-index 0). */
  z-index: 1;
}

.hero--partnership .hero__photo picture,
.hero--partnership .hero__photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Center crop -- the cradling gesture and forms are balanced in the
     composition, no focal-point bias needed. */
  object-position: center center;
}

/* ------------------------------------------------------------------
   Partnership Section 1 — Opening philosophy (body bg).
   Single contemplative paragraph framing partnership as the lead
   posture. Vested-interest framing without naming dollars (no `$`
   characters anywhere on this page per PRD non-goal). Centered
   content column, constrained max-width for readable measure.
   Mirrors the .services-intro rhythm so the partnership intro reads
   as a sibling section to the services intro.
   Sprint 4 item 7.
   ------------------------------------------------------------------ */
/* Partnership Section 1 — Opening philosophy: pure shared primitives,
   no per-page rules. Markup uses .section section--body + .section__inner
   + .section__heading + .section__paragraph. */

/* ------------------------------------------------------------------
   Partnership Section 2 — Business-growth focus (alt bg).
   Prose explaining how Jason builds applications that help clients
   earn more. 3 short paragraphs of framing prose; no examples list
   here (the payment-forms cards in Item 9 own the "concrete grid"
   visual idiom on this page, so the prose sections stay prose).
   Token-driven; matches the services-bespoke rhythm for the alt-bg
   sibling treatment. Sprint 4 item 7.
   ------------------------------------------------------------------ */
/* Partnership Section 2 — Business-growth focus: pure shared primitives,
   no per-page rules. */

/* ------------------------------------------------------------------
   Partnership Section 3 — Build, Host, Evolve (body bg).
   Three TRANSPARENT inline blocks on body bg, separated by display-
   font arrow glyphs. At >=768px the row is horizontal and the `→`
   separators (sep-desktop) are visible; the `↓` separators
   (sep-mobile) are hidden. Below 768px the row collapses to a
   vertical stack and the visibility flips: `↓` shown, `→` hidden.
   The blocks themselves carry NO background, NO border, NO shadow --
   the visual differentiation is the icon + Fraunces label + Inter
   description + the Fraunces separators between blocks (per the
   item brief's "transparent inline blocks" contract). Icons use
   fill="currentColor" / stroke="currentColor" so they inherit the
   section's body text color via plain CSS inheritance.
   Sprint 4 item 8.
   ------------------------------------------------------------------ */
/* Frame + H2 inherited from .section. The BHE block row + arrow
   separators + icons stay per-page. */

.partnership-bhe__row {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-32);
}

.partnership-bhe__block {
  background: transparent;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 0;
  color: var(--color-text-body);
}

.partnership-bhe__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--space-16);
  color: var(--color-rust-shade);
}

.partnership-bhe__icon svg {
  display: block;
  width: 32px;
  height: 32px;
}

.partnership-bhe__label {
  font-family: var(--font-display);
  font-size: var(--text-300);
  font-weight: var(--weight-display);
  line-height: 1.2;
  margin: 0 0 var(--space-12);
  color: var(--color-text-body);
}

.partnership-bhe__description {
  font-family: var(--font-body);
  font-size: var(--text-100);
  font-weight: var(--weight-body);
  line-height: 1.55;
  margin: 0;
  color: var(--color-text-body);
  max-width: 32ch;
}

/* Two-span separator pattern: desktop carries `→`, mobile carries `↓`.
   Both spans live in the markup at every gap; CSS hides whichever does
   not apply at the current breakpoint. Fraunces at a large display
   size so the arrows read as a typographic beat between blocks, not
   as small ornamentation. Aligned with the icon row (top-aligned via
   the row's align-items: flex-start, then nudged down with padding-
   top so the arrow sits in line with the icon visually). */
.partnership-bhe__sep-desktop,
.partnership-bhe__sep-mobile {
  font-family: var(--font-display);
  font-size: var(--text-500);
  font-weight: var(--weight-display);
  line-height: 1;
  color: var(--color-rust-shade);
  user-select: none;
  flex: 0 0 auto;
}

.partnership-bhe__sep-desktop {
  display: inline-block;
  padding-top: var(--space-4);
}

.partnership-bhe__sep-mobile {
  display: none;
}

@media (max-width: 767px) {
  .partnership-bhe__row {
    flex-direction: column;
    align-items: center;
    gap: var(--space-24);
  }
  .partnership-bhe__block {
    flex: 0 0 auto;
    width: 100%;
    max-width: 32rem;
  }
  .partnership-bhe__label {
    font-size: var(--text-200);
  }
  .partnership-bhe__sep-desktop {
    display: none;
  }
  .partnership-bhe__sep-mobile {
    display: inline-block;
    font-size: var(--text-400);
    padding: 0;
  }
}

/* ------------------------------------------------------------------
   Partnership Section 4 — Network connections (alt bg).
   Prose explaining the small-network posture, followed by a callout
   rendered as italic prose with a left-border rust-shade accent --
   the lightest treatment that still reads as "this is the example,
   not the framing" (per the item brief). The callout uses a real
   element (<aside>) rather than a styled <p> so assistive tech can
   land on it as a discrete region. NO card surface, NO heavier panel
   treatment; the page already has the rust closing CTA below (item
   9), and a card-like callout here would compete with it.
   Sprint 4 item 8.
   ------------------------------------------------------------------ */
/* Frame + H2 + paragraphs inherited from .section. The italic example
   callout stays per-page. */

.partnership-network__example {
  border-left: 4px solid var(--color-rust-shade);
  padding: var(--space-8) var(--space-24);
  margin: 0;
}

.partnership-network__example-body {
  font-family: var(--font-body);
  font-size: var(--text-200);
  font-weight: var(--weight-body);
  font-style: italic;
  line-height: 1.6;
  color: var(--color-text-body);
  margin: 0;
}

@media (max-width: 767px) {
  .partnership-network {
    padding: var(--space-64) var(--space-24);
  }
  .partnership-network__heading {
    font-size: var(--text-300);
    margin-bottom: var(--space-24);
  }
  .partnership-network__paragraph {
    font-size: var(--text-100);
  }
  .partnership-network__paragraph:last-of-type {
    margin-bottom: var(--space-32);
  }
  .partnership-network__example {
    padding: var(--space-8) var(--space-16);
  }
  .partnership-network__example-body {
    font-size: var(--text-100);
  }
}

/* ------------------------------------------------------------------
   Partnership Section 5 — Payment forms (body bg).
   Three linen-card cards in a 3-column grid at >=768px, single column
   below. Square corners (border-radius: 0) per the sitewide
   square-corner card requirement; gaps via grid `gap`; body bg around
   the grid so the linen-card surfaces float UP from the section bg
   (linen-card is lighter than body bg in the neutral 4-step palette).
   Per PRD non-goal there are NO dollar amounts in this section -- the
   cards describe payment SHAPES, with the cross-link paragraph below
   pointing at /services/#pricing for actual cash figures. The
   cross-link sits inside a <p> inside <main>, so it picks up the
   sitewide `main p a` inline-link cascade (rust-deep + hover) without
   a per-component rule. Sprint 4 item 9.
   ------------------------------------------------------------------ */
/* Frame + H2 + lede inherited from .section (the lede paragraph uses
   .section__lede for its larger bottom margin). The 3-card grid +
   payment-card surface + cross-link stay per-page. */

.partnership-payment-forms__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-32);
  margin-bottom: var(--space-48);
}

.partnership-payment-card {
  background: var(--color-bg-card);
  border-radius: 0;
  padding: var(--space-32);
  display: flex;
  flex-direction: column;
}

.partnership-payment-card__title {
  font-family: var(--font-display);
  font-size: var(--text-300);
  font-weight: var(--weight-display);
  line-height: 1.2;
  margin: 0 0 var(--space-16);
  color: var(--color-text-body);
}

.partnership-payment-card__body {
  font-family: var(--font-body);
  font-size: var(--text-100);
  font-weight: var(--weight-body);
  line-height: 1.6;
  color: var(--color-text-body);
  margin: 0 0 var(--space-16);
}

.partnership-payment-card__body:last-of-type {
  margin-bottom: 0;
}

/* Cross-link paragraph uses shared .section__paragraph; no per-page
   typography needed. */

@media (max-width: 767px) {
  .partnership-payment-forms__grid {
    grid-template-columns: 1fr;
    gap: var(--space-24);
    margin-bottom: var(--space-32);
  }
  .partnership-payment-card {
    padding: var(--space-24);
  }
  .partnership-payment-card__title {
    font-size: var(--text-200);
  }
}

/* ------------------------------------------------------------------
   Partnership Section 6 — Closing CTA wrapper (alt bg).
   The .cta-block include carries its own rust surface; this wrapper
   sits on alt bg so the rust card is framed by alt-bg above and below
   in document flow. Section 5 above is body bg; this wrapper keeps
   the alternating bg rhythm clean through the close of the page.
   Mirrors .about__cta (alt) and .services__cta (body) with the
   wrapper bg chosen per each page's preceding-section bg.
   Sprint 4 item 9.
   ------------------------------------------------------------------ */
.partnership__cta {
  background: var(--color-bg-alt);
}

/* ------------------------------------------------------------------
   CTA block — rust surface, centered headline + button.
   surface-dark is a shared utility for focus-indicator flipping.
   ------------------------------------------------------------------ */
.cta-block {
  position: relative;
  overflow: hidden;
  background: var(--color-cta-bg);
  color: var(--color-cta-text);
  padding: var(--space-96) var(--space-24);
}

/* Sitewide closing-CTA dot-grid (Sprint 5 Iteration 1 item 6).
   Promoted from the former home-only .home__cta::after so every
   page's closing CTA carries the texture. White dots on rust;
   same 32px lattice / 1.5px radii / 20deg rotation as the hero
   and the (now-removed) home partnership echo. reference-reactions.md
   > Icons / texture — texture as a tying device echoed lower.
   Hidden < 768px (see media query below). */
.cta-block::after {
  content: "";
  position: absolute;
  inset: -15% auto -15% 55%;
  width: min(500px, 45%);
  background-image: radial-gradient(circle, rgba(255, 255, 255, 0.20) 1.5px, transparent 1.5px);
  background-size: 32px 32px;
  transform: rotate(20deg);
  pointer-events: none;
  z-index: 0;
}

.cta-block__inner {
  position: relative;
  z-index: 1;
  max-width: 800px;
  margin: 0 auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-24);
}

.cta-block__headline {
  font-family: var(--font-display);
  font-size: var(--text-400);
  font-weight: var(--weight-display);
  line-height: 1.1;
  margin: 0;
}

.cta-block__subhead {
  font-family: var(--font-body);
  font-size: var(--text-200);
  line-height: 1.5;
  margin: 0;
  max-width: 50ch;
}

.cta-block__button {
  display: inline-block;
  background: var(--color-bg-card);
  color: var(--color-rust-shade);
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  text-decoration: none;
  padding: var(--space-16) var(--space-32);
  border-radius: 4px;
}

.surface-dark {
  /* shared utility, focus indicators flip to white on this surface */
}

/* ------------------------------------------------------------------
   Service card — linen-card tile, ~16px corners, top stripe by tone
   ------------------------------------------------------------------ */
.service-card {
  background: var(--color-bg-card);
  color: var(--color-text-body);
  border-radius: 16px;
  padding: var(--space-24);
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
  overflow: hidden;
  position: relative;
}

.service-card__stripe {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
}

.service-card--tone-1 .service-card__stripe { background: var(--color-purple); }
.service-card--tone-2 .service-card__stripe { background: var(--color-green); }
.service-card--tone-3 .service-card__stripe { background: var(--color-rust); }
.service-card--tone-4 .service-card__stripe { display: none; }

.service-card__title {
  font-family: var(--font-display);
  font-size: var(--text-300);
  font-weight: var(--weight-display);
  margin: 0;
}

.service-card__framing {
  font-family: var(--font-body);
  font-size: var(--text-100);
  line-height: 1.5;
  margin: 0;
}

.service-card__link {
  color: var(--color-link-inline);
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  text-decoration: underline;
  text-underline-offset: 3px;
  align-self: flex-start;
  margin-top: auto;
}

/* Stretched-link pattern: the ::after pseudo belongs to the anchor and
   absolutely covers the whole .service-card (which is position: relative
   above), so clicking anywhere on the card navigates. The visible
   "Learn more" text remains the only underlined element -- underline
   sits on the link text, not the pseudo. Trade-off: text selection
   inside the card is intercepted by the pseudo and doesn't work in the
   normal way; for short marketing card copy users won't typically
   select, this is the accepted call. The cursor inherits pointer from
   the anchor automatically when the pseudo is hovered. */
.service-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* ------------------------------------------------------------------
   Home section — Services overview (2x2 grid on body bg).
   Cards are the rounded set (service-card has its own border-radius).
   ------------------------------------------------------------------ */
/* Frame + H2 inherited from .section; the 2x2 service-card grid stays
   per-page. The heading takes a slightly larger bottom margin via
   .home__services-heading override below (services grid wants more
   breathing room than prose). */

.home__services-heading {
  margin-bottom: var(--space-48);
}

.home__services-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-24);
}

.home__services-item {
  display: flex;
}

.home__services-item .service-card {
  flex: 1;
}

@media (max-width: 767px) {
  .home__services-grid {
    grid-template-columns: 1fr;
  }
}

/* ------------------------------------------------------------------
   Home section: Use-cases proof (4 tiles on alt-bg, gallery rhythm).
   Sandwich rhythm: body-bg services above, alt-bg here, body-bg
   portfolio below. Square corners, sitewide gallery gap.
   ------------------------------------------------------------------ */
/* Frame + H2 inherited from .section; grid stays per-page. */

.home__usecases-heading {
  margin-bottom: var(--space-48);
}

.home__usecases-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-16);
}

.home__usecases-item {
  display: flex;
}

.home__usecases-item .usecase-card {
  flex: 1;
}

@media (max-width: 1023px) {
  .home__usecases-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 767px) {
  .home__usecases-grid {
    grid-template-columns: 1fr;
  }
}

/* ------------------------------------------------------------------
   Home section — Portfolio teaser (3 or 4 tiles on body bg).
   Sandwich rhythm: alt-bg use-cases above, body-bg here.
   Sprint 3 item 5.
   ------------------------------------------------------------------ */
/* Frame + H2 inherited from .section; grid stays per-page. */

.home__portfolio-heading {
  margin-bottom: var(--space-48);
}

.home__portfolio-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-16);
}

.home__portfolio-item {
  display: flex;
}

.home__portfolio-item .work-card {
  flex: 1;
}

.home__portfolio-more {
  margin: var(--space-48) 0 0;
}

.home__portfolio-more-link {
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  color: var(--color-link-inline);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.home__portfolio-more-link:hover,
.home__portfolio-more-link:focus-visible {
  color: var(--color-rust);
}

/* No additional mobile rules — base .section mobile padding + heading
   scaling handle the home__portfolio responsive shape. */

/* ------------------------------------------------------------------
   Home section — Partnership teaser (alt-bg callout above CTA).
   Lighter affordance than a full CTA block: button-style link rather
   than rust-surfaced section. Sprint 3 item 6.
   ------------------------------------------------------------------ */
/* Frame + H2 + paragraph inherited from .section + section--alt.
   Section needs position: relative + overflow: hidden to clip the
   decorative ::after dot-grid texture below; the inner needs z-index: 1
   so the content sits above the dot pseudo. */

.home__partnership {
  position: relative;
  overflow: hidden;
}

.home__partnership .section__inner {
  position: relative;
  z-index: 1;
}

.home__partnership-paragraph {
  margin-bottom: var(--space-32);
}

.home__partnership-paragraph strong {
  color: var(--color-rust-shade);
  font-weight: var(--weight-strong);
}

.home__partnership-cta-wrap {
  margin: 0;
}

.home__partnership-button {
  display: inline-block;
  background: var(--color-cta-bg);
  color: var(--color-cta-text);
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  padding: var(--space-12) var(--space-24);
  text-decoration: none;
  transition: background 200ms var(--ease-standard);
}

.home__partnership-button:hover,
.home__partnership-button:focus-visible {
  background: var(--color-rust-shade);
  color: var(--color-cta-text);
}

/* ------------------------------------------------------------------
   Home partnership + CTA dot-grid texture.
   Sprint 5 Iteration 1 item 6: the home next-to-last (partnership)
   dot-grid was removed entirely, and the home-only closing-CTA
   dot-grid was promoted to the shared `.cta-block` component (see
   `.cta-block::after` above) so every page's closing CTA carries it.
   The former `.home__partnership::after` / `.home__cta` /
   `.home__cta::after` / `.home__cta .cta-block__inner` rules are
   retired, not overridden — nothing home-scoped remains here.
   ------------------------------------------------------------------ */
@media (max-width: 767px) {
  .cta-block::after {
    display: none;
  }
}

/* ------------------------------------------------------------------
   Use Cases Hub — gallery rhythm (alt bg, 4 tiles uniform grid).
   3 columns desktop / 2 columns tablet / 1 column mobile. Square
   corners + sitewide gallery gap. The .usecase-card defaults (4/3
   aspect, full-bleed image, rust button overlay, summary below)
   already match the hub pattern; only the grid layout is page-scoped.
   Sprint 5 item 1.
   ------------------------------------------------------------------ */
.use-cases__gallery-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-16);
}

.use-cases__gallery-item {
  display: flex;
}

.use-cases__gallery-item .usecase-card {
  flex: 1;
}

@media (max-width: 1023px) {
  .use-cases__gallery-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 767px) {
  .use-cases__gallery-grid {
    grid-template-columns: 1fr;
  }
}

/* Use-cases CTA wrapper: keeps body-bg backdrop so the alt-bg gallery
   above and the body-bg here alternate. Mirrors the .services__cta /
   .partnership__cta wrappers. Sprint 5 item 1. */
.use-cases__cta {
  background: var(--color-bg-body);
}

/* ------------------------------------------------------------------
   Use-case card — square corners, gallery-pattern tile.
   Gallery variant: full-bleed image + rust button overlay.
   Home variant: smaller alt-bg tile.
   ------------------------------------------------------------------ */
.usecase-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}

.usecase-card__link {
  position: relative;
  display: block;
  color: inherit;
  text-decoration: none;
  overflow: hidden;
}

/* Sprint 3 item 4: the image is now an <img> inside an eleventy-img
   <picture>, not a div with a background-image. The .__media wrapper
   handles positioning, aspect-ratio, and overflow clipping. The image
   class targets the <img> directly so the sprint-2 hover-zoom rule
   continues to apply. */
.usecase-card__media {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background-color: var(--color-bg-alt);
}

.usecase-card__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transform-origin: center;
}

/* Bottom gradient overlay: keeps the rust button legible against any
   thumbnail color. Sits above the image, below the absolute-positioned
   button. */
.usecase-card__media::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 45%;
  background: linear-gradient(to top, rgb(0 0 0 / 0.35) 0%, transparent 100%);
  pointer-events: none;
}

.usecase-card__button {
  position: absolute;
  left: var(--space-16);
  bottom: var(--space-16);
  background: var(--color-cta-bg);
  color: var(--color-cta-text);
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  padding: var(--space-12) var(--space-16);
  font-size: var(--text-100);
}

.usecase-card__summary {
  font-family: var(--font-body);
  font-size: var(--text-100);
  line-height: 1.5;
  margin: 0;
  color: var(--color-text-body);
}

/* Home variant: tiles sit directly on the section's alt-bg (no card
   background, no inner padding). The section provides the surface;
   the image goes full-bleed within the tile. */
.usecase-card--home {
  padding: 0;
}

.usecase-card--home .usecase-card__media {
  aspect-ratio: 3 / 2;
}

/* ------------------------------------------------------------------
   Work card — square corners, gallery tile with external-link icon.
   Sprint 3 item 5 refactor: image is <img> inside eleventy-img <picture>,
   wrapped in .__media which handles aspect-ratio + clipping. Mirrors the
   usecase-card refactor from sprint 3 item 4. Hover-zoom + button-shift
   transitions live in the Sitewide Hover Patterns block further down.
   ------------------------------------------------------------------ */
.work-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}

/* Stacked card (Sprint 5 Iteration 1, Jason's restructure): the link
   is a centered flex column — image, then centered description text,
   then the centered button BELOW the text (no overlay). On /work a
   thin divider line sits below the button (gallery-only, see below).
   reference-reactions.md > Layout > Easton-style gallery: uniform
   tiles, generous whitespace rhythm; our variance is the centered
   text between image and button. */
.work-card__link {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-16);
  color: inherit;
  text-decoration: none;
  text-align: center;
}

.work-card__media {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background-color: var(--color-bg-alt);
}

.work-card__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transform-origin: center;
}

.work-card__description {
  font-family: var(--font-body);
  font-size: var(--text-100);
  line-height: 1.5;
  margin: 0;
  color: var(--color-text-body);
  text-align: center;
}

/* Button centered, BELOW the description text (no longer overlaid on
   the image — Jason's call). Solid rust pill keeps the white label
   legible on its own (white-on-rust = 5.21:1). white-space: nowrap
   keeps long labels (e.g. "Signature Hospitality Solutions") on one
   line; the button sizes to its content. */
.work-card__button {
  align-self: center;
  max-width: 100%;
  background: var(--color-cta-bg);
  color: var(--color-cta-text);
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  padding: var(--space-12) var(--space-16);
  font-size: var(--text-100);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  white-space: nowrap;
  gap: var(--space-8);
}

.work-card__icon {
  width: 14px;
  height: 14px;
  display: block;
  fill: currentColor;
}

.work-card--teaser .work-card__media {
  aspect-ratio: 4 / 3;
}

/* Gallery variant: matches the home teaser aspect for visual consistency
   across the two surfaces where work tiles render. Sprint 5 item 6. */
.work-card--gallery .work-card__media {
  aspect-ratio: 4 / 3;
}

/* Divider line below the button — /work gallery ONLY (Jason's call;
   the home teaser does not get the line). Rendered as a link ::after
   so it follows the button (the link's last child). Faint rust-deep
   hairline reads cleanly on the gallery's linen-alt section bg. */
.work-card--gallery .work-card__link::after {
  content: "";
  display: block;
  width: 100%;
  height: 1px;
  margin-top: var(--space-8);
  background-color: rgba(107, 63, 39, 0.25);
  pointer-events: none;
}

/* ------------------------------------------------------------------
   Work — gallery rhythm (alt bg, uniform tile grid).
   2 columns desktop / 1 column mobile. Square corners + sitewide
   gallery gap. Easton-style uniform gallery (reference-reactions.md >
   Layout > Easton-style gallery): fewer, larger uniform tiles read as
   a curated portfolio rather than a dense thumbnail wall. Scoped
   per-page (NOT shared with the home teaser, which keeps its own
   3-column .home__portfolio grid). Sprint 5 Iteration 1 item 2.
   ------------------------------------------------------------------ */
.work__gallery-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  /* Row gap > column gap (Jason's call): extra breathing room between
     a cell's description text and the top of the image in the row
     below. /work only — home teaser keeps .home__portfolio-grid. */
  gap: var(--space-48) var(--space-16);
}

.work__gallery-item {
  display: flex;
}

.work__gallery-item .work-card {
  flex: 1;
}

@media (max-width: 767px) {
  .work__gallery-grid {
    grid-template-columns: 1fr;
  }
}

/* Work CTA wrapper: keeps body-bg backdrop so the alt-bg gallery above
   and the body-bg here alternate. Mirrors .use-cases__cta /
   .services__cta / .partnership__cta wrappers. Sprint 5 item 6. */
.work__cta {
  background: var(--color-bg-body);
}

/* ===== Accessibility =====
   Sprint 1 Item 10. Skip link, sr-only utility, focus-visible
   outline that flips on dark surfaces and CTA buttons. */

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

.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  background: var(--color-bg-card);
  color: var(--color-text-body);
  padding: var(--space-12) var(--space-16);
  z-index: 100;
  text-decoration: none;
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  transform: translateY(-100%);
  transition: transform 150ms var(--ease-standard);
}

.skip-link:focus,
.skip-link:focus-visible {
  transform: translateY(0);
  outline: 2px solid var(--color-focus-light);
  outline-offset: 2px;
}

:focus-visible {
  outline: 2px solid var(--color-focus-light);
  outline-offset: 2px;
}

.surface-dark :focus-visible,
.cta-block__button:focus-visible {
  outline-color: var(--color-focus-dark);
}

/* ------------------------------------------------------------------
   Logo Kinetic System — Sprint 3 Iter 3 Item 5 (STAGE C in progress)
   Implements docs/spec.md > Motion System > Logo Kinetic System.

   Two behaviors:
   1. Hero logo (`.logo--hero`, home `/` only) — staged rework of the
      3-part assembly load-in. All three polygons use the same clip-
      path inset mechanic; the difference is direction. v-left and
      v-right reveal bottom-up (their natural axis goes up-and-left /
      up-and-right). c-tail reveals top-down because its geometry
      extends from a top point near the V's interior peak down to
      the SVG's bottom-right corner.
      Stagger: v-left 0ms, v-right 200ms, c-tail 800ms. Per-leg
      duration 700ms. Total motion budget 1500ms.
   2. Header logo (`.logo--header`, sitewide) — pre-assembled. On
      hover, the 3 polygons translate apart over 350ms; unhover
      returns them. Unchanged by Item 5 (Item 6 reworks this with
      the vocabulary established here in Item 5).

   Reduced motion: hero load-in disabled (site.js bails before adding
   `.logo--hero-loaded`); CSS @media block at the foot of this section
   is defense-in-depth. Header hover-split disabled and replaced with
   a static fill darken to --color-rust-shade.
   ------------------------------------------------------------------ */

/* STAGE A — v-left "grow up from the bottom along its diagonal".
   Reveal via clip-path: inset() that retreats from the top edge of
   the polygon's bounding box. At start, the clip covers 100% from
   the top, leaving nothing visible. As the clip retreats to 0,
   the polygon is exposed from its bottom edge upward. Because v-
   left's geometry is a trapezoid leaning up-and-to-the-left (bottom
   edge near the V vertex; top edge over at the SVG's left), the
   exposed silhouette extends along that diagonal as more of the
   polygon becomes visible — the polygon "grows out of the bottom"
   along its own axis. No transform, no opacity fade: full size, full
   color throughout. 700ms ease-standard. */
.logo--hero:not(.logo--hero-loaded) .logo__polygon--v-left {
  clip-path: inset(100% 0 0 0);
}

.logo--hero.logo--hero-loaded .logo__polygon--v-left {
  animation: logo-assemble-v-left var(--duration-logo-leg) var(--ease-standard) 0ms both;
}

/* STAGE B — v-right "grow up from the bottom along its diagonal".
   Same mechanic as v-left: clip-path: inset() retreats from the top
   of the polygon's bounding box. CSS clip-path goes on a wrapping
   <g class="logo__group--v-right"> (added in logo.njk) so the inner
   polygon's existing SVG clip-path attribute (the stroke-inset trick
   via url(#clip-v-right-...)) is preserved. 200ms stagger delay
   relative to Stage A so v-left enters first and v-right follows
   slightly behind. v-right is the inner outline of the V (carved
   by the stroke-clip), so the reveal "draws" the right-side outline
   bottom-up. */
.logo--hero:not(.logo--hero-loaded) .logo__group--v-right {
  clip-path: inset(100% 0 0 0);
}

.logo--hero.logo--hero-loaded .logo__group--v-right {
  animation: logo-assemble-v-right var(--duration-logo-leg) var(--ease-standard) 200ms both;
}

/* STAGE C — c-tail "appear from the top, moving down and to the right".
   Same clip-path mechanic as v-left and v-right but in the opposite
   direction. Pre-load: inset(0 0 100% 0) clips 100% from the BOTTOM,
   leaving nothing visible. Animation retreats the bottom clip to 0,
   exposing the polygon from its top edge downward. c-tail's geometry
   has its top vertex near (273, 57) — sitting at the V's interior
   peak where v-right's top meets — and extends down-and-right to the
   SVG's bottom-right corner. So the top-down reveal naturally
   propagates down-and-right along c-tail's diagonal. No wrap needed:
   c-tail has no existing SVG clip-path attribute, so CSS clip-path
   applies directly to the polygon. 800ms delay so c-tail enters
   when v-right is visually almost completely formed (with ease-
   standard's fast-start curve, v-right hits ~95%+ visible by 800ms
   = 6/7 of its 700ms duration, despite being only 86% through time). */
.logo--hero:not(.logo--hero-loaded) .logo__polygon--c-tail {
  clip-path: inset(0 0 100% 0);
}

.logo--hero.logo--hero-loaded .logo__polygon--c-tail {
  animation: logo-assemble-c-tail var(--duration-logo-leg) var(--ease-standard) 400ms both;
}

/* Hero logo sizing + centering. Lives inside .hero__panel--two on home.
   Sized to read as primary brand mark per PRD. Parent .hero__panel is
   already display: flex; align-items: center so vertical centering
   is inherited; justify-content centers horizontally inside the panel. */
.hero__panel--two {
  justify-content: center;
}

.hero__panel--two .logo--hero {
  width: clamp(180px, 30vw, 320px);
  height: auto;
  margin: 0 auto;
  display: block;
}

/* Hero logo on green: flip --logo-bg to white so the v-right polygon
   reads as a distinct shape against the green panel. Foreground stays
   black (Iteration 1: hero logo always renders in --color-text-body
   regardless of panel). Reading order on green: white halo around
   everything, black left trapezoid, white-with-inset-black-outline
   middle wedge, black right trapezoid. Halo/inset asymmetry tuning
   (halo radius=3 thicker than inset inner-visible 2.5) carried by the
   base rule above. */
.hero__panel--green .logo--hero {
  --logo-fg: var(--color-text-body);
  --logo-bg: #fff;
}

/* STAGE A + B keyframes — clip-path inset retreats from the top of
   the polygon's bounding box (bottom-up reveal). v-left clip applies
   directly; v-right clip applies on the wrapping <g class="logo__group
   --v-right"> so the inner polygon's SVG clip-path attribute (stroke-
   inset trick) is preserved. */
@keyframes logo-assemble-v-left {
  from { clip-path: inset(100% 0 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

@keyframes logo-assemble-v-right {
  from { clip-path: inset(100% 0 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* STAGE C keyframe — clip-path inset retreats from the BOTTOM of
   the polygon's bounding box (top-down reveal). c-tail extends down-
   and-right from its top vertex, so the top-down clip reveals the
   polygon's silhouette growing down-and-right along its natural axis. */
@keyframes logo-assemble-c-tail {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* Header + footer logo hover-cycle — Sprint 3 Iter 3 Item 6.

   Mirrors the hero load-in clip-path mechanic as a one-shot "unbuild
   then rebuild" cycle on hover. All three polygons run in sync (no
   stagger). At the cycle midpoint each polygon partially retreats
   along its load-in axis: v-left and v-right retreat from the top
   down (mirroring their bottom-up load-in direction); c-tail
   retreats from the bottom up (mirroring its top-down load-in
   direction). Partial retreat depth: 40% (~60% of the polygon
   visible at midpoint). Total cycle: 500ms (250ms unbuild + 250ms
   rebuild).

   No fill-mode on the animation: when :hover ends, the animation
   rule stops applying and the polygon returns to its default state.
   The cycle ends at the rest state, so a completed cycle followed
   by hover-off has no visible jump; hover-off mid-cycle snaps to
   rest. Per Jason's spec: no return animation on hover-end.

   Both header and footer logos render with variant="header" (single
   .logo--header class), so a single rule set covers both via the
   .site-header__logo-link / .site-footer__logo-link parent-link
   selectors. Trigger scoped to the parent link so hovering the
   adjacent wordmark in the header also fires the cycle via :hover
   propagation up the DOM.

   v-right's clip-path goes on its wrapping <g class="logo__group
   --v-right"> (added in Item 5) so the inner polygon's existing SVG
   clip-path attribute (stroke-inset trick) is preserved. v-left and
   c-tail apply directly to their polygons. */
.logo--header .logo__polygon--v-left,
.logo--header .logo__polygon--v-right,
.logo--header .logo__polygon--c-tail {
  transition: fill 200ms var(--ease-standard);
}

.site-header__logo-link:hover .logo--header .logo__polygon--v-left,
.site-footer__logo-link:hover .logo--header .logo__polygon--v-left {
  animation: hover-cycle-v-left 500ms var(--ease-standard);
}

.site-header__logo-link:hover .logo--header .logo__group--v-right,
.site-footer__logo-link:hover .logo--header .logo__group--v-right {
  animation: hover-cycle-v-right 500ms var(--ease-standard);
}

.site-header__logo-link:hover .logo--header .logo__polygon--c-tail,
.site-footer__logo-link:hover .logo--header .logo__polygon--c-tail {
  animation: hover-cycle-c-tail 500ms var(--ease-standard);
}

@keyframes hover-cycle-v-left {
  0%   { clip-path: inset(0 0 0 0); }
  50%  { clip-path: inset(40% 0 0 0); }
  100% { clip-path: inset(0 0 0 0); }
}

@keyframes hover-cycle-v-right {
  0%   { clip-path: inset(0 0 0 0); }
  50%  { clip-path: inset(40% 0 0 0); }
  100% { clip-path: inset(0 0 0 0); }
}

@keyframes hover-cycle-c-tail {
  0%   { clip-path: inset(0 0 0 0); }
  50%  { clip-path: inset(0 0 40% 0); }
  100% { clip-path: inset(0 0 0 0); }
}

/* Reduced motion: hero renders assembled instantly; header hover does
   NOT split — polygon fill darkens to rust-shade instead. */
@media (prefers-reduced-motion: reduce) {
  .logo--hero .logo__polygon--v-left,
  .logo--hero .logo__polygon--v-right,
  .logo--hero .logo__polygon--c-tail {
    animation: none;
    transform: none;
    opacity: 1;
  }

  /* Stages A, B, C: the pre-load rules hide each polygon behind a
     clip-path: inset() until .logo--hero-loaded lands. Under reduced
     motion site.js bails before adding that class (defense in depth
     — see site.js initHeroLogoLoadIn), so the pre-load clips would
     otherwise leave the polygons invisible. Clear them explicitly. */
  .logo--hero .logo__polygon--v-left,
  .logo--hero .logo__group--v-right,
  .logo--hero .logo__polygon--c-tail {
    clip-path: none;
  }

  /* Item 6 hover-cycle: kill the clip-path keyframe animations
     entirely. The fill-darken below still applies as the affordance
     fallback. v-right targets its wrap group (matching the active
     hover rule). */
  .site-header__logo-link:hover .logo--header .logo__polygon--v-left,
  .site-header__logo-link:hover .logo--header .logo__group--v-right,
  .site-header__logo-link:hover .logo--header .logo__polygon--c-tail,
  .site-footer__logo-link:hover .logo--header .logo__polygon--v-left,
  .site-footer__logo-link:hover .logo--header .logo__group--v-right,
  .site-footer__logo-link:hover .logo--header .logo__polygon--c-tail {
    animation: none;
  }

  /* Darken foreground polygons only. .logo__polygon--v-right is the
     valley carve-out (filled with --logo-bg to match surface); leave
     it alone so the silhouette stays correct. */
  .site-header__logo-link:hover .logo--header .logo__polygon--v-left,
  .site-header__logo-link:hover .logo--header .logo__polygon--c-tail,
  .site-footer__logo-link:hover .logo--header .logo__polygon--v-left,
  .site-footer__logo-link:hover .logo--header .logo__polygon--c-tail {
    fill: var(--color-rust-shade);
  }
}

/* ------------------------------------------------------------------
   Sitewide Hover Patterns — Sprint 2 Item 6
   Implements docs/spec.md > Motion System > Image Hover Zoom
   + docs/spec.md > Motion System > Reduced Motion — Summary Table
   + docs/spec.md > CTA Pattern
   + prd.md > Motion System > "subtle element-level motion" (hover zoom).

   Three interaction-feedback patterns, all of which RUN under
   reduced motion per the spec summary table (image/tile hover zoom,
   CTA button hover bg, inline link hover color shift):

   1. Image hover zoom on `.usecase-card__image` and `.work-card__image`
      — parent `:hover` scales the image to 1.03 over 200ms. The
      `__link` wrappers already declare `overflow: hidden` (sprint 1).
   2. CTA button + tile-overlay button hover background shift. The
      tile-overlay buttons default to rust (`--color-cta-bg`) and
      darken straight to rust-deep on hover. `.cta-block__button` is
      a linen pill on the rust CTA surface (sprint 1 default); to
      preserve contrast, hover flips the surface to rust-deep AND the
      label to white in one rule (word + icon parity — there's no
      icon here, but the principle stands).
   3. Inline prose links (scoped to `main p a` and `main li a` so the
      rule doesn't affect header CTA / mobile panel CTA / hamburger /
      tile-overlay buttons / footer nav, all of which carry their own
      component-scoped link rules at higher specificity). Color
      transitions to a fuller `--color-text-body` on hover and focus.

   Word + icon hover consistency on `.work-card__button`: the SVG
   external-link icon uses `fill="currentColor"` in the inline SVG
   (work-card.njk) and `.work-card__icon { fill: currentColor; }` in
   the existing CSS — so the icon inherits the button's color and
   moves with the same hover state automatically.
   ------------------------------------------------------------------ */

/* 1. Image hover zoom — runs under reduced motion (interaction feedback). */
.usecase-card__image,
.work-card__image {
  transition: transform var(--duration-hover-l) var(--ease-standard);
}

.usecase-card__link:hover .usecase-card__image,
.usecase-card__link:focus-visible .usecase-card__image,
.work-card__link:hover .work-card__image,
.work-card__link:focus-visible .work-card__image {
  transform: scale(1.03);
}

/* 2. Tile-overlay buttons: rust → rust-deep on parent link hover.
   Hover target is the parent `__link` so the entire tile is the
   interaction surface (word + image are one target). */
.usecase-card__button,
.work-card__button {
  transition: background-color var(--duration-hover-l) var(--ease-standard);
}

.usecase-card__link:hover .usecase-card__button,
.usecase-card__link:focus-visible .usecase-card__button,
.work-card__link:hover .work-card__button,
.work-card__link:focus-visible .work-card__button {
  background-color: var(--color-rust-shade);
}

/* 2b. CTA block button: linen pill on rust surface (sprint 1 default).
   On hover, flip to rust-deep bg + white label so contrast holds and
   the button reads as "engaged." Color transitions together with bg
   so any inline icon (currentColor) would move in lockstep. */
.cta-block__button {
  transition:
    background-color var(--duration-hover-l) var(--ease-standard),
    color var(--duration-hover-l) var(--ease-standard);
}

.cta-block__button:hover,
.cta-block__button:focus-visible {
  background-color: var(--color-rust-shade);
  color: var(--color-text-on-dark);
}

/* 3. Inline prose links — scoped to in-body paragraph + list content
   so component-scoped link rules (header CTA, mobile CTA, hamburger,
   tile-overlay buttons, footer nav) remain untouched. Spec doesn't
   pin a hover target color, so the locked phrasing reads: rust-deep
   default → body text on hover, with `text-decoration-thickness`
   shifting from 1px to 2px for a tactile thickness affordance. */
main p a,
main li a {
  color: var(--color-link-inline);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition:
    color var(--duration-hover) var(--ease-standard),
    text-decoration-thickness var(--duration-hover) var(--ease-standard);
}

main p a:hover,
main p a:focus-visible,
main li a:hover,
main li a:focus-visible {
  color: var(--color-text-body);
  text-decoration-thickness: 2px;
}

/* ------------------------------------------------------------------
   Element-level parallax, Sprint 3 Item 7.
   Implements docs/spec.md > Motion System > Element-Level Parallax.
   IO + rAF in site.js updates the --parallax-y custom property per
   visible [data-parallax-rate] target. `will-change: transform`
   stays scoped to parallax targets, never promoted sitewide.

   Reduced motion + mobile (< 768px) are gated in JS (early return)
   AND here in CSS (transform: none) for defense in depth.
   ------------------------------------------------------------------ */
[data-parallax-rate] {
  transform: translateY(var(--parallax-y, 0));
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  [data-parallax-rate] {
    transform: none;
  }
}

@media (max-width: 767px) {
  [data-parallax-rate] {
    transform: none;
  }
}

/* Sprint 4 prose width: prose paragraphs flow to the full content
   inner-wrapper width (var(--content-max-width) ≈ 1200-1600px), matching
   the bento + home grid sections. An earlier reading-measure max-width
   of 65ch was removed because it caused the visible "right edge stops
   short" effect; per Jason, prose stretches to the inner wrapper edge. */

/* ------------------------------------------------------------------
   Work hero — rust-left / dynamic-right coverflow carousel.
   Per-page scope via .hero--work. Right panel renders 3-position
   coverflow of work[*].thumbnail screenshots (center top-anchored +
   cut off at panel bottom, prev/next siblings tilted on Y axis at
   panel edges). Panel bg color shifts per active frame's heroColor
   value. CTA button anchors at panel bottom, links to active site
   (new tab). Auto-rotate 9s, pause on hover, pause 30s after click.
   Reduced-motion: no auto-rotate, no transitions, static three-up
   composition preserved. Below 768px: single-frame mode. Full
   mechanism spec at docs/sprint-7-story4-work-hero.md.
   Sprint 7 Story 4.
   ------------------------------------------------------------------ */
.hero--work .hero__panel--two {
  /* Panel becomes the carousel stage. Override the sitewide overflow:
     hidden with the same value (explicit) so future spec lift is
     transparent. Coverflow siblings extend slightly past edges via
     translate; overflow: hidden keeps them clipped to the panel. */
  padding: 0;
  overflow: hidden;
  /* Per-site bg color, driven by the --carousel-bg custom property
     written by JS on rotation. Default to the first entry's heroColor
     (inline style on the panel renders the initial value). */
  background-color: var(--carousel-bg, #000);
  transition: background-color 700ms var(--ease-standard);
  /* Establish 3D context for the tilted siblings. */
  perspective: 1200px;
}

.work-carousel__stage {
  position: relative;
  /* Stage fills the full panel; frames are absolutely positioned
     within it so they can overlap and transform without affecting
     layout. */
  position: absolute;
  inset: 0;
  /* Sit above the sitewide .hero__panel--two::before dot grid. */
  z-index: 1;
  transform-style: preserve-3d;
}

.work-carousel__frame {
  /* Reset button chrome — frames are <button> for keyboard
     accessibility but render as positioned media containers. */
  appearance: none;
  border: 0;
  background: transparent;
  margin: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  /* Absolute positioning relative to .work-carousel__stage. Each
     frame's "slot" class (.is-slot-center / --prev / --next /
     --offscreen-*) drives the transform. */
  position: absolute;
  top: 6%;
  left: 50%;
  width: 70%;
  /* Frame aspect-ratio matches the source screenshot aspect (4:3)
     so the cover-fit image inside doesn't have to horizontally
     crop a landscape screenshot into a tall frame. The frame sits
     near the top of the panel with the heroColor bg visible above
     (small gap), to the sides, and below (room for the CTA button).
     Reads as a framed screenshot on a tinted gallery surface — the
     "bottom cut off" intent is carried by the visual stacking of the
     CTA button overlaying the frame's lower edge. */
  aspect-ratio: 4 / 3;
  /* Center the frame's transform origin at the bottom-center so the
     tilt + scale gestures pivot from the visual base, reading as
     "rotating in place" rather than "rotating around an arbitrary
     point". */
  transform-origin: 50% 100%;
  transition:
    transform 700ms var(--ease-standard),
    opacity 700ms var(--ease-standard);
  will-change: transform, opacity;
}

.work-carousel__frame:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.85);
  outline-offset: 4px;
}

.work-carousel__frame-media {
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
  /* Subtle separation from the panel bg for dark frames. */
  border: 1px solid rgba(255, 255, 255, 0.18);
  /* Drop shadow only on the active center frame (rule below). */
  background: #000;
}

.work-carousel__frame-media picture {
  display: block;
  width: 100%;
  height: 100%;
}

.work-carousel__frame-media img {
  display: block;
  width: 100%;
  height: 100%;
  /* Top-of-screenshot alignment: cover-crop the image into the frame,
     biased to the top so the bottom is the part that gets clipped at
     the panel's bottom edge. */
  object-fit: cover;
  object-position: top center;
}

/* Slot transforms (centered around the panel midpoint).
   translateX is relative to the frame's already-translated
   "left: 50%" anchor — so 0 = exact center. */
.work-carousel__frame.is-slot-center {
  transform: translateX(-50%) translateZ(0) rotateY(0) scale(1);
  opacity: 1;
  /* Above the click zones (z:2) so the center frame's own click
     handler (open link in new tab) wins inside the center area. */
  z-index: 3;
}

/* Tilted sibling previews are visible only; the actual click targets
   for prev/next nav are the .work-carousel__zone--* buttons (below).
   Disable pointer events on the sibling frames so they don't absorb
   clicks meant for the zones. */
.work-carousel__frame.is-slot-prev {
  transform: translateX(-110%) translateZ(-80px) rotateY(28deg) scale(0.52);
  opacity: 0.78;
  z-index: 1;
  pointer-events: none;
}

.work-carousel__frame.is-slot-next {
  transform: translateX(10%) translateZ(-80px) rotateY(-28deg) scale(0.52);
  opacity: 0.78;
  z-index: 1;
  pointer-events: none;
}

.work-carousel__frame.is-slot-offscreen-left {
  transform: translateX(-200%) translateZ(-80px) rotateY(28deg) scale(0.52);
  opacity: 0;
  z-index: 0;
  pointer-events: none;
}

.work-carousel__frame.is-slot-offscreen-right {
  transform: translateX(100%) translateZ(-80px) rotateY(-28deg) scale(0.52);
  opacity: 0;
  z-index: 0;
  pointer-events: none;
}

/* Click zones — invisible buttons covering the empty half-panel
   areas left and right of the center frame. Click anywhere on the
   left half = previous; right half = next. Zones span the full
   panel height; the center frame sits above them (z:3) so its own
   click still wins in the center area.

   Width = 35% so the two zones together cover the panel edges and
   leave a ~30% center gutter for the center frame's own hit area.
   Tuned so the visible tilted siblings fall comfortably inside the
   zones, and the click feel matches Jason's brief ("the left and
   right area around the main image"). */
.work-carousel__zone {
  appearance: none;
  border: 0;
  background: transparent;
  margin: 0;
  padding: 0;
  position: absolute;
  top: 0;
  bottom: 0;
  width: 35%;
  z-index: 2;
  cursor: pointer;
}

.work-carousel__zone--prev { left: 0; }
.work-carousel__zone--next { right: 0; }

.work-carousel__zone:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.85);
  outline-offset: -8px;
}

/* Active (center) frame gets the depth shadow that lifts it visually
   forward of the tilted siblings. */
.work-carousel__frame.is-slot-center .work-carousel__frame-media {
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.35);
}

/* CTA button — anchored at panel bottom, centered horizontally,
   overlays the cut-off zone of the center screenshot. White CTA
   surface (NOT the sitewide rust CTA — the rust would clash with
   per-site dark bgs at low contrast on dark-teal; white + dark
   text reads on all three confirmed heroColor values). */
.work-carousel__cta {
  position: absolute;
  bottom: var(--space-24);
  left: 50%;
  transform: translateX(-50%);
  /* Above the click zones (z:2) and the center frame (z:3) so the
     CTA's own click is the actionable thing on its surface. */
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  background: var(--color-bg-card);
  color: var(--color-text-body);
  font-family: var(--font-body);
  font-weight: var(--weight-strong);
  font-size: var(--text-200);
  text-decoration: none;
  padding: var(--space-12) var(--space-24);
  border-radius: 4px;
  border: 1px solid rgba(255, 255, 255, 0.30);
  transition:
    background-color var(--duration-hover-l) var(--ease-standard),
    color var(--duration-hover-l) var(--ease-standard);
  white-space: nowrap;
}

.work-carousel__cta:hover,
.work-carousel__cta:focus-visible {
  background: var(--color-rust);
  color: var(--color-text-on-dark);
}

.work-carousel__cta-label {
  /* Crossfade the label text on rotation. Use opacity, swap text mid-
     transition via JS (see initWorkCarousel in site.js). */
  transition: opacity 350ms var(--ease-standard);
}

.work-carousel__cta.is-label-fading .work-carousel__cta-label {
  opacity: 0;
}

.work-carousel__cta-icon {
  display: block;
  flex-shrink: 0;
}

/* Below 768px: carousel is hidden entirely. The /work hero
   becomes a rust-only single-panel headline + sub-line; the
   carousel is a desktop affordance and the gallery below already
   shows the same sites at mobile scale, so the carousel adds
   nothing at narrow widths. */
@media (max-width: 767.98px) {
  .hero--work {
    /* Sitewide hero mobile grid is 45vh + 55vh (2 rows); collapse to
       a single auto row so the rust panel takes only its needed
       height instead of leaving an empty 55vh gap. */
    grid-template-rows: auto;
  }

  .hero--work .hero__panel--two {
    display: none;
  }

  /* The "between" spacer (separator bar between the two stacked
     panels at mobile) is meaningless with only one panel. The
     "bottom" spacer at sitewide width is part of the spacer frame
     around the panel layout; with the bottom panel hidden, it sits
     at the rust panel's lower edge as the closing affordance —
     keep it. */
  .hero--work .hero__spacer--between {
    display: none;
  }
}

/* Reduced motion: kill all carousel transitions. Auto-rotate is
   gated in JS; this rule covers the manual-nav case (instant slot
   swap, no animated transform/opacity/color). The three-up static
   composition is preserved so the visual "this is one of many"
   signal still reads at rest. */
@media (prefers-reduced-motion: reduce) {
  .hero--work .hero__panel--two,
  .work-carousel__frame,
  .work-carousel__cta-label {
    transition: none;
  }
}

