Safety for All.
Whether you’re a PPE industry pro or just starting out, we’re here to walk with you. With our premium safety shoes, let’s find our destiny together.
Comfort & Style,
Crafted Safely.
Every Step Deserves Protection.
Built for Work. Designed for Life.
Crafted for Strength. Certified for Protection.
S7S, EN ISO 20345: 2022
Stitch Down Durable Construction.
Super-Fast Closure System.
Global Footprint
CE Certified, Global Trusted
-
1Region
Exclusive Market Protection
-
300+Pairs MOQ
Flexible & Low-risk
-
100%Bespoke
Scalable OEM/ODM Solutions





















Step Into Quiet Confidence.
Your trusted companion.
Through every shift.
Through every season.
Every mark tells a story.

Four ways we build
success together.
Success together is our commitment.
We unite premium engineering with flexible commercial models to elevate your business.
Here is how we drive your growth, from startup to scale.
- Q: Where is PATRONI from?
- A: Born in Taiwan, crafted for the world. Beyond advanced engineering, our heart is our purpose. We seek like-minded partners who share our DNA: bringing inspiration to craftsmen, honoring the earth, and driving growth with a positive, entrepreneurial spirit.
- Q: I’m interested in becoming a partner. What are the qualifications?
- A: Whether you’re a PPE veteran or just starting out, we are ready to walk with you. Let’s build a shared future together with our premium safety footwear—we value alignment of vision above all else.
- Q: Can you develop specific products tailored to our local market?
- A: Absolutely. We leverage our advanced OEM/ODM capabilities to co-develop footwear tailored perfectly to your local regulations and unique regional demands.
- Q: Any exclusive patents or designs?
- A: Yes. We proudly hold the Stitch Down construction patent and offer a lifetime warranty on our quick-dial fit system to keep your users moving with absolute peace of mind.
- Q: How is the product pricing structured?
- A: We provide standard FOB pricing and a suggested MSRP, leaving the final pricing strategy entirely at your discretion to best thrive in your local market.
- Q: Do you accept trial orders?
- A: Always. We gladly welcome trial orders with flexible MOQs to help you test the market waters safely and confidently.
- Q: What is the standard contract period?
- A: We believe in long-term growth. Partnerships typically begin with a renewable 3-year agreement to ensure we build a stable, prosperous market together.
- Q: What marketing support do you provide?
- A: You will have full access to our premium catalogs, images, and videos. We will collaborate closely on asset approvals to ensure a unified, powerful brand presence in your region.
- Q: How are quality issues handled?
- A: We stand by our craftsmanship. On the rare occasion an issue arises, it is resolved promptly via returns or credits per our agreement—because your market reputation is our top priority.
const MQ_MOBILE = window.matchMedia('(max-width: 768px)'); const REDUCE = window.matchMedia('(prefers-reduced-motion: reduce)');
function onResizeFrame(fn){ let raf = 0; window.addEventListener('resize', () => { if (!raf) raf = requestAnimationFrame(() => { raf = 0; fn(); }); }, { passive: true }); }
let navJumping = false;
let navAbort = null; function scrollToY(y){
if (navAbort) navAbort();
const start = window.scrollY, dist = y - start; if (REDUCE.matches || Math.abs(dist) < 4){ window.scrollTo({ top: y, left: 0, behavior: 'instant' }); return; } const ms = Math.min(1100, Math.max(450, Math.abs(dist) * 0.09)); const t0 = performance.now(); const ease = p => (p < 0.5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2); navJumping = true; let done = false; const finish = () => { if (navAbort === stop) { navAbort = null; navJumping = false; } removeEventListener('wheel', stop); removeEventListener('touchstart', stop); removeEventListener('keydown', stop); }; function stop(){ done = true; finish(); } navAbort = stop;
addEventListener('wheel', stop, { passive: true }); addEventListener('touchstart', stop, { passive: true }); addEventListener('keydown', stop);
requestAnimationFrame(function frame(now){ if (done) return; const p = Math.min(1, (now - t0) / ms); window.scrollTo({ top: start + dist * ease(p), left: 0, behavior: 'instant' }); if (p < 1) requestAnimationFrame(frame); else { done = true; finish(); } }); } const IDLE = window.requestIdleCallback || (fn => setTimeout(fn, 1));
function afterLoad(fn){ if (document.readyState === 'complete') IDLE(fn); else window.addEventListener('load', () => IDLE(fn), { once: true }); }
function makeProbe(cssLength, axis){ const el = document.createElement('div'); el.setAttribute('aria-hidden', 'true');
el.style.cssText = 'position:absolute;top:0;left:0;visibility:hidden;pointer-events:none;contain:strict;width:0;height:0'; el.style[axis === 'w' ? 'width' : 'height'] = cssLength; document.body.appendChild(el); return el; }
if (typeof CSSPropertyRule === 'undefined' && !(window.CSS && typeof CSS.registerProperty === 'function')){ document.documentElement.classList.add('no-cssprop'); }
(function(){ const mqM = MQ_MOBILE; const rootS = document.documentElement.style; let vh = window.innerHeight, lastW = window.innerWidth;
function applyVh(){ if (mqM.matches) rootS.removeProperty('--vh-px'); else rootS.setProperty('--vh-px', window.innerHeight + 'px'); } applyVh();
const probe = makeProbe('var(--ch1-runway)', 'h'); const runway = () => probe.offsetHeight; const topOf = el => parseFloat(getComputedStyle(el).top) || 0; const maxY = () => Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
const E = {}, C = {}; function refreshCapture(){ E.ch3 = document.getElementById('ch3'); E.st = E.ch3 && E.ch3.querySelector('.ch3-stage'); E.c4p = document.querySelector('.ch4-pin'); E.c4 = E.c4p && E.c4p.querySelector('.ch4'); E.ch1 = document.getElementById('ch1'); E.s1 = E.ch1 && E.ch1.querySelector('.ch1-sticky');
C.s3 = (E.ch3 && E.st) ? E.ch3.offsetHeight - E.st.offsetHeight : 0; C.s4 = (E.c4p && E.c4) ? E.c4p.offsetHeight - E.c4.offsetHeight : 0; C.rw = runway(); C.t1 = E.s1 ? topOf(E.s1) : 0; C.max = maxY(); } let capStamp = 0; function refreshCaptureStamped(now){ if (now - capStamp < 400) return; capStamp = now; refreshCapture(); } refreshCapture(); window.addEventListener('load', refreshCapture); if (document.fonts && document.fonts.ready) document.fonts.ready.then(refreshCapture); function capture(){ if (C.s3 > 0){ const p = -E.ch3.getBoundingClientRect().top / C.s3; if (p > -0.85 && p < 1) return { k:'ch3', p }; } if (C.s4 > 0){ const p = -E.c4p.getBoundingClientRect().top / C.s4; if (p > -0.85 && p < 1.2) return { k:'ch4', p }; } if (C.rw > 0){ const p = (C.t1 - E.ch1.getBoundingClientRect().top) / C.rw; if (p > 0 && p < 1.4) return { k:'ch1', p }; } return { k:'frac', f: C.max > 0 ? window.scrollY / C.max : 0 }; } function restore(a){ refreshCapture(); if (!a) return; let y = null; if (a.k === 'ch3'){ const ch3 = document.getElementById('ch3'), st = ch3 && ch3.querySelector('.ch3-stage'); if (ch3 && st){ const s = ch3.offsetHeight - st.offsetHeight; if (s > 0) y = ch3.getBoundingClientRect().top + window.scrollY + a.p * s; } } else if (a.k === 'ch4'){ const c4p = document.querySelector('.ch4-pin'), c4 = c4p && c4p.querySelector('.ch4'); if (c4p && c4){ const s = c4p.offsetHeight - c4.offsetHeight; if (s > 0) y = c4p.getBoundingClientRect().top + window.scrollY + a.p * s; } } else if (a.k === 'ch1'){ const ch1 = document.getElementById('ch1'), s1 = ch1 && ch1.querySelector('.ch1-sticky'), rw = runway(); if (ch1 && s1 && rw > 0) y = ch1.getBoundingClientRect().top + window.scrollY - topOf(s1) + a.p * rw; } else y = a.f * maxY(); if (y == null || !isFinite(y)) return; window.scrollTo({ top: Math.min(maxY(), Math.max(0, y)), left: 0, behavior: 'instant' }); }
let resizing = false, anchor = capture(), raf = 0, timer = null;
let capRaf = 0; window.addEventListener('scroll', () => { if (resizing || capRaf) return; capRaf = requestAnimationFrame(now => { capRaf = 0; if (resizing) return; refreshCaptureStamped(now); anchor = capture(); }); }, { passive: true });
function frame(){ raf = 0; applyVh(); restore(anchor); window.dispatchEvent(new Event('scroll')); }
function onResize(){ const isM = mqM.matches; const wC = Math.abs(window.innerWidth - lastW) > 2, hC = Math.abs(window.innerHeight - vh) > 2; if (isM ? !wC : (!wC && !hC)) return; if (!resizing){ resizing = true; anchor = anchor || capture(); } lastW = window.innerWidth; vh = window.innerHeight; if (!raf) raf = requestAnimationFrame(frame); clearTimeout(timer); timer = setTimeout(() => { resizing = false; applyVh(); window.dispatchEvent(new Event('vhchange')); restore(anchor); window.dispatchEvent(new Event('scroll')); }, 120); }
window.addEventListener('resize', onResize, { passive: true }); window.addEventListener('orientationchange', onResize); mqM.addEventListener('change', () => { applyVh(); window.dispatchEvent(new Event('vhchange')); restore(anchor); }); })();
(function(){ const header = document.getElementById('siteHeader'); const btn = document.getElementById('hamburgerBtn'); const nav = document.getElementById('siteNav'); btn.addEventListener('click', () => { const open = header.classList.toggle('is-open'); btn.setAttribute('aria-expanded', open); nav.inert = !open; });
function sizeHam(){ const w = btn.clientWidth, h = btn.clientHeight; if (w > 0 && h > 0) header.style.setProperty('--ham-open-sx', (h / w).toFixed(3)); } sizeHam(); onResizeFrame(sizeHam); })();
const HERO_IMAGES = [ { pc: 'https://img.shoplineapp.com/media/image_clips/6a69af268f5c01d5e25048cb/original.jpg?1785311013=&owner_id=6572a1b95ad192005192397e', m: 'https://img.shoplineapp.com/media/image_clips/6a69af26a427afd0c078a6a2/original.jpg?1785311013=&owner_id=6572a1b95ad192005192397e', alt: 'A worker in protective goggles bends over a workbench on a mid-renovation interior fit-out site, representing PATRONI’s protection for every profession' } ];
(function(){ const media = document.getElementById('heroMedia'); const heroSec = document.querySelector('.hero'); const mqHero = MQ_MOBILE;
function setHeroImgH(){ if (!mqHero.matches) return; const img = media.querySelector('.hero-bg img'); if (img && img.naturalWidth){ const h = media.clientWidth * img.naturalHeight / img.naturalWidth; heroSec.style.setProperty('--hero-img-h', h + 'px'); } }
function setHeroRealH(){ if (!mqHero.matches) { document.documentElement.style.setProperty('--hero-real-h', 'var(--hero-h)'); } else { document.documentElement.style.setProperty('--hero-real-h', heroSec.getBoundingClientRect().height + 'px'); } }
function remeasure(){ setHeroImgH(); requestAnimationFrame(setHeroRealH); }
function watch(im){ im.addEventListener('load', remeasure); if (im.complete) remeasure(); } media.querySelectorAll('img').forEach(watch);
window.addEventListener('vhchange', remeasure); mqHero.addEventListener('change', remeasure);
onResizeFrame(() => { setHeroImgH(); setHeroRealH(); });
if (document.fonts && document.fonts.ready){ document.fonts.ready.then(remeasure); } remeasure();
if (REDUCE.matches) return;
function buildRest(){ HERO_IMAGES.forEach(item => { const pic = document.createElement('picture'); pic.className = 'hero-bg';
const source = document.createElement('source'); source.media = '(max-aspect-ratio: 6/5)'; source.srcset = item.m;
const img = document.createElement('img'); img.src = item.pc; img.alt = item.alt || ''; img.draggable = false; img.decoding = 'async'; img.fetchPriority = 'low';
pic.appendChild(source); pic.appendChild(img); media.appendChild(pic); watch(img); }); startSwap(); }
function startSwap(){ const imgs = media.querySelectorAll('.hero-bg'); if (imgs.length < 2) return; const swapSec = parseFloat( getComputedStyle(document.documentElement).getPropertyValue('--hero-swap-sec') ) || 5; let current = 0, timer = null, onScreen = true; const step = () => { imgs[current].classList.remove('is-active'); current = (current + 1) % imgs.length; imgs[current].classList.add('is-active'); };
const sync = () => { const want = onScreen && !document.hidden; if (want && timer == null) timer = setInterval(step, swapSec * 1000); else if (!want && timer != null){ clearInterval(timer); timer = null; } };
if ('IntersectionObserver' in window){ new IntersectionObserver(es => { onScreen = es[es.length - 1].isIntersecting; sync(); }, { threshold: 0 }).observe(heroSec); } document.addEventListener('visibilitychange', sync); sync(); }
afterLoad(buildRest); })();
const CH1_SLIDES = [ { type: 'video', pc: 'https://cdn.jsdelivr.net/gh/HFCharlieZhu/2BWEB@main/26_PATRONI_Ch3v9.mp4', m: 'https://cdn.jsdelivr.net/gh/HFCharlieZhu/2BWEB@main/26_PATRONI_Ch3v9.mp4', alt: 'PATRONI safety boots across four job scenarios and their key protections: construction (puncture-resistant), semiconductor cleanroom (anti-static), heavy industry (waterproof), and aviation (lightweight)',
title: '',
text: '',
counter: "https://img.shoplineapp.com/media/image_clips/6a60268362f91d92679c4bae/original.png?1784686211=&owner_id=6572a1b95ad192005192397e"
},
{
type: 'image',
pc: 'https://img.shoplineapp.com/media/image_clips/6a69831d0439014cd014d039/original.jpg?1785299741=&owner_id=6572a1b95ad192005192397e',
m: 'https://img.shoplineapp.com/media/image_clips/6a69831d0439014cd014d039/original.jpg?1785299741=&owner_id=6572a1b95ad192005192397e',
alt: 'PATRONI SF2307 Warrior Boot colorways floating in a composite illustration – black, olive green, and tan, with a black/orange-sole pair floating highest at top right',
title: 'Built for Work.
Designed for Life.',
text: '',
counter: "https://img.shoplineapp.com/media/image_clips/6a6026838220b47e9899bbce/original.png?1784686211=&owner_id=6572a1b95ad192005192397e"
},
{
type: 'image',
pc: 'https://img.shoplineapp.com/media/image_clips/6a69b1991919264d063010be/original.jpg?1785311640=&owner_id=6572a1b95ad192005192397e',
m: 'https://img.shoplineapp.com/media/image_clips/6a69b1991919264d063010be/original.jpg?1785311640=&owner_id=6572a1b95ad192005192397e',
alt: 'A worker kneels on a renovation site using a power tool, wearing PATRONI SF2307 Warrior Boots certified to S7S and EN ISO 20345:2022',
title: 'Crafted for Strength.
Certified for Protection.',
text: 'S7S, EN ISO 20345: 2022',
counter: "https://img.shoplineapp.com/media/image_clips/6a6026839e21d32a290863a3/original.png?1784686211=&owner_id=6572a1b95ad192005192397e"
},
{
type: 'image',
pc: 'https://img.shoplineapp.com/media/image_clips/6a69b1983e764bd160dfe925/original.jpg?1785311640=&owner_id=6572a1b95ad192005192397e',
m: 'https://img.shoplineapp.com/media/image_clips/6a69b1983e764bd160dfe925/original.jpg?1785311640=&owner_id=6572a1b95ad192005192397e',
alt: 'Macro illustration of PATRONI’s patented Stitch Down construction – thread hand-stitching the boot upper to the sole for a bond that never delaminates',
title: 'Crafted for Strength.
Certified for Protection.',
text: 'Stitch Down Durable Construction.',
counter: "https://img.shoplineapp.com/media/image_clips/6a602683a19249824d59013f/original.png?1784686211=&owner_id=6572a1b95ad192005192397e"
},
{
type: 'image',
pc: 'https://img.shoplineapp.com/media/image_clips/6a69b1986b6631c1a10fe573/original.jpg?1785311640=&owner_id=6572a1b95ad192005192397e',
m: 'https://img.shoplineapp.com/media/image_clips/6a69b1986b6631c1a10fe573/original.jpg?1785311640=&owner_id=6572a1b95ad192005192397e',
alt: 'A hand adjusting the ExpFit 3D quick-dial lacing 2.0 on a black PATRONI SF2501 Breaker boot, foot resting on scaffolding at a job site',
title: 'Crafted for Strength.
Certified for Protection.',
text: 'Super-Fast Closure System.',
counter: "https://img.shoplineapp.com/media/image_clips/6a602683cfecb6299bbc8a4e/original.png?1784686211=&owner_id=6572a1b95ad192005192397e"
}
];
const CH1_CONFIG = { decorCount: 7, tailScale: 0.8, tailOpacity: 0.25, exitShift: 1.5, tailSteps: 1 };
(function(){ const section = document.getElementById('ch1'); const deck = document.getElementById('ch1Deck'); const copyEl = document.getElementById('ch1Copy'); const counterImg = document.getElementById('ch1CounterImg'); const sticky = section.querySelector('.ch1-sticky'); const stack = section.querySelector('.ch1-stack'); const inner = section.querySelector('.ch1-inner'); const copyH2 = copyEl.querySelector('h2'); const copyP = copyEl.querySelector('p'); const mq = MQ_MOBILE; const rootEl = document.documentElement; const rootStyle = getComputedStyle(rootEl);
const slides = CH1_SLIDES; const total = slides.length;
slides[0].title = copyH2.innerHTML; slides[0].text = copyP.innerHTML;
const decor = CH1_CONFIG.decorCount; const poolCount = total + decor;
const steps = (total - 1 + (CH1_CONFIG.tailSteps || 0));
rootEl.style.setProperty('--ch1-steps', steps);
const runwayProbe = makeProbe('var(--ch1-runway)', 'h');
let RUNWAY = 0, STEP = 1, STICKY_TOP = 0, GAP_PX = 0, EXIT_PX = 0, VERTICAL = false, FADE_MS = 200;
function readGeometry(){ RUNWAY = runwayProbe.offsetHeight || 0; STEP = RUNWAY > 0 ? Math.max(1, RUNWAY / steps) : Math.max(1, window.innerHeight * 0.8); STICKY_TOP = parseFloat(getComputedStyle(sticky).top) || 0;
VERTICAL = mq.matches; const nx = parseFloat(rootStyle.getPropertyValue('--ch1-card-w-x')) || (VERTICAL ? 22 : 13); const xu = deck.offsetWidth / (nx || 1); GAP_PX = xu * (parseFloat(rootStyle.getPropertyValue('--ch1-gap')) || 0.5); EXIT_PX = xu * CH1_CONFIG.exitShift;
const f = parseFloat(rootStyle.getPropertyValue('--ch1-copy-fade'));
FADE_MS = (isFinite(f) && f > 0 ? f : 0.2) * 1000 + 20; }
let probe = null; function buildProbe(){ const pr = copyEl.cloneNode(false); pr.removeAttribute('id'); pr.setAttribute('aria-hidden', 'true'); pr.classList.add('ch1-copy-probe');
const h2s = [], ps = []; for (let i = 0; i < total; i++){ const h = copyH2.cloneNode(false); const p = copyP.cloneNode(false); h.removeAttribute('id'); p.removeAttribute('id'); h.innerHTML = slides[i].title; p.innerHTML = slides[i].text || ''; pr.appendChild(h); pr.appendChild(p); h2s.push(h); ps.push(p); } inner.appendChild(pr); return { pr, h2s, ps }; } let lastKey = ''; function measureCh1(force){ const w = Math.round(copyEl.clientWidth); if (w <= 0) return; const key = w + '|' + (mq.matches ? 1 : 0); if (!force && key === lastKey) return; lastKey = key; if (!probe) probe = buildProbe(); probe.pr.style.width = w + 'px'; let a = 0, b = 0; for (let i = 0; i < total; i++){ const ha = probe.h2s[i].offsetHeight; if (ha > a) a = ha; const pb = probe.ps[i].offsetHeight; if (pb > b) b = pb; } if (a > 0) rootEl.style.setProperty('--ch1-h2-min-h', a + 'px'); if (b > 0) rootEl.style.setProperty('--ch1-sub-min-h', b + 'px'); }
function syncMobileBlockH(){ if (!mq.matches){ rootEl.style.removeProperty('--ch1-block-h-m'); return; } const h = stack.offsetTop + stack.offsetHeight; if (h <= 0) return; const cur = parseFloat(rootEl.style.getPropertyValue('--ch1-block-h-m')) || 0; if (Math.abs(cur - h) > 1) rootEl.style.setProperty('--ch1-block-h-m', h + 'px'); }
let pending = null, copyTimer = 0, counterSrc = '';
function setCounter(src, n){ if (src && counterSrc !== src){ counterSrc = src; counterImg.src = src; } counterImg.alt = 'Slide ' + (n + 1) + ' of ' + total; }
function paintCopy(t, x){ if (copyH2.innerHTML !== t) copyH2.innerHTML = t; if (copyP.innerHTML !== x) copyP.innerHTML = x; copyH2.style.opacity = '1'; copyP.style.opacity = '1'; }
function flushCopy(){ if (copyTimer){ clearTimeout(copyTimer); copyTimer = 0; } const p = pending; pending = null; if (p){ setCounter(p.c, p.n); paintCopy(p.t, p.x); } else { copyH2.style.opacity = '1'; copyP.style.opacity = '1'; } }
function updateCopy(){ const n = idx < 0 ? 0 : (idx > total - 1 ? total - 1 : idx); const s = slides[n]; const t = s.title, x = s.text || '';
if (!pending && copyH2.innerHTML === t && copyP.innerHTML === x){ setCounter(s.counter, n); copyH2.style.opacity = '1'; copyP.style.opacity = '1'; return; }
pending = { t, x, c: s.counter, n };
if (copyH2.innerHTML !== t) copyH2.style.opacity = '0'; if (copyP.innerHTML !== x) copyP.style.opacity = '0';
if (!copyTimer){ copyTimer = setTimeout(() => { copyTimer = 0; const p = pending; pending = null; if (!p) return; setCounter(p.c, p.n); paintCopy(p.t, p.x); }, FADE_MS); } }
function mediaURL(s){ return mq.matches ? s.m : s.pc; }
function buildNode(s){ if (s.type === 'video'){ const v = document.createElement('video'); v.muted = true; v.loop = true; v.playsInline = true; v.setAttribute('muted',''); v.setAttribute('playsinline',''); v.setAttribute('controlsList', 'nodownload nofullscreen noremoteplayback noplaybackrate'); v.disablePictureInPicture = true; v.disableRemotePlayback = true;
v.preload = 'metadata'; v.src = mediaURL(s); v.poster = 'https://img.shoplineapp.com/media/image_clips/6a6ccacb398b0d8d7f8b740f/original.jpg?1785514698=&owner_id=6572a1b95ad192005192397e'; v.setAttribute('aria-label', s.alt || ''); return v; } const img = document.createElement('img'); img.decoding = 'async'; img.fetchPriority = 'low'; img.src = mediaURL(s); img.alt = s.alt || ''; img.draggable = false; return img; } function nodeFor(s){ if (!s._node) s._node = buildNode(s); return s._node; }
let warmed = false; function warmSlides(){ if (warmed) return; warmed = true;
afterLoad(() => { slides.forEach(s => { nodeFor(s); const c = new Image(); c.src = s.counter; }); }); } if ('IntersectionObserver' in window){ const warmIO = new IntersectionObserver(es => { if (es.some(e => e.isIntersecting)){ warmSlides(); warmIO.disconnect(); } }, { rootMargin: '150% 0px 150% 0px', threshold: 0 }); warmIO.observe(section); } else { window.addEventListener('load', warmSlides); }
const cards = []; for (let b = 0; b < poolCount; b++){ const el = document.createElement('div'); el.className = 'ch1-card'; deck.appendChild(el); cards.push({ el, base: b, hasMedia: false, tf: '', op: -1, z: NaN, wc: null, mixK: -1, front: null }); } let idx = -1, docHidden = !!document.hidden, inView = true; function attachMedia(c){ const wrap = document.createElement('div'); wrap.className = 'ch1-card-media'; wrap.appendChild(nodeFor(slides[c.base])); c.el.appendChild(wrap); c.hasMedia = true; requestAnimationFrame(() => requestAnimationFrame(() => wrap.classList.add('is-on'))); }
function setVideoState(v, on){ if (!v || v._want === on) return; v._want = on; if (on){ const p = v.play(); if (p && p.catch) p.catch(() => { v._want = null; }); } else { v.pause(); } }
const mixCache = new Array(decor + 1); function mixFor(kk){ let m = mixCache[kk]; if (!m){ const pct = (1 - (1 - CH1_CONFIG.tailOpacity) * (kk / decor)) * 100; m = mixCache[kk] = { bg: 'color-mix(in srgb, var(--paper) ' + pct + '%, var(--brand))', bd: 'color-mix(in srgb, var(--ch1-card-line) ' + pct + '%, var(--brand))' }; } return m; }
function layout(){ for (let i = 0; i < cards.length; i++){ const c = cards[i]; const k = c.base - idx; let transform, opacity; if (k < 0){ transform = VERTICAL ? 'translateY(' + (-EXIT_PX).toFixed(2) + 'px)' : 'translateX(' + (-EXIT_PX).toFixed(2) + 'px)'; opacity = 0; } else { const kk = k < decor ? k : decor; const scale = 1 - (1 - CH1_CONFIG.tailScale) * (kk / decor); const d = GAP_PX * kk; transform = (VERTICAL ? 'translateY(' + d.toFixed(2) + 'px)' : 'translateX(' + d.toFixed(2) + 'px)') + ' scale(' + scale.toFixed(4) + ')'; opacity = (k > decor) ? 0 : 1;
if (c.mixK !== kk){ c.mixK = kk; const m = mixFor(kk); c.el.style.background = m.bg; c.el.style.borderColor = m.bd; } }
if (c.tf !== transform){ c.tf = transform; c.el.style.transform = transform; } if (c.op !== opacity){ c.op = opacity; c.el.style.opacity = opacity; }
const z = 60 - k; if (c.z !== z){ c.z = z; c.el.style.zIndex = z; }
const wc = (k === 0 || k === 1) ? 'transform, opacity' : ''; if (c.wc !== wc){ c.wc = wc; c.el.style.willChange = wc; }
const isFront = (k === 0 && c.base < total); if (c.front !== isFront){ c.front = isFront; c.el.classList.toggle('is-front', isFront); } if (isFront && !c.hasMedia) attachMedia(c); if (c.hasMedia && slides[c.base].type === 'video'){ setVideoState(slides[c.base]._node, isFront && inView && !docHidden); } } } function syncVideos(){ for (let i = 0; i < cards.length; i++){ const c = cards[i]; if (!c.hasMedia) continue; const s = slides[c.base]; if (s.type === 'video') setVideoState(s._node, c.front && inView && !docHidden); } } if ('IntersectionObserver' in window){ inView = false; new IntersectionObserver(es => { const e = es[es.length - 1];
inView = e.isIntersecting && e.intersectionRatio > 0.01; syncVideos(); }, { rootMargin: '0px', threshold: [0, 0.02, 0.25] }).observe(sticky); }
const ONE_PER_SWIPE = matchMedia('(hover: none) and (pointer: coarse)').matches; let touchActive = false, gestureStartIdx = -1, gestureTargetIdx = -1, idleTimer = null;
function sectionTop(){ return section.getBoundingClientRect().top + window.scrollY; } function stepY(k){ return sectionTop() - STICKY_TOP + STEP * k; }
function resetGesture(){
if (navJumping){ clearTimeout(idleTimer); idleTimer = setTimeout(resetGesture, 120); return; } touchActive = false; gestureStartIdx = -1; gestureTargetIdx = -1; }
if (ONE_PER_SWIPE){ window.addEventListener('touchstart', (e) => { if (navJumping) return; clearTimeout(idleTimer); touchActive = true; gestureStartIdx = idx; gestureTargetIdx = -1; }, { passive: true });
window.addEventListener('touchend', () => { clearTimeout(idleTimer); idleTimer = setTimeout(resetGesture, 400); }, { passive: true });
window.addEventListener('touchcancel', resetGesture, { passive: true }); }
let scrollRaf = 0;
function applyScroll(){ if (scrollRaf){ cancelAnimationFrame(scrollRaf); scrollRaf = 0; }
const rect = section.getBoundingClientRect(); const raw = (STICKY_TOP - rect.top) / STEP;
const rEps = raw + 1e-3; let n = rEps <= 0 ? 0 : (rEps >= total - 1 ? total - 1 : (rEps | 0));
if (ONE_PER_SWIPE && idx >= 0 && (touchActive || gestureTargetIdx !== -1)){ const y = window.scrollY; const minY = stepY(0); const maxY = stepY(total - 1);
if (y < minY - 20 || y > maxY + STEP * 0.8){ resetGesture(); } else if (gestureTargetIdx !== -1){
n = gestureTargetIdx; } else if (touchActive && !navJumping){ const r2 = raw + 0.1; const rawIdx = r2 <= 0 ? 0 : (r2 >= total - 1 ? total - 1 : (r2 | 0)); if (rawIdx !== gestureStartIdx){ gestureTargetIdx = Math.max(0, Math.min(total - 1, gestureStartIdx + Math.sign(rawIdx - gestureStartIdx))); n = gestureTargetIdx; scrollToY(stepY(n)); } else { n = gestureStartIdx; } } else { n = gestureStartIdx; } }
if (n !== idx){ const first = idx === -1; idx = n; layout(); if (first) setCounter(slides[idx].counter, idx); else updateCopy(); } }
function onScroll(){ if (!scrollRaf) scrollRaf = requestAnimationFrame(applyScroll); }
function refresh(force){ measureCh1(force); syncMobileBlockH(); readGeometry(); if (idx >= 0) layout(); applyScroll(); }
mq.addEventListener('change', () => { slides.forEach(s => { if (!s._node) return; const url = mediaURL(s); if (s._node.getAttribute('src') !== url) s._node.src = url; }); lastKey = ''; refresh(true); });
window.addEventListener('vhchange', () => refresh(true)); onResizeFrame(() => refresh(false));
window.addEventListener('scroll', onScroll, { passive: true });
document.addEventListener('visibilitychange', () => { docHidden = document.hidden; if (docHidden){ flushCopy(); syncVideos(); } else { if (idx >= 0) layout(); applyScroll(); syncVideos(); } });
window.addEventListener('pagehide', flushCopy);
if (document.fonts && document.fonts.ready){ document.fonts.ready.then(() => refresh(true)); }
readGeometry(); refresh(true); })();
(function(){ const inner = document.querySelector('.ch2-inner'); if (!inner) return; const head = inner.querySelector('.ch2-head'); const marquee = inner.querySelector('.ch2-marquee');
function measureCh2(){ const h = Math.round(inner.offsetHeight); if (h > 0) { const cur = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--ch2-sec-h')) || 0; if (Math.abs(cur - h) > 2) { document.documentElement.style.setProperty('--ch2-sec-h', h + 'px'); } } if (head){ const fade = Math.round(head.offsetTop + head.offsetHeight); if (fade > 0) { const cur = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--ch2-fade-h')) || 0; if (Math.abs(cur - fade) > 2) { document.documentElement.style.setProperty('--ch2-fade-h', fade + 'px'); } } } if (marquee){ const fb = Math.round(inner.offsetHeight - marquee.offsetTop); if (fb > 0) { const cur = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--ch2-fade-bottom-h')) || 0; if (Math.abs(cur - fb) > 2) { document.documentElement.style.setProperty('--ch2-fade-bottom-h', fb + 'px'); } } } }
measureCh2(); window.addEventListener('load', measureCh2); window.addEventListener('vhchange', measureCh2);
onResizeFrame(measureCh2); if (document.fonts && document.fonts.ready) document.fonts.ready.then(measureCh2); })();
(function(){ const map = document.getElementById('ch2Map'); if (!map) return;
if (!('IntersectionObserver' in window) || REDUCE.matches){ map.classList.add('is-loaded', 'is-live'); return; }
const loadIO = new IntersectionObserver(es => { if (!es.some(e => e.isIntersecting)) return; loadIO.disconnect(); afterLoad(() => map.classList.add('is-loaded')); }, { rootMargin: '200% 0px 200% 0px', threshold: 0 }); loadIO.observe(map);
let onScreen = false; const sync = () => map.classList.toggle('is-live', onScreen && !document.hidden);
new IntersectionObserver(es => { onScreen = es[es.length - 1].isIntersecting; sync(); }, { rootMargin: '25% 0px 25% 0px', threshold: 0 }).observe(map);
document.addEventListener('visibilitychange', sync); })();
(function(){ const track = document.getElementById('ch2Track'); const set = track && track.querySelector('.ch2-set'); if (!track || !set) return;
const clone = set.cloneNode(true); clone.setAttribute('aria-hidden', 'true'); clone.querySelectorAll('img').forEach(im => im.setAttribute('alt', '')); track.appendChild(clone);
const SPEED_PER_X = 2.67; const xProbe = makeProbe('var(--x)', 'w');
let setWidth = 0; let speed = 160; function measure(){ const w = set.getBoundingClientRect().width; if (w > 0) setWidth = w; const xw = xProbe.offsetWidth; if (xw > 0) speed = xw * SPEED_PER_X; } measure(); if ('ResizeObserver' in window){ const ro = new ResizeObserver(measure); ro.observe(set); ro.observe(xProbe); } else { window.addEventListener('resize', measure, { passive:true }); }
let x = 0; let last = null; let rafId = null;
function frame(now){ rafId = requestAnimationFrame(frame); if (last == null){ last = now; return; } const dt = (now - last) / 1000; last = now; if (REDUCE.matches || setWidth <= 0) return; x -= dt * speed; const pos = (-x) % setWidth; x = -pos; track.style.transform = 'translate3d(' + x.toFixed(2) + 'px,0,0)'; } function start(){ if (rafId != null) return; last = null; track.style.willChange = 'transform'; rafId = requestAnimationFrame(frame); } function stop(){ if (rafId == null) return; cancelAnimationFrame(rafId); rafId = null; track.style.willChange = 'auto'; } let onScreen = true; const sync = () => { (onScreen && !document.hidden && !REDUCE.matches) ? start() : stop(); };
if ('IntersectionObserver' in window){ onScreen = false; new IntersectionObserver(es => { onScreen = es[es.length - 1].isIntersecting; sync(); }, { rootMargin: '25% 0px 25% 0px', threshold: 0 }).observe(track); } document.addEventListener('visibilitychange', sync); sync();
})();
(function(){ const inner = document.querySelector('.ch3-inner'); if (!inner) return; const card = inner.querySelector('.ch3-card'); const head = inner.querySelector('.ch3-head'); const body = inner.querySelector('.ch3-body'); const contact = inner.querySelector('.ch3-contact'); const root = document.documentElement;
function vspace(el, withHeight){ if (!el) return 0; const s = getComputedStyle(el); return (withHeight ? el.offsetHeight : 0) + (parseFloat(s.marginTop) || 0) + (parseFloat(s.marginBottom)|| 0); }
function measureCh3() { const cs = getComputedStyle(inner); const padV = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0);
const fixed = Math.round( padV + vspace(head, true) + vspace(body, true) + vspace(contact, true) + vspace(card, false) ); if (fixed > 0) root.style.setProperty('--ch3-fixed-h', fixed + 'px'); }
measureCh3(); window.addEventListener('load', measureCh3); window.addEventListener('vhchange', measureCh3);
onResizeFrame(measureCh3); if (document.fonts && document.fonts.ready) document.fonts.ready.then(measureCh3); })();
(function(){ const root = document.getElementById('ch3'); if (!root) return; const track = root; const stage = root.querySelector('.ch3-stage'); const canvas = root.querySelector('.ch3-canvas'); const poster = root.querySelector('.ch3-poster'); const zoom = root.querySelector('.ch3-zoom'); const bg = root.querySelector('.ch3-bg'); if (!track || !stage || !canvas || !poster || !zoom || !bg) return;
const TOTAL = parseInt(root.dataset.frames || '121', 10);
const PLAY_FRACTION = Math.max(0.001, Math.min(1, parseFloat(root.dataset.playFraction || '1'))); const BASE = root.dataset.src || 'https://cdn.jsdelivr.net/gh/HFCharlieZhu/2BWEB@main/SF2307NTO/frame_'; const BG_SRC = root.dataset.bgSrc || ''; if (BG_SRC) bg.src = BG_SRC;
const BG_W = parseInt(root.dataset.bgW || '0', 10); const BG_H = parseInt(root.dataset.bgH || '0', 10); const CROP_X = parseInt(root.dataset.cropX || '0', 10); const CROP_Y = parseInt(root.dataset.cropY || '0', 10); const CONCURRENCY = 6; const FALLBACK_MS = 6000; const ctx = canvas.getContext('2d'); const pad = n => String(n).padStart(3, '0');
let targetFrame = 0, currentFrame = -1, rafPending = false, inited = false;
const reduced = REDUCE.matches;
if (reduced) document.documentElement.style.setProperty('--ch3-runway', '0px');
let fbFired = false; function useFallback(){ if (inited || fbFired) return; fbFired = true; poster.style.display = 'block'; zoom.style.display = 'none'; } let fbTimer = null;
const frames = new Array(TOTAL); let nextIdx = 0, activeLoads = 0, loadingOn = false, io = null; const ready = i => { const f = frames[i]; return !!(f && f.complete && f.naturalWidth > 0); };
function initCanvas(){ if (inited || !ready(0)) return; inited = true; if (fbTimer) clearTimeout(fbTimer); canvas.width = BG_W || frames[0].naturalWidth; canvas.height = BG_H || frames[0].naturalHeight; ctx.drawImage(frames[0], CROP_X, CROP_Y); zoom.style.display = 'block'; poster.style.display = 'none'; onScroll(); } function loadOne(i){ activeLoads++; const img = new Image(); frames[i] = img; img.onload = () => { activeLoads--; if (i === 0) initCanvas(); if (inited && !rafPending){ currentFrame = -1; rafPending = true; requestAnimationFrame(draw); } pump(); }; img.onerror = () => { activeLoads--; if (i === 0) useFallback(); pump(); }; img.src = BASE + pad(i + 1) + '.webp'; } function pump(){ if (!loadingOn) return; while (activeLoads < CONCURRENCY && nextIdx < TOTAL) loadOne(nextIdx++); if (nextIdx >= TOTAL && activeLoads === 0 && io){ io.disconnect(); io = null; } }
if (!reduced && 'IntersectionObserver' in window){ io = new IntersectionObserver(entries => { const en = entries[entries.length - 1]; if (en.isIntersecting){ loadingOn = true; if (!fbTimer && !inited && !fbFired) fbTimer = setTimeout(useFallback, FALLBACK_MS); pump(); } else { loadingOn = false; if (fbTimer && !inited){ clearTimeout(fbTimer); fbTimer = null; } } }, { rootMargin: '150% 0px 150% 0px', threshold: 0 }); io.observe(track); } else if (!reduced){ loadingOn = true; fbTimer = setTimeout(useFallback, FALLBACK_MS); pump(); }
function draw(){ rafPending = false; if (!inited || targetFrame === currentFrame) return; let fi = targetFrame; if (!ready(fi)){ for (let d = 1; d < TOTAL; d++){ const lo = targetFrame - d, hi = targetFrame + d; if (lo >= 0 && ready(lo)){ fi = lo; break; } if (hi < TOTAL && ready(hi)){ fi = hi; break; } } } if (ready(fi)){ ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(frames[fi], CROP_X, CROP_Y); currentFrame = targetFrame; } } let nearby = true, scrollable = 1; function measureTrack(){ scrollable = Math.max(1, track.offsetHeight - stage.offsetHeight); } function onScroll(){ if (reduced || !nearby) return; const rect = track.getBoundingClientRect(); const progress = Math.max(0, Math.min(1, -rect.top / scrollable)); const playProgress = Math.min(1, progress / PLAY_FRACTION); targetFrame = Math.min(TOTAL - 1, Math.round(playProgress * (TOTAL - 1))); if (!rafPending){ rafPending = true; requestAnimationFrame(draw); } } if (!reduced && 'IntersectionObserver' in window){ nearby = false; new IntersectionObserver(es => { nearby = es[es.length - 1].isIntersecting; if (nearby){ measureTrack(); onScroll(); } }, { rootMargin: '50% 0px 50% 0px', threshold: 0 }).observe(track); }
window.addEventListener('scroll', onScroll, { passive: true }); window.addEventListener('vhchange', () => { measureTrack(); onScroll(); }); onResizeFrame(() => { measureTrack(); onScroll(); }); measureTrack(); onScroll();
})();
(function(){ const ch3 = document.getElementById('ch3'); const stage = ch3 && ch3.querySelector('.ch3-stage'); const ch2s = document.querySelector('.ch2-sticky'); if (!ch3 || !stage || !ch2s) return;
let curDy = 0, rafId = null, lastY = -1, idle = 0;
ch2s.style.willChange = 'transform'; ch2s.style.transform = 'translate3d(0,0,0)';
const gapProbe = makeProbe('var(--ch2-hold-gap)', 'h'); let gapCache = 0; function measureGap(){ gapCache = gapProbe.offsetHeight || 0; } function gapPx(){ return gapCache; } measureGap();
function pinnedTop(){ return parseFloat(getComputedStyle(stage).top) || 0; }
let fixedOn = false; function toFollow(){ if (!fixedOn) return; fixedOn = false; curDy = 0; ch2s.style.position = ''; ch2s.style.left = ''; ch2s.style.right = ''; ch2s.style.top = ''; ch2s.style.transform = 'translate3d(0,0,0)'; } function setDy(dy){ if (fixedOn) return; if (Math.abs(dy - curDy) < 0.02) return; curDy = dy; ch2s.style.transform = 'translate3d(0,' + dy + 'px,0)'; } function setFixed(top){ if (!fixedOn){ fixedOn = true; curDy = 0; ch2s.style.transform = 'none'; ch2s.style.position = 'fixed'; ch2s.style.left = '0'; ch2s.style.right = '0'; } const cur = parseFloat(ch2s.style.top); if (isNaN(cur) || Math.abs(cur - top) > 0.5) ch2s.style.top = top + 'px'; }
function update(){ const s = stage.getBoundingClientRect(); const pt = pinnedTop(); if (s.top > pt + 1 || s.bottom <= 0){ toFollow(); setDy(0); return false; } const pinned = (pt - s.top) <= 1; if (fixedOn && pinned){ setFixed(pt - gapPx() - ch2s.offsetHeight); return true; } if (fixedOn) toFollow(); const c = ch2s.getBoundingClientRect(); const naturalBottom = c.bottom - curDy; const dyWanted = (s.top - gapPx()) - naturalBottom; if (pinned && dyWanted > 0.5){ setFixed(pt - gapPx() - ch2s.offsetHeight); return true; } setDy(Math.max(0, dyWanted)); return true; }
function tick(){ const y = window.scrollY; const moved = (y !== lastY); lastY = y; const active = update(); if (!active){ rafId = null; return; } idle = moved ? 0 : idle + 1; rafId = (idle < 4) ? requestAnimationFrame(tick) : null; } function ensureTick(){ if (rafId == null){ idle = 0; rafId = requestAnimationFrame(tick); } } window.addEventListener('scroll', () => { update(); ensureTick(); }, { passive: true }); window.addEventListener('vhchange', () => { measureGap(); ensureTick(); }); window.addEventListener('resize', ensureTick); ensureTick(); })();
(function(){
document.querySelectorAll('.ch4-item .ch4-q').forEach(btn => { btn.addEventListener('mousedown', e => e.preventDefault()); btn.addEventListener('click', () => { const item = btn.closest('.ch4-item'); const open = item.classList.toggle('is-open'); btn.setAttribute('aria-expanded', open ? 'true' : 'false'); }); });
const inner = document.querySelector('.ch4-inner'); if (inner){ const root = document.documentElement; let last4 = 0; function measureCh4(){ const h = Math.round(inner.offsetHeight); if (h > 0 && Math.abs(h - last4) > 1){ last4 = h; root.style.setProperty('--ch4-sec-h', h + 'px'); } } const ro = new ResizeObserver(measureCh4); ro.observe(inner); measureCh4(); window.addEventListener('load', measureCh4); window.addEventListener('vhchange', measureCh4); if (document.fonts && document.fonts.ready) document.fonts.ready.then(measureCh4); } })();
(function(){ const btn = document.getElementById('footerBackTop'); if (!btn) return; btn.addEventListener('click', (e) => { e.preventDefault(); scrollToY(0); }); })();
document.addEventListener('contextmenu', e => {
const t = e.target;
if (!t || !t.tagName) return;
if (t.tagName === 'IMG' || t.tagName === 'CANVAS' || t.tagName === 'VIDEO' || t.closest('picture')) e.preventDefault();
});




