/* ============================================================
   Wild Idea 09 — FLOATING ARCHIVE
   Adapted for production: reads real posts from
   #wk-blog-posts-data, navigates to /blog/[slug]/ on read.
   ============================================================ */

const flCss = `
.fl-wrap { background:#050505; color:#FFF; font-family:'PP Neue Montreal','Inter',sans-serif; position:fixed; inset:0; overflow:hidden; user-select:none; cursor:grab; }
.fl-wrap.dragging { cursor:grabbing; }
.fl-wrap.focused { cursor:default; }

/* 2026-05-30 — cut the red haze over the videos: the red ellipse was the
   dull desaturated wash the owner flagged. Dropped its opacity way down and
   leaned the ambient toward the cooler purple so the tiles read cleaner. */
.fl-wrap::before { content:''; position:absolute; inset:0; z-index:0; pointer-events:none; background:
  radial-gradient(ellipse at 60% 40%, rgba(199,44,44,0.012), transparent 50%),
  radial-gradient(ellipse at 25% 70%, rgba(40,20,60,0.13), transparent 55%),
  radial-gradient(ellipse at 80% 80%, rgba(120,40,20,0.015), transparent 60%);
}
.fl-stars { position:absolute; inset:0; z-index:1; pointer-events:none; opacity:0.6;
  background-image:
    radial-gradient(rgba(255,255,255,0.45) 1px, transparent 1.2px),
    radial-gradient(rgba(255,200,160,0.5) 0.7px, transparent 1px),
    radial-gradient(rgba(199,44,44,0.5) 0.6px, transparent 1px);
  background-size:170px 170px, 110px 110px, 240px 240px;
  background-position:0 0, 80px 60px, 30px 130px;
}

.fl-head { position:absolute; top:0; left:0; right:0; padding:28px 56px; display:flex; justify-content:space-between; align-items:baseline; z-index:30; font-family:'JetBrains Mono','IBM Plex Mono',monospace; font-size:11px; letter-spacing:0.32em; text-transform:uppercase; color:rgba(255,255,255,0.5); pointer-events:none; }
.fl-head .l { font-family:'PP Fragment Serif','Fraunces',serif; font-style:italic; font-size:24px; letter-spacing:-0.01em; text-transform:none; pointer-events:auto; }
.fl-head .l a { color:inherit; text-decoration:none; }
.fl-head .l b { color:#C72C2C; font-style:normal; font-family:'JetBrains Mono','IBM Plex Mono',monospace; font-size:11px; letter-spacing:0.32em; text-transform:uppercase; margin-right:14px; vertical-align:5px; }
.fl-head .r b { color:#C72C2C; font-weight:400; }

.fl-stage { position:absolute; inset:0; perspective:1600px; perspective-origin:50% 50%; z-index:5; }
.fl-camera { position:absolute; inset:0; transform-style:preserve-3d; }

.fl-backdrop { position:absolute; left:50%; top:50%; width:2000px; margin-left:-1000px; height:2000px; margin-top:-1000px; transform-style:preserve-3d; transform: translate3d(0, 200px, -4200px); pointer-events:none; }
.fl-backdrop .spin { width:100%; height:100%; transform-style:preserve-3d; animation: flLogoSpin 28s ease-in-out infinite; transform: rotateY(0deg); }
.fl-backdrop.intro .spin { animation-play-state: paused; transform: rotateY(0deg); }
.fl-backdrop video, .fl-backdrop img { width:100%; height:100%; object-fit:contain; filter: none; opacity:1; }
@keyframes flLogoSpin {
  0%   { transform: rotateY(  0deg); }
  25%  { transform: rotateY( 52deg); }
  50%  { transform: rotateY(  0deg); }
  75%  { transform: rotateY(-52deg); }
  100% { transform: rotateY(  0deg); }
}

/* 2026-05-31 — SIX DISTINCT drift paths so each tile traces a DIFFERENT orbit,
   not the same loop offset in time. The point (per the owner) is that the DISTANCE
   BETWEEN NEIGHBORS constantly changes — objects bobbing in space that only
   roughly align to a grid — rather than the whole grid drifting in unison. Each
   tile is assigned one of flBob1..6 (by index, see the inline --bob-name var)
   with its own duration; adjacent tiles get different paths + speeds so they
   pull apart and back together over time. Paths differ in direction, x/y ratio,
   rotation sign, and waypoint phase. ~14-18px travel + ~1.5° tilt. */
@keyframes flBob1 {
  0%   { transform: translate3d(-13px, -16px, 0) rotate(-1.4deg); }
  33%  { transform: translate3d( 14px,  -4px, 0) rotate( 1.1deg); }
  66%  { transform: translate3d(  6px,  16px, 0) rotate( 1.5deg); }
  100% { transform: translate3d(-13px, -16px, 0) rotate(-1.4deg); }
}
@keyframes flBob2 {
  0%   { transform: translate3d( 15px,  10px, 0) rotate( 1.2deg); }
  30%  { transform: translate3d( -8px,  16px, 0) rotate(-0.9deg); }
  62%  { transform: translate3d(-15px,  -8px, 0) rotate(-1.5deg); }
  100% { transform: translate3d( 15px,  10px, 0) rotate( 1.2deg); }
}
@keyframes flBob3 {
  0%   { transform: translate3d( -9px,  14px, 0) rotate( 0.8deg); }
  40%  { transform: translate3d( 16px,   6px, 0) rotate( 1.4deg); }
  72%  { transform: translate3d(  4px, -16px, 0) rotate(-1.1deg); }
  100% { transform: translate3d( -9px,  14px, 0) rotate( 0.8deg); }
}
@keyframes flBob4 {
  0%   { transform: translate3d( 12px, -14px, 0) rotate(-1.2deg); }
  28%  { transform: translate3d(-14px,  -2px, 0) rotate( 1.3deg); }
  58%  { transform: translate3d( -3px,  17px, 0) rotate( 0.7deg); }
  100% { transform: translate3d( 12px, -14px, 0) rotate(-1.2deg); }
}
@keyframes flBob5 {
  0%   { transform: translate3d(-16px,   4px, 0) rotate( 1.5deg); }
  36%  { transform: translate3d(  7px, -15px, 0) rotate(-1.0deg); }
  68%  { transform: translate3d( 14px,  11px, 0) rotate(-1.4deg); }
  100% { transform: translate3d(-16px,   4px, 0) rotate( 1.5deg); }
}
@keyframes flBob6 {
  0%   { transform: translate3d(  8px,  16px, 0) rotate(-0.7deg); }
  32%  { transform: translate3d( 15px,  -7px, 0) rotate( 1.5deg); }
  70%  { transform: translate3d(-13px,  -9px, 0) rotate( 1.0deg); }
  100% { transform: translate3d(  8px,  16px, 0) rotate(-0.7deg); }
}

.fl-dust { position:absolute; inset:-10% -5%; z-index:2; pointer-events:none; opacity:0.45; }
.fl-dust i { position:absolute; width:2px; height:2px; border-radius:50%; background:rgba(255,255,255,0.6); filter:blur(0.3px); animation:flDust linear infinite; }
.fl-dust i.lg { width:3px; height:3px; background:rgba(255,210,180,0.55); }
.fl-dust i.red { background:rgba(199,44,44,0.7); box-shadow:0 0 4px rgba(199,44,44,0.6); }
@keyframes flDust {
  0%   { transform: translate3d(0, 0, 0); opacity:0; }
  10%  { opacity:1; }
  90%  { opacity:1; }
  100% { transform: translate3d(40px, -120px, 0); opacity:0; }
}

.fl-tile { position:absolute; left:50%; top:50%; width:560px; aspect-ratio:16/9; cursor:pointer; will-change:transform; transition:filter .35s ease, opacity .35s ease; }
.fl-tile.intro { transform: translate3d(0px, 0px, -2400px) scale(0.15) rotate(var(--intro-rot, 0deg)) !important; opacity:0; filter:blur(6px) brightness(1.4); }
.fl-tile.intro .fl-tile-inner { box-shadow: 0 0 0 1px rgba(255,255,255,0.25), 0 0 80px rgba(199,44,44,0.85), 0 0 160px rgba(255,200,160,0.4); }
.fl-tile.intro-ready { transition: transform 1.4s cubic-bezier(.14, 1.2, .3, 1), opacity 0.6s ease-out, filter 0.85s ease-out; transition-delay: var(--intro-delay, 0s); }
.fl-tile.intro-ready .fl-tile-inner { transition: box-shadow 2.4s ease-out var(--intro-delay, 0s); }

.fl-backdrop.intro { opacity:0; transform: translate3d(0, 200px, -4200px) scale(0.05); }
.fl-backdrop.intro-anim { transition: opacity 1.2s ease-out 0.35s, transform 1.6s cubic-bezier(.16, 1.05, .32, 1) 0.2s; }

.fl-bang { position:absolute; left:50%; top:50%; width:120px; height:120px; margin:-60px 0 0 -60px; border-radius:50%; background:radial-gradient(circle, rgba(255,255,255,0.95) 0%, rgba(199,44,44,0.85) 30%, rgba(199,44,44,0.4) 55%, rgba(199,44,44,0) 75%); z-index:35; pointer-events:none; opacity:0; transform:scale(0.1); }
.fl-bang.go { animation: flBang 1.6s cubic-bezier(.16, 1.05, .32, 1) forwards; }
@keyframes flBang {
  0%   { opacity:0;    transform:scale(0.1); }
  18%  { opacity:1;    transform:scale(1.2); }
  45%  { opacity:0.75; transform:scale(8); }
  100% { opacity:0;    transform:scale(22); }
}

.fl-tile-inner { position:absolute; inset:0; background:#111; box-shadow:0 20px 50px rgba(0,0,0,0.65), 0 0 0 1px rgba(255,255,255,0.06); overflow:hidden; transition:box-shadow .35s ease; animation: var(--bob-name, flBob1) var(--bob-dur, 7s) ease-in-out infinite; animation-delay: var(--bob-delay, 0s); }
.fl-tile-inner img, .fl-tile-inner video { width:100%; height:100%; object-fit:cover; transition:filter .35s ease, transform 1.2s cubic-bezier(.2,.85,.2,1); filter:grayscale(0.3) brightness(0.85); }
.fl-tile:hover .fl-tile-inner { box-shadow:0 20px 50px rgba(0,0,0,0.7), 0 0 0 2px #C72C2C, 0 0 40px rgba(199,44,44,0.4); }
.fl-tile:hover .fl-tile-inner img, .fl-tile:hover .fl-tile-inner video { filter:none; transform:scale(1.04); }
.fl-tile-inner video { display:block; pointer-events:none; }
.fl-tile-inner::after { content:''; position:absolute; left:0; right:0; bottom:0; height:60%; background:linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.0) 28%, rgba(0,0,0,0.88) 100%); pointer-events:none; }
/* 2026-05-30 — Tile titles moved BELOW the thumbnail in the nav-button font
   (JetBrains Mono, uppercase, letter-spaced). Corner media icons removed.
   Reads like an editorial contact sheet; never competes with the image. */
.fl-tile-title { position:absolute; left:0; right:0; top:calc(100% + 16px); text-align:center; font-family:'PP Neue Montreal','Inter','Helvetica Neue',sans-serif; font-weight:700; font-size:21px; line-height:1.18; letter-spacing:0.06em; text-transform:uppercase; color:rgba(255,255,255,0.92); margin:0; z-index:2; pointer-events:none; }
.fl-tile-title em { font-style:normal; color:#fff; }
.fl-tile.focused .fl-tile-title, .fl-tile.dim .fl-tile-title { opacity:0; }

.fl-tile::after { content:''; position:absolute; left:50%; bottom:-46px; width:1px; height:36px; background:linear-gradient(180deg, rgba(199,44,44,0.6), transparent); transform:translateX(-50%); opacity:0.5; }

.fl-tile.focused { z-index:50;
  /* Snappier transition during the click-to-focus fly: tile lifts out of the
     grid toward the viewer.  Overrides the slower 1.4s intro-ready ease so
     the fly-forward is visible before the .fl-post overlay covers it. */
  transition: transform 0.6s cubic-bezier(0.2, 0.85, 0.2, 1), filter .35s ease, opacity .35s ease !important;
  transition-delay: 0s !important;
}
.fl-tile.focused .fl-tile-inner { box-shadow:0 40px 100px rgba(0,0,0,0.85), 0 0 0 2px #C72C2C, 0 0 80px rgba(199,44,44,0.5); }
.fl-tile.dim { opacity:0.18; filter:blur(2px); }

.fl-post { position:absolute; inset:0; z-index:55; display:flex; align-items:center; justify-content:center; gap:48px; padding:48px 80px; opacity:0; pointer-events:none; transition:opacity .55s ease .45s; background:radial-gradient(ellipse at center, rgba(5,5,5,0.0) 0%, rgba(5,5,5,0.55) 55%, rgba(5,5,5,0.92) 100%); }
.fl-post.on { opacity:1; pointer-events:auto; }
.fl-post-media { width:780px; max-width:55vw; aspect-ratio:16/9; position:relative; box-shadow:0 40px 100px rgba(0,0,0,0.85), 0 0 0 2px #C72C2C, 0 0 80px rgba(199,44,44,0.45); overflow:hidden; background:#111; transform:scale(0.92); transition:transform .6s cubic-bezier(.2,.85,.2,1) .1s; }
.fl-post.on .fl-post-media { transform:scale(1); }
.fl-post-media img { width:100%; height:100%; object-fit:cover; }
.fl-post-media .play { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); width:88px; height:88px; border-radius:50%; border:1px solid rgba(255,255,255,0.85); display:flex; align-items:center; justify-content:center; background:rgba(0,0,0,0.18); backdrop-filter:blur(3px); cursor:pointer; transition:background .25s ease, border-color .25s ease; }
.fl-post-media .play::after { content:''; display:block; width:0; height:0; border-left:18px solid #FFF; border-top:11px solid transparent; border-bottom:11px solid transparent; transform:translateX(3px); }
.fl-post-media .play:hover { background:rgba(199,44,44,0.85); border-color:#C72C2C; }
/* Content panel sized to match the media height (16:9 of the media width) so top/bottom align with the thumbnail */
.fl-post-content { width:480px; max-width:34vw; height:calc(min(780px, 55vw) * 9 / 16); display:flex; flex-direction:column; opacity:0; transform:translateY(10px); transition:opacity .45s ease .35s, transform .55s cubic-bezier(.2,.85,.2,1) .3s; }
.fl-post.on .fl-post-content { opacity:1; transform:translateY(0); }
.fl-post-content .meta { display:flex; align-items:center; gap:14px; font-family:'JetBrains Mono','IBM Plex Mono',monospace; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; color:#C72C2C; margin-bottom:14px; flex-shrink:0; }
.fl-post-content .meta .dot { width:6px; height:6px; border-radius:50%; background:#C72C2C; box-shadow:0 0 8px #C72C2C; animation:flPulse 1.6s ease-in-out infinite; }
@keyframes flPulse { 0%,100% { opacity:1; } 50% { opacity:0.35; } }
.fl-post-content h2 { font-family:'PP Neue Montreal','Inter',sans-serif; font-weight:700; text-transform:uppercase; letter-spacing:0.005em; font-size:34px; line-height:1.02; margin:0 0 16px; color:#FFF; flex-shrink:0; }
.fl-post-content h2 em { font-style:normal; font-weight:700; }
.fl-post-content p { font-family:'PP Fragment Serif','Fraunces',serif; font-style:italic; font-size:17px; line-height:1.45; color:rgba(255,255,255,0.88); margin:0 0 16px; flex:1 1 auto; overflow:hidden; min-height:0; }
.fl-post-content p.body { font-family:'PP Neue Montreal','Inter',sans-serif; font-style:normal; font-size:15px; line-height:1.6; color:rgba(255,255,255,0.7); margin:0 0 22px; }
.fl-post-content .actions { display:flex; gap:24px; align-items:center; padding-top:18px; border-top:1px solid rgba(255,255,255,0.12); flex-shrink:0; margin-top:auto; }
.fl-post-content .read { font-family:'JetBrains Mono','IBM Plex Mono',monospace; font-size:11px; letter-spacing:0.32em; text-transform:uppercase; color:#FFF; cursor:pointer; border-bottom:1px solid #C72C2C; padding-bottom:2px; text-decoration:none; }
.fl-post-content .read::after { content:' \\2192'; color:#C72C2C; }
.fl-post-content .back { font-family:'JetBrains Mono','IBM Plex Mono',monospace; font-size:11px; letter-spacing:0.32em; text-transform:uppercase; color:rgba(255,255,255,0.55); cursor:pointer; }
.fl-post-content .back::before { content:'\\2190  '; color:#C72C2C; margin-right:6px; }
.fl-post-content .back:hover { color:#FFF; }

.fl-close { position:absolute; top:30px; right:56px; z-index:60; width:42px; height:42px; border:1px solid rgba(255,255,255,0.25); display:flex; align-items:center; justify-content:center; color:#FFF; cursor:pointer; font-family:'PP Neue Montreal','Inter',sans-serif; font-size:22px; line-height:0; background:rgba(0,0,0,0.4); transition:border-color .25s ease, background .25s ease; }
.fl-close:hover { border-color:#C72C2C; background:rgba(199,44,44,0.2); }

.fl-nav { position:absolute; top:50%; z-index:60; width:60px; height:60px; border:1px solid rgba(255,255,255,0.22); display:flex; align-items:center; justify-content:center; color:#FFF; cursor:pointer; background:rgba(0,0,0,0.4); transition:border-color .25s ease, background .25s ease, transform .25s ease; backdrop-filter:blur(6px); transform:translateY(-50%); }
.fl-nav:hover { border-color:#C72C2C; background:rgba(199,44,44,0.2); }
.fl-nav.prev { left:36px; }
.fl-nav.next { right:36px; }
.fl-nav.prev:hover { transform:translateY(-50%) translateX(-4px); }
.fl-nav.next:hover { transform:translateY(-50%) translateX(4px); }
.fl-nav.dim { opacity:0.25; pointer-events:none; }
.fl-nav svg { display:block; }

.fl-hint { position:absolute; bottom:30px; left:0; right:0; display:flex; justify-content:space-between; padding:0 56px; z-index:25; font-family:'JetBrains Mono','IBM Plex Mono',monospace; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; color:rgba(255,255,255,0.4); pointer-events:none; }
.fl-hint b { color:#C72C2C; font-weight:400; }
.fl-hint .center { display:flex; gap:24px; align-items:center; }
.fl-hint .center span:not(:first-child)::before { content:'\\00B7\\00A0\\00A0'; color:rgba(255,255,255,0.3); margin-right:6px; }

.fl-crosshair { position:absolute; left:50%; top:50%; width:34px; height:34px; transform:translate(-50%,-50%); z-index:22; pointer-events:none; }
.fl-crosshair::before, .fl-crosshair::after { content:''; position:absolute; background:rgba(199,44,44,0.6); }
.fl-crosshair::before { left:0; right:0; top:50%; height:1px; transform:translateY(-50%); }
.fl-crosshair::after  { top:0; bottom:0; left:50%; width:1px; transform:translateX(-50%); }

.fl-counter { position:absolute; top:30px; right:56px; z-index:30; font-family:'JetBrains Mono','IBM Plex Mono',monospace; font-size:10px; letter-spacing:0.32em; text-transform:uppercase; color:rgba(255,255,255,0.45); pointer-events:none; }
.fl-counter b { color:#FFF; font-weight:400; }
.fl-counter em { color:#C72C2C; font-style:normal; }

.fl-map { position:absolute; right:56px; bottom:80px; width:140px; height:140px; border:1px solid rgba(255,255,255,0.10); z-index:25; pointer-events:none; }
.fl-map::before { content:''; position:absolute; left:50%; top:50%; width:18px; height:18px; transform:translate(-50%,-50%); border:1px solid rgba(199,44,44,0.6); }
.fl-map i { position:absolute; width:4px; height:4px; border-radius:50%; background:rgba(255,255,255,0.5); transform:translate(-50%,-50%); }
.fl-map i.active { background:#C72C2C; box-shadow:0 0 8px #C72C2C; }
.fl-map-lbl { position:absolute; right:0; top:-22px; font-family:'JetBrains Mono','IBM Plex Mono',monospace; font-size:9px; letter-spacing:0.32em; text-transform:uppercase; color:rgba(255,255,255,0.45); }

/* ---- Preview-shorts carousel inside .fl-post-media ---- */
.fl-post-media video { width:100%; height:100%; object-fit:cover; display:block; background:#000; }
.fl-shorts-prev, .fl-shorts-next {
  position:absolute; top:50%; transform:translateY(-50%);
  width:44px; height:64px;
  background:rgba(0,0,0,0.55); color:#fff;
  font-family:inherit; font-size:30px; line-height:1; font-weight:300;
  border:0; cursor:pointer; padding:0;
  display:flex; align-items:center; justify-content:center;
  z-index:10; transition:background .2s ease, opacity .2s ease, transform .2s ease;
}
.fl-shorts-prev { left:0; }
.fl-shorts-next { right:0; }
.fl-shorts-prev:hover, .fl-shorts-next:hover { background:rgba(199,44,44,0.85); }
.fl-shorts-prev.dim, .fl-shorts-next.dim { opacity:0.25; cursor:default; pointer-events:none; }
.fl-shorts-counter {
  position:absolute; top:14px; right:14px; z-index:10;
  font-family:'JetBrains Mono','IBM Plex Mono',monospace;
  font-size:10px; letter-spacing:0.18em; text-transform:uppercase;
  color:rgba(255,255,255,0.85);
  background:rgba(0,0,0,0.55);
  padding:6px 10px;
  pointer-events:none;
}
.fl-shorts-dots {
  position:absolute; bottom:46px; left:0; right:0;
  display:flex; gap:8px; justify-content:center;
  z-index:10; pointer-events:auto;
}
.fl-shorts-dot {
  width:8px; height:8px; border-radius:50%;
  background:rgba(255,255,255,0.35); border:0; padding:0; cursor:pointer;
  transition:background .2s ease, transform .2s ease;
}
.fl-shorts-dot:hover { background:rgba(255,255,255,0.7); }
.fl-shorts-dot.active { background:#C72C2C; transform:scale(1.4); }
`;

// Slug -> array of slideshow image URLs. Tiles whose slug matches will cycle
// through these images on a crossfade loop instead of showing a single hero.
const SLIDESHOWS = {
  'kiki-x-wild-kindness': [
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/kiki/kiki-01.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/kiki/kiki-02.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/kiki/kiki-03.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/kiki/kiki-04.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/kiki/kiki-05.jpg',
  ],
  'femmeslut-x-wild-kindness': [
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/femmeslut/femmeslut-01.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/femmeslut/femmeslut-02.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/femmeslut/femmeslut-03.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/femmeslut/femmeslut-04.jpg',
  ],
  'tia-silk-x-wild-kindness': [
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/tia/tia-01.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/tia/tia-02.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/tia/tia-03.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/tia/tia-04.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/tia/tia-05.jpg',
    'https://staging.wildkindness69.com/content/images/2026/05/slideshows/tia/tia-06.jpg',
  ],
};

// ---- Per-slug preview-shorts carousel ----
// When a slug has 1+ entries here, the focused-post media area becomes a
// video carousel: one short visible at a time with prev/next + position
// dots.  The "Read the full post" CTA swaps to "Watch full video on
// OnlyFans" which links to POST_OF_URL.
const POST_SHORTS = {
  'ts-suzie-xl-x-wild-kindness': [
    'https://wildkindness-cdn.b-cdn.net/tile-videos/suzie-promo.mp4',
    'https://wildkindness-cdn.b-cdn.net/tile-videos/suzie-promo2.mp4',
  ],
  'random-acts-of-wild-kindness-the-vlog-episode-1': [
    'https://staging.wildkindness69.com/content/media/2026/05/vlog-short-1.mp4',
    'https://staging.wildkindness69.com/content/media/2026/05/vlog-short-2.mp4',
    'https://staging.wildkindness69.com/content/media/2026/05/vlog-short-3.mp4',
    'https://staging.wildkindness69.com/content/media/2026/05/vlog-short-4.mp4',
  ],
  'foxy-alex-x-wild-kindness': [
    'https://wildkindness-cdn.b-cdn.net/post-shorts/foxy-alex/1-clip03.mp4',
    'https://wildkindness-cdn.b-cdn.net/post-shorts/foxy-alex/2-clip04.mp4',
  ],
  'trans-fmm-threesome-x-wild-kindness': [
    'https://wildkindness-cdn.b-cdn.net/post-shorts/trans-fmm-threesome/1-clip05.mp4',
    'https://wildkindness-cdn.b-cdn.net/post-shorts/trans-fmm-threesome/2-clip02.mp4',
    'https://wildkindness-cdn.b-cdn.net/post-shorts/trans-fmm-threesome/3-clip03.mp4',
  ],
  'straight-guy-does-gay-x-wild-kindness': [
    'https://wildkindness-cdn.b-cdn.net/post-shorts/straight-guy-does-gay/1-clip02.mp4',
    'https://wildkindness-cdn.b-cdn.net/post-shorts/straight-guy-does-gay/2-clip03.mp4',
    'https://wildkindness-cdn.b-cdn.net/post-shorts/straight-guy-does-gay/3-clip04.mp4',
  ],
  'arielles-first-time-x-wild-kindness': [
    'https://wildkindness-cdn.b-cdn.net/post-shorts/arielles-first-time/1-clip05.mp4',
    'https://wildkindness-cdn.b-cdn.net/post-shorts/arielles-first-time/2-clip01.mp4',
  ],
  'femmeslut-x-wild-kindness': [
    'https://wildkindness-cdn.b-cdn.net/post-shorts/femmeslut/1-lotus3.mp4',
  ],
  'kiki-x-wild-kindness': [
    'https://wildkindness-cdn.b-cdn.net/post-shorts/kiki/1-kiki1.mp4',
    'https://wildkindness-cdn.b-cdn.net/post-shorts/kiki/2-kiki2.mp4',
  ],
  'tia-silk-x-wild-kindness': [
    'https://wildkindness-cdn.b-cdn.net/post-shorts/tia-silk/2-unleashed.mp4',
    'https://wildkindness-cdn.b-cdn.net/post-shorts/tia-silk/3-lace.mp4',
  ],
};
const POST_OF_URL = 'https://onlyfans.com/wildkindness69';
function getPostShorts(slug) {
  var s = POST_SHORTS[slug];
  return (s && s.length > 0) ? s : null;
}

// ---- Per-slug video tile thumbnails (Bunny Storage URLs) ----
// When a slug has an entry here AND viewport is desktop-width AND the user
// has not opted into reduced motion, the tile renders an autoplay-muted-loop
// video. Otherwise it falls through to the still image / slideshow.
const TILE_VIDEOS = {
  'random-acts-of-wild-kindness-the-vlog-episode-1': 'https://staging.wildkindness69.com/content/media/2026/05/vlog-tile.mp4',
  'ts-suzie-xl-x-wild-kindness':          'https://staging.wildkindness69.com/content/media/2026/05/ts-suzie-xl-tile.mp4',
  'foxy-alex-x-wild-kindness':            'https://wildkindness-cdn.b-cdn.net/tile-videos/foxy-alex-x-wild-kindness-v2.mp4',
  'trans-fmm-threesome-x-wild-kindness':  'https://wildkindness-cdn.b-cdn.net/tile-videos/trans-fmm-threesome-x-wild-kindness.mp4',
  'straight-guy-does-gay-x-wild-kindness':'https://wildkindness-cdn.b-cdn.net/tile-videos/straight-guy-does-gay-x-wild-kindness.mp4',
  'arielles-first-time-x-wild-kindness':  'https://wildkindness-cdn.b-cdn.net/tile-videos/arielles-first-time-x-wild-kindness.mp4',
  'femmeslut-x-wild-kindness':            'https://wildkindness-cdn.b-cdn.net/tile-videos/femmeslut-x-wild-kindness-v3.mp4',
  'kiki-x-wild-kindness':                 'https://wildkindness-cdn.b-cdn.net/tile-videos/kiki-x-wild-kindness-v2.mp4',
  'tia-silk-x-wild-kindness':             'https://wildkindness-cdn.b-cdn.net/tile-videos/tia-silk-x-wild-kindness-v3.mp4',
};

// Perf-test override — keep null in production. Set to a single URL to point
// every tile at the same clip for stress-testing.
const TILE_VIDEO_TEST_URL = null;

// Hard-gate to desktop: 901px+. Avoids decoder pressure on mobile and matches
// the mobile-takeover threshold.
const FL_IS_DESKTOP = typeof window !== 'undefined'
  && !window.matchMedia('(max-width: 900px)').matches;

// Respect OS-level "Reduce motion" preference — those users get the still
// image / slideshow path instead of autoplay video tiles.
const FL_REDUCED_MOTION = typeof window !== 'undefined'
  && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

function getTileVideo(slug) {
  if (!FL_IS_DESKTOP) return null;
  if (FL_REDUCED_MOTION) return null;
  if (TILE_VIDEO_TEST_URL) return TILE_VIDEO_TEST_URL; // perf-test override
  return TILE_VIDEOS[slug] || null;
}

function slugFromUrl(url) {
  try {
    const m = (url || '').match(/\/([^\/?#]+)\/?$/);
    return m ? m[1] : '';
  } catch (e) { return ''; }
}

// Pull post data from the hidden DOM rows rendered by Ghost
function loadPostsFromDom() {
  const rows = document.querySelectorAll('#wk-blog-posts-data .wk-blog-post-row');
  return Array.from(rows).map((el) => {
    const tag = (el.dataset.tag || '').toLowerCase();
    const url = el.dataset.url || '#';
    const slug = slugFromUrl(url);
    return {
      kind: 'post',
      n: el.dataset.n || '',
      title: el.dataset.title || '',
      url: url,
      slug: slug,
      date: (el.dataset.date || '').replace(/2025/g, '2026'),  /* pre-launch posts dated 2025 in Ghost; show as 2026 — 2026-05-30 */
      dateIso: (el.dataset.dateIso || '').replace(/2025/g, '2026'),
      deck: el.dataset.excerpt || '',
      img: el.dataset.img || 'https://wildkindness-cdn.b-cdn.net/selects-v4/DSC00870.jpg',
      imgs: SLIDESHOWS[slug] || null,
      runtime: el.dataset.runtime ? (el.dataset.runtime + ' min') : '',
      media: tag === 'vlog' ? 'video' : 'photo',
    };
  });
}

const flItems = loadPostsFromDom();

// Lay tiles out on a cols×rows grid. If the grid has more cells than tiles,
// reserve the cells CLOSEST to the center as empty space (so the spinning logo
// behind the stage is visible through the gap) — never a corner.
function buildPositions(total) {
  if (total <= 0) return [];
  const cols = Math.min(4, Math.max(2, Math.ceil(Math.sqrt(total))));
  const rows = Math.ceil(total / cols);
  const empties = cols * rows - total;

  const cells = [];
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      const dx = c - (cols - 1) / 2;
      const dy = r - (rows - 1) / 2;
      cells.push({ c, r, dist: Math.hypot(dx, dy), idx: r * cols + c });
    }
  }

  // Cells to leave empty = the N closest to grid center, with reading-order as tiebreak
  const reserved = new Set(
    [...cells]
      .sort((a, b) => (a.dist - b.dist) || (a.idx - b.idx))
      .slice(0, empties)
      .map((x) => x.idx)
  );

  // Usable cells in natural reading order, one per tile
  const usable = cells.filter((x) => !reserved.has(x.idx));

  return usable.slice(0, total).map((cell, i) => {
    const r1 = Math.sin(i * 12.9898) * 43758.5453;
    const r2 = Math.sin(i * 78.233)  * 43758.5453;
    const r3 = Math.sin(i * 39.346)  * 43758.5453;
    const jx = (r1 - Math.floor(r1) - 0.5) * 120;
    const jy = (r2 - Math.floor(r2) - 0.5) * 100;
    const jz = (r3 - Math.floor(r3) - 0.5) * 140;
    const x = (cell.c - (cols - 1) / 2) * 820 + jx;
    const y = (cell.r - (rows - 1) / 2) * 600 + jy;
    return { x, y, z: jz, rot: 0 };
  });
}

const flPositions = buildPositions(flItems.length);

function mediaIcon(kind) {
  if (kind === 'video') return (<svg width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M3 1.5L10 6.5L3 11.5V1.5Z" fill="#FFF"/></svg>);
  if (kind === 'photo') return (<svg width="15" height="13" viewBox="0 0 15 13" fill="none"><rect x="1" y="3" width="13" height="9" rx="1" stroke="#FFF" strokeWidth="1.3"/><path d="M4.5 3L5.7 1H9.3L10.5 3" stroke="#FFF" strokeWidth="1.3"/><circle cx="7.5" cy="7.5" r="2.2" stroke="#FFF" strokeWidth="1.3"/></svg>);
  return (<svg width="13" height="13" viewBox="0 0 13 13" fill="none"><line x1="2" y1="3.5" x2="11" y2="3.5" stroke="#FFF" strokeWidth="1.3"/><line x1="2" y1="6.5" x2="11" y2="6.5" stroke="#FFF" strokeWidth="1.3"/><line x1="2" y1="9.5" x2="8"  y2="9.5"  stroke="#FFF" strokeWidth="1.3"/></svg>);
}

function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }

// Crossfading slideshow for tiles with multiple images. ~5.5s per slot, 900ms crossfade.
// Each tile starts on a different image AND its timer is offset, so all the slideshows
// on the page are permanently out of phase with each other.
function TileSlideshow({ imgs, seed }) {
  const n = imgs ? imgs.length : 0;
  const startIdx = n ? ((seed || 0) * 2 + 1) % n : 0;
  const [idx, setIdx] = React.useState(startIdx);
  React.useEffect(() => {
    if (!imgs || imgs.length < 2) return;
    const period = 5500;
    const phaseOffset = ((seed || 0) * 1700) % period; // pseudo-random per-tile shift
    let interval;
    const kickoff = setTimeout(() => {
      setIdx((i) => (i + 1) % imgs.length);
      interval = setInterval(() => {
        setIdx((i) => (i + 1) % imgs.length);
      }, period);
    }, phaseOffset);
    return () => { clearTimeout(kickoff); if (interval) clearInterval(interval); };
  }, [imgs && imgs.length, seed]);
  return (
    <>
      {imgs.map((src, j) => (
        <img
          key={j}
          src={src}
          alt=""
          draggable={false}
          loading={j === 0 ? 'eager' : 'lazy'}
          style={{
            position: 'absolute',
            inset: 0,
            width: '100%',
            height: '100%',
            objectFit: 'cover',
            opacity: j === idx ? 1 : 0,
            transition: 'opacity 0.9s ease',
            zIndex: j === idx ? 1 : 0,
          }}
        />
      ))}
    </>
  );
}

function WildIdea09_FloatingArchive() {
  const wrapRef = React.useRef(null);
  const cameraDomRef = React.useRef(null);
  const movedRef = React.useRef(false);
  const camRef = React.useRef({ camX:0, camY:0, dolly:-1650, vx:0, vy:0, vz:0, yaw:0, pitch:0 });
  const rafRef = React.useRef(null);
  const [focusedIdx, setFocusedIdx] = React.useState(null);
  // Index of the currently-visible short within the focused post's carousel.
  // Resets to 0 every time the user opens a different tile (see useEffect below).
  const [shortIdx, setShortIdx] = React.useState(0);
  // Persist the carousel unmute choice for the session. Default false =
  // muted, so audio NEVER starts unprompted; once the user unmutes via the
  // native controls it stays on as they flip clips (and on reopen).
  const audioWantedRef = React.useRef(false);
  const [dragging, setDragging] = React.useState(false);
  const draggingRef = React.useRef(false);
  const [intro, setIntro] = React.useState(true);
  const [introReady, setIntroReady] = React.useState(false);
  const [introAnim, setIntroAnim] = React.useState(true);

  React.useEffect(() => {
    const r = requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        setIntroReady(true);
        setTimeout(() => setIntro(false), 40);
      });
    });
    const t = setTimeout(() => setIntroAnim(false), 3200);
    return () => { cancelAnimationFrame(r); clearTimeout(t); };
  }, []);

  // When a tile is focused, snap the camera back to origin so the tile's
  // fly-toward-viewer animation targets the actual viewport centre rather
  // than wherever the user happened to have dragged the camera.
  React.useEffect(() => {
    if (focusedIdx === null) return;
    const c = camRef.current;
    c.camX = 0; c.camY = 0; c.dolly = -1650;   /* 2026-05-31 — pulled back (700→1100→1650) so the whole grid is visible at rest */
    c.vx = 0; c.vy = 0; c.vz = 0;
    c.yaw = 0; c.pitch = 0;
  }, [focusedIdx]);

  // Reset the carousel to the first short whenever the focused tile changes
  // (including close → reopen, or next/prev post navigation while focused).
  React.useEffect(() => {
    setShortIdx(0);
  }, [focusedIdx]);

  React.useEffect(() => {
    let mounted = true;
    let last = performance.now();
    const tick = () => {
      if (!mounted) return;
      const now = performance.now();
      const dt = Math.min(2.5, (now - last) / 16.67);
      last = now;
      const c = camRef.current;
      const drag = draggingRef.current ? 0.72 : 0.93;
      c.vx *= Math.pow(drag, dt);
      c.vy *= Math.pow(drag, dt);
      c.vz *= Math.pow(0.90, dt);
      if (focusedIdx === null && !draggingRef.current) {
        c.vx += Math.sin(now * 0.00041) * 0.05 * dt;
        c.vy += Math.cos(now * 0.00029) * 0.035 * dt;
      }
      c.camX  += c.vx * dt;
      c.camY  += c.vy * dt;
      c.dolly += c.vz * dt;
      const BX = 1800, BY = 1200, BZminus = -2200, BZplus = 1400;
      if (c.camX >  BX) { c.vx -= (c.camX - BX) * 0.04 * dt; c.vx *= 0.85; }
      if (c.camX < -BX) { c.vx -= (c.camX + BX) * 0.04 * dt; c.vx *= 0.85; }
      if (c.camY >  BY) { c.vy -= (c.camY - BY) * 0.04 * dt; c.vy *= 0.85; }
      if (c.camY < -BY) { c.vy -= (c.camY + BY) * 0.04 * dt; c.vy *= 0.85; }
      if (c.dolly > BZplus)  { c.vz -= (c.dolly - BZplus) * 0.05 * dt; c.vz *= 0.80; }
      if (c.dolly < BZminus) { c.vz -= (c.dolly - BZminus) * 0.05 * dt; c.vz *= 0.80; }
      const targetYaw   = clamp(-c.vx * 0.0015, -0.10, 0.10);
      const targetPitch = clamp( c.vy * 0.0012, -0.08, 0.08);
      c.yaw   += (targetYaw   - c.yaw)   * 0.10 * dt;
      c.pitch += (targetPitch - c.pitch) * 0.10 * dt;
      if (cameraDomRef.current) {
        cameraDomRef.current.style.transform =
          `translate3d(${c.camX}px, ${c.camY}px, ${c.dolly}px) rotateY(${c.yaw}rad) rotateX(${c.pitch}rad)`;
      }
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => { mounted = false; cancelAnimationFrame(rafRef.current); };
  }, [focusedIdx]);

  React.useEffect(() => {
    const el = wrapRef.current;
    if (!el) return;
    let down = false, lastX = 0, lastY = 0, lastT = 0, downX = 0, downY = 0;
    const onDown = (e) => {
      if (focusedIdx !== null) return;
      if (e.preventDefault && e.button !== undefined) { try { e.preventDefault(); } catch (_) {} }
      down = true; setDragging(true); draggingRef.current = true;
      lastX = e.touches ? e.touches[0].clientX : e.clientX;
      lastY = e.touches ? e.touches[0].clientY : e.clientY;
      downX = lastX; downY = lastY;
      movedRef.current = false;
      lastT = performance.now();
      camRef.current.vx = 0; camRef.current.vy = 0;
    };
    const onMove = (e) => {
      if (!down) return;
      const x = e.touches ? e.touches[0].clientX : e.clientX;
      const y = e.touches ? e.touches[0].clientY : e.clientY;
      const now = performance.now();
      const dx = x - lastX, dy = y - lastY;
      const dt = Math.max(1, now - lastT);
      lastX = x; lastY = y; lastT = now;
      const totalDx = x - downX, totalDy = y - downY;
      if (Math.hypot(totalDx, totalDy) > 5) movedRef.current = true;
      const c = camRef.current;
      c.camX += dx * 3.2;
      c.camY += dy * 2.8;
      const speedFactor = 16.67 / dt;
      c.vx = (dx * 3.2) * speedFactor * 0.55;
      c.vy = (dy * 2.8) * speedFactor * 0.55;
    };
    const onUp = () => { down = false; setDragging(false); draggingRef.current = false; };
    const onWheel = (e) => {
      if (focusedIdx !== null) return;
      e.preventDefault();
      const dz = (Math.abs(e.deltaY) > Math.abs(e.deltaX) ? e.deltaY : e.deltaX);
      camRef.current.vz += -dz * 0.6;
    };
    const onKey = (e) => {
      if (focusedIdx !== null) {
        if (e.key === 'Escape') setFocusedIdx(null);
        if (e.key === 'ArrowRight' && focusedIdx < flItems.length - 1) setFocusedIdx(focusedIdx + 1);
        if (e.key === 'ArrowLeft'  && focusedIdx > 0)                  setFocusedIdx(focusedIdx - 1);
        return;
      }
      const c = camRef.current;
      if (e.key === 'ArrowLeft')  c.vx +=  18;
      if (e.key === 'ArrowRight') c.vx += -18;
      if (e.key === 'ArrowUp')    c.vy +=  14;
      if (e.key === 'ArrowDown')  c.vy += -14;
    };
    el.addEventListener('mousedown', onDown);
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    el.addEventListener('touchstart', onDown, { passive:true });
    window.addEventListener('touchmove', onMove, { passive:true });
    window.addEventListener('touchend', onUp);
    el.addEventListener('wheel', onWheel, { passive:false });
    window.addEventListener('keydown', onKey);
    return () => {
      el.removeEventListener('mousedown', onDown);
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
      el.removeEventListener('touchstart', onDown);
      window.removeEventListener('touchmove', onMove);
      window.removeEventListener('touchend', onUp);
      el.removeEventListener('wheel', onWheel);
      window.removeEventListener('keydown', onKey);
    };
  }, [focusedIdx]);

  const initialCamTransform = `translate3d(0, 0, -1650px)`;
  const focused = focusedIdx !== null ? flItems[focusedIdx] : null;

  if (flItems.length === 0) {
    return (
      <div className="fl-wrap" style={{ display:'flex', alignItems:'center', justifyContent:'center' }}>
        <style>{flCss}</style>
        <p style={{ fontFamily:'PP Fragment Serif, serif', fontStyle:'italic', fontSize:22, color:'rgba(255,255,255,0.55)' }}>
          No posts yet. Check back soon.
        </p>
      </div>
    );
  }

  return (
    <>
      <style>{flCss}</style>
      <div
        className={'fl-wrap' + (dragging ? ' dragging' : '') + (focusedIdx !== null ? ' focused' : '')}
        ref={wrapRef}
        tabIndex={0}
      >
        {introReady && <div className="fl-bang go"></div>}

        <div className="fl-stars"></div>
        <div className="fl-dust">
          {Array.from({ length: 40 }).map((_, i) => {
            const r1 = Math.sin(i * 12.9898) * 43758.5453;
            const r2 = Math.sin(i * 78.233)  * 43758.5453;
            const r3 = Math.sin(i * 39.346)  * 43758.5453;
            const lx = (r1 - Math.floor(r1)) * 100;
            const ly = (r2 - Math.floor(r2)) * 100;
            const dur = 14 + ((r3 - Math.floor(r3)) * 18);
            const delay = -((r3 - Math.floor(r3)) * dur);
            const cls = i % 9 === 0 ? 'red' : (i % 3 === 0 ? 'lg' : '');
            return <i key={i} className={cls} style={{ left: lx + '%', top: ly + '%', animationDuration: dur + 's', animationDelay: delay + 's' }}></i>;
          })}
        </div>

        <div className="fl-stage" style={{ opacity: focusedIdx !== null ? 0 : 1, transition:'opacity .45s ease', pointerEvents: focusedIdx !== null ? 'none' : 'auto' }}>
          <div className="fl-camera" ref={cameraDomRef} style={{ transform: initialCamTransform }}>

            <div className={'fl-backdrop' + (intro ? ' intro' : '') + (introAnim ? ' intro-anim' : '')}>
              <div className="spin">
                {/* 2026-05-30 — Pre-keyed transparent APNG instead of the raw
                    green-screen WEBM + SVG chroma-key. Safari/WebKit does NOT
                    apply SVG filters to <video>, so it showed the raw green
                    background. The APNG is already transparent and renders
                    identically in every browser. */}
                <img
                  src="https://wildkindness-cdn.b-cdn.net/wild-kindness-heart-drip.apng"
                  alt=""
                  draggable={false}
                />
              </div>
            </div>

            {flItems.map((p, i) => {
              const pos = flPositions[i];
              const isFocused = focusedIdx === i;
              const dim = focusedIdx !== null && !isFocused;
              const scale = isFocused ? 1.7 : 1;
              // On focus, override the grid position so the tile flies to
              // viewport center along z toward the viewer.  The camera is
              // reset to origin in a useEffect below so "0,0" really IS
              // the centre of what the user sees.
              const fx   = isFocused ? 0          : pos.x;
              const fy   = isFocused ? 0          : pos.y;
              const fz   = isFocused ? 500        : pos.z;
              const frot = isFocused ? 0          : pos.rot;
              return (
                <article
                  key={'post-' + i}
                  className={'fl-tile' + (isFocused ? ' focused' : '') + (dim ? ' dim' : '') + (intro ? ' intro' : '') + (introReady ? ' intro-ready' : '')}
                  style={{
                    transform: `translate3d(${fx}px, ${fy}px, ${fz}px) rotateY(${frot}deg) translate(-50%, -50%) scale(${scale})`,
                    transformOrigin:'50% 50%',
                    '--intro-delay': (0.15 + (i * 0.06)).toFixed(2) + 's',
                    '--intro-rot': ((Math.sin(i * 39.346) * 43758.5453 % 1) * 90 - 45).toFixed(2) + 'deg',
                  }}
                  onMouseDown={(e) => { e.preventDefault(); }}
                  onDragStart={(e) => { e.preventDefault(); }}
                  onClick={(e) => {
                    e.stopPropagation();
                    if (movedRef.current) return;
                    setFocusedIdx(isFocused ? null : i);
                  }}
                >
                  <div className="fl-tile-inner" style={{
                    /* Each tile gets a DIFFERENT drift PATH (flBob1..6) so the
                       distance between neighbors constantly changes — bobbing in
                       space, not a grid drifting in unison. The (i*5)%6 step
                       spreads consecutive indices across non-adjacent paths
                       (0,5,4,3,2,1,0,5,…), and the varied duration + negative
                       delay desync even same-path tiles. */
                    '--bob-name': 'flBob' + (((i * 5) % 6) + 1),
                    '--bob-dur': (8.5 + ((Math.sin(i * 53.13) * 43758.5453 % 1 + 1) % 1) * 5).toFixed(2) + 's',
                    '--bob-delay': '-' + ((i * 1.7) % 9).toFixed(2) + 's'
                  }}>
                    {(() => {
                      const videoUrl = getTileVideo(p.slug);
                      if (videoUrl) {
                        // onPause re-resume: Chrome auto-pauses muted videos once
                        // at ~0.4s due to its 3D-transform / offscreen heuristic.
                        // Re-call play() on pause to keep the loop alive.
                        return (
                          <video
                            src={videoUrl}
                            autoPlay
                            muted
                            loop
                            playsInline
                            preload="auto"
                            onPause={(e) => { const el = e.currentTarget; setTimeout(() => { try { el.play(); } catch (_) {} }, 50); }}
                          />
                        );
                      }
                      if (p.imgs && p.imgs.length > 1) {
                        return <TileSlideshow imgs={p.imgs} seed={i} />;
                      }
                      return p.img && <img src={p.img} alt="" draggable={false} />;
                    })()}
                  </div>
                  <h2 className="fl-tile-title">{p.title.replace(/\s*[×x]\s*Wild\s+Kindness\s*$/i, '')}</h2>
                </article>
              );
            })}
          </div>
        </div>

        {focusedIdx === null && <div className="fl-crosshair"></div>}


        {focused && (() => {
          const shorts = getPostShorts(focused.slug);
          return (
          <>
            <div className="fl-post on" onClick={(e) => { if (e.target === e.currentTarget) setFocusedIdx(null); }}>
              <figure className="fl-post-media">
                {shorts ? (
                  <>
                    <video
                      key={focused.slug + '-' + shortIdx}
                      src={shorts[shortIdx]}
                      poster={focused.img || undefined}
                      controls
                      playsInline
                      autoPlay
                      preload="auto"
                      data-wk-player="1"
                      ref={(el) => { if (el) el.muted = !audioWantedRef.current; }}
                      onVolumeChange={(e) => { audioWantedRef.current = !e.currentTarget.muted; }}
                    />
                    {shorts.length > 1 && (
                      <>
                        <button
                          className={'fl-shorts-prev' + (shortIdx === 0 ? ' dim' : '')}
                          onClick={(e) => { e.stopPropagation(); if (shortIdx > 0) setShortIdx(shortIdx - 1); }}
                          aria-label="Previous clip"
                        >‹</button>
                        <button
                          className={'fl-shorts-next' + (shortIdx === shorts.length - 1 ? ' dim' : '')}
                          onClick={(e) => { e.stopPropagation(); if (shortIdx < shorts.length - 1) setShortIdx(shortIdx + 1); }}
                          aria-label="Next clip"
                        >›</button>
                        <div className="fl-shorts-counter">{(shortIdx + 1) + ' / ' + shorts.length}</div>
                        <div className="fl-shorts-dots" onClick={(e) => e.stopPropagation()}>
                          {shorts.map((_, i) => (
                            <button
                              key={i}
                              className={'fl-shorts-dot' + (i === shortIdx ? ' active' : '')}
                              onClick={() => setShortIdx(i)}
                              aria-label={'Clip ' + (i + 1)}
                            />
                          ))}
                        </div>
                      </>
                    )}
                  </>
                ) : (
                  <>
                    {focused.img && <img src={focused.img} alt="" />}
                    {focused.media === 'video' && <button className="play" aria-label="play"></button>}
                  </>
                )}
              </figure>
              <div className="fl-post-content">
                <div className="meta">
                  <span className="dot"></span>
                  <span>{focused.date}{focused.runtime ? ' · ' + focused.runtime : ''}</span>
                </div>
                <h2>{focused.title}</h2>
                <p>{focused.deck}</p>
                <div className="actions">
                  {shorts
                    ? <a className="read" href={POST_OF_URL} target="_blank" rel="noopener">Watch full video on OnlyFans</a>
                    : <a className="read" href={focused.url}>Read the full post</a>}
                  <span className="back" onClick={() => setFocusedIdx(null)}>Back to space</span>
                </div>
              </div>
            </div>
            <button className="fl-close" onClick={() => setFocusedIdx(null)} aria-label="close">×</button>
            <button className={'fl-nav prev' + (focusedIdx === 0 ? ' dim' : '')} onClick={(e) => { e.stopPropagation(); if (focusedIdx > 0) setFocusedIdx(focusedIdx - 1); }} aria-label="previous"><svg width="22" height="22" viewBox="0 0 22 22" fill="none"><path d="M14 4L7 11L14 18" stroke="#FFF" strokeWidth="1.6"/></svg></button>
            <button className={'fl-nav next' + (focusedIdx === flItems.length - 1 ? ' dim' : '')} onClick={(e) => { e.stopPropagation(); if (focusedIdx < flItems.length - 1) setFocusedIdx(focusedIdx + 1); }} aria-label="next"><svg width="22" height="22" viewBox="0 0 22 22" fill="none"><path d="M8 4L15 11L8 18" stroke="#FFF" strokeWidth="1.6"/></svg></button>
          </>
          );
        })()}

      </div>
    </>
  );
}

const rootEl = document.getElementById('wk-blog-root');
if (rootEl) {
  ReactDOM.createRoot(rootEl).render(<WildIdea09_FloatingArchive />);
}