/* * BIOTEN Developer Home V7.4.0 * Page-scoped bilingual content, accessible workbench/passport tabs, * IntersectionObserver reveals and semantic evidence-spine particles. */ (function () { 'use strict'; const EN = Object.freeze({ 'hero.kicker': 'BIOTEN DEVELOPER', 'hero.title.1': 'Turn real application problems', 'hero.title.2': 'into verifiable materials-development tasks.', 'hero.lead': 'From material roles, formulation variables and process windows to application samples, validation records and stage decisions, the platform coordinates sample validation, pilot scale-up and production introduction.', 'hero.cta.primary': 'Propose a Development Task', 'hero.cta.secondary': 'View the Development Method', 'hero.visual.aria': 'Animated diagram showing dispersed problem seeds clustering into hypotheses and connecting into a validation and evidence path', 'hero.flow.problem': 'Problem', 'hero.flow.hypothesis': 'Hypothesis', 'hero.flow.variable': 'Variable', 'hero.flow.sample': 'Sample', 'hero.flow.evidence': 'Evidence', 'hero.visual.caption.label': 'Evidence Spine', 'hero.visual.caption.value': 'From dispersed problem seeds to a reviewable stage decision', 'hero.index.aria': 'Developer homepage sections', 'hero.index.engine': 'Development Method', 'hero.index.engine.detail': 'Problem to stage decision', 'hero.index.workbenches': 'Application Workbenches', 'hero.index.workbenches.detail': 'Objects and validation paths', 'hero.index.network': 'Collaboration Roles', 'hero.index.network.detail': 'Responsibilities and access', 'hero.index.evidence': 'Evidence Passport', 'hero.index.evidence.detail': 'Versions and applicability', 'engine.kicker': 'DEVELOPMENT LOOP', 'engine.title': 'From problem definition to a verifiable stage decision.', 'engine.intro': 'First define the application object, use conditions and failure mode. Then establish falsifiable hypotheses and define material roles, variables, controls, evaluation methods and stop criteria. Every stage retains its version, result, failure and unknowns.', 'engine.rail.aria': 'Seven-step development path from a real problem to a stage decision', 'engine.step1.title': 'Real Problem', 'engine.step1.copy': 'Object, conditions, baseline and failure.', 'engine.step2.title': 'Material Role', 'engine.step2.copy': 'Barrier, film former, carrier or interface modifier.', 'engine.step3.title': 'Formulation Variables', 'engine.step3.copy': 'Compatibility, rheology, wetting and component relationships.', 'engine.step4.title': 'Process Window', 'engine.step4.copy': 'Shear, coating, drying, forming and storage.', 'engine.step5.title': 'Application Sample', 'engine.step5.copy': 'Versioned materials, structures and representative articles.', 'engine.step6.title': 'Validation Record', 'engine.step6.copy': 'Method, conditions, results, failures and unknowns.', 'engine.step7.title': 'Stage Decision', 'engine.step7.copy': 'Continue, supplement, reroute or stop.', 'engine.caption.label': 'Output Boundary', 'engine.caption.value': 'One sample or one test does not automatically establish a finished-article, production-line, regulatory or market conclusion.', 'workbench.kicker': 'APPLICATION WORKBENCHES', 'workbench.title': 'Different application objects require different development paths.', 'workbench.intro': 'BIOTEN Packaging focuses on fiber-based barrier systems engineering. Agricultural ecology, personal care and bio-based coatings enter controlled joint development by object. Materials, formulations, processes and validation boundaries are managed separately.', 'workbench.tabs.aria': 'Choose an application workbench', 'workbench.packaging.weight': 'Priority Industrial Application', 'workbench.controlled': 'Controlled Joint Development', 'workbench.packaging.title': 'BIOTEN Packaging', 'workbench.packaging.short': 'Fiber-based barrier systems engineering', 'workbench.agriculture.title': 'Agricultural Ecology', 'workbench.agriculture.short': 'Seed coating and agrochemical systems', 'workbench.care.title': 'Personal Care', 'workbench.care.short': 'Formulation compatibility and sensory interfaces', 'workbench.coatings.title': 'Bio-based Coatings', 'workbench.coatings.short': 'Waterborne film formation and functional carriers', 'workbench.packaging.alt': 'Fiber-based packaging development workbench with paper cups, cartons, molded-fiber articles and laboratory equipment', 'workbench.packaging.figure.label': 'PACKAGING / PRIORITY', 'workbench.packaging.figure.value': 'From paper and coating to formed articles and target markets', 'workbench.packaging.eyebrow': 'BIOTEN PACKAGING', 'workbench.packaging.heading': 'Fiber-based Barrier Systems Engineering', 'workbench.packaging.summary': 'For paper cups, cartons, bags, coated paper and molded-fiber articles, the workbench identifies the specific role of PHA waterborne functional emulsions within barrier, heat-seal, surface and interface systems.', 'workbench.fact.problem': 'Problem Object', 'workbench.fact.variables': 'Key Variables', 'workbench.fact.validation': 'Validation', 'workbench.packaging.problem': 'Paper porosity, contents, converting process, use conditions and end-of-life target', 'workbench.packaging.variables': 'Substrate, coat weight, drying window, formulation compatibility, creasing and heat-seal conditions', 'workbench.packaging.validation': 'Barrier and sealing, processing integrity, migration/target market, finished articles and the intended end-of-life route', 'workbench.packaging.boundary': 'Material or coating properties do not automatically establish food-contact, fluorine-free/PFAS, recyclability, repulpability, compostability or finished-article conclusions.', 'workbench.packaging.cta': 'Enter the Packaging Development Path', 'workbench.agriculture.alt': 'Agricultural application workbench with corn, wheat, soybeans, coated seed and waterborne formulations', 'workbench.agriculture.figure.value': 'From seeds and active systems to storage, sowing and staged validation', 'workbench.agriculture.heading': 'Seed Coating and Agrochemical Systems', 'workbench.agriculture.summary': 'The route studies practical windows for waterborne film formation, binding and functional carriers in seed or agrochemical systems, addressing compatibility, processing and release before higher-level validation.', 'workbench.agriculture.problem': 'Crop seed, active ingredients, coating structure, storage and sowing conditions', 'workbench.agriculture.variables': 'Solids, viscosity, adhesion, active compatibility, drying, dust-off and release window', 'workbench.agriculture.validation': 'Coating integrity, storage stability, release, germination and applicable field conditions', 'workbench.agriculture.boundary': 'Material film formation or release behavior cannot replace conclusions on crop safety, germination, field performance or agrochemical compliance.', 'workbench.agriculture.cta': 'Enter Agricultural Joint Development', 'workbench.care.alt': 'Personal-care application workbench with cleansing, bath and laundry formulations and waterborne emulsions', 'workbench.care.figure.value': 'From complete formulation relationships to stability, use and safety boundaries', 'workbench.care.heading': 'Personal-care Formulation and Sensory Interface', 'workbench.care.summary': 'Candidate materials return to the complete surfactant, rheology, electrolyte and preservation system to study their roles in film formation, delivery and interface modification.', 'workbench.care.problem': 'Complete component relationships in cleansing, bath, laundry and care formulations', 'workbench.care.variables': 'Surfactants, pH, electrolytes, rheology, temperature cycling and order of addition', 'workbench.care.validation': 'Compatibility, stability, sensory response, rinsing, pack compatibility and project-specific safety assessment', 'workbench.care.boundary': 'Formulation compatibility or sensory observation is not evidence of efficacy, safety, skin suitability or a public claim.', 'workbench.care.cta': 'Enter Personal-care Joint Development', 'workbench.coatings.alt': 'Bio-based coating development workbench with waterborne emulsion, drawdown panels, wood coatings and application tools', 'workbench.coatings.figure.value': 'From emulsion and substrate to application, film and aging conditions', 'workbench.coatings.heading': 'Waterborne Film Formation and Functional Carriers', 'workbench.coatings.summary': 'The route identifies conditions for PHA waterborne functional emulsions as components or functional carriers by studying coalescence, pigment/filler compatibility, substrate interfaces and application windows.', 'workbench.coatings.problem': 'Formulations, substrates and use environments for waterborne, wood and functional coatings', 'workbench.coatings.variables': 'Binder compatibility, pigments/fillers, additives, film-formation temperature, application and drying', 'workbench.coatings.validation': 'In-can stability, application, adhesion, film state, durability and representative aging', 'workbench.coatings.boundary': 'Bio-based origin or formulation inclusion does not automatically establish low-carbon, durability, environmental-safety or complete-coating performance conclusions.', 'workbench.coatings.cta': 'Enter Coatings Joint Development', 'network.kicker': 'COLLABORATION ROLES', 'network.title': 'Four roles complete one development loop.', 'network.intro': 'Brand and application owners define the real problem. Materials R&D builds the hypothesis. Formulation and processing create manufacturable samples. Testing and validation record results, failures and boundaries. Each project is manually reviewed before its public, request-based or NDA path is confirmed.', 'network.role1.label': 'BRAND / APPLICATION', 'network.role1.title': 'Brand and Application Owner', 'network.role2.label': 'MATERIAL R&D', 'network.role2.title': 'Materials R&D', 'network.role3.label': 'FORMULATION / PROCESS', 'network.role3.title': 'Formulation and Processing', 'network.role4.label': 'TEST / VALIDATION', 'network.role4.title': 'Testing and Validation', 'network.input': 'Input', 'network.output': 'Responsibility', 'network.role1.input': 'Real object, use conditions, target and non-negotiables', 'network.role1.output': 'Confirm the scenario, samples, evaluation and stage-acceptance criteria', 'network.role2.input': 'Material identity, mechanism hypothesis, candidate samples and boundaries', 'network.role2.output': 'Define the material role, variable relationships and control design', 'network.role3.input': 'Complete formulation, equipment capability, process parameters and quality window', 'network.role3.output': 'Create manufacturable samples and record scale-up risks', 'network.role4.input': 'Method, conditioning, test conditions, sample version and decision rules', 'network.role4.output': 'Retain results, failures, unknowns and applicability boundaries', 'access.kicker': 'ACCESS CONTROL', 'access.title': 'Access follows the object and project stage.', 'access.intro': 'Public readability does not mean formulations, reports or third-party rights may circulate freely. Source, contract and accountable owners determine access.', 'access.table.aria': 'Information access boundaries', 'access.column.level': 'Scope', 'access.column.content': 'Typical Content', 'access.column.route': 'Access Route', 'access.public.level': 'Public', 'access.public.content': 'Platform methods, public news and authorized materials', 'access.public.route': 'Direct browsing', 'access.request.level': 'On Request', 'access.request.content': 'Project-relevant TDS, method notes and sample conditions', 'access.request.route': 'Purpose-based review', 'access.nda.level': 'NDA / Single Project', 'access.nda.content': 'Formulation versions, raw data, failure analysis and collaboration files', 'access.nda.route': 'Contract- and owner-controlled', 'evidence.kicker': 'EVIDENCE PASSPORT', 'evidence.title': 'Every conclusion returns to its object, version, method and conditions.', 'evidence.intro': 'The technical passport links materials, formulations, substrates, articles, methods, results and access. It is not a marketing certificate and does not replace validation of the target market, finished article and actual use conditions.', 'evidence.image.alt': 'Materials-development workbench with waterborne emulsions, powder and film-formation samples', 'evidence.image.label': 'MATERIAL STATE → APPLICATION SAMPLE', 'evidence.image.value': 'Keep the object and conditions continuous from material state to sample result', 'passport.title': 'Technical Passport Structure', 'passport.status': 'PROJECT-SCOPED / RECORDED AFTER PROJECT INTAKE', 'passport.sample': 'Sample and Version', 'passport.sample.value': 'Numbered after intake and linked to materials, formulations, substrates and articles', 'passport.method': 'Method and Conditions', 'passport.method.value': 'Record conditioning, environment, medium, duration and decision method', 'passport.result': 'Result and Failure', 'passport.result.value': 'Record passes, failures, unknowns and items requiring further validation', 'passport.boundary': 'Boundary and Access', 'passport.boundary.value': 'Limit object, use, market, valid version and information scope', 'passport.tabs.aria': 'View the components of a technical passport', 'passport.tab.object': 'Object', 'passport.tab.method': 'Method', 'passport.tab.decision': 'Decision', 'passport.tab.access': 'Access', 'passport.object.title': 'First answer: “Which object?”', 'passport.object.copy': 'When the same material enters a different formulation, substrate, process or article, a new version relationship must be established. Abstract conclusions cannot simply carry over.', 'passport.method.title': 'Then answer: “Under what conditions?”', 'passport.method.copy': 'Beyond the method name, retain sample conditioning, environment, medium, duration, equipment and decision rules so results remain comparable.', 'passport.decision.title': 'Unknowns and failures are decision evidence.', 'passport.decision.copy': 'Record failure modes, deviations and unanswered questions. This prevents “not tested” from becoming “passed” and reduces repetition in the next experiment.', 'passport.access.title': 'Validity and permission to disclose are different questions.', 'passport.access.copy': 'Evidence status, communication permission, third-party rights and contract scope are recorded separately. Public excerpts must remain within the source material’s boundary.', 'passport.cta.evidence': 'View the Evidence Path', 'passport.cta.tools': 'Open Developer Tools', 'claim.label': 'CLAIM BOUNDARY', 'claim.copy': 'Food contact, PFAS/fluorine-free, recyclability, repulpability, compostability, degradation, migration and performance conclusions must be tied to material identity, complete structure, method, conditions, target market and valid evidence. This page makes no unconditional claim.', 'start.kicker': 'START A DEVELOPMENT TASK', 'start.title': 'Define the problem before choosing the material route.', 'start.copy': 'Prepare the application object, current solution, failure mode, equipment conditions, performance target, target market and any samples or data already available.', 'start.actions.aria': 'Developer action routes', 'start.action.task': 'Propose a Development Task', 'start.action.tools': 'Open Developer Tools', 'start.action.evidence': 'View the Evidence Path', 'start.action.contact': 'Contact BIOTEN', 'start.boundary.title': 'Current Intake Route', 'start.boundary.copy': 'Development tasks are handled through manual pre-review. After submission, the team confirms the application object, information access, sample conditions, target market and next-stage work. Accounts, real-time project status and automated matching are not current service commitments.', 'footer.title': 'Start with a real problem and advance verifiable materials development.', 'footer.copy': 'Problem definition, joint development, application validation and evidence coordination connect R&D decisions with industrial introduction.', 'footer.links.aria': 'Key developer entries', 'footer.link.engine': 'Development Method', 'footer.link.workbenches': 'Application Workbenches', 'footer.link.evidence': 'Evidence Path', 'footer.link.contact': 'Propose a Task', 'footer.company': 'DBC New Materials Technology (Shanghai) Co., Ltd.', 'footer.location': 'Shanghai · China', 'footer.sitemap': 'Sitemap', 'footer.privacy': 'Privacy Notice', 'footer.copyright': '© 2010-2026 DBC' }); const SEO = Object.freeze({ zh: { title: 'BIOTEN 开发者|问题定义、联合开发与证据协同', description: 'BIOTEN 开发者把真实应用问题转化为可执行、可验证的材料开发任务,连接问题定义、材料角色、配方变量、工艺窗口、应用试样、验证记录与阶段决策。', socialDescription: '把真实应用问题转化为可执行、可验证的材料开发任务。', imageAlt: '水性乳液、材料粉体与成膜试样组成的材料研发工作台' }, en: { title: 'BIOTEN Developer | Problem Definition, Joint Development and Evidence Coordination', description: 'BIOTEN Developer turns real application problems into executable, verifiable materials-development tasks across problem definition, material roles, formulation variables, process windows, application samples, validation records and stage decisions.', socialDescription: 'Turn real application problems into executable, verifiable materials-development tasks.', imageAlt: 'Materials-development workbench with waterborne emulsions, powder and film-formation samples' } }); /* * The shared language runtime may finish loading its translation pack after * this page has switched language. Register the page title as a page-local * extension so that the shared late pass cannot restore the Chinese source * title while the document is in English. */ window.DBC_EN_TRANSLATIONS = Object.assign(window.DBC_EN_TRANSLATIONS || {}, { [SEO.zh.title]: SEO.en.title }); const originalText = new WeakMap(); const originalAttributes = new WeakMap(); const mobileQuery = window.matchMedia('(max-width: 760px)'); const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); function currentLanguage() { return document.documentElement.dataset.lang === 'en' ? 'en' : 'zh'; } function rememberText(element) { if (!originalText.has(element)) originalText.set(element, element.textContent); return originalText.get(element); } function rememberAttribute(element, attribute) { if (!originalAttributes.has(element)) originalAttributes.set(element, {}); const values = originalAttributes.get(element); if (!Object.prototype.hasOwnProperty.call(values, attribute)) values[attribute] = element.getAttribute(attribute); return values[attribute]; } function updateMeta(selector, attribute, value) { const element = document.querySelector(selector); if (element) element.setAttribute(attribute, value); } function applyLanguage(language) { const lang = language === 'en' ? 'en' : 'zh'; document.querySelectorAll('[data-dev740-i18n]').forEach(element => { const key = element.dataset.dev740I18n; const fallback = rememberText(element); const value = lang === 'en' && EN[key] ? EN[key] : fallback; if (element.textContent !== value) element.textContent = value; }); document.querySelectorAll('[data-dev740-alt]').forEach(element => { const key = element.dataset.dev740Alt; const fallback = rememberAttribute(element, 'alt'); const value = lang === 'en' && EN[key] ? EN[key] : fallback; if (element.getAttribute('alt') !== value) element.setAttribute('alt', value); }); document.querySelectorAll('[data-dev740-aria]').forEach(element => { const key = element.dataset.dev740Aria; const fallback = rememberAttribute(element, 'aria-label'); const value = lang === 'en' && EN[key] ? EN[key] : fallback; if (element.getAttribute('aria-label') !== value) element.setAttribute('aria-label', value); }); const seo = SEO[lang]; document.title = seo.title; updateMeta('meta[name="description"]', 'content', seo.description); updateMeta('meta[property="og:title"]', 'content', seo.title); updateMeta('meta[property="og:description"]', 'content', seo.socialDescription); updateMeta('meta[property="og:image:alt"]', 'content', seo.imageAlt); updateMeta('meta[name="twitter:title"]', 'content', seo.title); updateMeta('meta[name="twitter:description"]', 'content', seo.socialDescription); updateMeta('meta[name="twitter:image:alt"]', 'content', seo.imageAlt); updateMeta('meta[property="og:locale"]', 'content', lang === 'en' ? 'en_US' : 'zh_CN'); document.body.dataset.dev740Language = lang; document.dispatchEvent(new CustomEvent('dev740:languageapplied', { detail: { language: lang } })); } function settleLanguage(language) { const lang = language === 'en' ? 'en' : 'zh'; applyLanguage(lang); [0, 120, 500, 1500].forEach(delay => { window.setTimeout(() => applyLanguage(currentLanguage()), delay); }); } function createTabController(options) { const root = document.querySelector(options.root); if (!root) return null; const tabList = root.querySelector(options.tabList); const tabs = Array.from(root.querySelectorAll(options.tabs)); const panels = Array.from(root.querySelectorAll(options.panels)); if (!tabList || !tabs.length || !panels.length) return null; let selected = tabs.find(tab => tab.getAttribute('aria-selected') === 'true')?.dataset[options.tabData] || tabs[0].dataset[options.tabData]; const activate = (value, focus = false, updateHash = false) => { if (!tabs.some(tab => tab.dataset[options.tabData] === value)) return; selected = value; tabs.forEach(tab => { const active = tab.dataset[options.tabData] === selected; tab.setAttribute('aria-selected', String(active)); tab.tabIndex = active ? 0 : -1; if (focus && active) tab.focus({ preventScroll: true }); }); panels.forEach(panel => { const active = panel.dataset[options.panelData] === selected; panel.hidden = !active; panel.setAttribute('aria-hidden', String(!active)); }); if (updateHash && options.hashPrefix && window.history && window.history.replaceState) { window.history.replaceState(null, '', '#' + options.hashPrefix + selected); } }; const syncMode = () => { root.dataset.mobileExpanded = 'false'; tabList.setAttribute('aria-hidden', 'false'); tabs.forEach(tab => { tab.tabIndex = tab.dataset[options.tabData] === selected ? 0 : -1; }); activate(selected, false, false); }; tabs.forEach((tab, index) => { tab.addEventListener('click', () => activate(tab.dataset[options.tabData], false, true)); tab.addEventListener('keydown', event => { if (!['ArrowRight', 'ArrowLeft', 'Home', 'End'].includes(event.key)) return; event.preventDefault(); let nextIndex = index; if (event.key === 'ArrowRight') nextIndex = (index + 1) % tabs.length; if (event.key === 'ArrowLeft') nextIndex = (index - 1 + tabs.length) % tabs.length; if (event.key === 'Home') nextIndex = 0; if (event.key === 'End') nextIndex = tabs.length - 1; activate(tabs[nextIndex].dataset[options.tabData], true, true); }); }); const requested = window.location.hash.replace('#' + (options.hashPrefix || ''), ''); if (requested && tabs.some(tab => tab.dataset[options.tabData] === requested)) selected = requested; syncMode(); return { activate, syncMode }; } function initReveals() { const elements = Array.from(document.querySelectorAll('.dev740-reveal')); if (!elements.length) return; if (reducedMotionQuery.matches || !('IntersectionObserver' in window)) { elements.forEach(element => element.classList.add('is-visible')); return; } const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (!entry.isIntersecting && entry.boundingClientRect.bottom >= 0) return; entry.target.classList.add('is-visible'); observer.unobserve(entry.target); }); }, { rootMargin: '0px 0px -8% 0px', threshold: .08 }); elements.forEach(element => observer.observe(element)); } class EvidenceParticleField { constructor(stage) { this.stage = stage; this.canvas = stage.querySelector('[data-dev740-particles]'); this.context = this.canvas?.getContext('2d', { alpha: true }); this.particles = []; this.width = 0; this.height = 0; this.frame = 0; this.visible = false; this.startTime = performance.now(); this.onFrame = this.onFrame.bind(this); this.resize = this.resize.bind(this); if (!this.canvas || !this.context) return; this.resizeObserver = new ResizeObserver(this.resize); this.resizeObserver.observe(stage); this.visibilityObserver = new IntersectionObserver(entries => { this.visible = Boolean(entries[0]?.isIntersecting); if (this.visible) this.start(); else this.stop(); }, { rootMargin: '120px 0px', threshold: 0 }); this.visibilityObserver.observe(stage); document.addEventListener('visibilitychange', () => { if (document.hidden) this.stop(); else if (this.visible) this.start(); }); reducedMotionQuery.addEventListener?.('change', () => { this.stop(); this.resize(); if (!reducedMotionQuery.matches && this.visible) this.start(); }); } resize() { if (!this.canvas || !this.context) return; const rect = this.stage.getBoundingClientRect(); const width = Math.max(1, Math.round(rect.width)); const height = Math.max(1, Math.round(rect.height)); const ratio = Math.min(window.devicePixelRatio || 1, 1.6); this.width = width; this.height = height; this.canvas.width = Math.round(width * ratio); this.canvas.height = Math.round(height * ratio); this.canvas.style.width = width + 'px'; this.canvas.style.height = height + 'px'; this.context.setTransform(ratio, 0, 0, ratio, 0, 0); const count = window.innerWidth <= 560 ? 42 : window.innerWidth <= 760 ? 58 : 88; this.buildParticles(count); this.canvas.dataset.particleCount = String(count); this.canvas.dataset.motion = reducedMotionQuery.matches ? 'static' : 'semantic-loop'; this.draw(reducedMotionQuery.matches ? .72 : .04); } buildParticles(count) { this.particles = Array.from({ length: count }, (_, index) => { const seed = { x: .06 + Math.random() * .31, y: .12 + Math.random() * .71 }; const group = index % 3; const centers = [ { x: .42, y: .27 }, { x: .51, y: .49 }, { x: .46, y: .69 } ]; const angle = Math.random() * Math.PI * 2; const radius = .018 + Math.random() * .07; const cluster = { x: centers[group].x + Math.cos(angle) * radius, y: centers[group].y + Math.sin(angle) * radius }; const lane = index / Math.max(1, count - 1); const path = { x: .59 + lane * .34, y: .22 + lane * .56 + Math.sin(index * 1.7) * .012 }; return { seed, cluster, path, size: .75 + Math.random() * 1.8, alpha: .3 + Math.random() * .58, drift: Math.random() * Math.PI * 2, group }; }); } smooth(value) { const t = Math.max(0, Math.min(1, value)); return t * t * (3 - 2 * t); } mix(a, b, value) { return a + (b - a) * value; } position(particle, progress) { let from = particle.seed; let to = particle.cluster; let amount = 0; if (progress < .2) { amount = this.smooth(progress / .2) * .12; } else if (progress < .48) { amount = this.smooth((progress - .2) / .28); } else if (progress < .8) { from = particle.cluster; to = particle.path; amount = this.smooth((progress - .48) / .32); } else { from = particle.path; to = particle.seed; amount = this.smooth((progress - .8) / .2); } const driftStrength = progress < .48 ? .006 : .002; return { x: this.mix(from.x, to.x, amount) + Math.cos(particle.drift + progress * Math.PI * 2) * driftStrength, y: this.mix(from.y, to.y, amount) + Math.sin(particle.drift + progress * Math.PI * 2) * driftStrength }; } drawSpine(progress) { if (progress < .43) return; const ctx = this.context; const strength = progress < .8 ? this.smooth((progress - .43) / .25) : 1 - this.smooth((progress - .8) / .2); ctx.save(); ctx.globalAlpha = .42 * strength; ctx.strokeStyle = '#65ddb4'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(this.width * .57, this.height * .2); ctx.bezierCurveTo(this.width * .68, this.height * .33, this.width * .78, this.height * .55, this.width * .94, this.height * .8); ctx.stroke(); ctx.restore(); } drawConnections(points, progress) { if (progress < .22 || progress > .82) return; const ctx = this.context; const maxDistance = Math.min(this.width, this.height) * (progress < .5 ? .11 : .072); ctx.save(); ctx.lineWidth = .55; for (let index = 0; index < points.length; index += 1) { for (let other = index + 1; other < Math.min(points.length, index + 10); other += 1) { const dx = points[index].x - points[other].x; const dy = points[index].y - points[other].y; const distance = Math.hypot(dx, dy); if (distance > maxDistance) continue; ctx.globalAlpha = (1 - distance / maxDistance) * .14; ctx.strokeStyle = points[index].group === points[other].group ? '#6fe0b8' : '#62afe0'; ctx.beginPath(); ctx.moveTo(points[index].x, points[index].y); ctx.lineTo(points[other].x, points[other].y); ctx.stroke(); } } ctx.restore(); } draw(progress) { const ctx = this.context; if (!ctx || !this.width || !this.height) return; ctx.clearRect(0, 0, this.width, this.height); this.drawSpine(progress); const points = this.particles.map(particle => { const position = this.position(particle, progress); return { x: position.x * this.width, y: position.y * this.height, particle, group: particle.group }; }); this.drawConnections(points, progress); points.forEach(point => { const { particle } = point; const finalPhase = progress > .49 && progress < .82; ctx.save(); ctx.globalAlpha = particle.alpha * (finalPhase ? .92 : .7); ctx.fillStyle = particle.group === 2 ? '#6fb7e5' : '#72e6bb'; ctx.beginPath(); ctx.arc(point.x, point.y, particle.size * (finalPhase ? 1.05 : .88), 0, Math.PI * 2); ctx.fill(); ctx.restore(); }); } onFrame(now) { if (!this.visible || reducedMotionQuery.matches || document.hidden) { this.frame = 0; return; } const progress = ((now - this.startTime) % 14000) / 14000; this.draw(progress); this.frame = window.requestAnimationFrame(this.onFrame); } start() { if (reducedMotionQuery.matches) { this.draw(.72); return; } if (!this.frame) this.frame = window.requestAnimationFrame(this.onFrame); } stop() { if (this.frame) window.cancelAnimationFrame(this.frame); this.frame = 0; } } function init() { if (!document.body.matches('[data-v740-page="developer-home"]')) return; document.querySelectorAll('[data-dev740-i18n], [data-dev740-alt], [data-dev740-aria]').forEach(element => { element.setAttribute('data-v720-language-managed', 'true'); }); settleLanguage(currentLanguage()); document.addEventListener('dbc:languagechange', event => { settleLanguage(event.detail?.language === 'en' ? 'en' : 'zh'); }); document.addEventListener('dbc:translations-loaded', () => { settleLanguage(currentLanguage()); }); const workbench = createTabController({ root: '[data-dev740-workbench]', tabList: '.dev740-workbench__tabs', tabs: '[data-dev740-workbench-tab]', panels: '[data-dev740-workbench-panel]', tabData: 'dev740WorkbenchTab', panelData: 'dev740WorkbenchPanel', hashPrefix: 'workbench-' }); const passport = createTabController({ root: '[data-dev740-passport]', tabList: '.dev740-passport__tabs', tabs: '[data-dev740-passport-tab]', panels: '[data-dev740-passport-panel]', tabData: 'dev740PassportTab', panelData: 'dev740PassportPanel', hashPrefix: 'passport-' }); const syncResponsiveControllers = () => { workbench?.syncMode(); passport?.syncMode(); }; mobileQuery.addEventListener?.('change', syncResponsiveControllers); initReveals(); document.querySelectorAll('[data-dev740-particle-stage]').forEach(stage => { new EvidenceParticleField(stage); }); document.body.dataset.dev740Ready = 'true'; } document.addEventListener('DOMContentLoaded', init, { once: true }); })();