/* FX template: classes only, nothing baked into image files.
   .fx-photo  : wrap any <img> or <video> -> aged look (grain, lifted blacks, warmth, vignette, a little dust)
   .fx-poster : full poster = .fx-photo + halftone dots over the whole picture; straight bottom edge (Li 10-27).
   Markup:  <section class="fx-poster [is-light]"> <img|video class="fx-media"> <i class="fx-dots"></i> ...text (z-index 5)... </section>
   Swap the picture = change src only. Intensities: top of base.css. */
.fx-photo{--fx-dust:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='900' height='900'><g fill='%23fff' opacity='.5'><circle cx='130' cy='210' r='1.1'/><circle cx='610' cy='90' r='.9'/><circle cx='340' cy='640' r='1.2'/><circle cx='790' cy='520' r='.8'/><circle cx='70' cy='810' r='1'/></g></svg>")}
/* posters carry no dust and no scratch lines: any isolated speck or hairline reads as a defect over a face or a seam */
.fx-photo,.fx-poster{position:relative;overflow:hidden;isolation:isolate}
.fx-poster{background:var(--fx-bg,#1A1916)}
.fx-photo > img,.fx-photo > video,.fx-media{filter:contrast(var(--fx-contrast)) saturate(var(--fx-sat)) sepia(var(--fx-warm))}
/* grain + a very little dust and two hair scratches (static tile, kept faint) */
.fx-photo::before,.fx-poster::before{content:"";position:absolute;inset:-8px;z-index:1;pointer-events:none;mix-blend-mode:overlay;opacity:var(--fx-grain);
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 .5  0 0 0 0 .5  0 0 0 0 .5  0 0 0 1.4 -.2'/></filter><rect width='220' height='220' filter='url(%23n)'/></svg>");
  background-size:220px 220px;animation:fx-grain .9s steps(1) infinite}
@keyframes fx-grain{0%{background-position:0 0}20%{background-position:-37px 21px}40%{background-position:52px -33px}60%{background-position:-18px -61px}80%{background-position:41px 47px}}
/* lifted blacks + vignette + top shade + dust */
.fx-photo::after,.fx-poster::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    linear-gradient(to bottom,rgba(8,7,6,var(--fx-topshade)) 0,rgba(8,7,6,calc(var(--fx-topshade) * .78)) 8%,rgba(8,7,6,calc(var(--fx-topshade) * .42)) 17%,rgba(8,7,6,calc(var(--fx-topshade) * .14)) 25%,transparent 31%),
    linear-gradient(to right,rgba(var(--fx-scrim-rgb),var(--fx-leftshade,.78)) 0,rgba(var(--fx-scrim-rgb),calc(var(--fx-leftshade,.78) * .5)) 30%,transparent 46%),
    radial-gradient(ellipse var(--fx-scrim-size,52% 62%) at var(--fx-scrim-pos,24% 52%),rgba(var(--fx-scrim-rgb),var(--fx-scrim-a)),transparent 75%),
    radial-gradient(ellipse 80% 75% at 50% 45%,transparent 55%,rgba(10,8,6,var(--fx-vignette))),
    var(--fx-dust,none) 0 0/900px 900px,
    rgba(var(--fx-fade-rgb),var(--fx-fade))}
.fx-poster .fx-media{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* Classic halftone gradient: ONE dot grid, dot size follows position. Biggest and densest at the bottom-right corner, fading along --fx-dot-angle
   to nothing at the top-left, no cut-off line anywhere. Method: staggered dot-pattern + a linear gradient added to it, then a hard contrast
   threshold (the pattern peaks below the threshold, so where the gradient is below --fx-dot-gate NO dot exists: dots shrink to nothing, they are not faded) turns the sum into round dots whose radius grows with the gradient. mix-blend-mode puts the dots on the photo. */
.fx-dots{position:absolute;inset:0;z-index:2;pointer-events:none;
  --p:var(--fx-dot-pitch);
  background:
    linear-gradient(var(--fx-dot-angle),rgb(calc(var(--fx-dot-gate) * 255),calc(var(--fx-dot-gate) * 255),calc(var(--fx-dot-gate) * 255)) var(--fx-dot-end),rgb(calc(var(--fx-dot-hi) * 255),calc(var(--fx-dot-hi) * 255),calc(var(--fx-dot-hi) * 255)) 100%),
    radial-gradient(circle at 50% 50%,#ddd 0,#000 calc(var(--p) * .5)) 0 0/var(--p) var(--p),
    radial-gradient(circle at 50% 50%,#ddd 0,#000 calc(var(--p) * .5)) calc(var(--p) / 2) calc(var(--p) / 2)/var(--p) var(--p);
  background-color:#000;
  background-blend-mode:screen,lighten,normal;
  filter:brightness(var(--fx-dot-bright)) contrast(var(--fx-dot-sharp));
  mix-blend-mode:screen;opacity:var(--fx-dot-a)}
.fx-poster.is-light{--fx-leftshade:.8;--fx-bg:#EDE8DD;--fx-scrim-rgb:247,244,238;--fx-scrim-a:.72;--fx-dot-rgb:26,25,22;--fx-dot-a:.2;--fx-text:#1A1916;--fx-topshade:0;--fx-vignette:.14;--fx-fade:.03}
/* Li 10-27: brush edge removed - the poster ends on a straight line. --fx-edge-h:0 keeps text and still positions consistent (old edge: fx.bak-1027.css, assets/edge-e.webp) */
.fx-poster{--fx-edge-h:0px}
@media (prefers-reduced-motion:reduce){.fx-photo::before,.fx-poster::before{animation:none}}
/* background clip player layers (fx.js) */
.fx-clips{position:absolute;inset:0;z-index:0}
.fx-clips .fx-clip{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0}
.fx-clips .fx-clip.on{opacity:1}
/* stills: right 62% of the poster, fading into the ink on the left, so text never sits on the subject. focus = object-position of the subject */
/* Li 10-26: desktop shows the whole still (whole head), not a cover-crop: height ends inside the brush strip, width follows the picture's 3:4 */
.fx-clips img.fx-clip.side{left:auto;right:0;width:auto;height:calc(100% - var(--fx-edge-h) * .3);aspect-ratio:3/4;object-fit:cover;transform-origin:50% 100%;-webkit-mask-image:linear-gradient(to right,transparent 0,#000 34%);mask-image:linear-gradient(to right,transparent 0,#000 34%)}
/* phone: picture takes the top ~58%, fading into ink; text sits on the ink */
@media (max-width:860px){.fx-clips .fx-clip,.fx-clips img.fx-clip.side{left:0;right:0;width:100%;height:58%;-webkit-mask-image:linear-gradient(to bottom,#000 70%,transparent);mask-image:linear-gradient(to bottom,#000 70%,transparent)}}
.fx-clips img.fx-clip.on{animation:fx-push var(--dur,4s) ease-out both}
@keyframes fx-push{from{transform:scale(1)}to{transform:scale(1.1)}}
@media (min-width:861px){.fx-clips img.fx-clip.side.on{animation-name:fx-push-s}}
@keyframes fx-push-s{from{transform:scale(1)}to{transform:scale(1.04)}}
@media (prefers-reduced-motion:reduce){.fx-clips img.fx-clip.on{animation:none}}

@media (max-width:860px){.fx-poster{--fx-leftshade:0;--fx-scrim-a:.0}}
@media (max-width:640px){.fx-poster{--fx-scrim-size:140% 58%;--fx-scrim-pos:40% 86%}.fx-poster.is-light{--fx-leftshade:.8;--fx-scrim-a:.8}}

/* phone: the box is tall, so start the dots later along the diagonal to keep the text block clean */
@media (max-width:860px){.fx-poster{--fx-dot-end:70%;--fx-dot-pitch:12px}}

/* .fx-land (Li 10-27, option A): one full-bleed landscape picture, subject in the right half.
   Markup: <section class="fx-land"> <img class="fx-pic"> <canvas class="fx-ht"></canvas> <i class="fx-shade"></i> ...text (z-index 5)... </section>
   Picture graded into the brand range (#fx-grade: black -> ink, white -> paper). fx.js FXLand redraws it as a tonal halftone
   (dot size follows brightness, 45-degree screen, picture's own colours on ink), shown on the text side only, so the face stays photographic. */
.fx-land{position:relative;overflow:hidden;isolation:isolate;background:var(--ink);--fx-edge-h:0px}   /* edge-h: text sits where it did on the old poster */
.fx-land .fx-pic{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:70% 50%;filter:url(#fx-grade) saturate(.9)}
.fx-land .fx-ht{position:absolute;inset:0;width:100%;height:100%;z-index:1;pointer-events:none;
  -webkit-mask-image:linear-gradient(90deg,#000 0,rgba(0,0,0,.85) 30%,transparent 58%);mask-image:linear-gradient(90deg,#000 0,rgba(0,0,0,.85) 30%,transparent 58%)}
.fx-land .fx-pic,.fx-land .fx-ht{transition:opacity .35s linear}
.fx-land .fx-film{position:absolute;inset:0;z-index:1;width:100%;height:100%;object-fit:cover;object-position:70% 50%}
.fx-land.fx-swap .fx-pic,.fx-land.fx-swap .fx-ht{opacity:0}
/* Li 10-28: the whole picture shows (no halftone over the left half); only a soft shade behind the text and the menu */
.fx-land .fx-ht{display:none}
.fx-land .fx-pic{filter:none}
.fx-land .fx-shade{position:absolute;inset:0;z-index:2;pointer-events:none;background:
  linear-gradient(180deg,rgba(14,13,11,.45) 0,rgba(14,13,11,0) 16%),
  linear-gradient(90deg,rgba(20,19,17,.62) 0,rgba(20,19,17,.38) 32%,rgba(20,19,17,0) 55%),
  linear-gradient(0deg,rgba(14,13,11,.4) 0,rgba(14,13,11,0) 30%)}
/* phone: picture on top, text on ink below; dots in the lower part */
@media (max-width:860px){
  .fx-land .fx-pic{height:62%;object-position:78% 50%}
  .fx-land .fx-film{height:62%;object-position:50% 0}
  .fx-land .fx-shade{background:linear-gradient(180deg,rgba(14,13,11,.82) 0,rgba(14,13,11,.55) 7%,rgba(14,13,11,0) 17%,rgba(26,25,22,0) 38%,rgba(26,25,22,.1) 42%,rgba(26,25,22,.26) 46%,rgba(26,25,22,.44) 50%,rgba(26,25,22,.6) 54%,rgba(26,25,22,.78) 58%,rgba(26,25,22,.92) 60.5%,var(--ink) 62%)}
  .fx-land .fx-ht{-webkit-mask-image:linear-gradient(180deg,transparent 30%,#000 62%);mask-image:linear-gradient(180deg,transparent 30%,#000 62%)}
}
