1(() => { 2 "use strict"; 3 4 const titles = [ 5 "å «ä¸ªå®ç½ PDPï¼ä¸æ¯å «ç§å®¡ç¾ï¼èæ¯åå¥è´ä¹°é£é©å ³éç³»ç»", 6 "é¢å 页é¢ä¸æ¯âæ´å¤å 容âï¼èæ¯åç±»é£é©é½æè¯æ®éç¯", 7 "å «ä¸ªæ ·æ¬ç»ä¸å° 4â6 åº§æ¨¡åæ²åï¼ç¨âä¸å±é¡µé¢âé项审计", 8 "å «ç«çæ£æå¼å·®è·çæ¯åªä½ä»»å¡ãé 置深度ä¸é£é©æ¿è¯º", 9 "å «ä¸ªåçå·²åå为åç§ PDP åå", 10 "Article é¦å±ç¨ 7+1 å¼ ä»»å¡å¾ï¼ä¸æ¬¡è¯´æ¸ å¸å±ãæè´¨ä¸å°è´§", 11 "Article çâè½» A+âç¨ç»æåææ¬ä¸æå¡æ¿è¯ºæ¿ä»£é¿å¾çå¸", 12 "Article æ AR ä»ä¸»å¾åºå ¥å£å»¶ä¼¸å°ææºç©ºé´éªè¯", 13 "Burrow é¦å±ç¨å¾ä¸å¨ä½è§é¢è§£éâæä¹é ãæä¹åâ", 14 "Burrow ä¸å页åå¤è¯æï¼ä¸å¥æ¨¡åè½éªç¨æ·æç»åå", 15 "Albany é¦å±çéç¹ï¼32 æ¬¾é¢æå¦ä½è¢«çéï¼å¹¶éä½ééé£é©", 16 "Albany ç¨ 8 Benefitsãåæè¯æ®ä¸ AI VOC 宿å³çæå¡", 17 "Lovesac ç¨ 9 å¼ é¦å¾ã1 个 GIFï¼ææ²å讲æå¯æç»å级系ç»", 18 "Lovesac ç A+ æ¯ä¸å¥è´ä¹°åæä½ç³»ç»ï¼è䏿¯åç¹é¿å·", 19 "HONBAY é¦å±æ 9 å¾/è²ãå°ºå¯¸ä¸ 10 å 裹åæä»·æ ¼å", 20 "HONBAY ç¨ 2 æ¡ UGC + 6 å¼ A+ æåè½åæè¯æ®", 21 "LINSYï¼24 å¼ å¾è¯æâè½å级âï¼éä»¶çå¸å´ææ ¢è¯æ®", 22 "LINSY ç 4 å¼ A+ é»è¾æ¸ æ¥ï¼é®é¢åªå¨è¯æ®å°è¾¾å¤ªæ", 23 "La-Z-Boy ç¨ 3Dã662 个å è¦é项åä½åéé æ¯æé«ä»·", 24 "La-Z-Boy 以æå¡ãå·¥èºãææ¡£åé¨åºå®æé«ä»·ä¿¡ä»»", 25 "West Elm ç¨å°å¾ã颿ä¸ç½æå¥å»ºç«é«ç«¯ç¡®å®æ§", 26 "West Elm ç¨å°èå¼ºçæ¨¡åè®²æ¸ èéãå·¥èºä¸éè£ ", 27 "主å¾åºçææææ 䏿¯å¼ æ°ï¼èæ¯æ¯å¼ æ¯å¦åçä¸åé®é¢", 28 "è§é¢åªå¨è§£éâååãå¨ä½ãçå®ä½¿ç¨âæ¶æä»·å¼", 29 "A+ çä»·å¼ä¸å¨æ°éï¼è卿¯æ®µæ¯å¦æåç¹åæè¯æ®", 30 "é«è½¬å页é¢éµå¾ªåä¸é¡ºåºï¼å é对ï¼åè¯æï¼å交ä»ï¼æååè¯", 31 "å稿æ¹åæ£ç¡®ï¼ä½ä¸å¤å¿ é¡»å级ãäºé¡¹éè¦éæ°å®ä½", 32 "Monvane é¦å±åªéè¦ 10 个é«ä»»å¡å¯åº¦åªä½", 33 "Monvane ä¸å页建议 12 段ï¼ä¸çé®ãä¸è§è§ãä¸å段", 34 "页é¢åºæâéæ©âè¯æâ交ä»âåè¯âéæ", 35 "å è¡¥åªä½ä¸äº¤æéæï¼åè¡¥çéä¸ VOC" 36 ]; 37 38 function nativeText(order, bbox, text, options = {}) { 39 const { 40 size = 16, 41 color = "#14202A", 42 bold = false, 43 font = "Arial", 44 align = "left", 45 valign = "top", 46 wrap = "square" 47 } = options; 48 return { 49 order, 50 kind: "shape", 51 name: `AR Text ${order}`, 52 bbox, 53 rotation: 0, 54 geometry: "rect", 55 fillColor: null, 56 lineColor: null, 57 lineWidth: 0, 58 text, 59 textStyle: { 60 alignment: align, 61 verticalAlignment: valign, 62 wrap, 63 insets: { top: 0, right: 0, bottom: 0, left: 0 } 64 }, 65 paragraphs: [{ 66 alignment: align, 67 marginLeft: 0, 68 runs: [{ text, fontSize: size, typeface: font, color, bold, alignment: null }] 69 }] 70 }; 71 } 72 73 function nativePanel(order, bbox, fillColor, lineColor = null, lineWidth = 0) { 74 return { 75 order, 76 kind: "shape", 77 name: `AR Panel ${order}`, 78 bbox, 79 rotation: 0, 80 geometry: "rect", 81 fillColor, 82 lineColor, 83 lineWidth, 84 text: "", 85 textStyle: null, 86 paragraphs: [] 87 }; 88 } 89 90 function nativeImage(order, bbox, asset) { 91 return { 92 order, 93 kind: "image", 94 name: `AR Image ${order}`, 95 bbox, 96 rotation: 0, 97 asset, 98 text: "", 99 textStyle: null, 100 paragraphs: [] 101 }; 102 } 103 104 const articleArSlide = { 105 page: 8, 106 background: "#F7F5EF", 107 elements: [ 108 nativeText(1, [44, 25, 1168, 50], "Article æ AR ä»ä¸»å¾åºå ¥å£å»¶ä¼¸å°ææºç©ºé´éªè¯", { size: 34, bold: true, font: "Georgia", wrap: "none" }), 109 nativeText(2, [45, 79, 1120, 30], "æ¡é¢ç«¯åªè´è´£è§¦åï¼æ«ç åç± iPhone / iPad 宿çå®ç©ºé´è½ä½ã", { size: 20, color: "#5C6670", wrap: "none" }), 110 nativePanel(3, [44, 116, 1192, 2], "#C7D6D1"), 111 112 nativePanel(4, [44, 132, 348, 36], "#0F6E5F"), 113 nativeText(5, [62, 140, 300, 22], "01ï½ä¸»å¾åºå ¥å£", { size: 16, color: "#FFFFFF", bold: true, wrap: "none" }), 114 nativePanel(6, [44, 172, 348, 383], "#FFFFFF", "#C7D6D1", 1), 115 nativeImage(7, [44, 172, 348, 383], "article-ar/article-ar-entry.png"), 116 nativeText(8, [44, 566, 348, 46], "âView in your spaceâ ä¸ä¸»å¾åå±ï¼\nAR 被å®ä¹ä¸ºå¾åºä¸ç空é´éªè¯ä»»å¡ã", { size: 15, color: "#5C6670" }), 117 118 nativePanel(9, [416, 132, 500, 36], "#D18B00"),
119 nativeText(10, [434, 140, 450, 22], "02ï½æ«ç 跨端æ¥å", { size: 16, color: "#FFFFFF", bold: true, wrap: "none" }), 120 nativePanel(11, [416, 172, 500, 250], "#FFFFFF", "#C7D6D1", 1), 121 nativeImage(12, [416, 172, 500, 250], "article-ar/article-ar-qr.png"), 122 nativePanel(13, [416, 438, 500, 158], "#E8EFEC", "#C7D6D1", 1), 123 nativeText(14, [438, 456, 458, 28], "æ¡é¢è§¦åï¼ææºå®æ", { size: 20, color: "#0F6E5F", bold: true, wrap: "none" }), 124 nativeText(15, [438, 493, 454, 78], "ç¹å»åå¼¹åºäºç»´ç ï¼ç¨æ·ç¨ iPhone / iPad æ«ç è¿å ¥ã\nå®ç½åæ¶åæ iOS / iPadOS 12+ ç设å¤è¦æ±ã", { size: 16, color: "#14202A" }), 125 126 nativePanel(16, [940, 132, 296, 36], "#14202A"), 127 nativeText(17, [958, 140, 252, 22], "03ï½çå®ç©ºé´è½ä½", { size: 16, color: "#FFFFFF", bold: true, wrap: "none" }), 128 nativePanel(18, [940, 172, 296, 137], "#FFFFFF", "#C7D6D1", 1), 129 nativeImage(19, [940, 172, 296, 137], "article-ar/article-ar-result.jpg"), 130 nativePanel(20, [940, 325, 296, 271], "#FFFFFF", "#C7D6D1", 1), 131 nativeText(21, [962, 345, 252, 28], "AR ç»æåç空é´é£é©", { size: 20, color: "#0F6E5F", bold: true, wrap: "none" }), 132 nativeText(22, [962, 384, 250, 142], "ç§»å¨ç«¯æé䏿²åæ¾å ¥ç宿¿é´ï¼\nç´æ¥æ£æ¥ï¼\n\n· 尺度æ¯å¦éé \n· å´åææ¯å¦åé\n· éè¡å¨çº¿æ¯å¦è¢«å ç¨", { size: 16, color: "#14202A" }), 133 nativeText(23, [962, 548, 250, 28], "ä»âçå¾âæ¨è¿å°âè¯æâã", { size: 16, color: "#D18B00", bold: true, wrap: "none" }), 134 135 nativePanel(24, [44, 625, 1192, 46], "#14202A"), 136 nativeText(25, [64, 638, 1150, 24], "å ³é®ä»·å¼ï½AR ä¸å¢å æ¡é¢é ç½®è´æ ï¼èæ¯å¨ä¸ååå ³éâæ¾ä¸æ¾å¾ä¸ãæèµ·æ¥ä»ä¹æ ·âç空é´é£é©ã", { size: 17, color: "#FFFFFF", bold: true, wrap: "none" }), 137 nativeText(26, [44, 687, 950, 22], "æ¥æºï¼Article Leigh 宿¹ PDP · ç¨æ·æä¾æªå¾ä¸ææº AR 宿µ · 2026-08-04", { size: 14, color: "#5C6670", wrap: "none" }), 138 nativeText(27, [1160, 687, 76, 22], "08 / 31", { size: 14, color: "#0F6E5F", align: "right", wrap: "none" }) 139 ] 140 }; 141 142 const livePages = { 143 7: { brand: "Article", asset: "longpage/article-fullpage.png", bbox: [44, 162, 202, 472] }, 144 10: { brand: "Burrow", asset: "longpage/burrow-fullpage.png", bbox: [44, 162, 152, 472] }, 145 12: { brand: "Albany", asset: "longpage/albany-fullpage.png", bbox: [44, 162, 165, 476] }, 146 14: { brand: "Lovesac", asset: "longpage/lovesac-fullpage.png", bbox: [44, 162, 125, 472] }, 147 16: { brand: "HONBAY", asset: "longpage/honbay-fullpage-verified.jpg", bbox: [260, 132, 310, 258] }, 148 18: { brand: "LINSY", asset: "longpage/linsy-fullpage-verified.jpg", bbox: [44, 252, 172, 386] }, 149 20: { brand: "La-Z-Boy", asset: "longpage/lazboy-fullpage.png", bbox: [44, 162, 230, 472] }, 150 22: { brand: "West Elm", asset: "longpage/westelm-fullpage.png", bbox: [44, 162, 300, 472] } 151 }; 152 153 function makeOfficialGallery({ 154 dir, 155 prefix, 156 count, 157 extensions = "jpg", 158 start = 1, 159 titleFor, 160 description, 161 descriptionFor, 162 entries, 163 source 164 }) { 165 return Array.from({ length: count }, (_, index) => { 166 const number = index + start; 167 const fileNumber = String(number).padStart(2, "0"); 168 const extension = Array.isArray(extensions) ? extensions[index] : extensions; 169 const entry = entries?.[index] || {}; 170 return { 171 src: `assets/evidence/${dir}/${prefix}-${fileNumber}.${extension}`, 172 title: entry.title || (titleFor ? titleFor(number, index) : `${fileNumber}ï½å®ç½äº§åå¾åºåå¾`), 173 description: entry.description || (descriptionFor 174 ? descriptionFor(number, index) 175 : description || `æå®ç½å½å主å¾åºé¡ºåºå±ç¤ºç第 ${number} å¼ åå¾ã`), 176 source 177 }; 178 }); 179 } 180 181 function makeDetailSteps({ src, source, sections, scroll = false, segmentPrefix = "" }) { 182 return sections.map((section, index) => ({ 183 src: section.src || (segmentPrefix 184 ? `${segmentPrefix}
184-${String(index + 1).padStart(2, "0")}.webp` 185 : src), 186 title: `${String(index + 1).padStart(2, "0")}ï½${section.title}`, 187 description: section.description, 188 source, 189 mode: section.mode || (scroll && !segmentPrefix ? "scroll" : "panel"), 190 focus: section.focus ?? 0 191 })); 192 } 193 194 const albanyFrameCopy = [ 195 ["æ£é¢åºåè§è§", "æ£é¢åæ¶çå°å·¦ä¾§è´µå¦ä½ãä¸å¤®åº§ä½åå³ä¾§è½¬è§ï¼ä½ä¸ºå¤æ Kova Box Cushion Corner Sectional æ´ä½è½®å»çåºå帧ã"], 196 ["å·¦å轻转è§è§", "轻微转å左侧åï¼è´µå¦ä½ä¼¸åºé¿åº¦ä¸ä¸å¤®æ¨¡åè¿æ·±æ´æ¸ æ¥ï¼è¡¥å æ£é¢å¾ç空é´å ç¨ä¿¡æ¯ã"], 197 ["å·¦åæä¾§è§è§", "æä¾§è§åº¦çªåºå·¦ç«¯æ¶æå度ãåå«åç¼å转è§å ä¾§ï¼ä¾¿äºè§å¯ç®±ååå«çç«ä½æã"], 198 ["左侧å¤è½®å»", "è§çº¿ç§»å左侧ï¼å¼å§æ¾é²è¿ç»å¤ä¾§å è¦åä½èç»æï¼è¯´ææ²åä¾§é¢ç¦»å°é«åº¦ä¸ä½éã"], 199 ["å·¦åè¿æ¸¡è§è§", "ä»å·¦åæ¹è§å¯è´µå¦æ¨¡åä¸é¿èæ¿çè¿æ¥ï¼æ¨¡åæ¥ç¼åèé¨å è¦å ³ç³»éæ¸æ¸ æ°ã"], 200 ["å·¦åæè§è§", "å·¦åè§åº¦éä¸å±ç¤ºèæ¿å段ã转è§å¤è½®å»ä»¥åé å«é«åºæ¡æ¶ç屿¬¡ã"], 201 ["åä¾§åå·¦è§è§", "æ¥è¿åä¾§æ¶å¯çå°é¿èæ¿è¿ç»æ§ä¸å¤ä¸ªæ¨¡ååºåº§çæ¼æ¥ç¹ï¼éå夿弿¾ç©ºé´ä¸çèé¢è§æã"], 202 ["åæ¹å·¦ç§»è§è§", "åæ¹åå·¦çè§çº¿åå°åº§é¢å¹²æ°ï¼ä¸»è¦æ ¸å¯¹èæ¿é«åº¦ãçºµåæ¼ç¼åä½èåå¸ã"], 203 ["è颿£è§è§", "è颿£è§å®æ´åç°é¿è¾¹å è¦ã模ååæ®µä¸ç»ä¸é«åº¦ï¼åçæ²åä¸é å¢ææ¾æ¶æ¯å¦æ´æ´ã"], 204 ["åæ¹å³ç§»è§è§", "ä»èé¢åå³ç§»å¨åï¼å¤ä¾§è½¬è§åçè¾¹èæ¿å¼å§åºç°ï¼å¯è¾¨è®¤ L å½¢ç»åçå¤é¨æçº¿ã
204"], 205 ["å³åé¿è¾¹è§è§", "å³åæè§åæ¶åç°é¿èæ¿ä¸è½¬è§çè¾¹ï¼çªåºä¸¤æ¡å¤è½®å»å¨è§é¨çè¡æ¥æ¹å¼ã"], 206 ["å¤è½¬è§åè§è§", "é头æ£å¯¹ç»åå¤è½¬è§ï¼æ¸ æ¥çå°ä¸¤ä¾§èæ¿ãåºåº§æ¨¡åå转è§å¤çè¿ç»å è¦ã"], 207 ["å¤è½¬è§å±å¼è§è§", "ç¨å¾®å±å¼çåè§è§æä¸¤æ¡èé¢å»¶ä¼¸æ¹ååæ¶çº³å ¥ç»é¢ï¼ä¾¿äºå¤æè½¬è§ç»åçå å°å½¢æã"], 208 ["å³åæä¾§è§è§", "å³åæ¹çªåºç边模åçèé¢å®½åº¦ãè½¬è§æ¨¡åå度åé å«é«ä½å±æ¬¡ã"], 209 ["å³åä¾§é¢è§è§", "é头è¿ä¸æ¥è½¬åå³ä¾§ï¼çè¾¹å¤ä¾§æä¸ºä¸»ä½ï¼å±ç¤ºæ¶æå¤å½¢ååºåº§ä¾§é¢ç宿´æ¯ä¾ã"], 210 ["å³ä¾§ååè§è§", "å³ä¾§ååè§åº¦æ ¸å¯¹çè¾¹é¿åº¦ãåæ¡å度ä¸ä½èä½ç½®ï¼åº§é¢åªä¿çå°éå¯è§ä¿¡æ¯ã"], 211 ["å³ä¾§å¤è½®å»è§è§", "æ¥è¿çº¯ä¾§è§æ¶ï¼æ²åçæ»ä½è¿æ·±ãæ¡æ¶é«åº¦åé å«ååè½å·®æå®¹æè¢«è¯å«ã"], 212 ["å³ä¾§åç§»è§è§", "è§çº¿ç±ä¾§é¢ååç§»å¨ï¼æ¶æãåå«åç¼åç边座ä½éæ°è¿å ¥ç»é¢ï¼å¼å§æ¢å¤ä½¿ç¨é¢ä¿¡æ¯ã"], 213 ["å³åè¿æ¸¡è§è§", "å³åæè§çªåºå³ç«¯æ¶æå è¦ãç¸é»å嫿¥ç¼å转è§å ä¾§çå´åç¨åº¦ã"], 214 ["å³åæä¾§è§è§", "ä»å³åæ¹åæ¶çå°çè¾¹ä¸é¿è¾¹åº§ä½ï¼ä¾¿äºæ¯è¾ä¸¤æ¡ååºç座深åé 嫿åã"], 215 ["å³åç»æè§è§", "é头继ç»é è¿æ£é¢ï¼è½¬è§åº§ãä¸å¤®æ¨¡ååå³ç«¯æ¶æä¹é´çç»åå ³ç³»æ´æç¡®ã"], 216 ["å³å弿¾è§è§", "å³åè§åº¦æè½¬è§å¼å£åé¿è¾¹åº§ä½å±å¼ï¼å¼ºè°å¤äººå´åæ¶çéè¡å ¥å£ä¸è§çº¿å ³ç³»ã"], 217 ["æ£é¢åå³è§è§", "æ£é¢åå³ç»é¢çªåºå³ä¾§ååºãä¸å¤®è½¬è§åè´µå¦ä½è¿ç«¯ï¼éªè¯ä¸å¯¹ç§°ç»åçè§è§å¹³è¡¡ã"], 218 ["æ£é¢å³ä¾§å ¨æ¯", "大é¨å座é¢å·²æ£å¯¹é头ï¼è½å¤æ ¸å¯¹å嫿°éãé å«å®½åº¦åå·¦å³ä¸¤ç«¯çé¿åº¦å·®ã"], 219 ["æ£é¢åå³åºå", "æ¥è¿æ£é¢æ¶éç¹åç°ç®±ååå«çåæ®µã使¶æä¸æ·±åº§æ¯ä¾ï¼æä¾å¤äººä½¿ç¨çç´è§å°ºåº¦ã"], 220 ["æ£å转è§è§è§", "é头æ£å¯¹å 转è§ï¼é¿è¾¹ä¸çè¾¹ç座ä½å ³ç³»ææ¸ æ°ï¼ä¹è½çå°é å«å¨è§é¨çå å´æã"], 221 ["æ£é¢å ¨ç»åè§è§", "宿´å±å¼ L 形座åºä¸è´µå¦ç«¯ï¼æ¸ æ¥äº¤ä»£å ¨é¨æ¨¡åã弿¾è¾¹çåæ´ä½å å°å®½åº¦ã"], 222 ["æ£é¢å± ä¸è§è§", "å± ä¸æ£è§åå°éè§åå·®ï¼ç¨äºæ¯è¾ååå«å®½åº¦ãé èåæ®µåä¸¤ç«¯æ¶æé«åº¦ã"], 223 ["æ£é¢åå·¦è§è§", "仿£é¢å左轻转ï¼è´µå¦ä½æä¸ºè§è§éç¹ï¼è½å¤å¤æèººå§é¿åº¦åå ¶ä¸ä¸å¤®ååºçè¿æ¥ã"], 224 ["å·¦åå ¨æ¯è§è§", "å·¦åå ¨æ¯åæ¶è¡¨ç°è´µå¦ä½ã转è§åé¿è¾¹åº§ä½ï¼ææ¥è¿ç¨æ·è¿å ¥å®¢å æ¶çè§çè§åº¦ã"], 225 ["å·¦åè¿æ£è§è§", "è¾å°çå·¦åå转强è°è´µå¦ç«¯åå«å度ãå·¦æ¶æä¸ä¸å¤®æ¨¡åçè¿ç»æ§ã"], 226 ["åå°æ£é¢è§è§", "æè½¬åºååå°æ£é¢éè¿ï¼ä»¥å·¦ä¾§è´µå¦ä½å宿´é¿è¾¹ç»æä¸å¨è§å¯ï¼ä¾¿äºä¸é¦å¸§è¿ç»å¯¹ç §ã"] 227 ]; 228 229 const evidencePages = { 230 6: { 231 brand: "Article", 232 title: "Articleï½7 å¼ äº§åä»»å¡å¾ + 1 å¼ å°ºå¯¸å¾", 233 url: "https://www.article.com/product/29338/leigh-121-5-modular-5-seater-u-shaped-sectional-silver-moss", 234 hotspots: [ 235 { bbox: [55.84, 128, 714.32, 322], label: "æ¥ç 7+1 åå¾", start: 0 }, 236 { bbox: [820.32, 130, 397.36, 188], label: "æè§£å³ä¾§è´ä¹°åº", start: 0, collection: "detail", count: 7 } 237 ], 238 detail: { 239 title: "Articleï½å³ä¾§é ç½®ãå è´ä¸æçåºæè§£", 240 preview: "assets/detail/article-right-hd.webp", 241 items: makeDetailSteps({ 242 src: "assets/detail/article-right-hd.webp", 243 source: "Article 宿¹ PDP · Leigh 5-Seater", 244 scroll: true, 245 segmentPrefix: "assets/detail/article-detail", 246 sections: [ 247 { title: "åå身份ä¸ä»·æ ¼", description: "æ é¢ã$3,799 ä»·æ ¼ãåæå ¥å£ä¸ 3.0 è¯åå 宿åå身份åé¢ç®å¤æã", focus: 0.02 }, 248 { title: "Arrangement å¸å±", description: "9 个å¸å±ç¼©ç¥å¾ç´æ¥åæ¢æ¨¡åç»åï¼å½åéä¸ Closed Pit Sectionalã", focus: 0.16 }, 249 { title: "æè´¨ä¸é¢è²", description: "Featured Material åºå Fabric / Velvetï¼Custom Color æä¾ 9 ä¸ªè²æ ·å¹¶è¿æ¥ Order Swatchesã", focus: 0.32 }, 250 { title: "è´ç©è½¦ä¸æ¶è", description: "éç®ç ADD TO CART æ¯ä¸»å¨ä
250½ï¼å³ä¾§å¿å½¢å ¥å£æ¿æ¥æ¶èæå¾ã", focus: 0.47 }, 251 { title: "é éä¸å®å¶äº¤æ", description: "é®ç¼é 鿥æãå®å¶ç产æç¤ºä¸äº¤ä»ææå¨å è´æé®ä¸å³æ¶è¯´æã", focus: 0.56 }, 252 { title: "ä¿é䏿é è´", description: "30 天满æä¿è¯éä½éæ¢é£é©ï¼Complete the look with ç¨ååæé æ©å±å®¢åã", focus: 0.67 }, 253 { title: "è¯¦æ æå åº", description: "DescriptionãFeaturesãSpecificationsãCare & Assembly åç»æå é¡¹æ¿æ¥æ·±åº¦ä¿¡æ¯ã", focus: 0.84 } 254 ] 255 }) 256 }, 257 items: [ 258 { src: "assets/evidence/article/article-1.jpg", title: "01ï½å®¢å æ å¢ä¸»å¾", description: "宿´ U å½¢ç»åç½®äºçå®å®¢å ï¼åçæ´ä½é£æ ¼ãå å°æä¸å¤äººä½¿ç¨åºæ¯ã", source: "Article 宿¹äº§å页 · SKU29338" }, 259 { src: "assets/evidence/article/article-2.jpg", title: "02ï½ä¸åä¹å产åè§åº¦", description: "å»é¤ç¯å¢å¹²æ°ï¼æ¸ æ¥å±ç¤ºè´µå¦ä½ã转è§ãæ¶æå忍¡åçç»åå ³ç³»ã", source: "Article 宿¹äº§å页 · SKU29338" }, 260 { src: "assets/evidence/article/article-3.jpg", title: "03ï½æ£é¢ä¸åææ å°º", description: "æ£é¢è§å¾ç¡®è®¤åº§ä½æ°éãé å«ç»æï¼å¹¶ç¨è½¯ç¡¬æ 尺补å åæä¿¡æ¯ã", source: "Article 宿¹äº§å页 · SKU29338" }, 261 { src: "assets/evidence/article/article-4.jpg", title: "04ï½èé¢ç»æ", description: "ä»èåå±ç¤ºæ¨¡åæ¼æ¥ãé èé«åº¦åç¦»å¢ææ¾æ¶çå¤è§ã", source: "Article 宿¹äº§å页 · SKU29338" }, 262 { src: "assets/evidence/article/article-5.jpg", title: "05ï½å侧横åè§å¾", description: "å±ç¤ºä¸æ®µé èçè¿ç»æ§ä¸æ¨¡åè¾¹çï¼å¸®å©å¤æå¼æ¾ç©ºé´ä¸çèé¢è§æã", source: "Article 宿¹äº§å页 · SKU29338" }, 263 { src: "assets/evidence/article/article-6.jpg", title: "06ï½ä¿¯è§å¸å±", description: "ä»ä¸æ¹ç¡®è®¤ Closed Pit Sectional ç U å½¢å´åãééå¼å£ä¸åº§ä½å ³ç³»ã", source: "Article 宿¹äº§å页 · SKU29338" }, 264 { src: "assets/evidence/article/article-7.jpg", title: "07ï½Silver Moss é¢æè¿æ¯", description: "ç¨ç»ç©å¾®è·åçé¢è²ã纹ç䏿¥å¸¸è§¦ææ³è±¡ï¼è䏿¯é夿´å¥æ²åå¤è§ã", source: "Article 宿¹äº§å页 · SKU29338" }, 265 { src: "assets/evidence/article/article-8.jpg", title: "08ï½ç¬ç«å°ºå¯¸å¾", description: "æç¡® 120.5 è±å¯¸å®½ã81 è±å¯¸æ·±ã33 è±å¯¸é«åå ³é®æ¨¡å尺度ï¼ç´æ¥å ³é空é´éé é£é©ã", source: "Article 宿¹äº§å页 · SKU29338" } 266 ] 267 }, 268 9: { 269 brand: "Burrow", 270 title: "Burrowï½16 å¼ éæäº§åå¾ï¼å®ç½å¦å« 3 段è§é¢ï¼", 271 url: "https://burrow.com/products/range-plus-4-piece-sectional", 272 hotspots: [ 273 { bbox: [55.84, 128, 714.32, 322], label: "æ¥ç 16 å¼ åå¾", start: 0 }, 274 { bbox: [820.32, 130, 397.36, 188], label: "æè§£å³ä¾§è´ä¹°åº", start: 0, collection: "detail", count: 8 } 275 ], 276 detail: { 277 title: "Burrowï½å³ä¾§é ç½®ãå级ä¸è´ä¹°åºæè§£", 278 preview: "assets/detail/burrow-right-hd.webp", 279 items: makeDetailSteps({ 280 src: "assets/detail/burrow-right-hd.webp", 281 source: "Burrow 宿¹ PDP · Range Plus 4-Piece Sectional", 282 scroll: true, 283 segmentPrefix: "assets/detail/burrow-detail", 284 sections: [ 285 { title: "è¯åãæ é¢ä¸ä»·æ ¼", description: "è¯è®ºéãåååã常è§ä»·ä¸ä¿éç ä»·æ ¼éä¸å¨é¡¶é¨ï¼å 建ç«ç¤¾ä¼è¯æåä»·æ ¼éç¹ã", focus: 0.03 }, 286 { title: "Configuration é ç½®", description: "å¸å±ç¼©ç¥å¾ä¸ See More Configurations è´è´£åæ¢æ¨¡åæ°éåæåã", focus: 0.17 }, 287 { title: "Fabric 颿", description: "å½å Stone Grey Performance Flatweaveï¼ä»¥è²åå½¢å¼å±ç¤º 5 ä¸ªé¢æéæ©å¹¶æä¾ Order Swatchesã", focus: 0.32 }, 288 { title: "Leg Finish è ¿è²", description: "Black Metal ç 3 ä¸ªè ¿é¨é¥°é¢ç¬ç«æç»ï¼é¿å æç»æä»¶éæ©æ··å ¥é¢æã", focus: 0.43 }, 289 { title: "Upgrades å级件", description: "Ottoman ä¸ Sleep Kit 以ååå¡å½¢å¼å å ¥æ¹æ¡ï¼ç´æ¥å±ç¤ºä»·æ ¼ã", focus: 0.54 }, 290 { title: "ä¿æ¤ä¸å®è£ æå¡", description: "Add Protection ä¸ Add Furniture Assembly ç¨æå è¡æ¿æ¥å¯éæå¡ã", focus: 0.66 }, 291 { title: "æ°éãå è´ä¸åæ", description: "æ°éæ¥è¿ãAdd to cartãShop Pay åæåææ£è¯´æè¢«å缩为è¿ç»äº¤æå¨ä½ã", focus: 0.76 }, 292 { title: "åºåä¸è¯¦æ ", description: "Ready to Ship æä¾å³æ¶åºåä¿¡å·ï¼ä¸æ¹ Details ç»§ç»æ¿æ¥ææå »æ¤ãé ééè´§åå®è£ 说æã", focus: 0.91 } 293 ] 294 }) 295 }, 296 items: makeOfficialGallery({ 297 dir: "burrow", 298 prefix: "burrow", 299 count: 16, 300 extensions: ["jpg", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg", "png", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg"],
301 entries: [ 302 { title: "01ï½Stone Grey ç½åºæ£é¢å¾", description: "ç½åºè§è§å®æ´äº¤ä»£ 4-piece 转è§ç»åçåº§ä½æ°éã弿¾ç«¯ãç´çº¿æ¶æä¸ç»éå±èï¼æ¯å¤æå½åé 置轮å»çåºåå¾ã" }, 303 { title: "02ï½è½¬è§ç»åæä¾§è§å¾", description: "ä»å¼æ¾ç«¯æç模åå ³ç³»ï¼çªåºè½¬è§åº§ãé èè¿ç»æ§ä¸è´µå¦ç«¯ä¼¸åºé¿åº¦ï¼è¡¥è¶³æ£é¢å¾çä¸å°çè¿æ·±ã" }, 304 { title: "03ï½ç±³è²ç´æå®¢å åºæ¯", description: "ç¨æ¸©ææ¨è´¨å®¢å å±ç¤ºæµ è² Range ç³»åççå®å°ºåº¦ãæ±ææé ä¸ç¦»å¢ææ¾ææï¼åç产åè¿å ¥æ¥å¸¸ç©ºé´åç飿 ¼è¡¨ç°ã" }, 305 { title: "04ï½å人çå®ä½¿ç¨åºæ¯", description: "两人åå§¿æåº§å®½ãé èé«åº¦åæ¶ææ¿æè½¬æå¯æç¥ç人ä½å°ºåº¦ï¼åæ¶è¯æç´æç»åçç¤¾äº¤ä½¿ç¨æ¹å¼ã" }, 306 { title: "05ï½å¤è²æ¨¡åååºæé ", description: "åä¸å®¢å å¹¶ç½®æ£è²ä¸æµ è²ç»åï¼å±ç¤º Range 模åå¨ä¸åé¢è²ãæåå空é´ååºä¸çæé å¼¹æ§ã" }, 307 { title: "06ï½æ·±è转è§å®¢å å ¨æ¯", description: "æ·±è L å½¢ç»åç½®äºæ¨è´¨å®¢å ï¼æ¸ æ¥åç°è½¬è§å´åãå¤äººåº§ä½åç»è ¿æ¶ç©ºå¸¦æ¥çè½»çæã" }, 308 { title: "07ï½æ·±èç»ååä¾§è§å¾", description: "åä¾§è§åº¦éç¹å±ç¤ºè¿ç»å è¦çä½é èã转è§å¤è½®å»ä¸å¼æ¾ç©ºé´ä¸çèé¢è§æã" }, 309 { title: "08ï½æµ ç°èé¢ä¸èå³", description: "ä»èåè§å¯æµ ç°æ¨¡åä¸ç¬ç«èå³ï¼è¯´æèæ¿å段ãé èé«åº¦ä»¥åèå³å¯è±ç¦»ä¸»ä½åç¬ææ¾ã" }, 310 { title: "09ï½å¼æ¾ç«¯åå«è¿æ¯", description: "è¿è·ç¦»åç°å¼æ¾ç«¯åå«å度ãåæ±è °æãæ¶æç¼ºçåç躺å§è¾¹çå Performance Flatweave 表é¢ã" }, 311 { title: "10ï½è´µå¦ä½å人使ç¨", description: "ç人åå§¿éªè¯è´µå¦ä½é¿åº¦ãè ¿é¨æ¿æåå¤äººæ¨¡åæçä¸ªäººä¼æ¯åºï¼è¡¥å ç½åºäº§å徿 æ³æä¾çåæå°ºåº¦ã" }, 312 { title: "11ï½ä¸åé ç½®ç空é´åè", description: "å®ç½æç¡®æ 注为ä¸åé 置示æï¼ç¨åæ£æ¨¡åå±ç¤ºè½¬è§ãè´µå¦ä½åç¬ç«åº§å¯éæ°ç»åï¼è䏿¯å½å 4-piece ç䏿¯ä¸å¤è§ã" }, 313 { title: "12ï½åå«çè ¿æ¿æè¿æ¯", description: "çè ¿åå§¿éä¸è¯æåº§é¢å®½åº¦ãé èå 裹å颿å¨ååç¶æä¸ç表ç°ï¼åçéæ ååå§¿æ¯å¦èéã" }, 314 { title: "13ï½ä¸åº§ç´æçæ¬åºæ¯", description: "ä¸åº§ç´æçæ¬å±ç¤ºåä¸è®¾è®¡è¯è¨å¨å°åå¸å±ä¸çæ£é¢æ¯ä¾ï¼å¹¶æ¸ æ¥çå°åæ±è °æãè¿ç»åº§å«ä¸ç»è ¿ã" }, 315 { title: "14ï½å座模å人ä½å°ºåº¦", description: "ç¬ç«å座ä¸çäººåæ¡ï¼ç´æ¥æ¾ç¤ºåº§é«ãåº§æ·±ãæ¶æé«åº¦ååæ±è °æå¯¹ä¾§åå§¿çæ¯æã" }, 316 { title: "15ï½æ¨¡ååè½æ 注å¾", description: "å¨å®¢å åºæ¯ä¸é䏿 åºè¬æ¾é å«ãPerformance Fabricã弿¾ç«¯è´µå¦ä½ä¸å¯éææ¡é¢çåè½ç¹ï¼æ¿æ åç¹ç´¢å¼ä½ç¨ã" }, 317 { title: "16ï½ä¸å±åå«ç»æè¯´æ", description: "åé¢å¼æ¾å¤§å¼ºè°å级åçä¸å±æ³¡æ£åå«ï¼æ´æ·±çèéå±ä¸æ´æå®çæ¯æå±å ±åæ¹åæè½¯åº¦ååå¼¹ã" } 318 ], 319 source: "Burrow 宿¹ PDP · Range Plus 4-Piece Sectional" 320 }) 321 }, 322 11: { 323 brand: "Albany Park", 324 title: "Albany Parkï½32 帧 360° 产åè§å¾ + 7 å¼ å¾ç + 1 段è§é¢", 325 url: "https://www.albanypark.com/products/kova-corner-sectional?variant=42681627705395", 326 hotspots: [ 327 { bbox: [55.84, 128, 714.32, 322], label: "æ¥ç 32 帧 + 7 å¾ + 1 è§é¢", start: 0, count: 40 }, 328 { bbox: [820.32, 130, 397.36, 188], label: "æè§£å³ä¾§è´ä¹°åº", start: 0, collection: "detail", count: 7 } 329 ], 330 detail: { 331 title: "Albany Parkï½é¢æçéãåå«ä¸æå¡åºæè§£", 332 preview: "assets/detail/albany-right-hd.webp", 333 items: makeDetailSteps({ 334 src: "assets/detail/albany-right-hd.webp", 335 source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional", 336 scroll: true, 337 segmentPrefix: "assets/detail/albany-detail", 338 sections: [ 339 { title: "ååãä»·æ ¼ä¸ç½æå¥", description: "åååãç°ä»·/å线价ãèçéé¢ä¸ Complimentary White Glove Delivery ä»·å¼è¯´æåæ¶åºç°ã", focus: 0.03 }, 340 { title: "Choose Fabric", description: "32 æ¬¾é¢æç± Filters ä¸è²æ ·ç½æ ¼æ¿è½½ï¼å½å Almond Performance Velvet æç¡®é«äº®ã", focus: 0.18 }, 341 { title: "å è´¹è²æ ·", description: "Order free swatches ç´§è´´é¢æéæ©ï¼è§£å³å±å¹é¢è²ä¸ç¡®å®æ§ã
341", focus: 0.38 }, 342 { title: "Choose Cushions", description: "Box Cushion / Pillow Cushion ç¨å®æå¡ç对æ¯åå«è½®å»ååææ¹åã", focus: 0.50 }, 343 { title: "Add to Cart ä¸åè´§", description: "主å è´æé®åç´æ¥ç»åºé¢è®¡åè´§åºé´ï¼é¿å æäº¤æèå°ç»ç®é¡µã", focus: 0.64 }, 344 { title: "æç说æ", description: "Limited Lifetime Warrantyã30-Day ReturnsãCustom Made to OrderãHandcrafted in North America å项æ¿è¯ºéä¸ååºã", focus: 0.72 }, 345 { title: "Description 䏿·±åº¦ä¿¡æ¯", description: "åå说æå¨åä¸è´ä¹°æ ç»§ç»å±å¼ï¼å°ºå¯¸ãå®è£ ä¸éæ¢ä¿¡æ¯ä½äºåç»æå æ¨¡åã", focus: 0.86 } 346 ] 347 }) 348 }, 349 items: [ 350 ...makeOfficialGallery({ 351 dir: "albany", 352 prefix: "albany", 353 count: 32, 354 extensions: "webp", 355 titleFor: (number, index) => `${String(number).padStart(2, "0")}ï½360° ${albanyFrameCopy[index][0]}`, 356 descriptionFor: (number, index) => `${albanyFrameCopy[index][1]}ï¼æè½¬åºå ${number}/32ï¼`, 357 source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional" 358 }), 359 { src: "assets/evidence/albany/albany-dimensions.webp", title: "33ï½äº§å尺寸å¾", description: "å®ç½åªä½æ¡ä¸çç¬ç«å°ºå¯¸è§å¾ï¼å®æ´å±ç¤º 122 à 122 è±å¯¸è½¬è§å¸å±ï¼ä»¥å 31 / 39 è±å¯¸å ³é®é«åº¦ã", source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional" }, 360 { src: "assets/evidence/albany/albany-material.webp", title: "34ï½Almond Performance Velvet 颿ç¹å", description: "2048px é¢æè¿æ¯ï¼ç¨äºæ ¸å¯¹å½å Almond Performance Velvet çé¢è²ä¸è¡¨é¢çº¹çã", source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional" }, 361 { src: "assets/evidence/albany/albany-33.jpeg", title: "35ï½å±±æ¯å®¢å æ£é¢åºæ¯", description: "大尺度转è§ç»åå¨éé客å ä¸çæ£é¢è§å¾ï¼å±ç¤ºå¤äººå´åãæ¨¡åæ¯ä¾ä¸æ´ä½ç©ºé´æã", source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional" }, 362 { src: "assets/evidence/albany/albany-34.png", title: "36ï½åºé¢éå 客å åºæ¯", description: "ä¾§åæ¹è§è§åç°åæ·±ã转è§å ³ç³»ãé 嫿¯æåèªç¶å ä¸çæµ è²é¢æè¡¨ç°ã", source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional" }, 363 { src: "assets/evidence/albany/albany-35.jpeg", title: "37ï½å »å® å®¶åºçå®ä½¿ç¨åºæ¯", description: "çå®å®¶åºä¸çå® ç©å ±å¤ç»é¢ï¼è¡¥å æ²åå¨é«é¢çæ´»ç¯å¢éç尺度ä¸ä½¿ç¨ç¶æã", source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional" }, 364 { src: "assets/evidence/albany/albany-36.jpeg", title: "38ï½å人æ²åä¸è峿é åºæ¯", description: "è¾å°ç»åä¸ç¬ç«èå³çé设æ¹å¼ï¼å±ç¤ºæ¨¡åæååç空é´éåºæ§ã", source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional" }, 365 { src: "assets/evidence/albany/albany-37.jpeg", title: "39ï½å¼æ¾å¼å®¢é¤å 模åç»å", description: "å¨å¼æ¾å¼å®¢é¤å ä¸å±ç¤º L å½¢ç»åä¸èå³ï¼è¡¥å 夿¨¡åæ¼æ¥åéè¡å¨çº¿ä¿¡æ¯ã", source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional" }, 366 { type: "video", src: "assets/evidence/albany/albany-product-video.mp4", poster: "assets/evidence/albany/albany-video-poster.jpg", title: "40ï½å®æ¹æ¨¡åæè£ è§é¢", description: "å®ç½åªä½æ¡ä¸ç 1080p 产åè§é¢ï¼ç´æ¥æ¼ç¤ºæ¨¡åè¿æ¥ãæåä¸éæ°å¸ç½®çå®é è¿ç¨ã", source: "Albany Park 宿¹ PDP · Kova Box Cushion Corner Sectional" } 367 ] 368 }, 369 13: { 370 brand: "Lovesac", 371 title: "Lovesacï½9 å¼ å®æ¹äº§åå¾åºåå¾", 372 url: "https://www.lovesac.com/sactionals/shop/6seats8sides-1424", 373 hotspots: [ 374 { bbox: [55.84, 128, 714.32, 322], label: "æ¥ç 9 å¼ åå¾", start: 0 }, 375 { bbox: [820.32, 130, 397.36, 188], label: "æè§£å³ä¾§é ç½®åº", start: 0, collection: "detail", count: 6 } 376 ], 377 detail: { 378 title: "Lovesacï½ç»ä»¶çº§é ç½®ä¸è´ç©è½¦ä¿æ¤æè§£", 379 preview: "assets/detail/lovesac-right-verified.webp", 380 items: makeDetailSteps({ 381 src: "assets/detail/lovesac-right-verified.webp", 382 source: "Lovesac 宿¹ PDP · 6 Seats + 8 Sides", 383 sections: [ 384 { title: "ç»åè§æ ¼ä¸å½åä»·æ ¼", description: "â6 Seats + 8 Sidesâç¨ç»ä»¶æ°éå®ä¹ç»åï¼å®ç½å½åå° $8,010.00 åçº¿ä»·ä¸ $6,007.50 å®ä»·å¹¶ç½®ã", src: "assets/detail/lovesac-step-1-ver
384ified.webp" }, 385 { title: "è¯åä¸è¯è®ºé", description: "4.28 åä¸ 8,369 æ¡è¯ä»·ç´æ¥è¿æ¥ç¨æ·è¯è®ºï¼ä¸ºé«å®¢å模åç»åè¡¥å 社ä¼è¯æã", src: "assets/detail/lovesac-step-2-verified.webp" }, 386 { title: "æ¶æä¸é è", description: "IN THIS SETUP å å Arm Style: Standard Arms ä¸ Back Style: Standardï¼æç»æéæ©æå°ç»ä»¶å±ã", src: "assets/detail/lovesac-step-3-verified.webp" }, 387 { title: "颿ä¸å¡«å ", description: "Fabric 为 Beachwood Rained Chenilleï¼Fill Type 为 Standard Foamï¼åå«äº¤ä»£å¤è§è§¦æä¸åæåºç¡ã", src: "assets/detail/lovesac-step-4-verified.webp" }, 388 { title: "è´ç©è½¦ä¿æ¤è®¡å", description: "Everyday Accident Protection æ æ³¨ä¸º Available in Cartï¼å¹¶æä¾ What's Covered å ¥å£ï¼ä¿æ¤è®¡åå¨è´ç©è½¦é¶æ®µç»§ç»éæ©ã", src: "assets/detail/lovesac-step-5-verified.webp" }, 389 { title: "é éãå è´ä¸èèµ", description: "Free Shipping in 1â2 Weeks ä½äºæç»ä»·æ ¼å ADD TO CART ä¹åï¼æé®ä¸æ¹ç»§ç»ç»åº 24 个æèèµæç¤ºã", src: "assets/detail/lovesac-step-6-verified.webp" } 390 ] 391 }) 392 }, 393 items: makeOfficialGallery({ 394 dir: "lovesac", 395 prefix: "lovesac", 396 count: 9, 397 entries: [ 398 { title: "01ï½6 Seats + 8 Sides æ£é¢æåå¾", description: "ç½åºæ£é¢åæ¶å±ç¤ºä¸»ç»åãç¬ç« Seat ä¸ Side ç»ä»¶ï¼ç´æ¥è¯´æ Sactionals ç±å¯æåº§ä½åä¾§æ¿ææï¼è䏿¯åºå®å¼æ²åã" }, 399 { title: "02ï½å®æ´ç»åæä¾§è§å¾", description: "æä¾§è§è§æ U å½¢å´åãä¸å¤®èå³ãStandard Arms ä¸ Standard Back çè¿ç»å ³ç³»å®æ´å±å¼ã" }, 400 { title: "03ï½Beachwood Rained Chenille 颿", description: "颿微è·åæ¶æ 注 Machine WashableãFamily FriendlyãPet Friendly ä¸ 100% Polyesterï¼æ¿æ æè´¨åå »æ¤è¯æ®ã" }, 401 { title: "04ï½ç¬ç« Seat åå«è¿æ¯", description: "è¿è·ç¦»å±ç¤ºåº§å«å度ãå è¾¹ç¼çº¿å Beachwood è²å¨èªç¶å ä¸ç纹çï¼å¸®å©å¤æåå«ç»æä¸æ¥å¸¸å¤è§ã" }, 402 { title: "05ï½Standard Side 䏿¯æ¶é ä»¶", description: "ä¾§æ¿ç¹ååç°ç«åç»ç¼ãæ¶æé«åº¦åæè½½å¼æ¯æ¶ï¼è¯´æ Sactionals é ä»¶å¯ç´æ¥éçå¨ Side ä¸ã" }, 403 { title: "06ï½ç»åèé¢ç»æ", description: "ç½åºè颿£è§æ¸ æ¥æ¾ç¤º 8 个 Sides çåæ®µãè¿æ¥èå¥åç»ä¸é«åº¦ï¼åç弿¾å¼ææ¾æ¶çè颿´æ´åº¦ã" }, 404 { title: "07ï½å¤§å®¢å å¤äººå´ååºæ¯", description: "çå®ç©ºé´ä¸ç大尺度å´åç»åå±ç¤ºå¤åº§ä½ãèå³ãæ±æä¸éè¡å¼å£ï¼ä½ç°å®¶åºèä¼åå¤äººä½¿ç¨æ¹å¼ã" }, 405 { title: "08ï½å¯æºæ´é¢æææ±¡ç¤ºæ", description: "æ·±è²é¢æä¸çèåå°æ£ææ¥å¸¸æ±¡æ¸é£é©å¯è§åï¼å¹¶ç¨â150+ Washable Fabric Optionsâ强å坿æ´å è¦çä»·å¼ã" }, 406 { title: "09ï½åä¾§ä¸åä¹åè§å¾", description: "åä¾§è§åº¦åæ¶è§å¯ä¾§æ¿ç»ç¼ã转è§è¿æ¥ä¸é å«é«å·®ï¼è¡¥å æ£é¢å¾æªå±ç¤ºçå¤è½®å»ã" } 407 ], 408 source: "Lovesac 宿¹ PDP · 6 Seats + 8 Sides" 409 }) 410 }, 411 15: { 412 brand: "HONBAY", 413 title: "HONBAYï½Corduroy Green å½åé è² 9 å¼ åå¾", 414 url: "https://honbaymall.com/products/honbay-6-piece-plush-double-layer-cushion-corduroy-modular-u-shaped-sectional-cloud-sofa", 415 hotspots: [ 416 { bbox: [55.84, 128, 714.32, 322], label: "æ¥ç 9 å¼ åå¾", start: 0 }, 417 { bbox: [820.32, 130, 397.36, 188], label: "æè§£å³ä¾§è´ä¹°åº", start: 0, collection: "detail", count: 8 } 418 ], 419 detail: { 420 title: "HONBAYï½ä»·æ ¼ãé ç½®ãå è£¹ä¸æçåºæè§£", 421 preview: "assets/detail/honbay-right-verified.webp", 422 items: makeDetailSteps({ 423 src: "assets/detail/honbay-right-verified.webp", 424 source: "HONBAY 宿¹ PDP · Opua 6-Piece Corduroy Cloud Sectional", 425 sections: [ 426 { title: "åå身份ãè¯ä»·ä¸ä»·æ ¼", description: "å®ç½å½å为 Opua 6-Piece Corduroy Cloud Sectionalï¼å¯æ 颿³¨æ Double Layer CushionãModularãStorageï¼ä¸æ¹æ¥è¯åã$2,269.99 å®ä»·ä¸å线åä»·ã", src: "assets/detail/honbay-step-1-ver
426ified.webp" }, 427 { title: "æ´»å¨ä»·ãé¢å¤ä¼æ ä¸åæ", description: "BACK TO SCHOOL EVENT æ¾ç¤º 35% offï¼ä»·æ ¼å¡ç»§ç»åé¢å¤ $100 伿 ãæå¤´åç¨è´¹ Includedãæ»èçãå è¿è´¹ã4 æå æ¯ä¸ PayPal æä»å ¥å£ã", src: "assets/detail/honbay-step-2-verified.webp" }, 428 { title: "ConfigurationãFabric ä¸ Color", description: "å ç¨ç»å缩ç¥å¾éæ© Configurationï¼å确认 Corduroy 颿ï¼å½åé¢è²ä¸º Corduroy Greenï¼å¹¶æä¾å¦å¤ä¸¤æè²æ ·ã", src: "assets/detail/honbay-step-3-verified.webp" }, 429 { title: "尺寸ä¸å è£¹å ¥å£", description: "View dimension ä¸ Package å¹¶åæ¾ç½®ï¼å°ºå¯¸å ¥å£ç¨äºæ ¸å¯¹æ´ä½ã座深ã座é«ãæ¶æãèå³ä¸å¨ç©ç©ºé´ï¼å è£¹å ¥å£è¯´æè¯¥ç»åå 10 ç®±åè´§ä¸å¯è½åæ¹å°è¾¾ã", src: "assets/detail/honbay-step-4-verified.webp" }, 430 { title: "详æ ãæ¨¡åå è´ä¸å®è£ ", description: "The DetailsãBuild Your Own OPUAâExploreãSofa Installation Service ä¸ä¸ªæå å ¥å£ä¾æ¬¡æ¿æ¥ååç»èãæ¨¡ååå è´åå®è£ æå¡ã", src: "assets/detail/honbay-step-5-verified.webp" }, 431 { title: "æ°éãAdd To Cart ä¸åºå", description: "æ°ééæ©ä¸ Add To Cart · $2,169.99 åè¡ï¼ä¸æ¹ä»¥ In stock! Ready to Ship ç»åºå½ååºåç¶æã", src: "assets/detail/honbay-step-6-verified.webp" }, 432 { title: "å项æç䏿¯ä»æ¹å¼", description: "Free Size Guideã4 Interest-Free / AfterpayãFree Shippingã30-Day Returns å项æçæç»å±ç¤ºï¼å¹¶ç»§ç»ååºå®ç½å½åæ¯ä»æ è¯ã", src: "assets/detail/honbay-step-7-verified.webp" }, 433 { title: "说ææå åº", description: "è´ä¹°å¨ä½ä¹å便¬¡æä¾ DescriptionãShipping & ReturnsãCare & Buying Guideï¼ååä¸è¿å ¥ Real Life, Real Comfort å 容ã", src: "assets/detail/honbay-step-8-verified.webp" } 434 ] 435 }) 436 }, 437 items: makeOfficialGallery({ 438 dir: "honbay", 439 prefix: "honbay", 440 count: 9, 441 extensions: ["webp", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg"], 442 entries: [ 443 { title: "01ï½Corduroy Green ç½åºç»åå¾", description: "ç½åºä¸»å¾å®æ´å±ç¤º 6-piece U å½¢ç»åãåå±é å«ä¸å¨ç©èå³ï¼å¹¶ä»¥ OEKO-TEX Standard 100 æ è¯è¡¥å 颿å®å ¨ä¿¡æ¯ã" }, 444 { title: "02ï½U å½¢å®¢å æ£é¢åºæ¯", description: "çå®å®¢å æ£é¢è§è§å±ç¤ºå¤äººå´åãå·¦å³è´µå¦ä
444½å深绿è²ç¯è¯ç»å¨èªç¶å ä¸ç表ç°ã" }, 445 { title: "03ï½U å½¢ç»å俯è§åºæ¯", description: "é«è§åº¦ææ¯ä¸ªæ¨¡åãä¸å¤®å¼å£å两端èå³çå ³ç³»ä¸æ¬¡å±å¼ï¼ä¾¿äºå¤ææ´ä½å å°ä¸éè¡ç©ºé´ã" }, 446 { title: "04ï½å¯è°æ¶æä¸é è", description: "䏿®µè¿ç»å¨ä½å¾æ¼ç¤ºä¾§ç¿¼ç±ç´ç«å°å¤ç¿»çè§åº¦ååï¼è¯´ææ¶æåé è坿åå§¿ãèººå§¿è°æ´ã" }, 447 { title: "05ï½åå±è¬æ¾åå«ç»æ", description: "åè§ä¸æåç¹å强è°è¡¨å±è¬æ¾å¡«å åä¸å±æ¯æå ±åå·¥ä½ï¼æâPlush double-layer cushionâ转æå¯è§è¯æ®ã" }, 448 { title: "06ï½31.5 è±å¯¸å®½å¤§åº§é¢", description: "座é¢è¿æ¯ç´æ¥æ åº 31.5 è±å¯¸æ·±/宽尺度ï¼çªåºçè ¿ãè·ååå¤äººä½¿ç¨æ¶ç宽æ¾ç©ºé´ã" }, 449 { title: "07ï½ä¸ç§æ¨¡åç»å示ä¾", description: "åä¸å¥æ¨¡åæ¼ç¤º U å½¢å´åãç´§å转è§ä¸é¿ç´æä¸ç§å¸å±ï¼è¯æç»åå¯ä»¥éæ¿é´å人æ°ååã" }, 450 { title: "08ï½å¨ç©èå³å®¹éå±ç¤º", description: "æå¼å ¨é¨åº§ä¸å¨ç©ç©ºé´å¹¶æ 注èå³ 27 à 27 à 7.1 è±å¯¸ï¼æ¸ æ¥è¯´ææ¯¯åãç©å ·åæç©çæ¶çº³ä½ç½®ã" }, 451 { title: "09ï½å ³é®å°ºå¯¸æ»è§", description: "尺寸å¾é䏿 åº 105.5 è±å¯¸åè¾¹é¿åº¦ã31.5 è±å¯¸åº§æ·±ã28.7 è±å¯¸æ»é«ã20.5 è±å¯¸åº§é«çå ³é®æ°æ®ï¼ç¨äºæ ¸å¯¹æ¿é´éé ã" } 452 ], 453 source: "HONBAY 宿¹ PDP · Opua 6-Piece Corduroy Cloud Sectional" 454 }) 455 }, 456 17: { 457 brand: "LINSY HOME", 458 title: "LINSY HOMEï½å®ç½å½åçæ¬ 23 å¼ äº§åå¾", 459 url: "https://www.linsyhome.com/products/6-seater-modular-sofa", 460 hotspots: [ 461 { bbox: [55.84, 128, 714.32, 322], label: "æ¥ç 23 å¼ åå¾", start: 0 }, 462 { bbox: [820.32, 130, 397.36, 188], label: "æè§£å³ä¾§è´ä¹°åº", start: 0, collection: "detail", count: 8 } 463 ], 464 detail: { 465 title: "LINSY HOMEï½ä¸æ¥é ç½®ãéä»¶ä¸äº¤æåºæè§£", 466 preview: "assets/detail/linsy-right-verified.webp", 467 items: makeDetailSteps({ 468 src: "assets/detail/linsy-right-verified.webp", 469 source: "LINSY HOME 宿¹ PDP · RUBIK III 6 Seats Sofa", 470 sections: [ 471 { title: "æ é¢ãè¯ä»·ä¸æ´»å¨ä»·æ ¼", description: "å®ç½å½å为 RUBIK III 6 SEATS SOFAï¼æ é¢ä¸æ¾ç¤º 462 reviewsï¼å®ä»· $1,277.00ãå线价 $2,113.00ï¼å¹¶æç¡® Save $836.00ã", src: "assets/detail/linsy-step-1-verified.webp" }, 472 { title: "第 1 æ¥ï¼Fabric", description: "Egyptian Cotton ä¸ Velvet 两个大æé®äºéä¸ï¼å½å Velvet 被深è²è¾¹æ¡éä¸ã", src: "assets/detail/linsy-step-2-verified.webp" }, 473 { title: "第 2 æ¥ï¼Color", description: "å®ç½å½åæ¾ç¤º Navy Blue ä¸ Slate Grey 两æè²æ ·ï¼å½å Navy Blue 忥åå ¥æ¥éª¤æ é¢å¹¶ä»¥æè¾¹é«äº®ã", src: "assets/detail/linsy-step-3-verified.webp" }, 474 { title: "第 3 æ¥ï¼Accessory", description: "æ¡é¢è´ä¹°åºå½åå¯è§ 6 个éä»¶ï¼Rolled Armrest PairãOttomanãVersatile ConsoleãSeatãCornerãWide Track Armrest Pairï¼æ¯é¡¹å¯å¾éå¹¶åç¬è°æ°éã", src: "assets/detail/linsy-step-4-verified.webp" }, 475 { title: "åä¸ªè¯¦æ æå 项", description: "DescriptionãMaterialãDelivery NotesãCleaning Guide åç»ä¿¡æ¯ç´§è·å¨éä»¶å表ä¹åã", src: "assets/detail/linsy-step-5-verified.webp" }, 476 { title: "æ°éä¸å ¨åå é®", description: "主åå QUANTITY æ°éå¨ä¹ååå Free Shipping on All Orders!ï¼åè¿å ¥ä¸»è¦äº¤ææé®ã", src: "assets/detail/linsy-step-6-verified.webp" }, 477 { title: "Add to cart", description: "éç»¿è² Add to cart æ¯ä¸»æé®ï¼ä¸æ¹å¦è®¾èè² Buy it nowï¼åºåå å ¥è´ç©è½¦ä¸ç«å³ç»è´¦ã", src: "assets/detail/linsy-step-7-verified.webp" }, 478 { title: "ç«å³è´ä¹°ãå°è´§ä¸æ¯ä»", description: "Buy it now ä¹åå±ç¤ºé¢ä¼°éè¾¾ä¸ Promise æç¤ºï¼å¹¶ååºå®ç½å½åæ¯æçé¶è¡å¡åæ°åæ¯ä»æ è¯ã", src: "assets/detail/linsy-step-8-verified.webp" } 479 ] 480 }) 481 }, 482 items: makeOfficialGallery({ 483 dir: "linsy", 484 prefix: "linsy", 485 count: 23, 486 extensions: ["jpg", "jpg", "png", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg", "png", "jpg", "jpg", "png", "jpg", "jpg", "png", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg"],
487 entries: [ 488 { title: "01ï½Slate Grey 转è§ç½åºå¾", description: "ç½åºæä¾§è§è§åç° RUBIK III ç L å½¢ 6 座ç»åã纽æ£é èãå®½æ¶æåæ¨¡åæ¥ç¼ï¼æ¯ç°è²é ç½®çå¤è§åºåã" }, 489 { title: "02ï½Slate Grey 客å ç»å", description: "ç°è²è½¬è§ç»åç½®äºçå®å®¢å ï¼éè¿æ±æã毯ååèªç¶å å±ç¤ºäº§åå°ºåº¦åæ·±è²é¢æçå± å®¶æ°å´ã" }, 490 { title: "03ï½ç°è² Egyptian Cotton è¿æ¯", description: "å¾®è·å¾å¼ºè° Egyptian Cotton çç»çº¹ï¼å¹¶ä»¥âæ´å¼ºé§ãæ´æè½¯ãæ´è½»â说æè¯¥é¢æç触æä¸ä½¿ç¨å®ä½ã" }, 491 { title: "04ï½ç±³ç½åè´µå¦ä½ç½åºå¾", description: "æ£é¢ç½åºå¾å±ç¤ºå·¦å³åè´µå¦ä½ä¸ä¸å¤®ä¸åº§ç对称å¸å±ï¼ä¾¿äºæ¯è¾åº§ä½æ°éãå¼å£å®½åº¦åæ¶æé«åº¦ã" }, 492 { title: "05ï½ç±³ç½ U å½¢æè²å®¢å ", description: "åè´µå¦ä½ç»åç½®äºæè²å®¢å ï¼æ¸ æ¥åç°å¤äººå´åã两端躺å§åºåç±³ç½é¢æä¸æ¨è²å®¶å ·çæé ã" }, 493 { title: "06ï½ç±³ç½ç»åæ£é¢çæ´»åºæ¯", description: "æ¥è¿æ£é¢ç空é´å¾å¼ºè°ä¸å¤®åº§ä½ã两侧贵å¦ç«¯ä¸æ±æå±æ¬¡ï¼åç大ç»å卿®é客å ä¸çæ£é¢ä½éã" }, 494 { title: "07ï½Slate Grey 弿¾ç«¯ç»å", description: "ç½åºæè§å±ç¤ºä¸ç«¯è´µå¦ä½ä¸å¦ä¸ç«¯å¼æ¾åº§çéå¯¹ç§°ç»æï¼è¯´ææ¨¡åå¯ä»æ å U å½¢æææ´çµæ´»çè½¬è§æ¹æ¡ã" }, 495 { title: "08ï½Slate Grey æè²å®¢å åºæ¯", description: "ç°è² U å½¢ç»å卿è²å®¤å ä¸çº¢è²å°æ¯¯åæ¡ï¼å±ç¤ºæ·±è²é¢æå¯¹é«é¢å®¶åºç©ºé´çè§è§åéææã" }, 496 { title: "09ï½Slate Grey åè´µå¦æ£é¢å¾", description: "ç½åºæ£é¢å®æ´å±ç¤ºå·¦å³è´µå¦ãä¸å¤®ä¸åº§ã纽æ£é èåå®½è½¨æ¶æï¼ä¾¿äºæ ¸å¯¹å¯¹ç§°é ç½®çæ¨¡åæ°éã" }, 497 { title: "10ï½Slate Grey èå¢å®¢å ", description: "å·è²å®¢å ä¸çç°è² U å½¢ç»åçªåºè´µå¦ä½é¿åº¦ã深座åå¤äººä¼æ¯åºæ¯ï¼åæ¶éªè¯ä¸èç°ç©ºé´çé è²å ³ç³»ã" }, 498 { title: "11ï½ç°è² Velvet é¢æè¿æ¯", description: "ä¸ç»å¾®è·è¯´æçèå¯çç»é¢ä¸æååå ï¼ç¨äºåºå Velvet ä¸ Egyptian Cotton ç表é¢è§æã" }, 499 { title: "12ï½ç±³ç½åè´µå¦æ£é¢å¾", description: "ä¸ç¬¬ 09 å¼ ç¸åå¸å±åæ¢ä¸ºç±³ç½é è²ï¼æä¾å¨æäº®é¢æä¸çè½®å»ãæ¥ç¼åé èç»èå¯¹ç §ã" }, 500 { title: "13ï½ç±³ç½èå¢å®¢å åºæ¯", description: "ç±³ç½ç»åç½®äºèå¢ä¸èè²å°æ¯¯ç¯å¢ï¼å±ç¤ºæµ è²é¢ææäº®ç©ºé´çææä»¥ååè´µå¦ä½çå®é å å°ã" }, 501 { title: "14ï½ç±³ç½ Egyptian Cotton è¿æ¯", description: "æµ è² Egyptian Cotton å¾®è·æ¸ æ¥åç°ç»å¯ç»çº¹åæç±³ç½è²è°ï¼ç¨äºå¤æå±å¹é¢è²ã纹ç䏿¥å¸¸æ¸ æ´é£é©ã" }, 502 { title: "15ï½Navy Blue 转è§ç½åºå¾", description: "æµ·åè L å½¢ç»å以ç½åºæä¾§æ¹å¼å±ç¤ºä¸ç«¯å¼æ¾ãä¸ç«¯å®½æ¶æçå¸å±åæ·±è² Velvet çæ´ä½è½®å»ã" }, 503 { title: "16ï½Navy Blue åè´µå¦æ£é¢å¾", description: "æ£é¢å±å¼æµ·åèåè´µå¦é ç½®ï¼çªåºä¸å¤®åº§ä½ã两端躺å§åºå纽æ£é è卿·±è²é¢æä¸ç屿¬¡ã" }, 504 { title: "17ï½Navy Blue Velvet è¿æ¯", description: "æ·±è Velvet å¾®è·å±ç¤ºç»é¢æ¹åå½¢æçææååï¼å¹¶è¯´æçç»ãè´å¯ä¸æè½¯ç表é¢ç¹å¾ã" }, 505 { title: "18ï½æµ ç°ç´æå èå³ç»å", description: "ç½åºæ£é¢æç´æä¸»ä½ä¸ä¸¤ç«¯å¯ç§»å¨è峿æè¿ç»ååºï¼å±ç¤ºæ¨¡åä»è´µå¦ä½è½¬æ¢ä¸ºç¬ç«èå³çæ¹å¼ã" }, 506 { title: "19ï½Slate Grey 大å U å½¢åºæ¯", description: "大å U å½¢ç»åå æ®å®¢å 主ä½ï¼çªåºåè´µå¦ãå®½æ¶æåå¤äººå´åè½åï¼æ¯å¤æå åº§çæ¬æ´ä½ä½éçå ³é®åºæ¯ã" }, 507 { title: "20ï½æµ ç°å®¶åºå®¢å æ£é¢åºæ¯", description: "æµ ç°ç´æä¸èå³å¨å£ç客å 䏿£é¢åºç°ï¼éè¿æ±æå毯å表ç°å®¶åºä½¿ç¨ç¶æåæµ è²é¢æçç宿æã" }, 508 { title: "21ï½æ·±ç° U å½¢æäº®å®¢å ", description: "æ·±ç°åè´µå¦ç»åç½®äºå¤§çªåï¼å±ç¤ºå¼ºèªç¶å ä¸çé¢è²ååã两端躺å§ç©ºé´åä¸å¤
508®å¤äººåº§ä½ã" }, 509 { title: "22ï½ç±³ç½ç»ååä¾§è§å¾", description: "ç½åºåä¾§ä¸åä¹åè§åº¦å±ç¤ºè¿ç»èæ¿ãæ¨¡åæ¼ç¼åå¤è½¬è§è½®å»ï¼åç弿¾å¼å®¢å ä¸çèé¢è§æã" }, 510 { title: "23ï½å¤åè½ Console å¨ç©å°ºå¯¸", description: "åè§å¾å±ç¤º Console å é¨å¨ç©ãæ¯æ¶ä¸å°é¢ç©ºé´ï¼å¹¶æ 注约 22.44 à 22.44 è±å¯¸å å°å 8.46 è±å¯¸å¨ç©æ·±åº¦ã" } 511 ], 512 source: "LINSY HOME 宿¹ PDP · RUBIK III 6 Seats Sofa" 513 }) 514 }, 515 19: { 516 brand: "La-Z-Boy", 517 title: "La-Z-Boyï½16 å¼ äº§åå¾ï¼å¦å« Customize in 3D å ¥å£ï¼", 518 url: "https://www.la-z-boy.com/p/sofas-sectionals/reclining-sofas-sectionals/reclining-sectionals/trouper-modular-reclining-sectional/R-xxs724?colorFam=green", 519 hotspots: [ 520 { bbox: [50, 130, 318, 318], label: "æ¥ç 16 å¼ åå¾", start: 0 }, 521 { bbox: [402, 133.75, 414, 310.5], label: "æè§£å³ä¾§é ç½®åº", start: 0, collection: "detail", count: 7 } 522 ], 523 detail: { 524 title: "La-Z-Boyï½3Dãå è¦é ç½®ä¸é¨åºæå¡æè§£", 525 preview: "assets/detail/lazboy-right-verified.webp", 526 items: makeDetailSteps({ 527 src: "assets/detail/lazboy-right-verified.webp", 528 source: "La-Z-Boy 宿¹ PDP · Trouper Modular Sectional", 529 sections: [ 530 { title: "Customize in 3D", description: "产å主å¾ä¸ç 3D å ¥å£æå¤æçæ¨¡åç»åä»éæé 读转æå¯è§åé ç½®ã", src: "assets/detail/lazboy-step-1-verified.webp" }, 531 { title: "Sectional ç»å", description: "å³ä¾§ç¬¬ä¸å¼ é ç½®å¡æ æ SectionalãL-Shapeï¼4 pc.ï¼ï¼ç¹å»å¡çè¿å ¥ç»åç»æéæ©ã", src: "assets/detail/lazboy-step-2-verified.webp" }, 532 { title: "Coverãä»·æ ¼ä¸çé¨è²æ ·", description: "Cover å¡å½åæ¾ç¤º 593 个é项ãAntique D212432ãMade to Order ä¸ $5,346ï¼ä¸æ¹ Popular covers æä¾äºæé«é¢è²æ ·ã", src: "assets/detail/lazboy-step-3-verified.webp" }, 533 { title: "Cushion åå«", description: "Cushion (optional) ç¬ç«æå¡ï¼å½å为 Standard Cushionã$0ï¼é¿å æåå«éæ©æ··å ¥å è¦é¢è²ã", src: "assets/detail/lazboy-step-4-verified.webp" }, 534 { title: "Review Summary ä¸ Contact Store", description: "è¯è®ºæè¦å ¥å£ä¹å以 CONTACT STORE ä½ä¸ºä¸»è¦äº¤æå¨ä½ï¼å°è¯¢ä»·åä¸åè¡æ¥å°æ¬å°é¨åºã", src: "assets/detail/lazboy-step-5-verified.webp" }, 535 { title: "At a Glance ä½åä¸åè½", description: "Perfect for Tall 5'10\"â6'2\" ä¸å®¶åºé£æ ¼ãç»åçµæ´»æ§ãæå¨/çµå¨èººä½çè¦ç¹å ±åæä¾èº«ä½å°ºåº¦ååè½è¯æ®ã", src: "assets/detail/lazboy-step-6-verified.webp" }, 536 { title: "èèµãè²æ ·ä¸è®¾è®¡æå¡", description: "Still Deciding? åºä¾æ¬¡æä¾ Apply for FinancingãOrder Free SwatchesãFree Design Servicesã", src: "assets/detail/lazboy-step-7-verified.webp" } 537 ] 538 }) 539 }, 540 items: makeOfficialGallery({ 541 dir: "lazboy", 542 prefix: "lazboy", 543 count: 16, 544 extensions: ["jpg", "png", "png", "png", "png", "png", "png", "png", "png", "png", "png", "png", "png", "png", "png", "png"], 545 entries: [ 546 { title: "01ï½Trouper 模åç»åç½åºå¾", description: "ç½åºå ¨æ¯å±ç¤º Trouper ç L å½¢çµå¨èººæ¤ ç»åã转è§åº§ãå¸¦æ¯æ¶ä¸æ§å°åç¬ç«èå³ï¼å»ºç«å®æ´é ç½®åºåã" }, 547 { title: "02ï½æµ ç°å®¶åºå®¢å å ¨æ¯", description: "çå®å®¢å åºæ¯æé«é èãåæ¶æã转è§å´ååè¶å è·ç¦»è½¬æå¯æç¥çæ´ä½ä½éã" }, 548 { title: "03ï½ä¸æ§å°æ½å±ä¸åæ¯æ¶", description: "俯è§è¿æ¯åæ¶æå¼ååå¨ç©æ½å±å¹¶å±ç¤ºåæ¯æ¶ï¼è¯´æé¥æ§å¨ãæå¿ä¸é¥®åçæ¶çº³ä½ç½®ã" }, 549 { title: "04ï½ååºé«é èç»è", description: "é èè¿æ¯çªåºå¤´æãè °é¨ä¸¤æ®µå¡«å åå è¾¹ç¼çº¿ï¼è§£é Trouper é«é èçæ¯æç»æã" }, 550 { title: "05ï½åº§ä½ã饿§å¨ä¸æ¯æ¶", description: "ååº§è¿æ¯æé¥æ§å¨ãæ¯æ¶ååå®é èåæ¶çº³å ¥ç»é¢ï¼åç°ä¼é²å¨±ä¹æ¶çæä½è·ç¦»ã" }, 551 { title: "06ï½è½¯å æ¶æä¸åºåº§è½®å»", description: "ä¾§é¢è¿æ¯å±ç¤ºåæ¶¦æ¶æãè¿ç»å è¦åé è¿å°é¢çååºåº§ï¼å¼ºè°ä¼ ç»ç¾å¼èººæ¤ çå鿝ææã" }, 552 { title: "07ï½è´µå¦ä½å ¨èººå§¿æ", description: "èæå®å ¨å±å¼å¹¶æé 毯åï¼ç´æ¥å±ç¤ºè ¿é¨æ¿æé¿åº¦ãé èå¾è
552§ååäººæ·±åº¦æ¾æ¾åºæ¯ã" }, 553 { title: "08ï½è½¬è§åº§ä¸å¤ä¾§æ¶æ", description: "æä¾§åºæ¯èç¦è½¬è§æ¨¡åãç¸é»åº§ä½åå¤ä¾§æ¶æçè¿æ¥ï¼è¯´æç»åå¨è½¬å¼¯å¤ä»ä¿æè¿ç»å è¦ã" }, 554 { title: "09ï½å人è§å½±ä½¿ç¨åºæ¯", description: "两ä½ç¨æ·åæ¶å±å¼èæå¹¶ä½¿ç¨ä¸æ§å°ï¼éªè¯å人躺å§ãé¥®åæ¾ç½®å娱ä¹è®¾å¤åæ¶å·¥ä½çè§å½±åºæ¯ã" }, 555 { title: "10ï½å¤åº§å ¨èººå®¢å åºæ¯", description: "å¤ä¸ªåº§ä½å¤äºä¸å躺å§ç¶æï¼å®æ´å±ç¤ºæ¨¡åç»åå¦ä½åæ¶æ¯æåã躺åå¤äººå ±äº«ã" }, 556 { title: "11ï½æ 线å çµä¸æ§å¶é¢æ¿", description: "䏿§å°ç¹åå±ç¤ºæ 线å çµä½ã忝æ¶ååå½¢æ§å¶é®ï¼æä¾çµã饮åä¸è°èåè½éä¸å¨æè¾¹ã" }, 557 { title: "12ï½ä¾§ç½®å¨ç©æ½å±", description: "æå¼çæ¨è´¨æ½å±å±ç¤ºå¯å®¹çº³ä¹¦ç±å饿§å¨çå®é 容éï¼è¡¥å 䏿§å°éèæ¶çº³åè½ã" }, 558 { title: "13ï½å¤ä¾§çµå¨è°èæé®", description: "æ¶æå¤ä¾§è¿æ¯æ¸ æ¥åç°çµå¨èººä½æ§å¶é¢æ¿å USB 端å£ä½ç½®ï¼è¯´æåä¸åå¯ç´æ¥è§¦è¾¾çæä½æ¹å¼ã" }, 559 { title: "14ï½éèå¼çµæºæ¥å£", description: "å¨ç©ç©ºé´å é¨å±ç¤ºçµæºæ¨¡ååè¿æ¥çº¿ï¼è¯´æå çµä¸å¨±ä¹è®¾å¤ä¾çµè¢«éèå¨ä¸æ§ç»æä¸ã" }, 560 { title: "15ï½å躺ä½å 䏿§ç»å", description: "ç´§åæ£é¢åºæ¯ç±åä¸ªèººæ¤ ä¸ç¸é»ä¸æ§å°ææï¼å±ç¤ºæ¨¡åå¯ç¼©å为å°ç©ºé´å¨±ä¹åå ã" }, 561 { title: "16ï½åèººä½æ£é¢ç»å", description: "å人æ£é¢é ç½®åæ¶å±ç¤ºä¸¤åªé«é èåº§æ¤ ãä¸é´ä¸æ§å°ãæ¯æ¶åæè¾¹ç½®ç©åºï¼æç¡®å®¶åºå½±é¢å¼å¸å±ã" } 562 ], 563 source: "La-Z-Boy 宿¹ PDP · Trouper Modular Sectional" 564 }) 565 }, 566 21: { 567 brand: "West Elm", 568 title: "West Elmï½6 å¼ å®æ¹äº§åå¾åºåå¾", 569 url: "https://www.westelm.com/products/harmony-modular-6-piece-pit-sectional-h11450/", 570 hotspots: [ 571 { bbox: [63, 128, 322, 322], label: "æ¥ç 6 å¼ åå¾", start: 0 }, 572 { bbox: [443, 128, 322, 322], label: "æè§£å³ä¾§è´ä¹°åº", start: 0, collection: "detail", count: 6 } 573 ], 574 detail: { 575 title: "West Elmï½ä»·æ ¼ãé é䏿å¡å¨ä½æè§£", 576 preview: "assets/detail/westelm-right-verified.webp", 577 items: makeDetailSteps({ 578 src: "assets/detail/westelm-right-verified.webp", 579 source: "West Elm 宿¹ PDP · Harmony Modular 6-Piece Pit Sectional", 580 sections: [ 581 { title: "Contract Gradeãåååä¸ä»·æ ¼", description: "Contract Grade æ ç¾å 121 è±å¯¸å®æ´åååä¹åï¼å®ç½å½åæ¾ç¤º $5,315.20â$9,044 æ´»å¨ä»·ãåçº¿ä»·æ ¼åºé´ä¸ Up to 20% Offã", src: "assets/detail/westelm-step-1-verified.webp" }, 582 { title: "Make It Your Own", description: "é ç½®åºæç¡®æ æ³¨ 132 Choicesï¼å¹¶æä¾ ColorãFabricãFeatures ä¸ä¸ªçéå ¥å£ã", src: "assets/detail/westelm-step-2-verified.webp" }, 583 { title: "Fabric and Color ä¸å è´¹è²æ ·", description: "å½åå±å¼ Linen é¢æç½æ ¼ï¼å±ç¤º AlabasterãFrost GrayãGraphite çé¢æè²æ ·ï¼åºé¨ç¨ ORDER FREE SWATCHES 转为å®ç©éªè¯ã", src: "assets/detail/westelm-step-3-verified.webp" }, 584 { title: "é éãæ°éä¸ Add To Cart", description: "Delivery & Pickup Options å 确认 Ship to Home åé®ç¼ï¼åå¤è¿°æ´»å¨ä»·æ ¼ï¼æ°éæ¥è¿ä¸ ADD TO CART ä½äºé é确认ä¹åã", src: "assets/detail/westelm-step-4-verified.webp" }, 585 { title: "Registry ä¸é å¥èå³", description: "主交æå¨ä½ä¹åæä¾ ADD TO REGISTRYï¼å¹¶ç¨ Choose Your Matching Ottoman æ¨è Harmony Modular Ottomanã", src: "assets/detail/westelm-step-5-verified.webp" }, 586 { title: "设计é¢çº¦ä¸å è´¹è²æ ·", description: "Still Deciding? åºæä¾ Request a Free Design Appointment ä¸ Order Free Swatchesï¼ä¸ºé«å®¢åé¢æéæ©è¡¥å çäººæ¯æåå®ç©è¯æ®ã", src: "assets/detail/westelm-step-6-verified.webp" } 587 ] 588 }) 589 }, 590 items: makeOfficialGallery({ 591 dir: "westelm", 592 prefix: "westelm", 593 count: 6, 594 description: "West Elm Harmony Modular 6-Piece Pit Sectional å½å产åå¾åºç宿¹åå¾ã", 595 source: "West Elm 宿¹ PDP · Harmony Modular 6-Piece Pit Sectional" 596 }) 597 } 598 }; 599 600 const sections = [ 601 { from: 1, to: 1, label: "å°é¢" }, 602 { from: 2, to: 5, label: "æ¡æ¶ä¸æ ·æ¬" }, 603 { from: 6, to: 22, label: "åçæè§£" }, 604 { from: 23, to: 27, label: "è·¨åçç»è®º" }, 605 { from: 28, to: 31, label: "Monvane éæå»ºè®®" } 606 ]; 607 608 const TOTAL = titles.length; 609 const deckData = window.MONVANE_DECK; 610 if (!deckData || deckData.slides?.length !== TOTAL - 1) { 611 throw new Error("Native slide data is missing or incomplete."); 612 } 613 614 function getSlideForPage(page) { 615 if (page === 8) return articleArSlide; 616 return deckData.slides[page > 8 ? page - 2 : page - 1]; 617 } 618 const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; 619 let current = readHash(); 620 let changing = false;
621 let touchStartX = 0; 622 let touchStartY = 0; 623 let liveScroller = null; 624 let focusScroller = null; 625 let focusPage = null; 626 let focusScale = 1; 627 let evidencePage = null; 628 let evidencePosition = 0; 629 let evidenceCollection = "items"; 630 631 const $ = (selector) => document.querySelector(selector); 632 const slideShell = $("#slideShell"); 633 const nativeSlide = $("#nativeSlide"); 634 const evidenceLayer = $("#evidenceLayer"); 635 const liveLayer = $("#liveLayer"); 636 const progressBar = $("#progressBar"); 637 const currentPage = $("#currentPage"); 638 const topPageNumber = $("#topPageNumber"); 639 const sectionLabel = $("#sectionLabel"); 640 const announcement = $("#slideAnnouncement"); 641 const prevButton = $("#prevButton"); 642 const nextButton = $("#nextButton"); 643 const overviewDialog = $("#overviewDialog"); 644 const overviewPanel = $(".overview__panel"); 645 const overviewGrid = $("#overviewGrid"); 646 const focusDialog = $("#focusDialog"); 647 const focusViewport = $("#focusViewport"); 648 const focusImage = $("#focusImage"); 649 const focusTitle = $("#focusTitle"); 650 const focusPlay = $("#focusPlay"); 651 const focusZoomOut = $("#focusZoomOut"); 652 const focusZoomReset = $("#focusZoomReset"); 653 const focusZoomIn = $("#focusZoomIn"); 654 const focusProgress = $("#focusProgress"); 655 const evidenceDialog = $("#evidenceDialog"); 656 const evidenceMedia = $("#evidenceMedia"); 657 const evidenceImage = $("#evidenceImage"); 658 const evidenceVideo = $("#evidenceVideo"); 659 const evidenceEyebrow = $("#evidenceEyebrow"); 660 const evidenceTitle = $("#evidenceTitle"); 661 const evidenceCounter = $("#evidenceCounter"); 662 const evidenceIndex = $("#evidenceIndex"); 663 const evidenceItemTitle = $("#evidenceItemTitle"); 664 const evidenceDescription = $("#evidenceDescription"); 665 const evidenceSource = $("#evidenceSource"); 666 const evidenceThumbs = $("#evidenceThumbs"); 667 const evidencePrev = $("#evidencePrev"); 668 const evidenceNext = $("#evidenceNext"); 669 670 function pad(value) { 671 return String(value).padStart(2, "0"); 672 } 673 674 function readHash() { 675 const match = location.hash.match(/slide-(\d{1,2})/); 676 if (!match) return 1; 677 return Math.min(TOTAL, Math.max(1, Number(match[1]))); 678 } 679 680 function sectionFor(page) { 681 return sections.find((section) => page >= section.from && page <= section.to)?.label || ""; 682 } 683 684 function setHash(page) { 685 const hash = `slide-${pad(page)}`; 686 if (location.hash !== `#${hash}`) history.replaceState(null, "", `#${hash}`); 687 } 688 689 function updateChrome() { 690 const formatted = pad(current); 691 progressBar.style.width = `${(current / TOTAL) * 100}%`; 692 currentPage.textContent = formatted; 693 topPageNumber.textContent = `${formatted} / ${TOTAL}`; 694 sectionLabel.textContent = sectionFor(current); 695 prevButton.disabled = current === 1; 696 nextButton.disabled = current === TOTAL; 697 announcement.textContent = `第 ${current} 页ï¼å ± ${TOTAL} 页ï¼${titles[current - 1]}`; 698 document.title = `${formatted} / ${TOTAL}ï½${titles[current - 1]}`; 699 } 700 701 function preloadAround(page) { 702 [page - 1, page + 1] 703 .filter((candidate) => candidate >= 1 && candidate <= TOTAL) 704 .flatMap((candidate) => getSlideForPage(candidate).elements) 705 .filter((element) => element.kind === "image") 706 .forEach((element) => { 707 const image = new Image(); 708 image.src = `assets/media/${element.asset}`; 709 }); 710 } 711 712 function setNativeScale() { 713 const scale = slideShell.clientWidth / deckData.width; 714 nativeSlide.style.setProperty("--native-scale", String(scale)); 715 } 716 717 const nativeResizeObserver = new ResizeObserver(setNativeScale); 718 nativeResizeObserver.observe(slideShell); 719
720 function appendRunText(parent, text) { 721 String(text).split("\n").forEach((part, index, parts) => { 722 if (part) parent.append(document.createTextNode(part)); 723 if (index < parts.length - 1) parent.append(document.createElement("br")); 724 }); 725 } 726 727 function renderElementText(node, element) { 728 const textStyle = element.textStyle || {}; 729 const insets = textStyle.insets || {}; 730 node.classList.add("has-text"); 731 node.style.padding = `${insets.top || 0}px ${insets.right || 0}px ${insets.bottom || 0}px ${insets.left || 0}px`; 732 node.style.textAlign = textStyle.alignment || "left"; 733 node.style.whiteSpace = textStyle.wrap === "none" ? "nowrap" : "normal"; 734 if (textStyle.wrap === "none") node.classList.add("is-no-wrap"); 735 node.style.justifyContent = textStyle.verticalAlignment === "middle" 736 ? "center" 737 : textStyle.verticalAlignment === "bottom" ? "flex-end" : "flex-start"; 738 739 const paragraphs = element.paragraphs?.length ? element.paragraphs : [{ runs: [{ text: element.text }] }]; 740 paragraphs.forEach((paragraph) => { 741 const paragraphNode = document.createElement("div"); 742 paragraphNode.className = "native-paragraph"; 743 paragraphNode.style.textAlign = paragraph.alignment || textStyle.alignment || "left"; 744 if (paragraph.marginLeft) paragraphNode.style.paddingLeft = `${paragraph.marginLeft}px`; 745 746 (paragraph.runs || []).forEach((run) => { 747 const runNode = document.createElement("span"); 748 runNode.className = "native-run"; 749 if (run.fontSize) runNode.style.fontSize = `${run.fontSize}px`; 750 if (run.typeface) { 751 runNode.style.fontFamily = run.typeface === "Georgia" 752 ? 'Georgia, "Songti SC", "STSong", serif' 753 : 'Arial, "Microsoft YaHei", "PingFang SC", sans-serif'; 754 } 755 if (run.color) runNode.style.color = run.color; 756 if (run.bold) runNode.style.fontWeight = "700"; 757 appendRunText(runNode, run.text); 758 paragraphNode.append(runNode); 759 }); 760 node.append(paragraphNode); 761 }); 762 } 763 764 function createNativeElement(element, page) { 765 const [x, y, width, height] = element.bbox; 766 const node = document.createElement(element.kind === "image" ? "figure" : "div"); 767 node.className = `native-element native-element--${element.kind}`; 768 node.style.left = `${x}px`; 769 node.style.top = `${y}px`; 770 node.style.width = `${Math.max(width, 1)}px`; 771 node.style.height = `${Math.max(height, 1)}px`; 772 node.style.zIndex = String(element.order || 1); 773 node.style.setProperty("--enter-delay", `${Math.min(900, Math.max(0, y / 720 * 520 + ((element.order || 0) % 5) * 28))}ms`); 774 if (element.rotation) node.style.setProperty("--element-rotation", `${element.rotation}deg`); 775 776 if (element.kind === "image") { 777 const liveConfig = livePages[page]; 778 if (liveConfig?.asset === element.asset) return null; 779 const image = document.createElement("img"); 780 image.src = `assets/media/${element.asset}`; 781 image.alt = ""; 782 image.draggable = false; 783 node.append(image); 784 if (width * height > 40000) node.classList.add("is-major-media"); 785 return node; 786 } 787 788 if (element.kind === "line") { 789 if (width > 3 && height > 3) { 790 node.classList.add("is-diagonal-line"); 791 node.style.setProperty("--diagonal-color", element.lineColor || "#C7D6D1"); 792 node.style.setProperty("--diagonal-angle", `${-Math.atan2(height, width) * 180 / Math.PI}deg`); 793 node.style.setProperty("--diagonal-length", `${Math.hypot(width, height)}px`); 794 } else { 795 node.style.background = element.lineColor || "#C7D6D1"; 796 if (width > height) node.classList.add("is-horizontal-line"); 797 else node.classList.add("is-vertical-line"); 798 } 799 return node; 800 } 801 802 if (element.fillColor) node.style.background = element.fillColor; 803 if (element.lineColor && element.lineWidth) { 804 node.style.border = `${Math.max(1, element.lineWidth)}px solid ${element.lineColor}`; 805 } 806 if (element.geometry === "ellipse") node.style.borderRadius = "50%"; 807 if (element.geometry === "roundRect") node.style.borderRadius = "12px"; 808 if (element.geometry === "custom" && !element.text) node.classList.add("is-decorative-custom"); 809 if (element.fillColor && width * height > 4500) node.classList.add("is-panel"); 810 if (element.text) renderElementText(node, element); 811 812 const maxFont = Math.max(0, ...(element.paragraphs || []).flatMap((paragraph) => 813 (paragraph.runs || []).map((run) => run.fontSize || 0))); 814 if (y < 115 && maxFont >= 20) node.classList.add("is-slide-heading"); 815 if (/^[\s\d,.%/+·h]+$/.test(element.text || "") && maxFont >= 24) node.classList.add("is-metric"); 816 return node; 817 } 818 819 function fitNativeText(node) { 820 const runs = [...node.querySelectorAll(".native-run")]; 821 if (!runs.length) return; 822 const measuredParagraphWidths = node.style.whiteSpace === "nowrap" 823 ? [...node.querySelectorAll(".native-paragraph")].map((paragraph) => { 824 const probe = paragraph.cloneNode(true); 825 Object.assign(probe.style, { 826 position: "fixed", 827 inset: "0 auto auto 0", 828 width: "max-content", 829 maxWidth: "none", 830 visibility: "hidden", 831 whiteSpace: "pre" 832 }); 833 document.body.append(probe); 834 const measuredWidth = probe.getBoundingClientRect().width; 835 probe.remove(); 836 return measuredWidth; 837 }) 838 : []; 839 const widthSafety = node.style.whiteSpace === "nowrap" ? 1.045 : 1; 840 const widestParagraph = Math.max(node.scrollWidth, ...measuredParagraphWidths) * widthSafety; 841 const widthRatio = widestParagraph > node.clientWidth 842 ? node.clientWidth / widestParagraph 843 : 1; 844 const heightRatio = node.scrollHeight > node.clientHeight 845 ? node.clientHeight / node.scrollHeight 846 : 1; 847 const ratio = Math.min(widthRatio, heightRatio); 848 if (ratio >= 0.995) return; 849 const safeRatio = Math.max(0.68, ratio * 0.985); 850 runs.forEach((run) => { 851 const size = Number.parseFloat(run.style.fontSize || getComputedStyle(run).fontSize); 852 if (size) run.style.fontSize = `${size * safeRatio}px`; 853 }); 854 } 855 856 function normalizeSlideElement(page, sourceElement) { 857 if (!/^\d{2} \/ 30$/.test(sourceElement.text || "")) return sourceElement; 858 const footer = `${pad(page)} / ${TOTAL}`; 859 return { 860 ...sourceElement, 861 text: footer, 862 paragraphs: sourceElement.paragraphs?.map((paragraph) => ({ 863 ...paragraph, 864 runs: paragraph.runs?.map((run) => ({ 865 ...run, 866 text: /^\d{2} \/ 30$/.test(run.text || "") ? footer : run.text 867 })) 868 })) 869 }; 870 } 871 872 function renderNativeSlide(page) { 873 const slide = getSlideForPage(page); 874 nativeSlide.replaceChildren(); 875 nativeSlide.className = `native-slide page-${pad(page)} section-${sections.findIndex((section) => page >= section.from && page <= section.to) + 1}`; 876 nativeSlide.style.background = slide.background || "#F7F5EF"; 877 nativeSlide.setAttribute("aria-label", `第 ${page} 页ï¼${titles[page - 1]}`); 878 879 const fragment = document.createDocumentFragment();
880 slide.elements.forEach((sourceElement) => { 881 const element = normalizeSlideElement(page, sourceElement); 882 const node = createNativeElement(element, page); 883 if (node) fragment.append(node); 884 }); 885 nativeSlide.append(fragment); 886 nativeSlide.querySelectorAll(".native-element.has-text").forEach(fitNativeText); 887 setNativeScale(); 888 requestAnimationFrame(() => requestAnimationFrame(() => nativeSlide.classList.add("is-active"))); 889 } 890 891 function renderEvidencePage(page) { 892 evidenceLayer.replaceChildren(); 893 const config = evidencePages[page]; 894 if (!config) return; 895 896 config.hotspots.forEach((hotspot) => { 897 const [x, y, width, height] = hotspot.bbox; 898 const isDetail = hotspot.collection === "detail"; 899 const hotspotItems = isDetail ? config.detail?.items : config.items; 900 const button = document.createElement("button"); 901 button.type = "button"; 902 button.className = `evidence-hotspot${hotspot.variant ? ` evidence-hotspot--${hotspot.variant}` : ""}${isDetail ? " evidence-hotspot--detail" : ""}`; 903 button.style.left = `${(x / 1280) * 100}%`; 904 button.style.top = `${(y / 720) * 100}%`; 905 button.style.width = `${(width / 1280) * 100}%`; 906 button.style.height = `${(height / 720) * 100}%`; 907 button.setAttribute("aria-label", `${config.brand}ï¼${hotspot.label}`); 908 if (isDetail && config.detail?.preview) { 909 const preview = document.createElement("img"); 910 preview.className = "evidence-hotspot__preview"; 911 preview.src = config.detail.preview; 912 preview.alt = ""; 913 preview.draggable = false; 914 button.append(preview); 915 } 916 const badge = document.createElement("span"); 917 badge.className = "evidence-hotspot__badge"; 918 badge.innerHTML = `<i aria-hidden="true">ï¼</i>${hotspot.label}<b>${hotspot.count ?? hotspotItems?.length ?? 0}</b>`; 919 button.append(badge); 920 button.addEventListener("click", (event) => { 921 event.stopPropagation(); 922 openEvidence(page, hotspot.start || 0, hotspot.collection || "items"); 923 }); 924 evidenceLayer.append(button); 925 }); 926 927 if (config.url) { 928 const sourceLink = document.createElement("a"); 929 sourceLink.className = "evidence-source-link"; 930 sourceLink.href = config.url; 931 sourceLink.target = "_blank"; 932 sourceLink.rel = "noopener noreferrer"; 933 sourceLink.setAttribute("aria-label", `æå¼ ${config.brand} 宿¹äº§å页`); 934 sourceLink.innerHTML = `<span>æå¼ ${config.brand} 宿¹ PDP â</span>`; 935 evidenceLayer.append(sourceLink); 936 } 937 } 938 939 function getEvidencePayload(page = evidencePage, collection = evidenceCollection) { 940 const config = evidencePages[page]; 941 if (!config) return null; 942 if (collection === "detail" && config.detail?.items?.length) { 943 return { config, items: config.detail.items, title: config.detail.title, isDetail: true }; 944 } 945 return { config, items: config.items, title: config.title, isDetail: false }; 946 } 947 948 function buildEvidenceThumbs(config, items) { 949 const fragment = document.createDocumentFragment(); 950 items.forEach((item, index) => { 951 const button = document.createElement("button"); 952 button.type = "button"; 953 button.className = "evidence-thumb"; 954 button.dataset.index = String(index); 955 button.setAttribute("aria-label", `æ¥ç第 ${index + 1} 项ï¼${item.title}`); 956 const image = document.createElement("img"); 957 image.src = item.type === "video" ? item.poster : item.src; 958 image.alt = ""; 959 image.draggable = false; 960 if (item.mode === "scroll") image.style.objectPosition = `center ${Math.round((item.focus || 0) * 100)}%`; 961 if (item.type === "video") { 962 button.classList.add("is-video"); 963 const play = document.createElement("em"); 964 play.textContent = "â¶"; 965 play.setAttribute("aria-hidden", "true"); 966 button.append(play); 967 } 968 const number = document.createElement("span"); 969 number.textContent = pad(index + 1); 970 button.append(image, number); 971 button.addEventListener("click", () => showEvidenceItem(index)); 972 fragment.append(button); 973 }); 974 evidenceThumbs.replaceChildren(fragment); 975 } 976 977 function showEvidenceItem(index) { 978 const payload = getEvidencePayload(); 979 if (!payload) return; 980 const { config, items, isDetail } = payload; 981 const total = items.length; 982 evidencePosition = (index + total) % total; 983 const item = items[evidencePosition]; 984 evidenceMedia.classList.toggle("is-scroll", item.mode === "scroll"); 985 evidenceMedia.scrollTop = 0; 986 evidenceMedia.scrollLeft = 0; 987 const itemPosition = evidencePosition; 988 const applyFocus = () => { 989 if (evidencePosition !== itemPosition || item.mode !== "scroll") return; 990 const maxScroll = Math.max(0, evidenceMedia.scrollHeight - evidenceMedia.clientHeight); 991 evidenceMedia.scrollTop = maxScroll * Math.max(0, Math.min(1, item.focus || 0)); 992 }; 993 evidenceVideo.pause(); 994 evidenceVideo.hidden = true; 995 evidenceVideo.removeAttribute("src"); 996 evidenceVideo.removeAttribute("poster"); 997 evidenceVideo.removeAttribute("aria-label"); 998 evidenceImage.onload = null; 999 if (item.type === "video") { 1000 evidenceImage.hidden = true; 1001 evidenceImage.removeAttribute("src"); 1002 evidenceImage.alt = ""; 1003 evidenceVideo.hidden = false;
1004 evidenceVideo.poster = item.poster || ""; 1005 evidenceVideo.src = item.src; 1006 evidenceVideo.setAttribute("aria-label", `${config.brand} åå¾è¯æ®è§é¢ï¼${item.title}`); 1007 evidenceVideo.load(); 1008 } else { 1009 evidenceImage.hidden = false; 1010 evidenceImage.onload = () => requestAnimationFrame(() => requestAnimationFrame(applyFocus)); 1011 evidenceImage.src = item.src; 1012 if (evidenceImage.complete && evidenceImage.naturalWidth) requestAnimationFrame(() => requestAnimationFrame(applyFocus)); 1013 evidenceImage.alt = `${config.brand}${isDetail ? "å³ä¾§è´ä¹°åº" : "åå¾è¯æ®"}ï¼${item.title}`; 1014 } 1015 evidenceCounter.textContent = `${pad(evidencePosition + 1)} / ${pad(total)}`; 1016 evidenceIndex.textContent = `${isDetail ? "模å" : "è¯æ®"} ${pad(evidencePosition + 1)}`; 1017 evidenceItemTitle.textContent = item.title; 1018 evidenceDescription.textContent = item.description; 1019 evidenceSource.replaceChildren(); 1020 if (config.url) { 1021 const sourceLink = document.createElement("a"); 1022 sourceLink.href = config.url; 1023 sourceLink.target = "_blank"; 1024 sourceLink.rel = "noopener noreferrer"; 1025 sourceLink.textContent = `${item.source} · æå¼å®æ¹ PDP â`; 1026 evidenceSource.append(sourceLink); 1027 } else { 1028 evidenceSource.textContent = item.source; 1029 } 1030 evidencePrev.disabled = total < 2; 1031 evidenceNext.disabled = total < 2; 1032 evidenceThumbs.querySelectorAll(".evidence-thumb").forEach((thumb) => { 1033 thumb.classList.toggle("is-active", Number(thumb.dataset.index) === evidencePosition); 1034 }); 1035 } 1036 1037 function openEvidence(page, start = 0, collection = "items") { 1038 const payload = getEvidencePayload(page, collection); 1039 if (!payload) return; 1040 evidencePage = page; 1041 evidenceCollection = payload.isDetail ? "detail" : "items"; 1042 evidenceDialog.classList.toggle("is-detail", payload.isDetail); 1043 evidenceEyebrow.textContent = payload.isDetail ? "PURCHASE PANEL BREAKDOWN" : "ORIGINAL EVIDENCE"; 1044 evidenceTitle.textContent = payload.title; 1045 buildEvidenceThumbs(payload.config, payload.items); 1046 showEvidenceItem(start); 1047 if (!evidenceDialog.open) evidenceDialog.showModal(); 1048 } 1049 1050 function closeEvidence() { 1051 evidencePage = null; 1052 evidencePosition = 0; 1053 evidenceCollection = "items"; 1054 evidenceVideo.pause(); 1055 evidenceVideo.removeAttribute("src"); 1056 evidenceVideo.removeAttribute("poster"); 1057 evidenceVideo.hidden = true; 1058 evidenceImage.onload = null; 1059 evidenceImage.removeAttribute("src"); 1060 evidenceImage.hidden = false; 1061 evidenceDialog.classList.remove("is-detail"); 1062 evidenceEyebrow.textContent = "ORIGINAL EVIDENCE"; 1063 evidenceDialog.close(); 1064 } 1065 1066 function cancelScroller(scroller) { 1067 if (scroller) scroller.destroy(); 1068 } 1069 1070 function createAutoScroller(viewport, options = {}) { 1071 let frame = 0; 1072 let last = 0; 1073 let direction = 1; 1074 let playing = !reducedMotion; 1075 let position = viewport.scrollTop; 1076 let pauseUntil = performance.now() + (options.delay || 650); 1077 const speed = options.speed || 0.032; 1078 const onProgress = options.onProgress || (() => {}); 1079 1080 function report() { 1081 const max = Math.max(0, viewport.scrollHeight - viewport.clientHeight); 1082 if (!playing) position = viewport.scrollTop; 1083 const ratio = max ? viewport.scrollTop / max : 0; 1084 onProgress(ratio, viewport.clientHeight / Math.max(viewport.scrollHeight, 1)); 1085 return max; 1086 } 1087 1088 function tick(now) { 1089 const delta = Math.min(42, now - (last || now)); 1090 last = now; 1091 const max = report(); 1092 if (playing && now >= pauseUntil && max > 1) { 1093 position += direction * speed * delta; 1094 viewport.scrollTop = position; 1095 if (direction > 0 && position >= max - 1) { 1096 position = max; 1097 viewport.scrollTop = position; 1098 direction = -1; 1099 pauseUntil = now + 900; 1100 } else if (direction < 0 && position <= 1) { 1101 position = 0; 1102 viewport.scrollTop = position; 1103 direction = 1; 1104 pauseUntil = now + 900; 1105 } 1106 } 1107 frame = requestAnimationFrame(tick); 1108 } 1109 1110 function pauseTemporarily() { 1111 playing = false;
1112 clearTimeout(pauseTemporarily.timer); 1113 pauseTemporarily.timer = setTimeout(() => { 1114 if (!reducedMotion) playing = true; 1115 }, 1800); 1116 } 1117 1118 viewport.addEventListener("wheel", pauseTemporarily, { passive: true }); 1119 viewport.addEventListener("touchstart", pauseTemporarily, { passive: true }); 1120 viewport.addEventListener("scroll", report, { passive: true }); 1121 frame = requestAnimationFrame(tick); 1122 1123 return { 1124 destroy() { 1125 cancelAnimationFrame(frame); 1126 clearTimeout(pauseTemporarily.timer); 1127 viewport.removeEventListener("wheel", pauseTemporarily); 1128 viewport.removeEventListener("touchstart", pauseTemporarily); 1129 viewport.removeEventListener("scroll", report); 1130 }, 1131 setPlaying(value) { playing = value; }, 1132 toggle() { playing = !playing; return playing; }, 1133 isPlaying() { return playing; }, 1134 toTop() { position = 0; viewport.scrollTo({ top: 0, behavior: reducedMotion ? "auto" : "smooth" }); direction = 1; }, 1135 toBottom() { position = Math.max(0, viewport.scrollHeight - viewport.clientHeight); viewport.scrollTo({ top: viewport.scrollHeight, behavior: reducedMotion ? "auto" : "smooth" }); direction = -1; }, 1136 report 1137 }; 1138 } 1139 1140 function renderLivePage(page) { 1141 cancelScroller(liveScroller); 1142 liveScroller = null; 1143 liveLayer.replaceChildren(); 1144 1145 const config = livePages[page]; 1146 if (!config) return; 1147 1148 const [x, y, w, h] = config.bbox; 1149 const frame = document.createElement("div"); 1150 frame.className = "pdp-frame"; 1151 frame.style.left = `${(x / 1280) * 100}%`; 1152 frame.style.top = `${(y / 720) * 100}%`; 1153 frame.style.width = `${(w / 1280) * 100}%`; 1154 frame.style.height = `${(h / 720) * 100}%`; 1155 const viewport = document.createElement("div"); 1156 viewport.className = "pdp-viewport"; 1157 viewport.setAttribute("role", "region"); 1158 viewport.setAttribute("aria-label", `${config.brand} å®ç½é¿é¡µé¢å¨ææµè§`); 1159 1160 const image = document.createElement("img"); 1161 image.src = `assets/${config.asset}`; 1162 image.alt = `${config.brand} å®ç½äº§å详æ é¿é¡µé¢æªå¾`; 1163 image.draggable = false; 1164 viewport.append(image); 1165 1166 const expand = document.createElement("button"); 1167 expand.type = "button"; 1168 expand.className = "pdp-expand"; 1169 expand.setAttribute("aria-label", `æ¾å¤§æµè§ ${config.brand} 页é¢`); 1170 expand.title = "æ¾å¤§å¨ææµè§"; 1171 expand.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5"/></svg>'; 1172 expand.addEventListener("click", (event) => { 1173 event.stopPropagation(); 1174 openFocus(page); 1175 }); 1176 1177 const track = document.createElement("span"); 1178 track.className = "pdp-track"; 1179 const thumb = document.createElement("span"); 1180 thumb.className = "pdp-thumb"; 1181 track.append(thumb); 1182 1183 const scanLine = document.createElement("span"); 1184 scanLine.className = "scan-line"; 1185 frame.append(viewport, expand, track, scanLine); 1186 liveLayer.append(frame); 1187 1188 let activated = false; 1189 const activateLiveScroll = () => { 1190 if (activated) return; 1191 activated = true; 1192 viewport.scrollLeft = Math.max(0, (viewport.scrollWidth - viewport.clientWidth) / 2); 1193 liveScroller = createAutoScroller(viewport, { 1194 speed: 0.045, 1195 delay: 520, 1196 onProgress(ratio, visibleRatio) { 1197 const trackHeight = track.clientHeight; 1198 const thumbHeight = Math.max(10, Math.min(trackHeight, trackHeight * visibleRatio)); 1199 thumb.style.height = `${thumbHeight}px`; 1200 thumb.style.transform = `translateY(${Math.max(0, (trackHeight - thumbHeight) * ratio)}px)`; 1201 } 1202 }); 1203 }; 1204 1205 if (image.complete && image.naturalWidth) requestAnimationFrame(activateLiveScroll); 1206 else image.addEventListener("load", activateLiveScroll, { once: true }); 1207 1208 frame.addEventListener("dblclick", () => openFocus(page)); 1209 } 1210 1211 function render(page, immediate = false) { 1212 const commit = () => { 1213 current = page; 1214 renderNativeSlide(current); 1215 renderEvidencePage(current); 1216 renderLivePage(current); 1217 updateChrome(); 1218 updateOverviewCurrent(); 1219 setHash(current); 1220 preloadAround(current); 1221 slideShell.classList.remove("is-leaving"); 1222 slideShell.classList.remove("is-entering"); 1223 void slideShell.offsetWidth; 1224 slideShell.classList.add("is-entering"); 1225 changing = false;
1226 }; 1227 1228 commit(); 1229 } 1230 1231 function goTo(page, immediate = false) { 1232 const target = Math.min(TOTAL, Math.max(1, page)); 1233 if ((target === current && !immediate) || changing) return; 1234 render(target, immediate); 1235 } 1236 1237 function buildOverview() { 1238 const fragment = document.createDocumentFragment(); 1239 titles.forEach((title, index) => { 1240 const page = index + 1; 1241 const button = document.createElement("button"); 1242 button.type = "button"; 1243 button.className = "overview-card"; 1244 button.dataset.page = String(page); 1245 button.setAttribute("aria-label", `è·³å°ç¬¬ ${page} 页ï¼${title}`); 1246 button.innerHTML = ` 1247 <span class="overview-card__visual" aria-hidden="true"> 1248 <span class="overview-card__section">${sectionFor(page)}</span> 1249 <span class="overview-card__hero-number">${pad(page)}</span> 1250 <span class="overview-card__signal"><i></i><i></i><i></i></span> 1251 </span> 1252 <span class="overview-card__meta"> 1253 <span class="overview-card__number">${pad(page)}</span> 1254 <span class="overview-card__title">${title}</span> 1255 </span>`; 1256 button.addEventListener("click", () => { 1257 overviewDialog.close(); 1258 goTo(page); 1259 }); 1260 fragment.append(button); 1261 }); 1262 overviewGrid.append(fragment); 1263 } 1264 1265 function updateOverviewCurrent() { 1266 overviewGrid.querySelectorAll(".overview-card").forEach((card) => { 1267 card.classList.toggle("is-current", Number(card.dataset.page) === current); 1268 }); 1269 } 1270 1271 function openOverview() { 1272 updateOverviewCurrent(); 1273 overviewPanel.scrollTop = 0; 1274 overviewDialog.showModal(); 1275 } 1276 1277 function openFocus(page) { 1278 const config = livePages[page]; 1279 if (!config) return; 1280 focusPage = page; 1281 cancelScroller(focusScroller); 1282 focusScroller = null; 1283 focusViewport.scrollTop = 0; 1284 focusViewport.scrollLeft = 0; 1285 focusScale = 1; 1286 focusImage.style.width = "auto"; 1287 focusZoomReset.textContent = "100%"; 1288 focusImage.src = `assets/${config.asset}`; 1289 focusImage.alt = `${config.brand} å®ç½äº§å详æ é¿é¡µé¢æªå¾`; 1290 focusTitle.textContent = `${config.brand}ï½å®ç½ PDP 髿¸ é¿é¡µ`; 1291 focusPlay.textContent = reducedMotion ? "ææ¾" : "æå"; 1292 focusPlay.setAttribute("aria-label", reducedMotion ? "å¼å§èªå¨æ»å¨" : "æåèªå¨æ»å¨"); 1293 focusDialog.showModal(); 1294 1295 let activated = false; 1296 const activate = () => { 1297 if (activated) return; 1298 activated = true; 1299 focusImage.style.width = `${focusImage.naturalWidth}px`; 1300 requestAnimationFrame(() => { 1301 focusViewport.scrollLeft = Math.max(0, (focusViewport.scrollWidth - focusViewport.clientWidth) / 2); 1302 }); 1303 focusScroller = createAutoScroller(focusViewport, { 1304 speed: 0.052, 1305 delay: 700, 1306 onProgress(ratio) { focusProgress.style.width = `${ratio * 100}%`; } 1307 }); 1308 if (reducedMotion) focusScroller.setPlaying(false); 1309 }; 1310 1311 if (focusImage.complete && focusImage.naturalWidth) activate(); 1312 else focusImage.addEventListener("load", activate, { once: true }); 1313 } 1314 1315 function setFocusScale(nextScale) { 1316 if (!focusImage.naturalWidth) return; 1317 focusScale = Math.max(.5, Math.min(2, nextScale)); 1318 focusScroller?.setPlaying(false); 1319 focusPlay.textContent = "ææ¾"; 1320 focusPlay.setAttribute("aria-label", "å¼å§èªå¨æ»å¨"); 1321 focusImage.style.width = `${Math.round(focusImage.naturalWidth * focusScale)}px`; 1322 focusZoomReset.textContent = `${Math.round(focusScale * 100)}%`; 1323 } 1324 1325 function closeFocus() { 1326 cancelScroller(focusScroller); 1327 focusScroller = null; 1328 focusPage = null; 1329 focusScale = 1; 1330 focusImage.style.width = "auto"; 1331 focusDialog.close(); 1332 } 1333 1334 function toggleFullscreen() { 1335 if (!document.fullscreenElement) { 1336 document.documentElement.requestFullscreen?.(); 1337 } else { 1338 document.exitFullscreen?.(); 1339 } 1340 } 1341 1342 $("#homeButton").addEventListener("click", () => goTo(1)); 1343 $("#menuButton").addEventListener("click", openOverview); 1344 $("#overviewClose").addEventListener("click", () => overviewDialog.close()); 1345 $("#fullscreenButton").addEventListener("click", toggleFullscreen); 1346 prevButton.addEventListener("click", () => goTo(current - 1)); 1347 nextButton.addEventListener("click", () => goTo(current + 1)); 1348 $("#focusClose").addEventListener("click", closeFocus); 1349 $("#focusTop").addEventListener("click", () => focusScroller?.toTop()); 1350 $("#focusBottom").addEventListener("click", () => focusScroller?.toBottom()); 1351 focusZoomOut.addEventListener("click", () => setFocusScale(focusScale - .25)); 1352 focusZoomReset.addEventListener("click", () => setFocusScale(1)); 1353 focusZoomIn.addEventListener("click", () => setFocusScale(focusScale + .25)); 1354 focusPlay.addEventListener("click", () => { 1355 if (!focusScroller) return;
1356 const playing = focusScroller.toggle(); 1357 focusPlay.textContent = playing ? "æå" : "ææ¾"; 1358 focusPlay.setAttribute("aria-label", playing ? "æåèªå¨æ»å¨" : "å¼å§èªå¨æ»å¨"); 1359 }); 1360 $("#evidenceClose").addEventListener("click", closeEvidence); 1361 evidencePrev.addEventListener("click", () => showEvidenceItem(evidencePosition - 1)); 1362 evidenceNext.addEventListener("click", () => showEvidenceItem(evidencePosition + 1)); 1363 1364 overviewDialog.addEventListener("click", (event) => { 1365 if (event.target === overviewDialog) overviewDialog.close(); 1366 }); 1367 focusDialog.addEventListener("click", (event) => { 1368 if (event.target === focusDialog) closeFocus(); 1369 }); 1370 evidenceDialog.addEventListener("click", (event) => { 1371 if (event.target === evidenceDialog) closeEvidence(); 1372 }); 1373 1374 document.addEventListener("keydown", (event) => { 1375 if (evidenceDialog.open) { 1376 if (event.key === "Escape") { event.preventDefault(); closeEvidence(); } 1377 if (event.key === "ArrowLeft") { event.preventDefault(); showEvidenceItem(evidencePosition - 1); } 1378 if (event.key === "ArrowRight") { event.preventDefault(); showEvidenceItem(evidencePosition + 1); } 1379 return; 1380 } 1381 if (focusDialog.open) { 1382 if (event.key === "Escape") { event.preventDefault(); closeFocus(); } 1383 if (event.key === " ") { event.preventDefault(); focusPlay.click(); } 1384 if (event.key === "+" || event.key === "=") { event.preventDefault(); setFocusScale(focusScale + .25); } 1385 if (event.key === "-") { event.preventDefault(); setFocusScale(focusScale - .25); } 1386 if (event.key === "0") { event.preventDefault(); setFocusScale(1); } 1387 return; 1388 } 1389 if (overviewDialog.open) { 1390 if (event.key === "Escape") overviewDialog.close(); 1391 return; 1392 } 1393 if (["ArrowRight", "PageDown", " "].includes(event.key)) { 1394 event.preventDefault(); 1395 goTo(current + 1); 1396 } else if (["ArrowLeft", "PageUp"].includes(event.key)) { 1397 event.preventDefault(); 1398 goTo(current - 1); 1399 } else if (event.key === "Home") { 1400 event.preventDefault(); 1401 goTo(1); 1402 } else if (event.key === "End") { 1403 event.preventDefault(); 1404 goTo(TOTAL); 1405 } else if (event.key.toLowerCase() === "f") { 1406 event.preventDefault(); 1407 toggleFullscreen(); 1408 } else if (event.key.toLowerCase() === "o") { 1409 event.preventDefault(); 1410 openOverview(); 1411 } else if (event.key.toLowerCase() === "a" && liveScroller) { 1412 event.preventDefault();
1413 liveScroller.toggle(); 1414 } else if (event.key === "Enter") { 1415 if (evidencePages[current]) { 1416 event.preventDefault(); 1417 openEvidence(current, 0); 1418 } else if (livePages[current]) { 1419 event.preventDefault(); 1420 openFocus(current); 1421 } 1422 } 1423 }); 1424 1425 $("#stage").addEventListener("touchstart", (event) => { 1426 const touch = event.changedTouches[0]; 1427 touchStartX = touch.clientX; 1428 touchStartY = touch.clientY; 1429 }, { passive: true }); 1430 1431 $("#stage").addEventListener("touchend", (event) => { 1432 const touch = event.changedTouches[0]; 1433 const deltaX = touch.clientX - touchStartX; 1434 const deltaY = touch.clientY - touchStartY; 1435 if (Math.abs(deltaX) > 52 && Math.abs(deltaX) > Math.abs(deltaY) * 1.4) { 1436 goTo(current + (deltaX < 0 ? 1 : -1)); 1437 } 1438 }, { passive: true }); 1439 1440 window.addEventListener("hashchange", () => { 1441 const target = readHash(); 1442 if (target !== current) goTo(target); 1443 }); 1444 1445 window.addEventListener("beforeunload", () => { 1446 cancelScroller(liveScroller); 1447 cancelScroller(focusScroller); 1448 nativeResizeObserver.disconnect(); 1449 }); 1450 1451 buildOverview(); 1452 render(current, true); 1453})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.