{"id":148,"date":"2026-02-12T22:04:57","date_gmt":"2026-02-12T19:04:57","guid":{"rendered":"https:\/\/www.candyxxx.net\/?page_id=148"},"modified":"2026-05-30T17:11:06","modified_gmt":"2026-05-30T14:11:06","slug":"video","status":"publish","type":"page","link":"https:\/\/candyxxx.net\/en\/video\/","title":{"rendered":"Video"},"content":{"rendered":"\n<style>\n:root{\n  --pad-b: var(--mbm-h, 84px);\n  --stack-bottom: calc(var(--pad-b) + 86px);\n  --right: 16px;\n  --icon: 30px;\n  --txt: 12px;\n  --seek-bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);\n\/* moved down *\/\n}\n\n@media (max-width: 1024px){\n  :root{\n    --seek-bottom: calc(var(--pad-b) + 12px);\n    --stack-bottom: calc(var(--pad-b) + 86px);\n  }\n}\n\nhtml,body{height:100%;margin:0;background:#000;overflow:hidden!important;}\nhtml{scrollbar-gutter:auto!important;}\n.ct-container,.site-content,.content-area,.entry-content,.content{max-width:none!important;width:100%!important;}\n.entry-content{padding:0!important;margin:0!important;}\n\n#candy-feed{\n  position:fixed; inset:0;\n  background:#000;\n  overflow-y:scroll;\n  -webkit-overflow-scrolling:touch;\n  scroll-snap-type:y mandatory;\n  scroll-snap-stop:always;\n  overscroll-behavior-y:contain;\n  scrollbar-width:none;\n  -ms-overflow-style:none;\n}\n#candy-feed::-webkit-scrollbar{\n  width:0;\n  height:0;\n  display:none;\n}\n\n.candy-item{\n  height:100svh;\n  scroll-snap-align:start;\n  scroll-snap-stop:always;\n  display:flex;\n  align-items:center;\n  justify-content:center;\n  background:#000;\n  position:relative;\n  padding-bottom: var(--pad-b);\n  box-sizing:border-box;\n}\n\n.candy-frame{\n  position:relative;\n  height:calc(100svh - var(--pad-b));\n  aspect-ratio: 9 \/ 16;\n  width:auto;\n  max-width:100vw;\n  margin:0 auto;\n  background:#000;\n  overflow:hidden;\n}\n\n.candy-video{\n  position:absolute; inset:0;\n  width:100%; height:100%;\n  object-fit:contain;\n  background:#000;\n}\n\n\/* Page-specific Blocksy fixes from the old working version *\/\nbody.page-id-148,\nbody.page-id-148 html,\nbody.page-id-148 body{\n  height:100% !important;\n  overflow:hidden !important;\n  overflow-x:hidden !important;\n}\n\nbody.page-id-148 .site,\nbody.page-id-148 #main,\nbody.page-id-148 .site-content,\nbody.page-id-148 .ct-container,\nbody.page-id-148 .ct-content,\nbody.page-id-148 .content-area,\nbody.page-id-148 #primary,\nbody.page-id-148 main,\nbody.page-id-148 article,\nbody.page-id-148 .entry-content{\n  margin:0 !important;\n  padding:0 !important;\n  max-width:100% !important;\n  width:100% !important;\n  height:100% !important;\n}\n\nbody.page-id-148 #candy-feed{\n  height:calc(100dvh - var(--mbm-h) - env(safe-area-inset-bottom)) !important;\n  width:100vw !important;\n  overflow-y:auto !important;\n  overflow-x:hidden !important;\n  scroll-snap-type:y mandatory !important;\n  margin:0 !important;\n  padding:0 !important;\n  padding-bottom:0 !important;\n  box-sizing:border-box !important;\n  display:flex !important;\n  flex-direction:column !important;\n  align-items:center !important;\n  scrollbar-width:none;\n}\n\nbody.page-id-148 #candy-feed::-webkit-scrollbar{\n  width:0;\n  height:0;\n}\n\nbody.page-id-148 .candy-item{\n  width:100vw !important;\n  height:calc(100dvh - var(--mbm-h) - env(safe-area-inset-bottom)) !important;\n  scroll-snap-align:start !important;\n  display:flex !important;\n  align-items:center !important;\n  justify-content:center !important;\n  margin:0 !important;\n  padding:0 !important;\n  position:relative !important;\n}\n\nbody.page-id-148 .candy-player,\nbody.page-id-148 .candy-frame{\n  width:min(100vw, calc((100dvh - var(--mbm-h) - env(safe-area-inset-bottom)) * 9 \/ 16)) !important;\n  height:calc(100dvh - var(--mbm-h) - env(safe-area-inset-bottom)) !important;\n  max-width:100vw !important;\n  margin:0 !important;\n  padding:0 !important;\n  position:absolute !important;\n  top:0 !important;\n  left:50% !important;\n  right:auto !important;\n  transform:translateX(-50%) !important;\n  background:#000 !important;\n}\n\nbody.page-id-148 .candy-frame iframe,\nbody.page-id-148 .candy-frame video{\n  width:100% !important;\n  height:100% !important;\n  display:block !important;\n  border:0 !important;\n}\n\n@media (max-width: 1024px){\n  body.page-id-148 #candy-feed{\n    height:var(--app-vh, 100svh) !important;\n    bottom:auto !important;\n    width:100% !important;\n    padding-bottom:var(--pad-b) !important;\n    display:block !important;\n    align-items:stretch !important;\n  }\n\n  body.page-id-148 .candy-item{\n    width:100% !important;\n    height:var(--app-vh, 100svh) !important;\n    padding-bottom:var(--pad-b) !important;\n    box-sizing:border-box !important;\n  }\n\n  body.page-id-148 .candy-player,\n  body.page-id-148 .candy-frame{\n    position:relative !important;\n    top:auto !important;\n    left:auto !important;\n    right:auto !important;\n    transform:none !important;\n    width:auto !important;\n    max-width:100vw !important;\n    height:calc(var(--app-vh, 100svh) - var(--pad-b)) !important;\n    margin:0 auto !important;\n  }\n}\n\n\/* Spinner *\/\n.candy-spinner{\n  position:absolute;inset:0;\n  display:flex;align-items:center;justify-content:center;\n  z-index:25;pointer-events:none;\n  transition:opacity .2s;\n}\n.candy-spinner.hide{opacity:0;}\n.candy-spinner:before{\n  content:'';\n  width:44px;height:44px;border-radius:50%;\n  border:3px solid rgba(255,255,255,.22);\n  border-top-color:#fff;\n  animation:cspin 1s linear infinite;\n}\n@keyframes cspin{to{transform:rotate(360deg)}}\n\n\/* UI *\/\n.candy-ui{ position:absolute; inset:0; z-index:30; pointer-events:none; }\n\n\/* Right stack (no circles) *\/\n.candy-stack{\n  position:absolute;\n  right:var(--right);\n  bottom:var(--stack-bottom);\n  display:flex;\n  flex-direction:column;\n  align-items:center;\n  gap:18px;\n  pointer-events:auto;\n}\n\n.candy-btn{\n  appearance:none;border:0;background:transparent;\n  padding:0;margin:0;\n  cursor:pointer;\n  color:#fff;\n  display:flex;align-items:center;justify-content:center;\n  filter: drop-shadow(0 2px 10px rgba(0,0,0,.7));\n}\n.candy-btn svg{\n  width:var(--icon);height:var(--icon);\n  display:block;\n}\n\n\/* Like count *\/\n.candy-count{\n  margin-top:7px;\n  font:600 var(--txt)\/1.1 system-ui,-apple-system,Segoe UI,Roboto,Arial;\n  color:#fff;\n  text-shadow:0 2px 10px rgba(0,0,0,.8);\n  opacity:.95;\n  user-select:none;\n}\n\n.candy-topbar{\n  position:absolute; left:0; right:0; top:0;\n  padding:10px 14px;\n  display:flex;\n  justify-content:center;\n  pointer-events:none;\n  z-index:35;\n}\n.candy-title{\n  max-width:92vw;\n  font:700 14px\/1.2 system-ui,-apple-system,Segoe UI,Roboto,Arial;\n  color:#fff;\n  text-shadow:0 2px 10px rgba(0,0,0,.8);\n  white-space:nowrap;\n  overflow:hidden;\n  text-overflow:ellipsis;\n  opacity:.92;\n}\n\n\/* Seek bar *\/\n.candy-seek{\n  position:absolute;\n  left:12px; right:12px;\n  bottom:var(--seek-bottom);\n  z-index:40;\n  pointer-events:auto;\n  display:flex;\n  gap:10px;\n  align-items:center;\n}\n\n\/* Pause indicator (triangle) *\/\n.candy-pause-indicator{\n  position:absolute;\n  left:50%; top:50%;\n  transform:translate(-50%,-50%);\n  width:min(64px, 20vw);\n  opacity:0;\n  transition:opacity .12s ease;\n  pointer-events:none;\n  z-index:45;\n  filter: drop-shadow(0 2px 14px rgba(0,0,0,.75));\n}\n.candy-pause-indicator.show{opacity:.55;}\n.candy-pause-indicator svg{width:100%;height:auto;display:block;}\n\n\n\/* Android \"tap to start\" gate (uses same triangle as pause indicator) *\/\n.candy-start-gate{\n  position:absolute;\n  inset:0;\n  display:none;\n  align-items:center;\n  justify-content:center;\n  z-index:46;\n  cursor:pointer;\n}\n.candy-start-gate.show{ display:flex; }\n.candy-start-gate-icon{\n  width:min(64px, 20vw);\n  opacity:.55;\n  filter: drop-shadow(0 2px 14px rgba(0,0,0,.75));\n}\n.candy-start-gate-icon svg{ width:100%; height:auto; display:block; }\n\n.candy-time{\n  font:600 12px\/1 system-ui,-apple-system,Segoe UI,Roboto,Arial;\n  color:#fff;\n  opacity:.85;\n  text-shadow:0 2px 10px rgba(0,0,0,.8);\n  user-select:none;\n}\n.candy-range{\n  flex:1;\n  width:100%;\n  pointer-events:none;     \/* \u0420\u00a0\u0421\u201d\u0420\u00a0\u0412\u00bb\u0420\u00a0\u0421\u2018\u0420\u00a0\u0421\u201d\u0420\u00a0\u0412\u00b0\u0420\u00a0\u0412\u00b5\u0420\u00a0\u0421\u0098\/\u0420\u040e\u0432\u0402\u0459\u0420\u040e\u0420\u040f\u0420\u00a0\u0420\u2026\u0420\u00a0\u0412\u00b5\u0420\u00a0\u0421\u0098 \u0420\u00a0\u0421\u2014\u0420\u00a0\u0421\u2022 \u0420\u00a0\u0421\u2022\u0420\u00a0\u0412\u00b1\u0420\u00a0\u0412\u00b5\u0420\u040e\u0420\u201a\u0420\u040e\u0432\u0402\u0459\u0420\u00a0\u0421\u201d\u0420\u00a0\u0412\u00b5 *\/\n  -webkit-appearance:none;\n  appearance:none;\n  height:20px;\n  background:transparent;\n  padding:0;margin:0;\n  --seek-c1: rgba(125, 211, 252, .95); \/* \u0420\u00a0\u0420\u2026\u0420\u00a0\u0412\u00b5\u0420\u00a0\u0412\u00b6\u0420\u00a0\u0420\u2026\u0420\u040e\u0432\u0402\u2116\u0420\u00a0\u0432\u201e\u2013 \u0420\u00a0\u0421\u2013\u0420\u00a0\u0421\u2022\u0420\u00a0\u0412\u00bb\u0420\u040e\u0421\u201c\u0420\u00a0\u0412\u00b1\u0420\u00a0\u0421\u2022\u0420\u00a0\u0432\u201e\u2013 *\/\n  --seek-c2: rgba(56, 189, 248, .95);  \/* \u0420\u00a0\u0412\u00b1\u0420\u00a0\u0421\u2022\u0420\u00a0\u0412\u00bb\u0420\u00a0\u0412\u00b5\u0420\u00a0\u0412\u00b5 \u0420\u00a0\u0420\u2026\u0420\u00a0\u0412\u00b0\u0420\u040e\u0420\u0453\u0420\u040e\u0432\u0402\u2116\u0420\u040e\u0432\u0402\u00b0\u0420\u00a0\u0412\u00b5\u0420\u00a0\u0420\u2026\u0420\u00a0\u0420\u2026\u0420\u040e\u0432\u0402\u2116\u0420\u00a0\u0432\u201e\u2013 \u0420\u00a0\u0421\u2013\u0420\u00a0\u0421\u2022\u0420\u00a0\u0412\u00bb\u0420\u040e\u0421\u201c\u0420\u00a0\u0412\u00b1\u0420\u00a0\u0421\u2022\u0420\u00a0\u0432\u201e\u2013 *\/\n  --seek-p:0%;\n}\n.candy-range-wrap{\n  flex:1;\n  position:relative;\n  height:28px;            \/* \u0420\u00a0\u0412\u00b1\u0420\u00a0\u0421\u2022\u0420\u00a0\u0412\u00bb\u0420\u040e\u0420\u0409\u0420\u040e\u0432\u201a\u00ac\u0420\u00a0\u0421\u2022\u0420\u00a0\u0432\u201e\u2013 \u0420\u040e\u0432\u0402\u00a6\u0420\u00a0\u0421\u2018\u0420\u040e\u0432\u0402\u0459\u0420\u00a0\u0412\u00b1\u0420\u00a0\u0421\u2022\u0420\u00a0\u0421\u201d\u0420\u040e\u0420\u0453 \u0420\u00a0\u0421\u201d\u0420\u00a0\u0412\u00b0\u0420\u00a0\u0421\u201d \u0420\u00a0\u0420\u2020 TikTok *\/\n  display:flex;\n  align-items:center;\n  touch-action:none;      \/* \u0420\u040e\u0432\u0402\u040e\u0420\u040e\u0432\u0402\u0459\u0420\u00a0\u0421\u2022\u0420\u00a0\u0412\u00b1\u0420\u040e\u0432\u0402\u2116 \u0420\u00a0\u0421\u2014\u0420\u00a0\u0412\u00b5\u0420\u040e\u0420\u201a\u0420\u00a0\u0412\u00b5\u0420\u040e\u0432\u0402\u0459\u0420\u00a0\u0412\u00b0\u0420\u040e\u0420\u0453\u0420\u00a0\u0421\u201d\u0420\u00a0\u0421\u2018\u0420\u00a0\u0420\u2020\u0420\u00a0\u0412\u00b0\u0420\u00a0\u0420\u2026\u0420\u00a0\u0421\u2018\u0420\u00a0\u0412\u00b5 \u0420\u00a0\u0412\u00b1\u0420\u040e\u0432\u0402\u2116\u0420\u00a0\u0412\u00bb\u0420\u00a0\u0421\u2022 \u0420\u040e\u0420\u0453\u0420\u040e\u0432\u0402\u0459\u0420\u00a0\u0412\u00b0\u0420\u00a0\u0412\u00b1\u0420\u00a0\u0421\u2018\u0420\u00a0\u0412\u00bb\u0420\u040e\u0420\u0409\u0420\u00a0\u0420\u2026\u0420\u040e\u0432\u0402\u2116\u0420\u00a0\u0421\u0098 *\/\n}\n\n.candy-range::-webkit-slider-runnable-track{\n  height:4px;border-radius:999px;\n  background:linear-gradient(to right,\n    var(--seek-c1) 0%,\n    var(--seek-c2) var(--seek-p),\n    rgba(255,255,255,.18) var(--seek-p),\n    rgba(255,255,255,.18) 100%\n  );\n}\n.candy-range::-webkit-slider-thumb{\n  -webkit-appearance:none;\n  width:14px;height:14px;border-radius:50%;\n  background:var(--seek-c2);\n  border:2px solid rgba(255,255,255,.55);\n  margin-top:-5px;\n  box-shadow:0 2px 10px rgba(0,0,0,.6);\n}\n.candy-range::-moz-range-track{\n  height:4px;border-radius:999px;\n  background:linear-gradient(to right,\n    var(--seek-c1) 0%,\n    var(--seek-c2) var(--seek-p),\n    rgba(255,255,255,.18) var(--seek-p),\n    rgba(255,255,255,.18) 100%\n  );\n}\n.candy-range::-moz-range-thumb{\n  width:14px;height:14px;border-radius:50%;\n  background:var(--seek-c2);\n  border:2px solid rgba(255,255,255,.55);\n  box-shadow:0 2px 10px rgba(0,0,0,.6);\n}\n\n\/* Tap zone *\/\n.candy-tapzone{ position:absolute; inset:0; z-index:20; pointer-events:auto; }\n\n@supports(padding: max(0px)){\n  :root{ --pad-b: max(var(--mbm-h, 84px), env(safe-area-inset-bottom)); }\n}\n@media (prefers-reduced-motion: reduce){\n  .candy-spinner:before{animation:none}\n}\n\n  .candy-endcard{\n    height: 100vh;\n    width: 100%;\n    display:flex;\n    flex-direction:column;\n    align-items:center;\n    justify-content:center;\n    gap:10px;\n    color:#fff;\n    background: #000;\n    text-align:center;\n    padding: 24px;\n    box-sizing:border-box;\n  }\n  .candy-end-title{ font-size:20px; font-weight:700; opacity:.95; }\n  .candy-end-sub{ font-size:14px; opacity:.75; max-width: 420px; }\n\n  .candy-view-counter{\n    position: fixed;\n    top: calc(env(safe-area-inset-top, 0px) + 10px);\n    left: 12px;\n    z-index: 999999;\n    background: rgba(0,0,0,0.45);\n    border: 1px solid rgba(255,255,255,0.10);\n    backdrop-filter: blur(10px);\n    -webkit-backdrop-filter: blur(10px);\n    border-radius: 14px;\n    padding: 10px 12px;\n    display:flex;\n    gap: 12px;\n    align-items:center;\n    font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;\n    user-select:none;\n  }\n  .candy-view-counter .lbl{ font-size:11px; opacity:.70; margin-right:6px; }\n  .candy-view-counter .val{ font-size:13px; font-weight:700; letter-spacing:.2px; }\n  .candy-view-counter__total, .candy-view-counter__today{ display:flex; align-items:baseline; }\n\n\n@media (max-width: 1024px){\n  #candy-view-counter{ display:none !important; }\n}\n\n.candy-btn.view{ cursor:default; opacity:.95; }\n\n\/* \u0432\u201d\u0402\u0432\u201d\u0402 bookmark \/ favourite \u0432\u201d\u0402\u0432\u201d\u0402 *\/\n.candy-btn.fav svg path{ transition:fill .15s,stroke .15s; }\n.candy-btn.fav.on svg path{ fill:#fff; }\n\n\n.candy-vip-promo{\n  position:absolute;\n  left:12px;\n  right:calc(var(--right) + var(--icon) + 18px);\n  bottom:calc(var(--seek-bottom) + 56px);\n  z-index:38;\n  pointer-events:auto;\n  display:flex;\n  flex-direction:column;\n  align-items:flex-start;\n  gap:9px;\n  animation:vipPromoIn .35s cubic-bezier(.22,1,.36,1) both;\n}\n@keyframes vipPromoIn{\n  from{opacity:0;transform:translateY(10px)}\n  to  {opacity:1;transform:translateY(0)}\n}\n.candy-vip-promo__text{\n  margin:0;\n  font-size:13px;\n  font-weight:500;\n  line-height:1.4;\n  color:rgba(255,255,255,.9);\n  text-shadow:0 1px 10px rgba(0,0,0,.85);\n}\n.candy-vip-promo__btn{\n  display:inline-flex;\n  align-items:center;\n  gap:7px;\n  padding:9px 20px;\n  border-radius:24px;\n  background:linear-gradient(95deg,#57d0ff 0%,#9ea8ff 40%,#e88bff 70%,#ff7ec7 100%);\n  color:#fff!important;\n  font-size:13px;\n  font-weight:700;\n  letter-spacing:.2px;\n  text-decoration:none!important;\n  box-shadow:0 4px 18px rgba(130,100,255,.45),inset 0 1px 0 rgba(255,255,255,.2);\n  transition:transform .18s ease,box-shadow .18s ease;\n  white-space:nowrap;\n}\n.candy-vip-promo__btn:hover{\n  transform:scale(1.05);\n  box-shadow:0 6px 26px rgba(160,100,255,.6),inset 0 1px 0 rgba(255,255,255,.25);\n}\n.candy-vip-promo__btn svg{\n  width:14px;height:14px;flex-shrink:0;\n}\n\n<\/style>\n\n<div id=\"candy-feed\"\n     data-feed=\"\/wp-json\/candy\/v1\/feed\"\n     data-like=\"\/wp-json\/candy\/v1\/like\"\n     data-fav=\"\/wp-json\/candy\/v1\/fav\"\n     data-view=\"\/wp-json\/candy\/v1\/view\"\n     data-progress=\"https:\/\/candyxxx.net\/en\/wp-json\/candy\/v1\/view-progress\"\n     data-counts=\"\/wp-json\/candy\/v1\/counts\"\n     data-vip-variants=\"{&quot;1&quot;:{&quot;text&quot;:&quot;\\u041f\\u043e\\u043b\\u043d\\u044b\\u0439 \\u0441\\u043b\\u0438\\u0432 \\u0441 \\u043d\\u0435\\u0439 \\u0434\\u043e\\u0441\\u0442\\u0443\\u043f\\u0435\\u043d \\u0432 \\u043f\\u0440\\u0438\\u0432\\u0430\\u0442\\u043e\\u0447\\u043a\\u0435&quot;,&quot;btn_label&quot;:&quot;\\u0414\\u043e\\u0441\\u0442\\u0443\\u043f \\u0432 \\u043f\\u0440\\u0438\\u0432\\u0430\\u0442&quot;,&quot;btn_url&quot;:&quot;https:\\\/\\\/t.me\\\/candyxxxrubot&quot;},&quot;2&quot;:{&quot;text&quot;:&quot;\\u041f\\u043e\\u043b\\u043d\\u044b\\u0439 \\u0441\\u043b\\u0438\\u0432 \\u0441 \\u043d\\u0435\\u0439 \\u0434\\u043e\\u0441\\u0442\\u0443\\u043f\\u0435\\u043d \\u0432 \\u043f\\u0440\\u0438\\u0432\\u0430\\u0442\\u043e\\u0447\\u043a\\u0435&quot;,&quot;btn_label&quot;:&quot;\\u0414\\u043e\\u0441\\u0442\\u0443\\u043f \\u0432 VIP&quot;,&quot;btn_url&quot;:&quot;https:\\\/\\\/t.me\\\/dvkprvtbot&quot;},&quot;3&quot;:{&quot;text&quot;:&quot;\\u042d\\u043a\\u0441\\u043a\\u043b\\u044e\\u0437\\u0438\\u0432 \\u0442\\u043e\\u043b\\u044c\\u043a\\u043e \\u0434\\u043b\\u044f VIP-\\u0447\\u043b\\u0435\\u043d\\u043e\\u0432&quot;,&quot;btn_label&quot;:&quot;\\u041e\\u0442\\u043a\\u0440\\u044b\\u0442\\u044c \\u044d\\u043a\\u0441\\u043a\\u043b\\u044e\\u0437\\u0438\\u0432&quot;,&quot;btn_url&quot;:&quot;https:\\\/\\\/t.me\\\/candyxxxrubot&quot;}}\">\n<\/div>\n\n\n<div id=\"candy-view-counter\" class=\"candy-view-counter\" aria-live=\"polite\">\n  <div class=\"candy-view-counter__total\"><span class=\"lbl\">\u0412\u0441\u0435\u0433\u043e<\/span><span class=\"val\" data-k=\"total\">&mdash;<\/span><\/div>\n  <div class=\"candy-view-counter__today\"><span class=\"lbl\">\u0421\u0435\u0433\u043e\u0434\u043d\u044f<\/span><span class=\"val\" data-k=\"today\">&mdash;<\/span><\/div>\n<\/div>\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/hls.js@1.5.15\/dist\/hls.min.js\"><\/script>\n<script>\n(function(){\n  'use strict';\n\n  \/\/ Lock viewport height to a stable pixel value so the Chrome Android URL bar\n  \/\/ collapsing on scroll does not resize the snap container or items.\n  \/\/ Reads the largest known visualViewport\/innerHeight (URL-bar-hidden state when\n  \/\/ available) and only refreshes on orientation change \u2014 not on plain resize.\n  (function setupAppVh(){\n    var root = document.documentElement;\n    var maxH = 0;\n    function apply(){\n      var vv = window.visualViewport;\n      var h = Math.round((vv && vv.height) ? vv.height : window.innerHeight);\n      if (!h) return;\n      if (h > maxH) maxH = h;\n      root.style.setProperty('--app-vh', maxH + 'px');\n    }\n    apply();\n    \/\/ Capture the larger viewport once the URL bar collapses on first interaction.\n    window.addEventListener('scroll', apply, { passive:true, once:true });\n    window.addEventListener('touchstart', apply, { passive:true, once:true });\n    \/\/ Reset and recalc on orientation change (real layout change).\n    window.addEventListener('orientationchange', function(){\n      maxH = 0;\n      \/\/ Wait for the browser to settle the new viewport.\n      setTimeout(apply, 250);\n    });\n  })();\n\n\n  \n  \/\/ Fullscreen guard: prevent feed from switching videos while in fullscreen\n  let candyIsFullscreen = false;\n  const candyDoc = document;\n  const candyFsChange = () => {\n    candyIsFullscreen = !!(candyDoc.fullscreenElement || candyDoc.webkitFullscreenElement || candyDoc.msFullscreenElement);\n  };\n  document.addEventListener('fullscreenchange', candyFsChange);\n  document.addEventListener('webkitfullscreenchange', candyFsChange);\n  document.addEventListener('MSFullscreenChange', candyFsChange);\nconst feedEl   = document.getElementById('candy-feed');\n  const FEED_URL = feedEl.dataset.feed;\n  const LIKE_URL = feedEl.dataset.like;\n  const FAV_URL  = feedEl.dataset.fav || '';\n  const VIEW_URL = feedEl.dataset.view;\n  const PROGRESS_URL = feedEl.dataset.progress || '';\n  const COUNTS_URL = feedEl.dataset.counts;\n  let VIP_VARIANTS = {};\n  try { VIP_VARIANTS = JSON.parse(feedEl.dataset.vipVariants || '{}') || {}; } catch(_) { VIP_VARIANTS = {}; }\n  function escHtml(s){ return String(s == null ? '' : s).replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;').replace(\/\"\/g,'&quot;').replace(\/'\/g,'&#39;'); }\n\n  \/\/ Desktop backdrop video disabled. Wide screens use a static dark background.\n  const isDesktop = () => window.matchMedia && window.matchMedia('(min-width:1025px)').matches;\n  let candyGlobalBg = null;\n  let candyGlobalBgHls = null;\n  let candyGlobalBgUrl = '';\n  function ensureGlobalBg(){\n    return null;\n  }\n  function loadGlobalBg(url){\n    return;\n  }\n  function syncGlobalBg(sourceVideo){\n    return;\n  }\n  window.addEventListener('resize', () => {\n    if (!isDesktop() && candyGlobalBg) {\n      try { candyGlobalBg.pause(); } catch(_){}\n    } else if (isDesktop() && activeEl && activeEl.dataset && activeEl.dataset.hls) {\n      loadGlobalBg(activeEl.dataset.hls);\n    }\n  });\n\n  let globalMuted = true;\n  let userInteracted = false;\n  const ua = navigator.userAgent || '';\n  const isIOS = \/iP(hone|ad|od)\/.test(ua);\n\n  function toast(msg){\n    let t = document.getElementById('candy-toast');\n    if (!t){\n      t = document.createElement('div');\n      t.id = 'candy-toast';\n      t.style.position='fixed';\n      t.style.left='50%';\n      t.style.bottom='90px';\n      t.style.transform='translateX(-50%)';\n      t.style.background='rgba(0,0,0,.75)';\n      t.style.color='#fff';\n      t.style.padding='10px 14px';\n      t.style.borderRadius='14px';\n      t.style.fontSize='13px';\n      t.style.zIndex='99999';\n      t.style.maxWidth='92vw';\n      t.style.textAlign='center';\n      t.style.opacity='0';\n      t.style.transition='opacity .18s ease';\n      document.body.appendChild(t);\n    }\n    t.textContent = msg;\n    t.style.opacity='1';\n    clearTimeout(t._candyT);\n    t._candyT = setTimeout(()=>{ t.style.opacity='0'; }, 1600);\n  }\n\n\n  \/\/ --- Views counters (total + today) ---\n  const counterEl = document.getElementById('candy-view-counter');\n  function fmt(n){\n    n = parseInt(n, 10) || 0;\n    return n.toString().replace(\/\\B(?=(\\d{3})+(?!\\d))\/g, ' ');\n  }\n  function setCounter(data){\n    if (!counterEl || !data) return;\n    const t = counterEl.querySelector('[data-k=\"total\"]');\n    const d = counterEl.querySelector('[data-k=\"today\"]');\n    if (t) t.textContent = fmt(data.total);\n    if (d) d.textContent = fmt(data.today);\n  }\n  async function loadCounts(){\n    if (!COUNTS_URL) return;\n    try{\n      const r = await fetch(COUNTS_URL, { credentials:'same-origin' });\n      const j = await r.json();\n      if (j && j.ok) setCounter(j);\n    } catch(_){}\n  }\n\n  function todayKey(){\n    const d = new Date();\n    const y = d.getFullYear();\n    const m = String(d.getMonth()+1).padStart(2,'0');\n    const dd = String(d.getDate()).padStart(2,'0');\n    return `${y}${m}${dd}`;\n  }\n  function viewedStoreKey(){ return 'candy_viewed_' + todayKey(); }\n  function getViewedSet(){\n    try{\n      const raw = localStorage.getItem(viewedStoreKey());\n      if (!raw) return new Set();\n      const arr = JSON.parse(raw);\n      if (!Array.isArray(arr)) return new Set();\n      return new Set(arr.map(x => String(x)));\n    } catch(_){ return new Set(); }\n  }\n  function saveViewedSet(set){\n    try{\n      const arr = Array.from(set.values()).slice(0, 2000);\n      localStorage.setItem(viewedStoreKey(), JSON.stringify(arr));\n    } catch(_){}\n  }\n  let viewedToday = getViewedSet();\n  let viewPingInFlight = new Set();\n  let progressTimer = null;\n\n  function createSessionKey(){\n    try {\n      if (window.crypto && typeof window.crypto.randomUUID === 'function') {\n        return window.crypto.randomUUID();\n      }\n    } catch(_){}\n    return 'sess_' + Date.now() + '_' + Math.random().toString(36).slice(2, 10);\n  }\n\n  function ensureProgressState(el){\n    if (!el) return null;\n    if (!el._candyProgress) {\n      el._candyProgress = {\n        sessionKey: createSessionKey(),\n        sentWatchedSeconds: 0,\n        sentMaxPercent: 0,\n        counted30: false,\n        partialRecentPushed: false,\n        recentPushed: false,\n      };\n    }\n    return el._candyProgress;\n  }\n\n  function collectProgress(el){\n    if (!el || !el._candy || !el._candy.video) return null;\n    const pid = parseInt(el.dataset.postId, 10) || 0;\n    if (!pid) return null;\n    const state = ensureProgressState(el);\n    if (!state) return null;\n    const video = el._candy.video;\n    const duration = Number.isFinite(video.duration) && video.duration > 0 ? video.duration : 0;\n    const current = Number.isFinite(video.currentTime) && video.currentTime > 0 ? video.currentTime : 0;\n    const percent = duration > 0 ? Math.min(100, (current \/ duration) * 100) : 0;\n    state.sentWatchedSeconds = Math.max(state.sentWatchedSeconds || 0, current);\n    state.sentMaxPercent = Math.max(state.sentMaxPercent || 0, percent);\n    return {\n      postId: pid,\n      sessionKey: state.sessionKey,\n      watchedSeconds: state.sentWatchedSeconds,\n      durationSeconds: duration,\n      maxPercent: state.sentMaxPercent,\n    };\n  }\n\n  async function sendProgress(el, opts){\n    opts = opts || {};\n    if (!PROGRESS_URL) return;\n    const payload = collectProgress(el);\n    if (!payload) return;\n    maybeCommitPartialRepeat(el, payload);\n    maybeCommitThreshold(el, payload);\n    if (!opts.force && payload.watchedSeconds <= 0 && payload.maxPercent <= 0) return;\n\n    const body = JSON.stringify({\n      post_id: payload.postId,\n      session_key: payload.sessionKey,\n      watched_seconds: payload.watchedSeconds,\n      duration_seconds: payload.durationSeconds,\n      max_percent: payload.maxPercent,\n    });\n\n    try {\n      await fetch(PROGRESS_URL, {\n        method: 'POST',\n        credentials: 'same-origin',\n        headers: { 'Content-Type':'application\/json' },\n        body,\n        keepalive: !!opts.keepalive,\n      });\n    } catch(_){}\n  }\n\n  function maybeCommitThreshold(el, payload){\n    if (!el || !payload || payload.maxPercent < 30) return;\n    const state = ensureProgressState(el);\n    if (!state) return;\n\n    const postId = String(parseInt(payload.postId, 10) || '');\n    if (!postId) return;\n\n    if (!state.counted30) {\n      state.counted30 = true;\n      trackView(postId, el);\n    }\n\n    if (!state.recentPushed) {\n      state.recentPushed = true;\n      pushRecentId(postId);\n      pushRecentGuid(el.dataset.bunnyGuid || '');\n    }\n  }\n\n  function maybeCommitPartialRepeat(el, payload){\n    if (!el || !payload) return;\n    const state = ensureProgressState(el);\n    if (!state || state.partialRecentPushed) return;\n    const postId = String(parseInt(payload.postId, 10) || '');\n    if (!postId) return;\n    if (payload.watchedSeconds < 1 && payload.maxPercent <= 0) return;\n    state.partialRecentPushed = true;\n    pushRecentId(postId);\n    pushRecentGuid(el.dataset.bunnyGuid || '');\n  }\n\n  function stopProgressTimer(){\n    if (!progressTimer) return;\n    clearInterval(progressTimer);\n    progressTimer = null;\n  }\n\n  function startProgressTimer(el){\n    stopProgressTimer();\n    if (!el) return;\n    ensureProgressState(el);\n    progressTimer = setInterval(() => {\n      if (document.hidden) return;\n      if (el !== activeEl) return;\n      sendProgress(el);\n    }, 5000);\n  }\n\n  async function trackView(postId, el){\n    postId = String(parseInt(postId,10) || '');\n    if (!postId) return;\n\n    \/\/ Count only once per post per day on this device (prevents spam while still counting real views)\n    if (viewedToday.has(postId)) return;\n    if (viewPingInFlight.has(postId)) return;\n\n    viewPingInFlight.add(postId);\n    viewedToday.add(postId);\n    saveViewedSet(viewedToday);\n\n    try{\n      const r = await fetch(VIEW_URL, {\n        method: 'POST',\n        credentials: 'same-origin',\n        headers: { 'Content-Type':'application\/json' },\n        body: JSON.stringify({ post_id: parseInt(postId,10) })\n      });\n      if (r && r.ok){\n        const j = await r.json().catch(()=>null);\n        if (j && typeof j.video_total === 'number' && el){\n          const vc = el.querySelector('[data-viewcount]');\n          if (vc) vc.textContent = String(j.video_total);\n        }\n      }\n    } catch(_){}\n    viewPingInFlight.delete(postId);\n\n    \/\/ Refresh counters (cheap)\n    loadCounts();\n  }\n\n\n\/\/ We keep an \"exclude set\" only for the *current* cycle to avoid repeats.\n\/\/ When we exhaust all videos, we reset the cycle and keep going forever.\nlet cycleSeen = new Set();\n\/\/ Track guids already in DOM to prevent duplicates (more reliable than post_id alone)\nlet cycleSeenGuids = new Set();\n\n\n\/\/ Persist \"recently played\" across page reloads to reduce repeats after refresh.\n\/\/ This is separate from the server-side seen history; we keep only a short tail.\nconst RECENT_MAX = 300;\nconst RECENT_KEY = 'candy_recent_ids_v1';\nconst RECENT_GUID_KEY = 'candy_recent_guids_v1';\nfunction getRecentIds(){\n  try{\n    const raw = localStorage.getItem(RECENT_KEY);\n    if (!raw) return [];\n    const arr = JSON.parse(raw);\n    if (!Array.isArray(arr)) return [];\n    \/\/ keep ints only\n    return arr.map(x => parseInt(x,10)).filter(n => n>0).slice(-RECENT_MAX);\n  }catch(_){ return []; }\n}\nfunction getRecentGuids(){\n  try{\n    const raw = localStorage.getItem(RECENT_GUID_KEY);\n    if (!raw) return [];\n    const arr = JSON.parse(raw);\n    if (!Array.isArray(arr)) return [];\n    return arr.map(x => String(x || '').trim()).filter(Boolean).slice(-RECENT_MAX);\n  }catch(_){ return []; }\n}\nfunction pushRecentId(id){\n  id = parseInt(id,10) || 0;\n  if (!id) return;\n  try{\n    const arr = getRecentIds();\n    \/\/ avoid immediate duplicates in the recent tail\n    if (arr.length && arr[arr.length-1] === id) return;\n    arr.push(id);\n    const sliced = arr.slice(-RECENT_MAX);\n    localStorage.setItem(RECENT_KEY, JSON.stringify(sliced));\n  }catch(_){ }\n}\nfunction pushRecentGuid(guid){\n  guid = String(guid || '').trim();\n  if (!guid) return;\n  try{\n    const arr = getRecentGuids();\n    if (arr.length && arr[arr.length - 1] === guid) return;\n    arr.push(guid);\n    localStorage.setItem(RECENT_GUID_KEY, JSON.stringify(arr.slice(-RECENT_MAX)));\n  }catch(_){ }\n}\nfunction pushRecentFromEl(el){\n  if (!el || !el.dataset) return;\n  pushRecentId(el.dataset.postId || '');\n  pushRecentGuid(el.dataset.bunnyGuid || '');\n}\nlet recentIds = getRecentIds();\nlet recentGuids = getRecentGuids();\n\n\/\/ Prevent inserting the same \"new\" video multiple times via polling\nlet newInserted = new Set();\n\/\/ TTL-\u0447\u0438\u0441\u0442\u043a\u0430 newInserted: \u043a\u0430\u0436\u0434\u044b\u0435 30 \u043c\u0438\u043d\u0443\u0442 \u0434\u0430\u0451\u043c pollNew \u0441\u043d\u043e\u0432\u0430 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u00ab\u0441\u0442\u0430\u0440\u044b\u0435 \u043d\u043e\u0432\u044b\u0435\u00bb\n\/\/ (\u043d\u0430 \u0441\u043b\u0443\u0447\u0430\u0439 \u0435\u0441\u043b\u0438 \u0438\u0445 \u0432\u0441\u0451-\u0442\u0430\u043a\u0438 \u0432\u044b\u043f\u0438\u043b\u0438\u043b\u0438 \u0438\u0437 DOM \u043a\u043e\u043c\u043f\u0430\u043a\u0446\u0438\u0435\u0439 \u0438\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043b \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443).\nsetInterval(() => { try { newInserted.clear(); } catch(_){} }, 30 * 60 * 1000);\n\nconst LIMIT = 30;\nlet loading = false;\nlet activeEl = null;\nlet playToken = 0;\nlet lastPlayedId = 0;\n\n\n\/\/ Android: require first user gesture before autoplay starts\nconst isAndroid = \/Android\/i.test(navigator.userAgent);\nlet androidAutoplayUnlocked = false;\n\n\/\/ Top spacer to preserve scroll height when we remove items above (prevents iOS snap jumps)\nlet topSpacer = null;\nlet topSpacerH = 0;\n\nfunction ensureTopSpacer(){ }\n\n\n\n\/\/ --- DOM virtualization (TikTok-like): keep small window around active ---\nconst KEEP_BEHIND = 3;\nconst KEEP_AHEAD  = 3;\n\nfunction removeItemEl(el){\n  if (!el) return 0;\n  const h = el.getBoundingClientRect ? el.getBoundingClientRect().height : (el.offsetHeight || 0);\n\n  try { io.unobserve(el); } catch(_){}\n  const api = el._candy;\n  if (api) { try { api.detach(); } catch(_){} }\n\n  try { el.remove(); } catch(_){}\n  return h;\n}\n\nfunction compactAroundActive(){ \/* iOS: do not remove snap items *\/ }\n\n\/\/ debounce: disabled (keep snap items stable on iOS)\nfunction scheduleCompact(){ }\n\n\n\n\n  const canNativeHls = (v) =>\n    !!v.canPlayType &&\n    (v.canPlayType('application\/vnd.apple.mpegurl') ||\n     v.canPlayType('application\/x-mpegURL'));\n\n  function fmtTime(s){\n    s = Math.max(0, Math.floor(s||0));\n    const m = Math.floor(s\/60);\n    const r = s % 60;\n    return m + ':' + String(r).padStart(2,'0');\n  }\n\n  const svgEye = () => `\n    <svg viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" aria-hidden=\"true\">\n      <path d=\"M4 32C10.5 18.5 21 10 32 10s21.5 8.5 28 22c-6.5 13.5-17 22-28 22S10.5 45.5 4 32Z\"\n            stroke=\"#fff\" stroke-width=\"6\" stroke-linejoin=\"round\"\/>\n      <circle cx=\"32\" cy=\"32\" r=\"10\" stroke=\"#fff\" stroke-width=\"6\"\/>\n    <\/svg>`;\n\n  \/\/ Heart icons: outline (empty) + filled pink\/red\n  const svgHeartEmpty = () => `\n    <svg viewBox=\"0 0 200 180\" aria-hidden=\"true\">\n      <path d=\"M100 164\n               C100 164 18 116 18 66\n               C18 36 40 18 66 18\n               C82 18 94 26 100 36\n               C106 26 118 18 134 18\n               C160 18 182 36 182 66\n               C182 116 100 164 100 164Z\"\n            fill=\"none\" stroke=\"#fff\" stroke-width=\"18\" stroke-linejoin=\"round\"\/>\n    <\/svg>`;\n\n  const svgHeartFill = () => `\n    <svg viewBox=\"0 0 200 180\" aria-hidden=\"true\">\n      <path d=\"M100 164\n               C100 164 18 116 18 66\n               C18 36 40 18 66 18\n               C82 18 94 26 100 36\n               C106 26 118 18 134 18\n               C160 18 182 36 182 66\n               C182 116 100 164 100 164Z\"\n            fill=\"#ff3b6a\" stroke=\"none\"\/>\n    <\/svg>`;\n\n  const svgFs = () => `<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n    <path d=\"M8 3H5a2 2 0 0 0-2 2v3\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\"\/>\n    <path d=\"M16 3h3a2 2 0 0 1 2 2v3\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\"\/>\n    <path d=\"M8 21H5a2 2 0 0 1-2-2v-3\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\"\/>\n    <path d=\"M16 21h3a2 2 0 0 0 2-2v-3\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\"\/>\n  <\/svg>`;\n\n  const svgPauseTri = () => `\n    <svg viewBox=\"0 0 120 120\" aria-hidden=\"true\">\n      <path d=\"M44 30 L94 60 L44 90 Z\"\n            fill=\"none\" stroke=\"#fff\" stroke-width=\"8\"\n            stroke-linejoin=\"round\" stroke-linecap=\"round\"\/>\n    <\/svg>`;\n\n  function svgMute(muted){\n    return muted\n      ? `<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M11 5 6 9H3v6h3l5 4z\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\"\/><path d=\"M16 9l5 6\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\"\/><path d=\"M21 9l-5 6\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\"\/><\/svg>`\n      : `<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M11 5 6 9H3v6h3l5 4z\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\"\/><path d=\"M16 9a5 5 0 0 1 0 6\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\"\/><path d=\"M18.5 6.5a9 9 0 0 1 0 11\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\"\/><\/svg>`;\n  }\n\n  const svgBookmark = (on) => on\n    ? `<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z\" fill=\"#fff\"\/><\/svg>`\n    : `<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\"\/><\/svg>`;\n\nfunction createHls(video, url){\n    if (canNativeHls(video)){\n      video.src = url;\n      return null;\n    }\n    if (window.Hls && Hls.isSupported()){\n      const hls = new Hls({\n  enableWorker: true,\n\n  \/\/ TikTok-like behavior\n  startLevel: -1,                 \/\/ \u0421\u0403\u0421\u201a\u0420\u00b0\u0421\u0402\u0421\u201a \u0421\u0403 \u0421\u0403\u0420\u00b0\u0420\u0458\u0420\u0455\u0420\u0456\u0420\u0455 \u0420\u0405\u0420\u0451\u0420\u00b6\u0420\u0405\u0420\u00b5\u0420\u0456\u0420\u0455 (240\/360) \u0432\u2020\u2019 \u0420\u00b1\u0421\u2039\u0421\u0403\u0421\u201a\u0421\u0402\u0421\u2039\u0420\u2116 \u0421\u0403\u0421\u201a\u0420\u00b0\u0421\u0402\u0421\u201a\n  capLevelToPlayerSize: true,    \/\/ \u0420\u0405\u0420\u00b5 \u0420\u0454\u0420\u00b0\u0421\u2021\u0420\u00b0\u0420\u00b5\u0420\u0458 \u0420\u0406\u0421\u2039\u0421\u20ac\u0420\u00b5 \u0421\u0402\u0420\u00b0\u0420\u00b7\u0420\u0458\u0420\u00b5\u0421\u0402\u0420\u00b0 \u0420\u0457\u0420\u00bb\u0420\u00b5\u0420\u00b5\u0421\u0402\u0420\u00b0\n  maxBufferLength: 6,            \/\/ \u0420\u00b1\u0421\u0453\u0421\u201e\u0420\u00b5\u0421\u0402 \u0420\u0406\u0420\u0457\u0420\u00b5\u0421\u0402\u0420\u00b5\u0420\u0491 ~4 \u0421\u0403\u0420\u00b5\u0420\u0454\n  maxMaxBufferLength: 8,\n  backBufferLength: 1,           \/\/ \u0420\u0457\u0420\u0455\u0421\u2021\u0421\u201a\u0420\u0451 \u0420\u0405\u0420\u00b5 \u0420\u0491\u0420\u00b5\u0421\u0402\u0420\u00b6\u0420\u0451\u0420\u0458 \u0420\u0405\u0420\u00b0\u0420\u00b7\u0420\u00b0\u0420\u0491\n\n  \/\/ \u0420\u00b0\u0420\u0456\u0421\u0402\u0420\u00b5\u0421\u0403\u0421\u0403\u0420\u0451\u0420\u0406\u0420\u0405\u0420\u00b5\u0420\u00b5 \u0420\u00b0\u0420\u0457\u0421\u0403\u0420\u0454\u0420\u00b5\u0420\u2116\u0420\u00bb\u0420\u0451\u0420\u0458 \u0420\u0454\u0420\u00b0\u0421\u2021\u0420\u00b5\u0421\u0403\u0421\u201a\u0420\u0406\u0420\u0455 \u0420\u0457\u0420\u0455 \u0421\u0403\u0420\u00b5\u0421\u201a\u0420\u0451\n  abrEwmaDefaultEstimate: 900000, \/\/ \u0421\u0403\u0421\u2021\u0420\u0451\u0421\u201a\u0420\u00b0\u0420\u00b5\u0420\u0458 \u0421\u0403\u0420\u00b5\u0421\u201a\u0421\u040a \"\u0421\u0403\u0421\u0402\u0420\u00b5\u0420\u0491\u0420\u0405\u0420\u00b5\u0420\u2116\" (0.9 Mbps)\n  abrBandWidthFactor: 0.85,\n  abrBandWidthUpFactor: 0.75,\n\n  \/\/ \u0420\u0458\u0420\u00b5\u0420\u0405\u0421\u040a\u0421\u20ac\u0420\u00b5 \u0432\u0402\u045a\u0421\u0403\u0420\u0454\u0420\u00b0\u0421\u2021\u0420\u00b0\u0420\u00bb \u0420\u0451 \u0420\u0491\u0420\u00b5\u0421\u0402\u0420\u00b6\u0420\u0451\u0421\u201a\u0432\u0402\u045c\n  lowLatencyMode: true,\n  progressive: false,\n});\n      hls.loadSource(url);\n      hls.attachMedia(video);\n      return hls;\n    }\n    video.src = url;\n    return null;\n  }\n\n  async function safePlay(video, token){\n    if (token !== playToken) return false;\n    try{\n      if (video.readyState < 3) {\n        await new Promise((res) => {\n          const t = setTimeout(res, 3500);\n          video.addEventListener('canplay', () => { clearTimeout(t); res(); }, {once:true});\n        });\n      }\n      await video.play();\n      return true;\n    }catch(_){\n      return false;\n    }\n  }\n\n\n  \/\/ Android-only: show a \"tap to start\" gate until the user explicitly starts playback once.\n  function showStartGate(el){\n    if (!el) return;\n    const g = el.querySelector('.candy-start-gate');\n    if (g) g.classList.add('show');\n  }\n  function hideStartGate(el){\n    if (!el) return;\n    const g = el.querySelector('.candy-start-gate');\n    if (g) g.classList.remove('show');\n  }\n\n  async function startFromGate(el){\n    if (!isAndroid) return;\n    if (!el || el !== activeEl) return;\n\n    const api = el._candy;\n    if (!api) return;\n\n    \/\/ Mark that we had an explicit gesture\n    userInteracted = true;\n    androidAutoplayUnlocked = true;\n\n    const v = api.video;\n\n    \/\/ Ensure active video uses current mute state\n    v.muted = globalMuted;\n    if (globalMuted) v.setAttribute('muted',''); else v.removeAttribute('muted');\n\n    \/\/ Kick loading a bit (helps Android show first frame faster)\n    try { v.preload = 'auto'; } catch(_){}\n    try { v.load(); } catch(_){}\n\n    \/\/ Try play; hide gate only after a successful attempt\n    const token = ++playToken;\n    const ok = await safePlay(v, token);\n    if (!ok){\n      try {\n        v.muted = true; v.setAttribute('muted','');\n        await v.play();\n      } catch(_){}\n    }\n\n    \/\/ If still not playing, keep gate visible\n    try{\n      if (v.paused) { showStartGate(el); return; }\n    }catch(_){}\n\n    hideStartGate(el);\n  }\n\n  function buildItem(item){\n    const wrap = document.createElement('div');\n    wrap.className = 'candy-item';\n    wrap.dataset.postId = item.post_id;\n    wrap.dataset.bunnyGuid = item.bunny_guid || '';\n    wrap.dataset.hls = item.hls;\n    ensureProgressState(wrap);\n\n    const frame = document.createElement('div');\n    frame.className = 'candy-frame';\n\n    const tapzone = document.createElement('div');\n    tapzone.className = 'candy-tapzone';\n\n    const v = document.createElement('video');\n    v.className = 'candy-video';\n\n    const pauseInd = document.createElement('div');\n    pauseInd.className = 'candy-pause-indicator';\n    pauseInd.innerHTML = svgPauseTri();\n\n\n    const startGate = document.createElement('div');\n    startGate.className = 'candy-start-gate';\n    startGate.innerHTML = `<div class=\"candy-start-gate-icon\">${svgPauseTri()}<\/div>`;\n\n    \/\/ Android: tap to start (do NOT unlock on swipe)\n    (function(){\n      if (!isAndroid) return;\n\n      let sx=0, sy=0, moved=false;\n      let lastTouchTs = 0;\n\n      startGate.addEventListener('touchstart', (e) => {\n        if (!e.touches || !e.touches[0]) return;\n        moved = false;\n        sx = e.touches[0].clientX;\n        sy = e.touches[0].clientY;\n      }, {passive:true});\n\n      startGate.addEventListener('touchmove', (e) => {\n        if (!e.touches || !e.touches[0]) return;\n        const dx = Math.abs(e.touches[0].clientX - sx);\n        const dy = Math.abs(e.touches[0].clientY - sy);\n        if (dx > 10 || dy > 10) moved = true;\n      }, {passive:true});\n\n      startGate.addEventListener('touchend', async (e) => {\n        if (moved) return; \/\/ swipe\n        lastTouchTs = Date.now();\n        \/\/ Prevent synthetic click after touch\n        try { e.preventDefault(); } catch(_){}\n        try { e.stopPropagation(); } catch(_){}\n        await startFromGate(wrap);\n      }, {passive:false});\n\n      startGate.addEventListener('click', async (e) => {\n        \/\/ Ignore click right after touchend\n        if (Date.now() - lastTouchTs < 350) return;\n        try { e.preventDefault(); } catch(_){}\n        try { e.stopPropagation(); } catch(_){}\n        await startFromGate(wrap);\n      });\n    })();\n\n\n    v.playsInline = true;\n    v.setAttribute('playsinline','');\n    v.setAttribute('webkit-playsinline','');\n    v.preload = 'none'; \/\/ start light; we switch to 'auto' only for active\/next\/prev\n    v.loop = false; \/\/ manual loop is more stable on iOS HLS\n    v.crossOrigin = 'anonymous';\n\n    \/\/ iOS Safari: sound can cut out if we try to auto-play with audio without a fresh gesture.\n    \/\/ Rule: if user explicitly enabled sound AND we have any interaction -> allow unmuted on active only.\n    \/\/ Otherwise keep muted and prompt.\n    let wantAudio = !globalMuted;\n    if (isIOS && wantAudio && !userInteracted) {\n      wantAudio = false;\n      toast('\u0420\u00a0\u0421\u045a\u0420\u00a0\u0412\u00b0\u0420\u00a0\u0412\u00b6\u0420\u00a0\u0421\u0098\u0420\u00a0\u0421\u2018 \u0420\u00a0\u0420\u2026\u0420\u00a0\u0412\u00b0 \u0420\u040e\u0420\u040a\u0420\u00a0\u0421\u201d\u0420\u040e\u0420\u201a\u0420\u00a0\u0412\u00b0\u0420\u00a0\u0420\u2026\/\u0420\u00a0\u0421\u201d\u0420\u00a0\u0420\u2026\u0420\u00a0\u0421\u2022\u0420\u00a0\u0421\u2014\u0420\u00a0\u0421\u201d\u0420\u040e\u0421\u201c \u0420\u00a0\u0412\u00b7\u0420\u00a0\u0420\u2020\u0420\u040e\u0421\u201c\u0420\u00a0\u0421\u201d\u0420\u00a0\u0412\u00b0, \u0420\u040e\u0432\u0402\u040e\u0420\u040e\u0432\u0402\u0459\u0420\u00a0\u0421\u2022\u0420\u00a0\u0412\u00b1\u0420\u040e\u0432\u0402\u2116 \u0420\u00a0\u0420\u2020\u0420\u00a0\u0421\u201d\u0420\u00a0\u0412\u00bb\u0420\u040e\u0420\u2039\u0420\u040e\u0432\u0402\u040e\u0420\u00a0\u0421\u2018\u0420\u040e\u0432\u0402\u0459\u0420\u040e\u0420\u0409 \u0420\u00a0\u0412\u00b7\u0420\u00a0\u0420\u2020\u0420\u040e\u0421\u201c\u0420\u00a0\u0421\u201d');\n    }\n    v.muted = !wantAudio;\n    if (!wantAudio) v.setAttribute('muted',''); else v.removeAttribute('muted');\n\n    const spinner = document.createElement('div');\n    spinner.className = 'candy-spinner';\n\n    const ui = document.createElement('div');\n    ui.className = 'candy-ui';\n\n    const top = document.createElement('div');\n    top.className = 'candy-topbar';\n    const title = document.createElement('div');\n    title.className = 'candy-title';\n    title.textContent = item.title || '';\n    top.appendChild(title);\n\n    const stack = document.createElement('div');\n    stack.className = 'candy-stack';\n\n    const viewWrap = document.createElement('div');\n    viewWrap.style.display = 'flex';\n    viewWrap.style.flexDirection = 'column';\n    viewWrap.style.alignItems = 'center';\n\n    const viewIcon = document.createElement('div');\n    viewIcon.className = 'candy-btn view';\n    viewIcon.innerHTML = svgEye();\n\n    const viewCount = document.createElement('div');\n    viewCount.className = 'candy-count';\n    viewCount.textContent = (item.views_total != null) ? String(item.views_total) : '0';\n    viewCount.dataset.viewcount = '1';\n\n    viewWrap.appendChild(viewIcon);\n    viewWrap.appendChild(viewCount);\n\n    const likeWrap = document.createElement('div');\n    likeWrap.style.display = 'flex';\n    likeWrap.style.flexDirection = 'column';\n    likeWrap.style.alignItems = 'center';\n\n    const btnLike = document.createElement('button');\n    btnLike.className = 'candy-btn like';\n    btnLike.innerHTML = item.liked ? svgHeartFill() : svgHeartEmpty();\n    btnLike.dataset.liked = item.liked ? '1' : '0';\n\n    const likeCount = document.createElement('div');\n    likeCount.className = 'candy-count';\n    likeCount.textContent = (item.like_count != null) ? String(item.like_count) : '';\n\n    likeWrap.appendChild(btnLike);\n    likeWrap.appendChild(likeCount);\n\n    const btnMute = document.createElement('button');\n    btnMute.className = 'candy-btn mute';\n    btnMute.innerHTML = svgMute(globalMuted);\n\n    const btnFs = document.createElement('button');\n    btnFs.className = 'candy-btn fs';\n    btnFs.innerHTML = svgFs();\n\n    const isFav = !!(item.is_favorited);\n\n    const favWrap = document.createElement('div');\n    favWrap.style.display = 'flex';\n    favWrap.style.flexDirection = 'column';\n    favWrap.style.alignItems = 'center';\n\n    const btnFav = document.createElement('button');\n    btnFav.className = 'candy-btn fav' + (isFav ? ' on' : '');\n    btnFav.innerHTML = svgBookmark(isFav);\n    btnFav.dataset.faved = isFav ? '1' : '0';\n    btnFav.setAttribute('aria-label', '\u0418\u0437\u0431\u0440\u0430\u043d\u043d\u043e\u0435');\n\n    const favCount = document.createElement('div');\n    favCount.className = 'candy-count';\n    favCount.textContent = (item.fav_count != null) ? String(item.fav_count) : '';\n\n    favWrap.appendChild(btnFav);\n    favWrap.appendChild(favCount);\n\n    stack.appendChild(viewWrap);\n    stack.appendChild(likeWrap);\n    stack.appendChild(btnMute);\n    stack.appendChild(btnFs);\n    stack.appendChild(favWrap);\n\n    const seek = document.createElement('div');\n    seek.className = 'candy-seek';\n\n    const timeL = document.createElement('div');\n    timeL.className = 'candy-time';\n    timeL.textContent = '0:00';\n\n    const timeR = document.createElement('div');\n    timeR.className = 'candy-time';\n    timeR.textContent = '0:00';\n\n    const rangeWrap = document.createElement('div');\n    rangeWrap.className = 'candy-range-wrap';\n\n    const range = document.createElement('input');\n    range.className = 'candy-range';\n    range.type = 'range';\n    range.min = '0';\n    range.max = '1000';\n    range.value = '0';\n    range.step = '1';\n\n    seek.appendChild(timeL);\n    rangeWrap.appendChild(range);\n    seek.appendChild(rangeWrap);\n    seek.appendChild(timeR);\n\n    ui.appendChild(top);\n    ui.appendChild(stack);\n\n    frame.appendChild(v);\n    frame.appendChild(pauseInd);\n    frame.appendChild(startGate);\n    frame.appendChild(tapzone);\n    frame.appendChild(spinner);\n    frame.appendChild(ui);\n    wrap.appendChild(frame);\n    wrap.appendChild(seek);\n\n    if (item.vip_promo) {\n      const vKey = String(item.vip_variant || 1);\n      const v = VIP_VARIANTS[vKey] || VIP_VARIANTS['1'] || {};\n      const vText  = v.text      || '\u041f\u043e\u043b\u043d\u043e\u0435 \u0432\u0438\u0434\u0435\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 VIP-\u043a\u0430\u043d\u0430\u043b\u0435';\n      const vLabel = v.btn_label || '\u0421\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 VIP';\n      const vUrl   = v.btn_url   || '\/vip\/';\n      const promo = document.createElement('div');\n      promo.className = 'candy-vip-promo';\n      promo.innerHTML =\n        '<p class=\"candy-vip-promo__text\">' + escHtml(vText) + '<\/p>' +\n        '<a class=\"candy-vip-promo__btn\" href=\"' + escHtml(vUrl) + '\">' +\n          '<svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\"><path d=\"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z\" fill=\"currentColor\"\/><\/svg>' +\n          escHtml(vLabel) +\n        '<\/a>';\n      wrap.appendChild(promo);\n    }\n\n    let hls = null;\n    let attached = false;\n    let seeking = false;\n\n    \/\/ iOS\/Safari can emit transient video\/HLS errors during switching.\n    \/\/ Do NOT auto-advance on errors (v4 behavior).\n    v.addEventListener('error', () => { \/* keep current; user may tap or scroll *\/ });\n    v.addEventListener('stalled', () => { \/* ignore minor stalls *\/ });\n    function attach(){\n      \/\/ Ensure <video> is present as the first child in frame (CSS expects it)\n      if (!v.parentNode) {\n        try { frame.insertBefore(v, frame.firstChild); } catch(_){ frame.appendChild(v); }\n      }\n      if (attached) return;\n      attached = true;\n      spinner.classList.remove('hide');\n      try { v.preload = 'auto'; } catch(_){}\n      hls = createHls(v, item.hls);\n      if (canNativeHls(v)) { try { v.load(); } catch(_){ } }\n    }\n\n    function detach(){\n      \/\/ Always stop & destroy streams, and remove <video> from DOM to keep video DOM bounded\n      try { v.pause(); } catch(_){}\n      try { v.preload = 'none'; } catch(_){}\n      try { v.removeAttribute('src'); v.load(); } catch(_){}\n      if (hls) { try { hls.destroy(); } catch(_){ } hls = null; }\n      attached = false;\n      spinner.classList.add('hide');\n      if (v.parentNode) {\n        try { v.parentNode.removeChild(v); } catch(_){}\n      }\n    }\n\n    function pause(){ try { v.pause(); } catch(_){ } }\n\n    tapzone.addEventListener('click', async (e) => {\n      userInteracted = true;\n      if (e.target && (e.target.closest('.candy-stack') || e.target.closest('.candy-seek'))) return;\n      e.preventDefault(); e.stopPropagation();\n      if (v.paused){\n        try { await v.play(); } catch(_){}\n      } else {\n        try { v.pause(); } catch(_){}\n      }\n    });\n\n    btnLike.addEventListener('click', async (e) => {\n      e.preventDefault(); e.stopPropagation();\n      const liked = btnLike.dataset.liked === '1';\n      const next = !liked;\n\n      btnLike.dataset.liked = next ? '1' : '0';\n      btnLike.innerHTML = next ? svgHeartFill() : svgHeartEmpty();\n\n      try{\n        const _cu = window.candyUser || {};\n        const r = await fetch(LIKE_URL, {\n          method:'POST',\n          headers:{'Content-Type':'application\/json', 'X-WP-Nonce': _cu.restNonce || ''},\n          credentials: 'same-origin',\n          body: JSON.stringify({ post_id: item.post_id, value: next ? 1 : 0 })\n        });\n        const j = await r.json();\n        if (j && j.ok && typeof j.like_count !== 'undefined'){\n          likeCount.textContent = String(j.like_count);\n        }\n      }catch(_){}\n    });\n\n    btnMute.addEventListener('click', async (e) => {\n      userInteracted = true;\n      e.preventDefault(); e.stopPropagation();\n      globalMuted = !globalMuted;\n\n      document.querySelectorAll('.candy-video').forEach(vv => {\n        vv.muted = globalMuted;\n        if (globalMuted) vv.setAttribute('muted',''); else vv.removeAttribute('muted');\n      });\n      document.querySelectorAll('.candy-btn.mute').forEach(b => b.innerHTML = svgMute(globalMuted));\n\n      if (!globalMuted && wrap === activeEl){\n        try {\n          v.muted = false; v.removeAttribute('muted');\n          await v.play();\n        } catch(_){}\n      }\n    });\n\n    btnFs.addEventListener('click', async (e) => {\n      e.preventDefault(); e.stopPropagation();\n\n      const ua = navigator.userAgent || '';\n      const isIOS = \/iP(hone|ad|od)\/.test(ua);\n\n      \/\/ Pause every other video so nothing plays in background\n      try {\n        document.querySelectorAll('.candy-video').forEach(vv => { if (vv !== v) vv.pause(); });\n      } catch(_){}\n\n      if (isIOS && typeof v.webkitEnterFullscreen === 'function') {\n        \/\/ iOS native fullscreen\n        try { v.webkitEnterFullscreen(); } catch(_){}\n        return;\n      }\n\n      \/\/ Android\/desktop: native-ish fullscreen by fullscreening the VIDEO element.\n      \/\/ (UI overlays won't show \u0420\u0406\u0420\u201a\u0432\u0402\u045c that's the tradeoff for native player)\n      const doc = document;\n      const fsEl = doc.fullscreenElement || doc.webkitFullscreenElement || doc.msFullscreenElement;\n\n      if (fsEl) {\n        const exit = doc.exitFullscreen || doc.webkitExitFullscreen || doc.msExitFullscreen;\n        if (exit) { try { exit.call(doc); } catch(_){ } }\n        return;\n      }\n\n      const req = v.requestFullscreen || v.webkitRequestFullscreen || v.msRequestFullscreen;\n      if (req) {\n        try { await v.play().catch(()=>{}); } catch(_){}\n        try { req.call(v); } catch(_){}\n        \/\/ Ensure it keeps playing after entering fullscreen\n        setTimeout(() => { try { v.play().catch(()=>{}); } catch(_){ } }, 120);\n      }\n    });\n\n    btnFav.addEventListener('click', async (e) => {\n      e.preventDefault(); e.stopPropagation();\n      const cu = window.candyUser || {};\n      if (!cu.loggedIn) {\n        if (typeof window.candyShowAuthModal === 'function') window.candyShowAuthModal();\n        return;\n      }\n      if (!FAV_URL) return;\n      const faved = btnFav.dataset.faved === '1';\n      const next  = !faved;\n      btnFav.dataset.faved = next ? '1' : '0';\n      btnFav.innerHTML = svgBookmark(next);\n      btnFav.classList.toggle('on', next);\n      \/\/ optimistic count bump\n      const prevCount = parseInt(favCount.textContent, 10);\n      const baseCount = isFinite(prevCount) ? prevCount : 0;\n      favCount.textContent = String(Math.max(0, baseCount + (next ? 1 : -1)));\n      try {\n        const r = await fetch(FAV_URL, {\n          method: 'POST',\n          headers: {'Content-Type':'application\/json', 'X-WP-Nonce': cu.restNonce || cu.nonce || ''},\n          body: JSON.stringify({ post_id: item.post_id, value: next ? 1 : 0 }),\n          credentials: 'same-origin',\n        });\n        const j = await r.json();\n        if (j && j.ok && typeof j.fav_count !== 'undefined') {\n          favCount.textContent = String(j.fav_count);\n        }\n      } catch(_){}\n    });\n\n    function syncRange(){\n      if (seeking) return;\n      if (!v.duration || !isFinite(v.duration)) return;\n      const p = v.currentTime \/ v.duration;\n      range.value = String(Math.round(p * 1000));\n      timeL.textContent = fmtTime(v.currentTime);\n      timeR.textContent = fmtTime(v.duration);\n    }\n\n    v.addEventListener('loadedmetadata', () => {\n      timeR.textContent = fmtTime(v.duration);\n      syncRange();\n    });\n    v.addEventListener('timeupdate', syncRange);\n    \n    v.addEventListener('pause', () => pauseInd.classList.add('show'));\n    v.addEventListener('play',  () => pauseInd.classList.remove('show'));\nv.addEventListener('playing', () => {\n      spinner.classList.add('hide');\n      try { syncGlobalBg(v); } catch(_){}\n    });\n    v.addEventListener('pause',  () => { try { const g = document.getElementById('candy-global-bg'); if (g) g.pause(); } catch(_){} });\n    v.addEventListener('play',   () => { try { syncGlobalBg(v); } catch(_){} });\n    v.addEventListener('seeked', () => { try { const g = document.getElementById('candy-global-bg'); if (g && isFinite(v.currentTime)) g.currentTime = v.currentTime; } catch(_){} });\n    v.addEventListener('waiting', () => spinner.classList.remove('hide'));\n\n    \/\/ Manual loop (iOS HLS can glitch with loop=true)\n    v.addEventListener('ended', () => {\n      try { v.currentTime = 0; } catch(_){ }\n      safePlay(v, playToken).catch(()=>{});\n    });\n\n\n    \/\/ Scrub (TikTok-like: drag anywhere on the bar)\n    let dragging = false;\n\n    function setFromClientX(clientX, applyToVideo){\n      const rect = rangeWrap.getBoundingClientRect();\n      if (!rect.width) return;\n      let p = (clientX - rect.left) \/ rect.width;\n      if (p < 0) p = 0;\n      if (p > 1) p = 1;\n      range.value = String(Math.round(p * 1000));\n      const t = (!v.duration || !isFinite(v.duration)) ? 0 : (p * v.duration);\n      timeL.textContent = fmtTime(t);\n      setSeekVisual(p);\n      if (applyToVideo && v.duration && isFinite(v.duration)) {\n        v.currentTime = Math.max(0, Math.min(v.duration, t));\n      }\n    }\n\n    rangeWrap.addEventListener('pointerdown', (e) => {\n      dragging = true;\n      seeking = true;\n      try { rangeWrap.setPointerCapture(e.pointerId); } catch(_){ }\n      e.preventDefault();\n      e.stopPropagation();\n      setFromClientX(e.clientX, true);\n    });\n    rangeWrap.addEventListener('pointermove', (e) => {\n      if (!dragging) return;\n      e.preventDefault();\n      e.stopPropagation();\n      setFromClientX(e.clientX, true);\n    });\n    rangeWrap.addEventListener('pointerup', async (e) => {\n      if (!dragging) return;\n      dragging = false;\n      seeking = false;\n      e.preventDefault();\n      e.stopPropagation();\n      if (!v.duration || !isFinite(v.duration)) return;\n      const p = (parseInt(range.value,10) || 0) \/ 1000;\n      v.currentTime = Math.max(0, Math.min(v.duration, p * v.duration));\n\n      \/\/ iPhone quirk: after fast scrubbing sound may feel \"off\" -> re-assert unmuted state\n      if (!globalMuted) {\n        try {\n          v.muted = false; v.removeAttribute('muted');\n          await v.play();\n          setTimeout(() => { try { v.muted = false; v.removeAttribute('muted'); } catch(_){} }, 120);\n        } catch(_){ }\n      }\n    });\n    rangeWrap.addEventListener('pointercancel', (e) => {\n      dragging = false;\n      seeking = false;\n    });\n    range.addEventListener('input', (e) => {\n      e.stopPropagation();\n      if (!v.duration || !isFinite(v.duration)) return;\n      const p = (parseInt(range.value,10) || 0) \/ 1000;\n      const t = Math.max(0, Math.min(v.duration, p * v.duration));\n      timeL.textContent = fmtTime(t);\n      setSeekVisual(p);\n    });\n\n    return { el: wrap, video: v, attach, detach, pause };\n  }\n\n  async function fetchFeed(opts){\n    opts = opts || {};\n    const onlyNew = !!opts.onlyNew;\n    const limit   = opts.limit ? opts.limit : LIMIT;\n    const shuffle = (opts.shuffle != null) ? String(opts.shuffle) : '';\n\n    \/\/ Exclude within the current cycle AND the recent tail (to avoid repeats after refresh)\n    \/\/ Keep it small to avoid huge URLs.\n    recentIds = getRecentIds();\n    recentGuids = getRecentGuids();\n    const mergedExclude = Array.from(new Set([ ...Array.from(cycleSeen), ...recentIds ])).slice(-120);\n    const exclude = mergedExclude.join(',');\n    \/\/ Merge cycleSeenGuids (DOM) with recentGuids (localStorage) for maximum dedup\n    const mergedGuids = Array.from(new Set([ ...Array.from(cycleSeenGuids), ...recentGuids ])).slice(-200);\n    const excludeGuids = mergedGuids.join(',');\n\n    const avoid = (typeof opts.avoid === 'number') ? opts.avoid : lastPlayedId;\n    const avoidGuid = (opts.avoidGuid != null)\n      ? String(opts.avoidGuid || '')\n      : ((activeEl && activeEl.dataset) ? String(activeEl.dataset.bunnyGuid || '') : '');\n\n    \/\/ Send heavy params (exclude \/ exclude_guids) via POST body to avoid 414 URI Too Long.\n    const params = new URLSearchParams();\n    params.set('loop', '1');\n    params.set('limit', String(limit));\n    if (onlyNew) params.set('only_new', '1');\n    if (exclude) params.set('exclude', exclude);\n    if (excludeGuids) params.set('exclude_guids', excludeGuids);\n    if (avoid) params.set('avoid', String(avoid));\n    if (avoidGuid) params.set('avoid_guid', avoidGuid);\n    if (shuffle) params.set('shuffle', shuffle);\n\n    const _cuFeed = window.candyUser || {};\n    const r = await fetch(FEED_URL, {\n      method: 'POST',\n      cache: 'no-store',\n      credentials: 'same-origin',\n      headers: {\n        'X-WP-Nonce': _cuFeed.restNonce || _cuFeed.nonce || '',\n        'Content-Type': 'application\/x-www-form-urlencoded; charset=UTF-8',\n      },\n      body: params.toString(),\n    });\n    const j = await r.json();\n    if (!j || !j.ok) return {items:[], end:false};\n    \/\/ Double-check: filter out any guid already in DOM (cycleSeenGuids) or recently played\n    const blockedGuids = new Set([ ...Array.from(cycleSeenGuids), ...recentGuids, ...(avoidGuid ? [avoidGuid] : []) ].filter(Boolean));\n    const filteredItems = (j.items || []).filter(item => {\n      const guid = String((item && item.bunny_guid) || '').trim();\n      if (!guid) return true;\n      return !blockedGuids.has(guid);\n    });\n    return {items: filteredItems, end: !!j.end};\n  }\nasync function loadMore(){\n    if (loading) return;\n    loading = true;\n    try{\n      let res = null;\n      for (let attempt = 0; attempt < 2; attempt++) {\n        const shuffle = attempt ? ('r' + Date.now() + '_' + attempt) : '';\n        res = await fetchFeed({avoid: lastPlayedId, shuffle});\n        if (res.items && res.items.length) break;\n      }\n\n      \/\/ If we exhausted the cycle (no items and end=true) -> full reset, fetch again\n      if ((!res.items || !res.items.length) && res.end) {\n        cycleSeen = new Set();\n        cycleSeenGuids = new Set();\n        newInserted.clear();\n        recentIds = [];\n        recentGuids = [];\n        try { localStorage.removeItem(RECENT_KEY); } catch(_){}\n        try { localStorage.removeItem(RECENT_GUID_KEY); } catch(_){}\n        res = await fetchFeed({avoid: lastPlayedId, shuffle: 'reset_' + Date.now()});\n      }\n\n      if (!res.items || !res.items.length) return;\n\n      res.items.forEach(item => {\n        if (!item || !item.post_id) return;\n        const guid = String(item.bunny_guid || '').trim();\n        \/\/ Skip if this guid is already in the DOM (prevents duplicates)\n        if (guid && cycleSeenGuids.has(guid)) return;\n        \/\/ Keep per-cycle exclusion to prevent repeats within cycle\n        cycleSeen.add(item.post_id);\n        if (guid) cycleSeenGuids.add(guid);\n\n        const node = buildItem(item);\n        node.el._candy = node;\n        \/\/ Keep <video> DOM bounded: new items start detached; they attach when they enter the \u00b13 window\n        try { node.detach(); } catch(_){}\n        feedEl.appendChild(node.el);\n        try { io.observe(node.el); } catch(_){ }\n      });\n\nscheduleCompact();\n\n    }catch(_){}\n    loading = false;\n  }\n\n\n  let newPollTimer = null;\n\n  async function pollNew(){\n    if (loading) return;\n    if (document.hidden) return;\n    try{\n      const res = await fetchFeed({onlyNew:true, limit: 30, avoid: lastPlayedId});\n      if (!res.items || !res.items.length) return;\n\n      \/\/ Insert new videos right AFTER current active (TikTok-like \"next up\")\n      let insertAfter = activeEl;\n      res.items.forEach(item => {\n        if (!item || !item.post_id) return;\n        const guid = String(item.bunny_guid || '').trim();\n\n        \/\/ Don't insert the same \"new\" video multiple times across polls\n        if (newInserted.has(item.post_id)) return;\n        \/\/ Skip if this guid is already in the DOM (prevents duplicates)\n        if (guid && cycleSeenGuids.has(guid)) return;\n\n        newInserted.add(item.post_id);\n\n        \/\/ Also mark it as seen in the current cycle so it won't appear again immediately\n        cycleSeen.add(item.post_id);\n        if (guid) cycleSeenGuids.add(guid);\n\n        const node = buildItem(item);\n        node.el._candy = node;\n\n        if (insertAfter && insertAfter.parentNode === feedEl) {\n          feedEl.insertBefore(node.el, insertAfter.nextSibling);\n          insertAfter = node.el;\n          try { io.observe(node.el); } catch(_){ }\n        } else {\n          \/\/ no active yet -> prepend\n          feedEl.insertBefore(node.el, feedEl.firstChild);\n          try { io.observe(node.el); } catch(_){ }\n        }\n      });\nscheduleCompact();\n    }catch(_){}\n  }\n\n  \/\/ Fast windowed attach\/detach without scanning the whole feed (prevents FPS drops on desktop)\n  const ATTACH_BEHIND = 3;\n  const ATTACH_AHEAD  = 3;\n  let attachedEls = new Set();\n\n  function collectWindow(el){\n    const out = [];\n    if (!el) return out;\n    out.push(el);\n    let p = el;\n    for (let i=0;i<ATTACH_BEHIND;i++){\n      p = p.previousElementSibling;\n      if (!p || !p.classList || !p.classList.contains('candy-item')) break;\n      out.push(p);\n    }\n    let n = el;\n    for (let i=0;i<ATTACH_AHEAD;i++){\n      n = n.nextElementSibling;\n      if (!n || !n.classList || !n.classList.contains('candy-item')) break;\n      out.push(n);\n    }\n    return out;\n  }\n\n  async function setActive(el){\n    if (typeof candyIsFullscreen !== 'undefined' && candyIsFullscreen) return;\n    if (!el || el === activeEl) return;\n\n    const prevActive = activeEl;\n    if (prevActive && prevActive !== el) {\n      sendProgress(prevActive, { force: true });\n    }\n    activeEl = el;\n    \/\/ Track last played post_id for avoid parameter\n    const pid = parseInt(el.dataset && el.dataset.postId, 10) || 0;\n    if (pid > 0) lastPlayedId = pid;\n    pushRecentFromEl(el);\n    playToken++;\n    startProgressTimer(el);\n\n    \/\/ Hide Android gate on the previous active\n    if (isAndroid) hideStartGate(prevActive);\n\n    \/\/ Determine which elements should have <video>\/HLS attached\n    const need = new Set(collectWindow(el));\n\n    \/\/ Detach elements that are no longer needed\n    for (const oldEl of attachedEls){\n      if (!need.has(oldEl)){\n        const api = oldEl._candy;\n        if (api) { try { api.detach(); } catch(_){} }\n      }\n    }\n\n    \/\/ Attach needed elements\n    for (const newEl of need){\n      const api = newEl._candy;\n      if (api) { try { api.attach(); } catch(_){} }\n    }\n\n    attachedEls = need;\n\n    \/\/ Hard-stop previous active immediately to avoid \"audio from A, picture from B\"\n    if (prevActive && prevActive !== el && prevActive._candy){\n      try {\n        const prevApi = prevActive._candy;\n        prevApi.pause();\n        \/\/ iOS audio-session can keep playing if we leave it unmuted\n        try { prevApi.video.muted = true; prevApi.video.setAttribute('muted',''); } catch(_){}\n      } catch(_){}\n    }\n\n    \/\/ Pause + mute any attached non-active (only up to 6 elements)\n    for (const wEl of attachedEls){\n      if (wEl !== el && wEl._candy){\n        try {\n          wEl._candy.pause();\n          try { wEl._candy.video.muted = true; wEl._candy.video.setAttribute('muted',''); } catch(_){}\n        } catch(_){}\n      }\n    }\n\n    const api = el._candy;\n    if (!api) return;\n\n    const v = api.video;\n    \/\/ Apply global mute state only to active\n    v.muted = globalMuted;\n    if (globalMuted) v.setAttribute('muted',''); else v.removeAttribute('muted');\n\n    \/\/ Desktop: update blurred fullscreen background to match the active item\n    try {\n      if (isDesktop()) {\n        const bgUrl = (el.dataset && el.dataset.hls) || '';\n        if (bgUrl) loadGlobalBg(bgUrl);\n      }\n    } catch(_){}\n\n    \/\/ Speed: for active, preload auto; for neighbors, metadata (handled in attach)\n    try { v.preload = 'auto'; } catch(_){}\n\n\n    \/\/ Android-only: require a user gesture once before enabling autoplay\n    if (isAndroid && !androidAutoplayUnlocked){\n      try { api.pause(); } catch(_){}\n      try { v.muted = true; v.setAttribute('muted',''); } catch(_){}\n      showStartGate(el);\n      return;\n    }\n\n    const ok = await safePlay(v, playToken);\n    if (!ok){\n      \/\/ fallback: start muted (iOS allows autoplay muted more reliably)\n      try {\n        v.muted = true; v.setAttribute('muted','');\n        await v.play();\n      } catch(_){}\n    }\n\n\n    \/\/ Commit legacy view\/seen signals only after the 30% threshold is reached.\n    try{\n      \/\/ small delay only to initialize progress after playback starts\n      setTimeout(() => {\n        if (el !== activeEl) return;\n        try{\n          const vv = el._candy ? el._candy.video : null;\n          if (!vv) return;\n          if (vv.paused) return;\n          const payload = collectProgress(el);\n          if (payload) maybeCommitThreshold(el, payload);\n        } catch(_){}\n      }, 600);\n    } catch(_){}\n    \/\/ iOS: if audio enabled, re-assert shortly after starting (helps rare \"silent start\")\n    if (!globalMuted && isIOS) {\n      setTimeout(() => {\n        if (el !== activeEl) return;\n        try {\n          const vv = el._candy ? el._candy.video : null;\n          if (!vv) return;\n          vv.muted = false;\n          vv.removeAttribute('muted');\n        } catch(_){}\n      }, 120);\n    }\n  }\n\n  document.addEventListener('visibilitychange', () => {\n    if (document.hidden) {\n      stopProgressTimer();\n      if (activeEl) sendProgress(activeEl, { force: true, keepalive: true });\n      return;\n    }\n    if (activeEl) startProgressTimer(activeEl);\n  });\n\n  window.addEventListener('pagehide', () => {\n    stopProgressTimer();\n    if (activeEl) sendProgress(activeEl, { force: true, keepalive: true });\n  });\n\n\n  const io = new IntersectionObserver((entries) => {\n    \n    if (typeof candyIsFullscreen !== 'undefined' && candyIsFullscreen) return;\nlet best = null;\n    let bestRatio = 0;\n    for (const e of entries){\n      if (e.isIntersecting && e.intersectionRatio > bestRatio){\n        best = e.target;\n        bestRatio = e.intersectionRatio;\n      }\n    }\n    if (best) setActive(best);\n  }, { threshold:[0.25,0.5,0.75,0.9] });\n\n  async function init(){\n    document.documentElement.style.overflow = 'hidden';\n    document.body.style.overflow = 'hidden';\n\nensureTopSpacer();\n\n    const _qp = new URLSearchParams(location.search);\n    const _playId = _qp.get('play');\n    const _listType = _qp.get('list'); \/\/ 'liked' | 'favs'\n    const _listUid  = _qp.get('uid');\n\n    \/\/ Playlist mode: bounded feed of a user's liked\/favorited videos (no random fill).\n    if (_listType && _listUid && (_listType === 'liked' || _listType === 'favs')) {\n      try {\n        const _clean = location.pathname;\n        history.replaceState(null, '', _clean);\n        const _cu = window.candyUser || {};\n        const _r = await fetch(FEED_URL + '?playlist=' + encodeURIComponent(_listType) + '&user_id=' + encodeURIComponent(_listUid), {\n          cache: 'no-store',\n          credentials: 'same-origin',\n          headers: { 'X-WP-Nonce': _cu.restNonce || _cu.nonce || '' },\n        });\n        const _j = await _r.json();\n        if (_j && _j.ok && _j.items && _j.items.length) {\n          _j.items.forEach(item => {\n            if (!item || !item.post_id) return;\n            const guid = String(item.bunny_guid || '').trim();\n            cycleSeen.add(item.post_id);\n            if (guid) cycleSeenGuids.add(guid);\n            const node = buildItem(item);\n            node.el._candy = node;\n            try { node.detach(); } catch(_){}\n            feedEl.appendChild(node.el);\n            try { io.observe(node.el); } catch(_){}\n          });\n          loadCounts();\n          \/\/ Scroll to the requested video (if specified), else first item\n          let target = feedEl.children[0];\n          if (_playId) {\n            const pidStr = String(parseInt(_playId, 10) || 0);\n            for (const ch of feedEl.children) {\n              if (ch.dataset && ch.dataset.postId === pidStr) { target = ch; break; }\n            }\n          }\n          if (target) {\n            try { target.scrollIntoView({block:'start'}); } catch(_){}\n            setActive(target);\n          }\n        }\n      } catch(_){}\n      return; \/\/ skip loadMore\/pollNew \u0432\u0402\u201d playlist is finite\n    }\n\n    \/\/ ?play=POST_ID \u0432\u0402\u201d open specific video first (from favorites\/liked grid)\n    if (_playId) {\n      try {\n        const _clean = location.pathname + location.search.replace(\/[?&]play=[^&]*\/,'').replace(\/^[?&]\/,'').replace(\/\\?$\/,'').replace(\/^\\?$\/,'');\n        history.replaceState(null, '', _clean || location.pathname);\n        const _cu2 = window.candyUser || {};\n        const _r = await fetch(FEED_URL + '?first_id=' + encodeURIComponent(_playId) + '&limit=1', {\n          cache: 'no-store',\n          credentials: 'same-origin',\n          headers: { 'X-WP-Nonce': _cu2.restNonce || _cu2.nonce || '' },\n        });\n        const _j = await _r.json();\n        if (_j && _j.ok && _j.items && _j.items.length) {\n          _j.items.forEach(item => {\n            if (!item || !item.post_id) return;\n            const guid = String(item.bunny_guid || '').trim();\n            cycleSeen.add(item.post_id);\n            if (guid) cycleSeenGuids.add(guid);\n            const node = buildItem(item);\n            node.el._candy = node;\n            try { node.detach(); } catch(_){}\n            feedEl.appendChild(node.el);\n            try { io.observe(node.el); } catch(_){}\n          });\n        }\n      } catch(_){}\n    }\n\n    loadCounts();\n    await loadMore();\n\n    const first = feedEl.children[0];\n    if (first && !activeEl) setActive(first);\n\n    \/\/ Desktop & iOS: load more when approaching bottom (cheap; avoids getBoundingClientRect in IO callback)\n    let scrollLoadT = null;\n    feedEl.addEventListener('scroll', () => {\n      if (scrollLoadT) return;\n      scrollLoadT = requestAnimationFrame(() => {\n        scrollLoadT = null;\n        if (loading) return;\n        const near = feedEl.scrollTop + feedEl.clientHeight >= feedEl.scrollHeight - (feedEl.clientHeight * 6);\n        if (near) loadMore();\n      });\n    }, { passive: true });\n\n    \/\/ Poll for freshly uploaded videos and put them next in queue\n    if (!newPollTimer) {\n      newPollTimer = setInterval(pollNew, 15000);\n      document.addEventListener('visibilitychange', () => { if (!document.hidden) pollNew(); });\n      window.addEventListener('focus', () => pollNew());\n    }\n  }\n\n  init();\n\n})();\n<\/script>\n\n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-148","page","type-page","status-publish","hentry"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/candyxxx.net\/en\/wp-json\/wp\/v2\/pages\/148","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/candyxxx.net\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/candyxxx.net\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/candyxxx.net\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/candyxxx.net\/en\/wp-json\/wp\/v2\/comments?post=148"}],"version-history":[{"count":41,"href":"https:\/\/candyxxx.net\/en\/wp-json\/wp\/v2\/pages\/148\/revisions"}],"predecessor-version":[{"id":8800,"href":"https:\/\/candyxxx.net\/en\/wp-json\/wp\/v2\/pages\/148\/revisions\/8800"}],"wp:attachment":[{"href":"https:\/\/candyxxx.net\/en\/wp-json\/wp\/v2\/media?parent=148"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}