← Back to Home

silversal.com Scam Check: 25/100 Trust | ScamMinder

Website: silversal.com

Screenshot of silversal.com

Safety Score

25/100
✗ Scam Risk

Exercise caution when interacting with this website.

AI Analysis Results

Category: E-commerce
About this website:

Detailed Analysis Report: Is Silversal.com Safe and Legit? Website Overview and Purpose Silversal.com is an e-commerce platform that specializes in selling a variety of clothing items, including hoodies, dresses, and outerwear . The site aims to attract fashion-conscious consumers with promotional offers and discounts, such as free shipping on orders over $69. Content Quality and User Experience Key Experience Highlights Offers a wide range of clothing categories, appealing to diverse fashion tastes. Promotional pop-ups for discounts, creating a sense of urgency for potential buyers. Visually appealing layout with vibrant colors and product images. Multiple payment options available, including major credit cards and digital wallets. Claims Verification and Red Flags ⚠️ Red Flags Detected Several red flags have been identified that raise concerns about the legitimacy of the site: Unrealistic Discounts: The site offers significant discounts (up to 50% off), which is often a tactic used by scam sites to lure customers. No Verifiable Company Information: There is a lack of clear company registration details or physical address, which is essential for trust. New Domain: The domain is only 2 years old, which is relatively new for an e-commerce site claiming to offer a wide range of products. Generic Product Descriptions: Many product descriptions appear vague and lack specific details, which is common in scam sites. Fake Reviews: Customer testimonials on the site seem overly positive and generic, raising suspicion about their authenticity. ⚠️ Caution Points Users should verify the legitimacy of the site before making any purchases. Be cautious of providing personal information, especially payment details. Security Note: The site uses a standard SSL certificate, but this does not guarantee legitimacy. Legitimacy and Reputation Assessment The domain has been operational for approximately 2 years , which is relatively new for an e-commerce platform. The site is hosted in Canada and uses Cloudflare for security. However, the lack of a solid reputation, combined with the absence of verifiable company information, raises concerns about its legitimacy. Final Verdict and Recommendations Conclusion: Based on the identified red flags and the overall lack of transparency, Silversal.com appears to be a high-risk e-commerce site . Users are advised to exercise caution and consider alternative, more established retailers for their shopping needs. Best practices include researching the site thoroughly, checking for customer reviews on independent platforms, and avoiding sharing sensitive information.

Risk Assessment: scam
⚠️ Red Flags:
  • [GUARDRAIL] No deterministic evidence for scam; downgrading to warning
  • [CLAIMS] Unrealistic discounts of up to 50% off, which is often a tactic used by scam sites.
  • [TRANSPARENCY] No clear company registration details or physical address available.
  • [DOMAIN HISTORY] The domain is only 2 years old, raising concerns about its legitimacy.
  • [CONTENT QUALITY] Generic product descriptions that lack specific details.
📊 Analysis Reasons:
  • [BUSINESS MODEL] The site offers unrealistic discounts, which is a common tactic used by scam sites to attract customers.
  • [TRANSPARENCY] There is no verifiable company information or physical address provided, raising trust issues.
  • [DOMAIN HISTORY] The domain is only 2 years old, which is relatively new for an e-commerce site claiming to offer a wide range of products.
  • [CONTENT QUALITY] Many product descriptions are vague and lack specific details, which is typical of fraudulent sites.
  • [TRUST SIGNALS] Customer testimonials appear overly positive and generic, suggesting they may be fabricated.
🛡️ Safety Actions Applied:
  • {"type":"scam_downgraded","reason":"No deterministic evidence for scam; downgrading to warning","scoreCeiling":null,"targetStatus":"warning"}
Score Source: openai_guardrail
AI Confidence: medium

Technical Details

\n\n\n \n \n \n Silversal\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n \n \n \n \n\n\n\n\n\n\n\n \n \n \n \n
\n \n
\n \n \n \n \n\n\n\n \n class SpzCustomLabelScript extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n }\n isLayoutSupported(layout) {\n return true;\n }\n mountCallback() {\n const script = this.element;\n const boxEl = script.closest('.product_snippet_label_area');\n const labelEl = boxEl.querySelector('.product_snippet__label');\n if(!labelEl) return;\n const observer = new ResizeObserver((entries) => {\n const labelEls = boxEl.querySelectorAll('.product_snippet__label');\n const offsetWidth = Math.max(...Array.from(labelEls).map(el => el.offsetWidth));\n if(offsetWidth>0){\n const padding = offsetWidth / 2 + 8 + 'px';\n boxEl.style.left = padding;\n boxEl.style.right = padding;\n boxEl.style.visibility = 'visible';\n }\n });\n observer.observe(labelEl);\n }\n }\n SPZ.defineElement('spz-custom-label-script', SpzCustomLabelScript);\n\n\n \n(function () {\n class SPZCustomEventTrack extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n }\n\n isLayoutSupported(layout) {\n return true;\n }\n\n buildCallback() {\n this.setupAction_();\n }\n\n track(key, value) {\n console.log('tracking...', key, value);\n if(window.sa){\n window.sa.track(key, value);\n }else{\n let sa = null;\n Object.defineProperty(window, 'sa',{\n get: function() {\n return sa;\n },\n set(val){\n sa = val;\n sa.track(key, value);\n }\n })\n }\n }\n\n setupAction_() {\n const clickParams = {\n business_type: 'product_theme',\n event_name: 'function_click',\n function_name: 'Farida',\n plugin_name: 'Farida',\n template_name: \"index\",\n template_type: 15,\n module: 'online_store',\n module_type: 'online_store',\n tab_name: '',\n card_name: '',\n event_developer: 'ccbGolumn',\n event_type: 'click',\n };\n\n this.registerAction('trackClick', (e) => {\n const event_info = e.args || {};\n this.track('function_click', {\n ...clickParams,\n event_info: JSON.stringify(event_info),\n });\n });\n\n this.registerAction('trackExpose', (e) => {\n const event_info = e.args || {};\n this.track('function_expose', {\n ...clickParams,\n event_name: 'function_expose',\n event_type: 'expose',\n event_info: JSON.stringify(event_info),\n });\n });\n }\n }\n\n SPZ.defineElement('spz-custom-event-track', SPZCustomEventTrack);\n}())\n\n\n\n
\n
\n\n\n \n\n\n\n\n \n \n\n\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n \n \n\n
\n \n\n
\n
\n
\n \n\n\n

\n \n \n \"\"\n \n\n \n \n \n \n\n

\n\n \n \n \n \n\n\n\n\n \n \n\n
\n \n
\n \n \n
\n
Powered by \"GoogleTranslate
\n \n \n \n \n \n \n
\n
\n
\n \"USD\"USD\n \n \n \n \n \n
    \n \n
  • \n \"USD\"United States Dollars (USD)\n
  • \n \n
  • \n \"EUR\"Euro (EUR)\n
  • \n \n
  • \n \"GBP\"United Kingdom Pounds (GBP)\n
  • \n \n
  • \n \"CAD\"Canadian Dollars (CAD)\n
  • \n \n
  • \n \"AUD\"Australian Dollars (AUD)\n
  • \n \n
\n
\n
\n
\n
\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n \n \n \n \n \n
\n
\n \n\n\n\n\n \n\n
\n
\n
\n\n \n
\n
\n \n\n\n\n\n \n\n
\n
\n \n \n
\n
\n \n
\n
\n \n \n \n
\n \n \n\n\n

\n \n \n \n \n\n \n \n \n \n\n

\n\n \n \n\n\n\n\n \n \n \n \n \n \n\n
\n \n
\n \n \n
\n
\n \n \n \n \n \n \n
\n
\n
\n \"USD\"USD\n \n \n \n \n \n
    \n \n
  • \n \"USD\"United States Dollars (USD)\n
  • \n \n
  • \n \"EUR\"Euro (EUR)\n
  • \n \n
  • \n \"GBP\"United Kingdom Pounds (GBP)\n
  • \n \n
  • \n \"CAD\"Canadian Dollars (CAD)\n
  • \n \n
  • \n \"AUD\"Australian Dollars (AUD)\n
  • \n \n
\n
\n
\n
\n
\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n
\n\n
\n \n \n \n \n \n\n \n \n \n \n
\n \n
\n
\n\n \n\n\n\n \n \n\n \n \n\n\n\n \n
\n\n\n(function () {\n let w = window.innerWidth;\n function setHeaderCssVar() {\n const headerEle = document.getElementById(\n \"shoplaza-section-header\",\n );\n if (!headerEle) {\n return;\n }\n document.body.style.setProperty(\n \"--window-height\",\n `${window.innerHeight}px`,\n );\n document.body.style.setProperty(\n \"--header-height\",\n `${headerEle.clientHeight}px`,\n );\n\n const mdScorllHideEle = headerEle.querySelector(\n \".header__mobile .header__scroll_hide\",\n );\n if (mdScorllHideEle) {\n document.body.style.setProperty(\n \"--header-scroll-hide-height-md\",\n `${mdScorllHideEle.clientHeight}px`,\n );\n }\n\n const pcScorllHideEle = headerEle.querySelector(\n \".header__desktop .header__scroll_hide\",\n );\n if (pcScorllHideEle) {\n document.body.style.setProperty(\n \"--header-scroll-hide-height-pc\",\n `${pcScorllHideEle.clientHeight}px`,\n );\n }\n }\n function handlResize() {\n if (w == window.innerWidth) {\n return;\n }\n w = window.innerWidth;\n setHeaderCssVar();\n }\n function init() {\n setHeaderCssVar();\n window.removeEventListener(\"resize\", window._theme_header_listener);\n window._theme_header_listener = handlResize;\n window.addEventListener(\"resize\", window._theme_header_listener);\n }\n init();\n})();\n\n\n\n
\n \n
\n
\n \n\n
\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n
\"\"\"\"
\n\n \n\n \n \n \n \n\n
\n \n \n \n
\n
\n
\n\n
\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n \n

Hoodie

\n \n \n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Shhh I'm Only Talking to God Today Long-sleeved Hoodie\n \n

\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n (1)\n \n
\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n +5\n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n She Overcame Everything Long Sleeve Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Melanin Every Shade Slays Zip Hood Long Sleeves Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n I Match Energy Long Sleeves Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n +3\n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Faith In God Change Everything Long-sleeved Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $29.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Black Queen Long-sleeved Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $29.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Dearly Beloved Long Sleeves Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Black Girl Magic Hooded Hoodie\n \n

\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n (6)\n \n
\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n +3\n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n I Can't But I Know A Guy Long-sleeved Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $29.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n I Can Do All Things Through Christ High Collar Pullover Irregular Long Sleeve Warm Sweatshirt\n \n

\n\n \n
\n \n \n \n \n \n \n $45.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Psalm 91 with God Long-sleeved Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $29.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Women's Don't Play About Me Anymore Letter Print Long-sleeved Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Taurus Girly Season Print Long-sleeved Hoodie\n \n

\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n (2)\n \n
\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Loc'd and Beautiful Long-sleeved Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $29.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 29%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 29%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Stay On Your P's... Long-sleeved Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $34.99\n \n \n \n \n $48.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n +2\n \n \n \n \n\n \n
\n\n
\n \n
\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Shhh I'm Only Talking to God Today Long-sleeved Hoodie\n \n

\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n (1)\n \n
\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n +5\n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n She Overcame Everything Long Sleeve Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Melanin Every Shade Slays Zip Hood Long Sleeves Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n I Match Energy Long Sleeves Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n +3\n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Faith In God Change Everything Long-sleeved Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $29.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Black Queen Long-sleeved Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $29.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Dearly Beloved Long Sleeves Hoodie\n \n

\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n\n\n \n \n \n\n \n \n \n \n \n\n \n \n \n \n Save 33%\n \n\n \n \n \n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Black Girl Magic Hooded Hoodie\n \n

\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n (6)\n \n
\n\n \n
\n \n \n \n \n \n \n $25.99\n \n \n \n \n $38.99\n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n +3\n \n \n \n \n\n \n
\n\n
\n \n
\n \n \n View more\n \n \n \n \n
\n
\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n \n \n \n \n \n \n\n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n\n \n
\n \n\n\n \n\n\n \"\"\n\n\n\n\n\n \n\n\n \n\n\n \n\n\n \n \n\n\n\n\n\n
\n
\n \n \n \n
\n \n \n\n \n\n \n \n\n \n\n \n \n \n\n \n \n
\n
\n\n
\n\n \n\n \n \n \n \n\n
\n \n \n \n
\n
\n
\n\n
\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n \n

New Hooded Coat

\n \n \n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Women Loose Fluffy Colorblock Hooded Coat\n \n

\n\n \n
\n \n \n \n \n \n \n $30.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n +3\n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Tribal Ethnic Patterns Long Sleeve Zip-Up Fleece Long Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Denim Patch Print Plush Lapel Winter Mid-Length Coat\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Autumn And Winter Solid Color Fur Collar Hooded Long Thick Warm Cotton-padded Coat\n \n

\n\n \n
\n \n \n \n \n \n \n $78.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n \"\"\n \n \n \n +2\n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Glittering Pink Polka Dots Plush Lapel Winter Mid-Length Coat\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n I Simple Don't Play About Me Anymore Long Sleeve Zip-Up Fleece Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $38.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Solid Color Long Sleeve Zip-Up Fleece Long Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Striped Curves Long Sleeve Zip-Up Fleece Long Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Simple Color Blocking Long Sleeve Zip-Up Fleece Long Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \"\"\n \n \n \n \n \n \n \n \n \n\n \n \"\"\n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n African Ethno Art Long Sleeve Zip-Up Fleece Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $38.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Afro Bun Black Woman Long Sleeve Zip-Up Fleece Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $38.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Gold Stripes Long Sleeve Zip-Up Fleece Long Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Ethnic African Pattern Long Sleeve Zip-Up Fleece Long Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n \n\n
\n \n \n\n\n\n\n\n \n\n\n\n\n\n \n\n \n \n \n\n \n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Bohemian Geometric Stripes Plush Lapel Winter Mid-Length Coat\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n
\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Women Loose Fluffy Colorblock Hooded Coat\n \n

\n\n \n
\n \n \n \n \n \n \n $30.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n +3\n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Tribal Ethnic Patterns Long Sleeve Zip-Up Fleece Long Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Denim Patch Print Plush Lapel Winter Mid-Length Coat\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Autumn And Winter Solid Color Fur Collar Hooded Long Thick Warm Cotton-padded Coat\n \n

\n\n \n
\n \n \n \n \n \n \n $78.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n +2\n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Glittering Pink Polka Dots Plush Lapel Winter Mid-Length Coat\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n I Simple Don't Play About Me Anymore Long Sleeve Zip-Up Fleece Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $38.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Solid Color Long Sleeve Zip-Up Fleece Long Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n
\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n
\n \n \n
\n
\n \n \n\n\n \n\n \n\n \n\n\n \n\n \n\n \n
\n \n \n
\n\n
\n \n \n\n \n

\n \n Striped Curves Long Sleeve Zip-Up Fleece Long Hoodies\n \n

\n\n \n
\n \n \n \n \n \n \n $48.99\n \n \n \n \n \n \n \n
\n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n
\n\n
\n \n
\n \n \n View more\n \n \n \n \n
\n
\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n \n \n \n \n \n \n\n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n \n \n\n
\n \n\n\n \n\n\n \n\n\n\n\n\n \n\n\n \n\n\n \n\n\n \n \n\n\n\n\n\n
\n
\n \n \n \n
\n \n \n\n \n\n \n \n\n \n\n \n \n \n\n \n \n
\n
\n\n
\n\n \n\n \n
\n\n \n\n \n \n \n \n\n
\n \n \n \n
\n
\n
\n\n
\n\n\n\n\n \n\n\n let section_id = '1760684384410';\n window.reviewSettings = {};\n window.reviewSettings[section_id] = {\n \"sub_title\": null,\n \"star_least\": \"5\",\n \"only_featured\": false,\n \"with_photo\": false,\n \"review_insufficient\": \"no_reviews\",\n \"minimum_comment_num\": 5,\n \"fill_strategy\": \"hide\",\n \"layout\": \"grid\",\n \"image_size\": \"natural\",\n \"wall_mobile_num\": 2,\n \"wall_pc_num\": 4,\n \"limit\": 8,\n \"show_product\": false,\n \"hide_review_section\": true,\n \"title\": \"Reviews\",\n \"accent_color\": null,\n \"color_title\": \"#000000\",\n \"text_color\": \"#000000\",\n \"card_wrap_color\": null,\n \"background_color\": \"#ffffff\"\n };\n\n\n\n\n
\n
\n\n\n const TAG = 'spz-custom-revue-util';\n const DEFAULT_DELAY_TIME = 100;\n\n class SpzCustomRevueUtil extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n this.templates_ = SPZServices.templatesForDoc();\n }\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n }\n\n static deferredMount() {\n return false;\n }\n mountCallback() {\n }\n\n debounceRender(el, thisEl, containerStr) {\n return this.smoothRender_(el, thisEl, containerStr).then(() => this.attemptToFit_(thisEl));\n }\n \n smoothRender_(newEl, thisEl, containerStr) {\n const that = this;\n that.appendAsUnvisibleContainer_(newEl, thisEl);\n const components = newEl.querySelectorAll('[layout]');\n return Promise.race([\n Promise.all(\n Array.prototype.map.call(components, (e) =>\n SPZ.whenDefined(e).then(() => e.whenBuilt())\n )\n ),\n SPZServices.timerFor(that.win).promise(DEFAULT_DELAY_TIME),\n ]).then(() => {\n return containerStr !== 'form_' ? thisEl.mutateElement(() => that.quickReplace(thisEl, newEl)) : thisEl.mutateElement(() => that.quickReplaceForm(thisEl, newEl));\n });\n }\n\n quickReplace(thisEl, newEl) {\n thisEl.container_ && this.toggleVisible_(thisEl.container_);\n this.toggleVisible_(newEl, true);\n thisEl.container_ && SPZCore.Dom.removeElement(thisEl.container_);\n thisEl.container_ = newEl;\n };\n\n quickReplaceForm(thisEl, newEl) {\n thisEl.form_ && this.toggleVisible_(thisEl.form_);\n this.toggleVisible_(newEl, true);\n const children = thisEl.form_.querySelector('*:not(template)');\n children && SPZCore.Dom.removeElement(children);\n this.toggleVisible_(thisEl.form_, true);\n thisEl.form_.appendChild(newEl);\n };\n \n appendAsUnvisibleContainer_(el, thisEl) {\n this.toggleVisible_(el);\n thisEl.element.appendChild(el);\n }\n \n attemptToFit_(thisEl) {\n const fitFunc = () => {\n thisEl.mutateElement(this.setElementHeight_.bind(thisEl));\n };\n const container = thisEl.container_ || thisEl.form_;\n if (container) {\n const children = container.querySelectorAll('*:not(template)');\n const spzChildren = Array.prototype.filter\n .call(children, SPZUtils.isSpzElement)\n .filter((e) => !(e.isMount && e.isMount()));\n spzChildren\n .map((e) => SPZ.whenDefined(e).then(() => e.whenMounted()))\n .forEach((p) => p.then(() => fitFunc()));\n }\n return fitFunc();\n }\n \n setElementHeight_() {\n const targetHeight = (this.container_ || this.form_)?./*OK*/ scrollHeight;\n const height = this.element./*OK*/ offsetHeight;\n if (height !== targetHeight) {\n SPZCore.Dom.setStyles(this.element, {\n height: `${targetHeight}px`,\n });\n }\n }\n \n toggleVisible_(el, visible = false) {\n if (!visible) {\n el.classList.add('i-spzhtml-layout-fill');\n SPZCore.Dom.setStyles(el, {\n 'z-index': -100000,\n 'opacity': 0,\n });\n } else {\n el.classList.remove('i-spzhtml-layout-fill');\n SPZCore.Dom.setStyles(el, {\n 'z-index': 'auto',\n 'opacity': 1,\n });\n }\n }\n \n setMinWidth_() {\n const targetWidth = this.container_?./*OK*/ scrollWidth;\n const width = this.element./*OK*/ offsetWidth;\n if (width !== targetWidth) {\n SPZCore.Dom.setStyles(this.element, {\n 'min-width': `${targetWidth}px`,\n });\n }\n }\n\n triggerEvent_ = (name, data) => {\n const event = SPZUtils.Event.create(this.win, `${TAG}.${name}`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n\n }\n SPZ.defineElement(TAG, SpzCustomRevueUtil);\n\n\n const TAG = 'spz-custom-revue-render';\n class SPZCustomRevueRender extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n }\n\n static deferredMount() {\n return false;\n }\n\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n }\n\n mountCallback = () => {}\n\n render = (data) => {\n return this.templates_\n .findAndRenderTemplate(this.element, data, null)\n .then((el) => {\n if (this.element.children.length > 0) {\n this.element.children[0].style.display = 'none';\n }\n this.element.appendChild(el);\n // const utilsEl = document.getElementById('spz_custom_revue_util');\n // utilsEl && SPZ.whenApiDefined(utilsEl).then((api) => {\n // api.debounceRender(el, this);\n // });\n });\n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n }\n SPZ.defineElement(TAG, SPZCustomRevueRender)\n\n\n\n\n\n\n\n\n\n\n const TAG = 'spz-custom-revue-star';\n class SPZCustomRevueStar extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n }\n\n static deferredMount() {\n return false;\n }\n\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n this.starNum = this.element.getAttribute('starNum');\n this.starTotal = this.element.getAttribute('starTotal');\n this.showStarText = this.element.getAttribute('showStarText');\n this.starColor = this.element.getAttribute('color');\n this.interact = this.element.getAttribute('interact');\n this.starSize = this.element.getAttribute('starSize') || 14;\n }\n\n mountCallback = () => {\n this.doRender_({\n starTotal: this.starTotal,\n totalArray: Array.from({ length: Number(this.starTotal) }, (v, k) => k + 1),\n starNum: this.starNum,\n showStarText: this.showStarText,\n starColor: this.starColor,\n starSize: this.starSize\n }).then(() => {\n if (this.interact) {\n this.addEventListeners_();\n }\n });\n }\n\n addEventListeners_ = () => {\n \n const stars = document.querySelectorAll('.revue-star__star');\n\n \n stars.forEach(star => {\n star.addEventListener('click', event => {\n const starEl = star.closest('.revue-star__star');\n const starIndex = Number(starEl.dataset.index);\n let isHalf = event.offsetX < star.offsetWidth / 2;\n // rtl\n if (document.documentElement.getAttribute('dir') === 'rtl') {\n isHalf = event.offsetX > star.offsetWidth / 2;\n }\n const starValue = isHalf ? starIndex - 0.5 : starIndex;\n this.starClickHandler_({ value: starValue });\n });\n });\n\n }\n\n renderStar = () => {\n \n\n const isRtl = document.documentElement.getAttribute('dir') === 'rtl';\n const stars = this.element.querySelectorAll('.revue-star__star');\n stars.forEach((star, i) => {\n const starIndex = i + 1;\n const starEl = star.querySelector('svg:nth-child(2)');\n const isHalf = this.starNum % 1 > 0 && Math.ceil(this.starNum) === starIndex;\n const isSolid = starIndex <= Math.ceil(this.starNum);\n starEl.style.display = isSolid ? 'block' : 'none';\n if (isHalf) {\n if (isRtl) {\n // RTL布局下,如果是半星,显示星星的右半边\n starEl.style.clipPath = `polygon(50% 0, 100% 0, 100% 100%, 50% 100%)`;\n } else {\n // LTR布局下,如果是半星,显示星星的左半边\n starEl.style.clipPath = `polygon(0 0, 50% 0, 50% 100%, 0 100%)`;\n }\n } else {\n starEl.style.clipPath = `polygon(0 0, 100% 0, 100% 100%, 0 100%)`\n }\n });\n\n const showCountEle = this.element.querySelector('#revue-star-show-count');\n showCountEle && SPZ.whenApiDefined(showCountEle).then((api) => {\n api.render({ starNum: this.starNum, starTotal: this.starTotal });\n });\n }\n\n doRender_ = (data) => {\n return this.templates_\n .findAndRenderTemplate(this.element, { starSize: this.starSize, ...data }, null)\n .then((el) => {\n const children = this.element.querySelector('*:not(template)');\n children && SPZCore.Dom.removeElement(children);\n this.element.appendChild(el);\n })\n .then(() => {\n this.starNum = data.starNum;\n this.renderStar();\n\n });\n }\n\n starClickHandler_ = (event) => {\n this.starNum = event.value;\n this.renderStar();\n this.triggerEvent_('change', { value: event.value });\n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n }\n SPZ.defineElement(TAG, SPZCustomRevueStar)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n const TAG = 'spz-custom-revue-like';\n class SPZCustomRevueLike extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n }\n\n static deferredMount() {\n return false;\n }\n\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n this.grayColor = this.element.getAttribute('gray_color') || \"#BDBDBD\";\n this.likedColor = this.element.getAttribute('like_color') || \"#FFCB44\";\n this.color = this.grayColor;\n this.count = this.element.getAttribute('count');\n this.revueId = this.element.getAttribute('revue-id');\n this.location = this.element.getAttribute('location');\n }\n\n mountCallback = () => {\n const likes = sessionStorage.getItem('likes') ? JSON.parse(sessionStorage.getItem('likes')) : [];\n const like = likes.find(item => item.id === this.revueId);\n if (like) {\n this.color = like.like_status === 1 ? this.likedColor : this.grayColor;\n }\n // 如果location是modal,则找到相同revue-id的list的元素,拿到其count,存在list count变了,但是modal的count没变的情况\n if (this.location === 'modal') {\n const listElement = document.querySelector(`spz-custom-revue-like[revue-id=\"${this.revueId}\"] .revue-like-count`);\n if (listElement) {\n this.count = listElement.getAttribute('data-real-count');\n }\n }\n this.doRender_({\n color: this.color,\n count: this.count\n }).then(() => {\n this.addEventListeners_();\n if(this.location === 'list') { // modal数量变更,list同步变更\n document.addEventListener('like-clicked', (e) => {\n if (e.detail.location !== this.location && e.detail.id === this.revueId) {\n this.color = e.detail.like_status === 1 ? this.likedColor : this.grayColor;\n this.count = e.detail.count;\n this.element.querySelector('.revue-like__icon').querySelector('svg').setAttribute('fill', this.color);\n this.element.querySelector('.revue-like__icon').querySelector('svg').querySelector('path').setAttribute('fill', this.color);\n this.element.querySelector('.revue-like-count').innerText = this.count > 99 ? '99+' : this.count < 1 ? '' : this.count;\n this.element.querySelector('.revue-like-count').setAttribute('data-real-count', this.count);\n if(this.count > 0){\n this.element.querySelector('.revue-like-count').classList.remove('hidden');\n }else{\n this.element.querySelector('.revue-like-count').classList.add('hidden');\n }\n }\n });\n }\n });\n }\n\n addEventListeners_ = () => {\n const icon = this.element.querySelector('.revue-like__icon');\n icon.addEventListener('click', (e) => {\n e.stopPropagation();\n const likeStatus = this.color === this.likedColor ? 0 : 1;\n this.color = this.color === this.likedColor ? this.grayColor : this.likedColor;\n this.count = likeStatus === 1 ? parseInt(this.count) + 1 : parseInt(this.count) - 1;\n icon.querySelector('svg').setAttribute('fill', this.color);\n icon.querySelector('svg').querySelector('path').setAttribute('fill', this.color);\n this.element.querySelector('.revue-like-count').innerText = this.count > 99 ? '99+' : this.count < 1 ? '' : this.count;\n this.element.querySelector('.revue-like-count').setAttribute('data-real-count', this.count);\n if(this.count > 0){\n this.element.querySelector('.revue-like-count').classList.remove('hidden');\n }else{\n this.element.querySelector('.revue-like-count').classList.add('hidden');\n }\n this.postLike(likeStatus);\n\n if (this.location === 'modal') {\n const clickedEvent = new CustomEvent('like-clicked', {\n detail: {\n id: this.revueId,\n like_status: likeStatus,\n count: this.count,\n location: this.location\n }\n });\n document.dispatchEvent(clickedEvent);\n }\n\n });\n }\n\n setLikeToStorage = (likeToStore) => {\n if (typeof (Storage) !== 'function') return;\n const likesInStore = sessionStorage.getItem('likes') ? JSON.parse(sessionStorage.getItem('likes')) : [];\n const reviewIndex = likesInStore.findIndex(item => item.id === likeToStore.id);\n\n if (reviewIndex !== -1) {\n likesInStore[reviewIndex].like_status = likeToStore.like_status;\n likesInStore[reviewIndex].count = likeToStore.count;\n } else {\n likesInStore.push(likeToStore);\n }\n sessionStorage.setItem('likes', JSON.stringify(likesInStore));\n }\n\n doRender_ = (data) => {\n return this.templates_\n .findAndRenderTemplate(this.element, data, null)\n .then((el) => {\n const children = this.element.querySelector('*:not(template)');\n children && SPZCore.Dom.removeElement(children);\n this.element.appendChild(el);\n });\n }\n\n postLike = (likeStatus) => {\n fetch('/api/comment/like', {\n method: 'POST',\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n id: this.revueId,\n status: likeStatus\n })\n }).then((res) => {\n if (res.status === 200) {\n this.setLikeToStorage({\n id: this.revueId,\n like_status: likeStatus,\n count: this.count\n });\n }\n });\n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n }\n SPZ.defineElement(TAG, SPZCustomRevueLike)\n\n\n\n\n\n\n\n\n\n\n\n\n\n const TAG = 'spz-custom-revue-media';\n class SPZCustomRevueMedia extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n }\n\n static deferredMount() {\n return false;\n }\n\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n this.imgCover = this.element.getAttribute('img-cover') ?? false;\n this.pc_layout = this.element.getAttribute('pc-layout') ?? '';\n // data-images 格式为 xxxx.png?width=1&height=1,xxxx.png?width=1&height=1\n const images = this.element.getAttribute('data-images').split(',') || [];\n const parsedImages = images.map(image => {\n return this.mediaParse_(image);\n });\n this.images = parsedImages;\n this.isPC = window.innerWidth > 960;\n }\n\n mountCallback = () => {\n this.doRender_({\n images: this.images,\n isPC: this.isPC,\n imgCover: this.imgCover,\n pc_layout: this.pc_layout\n }).then(() => {\n this.addEventListeners_();\n });\n }\n\n addEventListeners_ = () => {\n const images = this.element.querySelectorAll('.revue-image-item');\n images.forEach((image, index) => {\n image.addEventListener('click', () => {\n const carousel = document.querySelector('#revue-image-carousel-render');\n carousel && SPZ.whenApiDefined(carousel).then((api) => {\n \n const width = this.isPC ? 460 : window.innerWidth * 0.9;\n const height = this.isPC ? 630 : 500;\n api.render({ images: this.images, index: index, width: width, height: height });\n });\n });\n });\n }\n\n doRender_ = (data) => {\n return this.templates_\n .findAndRenderTemplate(this.element, data, null)\n .then((el) => {\n const children = this.element.querySelector('*:not(template)');\n children && SPZCore.Dom.removeElement(children);\n this.element.appendChild(el);\n });\n }\n\n mediaParse_ = function (url) {\n var result = {};\n try {\n url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) {\n try {\n result[key] = decodeURIComponent(value);\n } catch (e) {\n result[key] = value;\n }\n });\n result.preview_image = url.split('?')[0];\n } catch (e) {};\n return result;\n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n }\n SPZ.defineElement(TAG, SPZCustomRevueMedia)\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n const TAG = 'spz-custom-revue-sort';\n class SPZCustomRevueSort extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n }\n\n static deferredMount() {\n return false;\n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n this.isPC = window.innerWidth > 960;\n this.width = this.isPC ? `${this.element.getAttribute('width') || 150}px` : '100%';\n this.randomStr = Math.random().toString(36).substr(2);\n this.sectionId = this.element.getAttribute('section-id') || '1760684384410';\n this.prefix = this.element.getAttribute('prefix');\n }\n\n mountCallback = () => {\n const data = {\n width: this.width,\n randomStr: this.randomStr\n };\n this.doRender_(data).then(() => {\n let revueSortListRender = this.isPC ? this.element.querySelector(`#${this.prefix}-revue-sort-list-render-${this.sectionId}`) : this.element.querySelector(`#${this.prefix}-revue-sort-dropdown-render-${this.sectionId}`);\n revueSortListRender && SPZ.whenApiDefined(revueSortListRender).then((api) => {\n api.render(data).then(() => {\n if (this.isPC) {\n this.addEventListenersForPC_();\n } else {\n this.addEventListenersForMobile_();\n }\n });\n });\n });\n }\n\n doRender_ = (data) => {\n return this.templates_\n .findAndRenderTemplate(this.element, data, null)\n .then((el) => {\n const children = this.element.querySelector('*:not(template)');\n children && SPZCore.Dom.removeElement(children);\n this.element.appendChild(el);\n });\n }\n\n addEventListenersForPC_ = () => {\n const revueSelectList = this.element.querySelector('.revue_select_list');\n const revueSelectItem = this.element.querySelectorAll('.revue_select_item');\n\n const revueSelectSortIcon = this.element.querySelector(`#${this.prefix}-revue_select_sort_icon-${this.sectionId}`);\n \n revueSelectItem.forEach(item => {\n item.addEventListener('click', () => {\n const sort = item.getAttribute('data-sort');\n const direction = item.getAttribute('data-direction');\n \n \n this.triggerEvent_('sort', { sort, direction });\n\n this.element.querySelector('.revue_select_label').innerText = item.innerText;\n revueSelectList.classList.remove('revue_select_list_active');\n\n \n const revueChecked = this.element.querySelector(`#${this.prefix}-revue_checked`);\n revueChecked && SPZCore.Dom.removeElement(revueChecked);\n const revueCheckedClone = revueChecked.cloneNode(true);\n item.appendChild(revueCheckedClone);\n\n const pcDropdownEle = document.querySelector(`#${this.prefix}-revue-sort-pc-dropdown-${this.sectionId}`);\n if (!revueSelectSortIcon.classList.contains('up_icon')) {\n return;\n }\n revueSelectSortIcon.classList.remove('up_icon');\n SPZ.whenApiDefined(pcDropdownEle).then((api) => {\n api.close();\n });\n });\n });\n\n \n window.addEventListener('scroll', (e) => {\n if (!revueSelectSortIcon || !revueSelectSortIcon.classList.contains('up_icon')) {\n return;\n }\n revueSelectSortIcon.classList.remove('up_icon');\n SPZ.whenApiDefined(pcDropdownEle).then((api) => {\n api.close();\n });\n });\n }\n\n addEventListenersForMobile_ = () => {\n const revueSortDropdownRender = document.querySelector(`#${this.prefix}-revue-sort-dropdown-render-${this.sectionId}`);\n\n revueSortDropdownRender && SPZ.whenApiDefined(revueSortDropdownRender).then(async (api) => {\n await api.render();\n\n const revueSortDropdownItem = document.querySelectorAll(`#${this.prefix}-revue-sort-dropdown-${this.sectionId} .revue_sort_dropdown_item`);\n\n revueSortDropdownItem.forEach(item => {\n item.addEventListener('click', () => {\n const sort = item.getAttribute('data-sort');\n const direction = item.getAttribute('data-direction');\n revueSortDropdownItem.forEach((_item)=>{_item.classList.remove('selected')})\n item.classList.add('selected');\n // 抛出事件\n this.triggerEvent_('sort', { sort, direction });\n\n // 移除revue_checked元素,复制一个新的到当前选中的元素 \n const revueChecked = document.querySelector(`#${this.prefix}-revue-sort-dropdown-${this.sectionId} #${this.prefix}-revue_checked`);\n revueChecked && SPZCore.Dom.removeElement(revueChecked);\n const revueCheckedClone = revueChecked.cloneNode(true);\n item.appendChild(revueCheckedClone);\n \n const mDropdownEle = document.querySelector(`#${this.prefix}-revue-sort-dropdown-${this.sectionId}`);\n SPZ.whenApiDefined(mDropdownEle).then((api) => {\n api.close();\n }); \n });\n });\n })\n }\n }\n SPZ.defineElement(TAG, SPZCustomRevueSort)\n\n\n\n\n\n const TAG = 'spz-custom-revue-flow';\n class SpzCustomRevueFlow extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n this.sectionId = this.element.getAttribute('section-id');\n this.show_product = '';\n this.with_photo = '';\n this.limit = '';\n this.star_least = '';\n this.layout = ''\n this.wall_pc_num = ''\n this.wall_mobile_num = ''\n this.accent_color = ''\n this.isProductPage = '15' == 1;\n this.isCollectionPage = '15' == 2;\n this.isCartPage = '15' == 13;\n this.lastWidth = window.innerWidth;\n }\n\n static deferredMount() {\n return false;\n }\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n this.setupAction_();\n const url = new URL(window.location.href);\n const preview_theme_id = url.searchParams.get('preview_theme_id');\n if (preview_theme_id) {\n this.preview_theme_id = preview_theme_id;\n }\n this.commentConfig = {};\n this.sort = 'created_at';\n this.direction = 'desc';\n this.isPC = window.innerWidth > (window.breakpoint || 960);\n this.appendList = [];\n this.commentListRes = [];\n this.cardConfig = window.reviewSettings[this.sectionId];\n }\n\n render_ = (data={}) => {\n const {star_least, with_photo, show_product, limit, layout, wall_pc_num, wall_mobile_num, accent_color, fill_strategy, review_insufficient, minimum_comment_num, only_featured, hide_review_section} = this.cardConfig;\n Object.assign(this, {star_least, with_photo, show_product, limit, layout, wall_pc_num, wall_mobile_num, accent_color, fill_strategy, review_insufficient, minimum_comment_num, only_featured, hide_review_section});\n if(this.layout === 'wall'){\n this.with_photo = 1;\n };\n this.params = {\n offset: this.appendList.length || 0,\n sort_by: this.sort,\n sort_direction: this.direction,\n show_reply: 1 || this.commentConfig.show_reply ? 1 : 0,\n with_photo: this.with_photo,\n ...data\n }\n if(this.fill_strategy == 'store'){\n if(this.review_insufficient == 'less_than'){\n this.params.fill_min_threshold = minimum_comment_num;\n }else{\n this.params.fill_min_threshold = 1;\n }\n this.params.fill_strategy = this.fill_strategy;\n }\n const summaryObj = {\n star_least:this.star_least,\n product_ids: this.isProductPage ? '' : this.isCartPage ? '' : '',\n collection_id: this.isCollectionPage ? '' : '',\n filter_type: (this.isProductPage || this.isCartPage) ? 'product' : this.isCollectionPage ? 'collection' : 'store',\n fill_strategy: this.params?.fill_strategy || '',\n only_media: !!this.params.with_photo,\n only_featured:this.only_featured\n }\n if(this.params.fill_min_threshold){\n summaryObj.fill_min_threshold = this.params.fill_min_threshold;\n }\n Promise.all([\n this.fetchSummary_(summaryObj),\n this.fetchCommentConfig_(),\n this.fetchCommentList_(this.params)\n ]).then(response => {\n const [starCountRes,commentConfigRes, commentListRes] = response;\n this.commentConfig = commentConfigRes.data;\n this.commentConfig.show_product = this.show_product;\n this.commentListRes = commentListRes;\n this.starCountRes = starCountRes;\n const showEmpty = this.review_insufficient == 'less_than' && commentListRes?.data?.count < this.minimum_comment_num && this.fill_strategy == 'hide'\n const showEmpty2 = (!commentListRes?.data?.count || commentListRes?.data?.count*1 == 0) && (this.hide_review_section || this.fill_strategy == 'hide')\n if(showEmpty2 || showEmpty){\n this.renderEmpty_();\n return;\n }\n if (this.preview_theme_id) {\n this.fetchThemeConfig_(this.preview_theme_id).then(themeConfig => {\n if (themeConfig?.star_color) {\n this.commentConfig.star_color = themeConfig.star_color;\n }\n if(this.accent_color && this.accent_color != 'null'){\n this.commentConfig.star_color = this.accent_color;\n }\n });\n }\n \n const colums = this.calculateColums_();\n this.renderFlowMain_({ config: this.commentConfig, comment: commentListRes.data, column_count: colums }).then(() => {\n this.renderHeader_({\n starData: this.starCountRes.data,\n listData: this.commentListRes.data,\n star_color: this.commentConfig.star_color,\n comment_avg_star: this.commentListRes.data.avg_star,\n comment_count: this.commentListRes.data?.count,\n isPC: this.isPC,\n });\n this.renderStarCounts_(this.starCountRes.data);\n this.addImpression(`[data-section-id=\"${this.sectionId}\"] .revue_container`);\n this.renderCommentList_({ list: commentListRes.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name },true);\n });\n window.removeEventListener('resize', this.rerenderFn);\n window.addEventListener('resize', this.rerenderFn);\n })\n .catch(error => {\n this.renderEmpty_();\n console.error('error', error);\n });\n }\n mountCallback = () => {\n this.render_()\n }\n \n fetchCommentConfig_ = async () => {\n const response = await fetch('/api/comment-config');\n return response.json();\n }\n\n fetchSummary_ = async (data) => {\n const response = await fetch('/api/v1/comments/summary',{\n method: 'POST',\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(data)\n });\n return response.json();\n }\n\n fetchCommentList_ = async(data) => {\n const response = await fetch(`/api/v1/comments`,{\n method: 'POST',\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify({\n ...data,\n offset: data.offset,\n show_product:!!this.show_product,\n star_least:this.star_least,\n limit:this.limit,\n sort_by:data.sort_by || 'created_at',\n sort_direction: data.sort_direction || 'desc',\n filter_type:(this.isProductPage || this.isCartPage) ? 'product' : this.isCollectionPage ? 'collection' : 'store',\n show_reply: !!data.show_reply,\n only_media: !!data.with_photo,\n product_ids: this.isProductPage ? '' : this.isCartPage ? '' : '',\n collection_id: this.isCollectionPage ? '' : '',\n only_featured: this.only_featured,\n })\n });\n if(response.status != 200){\n return Promise.reject(false);\n }\n return response.json();\n }\n\n fetchThemeConfig_ = async(themeId) => {\n const response = await fetch(`/api/comment/theme-config?theme_id=${themeId}`);\n return response.json();\n }\n\n renderEmpty_ = () => {\n const holderEl = document.getElementById(`revue_no_data_placeholder_${this.sectionId}`);\n const skeleton = document.getElementById(`revue_flow_skeleton-${this.sectionId}`);\n if(skeleton){\n skeleton.style.display = 'none';\n };\n if (window.top !== window.self) {\n SPZ.whenApiDefined(holderEl).then((api) => {\n api.render({}, true);\n });\n }else{\n holderEl.style.display = 'none';\n }\n }\n\n renderFlowMain_ = async (data) => {\n const mainEle = document.querySelector(`#revue_flow_render-${this.sectionId}`);\n if (mainEle) {\n const api = await SPZ.whenApiDefined(mainEle);\n return api.render({ ...data },true);\n }\n }\n\n calculateColums_ = () => {\n let colums = 1;\n this.isPC = window.innerWidth > (window.breakpoint || 960);\n if (this.layout === 'grid') {\n colums = this.isPC ? 4 : 2;\n \n } else {\n colums = this.isPC ? 2 : 1;\n }\n if(this.layout == 'wall'){\n colums = this.isPC ? (this.wall_pc_num || 4) : (this.wall_mobile_num || 2);\n }\n \n return colums\n }\n\n rerenderFn = (list) => {\n try{\n if(!this?.commentListRes?.data) return;\n const currentWidth = window.innerWidth;\n if (currentWidth == this.lastWidth ) {\n return\n } else {\n this.lastWidth = currentWidth;\n }\n const throttleHandle = SPZCore.Types.throttle(window,()=>{\n let colums = this.calculateColums_();\n this.renderFlowMain_({ config: this.commentConfig, comment: this.commentListRes.data, column_count: colums }).then(() => {\n this.renderHeader_({\n starData: this.starCountRes.data,\n listData: this.commentListRes.data,\n star_color: this.commentConfig.star_color,\n comment_avg_star: this.commentListRes.data.avg_star,\n comment_count: this.commentListRes.data?.count,\n isPC: this.isPC,\n });\n this.renderStarCounts_(this.starCountRes.data);\n this.renderCommentList_({ list: this.commentListRes.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }, true);\n }); \n },200)\n throttleHandle()\n }catch(e){\n console.log(e);\n }\n }\n\n renderCommentList_ = (data,redo=false) => {\n if(this.accent_color && this.accent_color != 'null'){\n this.commentConfig.star_color = this.accent_color;\n }\n const listEle = document.querySelector(`#revue_flow_list-${this.sectionId}`);\n if (listEle) {\n const current_list = data.list.list.map((item, index) => {\n return {\n ...item,\n config: this.commentConfig,\n index: data.sorted ? index : this.appendList.length + index,\n shop_name: window.SHOPLAZZA.shop.shop_name\n }\n });\n if (data.sorted) {\n this.appendList = current_list;\n SPZ.whenApiDefined(listEle).then((api) => {\n api.listRender({ count: data.list.count, list: current_list },true);\n });\n } else {\n let obj = {};\n this.appendList = this.appendList.concat(current_list).reduce((cur,next) => {\n obj[next.id] ? \"\" : obj[next.id] = true && cur.push(next);\n return cur;\n },[]);\n SPZ.whenApiDefined(listEle).then((api) => {\n api.listRender({ count: data.list.count, list: current_list },redo);\n });\n }\n };\n this.renderLoadMoreBtn(data.list);\n }\n\n mediaParse_ = function (url) {\n var result = {};\n try {\n url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) {\n try {\n result[key] = decodeURIComponent(value);\n } catch (e) {\n result[key] = value;\n }\n });\n result.src = url.split('?')[0];\n } catch (e) {};\n return result;\n }\n\n impFunc = function (selector, cb) {\n // 添加自动曝光\n const el = document.querySelector(selector);\n const onImpress = () => {\n cb();\n };\n // 元素未曝光时添加曝光事件监听,已曝光则可以立刻触发处理器\n if (el && !el.getAttribute('imprsd')) {\n el.addEventListener('impress', onImpress);\n } else if (el) {\n onImpress();\n }\n };\n\n addImpression = function (selector) {\n this.impFunc(selector, () => {\n window.sa && window.sa.track('plugin_reviews_masonry_pv', {\n layout_type: this.layout,\n level_type: this.star_least,\n show_number: this.limit,\n plugin_timestamp: new Date().valueOf().toString(),\n reviews_num: this.appendList.length\n });\n });\n };\n\n addModalImpression = function (selector, params) {\n this.impFunc(selector, () => {\n window.sa && window.sa.track('plugin_reviews_modal_pv', {\n ...params,\n plugin_timestamp: new Date().valueOf().toString(),\n });\n });\n };\n\n \n setupAction_ = () => {\n this.registerAction('refresh', async(invocation) => {\n this.render_({\n ...this.params,\n offset: 0,\n sort_by: 'created_at',\n sort_direction: 'desc',\n show_reply: true,\n with_photo: false,\n })\n });\n this.registerAction('renderTypeChangeList', async(invocation) => {\n const {type,direction } = invocation.args.data;\n this.with_photo = type === 'with_photo';\n this.direction = direction;\n this.params = {\n ...this.params,\n offset: 0,\n sort_by: this.sort,\n sort_direction: this.direction,\n show_reply: 1,\n with_photo: this.with_photo\n };\n this.fetchCommentList_(this.params).then(response => {\n this.renderCommentList_({ sorted: true, list: response.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }, true);\n });\n })\n\n this.registerAction('renderSortedList', async(invocation) => {\n const {sort, direction} = invocation.args.data;\n this.sort = sort;\n this.direction = direction;\n const panelId = this.panelId;\n this.params = {\n ...this.params,\n offset: 0,\n sort_by: this.sort,\n sort_direction: this.direction,\n show_reply: 1 ,\n with_photo: this.with_photo\n }\n this.fetchCommentList_(this.params).then(response => {\n this.renderCommentList_({ sorted: true, list: response.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name }, true);\n });\n });\n this.registerAction('renderProductCommentModal', async(invocation) => {\n const id = invocation.args.id;\n const current = this.appendList?.find(_data => _data.id == id);\n const modalEle = document.querySelector(`#revueDetailModal-${this.sectionId}`);\n \n \n const imgArr = current.img.map(image => {\n const width = this.getUrlKey('width', image);\n const height = this.getUrlKey('height', image);\n return {\n width,\n height,\n rate: (height/width).toFixed(2)*100,\n url: image\n };\n });\n if (modalEle) {\n SPZ.whenApiDefined(modalEle).then((api) => {\n api.renderModalFn({\n data: {\n ...current,\n img: imgArr\n },\n commentConfig: this.commentConfig,\n layout: this.layout,\n level_type: this.star_least,\n show_number: this.limit\n });\n });\n }\n });\n this.registerAction('loadMore', async(invocation) => {\n this.params = {\n ...this.params,\n offset: this.appendList.length,\n sort_by: this.sort,\n sort_direction: this.direction,\n show_reply: 1 || this.commentConfig.show_reply ? 1 : 0,\n with_photo: this.with_photo\n }\n this.fetchCommentList_(this.params).then(response => {\n this.renderCommentList_({ list: response.data, config: this.commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name });\n });\n });\n }\n\n\n getUrlKey = (name, url) => {\n return (\n decodeURIComponent(\n (new RegExp(\"[?|&]\" + name + \"=\" + \"([^&;]+?)(&|#|;|$)\").exec(\n url\n ) || [, \"\"])[1].replace(/\\+/g, \"%20\")\n ) || null\n );\n }\n\n renderHeader_ = (data) => {\n if(this.accent_color && this.accent_color != 'null'){\n data.star_color = this.commentConfig.star_color = this.accent_color;\n }\n const headerEle = document.querySelector(`#review-revue-header-${this.sectionId}`);\n if (headerEle) {\n SPZ.whenApiDefined(headerEle).then(async (api) => {\n api.render(data);\n });\n }\n }\n\n renderStarCounts_ = (data) => {\n const summaryEle = document.querySelector(`#revue-summary-${this.sectionId}`);\n if (summaryEle) {\n SPZ.whenApiDefined(summaryEle).then((api) => {\n api.render({\n ...data,\n star_color: this.commentConfig.star_color\n });\n });\n }\n }\n\n renderLoadMoreBtn = (data) => {\n const loadEle = document.querySelector(`#revue_flow_load_more_render-${this.sectionId}`);\n if (loadEle) {\n SPZ.whenApiDefined(loadEle).then((api) => {\n api.render({ comment: data }, true);\n });\n }\n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n unmountCallback(){\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n }\n SPZ.defineElement(TAG, SpzCustomRevueFlow)\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n \n \n\n\n\n\n\n\n\n\n\n const TAG = 'spz-custom-revue-modal';\n class SPZCustomRevueModal extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n this.renderedId = '';\n this.closeCB = null;\n this.sectionId = this.element.getAttribute('section-id');\n }\n\n static deferredMount() {\n return false;\n }\n\n buildCallback = () => {\n this.setupAction_();\n }\n\n mountCallback = () => {\n \n }\n \n setupAction_ = () => {\n this.registerAction('renderModal', this.renderModalFn)\n this.registerAction('closeFn',() => {\n this?.closeCB?.()\n })\n }\n\n\n mediaParse_ = function (url) {\n var result = {};\n try {\n url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) {\n try {\n result[key] = decodeURIComponent(value);\n } catch (e) {\n result[key] = value;\n }\n });\n result.src = url.split('?')[0];\n } catch (e) {};\n return result;\n }\n\n\n impFunc = function (selector, cb) {\n // 添加自动曝光\n const el = document.querySelector(selector);\n const onImpress = () => {\n cb();\n };\n // 元素未曝光时添加曝光事件监听,已曝光则可以立刻触发处理器\n if (el && !el.getAttribute('imprsd')) {\n el.addEventListener('impress', onImpress);\n } else if (el) {\n onImpress();\n }\n };\n \n addModalImpression = function (selector, params) {\n this.impFunc(selector, () => {\n window.sa && window.sa.track('plugin_reviews_modal_pv', {\n ...params,\n plugin_timestamp: new Date().valueOf().toString(),\n });\n });\n };\n\n renderModalFn(receivedData){\n if(!receivedData) return;\n const {\n data:current,\n commentConfig,\n layout,\n level_type,\n show_number,\n closeCB,\n mimic_mobile_style,\n props\n } = receivedData;\n try{\n if(closeCB){\n this.closeCB = () => {\n closeCB()\n };\n }\n }catch(e){\n console.log(e);\n };\n const commentModalEl = document.querySelector(`#revue-product-comment-modal-${this.sectionId}`);\n const modalRenderEl = document.querySelector(`#revue_flow_modal_render-${this.sectionId}`);\n if (!!mimic_mobile_style) {\n if (commentModalEl) {\n commentModalEl.classList.add('mobile-wrap');\n }\n if (modalRenderEl) {\n modalRenderEl.classList.add('w-h-full-h5');\n }\n };\n const parsedImages = current?.img?.map(image => {\n return this.mediaParse_(`${image.url}?width=${image.width}&height=${image.height}`);\n });\n\n const modalEle = document.querySelector(`#revue_flow_modal_render-${this.sectionId}`);\n if (modalEle) {\n SPZ.whenApiDefined(modalEle).then((api) => {\n api.render({ ...current, img: parsedImages, config: commentConfig, shop_name: window.SHOPLAZZA.shop.shop_name, mimic_mobile_style }, true).then(() => {\n this.addModalImpression('.revue_modal_container', {\n id: current.id,\n username: current.username,\n content: current.content,\n star: current.star,\n is_verified: current.is_verified,\n is_featured: current.is_featured,\n anonymous: current.anonymous,\n iso_code_3: current.iso_code_3,\n like_count: current.like,\n layout_type: layout,\n level_type: level_type,\n show_number: show_number,\n });\n }).then(()=>{\n this.renderedId = current.id\n });\n });\n }\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n }\n SPZ.defineElement('spz-custom-revue-modal', SPZCustomRevueModal)\n\n\n\n\n\n\n\n const TAG = 'spz-custom-revue-video';\n class SPZCustomRevueVideo extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n }\n\n static deferredMount() {\n return false;\n }\n\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n // data-images 格式为 xxxx.png?width=1&height=1,xxxx.png?width=1&height=1\n const images = this.element.getAttribute('data-images').split(',') || [];\n const parsedImages = images.map(image => {\n return this.mediaParse_(image);\n });\n this.images = parsedImages;\n this.isPC = window.innerWidth > 960;\n }\n\n loadVideo = () => {\n this.doRender_({\n images: this.images,\n isPC: this.isPC\n }).then(()=>{\n this.triggerEvent_('connected', {});\n })\n }\n mountCallback = () => {\n this.loadVideo();\n \n this.registerAction('loadVideo', async(invocation) => {\n this.loadVideo();\n })\n }\n\n doRender_ = (data) => {\n return this.templates_\n .findAndRenderTemplate(this.element, data, null)\n .then((el) => {\n const children = this.element.querySelector('*:not(template)');\n children && SPZCore.Dom.removeElement(children);\n this.element.appendChild(el);\n });\n }\n\n mediaParse_ = function (url) {\n var result = {};\n try {\n url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) {\n try {\n result[key] = decodeURIComponent(value);\n } catch (e) {\n result[key] = value;\n }\n });\n result.preview_image = url.split('?')[0];\n } catch (e) {};\n return result;\n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n }\n SPZ.defineElement(TAG, SPZCustomRevueVideo)\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n const TAG = 'spz-custom-revue-header';\n\n class SPZCustomRevueHeader extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n this.showCount = this.element.getAttribute('show-count');\n }\n\n static deferredMount() {\n return false;\n }\n \n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n\n buildCallback() {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n this.showCount = this.element.getAttribute('show-count');\n this.showSummary = this.element.getAttribute('show-summary');\n this.showWriteReview = this.element.getAttribute('show-write-review');\n this.showType = this.element.getAttribute('show-type') ;\n this.showSort = this.element.getAttribute('show-sort') ;\n this.sectionId = this.element.getAttribute('section-id');\n this.viewall = this.element.getAttribute('viewall') ?? false;\n this.prefix = this.element.getAttribute('prefix');\n }\n\n mountCallback() {\n \n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n render(data) {\n const ndata = {\n ...data,\n showCount: this.showCount,\n showSummary: this.showSummary,\n showWriteReview: this.showWriteReview,\n showType: this.showType,\n showSort: this.showSort,\n }\n if(this.viewall == 'review'){\n ndata.viewall = false\n }\n return this.templates_\n .findAndRenderTemplate(this.element, ndata, null, true)\n .then(({el}) => {\n const children = this.element.querySelector('*:not(template)');\n children && SPZCore.Dom.removeElement(children);\n this.element.appendChild(el);\n }).then(() => {\n if(data && Object.keys(data).length > 0) {\n this.updateRender(data);\n this.setupSummaryContainerEffects_(data);\n }\n });\n }\n \n updateRender(data) {\n this.renderStarCounts_(data);\n this.renderTypeSelect(data);\n this.renderSortSelect(data);\n }\n\n renderStarCounts_(data) {\n const renderData = {\n ...data.starData,\n star_color: data.star_color,\n isPC: data.isPC,\n }\n const summaryEle = data.isPC ? this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header_pc`) : this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header`);\n if(summaryEle) {\n SPZ.whenApiDefined(summaryEle).then((api) => {\n api.render(renderData);\n });\n }\n }\n\n renderTypeSelect(data) {\n const typeSelect = this.element.querySelector(`#${this.prefix}-revue-header-type-${this.sectionId}`);\n if(typeSelect) {\n SPZ.whenApiDefined(typeSelect).then((api) => {\n api.render(data);\n api.registerAction('headerType_', (invocation) => {\n this.triggerEvent_('headerType', invocation.args.data);\n });\n });\n }\n }\n\n renderSortSelect(data) {\n const suffix = data.suffix || this.sectionId;\n const sortSelect = this.element.querySelector(`#${this.prefix}-revue-header-sort-${suffix}`);\n if(sortSelect) {\n SPZ.whenApiDefined(sortSelect).then((api) => {\n api.registerAction('headerSort_', (invocation) => {\n this.triggerEvent_('headerSort', invocation.args.data);\n });\n });\n }\n }\n\n setupSummaryContainerEffects_(data) {\n if(data.isPC) {\n this.setupSummaryContainerHover_();\n } else {\n this.setupSummaryContainerTap_();\n }\n }\n\n setupSummaryContainerHover_() {\n const summaryContainer = this.element.querySelector(`#revue-header-summary-container-${this.sectionId}`);\n const summaryEle = this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header_pc`);\n if (!summaryContainer || !summaryEle) return;\n\n let isHovering = false;\n\n // 鼠标移入容器时显示summary\n SPZUtils.Event.listen(summaryContainer, 'mouseenter', () => {\n isHovering = true;\n summaryEle.removeAttribute('hidden');\n const selectIcon = summaryContainer.querySelector(`#revue-header-summary-icon-${this.sectionId}`);\n if(selectIcon) {\n selectIcon.classList.add('up-icon');\n }\n });\n // 鼠标移入summary时也保持显示\n SPZUtils.Event.listen(summaryEle, 'mouseenter', () => {\n isHovering = true;\n });\n \n // 鼠标移出容器时,检查是否还在summary上\n SPZUtils.Event.listen(summaryContainer, 'mouseleave', () => {\n isHovering = false;\n setTimeout(() => {\n if (!isHovering) {\n summaryEle.setAttribute('hidden', 'true');\n const selectIcon = summaryContainer.querySelector(`#revue-header-summary-icon-${this.sectionId}`);\n if(selectIcon) {\n selectIcon.classList.remove('up-icon');\n }\n }\n }, 50);\n });\n // 鼠标移出summary时,检查是否还在容器上\n SPZUtils.Event.listen(summaryEle, 'mouseleave', () => {\n isHovering = false;\n setTimeout(() => {\n if (!isHovering) {\n summaryEle.setAttribute('hidden', 'true');\n const selectIcon = summaryEle.querySelector(`#revue-header-summary-icon-${this.sectionId}`);\n if(selectIcon) {\n selectIcon.classList.remove('up-icon');\n }\n }\n }, 50);\n });\n }\n\n setupSummaryContainerTap_() {\n const selectIcon = this.element.querySelector(`#revue-header-summary-icon-${this.sectionId}`);\n const summaryEle = this.element.querySelector(`#${this.prefix}-revue-summary-${this.sectionId}_header`);\n if(!summaryEle) return;\n\n let isTapped = false; // 是否显示summary\n SPZ.whenApiDefined(summaryEle).then((api) => {\n api.registerAction('display', () => {\n if(isTapped) {\n isTapped = false;\n summaryEle.removeAttribute('hidden');\n selectIcon.classList.add('up-icon');\n } else {\n isTapped = true;\n summaryEle.setAttribute('hidden', 'true');\n selectIcon.classList.remove('up-icon');\n }\n });\n });\n }\n }\n\n SPZ.defineElement(TAG, SPZCustomRevueHeader);\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n const TAG = 'spz-custom-revue-type';\n class SPZCustomRevueType extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n }\n\n static deferredMount() {\n return false;\n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n this.isPC = window.innerWidth > 960;\n this.width = this.isPC ? `${this.element.getAttribute('width') || 150}px` : '100%';\n this.randomStr = Math.random().toString(36).substr(2);\n this.sectionId = this.element.getAttribute('section-id') || '1760684384410';\n this.prefix = this.element.getAttribute('prefix');\n }\n\n mountCallback = () => {\n }\n\n render = (data) => {\n const renderData = {\n ...data,\n width: this.width,\n randomStr: this.randomStr\n };\n return this.templates_\n .findAndRenderTemplate(this.element, renderData, null)\n .then((el) => {\n const children = this.element.querySelector('*:not(template)');\n children && SPZCore.Dom.removeElement(children);\n this.element.appendChild(el);\n }).then(() => {\n let revueTypeListRender = this.isPC ? this.element.querySelector(`#${this.prefix}-revue-type-list-render-${this.sectionId}`) : this.element.querySelector(`#${this.prefix}-revue-type-dropdown-render-${this.sectionId}`);\n revueTypeListRender && SPZ.whenApiDefined(revueTypeListRender).then((api) => {\n api.render(renderData).then(() => {\n if (this.isPC) {\n this.addEventListenersForPC_();\n } else {\n this.addEventListenersForMobile_();\n }\n });\n });\n });\n }\n\n\n addEventListenersForPC_ = () => {\n const revueSelectList = this.element.querySelector('.revue_select_list');\n const revueSelectItem = this.element.querySelectorAll('.revue_select_item');\n\n const revueSelectTypeIcon = this.element.querySelector(`#${this.prefix}-revue_select_type_icon-${this.sectionId}`);\n \n revueSelectItem.forEach(item => {\n item.addEventListener('click', () => {\n const type = item.getAttribute('data-type');\n const direction = item.getAttribute('data-direction');\n\n \n this.triggerEvent_('type', { type, direction });\n\n this.element.querySelector('.revue_select_label').innerText = item.innerText;\n revueSelectList.classList.remove('revue_select_list_active');\n\n \n const revueChecked = this.element.querySelector(`#${this.prefix}-revue_checked`);\n revueChecked && SPZCore.Dom.removeElement(revueChecked);\n const revueCheckedClone = revueChecked.cloneNode(true);\n item.appendChild(revueCheckedClone);\n\n if (!revueSelectTypeIcon.classList.contains('up_icon')) {\n return;\n }\n\n const pcDropdownEle = this.element.querySelector(`#${this.prefix}-revue-type-pc-dropdown-${this.sectionId}`);\n revueSelectTypeIcon.classList.remove('up_icon');\n SPZ.whenApiDefined(pcDropdownEle).then((api) => {\n api.close();\n });\n });\n });\n \n window.addEventListener('scroll', (e) => {\n if (!revueSelectTypeIcon.classList.contains('up_icon')) {\n return;\n }\n revueSelectTypeIcon.classList.remove('up_icon');\n SPZ.whenApiDefined(pcDropdownEle).then((api) => {\n api.close();\n });\n });\n }\n\n addEventListenersForMobile_ = () => {\n const revueTypeDropdownItem = this.element.querySelectorAll(`#${this.prefix}-revue-type-dropdown-${this.sectionId} .revue_type_dropdown_item`);\n\n revueTypeDropdownItem.forEach(item => {\n item.addEventListener('click', () => {\n const type = item.getAttribute('data-type');\n const direction = item.getAttribute('data-direction');\n revueTypeDropdownItem.forEach((_item)=>{_item.classList.remove('selected')})\n item.classList.add('selected');\n // 抛出事件\n this.triggerEvent_('type', { type, direction });\n\n // 移除revue_checked元素,复制一个新的到当前选中的元素 \n const revueChecked = this.element.querySelector(`#${this.prefix}-revue-type-dropdown-${this.sectionId} #${this.prefix}-revue_checked`);\n revueChecked && SPZCore.Dom.removeElement(revueChecked);\n const revueCheckedClone = revueChecked.cloneNode(true);\n item.appendChild(revueCheckedClone);\n\n const mDropdownEle = this.element.querySelector(`#${this.prefix}-revue-type-dropdown-${this.sectionId}`);\n SPZ.whenApiDefined(mDropdownEle).then((api) => {\n api.close();\n });\n });\n });\n }\n }\n SPZ.defineElement(TAG, SPZCustomRevueType)\n\n\n\n\n \n\n \n\n\n const TAG = 'spz-custom-revue-progress';\n class SPZCustomRevueProgress extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n }\n\n static deferredMount() {\n return false;\n }\n\n buildCallback = () => {\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n this.templates_ = SPZServices.templatesForDoc(this.element);\n this.xhr_ = SPZServices.xhrFor(this.win);\n this.isPC = window.innerWidth > (window.breakpoint || 960);\n this.height = '6px';\n this.color = this.element.getAttribute('color') || '#000000';\n this.show_percentage = 'false';\n this.show_percentage_num = 100;\n this.count = this.element.getAttribute('count');\n this.total = this.element.getAttribute('total');\n\n }\n\n mountCallback = () => {\n this.doRender_({\n count: Number(this.count),\n total: Number(this.total),\n height: this.height,\n color: this.color,\n show_percentage: this.show_percentage,\n show_percentage_num: this.show_percentage_num\n }).then(() => {\n });\n }\n\n doRender_ = (data) => {\n return this.templates_\n .findAndRenderTemplate(this.element, data, null)\n .then((el) => {\n const children = this.element.querySelector('*:not(template)');\n children && SPZCore.Dom.removeElement(children);\n this.element.appendChild(el);\n });\n }\n\n triggerEvent_(name, data) {\n const event = SPZUtils.Event.create(this.win, `${ TAG }.${ name }`, data || {});\n this.action_.trigger(this.element, name, event);\n }\n\n isLayoutSupported(layout) {\n return layout == SPZCore.Layout.CONTAINER;\n }\n }\n SPZ.defineElement(TAG, SPZCustomRevueProgress)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n \n \n\n\n\n\n \n\n\n\n\n\n\n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n \n \n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n \n
\n \n \n \n
\n\n\n \n
Reviews (458)
All
All(458)
With Photos(94)
Newest
Newest
Most liked
Highest ratings
Lowest ratings
4
Love this and the others I've purchased
Teair H.
Sep 03, 2025
I messed up my order and picked the wrong thing and they more than quickly adjusted and fixed everything for me! Can't wait yp purchase more 😍🥰
Jade J.
Sep 02, 2025
9
I love this sweatshirt. Feels great to support you all especially as a Black woman. 💛
Jerrica B.
Sep 02, 2025
10
The quality of the sweatshirt was made well. I love the color and the message that it speaks. It resonates with me because it is important to me to remind others.
Patricia F.
Sep 02, 2025
11
I enjoy the stares of people reading my sweatshirt. it’s empowering to walk confidently with such bold statements
Shavaughn M.
Sep 02, 2025
Great sweatshirt. Great message and design. Really comfortable.
Kemila
Sep 01, 2025
Love this sweatshirt! Kicked off Womens History month rocking this.
Michelle V.
Sep 01, 2025
6
Fast and efficient
Robilyn H.
Sep 01, 2025
View more
\n\n\n\n
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n \n \n\n \n \n \n \n \n
\n
\n
\n \n
\n \n

\n COMPANY INFO\n

\n \n \n \n \n
\n\n \n \n \n
\n

\n COMPANY INFO\n \n

\n
\n \n
\n
\n
\n \n
\n \n
\n \n

\n HELP & SUPPORT\n

\n \n \n \n \n
\n\n \n \n \n
\n

\n HELP & SUPPORT\n \n

\n
\n \n
\n
\n
\n \n
\n \n
\n \n

\n CUSTOMER CARE\n

\n \n \n \n \n
\n\n \n \n \n
\n

\n CUSTOMER CARE\n \n

\n
\n \n
\n
\n
\n \n
\n \n
\n \n

\n Contact us\n

\n \n \n
    \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
  • \n \n \n \n\n\n info@silversal.com\n \n
  • \n\n\n\n\n\n\n\n\n\n\n\n\n\n \n
\n \n
\n\n \n \n \n
\n

\n Contact us\n \n

\n
\n
    \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
  • \n \n \n \n\n\n info@silversal.com\n \n
  • \n\n\n\n\n\n\n\n\n\n\n\n\n\n \n
\n
\n
\n
\n \n
\n
\n
\n \n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n \n \n \n \n \n\n \n\n \n\n \n \n\n
\n
\n \n \n
\n
    \n \n \n \n
  • \n American Express\n \n
  • \n \n \n \n \n
  • \n Apple Pay\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
  • \n \n \n \n \n
  • \n Mastercard\n \n \n \n \n \n
  • \n \n \n \n \n
  • \n PayPal\n \n \n \n \n \n \n \n \n
  • \n \n \n \n \n
  • \n Visa\n \n \n \n
  • \n \n \n \n \n
  • \n Discover\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
  • \n \n \n \n \n
  • \n JCB\n \n \n \n \n \n \n \n \n \n \n
  • \n \n \n \n \n
  • \n Maestro\n \n \n \n \n \n
  • \n \n \n \n \n
  • \n Diners Club\n \n \n \n
  • \n \n \n \n \n
  • \n \n
  • \n \n \n \n \n
  • \n Google Pay\n \n \n \n \n \n \n \n \n
  • \n \n \n
\n
\n \n\n \n \n
\n \n \n \n
\n \n\n \n ©  Silversal\n \n \n \n \n
\n
\n\n\n
\n\n\n
\n
\n (function() {\n const STATUS = {\n LOADING: 'loading',\n FINISH: 'finish'\n };\n\n const RESULT = {\n EMPTY: 'data-empty'\n };\n\n class SPZCustomCartSku extends SPZ.BaseElement {\n renderData = [];\n /**\n * add to cart reselect item, and delete process:\n * 1. record reselect id before show reselect modal\n * 2. add to cart success, mark `needDeleteReselectRecord` to true\n * 3. close reselect modal / drawer\n * 4. spz-cart re-render, triggered mounted event\n * 5. call refresh\n */\n // mark delete reselect id\n recordReselectId = null;\n // mark should delete reselect record after spz-cart mounted event\n needDeleteReselectRecord = false;\n\n refreshLock = false;\n addToCartSuccess = false;\n // cache paused refresh data\n refreshDataCache = [];\n // cache similar products data, for manual add to cart\n similarData = [];\n\n constructor(element) {\n super(element);\n\n this.xhr_ = SPZServices.xhrFor(this.win);\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n }\n\n setupAction_() {\n this.registerAction('refresh', (invocation) => {\n const data = invocation && invocation.args && invocation.args.data || {};\n this.refresh(data);\n });\n\n this.registerAction('deleteReselect', SPZCore.Types.debounce(\n this.win,\n (invocation) => {\n this.deleteReselect(invocation?.args?.id);\n },\n 200\n ));\n\n this.registerAction('deleteInvalid', SPZCore.Types.debounce(\n this.win,\n (invocation) => {\n this.deleteInvalid(invocation?.args?.id);\n },\n 200\n ));\n\n this.registerAction('setReselectRecordId', (invocation) => {\n this.setReselectRecordId(invocation?.args?.id);\n });\n\n this.registerAction('clearNeedReselectRecord', (invocation) => {\n this.clearNeedReselectRecord();\n });\n\n this.registerAction('registerDeleteReselectTask', (invocation) => {\n this.registerDeleteReselectTask(invocation?.args?.data);\n });\n\n this.registerAction('lockRefresh', (invocation) => {\n this.lockRefresh();\n });\n\n this.registerAction('releaseRefreshLock', (invocation) => {\n this.releaseRefreshLock();\n });\n\n this.registerAction('manualAddToCart', (invocation) => {\n this.manualAddToCart(invocation?.args?.id);\n });\n\n this.registerAction('syncSimilarData', (invocation) => {\n this.syncSimilarData(invocation?.args?.data);\n });\n\n // DEBUG\n this.registerAction('log', (invocation) => {\n const data = invocation && invocation.args && invocation.args.data || {};\n console.log('log', invocation);\n });\n }\n\n buildCallback() {\n this.setupAction_();\n }\n\n isLayoutSupported(layout) {\n return true;\n }\n\n /**\n * 数据去重\n * @param {Array} data\n */\n uniq_(data) {\n const result = [];\n for(let i = 0; i < data.length; i++) {\n if(!result.includes(data[i])) {\n result.push(data[i]);\n }\n }\n return result;\n }\n\n checkRefreshLock() {\n if (this.refreshLock) {\n return false;\n }\n return true;\n }\n\n setLoading(isLoading) {\n SPZCore.Dom.toggleAttribute(this.element, STATUS.LOADING, isLoading);\n SPZCore.Dom.toggleAttribute(this.element, STATUS.FINISH, !isLoading);\n }\n\n setDataResult(data) {\n const isDataEmpty = !data.reselectSkus.length && !data.invalidSkus.length && !data.line_items.length;\n\n SPZCore.Dom.toggleAttribute(this.element, RESULT.EMPTY, isDataEmpty);\n }\n\n // 存在多次请求顺序问题\n async refresh(_data) {\n // 接口失败时返回数据不为对象\n if ((typeof _data !== 'object' || !Array.isArray(_data.line_items))) {\n const newData = {\n line_items: [],\n reselectSkus: [],\n invalidSkus: [],\n displayInvalidSkus: []\n };\n this.trigger_('refreshError', newData);\n this.setLoading(false);\n return;\n }\n if (!this.checkRefreshLock()) {\n this.setRefreshCache(_data);\n return;\n }\n\n this.setLoading(true);\n\n let data = _data;\n if (this.needDeleteReselectRecord && this.recordReselectId) {\n let hasError = false;\n try {\n data = await this.deleteReselectRecordBeforeRefresh(_data);\n } catch (e) {\n hasError = true;\n console.error(e);\n const newData = {\n ...data,\n reselectSkus: [],\n invalidSkus: [],\n displayInvalidSkus: []\n };\n this.trigger_('refreshError', newData);\n this.setLoading(false);\n this.setDataResult(newData);\n return;\n }\n this.needDeleteReselectRecord = false;\n\n if (hasError) {\n return;\n }\n }\n\n // 获取失效商品\n const invisibleItems = data.ineffectives;\n\n // 获取失效商品内仅售罄商品的sku\n const soldOutItems = invisibleItems.filter(item => item.reason === \"line_item_sold_out\");\n // 通过失效 sku 获取 spu, 注意去重\n const soldOutSpuIds = this.uniq_(soldOutItems.map(item => item.product_id));\n const querySpuIds = soldOutSpuIds.map(spuId => `ids[]=${spuId}`).join('&');\n\n if (!soldOutSpuIds.length) {\n const newData = {\n ...data,\n reselectSkus: [],\n invalidSkus: [],\n displayInvalidSkus: []\n };\n this.trigger_('refreshSuccess', newData);\n this.renderData = newData;\n this.setLoading(false);\n this.setDataResult(newData);\n return;\n }\n\n // 请求 spu 下其他 sku 库存\n this.xhr_.fetchJson(`/api/product/list?limit=${soldOutSpuIds.length}&${querySpuIds}`)\n .then(res => {\n const reselectSkus = [];\n const invalidSkus = [];\n // spu 维度展示\n const displayInvalidSkus = [];\n\n res.data.list.forEach(product => {\n // spu 匹配, 存在多 sku 情况\n const soldOutSkus = soldOutItems.filter(item => item.product_id === product.id);\n\n if (!soldOutSkus.length) {\n return;\n }\n\n // 通过失效 sku 获取 spu 下其他 sku 库存, 存在其他可用库存时标记为 \"Reselect\" 按钮可用\n //const allowReselect = product.variants\n // .filter(variant => variant.option1 === soldOutProduct.variant.option1 && variant.option2 === soldOutProduct.variant.option2 && variant.option3 === soldOutProduct.variant.option3)\n // .some(variant => variant.available);\n\n // 查询售罄 sku 对应商品是否可加购, 标记为 Reselect 可用项\n if (product.available) {\n reselectSkus.push(...soldOutSkus);\n // 若商品不可用(全部 sku 售罄), 归纳到失效商品列表\n } else {\n invalidSkus.push(...soldOutSkus);\n // spu 维度, 仅添加一项 sku\n displayInvalidSkus.push(soldOutSkus[0]);\n }\n });\n\n const newData = {\n ...data,\n reselectSkus,\n invalidSkus,\n displayInvalidSkus\n };\n this.trigger_('refreshSuccess', newData);\n this.renderData = newData;\n this.setLoading(false);\n this.setDataResult(newData);\n })\n .catch(err => {\n this.setLoading(false);\n console.error(err);\n this.trigger_('refreshError', data);\n }).finally(() => {\n \n });\n }\n\n async deleteReselect(id, ignoreEmit) {\n if (!id) {\n return;\n }\n\n const targetSku = this.renderData.reselectSkus.find(item => item.id === id);\n\n try {\n const res = await this.xhr_.fetchJson(`/api/cart/${targetSku.variant_id}`, {\n method: 'DELETE',\n body: {\n id: targetSku.id,\n product_id: targetSku.product_id,\n variant_id: targetSku.variant_id,\n }\n });\n\n const newData = {\n ...this.renderData,\n reselectSkus: this.renderData.reselectSkus.filter(item => item.id !== id),\n };\n\n this.renderData = newData;\n\n !ignoreEmit && this.trigger_('deleteSuccess', newData);\n } catch (err) {\n console.error(err);\n !ignoreEmit && this.trigger_('deleteError', err);\n }\n }\n\n deleteInvalid(id) {\n if (!id) {\n return;\n }\n\n const targetSku = this.renderData.invalidSkus.find(item => item.id === id);\n\n this.xhr_.fetchJson(`/api/cart/${targetSku.variant_id}`, {\n method: 'DELETE',\n body: {\n id: targetSku.id,\n product_id: targetSku.product_id,\n variant_id: targetSku.variant_id,\n }\n })\n .then(res => {\n const newData = {\n ...this.renderData,\n invalidSkus: this.renderData.invalidSkus.filter(item => item.id !== id),\n displayInvalidSkus: this.renderData.displayInvalidSkus.filter(item => item.id !== id),\n };\n\n this.trigger_('deleteSuccess', newData);\n\n this.renderData = newData;\n })\n .catch(err => {\n console.error(err);\n this.trigger_('deleteError', err);\n });\n }\n\n // record reselect sku id before show reselect modal\n setReselectRecordId(id) {\n if (!id) {\n return;\n }\n this.recordReselectId = id;\n }\n\n async deleteReselectRecordBeforeRefresh(data) {\n if (!this.recordReselectId) {\n return;\n }\n\n await this.deleteReselect(this.recordReselectId, true);\n\n return {\n ...data,\n ineffectives: data.ineffectives.filter(item => item.id !== this.recordReselectId)\n }\n }\n\n clearNeedReselectRecord() {\n this.needDeleteReselectRecord = false;\n }\n\n registerDeleteReselectTask(productData) {\n const targetSku = this.renderData.reselectSkus.find(item => item.id === this.recordReselectId);\n\n if (targetSku?.variant_id && productData?.variant.id) {\n if (targetSku.variant_id === productData?.variant.id) {\n this.needDeleteReselectRecord = false;\n return;\n }\n }\n this.needDeleteReselectRecord = true;\n }\n\n // pause cart refresh(trigger by similar products)\n lockRefresh() {\n if (this.refreshLock) {\n return;\n }\n this.refreshLock = true;\n }\n\n releaseRefreshLock() {\n if (!this.refreshLock) {\n return;\n }\n this.refreshLock = false;\n\n this.refreshWithCache();\n }\n\n // direct add_to_cart(trigger by similar products)\n async manualAddToCart(id) {\n const target = this.similarData.find(item => item.id === id);\n this.lockRefresh();\n\n try {\n const res = await this.xhr_.fetchJson(`/api/cart`, {\n method: 'POST',\n body: {\n note: '',\n product_id: target.id,\n quantity: '1',\n variant_id: target.variants[0].id,\n refer_info: {\n source: 'add_to_cart'\n }\n }\n });\n const newCartItems = res.data.items;\n this.setRefreshCache(newCartItems, true);\n } catch (err) {\n console.error(err);\n }\n }\n\n syncSimilarData(data) {\n this.similarData = data.data;\n }\n\n setRefreshCache(data, patch) {\n if (patch) {\n this.refreshDataCache = {\n ...this.refreshDataCache,\n line_items: [...this.refreshDataCache.line_items, ...data]\n };\n } else {\n this.refreshDataCache = data;\n }\n }\n\n refreshWithCache() {\n this.refresh(this.refreshDataCache);\n }\n\n mountCallback() {\n }\n\n unmountCallback() {\n }\n\n /**\n * trigger event\n * @param {Object} data\n */\n trigger_(name, data) {\n const event = SPZUtils.Event.create(this.win, `spz-custom-cart-sku.${name}`, {\n data,\n });\n this.action_.trigger(this.element, name, event);\n }\n }\n\n SPZ.defineElement('spz-custom-cart-sku', SPZCustomCartSku);\n }())\n\n\n\n(function () {\n class SPZCustomCartTrack extends SPZ.BaseElement {\n constructor(element) {\n super(element);\n\n this.action_ = SPZServices.actionServiceForDoc(this.element);\n }\n\n isLayoutSupported(layout) {\n return true;\n }\n\n buildCallback() {\n this.setupAction_();\n }\n\n \n hash(val) {\n const hashKey = Object.keys(val).sort((a, b) => a - b).reduce((acc, k) => {\n acc += `{'${k}':'${val[k]}'}`;\n return acc;\n }, '');\n\n return hashKey;\n }\n\n trackExtra(key, value) {\n console.log('trackExtra...', key, value);\n if (!window.sa) {\n return;\n }\n const hashKey = this.hash(value);\n \n let hasExtraInfo = false;\n if (window.sa.eventInfo && window.sa.eventInfo[key] && window.sa.eventInfo[key].extra_properties) {\n hasExtraInfo = window.sa.eventInfo[key].extra_properties.some(p => {\n return hashKey === this.hash(p);\n });\n }\n if (hasExtraInfo) {\n return;\n }\n \n window.sa && window.sa.registerExtraInfo(key, value);\n }\n\n delExtraTrack(key, value) {\n const hashKey = this.hash(value);\n\n if (window.sa.eventInfo && window.sa.eventInfo[key] && window.sa.eventInfo[key].extra_properties) {\n window.sa.eventInfo[key].extra_properties = window.sa.eventInfo[key].extra_properties.filter(p => hashKey !== this.hash(p));\n }\n }\n\n track(key, value) {\n console.log('tracking...', key, value);\n window.sa && window.sa.track(key, value);\n }\n\n setupAction_() {\n this.registerAction('registerReselectAtc', () => {\n this.trackExtra('add_to_cart', {\n function_name: 'Farida',\n action_type: 'reselect'\n });\n });\n\n this.registerAction('clearReselectAtc', () => {\n this.delExtraTrack('add_to_cart', {\n function_name: 'Farida',\n action_type: 'reselect'\n });\n });\n\n this.registerAction('registerSimilarAtc', () => {\n this.trackExtra('add_to_cart', {\n function_name: 'Farida',\n action_type: 'similar_product'\n });\n });\n\n this.registerAction('clearSimilarAtc', () => {\n this.delExtraTrack('add_to_cart', {\n function_name: 'Farida',\n action_type: 'similar_product'\n });\n });\n\n const clickParams = {\n business_type: 'product_plugin',\n event_name: 'function_click',\n function_name: 'Farida',\n plugin_name: 'Farida',\n template_name: 'cart',\n template_type: '13',\n module: 'online_store',\n module_type: 'online_store',\n tab_name: '',\n card_name: '',\n event_developer: 'ccbken',\n event_type: 'click',\n };\n\n this.registerAction('trackDelReselect', () => {\n this.track('function_click', {\n ...clickParams,\n event_info: JSON.stringify({\n action_type: 'cart_delete',\n element_type: 'sku'\n })\n });\n });\n \n this.registerAction('trackClickReselect', () => {\n this.track('function_click', {\n ...clickParams,\n event_info: JSON.stringify({\n action_type: 'reselect',\n element_type: 'sku'\n })\n });\n });\n\n this.registerAction('trackDelSimilar', () => {\n this.track('function_click', {\n ...clickParams,\n event_info: JSON.stringify({\n action_type: 'cart_delete',\n element_type: 'spu'\n })\n });\n });\n\n this.registerAction('trackClickSimilar', () => {\n this.track('function_click', {\n ...clickParams,\n event_info: JSON.stringify({\n action_type: 'reselect',\n element_type: 'spu'\n })\n });\n });\n\n this.registerAction('trackOpenSimilar', () => {\n this.track('function_expose', {\n ...clickParams,\n event_name: 'function_expose',\n event_type: 'expose',\n event_info: JSON.stringify({\n popup_name: 'farida_product_popup'\n })\n });\n });\n }\n }\n\n SPZ.defineElement('spz-custom-cart-track', SPZCustomCartTrack);\n}())\n\n\n\n\n \n \n \n \n\n \n\n \n\n \n\n \n \n \n \n\n\n \n \n \n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n \n \n\n\n\n\n\n\n\n \n
\n \n \n
\n \n
\n \n
\n \n\n\n\n\n\n \n \n\n\n\n\n
\n\n\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
111
Item has been added
const zebu_ativity_pop_show_history_key = 'zebu_ativity_pop_show_history'; class SpzCustomComponent extends SPZ.BaseElement { constructor(element) { super(element); this.templates_ = null; this.container_ = null; this._atcLineItem = {}; this.cart_ = {}; this.top_product_ids_ = []; this.products_ = []; this.activityId_ = null; this.rendered_ = false; this.myInterceptor_ = null; this.i18n_ = {}; this.config_ = {}; this.page_ = 1; this.limit_ = 10; this.loading_ = false; this.activityPopShowHistoryConfig = null; } static deferredMount() { return false; } isLayoutSupported(layout) { return layout == SPZCore.Layout.CONTAINER; } buildCallback() { this.templates_ = SPZServices.templatesForDoc(this.element); this.setAction_(); } generateActivityPopShowHistoryKey(activity_id){ const customer_id = window.C_SETTINGS.customer.customer_id || 'unlogin'; return customer_id + '-' + activity_id; } judgeCanShowCartPop(data){ const pop_frequency = data.config.pop_frequency; if (pop_frequency == 'once'){ return { show: !window.sessionStorage.getItem('smart_pop_times'), type: 'once' }; } if( pop_frequency == 'not_limit' ){ return { show: true, type: 'not_limit' }; } const activityPopShowHistory = this.getActivityPopShowHistory(); if( !activityPopShowHistory ){ return { show: true, type: 'no_history' }; } const historyKey = this.generateActivityPopShowHistoryKey(data.id); const historyItem = activityPopShowHistory[historyKey]; if( !historyItem ){ return { show: true, type: 'no_history' }; } if( pop_frequency == 'once' ){ return { show: false, type: 'once' }; } const splitArray = pop_frequency.split(':'); if( splitArray.length != 3 ){ return { show: true, type: 'rule_error' }; } const limitType = splitArray[0]; const limitTypeNum = Number(splitArray[1]); const limitNum = Number(splitArray[2]); const startTime = Number(historyItem['startTime']); const count = Number(historyItem['count']); if( new Date().getTime() >= this.getLimitEndTime( startTime, limitType, limitTypeNum ) ){ return { show: true, type: 'another_range' }; } if( limitNum > count ){ return { show: true, type: 'under_limit' }; } return { show: false, type: 'exceed_range' }; } getActivityPopShowHistory(){ try{ const activityPopShowHistory = JSON.parse(window.localStorage.getItem(zebu_ativity_pop_show_history_key)); if( typeof activityPopShowHistory != 'object' ) return null; return activityPopShowHistory; }catch(e){ return null; } } addActivityPopShowHistory(props){ if( !props ) return; const { data, ruleCheckResult } = props; const pop_frequency = data.config.pop_frequency; if( pop_frequency == 'once' ){ return window.sessionStorage.setItem('smart_pop_times', Number(window.sessionStorage.getItem('smart_pop_times')) + 1); } const historyKey = this.generateActivityPopShowHistoryKey(data.id); const activityPopShowHistory = this.getActivityPopShowHistory() || {}; const historyItem = activityPopShowHistory[historyKey]; if( ruleCheckResult.type == 'another_range' || !historyItem ){ activityPopShowHistory[historyKey] = { startTime: new Date().getTime(), count: 1, }; }else{ activityPopShowHistory[historyKey].count += 1; } return window.localStorage.setItem(zebu_ativity_pop_show_history_key,JSON.stringify(activityPopShowHistory)); } removeActivityPopShowHistory(){ return window.localStorage.removeItem(zebu_ativity_pop_show_history_key); } getLimitEndTime(startTime, rangeType, rangeNum){ if( rangeType == 'day' ){ return startTime + 24*60*60*1000 * rangeNum; } if( rangeType == 'week' ){ return startTime + 24*60*60*1000 * 7 * rangeNum; } if( rangeType == 'month' ){ const date = new Date(startTime); date.setMonth( date.getMonth() + rangeNum ); return date.getTime(); } return startTime; } mountCallback() { this.i18n_ = (window.smartRecommendI18n && window.smartRecommendI18n[document.documentElement.lang] || window.smartRecommendI18n['en-US']) || {}; const cartPopRenderEl = document.getElementById(\"smart_cart_pop_render\"); const modalEl = document.getElementById(\"smart_cart_pop_modal\"); const spmBase = `smart_recommend_2`; const extra = { spmBase: spmBase, i18n: this.i18n_, }; const that = this; document.addEventListener('dj.addToCart', (event) => { try { const e = event.detail; if (e.source === 'buy_now' || window.__upsell_block || this.rendered_) return; that.fetchActivityData({product_id: e.product_id, variant_id: e.variant_id}).then(data => { if (!data || !data.products || !data.products.length) return; const ruleCheckResult = that.judgeCanShowCartPop(data); if( !ruleCheckResult.show ) return; that.config_ = data.config; const recommendStyle = document.createElement('style'); recommendStyle.innerHTML = ` #plugin_recommend_atc_pop { display: none !important; } `; document.head.appendChild(recommendStyle); SPZ.whenApiDefined(cartPopRenderEl).then(function(api){ api.render(Object.assign({}, data, extra), true).then(function() { that.rendered_ = true; if (data.products.length) { const headEl = document.getElementById(\"smart_cart_pop_head_render\"); if (headEl) { SPZ.whenApiDefined(headEl).then(function(head){ head.render({ data: data }); }); } SPZ.whenApiDefined(modalEl).then(function(api){ that.impressListen('#smart_cart_pop_activity', function(){ that.trackPluginImpression_(data); }); api.open(); that.activityPopShowHistoryConfig = { data, ruleCheckResult }; const intersectionObserver = new IntersectionObserver( function (entries) { if (entries[0].intersectionRatio > 0){ !that.loading_ && (that.products_.length - that.target_top_product_num_) === that.page_ * that.limit_ && that.viewMore(); } }, { threshold: [0.1] } ); if( document.querySelector('#smart_cart_pop_view_more_text') ){ intersectionObserver.observe( document.querySelector('#smart_cart_pop_view_more_text') ); } }); } }) }); }) } catch (e) { console.error(e); } }); } unmountCallback() { } viewMore () { const cartPopRenderEl = document.getElementById(\"smart_cart_pop_render\"); const that = this; const data = {}; SPZ.whenApiDefined(cartPopRenderEl).then(function(api){ that.fetchActivityData({ page: that.page_ + 1, limit: that.limit_ }).then(function(data) { data.products = that.products_; data.target_top_product_num = that.target_top_product_num_; data.i18n = that.i18n_; data.spmBase = `smart_recommend_2`; api.render(data); }) }) } fetchActivityData(data) { const that = this; if (data.product_id) { that._atcLineItem = data; } that.loading_ = true; return that.getCart().then(cart => { that.cart_ = cart.cart; return fetch(window.C_SETTINGS.routes.root + \"/api/possum/recommend_activities\", { method: \"POST\", headers: { \"Content-Type\": \"application/json\", \"store-id\": window.C_SETTINGS.shop.shop_id, }, body: JSON.stringify({ \"show_type\": 2, \"line_item\": { \"product_id\": that._atcLineItem.product_id, \"variant_id\": that._atcLineItem.variant_id, }, line_items: cart.cart.line_items, \"page\": data.page || 1, \"limit\": data.limit || 10, }) }).then(function(res){ if(res.ok){ return res.json(); } }).then(function(data){ data.cart = cart.cart; if (data.page === 1) { that.target_top_product_num_ = data.target_top_product_num || 0; } that.products_ = that.products_.concat(data.products || []); that.page_ = data.page || 1; that.limit_ = data.limit || 10; return data; }).catch(function(e){ console.log(e); }).finally(function(){ that.loading_ = false; }) }); }; setMatchDrawerHeight_(data) { const modalContent = document.querySelector(\"#smart_cart_pop_product_list\"); const windowHeight = window.innerHeight; modalContent.style.maxHeight = windowHeight * 0.85 + \"px\"; } setAction_() { this.registerAction('changeBannerColor', (data) => { if (!data.args.data || !data.args.data.data || !data.args.data.data.data) return false; const config = data.args.data.data.data.config; const bannerBgEl = document.querySelector('.smart_cart_pop_banner_bg'); if (bannerBgEl && config) { bannerBgEl.style.background = config.banner_bg_color; bannerBgEl.style.color = config.banner_text_color; } }); this.registerAction('handleProductChange', (data) => { const that = this; const imageEl = document.getElementById(`smart_cart_pop_image_${data.args.data.product_id}`); SPZ.whenApiDefined(imageEl).then(function(api){ api.render({ data: data.args.data, config: that.config_ }); }); const atcTextEl = document.getElementById(`smart_cart_pop_atc_${data.args.data.product_id}`); SPZ.whenApiDefined(atcTextEl).then(function(api){ api.render({ data: data.args.data, defaultText: data.args.defaultText, soldOutText: that.i18n_.sold_out }); }); if (data.args.data.variant.available) { document.getElementById(`smart_cart_pop_atc_btn_${data.args.data.product_id}`).classList.remove('zb-pointer-events-none'); } else { document.getElementById(`smart_cart_pop_atc_btn_${data.args.data.product_id}`).classList.add('zb-pointer-events-none'); } }); this.registerAction('handleProduct', (detail) => { const that = this; this.renderProductsForm_(detail.args.data.data); }); this.registerAction('addATCHook', (data) => { const params = data.args; this.myInterceptor_ = window.djInterceptors && window.djInterceptors.track.use({ event: 'dj.addToCart', params: { aid: 'smart_recommend.2.' + params.activity_id, ssp: params.ssp, scm: params.scm, cfb: params.cfb, spm: `..${window.C_SETTINGS.meta.page.template_name}.${params.spm}`, }, once: true }); }); this.registerAction('handleAtcSuccess', (detail) => { detail.args.data.product = detail.args.data.product || {}; detail.args.data.variant = detail.args.data.variant || {}; const defParams = detail.args.product.split('__'); const product_id = detail.args.data.product.id; const product_title = detail.args.data.product.title; const variant_id = detail.args.data.variant.id; const price = detail.args.data.variant.price; const aid = defParams[0]; const ifb = detail.args.data.product.ifb; const cfb = detail.args.data.product.cfb; const scm = defParams[1]; const spm = defParams[2]; const ssp = defParams[3]; const params = { id: product_id, product_id: product_id, number: 1, name: product_title, variant_id: variant_id, childrenId: variant_id, item_price: price, source: 'add_to_cart', _extra: { aid: aid, ifb: ifb, cfb: cfb, scm: scm, spm: `..${window.C_SETTINGS.meta.page.template_name}.${spm}`, ssp: ssp, } }; const activity_id = `${detail.args.activity_id}`; const target_drive_way = detail.args.target_drive_way; document.getElementById(`smart_cart_pop_loading_${product_id}`).classList.add('zb-hidden'); document.getElementById(`smart_cart_pop_added_${product_id}`).classList.remove('zb-hidden'); setTimeout(() => { document.getElementById(`smart_cart_pop_added_${product_id}`).classList.add('zb-hidden'); document.getElementById(`smart_cart_pop_atc_${product_id}`).classList.remove('zb-hidden'); document.getElementById(`smart_cart_pop_atc_btn_${product_id}`).classList.remove('zb-pointer-events-none'); }, 1000); this.tranckAddToCart(params); if (target_drive_way === 'rebate') { const bannerEl = document.getElementById(`smart_cart_pop_banner`); this.getRecommendInfo(activity_id).then(res => { if (res && res.rebate_tips) { bannerEl.innerHTML = res.rebate_tips; } }) } }); this.registerAction('handleCartSummary', (event) => { const that = this; const checkoutButtonEle = document.getElementById(\"smart_cart_pop_checkout_button\"); if (checkoutButtonEle) { SPZ.whenApiDefined(checkoutButtonEle).then(function(api){ api.render({ i18n: that.i18n_ }, false); }); } const tipEl = document.getElementById(\"smart_cart_pop_tip_info\"); const cart = event && event.args && event.args.data && event.args.data.data; if (!tipEl || !cart) return; let total_price = cart.total_price; if (!total_price) { SPZ.whenApiDefined(tipEl).then(function(api){ api.render({ total_price: total_price, i18n: that.i18n_ }, false); }); } else { this.getBindDiscount_(cart.line_items).then(res => { if (res && res.discount_code){ const total = cart.line_price - cart.total_discount - res.bundle_discount_value; if (total > 0) { total_price = total; } else { total_price = 0; } } SPZ.whenApiDefined(tipEl).then(function(api){ api.render({ total_price: total_price, i18n: that.i18n_ }, false); }); }) } }); this.registerAction('open', () => { this.setMatchDrawerHeight_(); this.addActivityPopShowHistory( this.activityPopShowHistoryConfig ); }); this.registerAction('close', () => { this.rendered_ = false; this.products_ = []; window.djInterceptors && window.djInterceptors.track.eject(this.myInterceptor_); }); } getCart() { return fetch(`${window.C_SETTINGS.routes.root || ''}/api/cart`, { method: 'GET', headers: { 'Content-Type': 'application/json; charset=UTF-8', }, }).then(res => res.json()) } getRecommendInfo (activity_id) { return this.getCart().then(cart => { this.cart_ = cart.cart; return fetch(`${window.C_SETTINGS.routes.root || ''}/api/possum/recommend_info`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: JSON.stringify({ show_type: 2, rule_id: `${activity_id}`, line_items: cart.cart.line_items, line_item: this._atcLineItem, }) }).then(res => res.json()) }) } renderProductsForm_(data) { const products = data.products; const listPopRenderEl = document.getElementById(\"smart_cart_pop_render\"); if (!listPopRenderEl) return; listPopRenderEl.querySelectorAll('.smart_cart_pop_atc_btn_bg').forEach(function(el){ el.style.color = data.config.add_to_cart_button_text_color; el.style.background = data.config.add_to_cart_button_color; }); products.forEach(function(product){ const productId = product.id; const productFormEls = listPopRenderEl.querySelectorAll(`ljs-product-form[product-id=\"${productId}\"]`); let variantsEl = listPopRenderEl.querySelectorAll(`ljs-product-form[product-id=\"${productId}\"] #smart_cart_pop_variant_${productId}_mobile ljs-variants`); if (window.innerWidth > 768) { variantsEl = listPopRenderEl.querySelectorAll(`ljs-product-form[product-id=\"${productId}\"] #smart_cart_pop_variant_${productId}_pc ljs-variants`); } productFormEls.forEach(function(el){ SPZ.whenApiDefined(el).then(function(api){ api.setProduct(product); }); }); variantsEl.forEach(function(el){ SPZ.whenApiDefined(el).then(function(api){ api.handleRender(product); }); }) }); } tranckAddToCart(detail) { if (window.$) { window.$(document.body).trigger('dj.addToCart', detail); } } trackPluginImpression_(rule){ if (window.sa && window.sa.track) { window.sa.track(\"module_impressions\", { aid: `smart_recommend.2.${rule.id}` }); } } getBindDiscount_(carts) { let bundle_sale_ids = []; try { bundle_sale_ids = sessionStorage['bundle_sale_ids'] && JSON.parse(sessionStorage['bundle_sale_ids']).filter((item, index, arr) => arr.indexOf(item, 0) === index).slice(-5); } catch (err) { console.error(err); } if (!carts.length) { Promise.resolve(); } return fetch(`${window.C_SETTINGS.routes.root || ''}/api/bundle-sales/cart`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=UTF-8', 'store-id': window.C_SETTINGS.shop.shop_id, }, body: JSON.stringify({ cart: carts, action_type: 'cart', bundle_sale_ids }) }).then(res => res.json()) } impressListen(selector, cb) { const el = document.querySelector(selector); const onImpress = (e) => { if (e) { e.stopPropagation(); } cb(); }; if (el && !el.getAttribute('imprsd')) { el.addEventListener('impress', onImpress) } else if (el) { onImpress(); } } } SPZ.defineElement('spz-custom-smart', SpzCustomComponent);
\n
\n
\n
\n \"gifts\"\n
\n
\n\n
\n
\n \"membership\"\n
\n
\n\n
\n
\n \"gifts\"\n \"membership\"\n
\n
\n
\n \"gifts\"\n GIFTS\n
\n
\n
\n \"membership\"\n POINTS\n
\n
\n
\n
\n
\n\n\n \n\n\n
\"\"
Original text
Rate this translation
Your feedback will be used to help improve Google Translate
copied
Subscribe and
GET 50% OFF
01
:
57
:
40
BF30
Copy
Join Now
\"\"
","screenshot":"https://cdn.scamminder.com/include/uploads/2025/11/silversal.com-20251111-063758.webp","loadTimeInSeconds":8.026,"title":"Silversal","keywords":"Silversal","description":"Silversal","links":["https://silversal.com","https://silversal.com/account/login","https://silversal.com/account/register","https://silversal.com/cart","https://silversal.com/collections/best-seller-zody","https://silversal.com/collections/outerwear","https://silversal.com/collections/hoodies","https://silversal.com/collections/sweatshirts","https://silversal.com/collections/hooded-cape","https://silversal.com/collections/tops","https://silversal.com/collections/short-sleeve-t-shirts","https://silversal.com/collections/long-sleeve-t-shirts","https://silversal.com/collections/vests","https://silversal.com/collections/blouses","https://silversal.com/collections/hooded-t-shirts","https://silversal.com/collections/turtleneck-tops","https://silversal.com/collections/dress","https://silversal.com/collections/mini-dresses","https://silversal.com/collections/midi-dresses","https://silversal.com/collections/maxi-dresses","https://silversal.com/collections/ankara-dresses","https://silversal.com/collections/memorial-day","https://silversal.com/collections/juneteenth","https://silversal.com/collections/black-history-month","https://silversal.com/collections/independence-day","https://silversal.com/collections/halloween","https://silversal.com/collections/constellation","https://silversal.com/collections/bottom","https://silversal.com/collections/pants","https://silversal.com/collections/jumpsuits","https://silversal.com/collections/leggings","https://silversal.com/collections/shorts","https://silversal.com/collections/two-pieces-sets","https://silversal.com/collections/sweatshirt-sets","https://silversal.com/collections/hoodie-sets","https://silversal.com/collections/t-shirt-sets","https://silversal.com/collections/accessories","https://silversal.com/collections/black-friday-deals","https://silversal.com/products/shhh-im-only-talking-to-god-today-long-sleeved-hoodie","https://silversal.com/products/shhh-im-only-talking-to-god-today-long-sleeved-hoodie?variant=74327fdb-2934-4c63-ad95-a4347bd58941","https://silversal.com/products/shhh-im-only-talking-to-god-today-long-sleeved-hoodie?variant=4511777f-04a3-4b16-bc97-9e8d1939d8d5","https://silversal.com/products/shhh-im-only-talking-to-god-today-long-sleeved-hoodie?variant=723241d3-96a6-49cc-baf0-1f32c19dd77b","https://silversal.com/products/she-overcame-everything-long-sleeve-hoodie","https://silversal.com/products/she-overcame-everything-long-sleeve-hoodie?variant=de29cd0c-1628-4070-a52f-783547c435b0","https://silversal.com/products/she-overcame-everything-long-sleeve-hoodie?variant=aa2cc223-6549-4327-a804-7ad711382341","https://silversal.com/products/she-overcame-everything-long-sleeve-hoodie?variant=d417ae73-0e31-4151-8805-73a1123a9be7","https://silversal.com/products/melanin-every-shade-slays-zip-hood-long-sleeves-hoodie","https://silversal.com/products/melanin-every-shade-slays-zip-hood-long-sleeves-hoodie?variant=35213303-69d2-44fa-9c50-27d3963b579d","https://silversal.com/products/i-match-energy-long-sleeves-hoodie","https://silversal.com/products/i-match-energy-long-sleeves-hoodie?variant=2b4e1bed-b80c-426f-b284-cab396a80335","https://silversal.com/products/i-match-energy-long-sleeves-hoodie?variant=ff910d26-90bb-4405-96a6-8d11b8d725aa","https://silversal.com/products/i-match-energy-long-sleeves-hoodie?variant=c52ce776-50e1-469e-b80a-fda261a9d6b0","https://silversal.com/products/faith-in-god-change-everything-long-sleeved-hoodies","https://silversal.com/products/faith-in-god-change-everything-long-sleeved-hoodies?variant=0ed701fe-1ed8-4501-ac6f-7308d889be15","https://silversal.com/products/black-queen-long-sleeved-hoodies","https://silversal.com/products/black-queen-long-sleeved-hoodies?variant=a9e7f644-889e-4112-b92a-9855f5357b7c","https://silversal.com/products/dearly-beloved-long-sleeves-hoodie","https://silversal.com/products/dearly-beloved-long-sleeves-hoodie?variant=ba75a761-6b2a-4f9b-b170-ea3a89b5dca5","https://silversal.com/products/black-girl-magic-hooded-hoodie-1","https://silversal.com/products/black-girl-magic-hooded-hoodie-1?variant=38dba5ba-d37b-4758-a765-097f1beaf102","https://silversal.com/products/black-girl-magic-hooded-hoodie-1?variant=415817bf-9d78-464c-a1e5-a9c6033e7938","https://silversal.com/products/black-girl-magic-hooded-hoodie-1?variant=907cdb1d-a484-43ad-9797-0c4083c61bf1","https://silversal.com/products/i-cant-but-i-know-a-guy-long-sleeved-hoodies","https://silversal.com/products/i-cant-but-i-know-a-guy-long-sleeved-hoodies?variant=34b046a1-8125-4d14-b9a9-1ade8bbfe599","https://silversal.com/products/i-can-do-all-things-through-christ-high-collar-pullover-irregular-long-sleeve-warm-sweatshirt","https://silversal.com/products/i-can-do-all-things-through-christ-high-collar-pullover-irregular-long-sleeve-warm-sweatshirt?variant=3824525d-091e-4b0a-badc-162e2c4d5b90","https://silversal.com/products/psalm-91-with-god-long-sleeved-hoodies","https://silversal.com/products/psalm-91-with-god-long-sleeved-hoodies?variant=1231d2a0-0982-41ef-9370-57c6605692f9","https://silversal.com/products/womens-dont-play-about-me-anymore-letter-print-long-sleeved-hoodie","https://silversal.com/products/womens-dont-play-about-me-anymore-letter-print-long-sleeved-hoodie?variant=324d7ad6-e5f3-40e9-a831-5b897d437ded","https://silversal.com/products/taurus-girly-season-print-unisex-long-sleeved-hoodie","https://silversal.com/products/taurus-girly-season-print-unisex-long-sleeved-hoodie?variant=9797ca0a-b98f-4371-9737-0806c7ef2462","https://silversal.com/products/locd-and-beautiful-long-sleeved-hoodies","https://silversal.com/products/locd-and-beautiful-long-sleeved-hoodies?variant=aa363fd2-4acf-4fce-bd9d-6605ecad6e1a","https://silversal.com/products/stay-on-your-ps-long-sleeved-hoodie","https://silversal.com/products/stay-on-your-ps-long-sleeved-hoodie?variant=c787cb0c-9e75-4feb-aec2-6082079383dc","https://silversal.com/products/stay-on-your-ps-long-sleeved-hoodie?variant=b7208b19-d5cd-42ae-a9f8-1b563039c54e","https://silversal.com/products/stay-on-your-ps-long-sleeved-hoodie?variant=fd5569f6-9ccc-490e-ad2c-633bb5f16414","https://silversal.com/collections/womens-hoodie","https://silversal.com/collections/christmas","https://silversal.com/products/women-loose-fluffy-colorblock-hooded-coat?cfb=594d7991-4cad-45f7-9705-afeba7d0ceaf&scm=collection.v38.177.202.203.204&score=2.325653162609434&ssp=-","https://silversal.com/products/women-loose-fluffy-colorblock-hooded-coat?variant=3ef0af55-9638-452c-a342-4102249672a0","https://silversal.com/products/women-loose-fluffy-colorblock-hooded-coat?variant=e5444cd5-e7f4-4011-ab64-8531e2dc5e7d","https://silversal.com/products/women-loose-fluffy-colorblock-hooded-coat?variant=ea5306ec-1aef-4696-9b8f-dd2ad0743553","https://silversal.com/products/tribal-ethnic-patterns-long-sleeve-zip-up-fleece-long-hoodies-1?cfb=594d7991-4cad-45f7-9705-afeba7d0ceaf&scm=collection.v38.177.202.203.204&score=1.4495965270793292&ssp=-","https://silversal.com/products/tribal-ethnic-patterns-long-sleeve-zip-up-fleece-long-hoodies-1?variant=949a79e5-5f7c-43c4-bba3-6f4db0dfa33f","https://silversal.com/products/denim-patch-print-plush-lapel-winter-mid-length-coat?cfb=594d7991-4cad-45f7-9705-afeba7d0ceaf&scm=collection.v38.177.202.203.204&score=1.207469396826568&ssp=-","https://silversal.com/products/denim-patch-print-plush-lapel-winter-mid-length-coat?variant=96a414b8-0355-4b87-9eb9-55296a4818fe","https://silversal.com/products/autumn-and-winter-solid-color-fur-collar-hooded-long-thick-warm-cotton-padded-coat?cfb=594d7991-4cad-45f7-9705-afeba7d0ceaf&scm=collection.v38.177.202.203.204&score=0.9195906089477801&ssp=-","https://silversal.com/products/autumn-and-winter-solid-color-fur-collar-hooded-long-thick-warm-cotton-padded-coat?variant=f10ef10b-9810-4f17-9f7d-a9ea7e0abf7e","https://silversal.com/products/autumn-and-winter-solid-color-fur-collar-hooded-long-thick-warm-cotton-padded-coat?variant=5f7ec476-8f40-486a-a378-e6433f1c77f8","https://silversal.com/products/autumn-and-winter-solid-color-fur-collar-hooded-long-thick-warm-cotton-padded-coat?variant=f6d2e5fb-26af-448d-8bb3-7c33f1e37f40","https://silversal.com/products/glittering-pink-polka-dots-plush-lapel-winter-mid-length-coat?cfb=594d7991-4cad-45f7-9705-afeba7d0ceaf&scm=collection.v38.177.202.203.204&score=0.6076093155000186&ssp=-","https://silversal.com/products/glittering-pink-polka-dots-plush-lapel-winter-mid-length-coat?variant=6ddd95df-5fd0-4e27-bcfe-94302c3d2884","https://silversal.com/products/i-simple-dont-play-about-me-anymore-long-sleeve-zip-up-fleece-hoodies?cfb=594d7991-4cad-45f7-9705-afeba7d0ceaf&scm=collection.v38.177.202.203.204&score=0.5854623802342631&ssp=-","https://silversal.com/products/i-simple-dont-play-about-me-anymore-long-sleeve-zip-up-fleece-hoodies?variant=6981c147-332b-4ec0-8608-9df687d1fe49","https://silversal.com/products/solid-color-long-sleeve-zip-up-fleece-long-hoodies?cfb=594d7991-4cad-45f7-9705-afeba7d0ceaf&scm=collection.v38.177.202.203.204&score=0.5775071126802871&ssp=-","https://silversal.com/products/solid-color-long-sleeve-zip-up-fleece-long-hoodies?variant=4febb943-ab71-4fe6-9e1c-2df1925f0260","https://silversal.com/products/solid-color-long-sleeve-zip-up-fleece-long-hoodies?variant=6170226f-7509-414e-9824-f814b2037bbf","https://silversal.com/products/solid-color-long-sleeve-zip-up-fleece-long-hoodies?variant=d23de049-4476-4440-8236-ed92d68277b8"],"scraper_engine":"Puppeteer (Enhanced)","screenshot_size_bytes":55201,"domSignals":{"lang":"en-US","canonical":"https://www.silversal.com/","hasLogin":false,"hasCheckout":false,"hasContact":true,"hasPolicy":true,"ogTitle":"Silversal","ogSite":"Silversal","ogDescription":"Silversal"},"formRisks":[],"httpStatus":200,"finalUrl":"https://www.silversal.com/","htmlLength":1888480,"textLength":34642,"lowEvidenceRecovery":false,"parkingDetection":{"isParked":false},"otherpages":{"internalLinks":["https://silversal.com"],"internalPageContents":["class SpzCustomLabelScript extends SPZ.BaseElement { constructor(element) { super(element); } isLayoutSupported(layout) { return true; } mountCallback() { const script = this.element; const boxEl = script.closest('.product_snippet_label_area'); const labelEl = boxEl.querySelector('.product_snippet__label'); if(!labelEl) return; const observer = new ResizeObserver((entries) => { const labelEls = boxEl.querySelectorAll('.product_snippet__label'); const offsetWidth = Math.max(...Array.from(labelEls).map(el => el.offsetWidth)); if(offsetWidth>0){ const padding = offsetWidth / 2 + 8 + 'px'; boxEl.style.left = padding; boxEl.style.right = padding; boxEl.style.visibility = 'visible'; } }); observer.observe(labelEl); } } SPZ.defineElement('spz-custom-label-script', SpzCustomLabelScript);\n(function () { class SPZCustomEventTrack extends SPZ.BaseElement { constructor(element) { super(element); this.action_ = SPZServices.actionServiceForDoc(this.element); } isLayoutSupported(layout) { return true; } buildCallback() { this.setupAction_(); } track(key, value) { console.log('tracking...', key, value); if(window.sa){ window.sa.track(key, value); }else{ let sa = null; Object.defineProperty(window, 'sa',{ get: function() { return sa; }, set(val){ sa = val; sa.track(key, value); } }) } } setupAction_() { const clickParams = { business_type: 'product_theme', event_name: 'function_click', function_name: 'Farida', plugin_name: 'Farida', template_name: \"index\", template_type: 15, module: 'online_store', module_type: 'online_store', tab_name: '', card_name: '', event_developer: 'ccbGolumn', event_type: 'click', }; this.registerAction('trackClick', (e) => { const event_info = e.args || {}; this.track('function_click', { ...clickParams, event_info: JSON.stringify(event_info), }); }); this.registerAction('trackExpose', (e) => { const event_info = e.args || {}; this.track('function_expose', { ...clickParams, event_name: 'function_expose', event_type: 'expose', event_info: JSON.stringify(event_info), }); }); } } SPZ.defineElement('spz-custom-event-track', SPZCustomEventTrack); }())\n\nFREE SHIPPING ON ORDERS OVER $69\n\n(function () { let w = window.innerWidth; function setHeaderCssVar() { const headerEle = document.getElementById( \"shoplaza-section-header\", ); if (!headerEle) { return; } document.body.style.setProperty( \"--window-height\", `${window.innerHeight}px`, ); document.body.style.setProperty( \"--header-height\", `${headerEle.clientHeight}px`, ); const mdScorllHideEle = headerEle.querySelector( \".header__mobile .header__scroll_hide\", ); if (mdScorllHideEle) { document.body.style.setProperty( \"--header-scroll-hide-height-md\", `${mdScorllHideEle.clientHeight}px`, ); } const pcScorllHideEle = headerEle.querySelector( \".header__desktop .header__scroll_hide\", ); if (pcScorllHideEle) { document.body.style.setProperty( \"--header-scroll-hide-height-pc\", `${pcScorllHideEle.clientHeight}px`, ); } } function handlResize() { if (w == window.innerWidth) { return; } w = window.innerWidth; setHeaderCssVar(); } function init() { setHeaderCssVar(); window.removeEventListener(\"resize\", window._theme_header_listener); window._theme_header_listener = handlResize; window.addEventListener(\"resize\", window._theme_header_listener); } init(); })();\nHoodie\nSave 33%\nShhh I'm Only Talking to God Today Long-sleeved Hoodie\n$25.99\n$38.99\n+5\nSave 33%\nShe Overcame Everything Long Sleeve Hoodie\n$25.99\n$38.99\nSave 33%\nMelanin Every Shade Slays Zip Hood Long Sleeves Hoodie\n$25.99\n$38.99\nSave 33%\nI Match Energy Long Sleeves Hoodie\n$25.99\n$38.99\n+3\nFaith In God Change Everything Long-sleeved Hoodies\n$29.99\nBlack Queen Long-sleeved Hoodies\n$29.99\nDearly Beloved Long Sleeves Hoodie\n$25.99\n$38.99\nBlack Girl Magic Hooded Hoodie\n$25.99\n$38.99\n+3\nView more\nNew Hooded Coat\nWomen Loose Fluffy Colorblock Hooded Coat\n$30.99\n+3\nTribal Ethnic Patterns Long Sleeve Zip-Up Fleece Long Hoodies\n$48.99\nDenim Patch Print Plush Lapel Winter Mid-Length Coat\n$48.99\nAutumn And Winter Solid Color Fur Collar Hooded Long Thick Warm Cotton-padded Coat\n$78.99\n+2\nGlittering Pink Polka Dots Plush Lapel Winter Mid-Length Coat\n$48.99\nI Simple Don't Play About Me Anymore Long Sleeve Zip-Up Fleece Hoodies\n$38.99\nSolid Color Long Sleeve Zip-Up Fleece Long Hoodies\n$48.99\nStriped Curves Long Sleeve Zip-Up Fleece Long Hoodies\n$48.99\nView more\nReviews (458)\n4\nLove this and the others I've purchased\nTeair H.\nSep 03, 2025\nI messed up my order and picked the wrong thing and they more than quickly adjusted and fixed everything for me! Can't wait yp purchase more 😍🥰\nJade J.\nSep 02, 2025\n9\nI love this sweatshirt. Feels great to support you all especially as a Black woman. 💛\nJerrica B.\nSep 02, 2025\n10\nThe quality of the sweatshirt was made well. I love the color and the message that it speaks. It resonates with me because it is important to me to remind others.\nPatricia F.\nSep 02, 2025\n11\nI enjoy the stares of people reading my sweatshirt. it’s empowering to walk confidently with such bold statements\nShavaughn M.\nSep 02, 2025\nGreat sweatshirt. Great message and design. Really comfortable.\nKemila\nSep 01, 2025\nLove this sweatshirt! Kicked off Womens History month rocking this.\nMichelle V.\nSep 01, 2025\n6\nFast and efficient\nRobilyn H.\nSep 01, 2025\nView more\n\nCOMPANY INFO\n\nHELP & SUPPORT\n\nCUSTOMER CARE\n\nContact us\n\n© \n2025\n Silversal\n(function() { const STATUS = { LOADING: 'loading', FINISH: 'finish' }; const RESULT = { EMPTY: 'data-empty' }; class SPZCustomCartSku extends SPZ.BaseElement { renderData = []; /** * add to cart reselect item, and delete process: * 1. record reselect id before show reselect modal * 2. add to cart success, mark `needDeleteReselectRecord` to true * 3. close reselect modal / drawer * 4. spz-cart re-render, triggered mounted event * 5. call refresh */ // mark delete reselect id recordReselectId = null; // mark should delete reselect record after spz-cart mounted event needDeleteReselectRecord = false; refreshLock = false; addToCartSuccess = false; // cache paused refresh data refreshDataCache = []; // cache similar products data, for manual add to cart similarData = []; constructor(element) { super(element); this.xhr_ = SPZServices.xhrFor(this.win); this.action_ = SPZServices.actionServiceForDoc(this.element); } setupAction_() { this.registerAction('refresh', (invocation) => { const data = invocation && invocation.args && invocation.args.data || {}; this.refresh(data); }); this.registerAction('deleteReselect', SPZCore.Types.debounce( this.win, (invocation) => { this.deleteReselect(invocation?.args?.id); }, 200 )); this.registerAction('deleteInvalid', SPZCore.Types.debounce( this.win, (invocation) => { this.deleteInvalid(invocation?.args?.id); }, 200 )); this.registerAction('setReselectRecordId', (invocation) => { this.setReselectRecordId(invocation?.args?.id); }); this.registerAction('clearNeedReselectRecord', (invocation) => { this.clearNeedReselectRecord(); }); this.registerAction('registerDeleteReselectTask', (invocation) => { this.registerDeleteReselectTask(invocation?.args?.data); }); this.registerAction('lockRefresh', (invocation) => { this.lockRefresh(); }); this.registerAction('releaseRefreshLock', (invocation) => { this.releaseRefreshLock(); }); this.registerAction('manualAddToCart', (invocation) => { this.manualAddToCart(invocation?.args?.id); }); this.registerAction('syncSimilarData', (invocation) => { this.syncSimilarData(invocation?.args?.data); }); // DEBUG this.registerAction('log', (invocation) => { const data = invocation && invocation.args && invocation.args.data || {}; console.log('log', invocation); }); } buildCallback() { this.setupAction_(); } isLayoutSupported(layout) { return true; } /** * 数据去重 * @param {Array} data */ uniq_(data) { const result = []; for(let i = 0; i < data.length; i++) { if(!result.includes(data[i])) { result.push(data[i]); } } return result; } checkRefreshLock() { if (this.refreshLock) { return false; } return true; } setLoading(isLoading) { SPZCore.Dom.toggleAttribute(this.element, STATUS.LOADING, isLoading); SPZCore.Dom.toggleAttribute(this.element, STATUS.FINISH, !isLoading); } setDataResult(data) { const isDataEmpty = !data.reselectSkus.length && !data.invalidSkus.length && !data.line_items.length; SPZCore.Dom.toggleAttribute(this.element, RESULT.EMPTY, isDataEmpty); } // 存在多次请求顺序问题 async refresh(_data) { // 接口失败时返回数据不为对象 if ((typeof _data !== 'object' || !Array.isArray(_data.line_items))) { const newData = { line_items: [], reselectSkus: [], invalidSkus: [], displayInvalidSkus: [] }; this.trigger_('refreshError', newData); this.setLoading(false); return; } if (!this.checkRefreshLock()) { this.setRefreshCache(_data); return; } this.setLoading(true); let data = _data; if (this.needDeleteReselectRecord && this.recordReselectId) { let hasError = false; try { data = await this.deleteReselectRecordBeforeRefresh(_data); } catch (e) { hasError = true; console.error(e); const newData = { ...data, reselectSkus: [], invalidSkus: [], displayInvalidSkus: [] }; this.trigger_('refreshError', newData); this.setLoading(false); this.setDataResult(newData); return; } this.needDeleteReselectRecord = false; if (hasError) { return; } } // 获取失效商品 const invisibleItems = data.ineffectives; // 获取失效商品内仅售罄商品的sku const soldOutItems = invisibleItems.filter(item => item.reason === \"line_item_sold_out\"); // 通过失效 sku 获取 spu, 注意去重 const soldOutSpuIds = this.uniq_(soldOutItems.map(item => item.product_id)); const querySpuIds = soldOutSpuIds.map(spuId => `ids[]=${spuId}`).join('&'); if (!soldOutSpuIds.length) { const newData = { ...data, reselectSkus: [], invalidSkus: [], displayInvalidSkus: [] }; this.trigger_('refreshSuccess', newData); this.renderData = newData; this.setLoading(false); this.setDataResult(newData); return; } // 请求 spu 下其他 sku 库存 this.xhr_.fetchJson(`/api/product/list?limit=${soldOutSpuIds.length}&${querySpuIds}`) .then(res => { const reselectSkus = []; const invalidSkus = []; // spu 维度展示 const displayInvalidSkus = []; res.data.list.forEach(product => { // spu 匹配, 存在多 sku 情况 const soldOutSkus = soldOutItems.filter(item => item.product_id === product.id); if (!soldOutSkus.length) { return; } // 通过失效 sku 获取 spu 下其他 sku 库存, 存在其他可用库存时标记为 \"Reselect\" 按钮可用 //const allowReselect = product.variants // .filter(variant => variant.option1 === soldOutProduct.variant.option1 && variant.option2 === soldOutProduct.variant.option2 && variant.option3 === soldOutProduct.variant.option3) // .some(variant => variant.available); // 查询售罄 sku 对应商品是否可加购, 标记为 Reselect 可用项 if (product.available) { reselectSkus.push(...soldOutSkus); // 若商品不可用(全部 sku 售罄), 归纳到失效商品列表 } else { invalidSkus.push(...soldOutSkus); // spu 维度, 仅添加一项 sku displayInvalidSkus.push(soldOutSkus[0]); } }); const newData = { ...data, reselectSkus, invalidSkus, displayInvalidSkus }; this.trigger_('refreshSuccess', newData); this.renderData = newData; this.setLoading(false); this.setDataResult(newData); }) .catch(err => { this.setLoading(false); console.error(err); this.trigger_('refreshError', data); }).finally(() => { }); } async deleteReselect(id, ignoreEmit) { if (!id) { return; } const targetSku = this.renderData.reselectSkus.find(item => item.id === id); try { const res = await this.xhr_.fetchJson(`/api/cart/${targetSku.variant_id}`, { method: 'DELETE', body: { id: targetSku.id, product_id: targetSku.product_id, variant_id: targetSku.variant_id, } }); const newData = { ...this.renderData, reselectSkus: this.renderData.reselectSkus.filter(item => item.id !== id), }; this.renderData = newData; !ignoreEmit && this.trigger_('deleteSuccess', newData); } catch (err) { console.error(err); !ignoreEmit && this.trigger_('deleteError', err); } } deleteInvalid(id) { if (!id) { return; } const targetSku = this.renderData.invalidSkus.find(item => item.id === id); this.xhr_.fetchJson(`/api/cart/${targetSku.variant_id}`, { method: 'DELETE', body: { id: targetSku.id, product_id: targetSku.product_id, variant_id: targetSku.variant_id, } }) .then(res => { const newData = { ...this.renderData, invalidSkus: this.renderData.invalidSkus.filter(item => item.id !== id), displayInvalidSkus: this.renderData.displayInvalidSkus.filter(item => item.id !== id), }; this.trigger_('deleteSuccess', newData); this.renderData = newData; }) .catch(err => { console.error(err); this.trigger_('deleteError', err); }); } // record reselect sku id before show reselect modal setReselectRecordId(id) { if (!id) { return; } this.recordReselectId = id; } async deleteReselectRecordBeforeRefresh(data) { if (!this.recordReselectId) { return; } await this.deleteReselect(this.recordReselectId, true); return { ...data, ineffectives: data.ineffectives.filter(item => item.id !== this.recordReselectId) } } clearNeedReselectRecord() { this.needDeleteReselectRecord = false; } registerDeleteReselectTask(productData) { const targetSku = this.renderData.reselectSkus.find(item => item.id === this.recordReselectId); if (targetSku?.variant_id && productData?.variant.id) { if (targetSku.variant_id === productData?.variant.id) { this.needDeleteReselectRecord = false; return; } } this.needDeleteReselectRecord = true; } // pause cart refresh(trigger by similar products) lockRefresh() { if (this.refreshLock) { return; } this.refreshLock = true; } releaseRefreshLock() { if (!this.refreshLock) { return; } this.refreshLock = false; this.refreshWithCache(); } // direct add_to_cart(trigger by similar products) async manualAddToCart(id) { const target = this.similarData.find(item => item.id === id); this.lockRefresh(); try { const res = await this.xhr_.fetchJson(`/api/cart`, { method: 'POST', body: { note: '', product_id: target.id, quantity: '1', variant_id: target.variants[0].id, refer_info: { source: 'add_to_cart' } } }); const newCartItems = res.data.items; this.setRefreshCache(newCartItems, true); } catch (err) { console.error(err); } } syncSimilarData(data) { this.similarData = data.data; } setRefreshCache(data, patch) { if (patch) { this.refreshDataCache = { ...this.refreshDataCache, line_items: [...this.refreshDataCache.line_items, ...data] }; } else { this.refreshDataCache = data; } } refreshWithCache() { this.refresh(this.refreshDataCache); } mountCallback() { } unmountCallback() { } /** * trigger event * @param {Object} data */ trigger_(name, data) { const event = SPZUtils.Event.create(this.win, `spz-custom-cart-sku.${name}`, { data, }); this.action_.trigger(this.element, name, event); } } SPZ.defineElement('spz-custom-cart-sku', SPZCustomCartSku); }())\n(function () { class SPZCustomCartTrack extends SPZ.BaseElement { constructor(element) { super(element); this.action_ = SPZServices.actionServiceForDoc(this.element); } isLayoutSupported(layout) { return true; } buildCallback() { this.setupAction_(); } hash(val) { const hashKey = Object.keys(val).sort((a, b) => a - b).reduce((acc, k) => { acc += `{'${k}':'${val[k]}'}`; return acc; }, ''); return hashKey; } trackExtra(key, value) { console.log('trackExtra...', key, value); if (!window.sa) { return; } const hashKey = this.hash(value); let hasExtraInfo = false; if (window.sa.eventInfo && window.sa.eventInfo[key] && window.sa.eventInfo[key].extra_properties) { hasExtraInfo = window.sa.eventInfo[key].extra_properties.some(p => { return hashKey === this.hash(p); }); } if (hasExtraInfo) { return; } window.sa && window.sa.registerExtraInfo(key, value); } delExtraTrack(key, value) { const hashKey = this.hash(value); if (window.sa.eventInfo && window.sa.eventInfo[key] && window.sa.eventInfo[key].extra_properties) { window.sa.eventInfo[key].extra_properties = window.sa.eventInfo[key].extra_properties.filter(p => hashKey !== this.hash(p)); } } track(key, value) { console.log('tracking...', key, value); window.sa && window.sa.track(key, value); } setupAction_() { this.registerAction('registerReselectAtc', () => { this.trackExtra('add_to_cart', { function_name: 'Farida', action_type: 'reselect' }); }); this.registerAction('clearReselectAtc', () => { this.delExtraTrack('add_to_cart', { function_name: 'Farida', action_type: 'reselect' }); }); this.registerAction('registerSimilarAtc', () => { this.trackExtra('add_to_cart', { function_name: 'Farida', action_type: 'similar_product' }); }); this.registerAction('clearSimilarAtc', () => { this.delExtraTrack('add_to_cart', { function_name: 'Farida', action_type: 'similar_product' }); }); const clickParams = { business_type: 'product_plugin', event_name: 'function_click', function_name: 'Farida', plugin_name: 'Farida', template_name: 'cart', template_type: '13', module: 'online_store', module_type: 'online_store', tab_name: '', card_name: '', event_developer: 'ccbken', event_type: 'click', }; this.registerAction('trackDelReselect', () => { this.track('function_click', { ...clickParams, event_info: JSON.stringify({ action_type: 'cart_delete', element_type: 'sku' }) }); }); this.registerAction('trackClickReselect', () => { this.track('function_click', { ...clickParams, event_info: JSON.stringify({ action_type: 'reselect', element_type: 'sku' }) }); }); this.registerAction('trackDelSimilar', () => { this.track('function_click', { ...clickParams, event_info: JSON.stringify({ action_type: 'cart_delete', element_type: 'spu' }) }); }); this.registerAction('trackClickSimilar', () => { this.track('function_click', { ...clickParams, event_info: JSON.stringify({ action_type: 'reselect', element_type: 'spu' }) }); }); this.registerAction('trackOpenSimilar', () => { this.track('function_expose', { ...clickParams, event_name: 'function_expose', event_type: 'expose', event_info: JSON.stringify({ popup_name: 'farida_product_popup' }) }); }); } } SPZ.defineElement('spz-custom-cart-track', SPZCustomCartTrack); }())"]}},"webrisk":{"overall_risk":"unknown","threats":[],"malware":false,"social_engineering":false,"unwanted_software":false,"error":"Request failed with status code 400"},"metadata":{"preflight":{"bestUrl":"https://silversal.com","probes":[{"url":"https://silversal.com","ok":true,"status":200},{"url":"https://www.silversal.com","ok":true,"status":200},{"url":"http://silversal.com","ok":true,"status":200}],"zyteCheck":null},"best_url":"https://silversal.com","phase_a_duration_ms":3506,"phase_b_duration_ms":24188,"early_exit_reason":null,"tls_warnings":[],"zyte_preflight":null,"low_evidence_recovery":false},"virustotal":{"malicious":0,"suspicious":0,"total":0,"scanned":false},"evidence_coverage":"90","ai_result_latest":{"flag":"high_risk","rate":25,"about":"

Detailed Analysis Report: Is Silversal.com Safe and Legit?

\n

Website Overview and Purpose

\n

Silversal.com is an e-commerce platform that specializes in selling a variety of clothing items, including hoodies, dresses, and outerwear. The site aims to attract fashion-conscious consumers with promotional offers and discounts, such as free shipping on orders over $69.

\n

Content Quality and User Experience

\n

Key Experience Highlights

\n\n

Claims Verification and Red Flags

\n

⚠️ Red Flags Detected

\n

Several red flags have been identified that raise concerns about the legitimacy of the site:

\n\n

⚠️ Caution Points

\n\n

Security Note: The site uses a standard SSL certificate, but this does not guarantee legitimacy.

\n

Legitimacy and Reputation Assessment

\n

The domain has been operational for approximately 2 years, which is relatively new for an e-commerce platform. The site is hosted in Canada and uses Cloudflare for security. However, the lack of a solid reputation, combined with the absence of verifiable company information, raises concerns about its legitimacy.

\n

Final Verdict and Recommendations

\n

Conclusion: Based on the identified red flags and the overall lack of transparency, Silversal.com appears to be a high-risk e-commerce site. Users are advised to exercise caution and consider alternative, more established retailers for their shopping needs.

\n

Best practices include researching the site thoroughly, checking for customer reviews on independent platforms, and avoiding sharing sensitive information.

","status":"scam","reasons":["[BUSINESS MODEL] The site offers unrealistic discounts, which is a common tactic used by scam sites to attract customers.","[TRANSPARENCY] There is no verifiable company information or physical address provided, raising trust issues.","[DOMAIN HISTORY] The domain is only 2 years old, which is relatively new for an e-commerce site claiming to offer a wide range of products.","[CONTENT QUALITY] Many product descriptions are vague and lack specific details, which is typical of fraudulent sites.","[TRUST SIGNALS] Customer testimonials appear overly positive and generic, suggesting they may be fabricated."],"category":"E-commerce","red_flags":["[GUARDRAIL] No deterministic evidence for scam; downgrading to warning","[CLAIMS] Unrealistic discounts of up to 50% off, which is often a tactic used by scam sites.","[TRANSPARENCY] No clear company registration details or physical address available.","[DOMAIN HISTORY] The domain is only 2 years old, raising concerns about its legitimacy.","[CONTENT QUALITY] Generic product descriptions that lack specific details.","[TRUST] Customer testimonials seem overly positive and generic, indicating potential fabrication."],"final_score":25,"subcategory":"Clothing Retail","final_status":"warning","score_source":"openai_guardrail","ai_confidence":"medium","claimed_brand":null,"brand_evidence":[],"business_model":"Selling clothing and accessories online","expected_domain":null,"target_audience":"Fashion-conscious consumers looking for discounts","confidence_level":"medium","guardrail_actions":[{"type":"scam_downgraded","reason":"No deterministic evidence for scam; downgrading to warning","scoreCeiling":null,"targetStatus":"warning"}],"analysis_timestamp":"2025-11-11T11:38:41.947Z","user_recommendation":"Exercise caution and consider alternative retailers.","contact_transparency":"poor","professionalism_score":4,"brand_claim_confidence":null},"final_domain_age":{"days":1051,"years":2,"source":"rdap","verified":true,"allSources":["rdap"]},"guardrail_summary":{"actions":[{"type":"scam_downgraded","reason":"No deterministic evidence for scam; downgrading to warning","scoreCeiling":null,"targetStatus":"warning"}],"scoreSource":"openai_guardrail","aiConfidence":"medium"}},"reviews":[],"has_archive":false,"archive_data":null,"archive_stats":null}};