/* ===================================================================
   JSM shared components
   Added 2026-08-09.

   Source of truth. Edit THIS file, then run:

       python3 _tools/build-components-css.py

   which hashes it into public/assets/jsm-components.<hash>.css and
   repoints every page's <link> at the new filename. The hash is what
   makes the year long immutable cache in public/_headers safe.

   Why classes and not inline styles: the base .jsm block in each page
   targets elements with [style*="..."] selectors, and the reveal script
   re-serialises style attributes at runtime, inserting a space after
   every comma. That silently breaks those selectors. Every component
   here carries a real class so it never depends on attribute matching.
   =================================================================== */

/* --- Section furniture ------------------------------------------- */
.jsmc-wrap{max-width:1080px;margin:0 auto;padding:0 32px}
.jsmc-sec{padding:78px 0}
.jsmc-sec.is-paper{background:#F7F9FA}
.jsmc-sec.is-white{background:#FDFDFD}
/* 620px capped the HEADING as well as the lede, so a 48px h2 wrapped to two
   lines on /faq and elsewhere. The head runs wide and the lede keeps its own
   measure, so headings sit on one line and body copy still reads at ~65ch. */
.jsmc-head{display:flex;flex-direction:column;gap:16px;max-width:920px;margin-bottom:40px}
.jsmc-h2{text-wrap:balance}
.jsmc-lede{max-width:620px}
.jsmc-eyebrow{font-size:10.5px;font-weight:500;letter-spacing:0.34em;text-transform:uppercase;color:#5D6B76}
.jsmc-h2{margin:0;font-weight:500;font-size:clamp(30px,3.8vw,48px);line-height:1.08;letter-spacing:-0.03em;color:#2C3E50}
.jsmc-h2 span{font-weight:200;color:rgba(44,62,80,0.72)}
.jsmc-lede{margin:0;font-size:16px;font-weight:300;line-height:1.75;color:#34495E}

/* --- FAQ accordion ------------------------------------------------
   Native <details>, so every answer is in the DOM and indexable whether
   or not it is open, and it works with JavaScript off. The animation is
   pure enhancement. --------------------------------------------- */
.jsmfaq{display:flex;flex-direction:column;max-width:860px}
.jsmfaq-item{border-top:1px solid rgba(189,195,199,0.6)}
.jsmfaq-item:last-child{border-bottom:1px solid rgba(189,195,199,0.6)}

.jsmfaq-q{cursor:pointer;list-style:none;display:flex;align-items:flex-start;justify-content:space-between;gap:24px;padding:23px 0;font-size:17px;font-weight:400;letter-spacing:-0.015em;line-height:1.45;color:#2C3E50;transition:color .2s}
.jsmfaq-q::-webkit-details-marker{display:none}
.jsmfaq-q::marker{content:''}
.jsmfaq-q:hover{color:#5D3A9B}

/* Plus that becomes a minus. Two 1px rules, one rotated on open. */
.jsmfaq-ico{position:relative;flex:0 0 17px;width:17px;height:17px;margin-top:5px}
.jsmfaq-ico::before,.jsmfaq-ico::after{content:'';position:absolute;background:#5D6B76;transition:transform .42s cubic-bezier(.22,.61,.36,1),background .2s}
.jsmfaq-ico::before{top:8px;left:0;width:17px;height:1px}
.jsmfaq-ico::after{top:0;left:8px;width:1px;height:17px}
.jsmfaq-item[open] .jsmfaq-ico::after{transform:rotate(90deg)}
.jsmfaq-q:hover .jsmfaq-ico::before,.jsmfaq-q:hover .jsmfaq-ico::after{background:#5D3A9B}

.jsmfaq-a{padding:0 0 25px}

/* THE CASCADE TRAP. This stylesheet is a <link> in the head; each page's
   base .jsm block is an inline <style> in the body, so it comes LATER in
   document order and wins every tie. The base resets `.jsm p{margin:0}` and
   `.jsm a{color:#2C3E50}`, both specificity 0,1,1 — exactly what a bare
   `.jsmfaq-a p` scores. Unprefixed, the paragraph gap silently collapsed to
   zero and answer links rendered navy with no underline.

   So any component rule setting margin on p/h1/h2/h3, or colour on a, is
   prefixed with .jsm to reach 0,2,1 and win regardless of order. Rules that
   set properties the base never touches do not need it. */
.jsm .jsmfaq-a p{margin:0 0 13px;max-width:720px;font-size:15px;font-weight:300;line-height:1.75;color:#34495E}
.jsm .jsmfaq-a p:last-child{margin-bottom:0}
.jsm .jsmfaq-a a{color:#5D3A9B;border-bottom:1px solid rgba(93,58,155,0.35)}
.jsmfaq-item[open] .jsmfaq-a{animation:jsmfaqIn .42s cubic-bezier(.22,.61,.36,1)}
@keyframes jsmfaqIn{from{opacity:0;transform:translateY(-7px)}to{opacity:1;transform:none}}

/* Height slide, where the browser can animate to a keyword size.
   Older browsers skip this block and the panel simply appears, so no
   one loses an answer to an unsupported property. */
@supports selector(::details-content){
  :root{interpolate-size:allow-keywords}
  .jsmfaq-item::details-content{block-size:0;overflow:hidden;transition:block-size .42s cubic-bezier(.22,.61,.36,1),content-visibility .42s allow-discrete;transition-behavior:allow-discrete}
  .jsmfaq-item[open]::details-content{block-size:auto}
}

/* --- Feature cards with pop out definitions -----------------------
   The grid is a 1px background showing through the gaps, so the cells
   share hairlines instead of each drawing its own. --------------- */
.jsmfeat{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:rgba(189,195,199,0.6);border:1px solid rgba(189,195,199,0.6);border-radius:2px;overflow:hidden}
.jsmfeat.is-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.jsmfeat.is-2{grid-template-columns:repeat(2,minmax(0,1fr))}

.jsmfeat-item{background:#FDFDFD;transition:background .2s}
.jsmfeat-item:hover{background:#F7F9FA}
.jsmfeat-item[open]{background:#F7F9FA}
.jsmfeat-top{cursor:pointer;list-style:none;display:flex;flex-direction:column;gap:10px;padding:26px 24px}
.jsmfeat-top::-webkit-details-marker{display:none}
.jsmfeat-top::marker{content:''}
.jsmfeat-name{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;font-size:16.5px;font-weight:500;letter-spacing:-0.015em;line-height:1.3;color:#2C3E50}
.jsmfeat-line{font-size:14px;font-weight:300;line-height:1.7;color:#5D6B76}
.jsmfeat-ico{position:relative;flex:0 0 13px;width:13px;height:13px;margin-top:3px}
.jsmfeat-ico::before,.jsmfeat-ico::after{content:'';position:absolute;background:#5D6B76;transition:transform .42s cubic-bezier(.22,.61,.36,1)}
.jsmfeat-ico::before{top:6px;left:0;width:13px;height:1px}
.jsmfeat-ico::after{top:0;left:6px;width:1px;height:13px}
.jsmfeat-item[open] .jsmfeat-ico::after{transform:rotate(90deg)}
.jsmfeat-def{padding:0 24px 26px}
.jsm .jsmfeat-def p{margin:0;padding-top:16px;border-top:1px solid rgba(189,195,199,0.6);font-size:14.5px;font-weight:300;line-height:1.75;color:#34495E}
/* Definitions on the videography meta page link through to the page that
   covers each format. Without this they inherit .jsm a's navy and read as
   body text, which is worse than not linking them at all. */
.jsm .jsmfeat-def a{color:#5D3A9B;border-bottom:1px solid rgba(93,58,155,0.35)}
.jsmfeat-item[open] .jsmfeat-def{animation:jsmfaqIn .42s cubic-bezier(.22,.61,.36,1)}
@supports selector(::details-content){
  .jsmfeat-item::details-content{block-size:0;overflow:hidden;transition:block-size .42s cubic-bezier(.22,.61,.36,1),content-visibility .42s allow-discrete;transition-behavior:allow-discrete}
  .jsmfeat-item[open]::details-content{block-size:auto}
}

/* --- Asset placeholders -------------------------------------------
   Deliberately quiet. If one of these reaches live before its photo
   does it should read as a frame not yet filled, not as a broken
   image. Every one names the file it is waiting for. ------------- */
.jsmph{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;padding:24px;background:#F7F9FA;border:1px dashed rgba(189,195,199,0.95);border-radius:2px;text-align:center;color:#5D6B76}
.jsmph.is-169{aspect-ratio:16/9}
.jsmph.is-32{aspect-ratio:3/2}
.jsmph.is-45{aspect-ratio:4/5}
.jsmph.is-11{aspect-ratio:1/1}
.jsmph-label{font-size:10px;font-weight:500;letter-spacing:0.28em;text-transform:uppercase;color:#5D6B76}
.jsmph-note{margin:0;font-size:12.5px;font-weight:300;line-height:1.65;max-width:300px;color:#5D6B76}
.jsmph-slot{font-size:11.5px;font-weight:300;letter-spacing:0.02em;color:#8A959E;word-break:break-word}
.jsmph-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
.jsmph-grid.is-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.jsmph-grid.is-4{grid-template-columns:repeat(4,minmax(0,1fr))}

/* --- Deliverable headings -----------------------------------------
   Was a wrapped row of bordered pills. They read as buttons and invited a
   click they never answered, so they are now real headings: an <h4> per
   deliverable under the category's <h3>, separated by hairlines rather
   than boxed. Same information, no false affordance, and the deliverable
   names are keyword-bearing headings instead of decorative spans.

   The wrapper keeps its inline grid-column:2 so the mobile release rule
   in the base block still finds it. -------------------------------- */
.jsmdel{display:flex;flex-direction:column;max-width:620px}
.jsm .jsmdel-item{margin:0;padding:11px 0;border-top:1px solid rgba(189,195,199,0.6);font-size:15.5px;font-weight:400;letter-spacing:-0.01em;line-height:1.45;color:#2C3E50}
.jsm .jsmdel-item:last-child{border-bottom:1px solid rgba(189,195,199,0.6)}

/* --- Justified photo rows and lightbox ----------------------------
   Lifted from the destination gallery pages so a gallery can be dropped
   on any page. Values are identical to the inline copies those pages
   already carry, so they render the same whichever definition wins.

   flex-grow carries the aspect ratio, which is what makes every frame in
   a row land on the same height and reflow with the viewport. ------ */
.jsmrow{display:flex;gap:6px;margin-bottom:6px}
.jsmf{position:relative;display:block;padding:0;border:0;background:#EDF1F4;overflow:hidden;cursor:zoom-in;min-width:0}
.jsmf img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;transition:transform .5s cubic-bezier(.22,.61,.36,1),opacity .3s}
.jsmf:hover img{transform:scale(1.03)}

.jsmlb{font-family:'Poppins',system-ui,sans-serif;position:fixed;inset:0;z-index:9999;background:rgba(20,28,36,0.96);display:none;align-items:center;justify-content:center}
.jsmlb.on{display:flex}
/* .jsm prefix required: the base block's `.jsm img{max-width:100%}` is an
   equal-specificity rule that loads later, so an unprefixed `.jsmlb img`
   loses its 94vw cap. The gallery pages get away with it because their
   copy sits in the same inline stylesheet, below that reset. */
.jsm .jsmlb img{max-width:94vw;max-height:88vh;object-fit:contain;display:block}
.jsmlb button{position:absolute;padding:0;border:0;background:none;color:#FDFDFD;cursor:pointer;font:inherit;opacity:.72;transition:opacity .2s}
.jsmlb button:hover{opacity:1}
.jsmlb .x{top:18px;right:22px;font-size:30px;line-height:1;width:44px;height:44px}
.jsmlb .p,.jsmlb .n{top:50%;transform:translateY(-50%);font-size:34px;line-height:1;width:56px;height:56px}
.jsmlb .p{left:8px}
.jsmlb .n{right:8px}
.jsmlb .c{position:absolute;bottom:16px;left:0;right:0;text-align:center;font-size:11.5px;letter-spacing:.16em;color:rgba(253,253,253,0.6);font-variant-numeric:tabular-nums}

/* On a phone a four frame row is unreadable, so rows wrap two up. */
@media(max-width:600px){
  .jsmrow{flex-wrap:wrap}
  .jsmrow .jsmf{flex:1 1 calc(50% - 3px) !important}
}

/* Compact variant. The deliverable name only, no visible one-liner, so a
   segment of eight sits in four short rows instead of eight tall cards.
   Used where the features sit inside a wider section that already carries
   the story, and the block would otherwise push the sample work off screen. */
.jsmfeat.is-compact .jsmfeat-top{padding:12px 16px;gap:0}
.jsm .jsmfeat.is-compact .jsmfeat-name{margin:0;font-size:14.5px;font-weight:400;letter-spacing:-0.005em}
.jsmfeat.is-compact .jsmfeat-def{padding:0 16px 16px}
.jsm .jsmfeat.is-compact .jsmfeat-def p{font-size:13.5px;line-height:1.7;padding-top:12px}
.jsmfeat.is-compact .jsmfeat-ico{flex:0 0 11px;width:11px;height:11px;margin-top:4px}
.jsmfeat.is-compact .jsmfeat-ico::before{top:5px;width:11px}
.jsmfeat.is-compact .jsmfeat-ico::after{left:5px;height:11px}

/* --- Video tiles --------------------------------------------------
   Thumbnail facades, not embeds. The player iframe is only created on
   click, which is the Core Web Vitals item from the SEO plan: a page can
   carry six of these and still load like it carries none. ---------- */
.jsmvid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
.jsmvid-item{display:flex;flex-direction:column;gap:12px;min-width:0}
.jsmvid-btn{position:relative;display:block;width:100%;aspect-ratio:16/9;overflow:hidden;padding:0;border:0;background:#2C3E50;cursor:pointer;border-radius:2px}
.jsmvid-btn img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.jsmvid-scrim{position:absolute;inset:0;background:rgba(24,34,45,0.24);transition:background .25s}
.jsmvid-btn:hover .jsmvid-scrim{background:rgba(24,34,45,0.08)}
.jsmvid-play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none}
.jsmvid-play span{width:52px;height:52px;border-radius:50%;border:1px solid rgba(253,253,253,0.75);display:flex;align-items:center;justify-content:center;background:rgba(24,34,45,0.35);transition:background .25s}
.jsmvid-btn:hover .jsmvid-play span{background:rgba(24,34,45,0.55)}
.jsmvid-play i{width:0;height:0;border-left:11px solid #FDFDFD;border-top:7px solid transparent;border-bottom:7px solid transparent;margin-left:3px}
.jsmvid-cap{font-size:15.5px;font-weight:400;letter-spacing:-0.01em;line-height:1.4;color:#2C3E50}
@media(max-width:900px){.jsmvid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:600px){.jsmvid{grid-template-columns:1fr}}

/* --- Article cards ------------------------------------------------ */
.jsmart{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;background:rgba(189,195,199,0.6);border-top:1px solid rgba(189,195,199,0.6);border-bottom:1px solid rgba(189,195,199,0.6)}
.jsmart-item{display:flex;flex-direction:column;gap:11px;padding:28px 26px 30px;background:#FDFDFD;transition:background .2s}
.jsmart-item:hover{background:#F7F9FA}
.jsmart-kicker{font-size:10px;font-weight:500;letter-spacing:0.28em;text-transform:uppercase;color:#5D6B76}
.jsmart-title{margin:0;font-size:18px;font-weight:400;letter-spacing:-0.015em;line-height:1.35;color:#2C3E50}
.jsmart-line{margin:0;font-size:14.5px;font-weight:300;line-height:1.7;color:#5D6B76}

/* --- Mobile -------------------------------------------------------
   One breakpoint at 900 for layout and one at 600 for the last drop to
   a single column, matching the two the rest of the site already uses. */
@media(max-width:900px){
  .jsmc-sec{padding:52px 0}
  .jsmc-wrap{padding:0 24px}
  .jsmc-head{margin-bottom:28px}
  .jsmfeat,.jsmfeat.is-3{grid-template-columns:repeat(2,minmax(0,1fr))}
  .jsmart{grid-template-columns:repeat(2,minmax(0,1fr))}
  .jsmph-grid,.jsmph-grid.is-4{grid-template-columns:repeat(2,minmax(0,1fr))}
  .jsmfaq-q{font-size:16px;gap:16px;padding:20px 0}
}
@media(max-width:600px){
  .jsmc-sec{padding:38px 0}
  .jsmfeat,.jsmfeat.is-3,.jsmfeat.is-2{grid-template-columns:1fr}
  .jsmart{grid-template-columns:1fr}
  .jsmph-grid,.jsmph-grid.is-2,.jsmph-grid.is-4{grid-template-columns:1fr}
  .jsmfeat-top{padding:22px 20px}
  .jsmfeat-def{padding:0 20px 22px}
}

/* --- Reduced motion ----------------------------------------------- */
@media(prefers-reduced-motion:reduce){
  .jsmfaq-item[open] .jsmfaq-a,.jsmfeat-item[open] .jsmfeat-def{animation:none}
  .jsmfaq-item::details-content,.jsmfeat-item::details-content{transition:none}
  .jsmfaq-ico::before,.jsmfaq-ico::after,.jsmfeat-ico::before,.jsmfeat-ico::after{transition:none}
}

/* --- Coming soon tile ---------------------------------------------
   For a slot whose asset is commissioned but not made yet, e.g. the drone
   showreel. Unlike .jsmph this is meant to ship: solid navy, muted play
   ring, reads as a considered "not yet" rather than a broken frame. */
.jsmsoon{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;aspect-ratio:16/9;background:#2C3E50;border-radius:2px;text-align:center;padding:24px}
.jsmsoon-ring{width:52px;height:52px;border-radius:50%;border:1px solid rgba(253,253,253,0.32);display:flex;align-items:center;justify-content:center}
.jsmsoon-ring i{width:0;height:0;border-left:11px solid rgba(253,253,253,0.5);border-top:7px solid transparent;border-bottom:7px solid transparent;margin-left:3px}
.jsmsoon-label{font-size:10px;font-weight:500;letter-spacing:0.28em;text-transform:uppercase;color:rgba(253,253,253,0.5)}
.jsmsoon-title{font-size:15.5px;font-weight:400;letter-spacing:-0.01em;color:#FDFDFD}
