1<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><link rel="canonical" href="https://www.cicciresearch.com/automation/"><link rel="icon" type="image/png" href="/logos/logo.png"><title>Cicci Research</title><link rel="stylesheet" href="/_astro/Footer.CLM6HcDu.css"><link rel="stylesheet" href="/_astro/automation.DLP8iH7R.css"></head><body class="bg-[#050816] text-white"><a href="https://www.cicciresearch.com/cdn-cgi/content?id=DSFgE3OxP0vbU5_ZDvfoih0OqJo25s6qHwSPvYSp4J8-1791438793.1032917-1.2.1.1-o0LSb7NkJUsSH4DQHwLgJO3OZxEDqfj.uV6vDpLWLSGnMz2PLqPOROBrgfNPUnHL" aria-hidden="true" rel="nofollow noopener" style="display: none !important; visibility: hidden !important"></a><nav class="site-navbar" data-site-navbar data-pagefind-ignore><div class="site-navbar__inner section-container"><a href="/" class="site-navbar__logo-link" aria-label="Cicci Research home"><img src="/logos/cicci-logo-hor.png" alt="Cicci Research" class="site-navbar__logo" width="320" height="100"></a><div class="site-navbar__desktop-links" aria-label="Main navigation"><div class="site-navbar__dropdown site-navbar__dropdown--active" data-desktop-dropdown><button class="site-navbar__dropdown-trigger" type="button" aria-expanded="false" data-dropdown-trigger><span>Products</span><svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="m6 8 4 4 4-4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"></path></svg></button><div class="site-navbar__dropdown-menu site-navbar__dropdown-menu--products" data-dropdown-menu><a href="/measurement" class="site-navbar__product-link"><span class="site-navbar__product-thumb" aria-hidden="true"><img src="/images/products/arkeo-platform.png" alt="" loading="eager"></span><span class="site-navbar__product-copy"><strong>Measurement</strong><small>ARKEO measurement platform</small></span></a><a href="/automation" class="site-navbar__product-link is-active"><span class="site-navbar__product-thumb" aria-hidden="true"><img src="/images/automation/charon-system-overview.png" alt="" loading="eager"></span><span class="site-navbar__product-copy"><strong>Automation</strong><small>CHARON automation platform</small></span></a><div class="site-navbar__product-nested" data-product-nested><div class="site-navbar__product-parent-row"><a href="/light-sources" class="site-navbar__product-link site-navbar__product-link--parent"><span class="site-navbar__product-thumb" aria-hidden="true"><img src="/images/products/nav/arkeo-soaker-xl.png" alt="" loading="eager"></span><span class="site-navbar__product-copy"><strong>Light Sources</strong><small>Controlled LED illumination</small></span></a><button type="button" class="site-navbar__product-parent-toggle" aria-label="Show Light Sources products" aria-expanded="false" data-product-submenu-trigger><span aria-hidden="true">â</span></button></div><div class="site-navbar__product-submenu" data-product-submenu hidden><a href="/light-sources/arkeo-soaker-xl" class="site-navbar__product-subitem"><span class="site-navbar__product-subitem-thumb" aria-hidden="true"><img src="/images/products/nav/arkeo-soaker-xl.png" alt="" loading="lazy"></span><span class="site-navbar__product-subitem-copy"><strong>ARKEO Soaker XL</strong><small>Large-area stability illumination</small></span></a><a href="/light-sources/arkeo-soaker-ms" class="site-navbar__product-subitem"><span class="site-navbar__product-subitem-thumb" aria-hidden="true"><img src="/images/products/nav/arkeo-soaker-ms.png" alt="" loading="lazy"></span><span class="site-navbar__product-subitem-copy"><strong>ARKEO Soaker M/S</strong><small>Compact programmable LED sources</small></span></a><a href="/light-sources/arkeo-96multiled" class="site-navbar__product-subitem"><span class="site-navbar__product-subitem-thumb" aria-hidden="true"><img src="/images/products/nav/arkeo-96multiled.png" alt="" loading="lazy"></span><span class="site-navbar__product-subitem-copy"><strong>ARKEO 96multiled</strong><small>Multiwell and multi-plate illumination</small></span></a></div></div><div class="site-navbar__product-nested" data-product-nested><div class="site-navbar__product-parent-row"><a href="/environmental-chambers" class="site-navbar__product-link site-navbar__product-link--parent"><span class="site-navbar__product-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-xxl.png" alt="" loading="eager"></span><span class="site-navbar__product-copy"><strong>Environmental Chambers</strong><small>Temperature & atmosphere control</small></span></a><button type="button" class="site-navbar__product-parent-toggle" aria-label="Show Environmental Chambers products" aria-expanded="false" data-product-submenu-trigger><span aria-hidden="true">â</span></button></div><div class="site-navbar__product-submenu" data-product-submenu hidden><a href="/environmental-chambers/chamber-xxl" class="site-navbar__product-subitem"><span class="site-navbar__product-subitem-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-xxl.png" alt="" loading="lazy"></span><span class="site-navbar__product-subitem-copy"><strong>Chamber XXL</strong><small>Large-format environmental control</small></span></a><a href="/environmental-chambers/chamber-m" class="site-navbar__product-subitem"><span class="site-navbar__product-subitem-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-m.png" alt="" loading="lazy"></span><span class="site-navbar__product-subitem-copy"><strong>Chamber M</strong><small>Compact benchtop controlled chamber</small></span></a><a href="/environmental-chambers/chamber-4-comps" class="site-navbar__product-subitem"><span class="site-navbar__product-subitem-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-4-comps.png" alt="" loading="lazy"></span><span class="site-navbar__product-subitem-copy"><strong>Chamber 4-comps</strong><small>Four-position chamber concept</small></span></a><a href="/environmental-chambers/chamber-x-ray" class="site-navbar__product-subitem"><span class="site-navbar__product-subitem-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-x-ray.png" alt="" loading="lazy"></span><span class="site-navbar__product-subitem-copy"><strong>Chamber X-Ray</strong><small>In-operando X-ray compatible chamber</small></span></a></div></div><a href="/installations" class="site-navbar__menu-card-link"><span class="site-navbar__menu-card-copy"><strong>Installations</strong></span></a></div></div><div class="site-navbar__dropdown" data-desktop-dropdown><button class="site-navbar__dropdown-trigger" type="button" aria-expanded="false" data-dropdown-trigger><span>Research</span><svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="m6 8 4 4 4-4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"></path></svg></button><div class="site-navbar__dropdown-menu site-navbar__dropdown-menu--cards" data-dropdown-menu><a href="/publications" class="site-navbar__menu-card-link"><span class="site-navbar__menu-card-copy"><strong>Publications</strong></span></a><a href="/projects" class="site-navbar__menu-card-link"><span class="site-navbar__menu-card-copy"><strong>Research Projects</strong></span></a><a href="/Conferences" class="site-navbar__menu-card-link"><span class="site-navbar__menu-card-copy"><strong>News & Events</strong></span></a></div></div><div class="site-navbar__dropdown" data-desktop-dropdown><button class="site-navbar__dropdown-trigger" type="button" aria-expanded="false" data-dropdown-trigger><span>Support</span><svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="m6 8 4 4 4-4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"></path></svg></button><div class="site-navbar__dropdown-menu site-navbar__dropdown-menu--cards" data-dropdown-menu><a href="/documentation" class="site-navbar__menu-card-link"><span class="site-navbar__menu-card-copy"><strong>Documentation</strong></span></a><a href="/remoteSupport" class="site-navbar__menu-card-link"><span class="site-navbar__menu-card-copy"><strong>Support services</strong></span></a><a href="/training" class="site-navbar__menu-card-link"><span class="site-navbar__menu-card-copy"><strong>Training</strong></span></a></div></div><a href="/AboutUs" class="site-navbar__top-link">About Us</a></div><div class="site-navbar__actions"><div class="site-search" data-site-search data-pagefind-ignore><button class="site-search__trigger" type="button" aria-label="Search Cicci Re
1search" aria-haspopup="dialog" data-search-open><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="11" cy="11" r="6.5" stroke="currentColor" stroke-width="2"></circle><path d="m16 16 4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path></svg><span class="site-search__trigger-label">Search</span><span class="site-search__trigger-shortcut">âK</span></button><div class="site-search__overlay" data-search-overlay role="dialog" aria-modal="true" aria-label="Search Cicci Research" hidden><button class="site-search__backdrop" type="button" aria-label="Close search" data-search-close></button><section class="site-search__panel"><div class="site-search__top"><div class="site-search__input-wrap"><svg class="site-search__input-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="11" cy="11" r="6.5" stroke="currentColor" stroke-width="2"></circle><path d="m16 16 4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path></svg><input type="search" autocomplete="off" spellcheck="false" placeholder="Search CICCI Research..." aria-label="Search the Cicci Re
1search website" aria-controls="site-search-results" data-search-input><button type="button" class="site-search__clear" aria-label="Clear search" title="Clear search" data-search-clear hidden><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M7 7l10 10M17 7 7 17" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path></svg></button><button type="button" class="site-search__close" aria-label="Close search" title="Close search" data-search-close><span>Esc</span></button></div><p class="site-search__status" data-search-status aria-live="polite">Search pages, products, projects, publications and individual sections.</p></div><div class="site-search__body"><div class="site-search__recommendations" data-search-recommendations><section class="site-search__group"><div class="site-search__group-heading"><h3 class="site-search__group-title" id="quick-access">Quick access</h3></div><div class="site-search__menu-list"><a class="site-search__menu-row" href="/measurement"><span class="site-search__row-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none"><path d="M6 5h12v14H6z" stroke="currentColor" stroke-width="1.6"></path><path d="M9 9h6M9 13h6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></span><span class="site-search__row-copy"><strong>Measurement</strong><small>ARKEO platform and measurement methods</small></span><span class="site-search__row-chevron" aria-hidden="true">âº</span></a><a class="site-search__menu-row" href="/publications"><span class="site-search__row-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none"><path d="M6 5h12v14H6z" stroke="currentColor" stroke-width="1.6"></path><path d="M9 9h6M9 13h6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></span><span class="site-search__row-copy"><strong>Publications</strong><small>Scientific papers and research references</small></span><span class="site-search__row-chevron" aria-hidden="true">âº</span></a><a class="site-search__menu-row" href="/training"><span class="site-search__row-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none"><path d="M6 5h12v14H6z" stroke="currentColor" stroke-width="1.6"></path><path d="M9 9h6M9 13h6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></span><span class="site-search__row-copy"><strong>Training</strong><small>Hands-on courses and educational activities</small></span><span class="site-search__row-chevron" aria-hidden="true">âº</span></a><a class="site-search__menu-row" href="/Conferences"><span class="site-search__row-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none"><path d="M6 5h12v14H6z" stroke="currentColor" stroke-width="1.6"></path><path d="M9 9h6M9 13h6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></span><span class="site-search__row-copy"><strong>News</strong><small>Conferences, events and research activities</small></span><span class="site-search__row-chevron" aria-hidden="true">âº</span></a></div></section><section class="site-search__group" data-recent-searches-section hidden><div class="site-search__group-heading"><h3 class="site-search__group-title" id="recent-searches">Recent searches</h3><button type="button" class="site-search__text-action" data-clear-search-history>Clear all</button></div><div class="site-search__menu-list" data-recent-searches-list></div></section><section class="site-search__group"><div class="site-search__group-heading"><h3 class="site-search__group-title" id="latest-research">Latest research</h3></div><div class="site-search__menu-list"><button type="button" class="site-search__menu-row site-search__menu-row--button" data-search-suggestion="PVSPACE"><span class="site-search__row-icon site-search__row-icon--research" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none"><path d="M9 4h6M10 4v5l-4 7a2 2 0 0 0 1.7 3h8.6A2 2 0 0 0 18 16l-4-7V4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8.5 14h7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></span><span class="site-search__row-copy"><strong>PVSPACE</strong><small>New-generation photovoltaics for space</small></span><span class="site-search__row-chevron" aria-hidden="true">âº</span></button><button type="button" class="site-search__menu-row site-search__menu-row--button" data-search-suggestion="ISOPHOS 2026"><span class="site-search__row-icon site-search__row-icon--research" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none">
1<path d="M9 4h6M10 4v5l-4 7a2 2 0 0 0 1.7 3h8.6A2 2 0 0 0 18 16l-4-7V4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8.5 14h7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></span><span class="site-search__row-copy"><strong>ISOPHOS 2026</strong><small>International school on hybrid and organic photovoltaics</small></span><span class="site-search__row-chevron" aria-hidden="true">âº</span></button><button type="button" class="site-search__menu-row site-search__menu-row--button" data-search-suggestion="ISOS 17"><span class="site-search__row-icon site-search__row-icon--research" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none"><path d="M9 4h6M10 4v5l-4 7a2 2 0 0 0 1.7 3h8.6A2 2 0 0 0 18 16l-4-7V4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8.5 14h7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></span><span class="site-search__row-copy"><strong>ISOS 17</strong><small>Stability protocols for organic and perovskite solar cells</small></span><span class="site-search__row-chevron" aria-hidden="true">âº</span></button><button type="button" class="site-search__menu-row site-search__menu-row--button" data-search-suggestion="Rete IFV 2025"><span class="site-search__row-icon site-search__row-icon--research" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none"><path d="M9 4h6M10 4v5l-4 7a2 2 0 0 0 1.7 3h8.6A2 2 0 0 0 18 16l-4-7V4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8.5 14h7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"></path></svg></span><span class="site-search__row-copy"><strong>Rete IFV 2025</strong><small>Italian photovoltaic research network conference</small></span><span class="site-search__row-chevron" aria-hidden="true">âº</span></button></div></section></div><div id="site-search-results" class="site-search__results" data-search-results role="listbox" aria-label="Search results"></div></div><footer class="site-search__footer"><div class="site-search__footer-shortcuts"><span><kbd>â</kbd><kbd>â</kbd> Navigate</span><span><kbd>âµ</kbd> Open</span><span><kbd>Esc</kbd> Close</span></div><span class="site-search__footer-brand">CICCI Research</span></footer></section></div></div>
1<script> 2 (() => { 3 document.querySelectorAll("[data-site-search]").forEach((root) => { 4 if (!(root instanceof HTMLElement) || root.dataset.searchReady === "true") return; 5 root.dataset.searchReady = "true"; 6 7 const overlay = root.querySelector("[data-search-overlay]"); 8 const input = root.querySelector("[data-search-input]"); 9 const results = root.querySelector("[data-search-results]"); 10 const status = root.querySelector("[data-search-status]"); 11 const recommendations = root.querySelector("[data-search-recommendations]"); 12 const openButton = root.querySelector("[data-search-open]"); 13 const clearButton = root.querySelector("[data-search-clear]"); 14 const closeButtons = root.querySelectorAll("[data-search-close]"); 15 const recentSection = root.querySelector("[data-recent-searches-section]"); 16 const recentList = root.querySelector("[data-recent-searches-list]"); 17 const clearHistoryButton = root.querySelector("[data-clear-search-history]"); 18 19 if ( 20 !(overlay instanceof HTMLElement) || 21 !(input instanceof HTMLInputElement) || 22 !(results instanceof HTMLElement) || 23 !(status instanceof HTMLElement) || 24 !(recommendations instanceof HTMLElement) || 25 !(openButton instanceof HTMLButtonElement) || 26 !(clearButton instanceof HTMLButtonElement) || 27 !(recentSection instanceof HTMLElement) || 28 !(recentList instanceof HTMLElement) || 29 !(clearHistoryButton instanceof HTMLButtonElement) 30 ) return; 31 32 /* 33 * The fixed navbar uses backdrop-filter, which can constrain fixed 34 * descendants in Chromium/Safari. The modal belongs directly under body. 35 */ 36 if (overlay.parentElement !== document.body) { 37 document.body.appendChild(overlay); 38 } 39 40 const RECENT_KEY = "cicci-search-recent-v2"; 41 const PENDING_KEY = "cicci-search-pending-landing-v1"; 42 const PENDING_MAX_AGE = 30000; 43 44 let pagefind = null; 45 let fallbackIndex = null; 46 let engine = null; 47 let debounceTimer = 0; 48 let lastFocused = null; 49 let selectedResultIndex = -1; 50 51 const escapeHtml = (value = "") => 52 String(value).replace(/[&<>"']/g, (char) => ({ 53 "&": "&", 54 "<": "<", 55 ">": ">", 56 '"': """, 57 "'": "'", 58 })[char]); 59 60 const normalize = (value = "") => 61 String(value) 62 .normalize("NFKD") 63 .replace(/[\u0300-\u036f]/g, "") 64 .toLowerCase(); 65 66 const isLocal = 67 location.hostname === "localhost" || 68 location.hostname === "127.0.0.1"; 69 70 /* -------------------------------------------------------------- */ 71 /* Recent searches */ 72 /* -------------------------------------------------------------- */ 73 74 const readRecentSearches = () => { 75 try { 76 const parsed = JSON.parse(localStorage.getItem(RECENT_KEY) || "[]"); 77 return Array.isArray(parsed) ? parsed.slice(0, 4) : []; 78 } catch { 79 return []; 80 } 81 }; 82 83 const writeRecentSearches = (items) => { 84 try { 85 localStorage.setItem(RECENT_KEY, JSON.stringify(items.slice(0, 4))); 86 } catch { 87 // Search must remain usable even when storage is unavailable. 88 } 89 }; 90 91 const renderRecentSearches = () => { 92 const items = readRecentSearches(); 93 94 recentSection.hidden = items.length === 0; 95 96 recentList.innerHTML = items 97 .map( 98 (term) => ` 99 <div class="site-search__recent-row"> 100 <button 101 type="button" 102 class="site-search__menu-row site-search__menu-row--button site-search__menu-row--recent" 103 data-recent-search="${escapeHtml(term)}" 104 title="Search again for ${escapeHtml(term)}" 105 > 106 <span class="site-search__row-icon" aria-hidden="true"> 107 <svg viewBox="0 0 24 24" fill="none"> 108 <path 109 d="M12 7v5l3 2M5.3 6.3A8 8 0 1 1 4 12" 110 stroke="currentColor" 111 stroke-width="1.6" 112 stroke-linecap="round" 113 stroke-linejoin="round" 114 /> 115 <path 116 d="M4 5v4h4" 117 stroke="currentColor" 118 stroke-width="1.6" 119 stroke-linecap="round" 120 stroke-linejoin="round" 121 /> 122 </svg> 123 </span>
124 <span class="site-search__row-copy"> 125 <strong>${escapeHtml(term)}</strong> 126 <small>Search again</small> 127 </span> 128 </button> 129 <button 130 type="button" 131 class="site-search__recent-remove" 132 aria-label="Remove recent search ${escapeHtml(term)}" 133 title="Remove" 134 data-remove-recent-search="${escapeHtml(term)}" 135 > 136 <svg viewBox="0 0 24 24" fill="none" aria-hidden="true"> 137 <path d="M7 7l10 10M17 7 7 17" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /> 138 </svg> 139 </button> 140 </div> 141 ` 142 ) 143 .join(""); 144 }; 145 146 const rememberSearch = (term) => { 147 const clean = String(term || "").trim(); 148 if (clean.length < 2) return; 149 150 const key = normalize(clean); 151 const previous = readRecentSearches().filter( 152 (item) => normalize(item) !== key 153 ); 154 155 writeRecentSearches([clean, ...previous].slice(0, 4)); 156 renderRecentSearches(); 157 }; 158 159 renderRecentSearches(); 160 161 /* -------------------------------------------------------------- */ 162 /* Landing highlight */ 163 /* -------------------------------------------------------------- */ 164 165 const clearLandingHighlight = () => { 166 document 167 .querySelectorAll("mark.site-search__landing-mark") 168 .forEach((mark) => { 169 const parent = mark.parentNode; 170 mark.replaceWith(document.createTextNode(mark.textContent || "")); 171 if (parent instanceof HTMLElement) parent.normalize(); 172 }); 173 }; 174 175 const findSectionByTitle = (sectionTitle) => { 176 const wanted = normalize(sectionTitle || "").trim(); 177 if (!wanted) return null; 178 179 const headings = [ 180 ...document.querySelectorAll( 181 "main h1, main h2, main h3, main h4, body h1, body h2, body h3, body h4" 182 ), 183 ]; 184 185 const heading = headings.find((element) => { 186 if (!(element instanceof HTMLElement)) return false; 187 const candidate = normalize(element.textContent || "").trim(); 188 189 return ( 190 candidate === wanted || 191 candidate.includes(wanted) || 192 wanted.includes(candidate) 193 ); 194 }); 195 196 if (!(heading instanceof HTMLElement)) return null; 197 198 return ( 199 heading.closest("section, article") || 200 heading.parentElement || 201 heading 202 ); 203 }; 204 205 const textNodesInside = (container) => { 206 if (!(container instanceof HTMLElement)) return []; 207 208 const walker = document.createTreeWalker( 209 container, 210 NodeFilter.SHOW_TEXT, 211 { 212 acceptNode(node) { 213 const parent = node.parentElement; 214 215 if (!parent) return NodeFilter.FILTER_REJECT; 216 217 if ( 218 parent.closest( 219 "script, style, noscript, nav, footer, .site-search__overlay, [data-pagefind-ignore]" 220 ) 221 ) { 222 return NodeFilter.FILTER_REJECT; 223 } 224 225 if (!(node.nodeValue || "").trim()) { 226 return NodeFilter.FILTER_REJECT; 227 } 228 229 return NodeFilter.FILTER_ACCEPT; 230 }, 231 } 232 ); 233 234 const nodes = []; 235 let node; 236 237 while ((node = walker.nextNode())) { 238 nodes.push(node); 239 } 240 241 return nodes; 242 }; 243 244 const markExactMatch = (node, term) => { 245 const source = node.nodeValue || ""; 246 const index = source 247 .toLocaleLowerCase() 248 .indexOf(term.toLocaleLowerCase()); 249 250 if (index < 0) return null; 251 252 const matchNode = node.splitText(index); 253 matchNode.splitText(term.length); 254 255 const mark = document.createElement("mark"); 256 mark.className = "site-search__landing-mark"; 257 258 matchNode.parentNode?.insertBefore(mark, matchNode); 259 mark.appendChild(matchNode); 260 261 return mark; 262 }; 263 264 const focusSearchMatch = ( 265 term, 266 preferredHash = "", 267 preferredSectionTitle = "" 268 ) => { 269 const cleanTerm = String(term || "").trim(); 270 if (!cleanTerm) return false;
271 272 clearLandingHighlight(); 273 274 const containers = []; 275 276 if (preferredHash) { 277 const id = decodeURIComponent(preferredHash.replace(/^#/, "")); 278 const anchor = document.getElementById(id); 279 280 if (anchor instanceof HTMLElement) { 281 containers.push( 282 anchor.closest("section, article") || 283 anchor.parentElement || 284 anchor 285 ); 286 } 287 } 288 289 const preferredSection = findSectionByTitle(preferredSectionTitle); 290 291 if ( 292 preferredSection instanceof HTMLElement && 293 !containers.includes(preferredSection) 294 ) { 295 containers.push(preferredSection); 296 } 297 298 const main = document.querySelector("main"); 299 300 if ( 301 main instanceof HTMLElement && 302 !containers.includes(main) 303 ) { 304 containers.push(main); 305 } 306 307 if (!containers.includes(document.body)) { 308 containers.push(document.body); 309 } 310 311 let mark = null; 312 313 for (const container of containers) { 314 for (const node of textNodesInside(container)) { 315 mark = markExactMatch(node, cleanTerm); 316 if (mark) break; 317 } 318 319 if (mark) break; 320 } 321 322 if (mark) { 323 window.setTimeout(() => { 324 mark.scrollIntoView({ 325 behavior: "smooth", 326 block: "center", 327 }); 328 }, 100); 329 330 return true; 331 } 332 333 /* 334 * If the sentence spans multiple inline nodes, we cannot safely wrap 335 * the exact phrase without restructuring the page. In that case we 336 * still scroll to the correct text block, but do not add a box. 337 */
338 const normalizedTerm = normalize(cleanTerm); 339 340 const blocks = [ 341 ...document.querySelectorAll( 342 "main h1, main h2, main h3, main h4, main p, main li, main td, main th" 343 ), 344 ].filter( 345 (element) => 346 element instanceof HTMLElement && 347 !element.closest( 348 "nav, footer, .site-search__overlay, [data-pagefind-ignore]" 349 ) 350 ); 351 352 const preferredBlocks = 353 preferredSection instanceof HTMLElement 354 ? blocks.filter((element) => preferredSection.contains(element)) 355 : []; 356 357 const ordered = [ 358 ...preferredBlocks, 359 ...blocks.filter((element) => !preferredBlocks.includes(element)), 360 ]; 361 362 const block = ordered.find((element) => 363 normalize(element.textContent || "").includes(normalizedTerm) 364 ); 365 366 if (block instanceof HTMLElement) { 367 window.setTimeout(() => { 368 block.scrollIntoView({ 369 behavior: "smooth", 370 block: "center", 371 }); 372 }, 100); 373 374 return true; 375 } 376 377 return false; 378 }; 379 380 /* 381 * Cross-page searches use sessionStorage for one navigation only. 382 * There are no ?search= parameters in the public URL. 383 * 384 * The pending state is deleted BEFORE applying it, so a browser reload 385 * never recreates the highlight. 386 */ 387 const consumePendingLanding = () => { 388 let pending = null; 389 390 try { 391 pending = JSON.parse(sessionStorage.getItem(PENDING_KEY) || "null"); 392 sessionStorage.removeItem(PENDING_KEY); 393 } catch { 394 sessionStorage.removeItem(PENDING_KEY); 395 } 396 397 if (!pending || typeof pending !== "object") return; 398 399 if ( 400 pending.path !== window.location.pathname || 401 Date.now() - Number(pending.createdAt || 0) > PENDING_MAX_AGE 402 ) { 403 return; 404 } 405 406 window.setTimeout(() => { 407 focusSearchMatch( 408 pending.term || "", 409 pending.hash || "", 410 pending.sectionTitle || "" 411 ); 412 }, 180); 413 }; 414 415 consumePendingLanding(); 416 417 document.addEventListener("astro:page-load", consumePendingLanding); 418 419 /* -------------------------------------------------------------- */ 420 /* Search engines */ 421 /* -------------------------------------------------------------- */ 422 423 const loadFallbackIndex = async () => { 424 if (fallbackIndex) return fallbackIndex; 425 426 const response = await fetch("/search-index.json", { 427 cache: isLocal ? "no-store" : "default", 428 }); 429 430 if (!response.ok) { 431 throw new Error(`search-index.json returned ${response.status}`); 432 } 433 434 const data = await response.json(); 435 436 if (!Array.isArray(data)) { 437 throw new Error("search-index.json is not an array"); 438 } 439 440 fallbackIndex = data; 441 return fallbackIndex; 442 }; 443 444 const loadPagefind = async () => { 445 if (pagefind) return pagefind; 446 447 pagefind = await import("/pagefind/pagefind.js"); 448 await pagefind.init(); 449 return pagefind; 450 }; 451 452 const loadEngine = async () => { 453 if (engine) return engine; 454 455 if (isLocal) { 456 try { 457 await loadFallbackIndex(); 458 engine = "fallback"; 459 return engine; 460 } catch (error) { 461 console.error("Local search index could not be loaded:", error); 462 status.textContent = 463 "Local search index is missing. Restart npm run dev."; 464 return null; 465 } 466 } 467 468 try { 469 await loadPagefind(); 470 engine = "pagefind"; 471 return engine; 472 } catch (pagefindError) { 473 console.warn("Pagefind unavailable, using static search index.", pagefindError); 474 475 try { 476 await loadFallbackIndex(); 477 engine = "fallback"; 478 return engine; 479 } catch (fallbackError) { 480 console.error("Fallback search index unavailable:", fallbackError); 481 status.textContent = "Search index is not available."; 482 return null; 483 } 484 } 485 }; 486 487 /* -------------------------------------------------------------- */ 488 /* UI state */ 489 /* -------------------------------------------------------------- */ 490 491 const updateClearButton = () => { 492 clearButton.hidden = input.value.length === 0; 493 }; 494 495 const resetSelection = () => { 496 selectedResultIndex = -1; 497 498 results 499 .querySelectorAll(".site-search__result--selected") 500 .forEach((item) => { 501 item.classList.remove("site-search__result--selected"); 502 item.removeAttribute("aria-selected"); 503 }); 504 }; 505 506 const resetSearchView = () => { 507 results.innerHTML = ""; 508 recommendations.hidden = false;
509 status.textContent = "Search across pages and individual sections."; 510 resetSelection(); 511 }; 512 513 const clearActiveSearch = ({ focusInput = true } = {}) => { 514 window.clearTimeout(debounceTimer); 515 input.value = ""; 516 updateClearButton(); 517 resetSearchView(); 518 clearLandingHighlight(); 519 520 if (focusInput) { 521 input.focus(); 522 } 523 }; 524 525 const openSearch = async () => { 526 lastFocused = document.activeElement; 527 overlay.hidden = false; 528 document.body.classList.add("site-search-open"); 529 renderRecentSearches(); 530 updateClearButton(); 531 532 requestAnimationFrame(() => input.focus()); 533 await loadEngine(); 534 }; 535 536 const closeSearch = ({ 537 clearHighlight = true, 538 resetQuery = true, 539 } = {}) => { 540 overlay.hidden = true; 541 document.body.classList.remove("site-search-open"); 542 543 if (resetQuery) { 544 input.value = ""; 545 updateClearButton(); 546 resetSearchView(); 547 } 548 549 if (clearHighlight) { 550 clearLandingHighlight(); 551 } 552 553 if (lastFocused instanceof HTMLElement) { 554 lastFocused.focus(); 555 } 556 }; 557 558 /* -------------------------------------------------------------- */ 559 /* Search formatting/ranking */ 560 /* -------------------------------------------------------------- */ 561 562 const contextualExcerpt = (text, term, maxLength = 210) => { 563 const source = String(text || "").replace(/\s+/g, " ").trim(); 564 if (!source) return ""; 565 566 const normalizedSource = normalize(source);
567 const normalizedTerm = normalize(term).trim(); 568 const words = normalizedTerm.split(/\s+/).filter(Boolean); 569 570 let matchIndex = normalizedSource.indexOf(normalizedTerm); 571 let matchLength = term.length; 572 573 if (matchIndex < 0) { 574 const firstMatchingWord = words.find((word) => 575 normalizedSource.includes(word) 576 ); 577 578 if (firstMatchingWord) { 579 matchIndex = normalizedSource.indexOf(firstMatchingWord); 580 matchLength = firstMatchingWord.length; 581 } 582 } 583 584 if (matchIndex < 0) { 585 const fallback = source.slice(0, maxLength); 586 return escapeHtml( 587 fallback + (source.length > maxLength ? "â¦" : "") 588 ); 589 } 590 591 let start = Math.max(0, matchIndex - Math.floor(maxLength * 0.38)); 592 let end = Math.min(source.length, start + maxLength); 593 594 if (start > 0) { 595 const nextSpace = source.indexOf(" ", start); 596 if (nextSpace > -1 && nextSpace < matchIndex) { 597 start = nextSpace + 1; 598 } 599 } 600 601 if (end < source.length) { 602 const previousSpace = source.lastIndexOf(" ", end); 603 if (previousSpace > matchIndex + matchLength) { 604 end = previousSpace; 605 } 606 } 607 608 const excerpt = source.slice(start, end); 609 const normalizedExcerpt = normalize(excerpt); 610 611 let localIndex = normalizedExcerpt.indexOf(normalizedTerm); 612 let localLength = term.length; 613 614 if (localIndex < 0) { 615 const firstMatchingWord = words.find((word) => 616 normalizedExcerpt.includes(word) 617 ); 618 619 if (firstMatchingWord) { 620 localIndex = normalizedExcerpt.indexOf(firstMatchingWord); 621 localLength = firstMatchingWord.length; 622 } 623 } 624 625 let rendered; 626 627 if (localIndex >= 0) { 628 rendered = 629 escapeHtml(excerpt.slice(0, localIndex)) + 630 "<mark>" + 631 escapeHtml(excerpt.slice(localIndex, localIndex + localLength)) + 632 "</mark>" + 633 escapeHtml(excerpt.slice(localIndex + localLength)); 634 } else { 635 rendered = escapeHtml(excerpt); 636 } 637 638 return `${start > 0 ? "â¦" : ""}${rendered}${end < source.length ? "â¦" : ""}`; 639 }; 640 641 const searchFallback = async (term) => { 642 const index = await loadFallbackIndex(); 643 const query = normalize(term).trim(); 644 const queryWords = query.split(/\s+/).filter(Boolean); 645 646 let scored = index 647 .map((item) => { 648 const pageTitle = normalize(item.pageTitle); 649 const sectionTitle = normalize(item.sectionTitle); 650 const text = normalize(item.text); 651 const haystack = `${pageTitle} ${sectionTitle} ${text}`; 652 653 const matchedWords = queryWords.filter((word) => 654 haystack.includes(word) 655 ).length; 656 657 if (matchedWords === 0) { 658 return null; 659 } 660 661 const allWordsMatch = matchedWords === queryWords.length; 662 const exactPhraseMatch = haystack.includes(query); 663 664 let score = 0; 665 666 if (sectionTitle === query) score += 240; 667 if (pageTitle === query) score += 210; 668 if (sectionTitle.startsWith(query)) score += 155; 669 if (pageTitle.startsWith(query)) score += 135; 670 if (sectionTitle.includes(query)) score += 110; 671 if (pageTitle.includes(query)) score += 90; 672 if (text.includes(query)) score += 60; 673 674 if (exactPhraseMatch) score += 80; 675 if (allWordsMatch) score += 55; 676 677 score += matchedWords * 18; 678 679 queryWords.forEach((word) => { 680 if (sectionTitle.includes(word)) score += 22; 681 if (pageTitle.includes(word)) score += 18; 682 if (text.includes(word)) score += 6; 683 }); 684 685 return { 686 ...item, 687 score, 688 matchedWords, 689 allWordsMatch, 690 }; 691 }) 692 .filter(Boolean); 693 694 /* 695 * Prefer results containing every word. If none exist, gracefully 696 * fall back to the strongest partial matches instead of returning an 697 * empty result set for useful searches such as "ISOPHOS 2026". 698 */ 699 const hasAllWordMatch = scored.some((item) => item.allWordsMatch); 700 701 if (hasAllWordMatch) { 702 scored = scored.filter((item) => item.allWordsMatch); 703 } else if (queryWords.length > 1) { 704 const minimumWords = Math.max( 705 1, 706 Math.ceil(queryWords.length / 2) 707 ); 708 709 scored = scored.filter( 710 (item) => item.matchedWords >= minimumWords 711 ); 712 } 713 714 scored.sort((a, b) => b.score - a.score); 715 716 const seen = new Set(); 717 718 return scored 719 .filter((item) => {
720 const key = `${item.url}\u0000${item.sectionTitle}`; 721 if (seen.has(key)) return false; 722 seen.add(key); 723 return true; 724 }) 725 .slice(0, 50) 726 .map((item) => ({ 727 pageTitle: item.pageTitle, 728 sectionTitle: item.sectionTitle, 729 url: item.url, 730 excerpt: contextualExcerpt(item.text || item.excerpt, term), 731 })); 732 }; 733 734 const searchPagefind = async (term) => { 735 const pf = await loadPagefind(); 736 const search = await pf.search(term); 737 738 const pageData = await Promise.all( 739 search.results.map(async (result) => result.data()) 740 ); 741 742 const sectionResults = []; 743 744 pageData.forEach((data) => { 745 const pageTitle = 746 data.meta?.title || 747 data.meta?.og_title || 748 "Cicci Research"; 749 750 if (Array.isArray(data.sub_results) && data.sub_results.length) { 751 data.sub_results.forEach((sub) => { 752 sectionResults.push({ 753 pageTitle, 754 sectionTitle: sub.title || pageTitle, 755 url: sub.url || data.url, 756 excerpt: sub.excerpt || data.excerpt || "", 757 }); 758 }); 759 } else { 760 sectionResults.push({ 761 pageTitle, 762 sectionTitle: pageTitle, 763 url: data.url, 764 excerpt: data.excerpt || "", 765 }); 766 } 767 }); 768 769 const seen = new Set(); 770 771 return sectionResults.filter((item) => { 772 const key = `${item.url}\u0000${item.sectionTitle}`; 773 if (!item.url || seen.has(key)) return false; 774 seen.add(key); 775 return true; 776 }); 777 }; 778 779 const renderResult = (item, index) => { 780 const pageTitle = item.pageTitle || "Cicci Research"; 781 const sectionTitle = 782 item.sectionTitle && item.sectionTitle !== pageTitle 783 ? item.sectionTitle 784 : ""; 785 786 return ` 787 <a 788 class="site-search__result" 789 href="${escapeHtml(item.url)}" 790 role="option" 791 aria-selected="false" 792 data-result-index="${index}" 793 data-result-page="${escapeHtml(pageTitle)}" 794 data-result-section="${escapeHtml(sectionTitle)}" 795 > 796 <span class="site-search__result-icon" aria-hidden="true"> 797 <svg viewBox="0 0 24 24" fill="none"> 798 <path d="M7 4h7l3 3v13H7z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" /> 799 <path d="M14 4v4h4M10 12h4M10 15h4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" /> 800 </svg> 801 </span> 802 <span class="site-search__result-copy"> 803 <span class="site-search__result-breadcrumb"> 804 ${escapeHtml(pageTitle)} 805 ${ 806 sectionTitle 807 ? `<span aria-hidden="true">âº</span><strong>${escapeHtml(sectionTitle)}</strong>` 808 : "" 809 } 810 </span> 811 <span class="site-search__excerpt">${item.excerpt || ""}</span> 812 </span> 813 </a> 814 `; 815 }; 816 817 const searchSite = async (term) => { 818 const trimmed = term.trim(); 819 820 clearLandingHighlight(); 821 822 if (trimmed.length < 2) { 823 resetSearchView(); 824 825 if (trimmed.length === 1) { 826 status.textContent = "Type one more character to search."; 827 } 828 829 return; 830 } 831 832 recommendations.hidden = true; 833 results.innerHTML = ""; 834 resetSelection(); 835 status.textContent = "Searchingâ¦"; 836 837 const selectedEngine = await loadEngine(); 838 if (!selectedEngine) return; 839 840 try { 841 let items = 842 selectedEngine === "pagefind" 843 ? await searchPagefind(trimmed) 844 : await searchFallback(trimmed); 845 846 /* 847 * Pagefind is still the preferred production engine, but if a
848 * dynamic/static edge case returns no results, try the generated 849 * JSON index before showing "No results". 850 */ 851 if ( 852 selectedEngine === "pagefind" && 853 items.length === 0 854 ) { 855 items = await searchFallback(trimmed); 856 } 857 858 if (input.value.trim() !== trimmed) return; 859 860 if (!items.length) { 861 results.innerHTML = ` 862 <div class="site-search__empty"> 863 <strong>No results for â${escapeHtml(trimmed)}â</strong> 864 <span>Try a shorter term or check the spelling.</span> 865 </div> 866 `; 867 status.textContent = "No matching sections."; 868 return; 869 } 870 871 results.innerHTML = items 872 .map((item, index) => renderResult(item, index)) 873 .join(""); 874 875 status.textContent = 876 `${items.length} matching section${items.length === 1 ? "" : "s"}.`; 877 } catch (error) { 878 console.error("Website search failed:", error); 879 results.innerHTML = ""; 880 status.textContent = "Search could not be completed.";
881 } 882 }; 883 884 /* -------------------------------------------------------------- */ 885 /* Result navigation */ 886 /* -------------------------------------------------------------- */ 887 888 const openResult = (link) => { 889 if (!(link instanceof HTMLAnchorElement)) return; 890 891 const targetUrl = new URL(link.href, window.location.href); 892 const currentUrl = new URL(window.location.href); 893 const term = input.value.trim(); 894 const sectionTitle = link.getAttribute("data-result-section") || ""; 895 896 rememberSearch(term); 897 898 if (targetUrl.pathname === currentUrl.pathname) { 899 closeSearch({ 900 clearHighlight: false, 901 resetQuery: true, 902 }); 903 904 window.setTimeout(() => { 905 focusSearchMatch( 906 term, 907 targetUrl.hash, 908 sectionTitle 909 ); 910 }, 80); 911 912 return; 913 } 914 915 /* 916 * Store the landing request for exactly one navigation, then use a 917 * CLEAN URL. No query string is exposed and reload cannot repeat it. 918 */ 919 try { 920 sessionStorage.setItem( 921 PENDING_KEY, 922 JSON.stringify({ 923 path: targetUrl.pathname, 924 term, 925 sectionTitle, 926 hash: targetUrl.hash, 927 createdAt: Date.now(), 928 }) 929 ); 930 } catch { 931 // The target page still opens if sessionStorage is unavailable. 932 } 933 934 window.location.assign(targetUrl.pathname); 935 }; 936 937 const selectResult = (nextIndex) => { 938 const links = [...results.querySelectorAll("a.site-search__result")]; 939 940 if (!links.length) { 941 selectedResultIndex = -1; 942 return; 943 } 944 945 selectedResultIndex = 946 ((nextIndex % links.length) + links.length) % links.length; 947 948 links.forEach((link, index) => { 949 const selected = index === selectedResultIndex; 950 link.classList.toggle("site-search__result--selected", selected); 951 link.setAttribute("aria-selected", String(selected)); 952 }); 953 954 links[selectedResultIndex]?.scrollIntoView({ 955 block: "nearest", 956 }); 957 }; 958 959 /* -------------------------------------------------------------- */ 960 /* Events */ 961 /* -------------------------------------------------------------- */ 962 963 openButton.addEventListener("click", openSearch); 964 965 closeButtons.forEach((button) => { 966 button.addEventListener("click", () => { 967 closeSearch({ 968 clearHighlight: true, 969 resetQuery: true, 970 }); 971 }); 972 }); 973 974 clearButton.addEventListener("click", () => { 975 clearActiveSearch(); 976 }); 977 978 /* 979 * Browsers fire "search" when their native type=search cancel control is 980 * used. This guarantees that both our X and a browser-provided X clear 981 * the page highlight too. 982 */ 983 input.addEventListener("search", () => { 984 if (input.value === "") { 985 clearActiveSearch(); 986 } 987 }); 988 989 input.addEventListener("input", () => { 990 window.clearTimeout(debounceTimer); 991 updateClearButton(); 992 resetSelection(); 993 clearLandingHighlight(); 994 995 debounceTimer = window.setTimeout(() => { 996 searchSite(input.value); 997 }, 100); 998 }); 999 1000 recommendations.addEventListener("click", (event) => { 1001 const suggestion = 1002 event.target instanceof Element 1003 ? event.target.closest("[data-search-suggestion]") 1004 : null; 1005 1006 if (suggestion instanceof HTMLButtonElement) { 1007 const query = suggestion.getAttribute("data-search-suggestion") || ""; 1008 1009 input.value = query; 1010 updateClearButton(); 1011 input.focus(); 1012 searchSite(query); 1013 } 1014 }); 1015 1016 recentList.addEventListener("click", (event) => { 1017 const item = 1018 event.target instanceof Element 1019 ? event.target.closest("[data-recent-search]") 1020 : null; 1021 1022 if (!(item instanceof HTMLButtonElement)) return; 1023 1024 const query = item.getAttribute("data-recent-search") || ""; 1025 1026 input.value = query; 1027 updateClearButton(); 1028 input.focus(); 1029 searchSite(query); 1030 }); 1031 1032 recentList.addEventListener("click", (event) => { 1033 const removeButton = 1034 event.target instanceof Element 1035 ? event.target.closest("[data-remove-recent-search]") 1036 : null; 1037 1038 if (!(removeButton instanceof HTMLButtonElement)) return; 1039 1040 event.stopPropagation(); 1041 1042 const term = 1043 removeButton.getAttribute("data-remove-recent-search") || "";
1044 const key = normalize(term); 1045 1046 writeRecentSearches( 1047 readRecentSearches().filter( 1048 (item) => normalize(item) !== key 1049 ) 1050 ); 1051 1052 renderRecentSearches(); 1053 }); 1054 1055 clearHistoryButton.addEventListener("click", () => { 1056 try { 1057 localStorage.removeItem(RECENT_KEY); 1058 } catch { 1059 // Nothing else required. 1060 } 1061 1062 renderRecentSearches(); 1063 }); 1064 1065 results.addEventListener("click", (event) => { 1066 const link = 1067 event.target instanceof Element 1068 ? event.target.closest("a.site-search__result") 1069 : null; 1070 1071 if (!(link instanceof HTMLAnchorElement)) return; 1072 1073 event.preventDefault(); 1074 openResult(link); 1075 }); 1076 1077 input.addEventListener("keydown", (event) => { 1078 if (event.key === "ArrowDown") { 1079 event.preventDefault(); 1080 selectResult(selectedResultIndex + 1); 1081 return; 1082 } 1083 1084 if (event.key === "ArrowUp") { 1085 event.preventDefault(); 1086 selectResult( 1087 selectedResultIndex < 0 1088 ? results.querySelectorAll("a.site-search__result").length - 1 1089 : selectedResultIndex - 1 1090 ); 1091 return; 1092 } 1093 1094 if (event.key === "Enter" && selectedResultIndex >= 0) { 1095 const selected = results.querySelector( 1096 `a.site-search__result[data-result-index="${selectedResultIndex}"]` 1097 ); 1098 1099 if (selected instanceof HTMLAnchorElement) { 1100 event.preventDefault(); 1101 openResult(selected); 1102 } 1103 } 1104 }); 1105 1106 document.addEventListener("keydown", (event) => { 1107 const shortcut = 1108 (event.ctrlKey || event.metaKey) && 1109 event.key.toLowerCase() === "k"; 1110 1111 if (shortcut) { 1112 event.preventDefault(); 1113 1114 if (overlay.hidden) { 1115 openSearch(); 1116 } else { 1117 closeSearch({ 1118 clearHighlight: true, 1119 resetQuery: true, 1120 }); 1121 } 1122 1123 return; 1124 } 1125 1126 if (event.key === "Escape" && !overlay.hidden) { 1127 closeSearch({ 1128 clearHighlight: true, 1129 resetQuery: true, 1130 }); 1131 } 1132 }); 1133 }); 1134 })(); 1135</script>
1135<button type="button" class="site-navbar__menu-button" aria-label="Open navigation menu" aria-expanded="false" aria-controls="mobile-navigation" data-menu-button><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path data-menu-open-icon d="M4 7h16M4 12h16M4 17h16" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path><path data-menu-close-icon hidden d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path></svg></button></div></div><div id="mobile-navigation" class="site-navbar__mobile-menu" data-mobile-menu hidden><div class="site-navbar__mobile-inner section-container"><div class="site-navbar__mobile-group" data-mobile-group><button type="button" class="site-navbar__mobile-group-trigger" aria-expanded="false" data-mobile-group-trigger><span>Products</span><svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="m6 8 4 4 4-4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"></path></svg></button><div class="site-navbar__mobile-submenu site-navbar__mobile-submenu--products" data-mobile-submenu hidden><a href="/measurement" class="site-navbar__mobile-product-link" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/arkeo-platform.png" alt="" loading="lazy"></span><span><strong>Measurement</strong><small>ARKEO measurement platform</small></span></a><a href="/automation" class="site-navbar__mobile-product-link" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/automation/charon-system-overview.png" alt="" loading="lazy"></span><span><strong>Automation</strong><small>CHARON automation platform</small></span></a><div class="site-navbar__mobile-product-nested" data-mobile-product-nested><div class="site-navbar__mobile-product-row"><a href="/light-sources" class="site-navbar__mobile-product-link" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/nav/arkeo-soaker-xl.png" alt="" loading="lazy"></span><span><strong>Light Sources</strong><small>Controlled LED illumination</small></span></a><button type="button" class="site-navbar__mobile-product-submenu-trigger" aria-label="Show Light Sources" aria-expanded="false" data-mobile-product-submenu-trigger>â</button></div><div class="site-navbar__mobile-product-children" data-mobile-product-children hidden><a href="/light-sources/arkeo-soaker-xl" class="site-navbar__mobile-product-link site-navbar__mobile-product-link--child" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/nav/arkeo-soaker-xl.png" alt="" loading="lazy"></span><span><strong>ARKEO Soaker XL</strong><small>Large-area stability illumination</small></span></a><a href="/light-sources/arkeo-soaker-ms" class="site-navbar__mobile-product-link site-navbar__mobile-product-link--child" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/nav/arkeo-soaker-ms.png" alt="" loading="lazy"></span><span><strong>ARKEO Soaker M/S</strong><small>Compact programmable LED sources</small></span></a><a href="/light-sources/arkeo-96multiled" class="site-navbar__mobile-product-link site-navbar__mobile-product-link--child" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/nav/arkeo-96multiled.png" alt="" loading="lazy"></span><span><strong>ARKEO 96multiled</strong><small>Multiwell and multi-plate illumination</small></span></a></div></div><div class="site-navbar__mobile-product-nested" data-mobile-product-nested><div class="site-navbar__mobile-product-row"><a href="/environmental-chambers" class="site-navbar__mobile-product-link" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-xxl.png" alt="" loading="lazy"></span><span><strong>Environmental Chambers</strong><small>Temperature & atmosphere control</small></span></a><button type="button" class="site-navbar__mobile-product-submenu-trigger" aria-label="Show Environmental Chambers" aria-expanded="false" data-mobile-product-submenu-trigger>â</button></div><div class="site-navbar__mobile-product-children" data-mobile-product-children hidden><a href="/environmental-chambers/chamber-xxl" class="site-navbar__mobile-product-link site-navbar__mobile-product-link--child" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-xxl.png" alt="" loading="lazy"></span><span><strong>Chamber XXL</strong><small>Large-format environmental control</small></span></a><a href="/environmental-chambers/chamber-m" class="site-navbar__mobile-product-link site-navbar__mobile-product-link--child" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-m.png" alt="" loading="lazy"></span><span><strong>Chamber M</strong><small>Compact benchtop controlled chamber</small></span></a><a href="/environmental-chambers/chamber-4-comps" class="site-navbar__mobile-product-link site-navbar__mobile-product-link--child" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-4-comps.png" alt="" loading="lazy"></span><span><strong>Chamber 4-comps</strong><small>Four-position chamber concept</small></span></a><a href="/environmental-chambers/chamber-x-ray" class="site-navbar__mobile-product-link site-navbar__mobile-product-link--child" data-mobile-link><span class="site-navbar__mobile-product-thumb" aria-hidden="true"><img src="/images/products/nav/chamber-x-ray.png" alt="" loading="lazy"></span><span><strong>Chamber X-Ray</strong><small>In-operando X-ray compatible chamber</small></span></a></div></div><a href="/installations" class="site-navbar__mobile-menu-card-link" data-mobile-link><strong>Installations</strong></a></div></div><div class="site-navbar__mobile-group" data-mobile-group><button type="button" class="site-navbar__mobile-group-trigger" aria-expanded="false" data-mobile-group-trigger><span>Research</span><svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="m6 8 4 4 4-4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"></path></svg></button><div class="site-navbar__mobile-submenu site-navbar__mobile-submenu--cards" data-mobile-submenu hidden><a href="/publications" class="site-navbar__mobile-menu-card-link" data-mobile-link><strong>Publications</strong></a><a href="/projects" class="site-navbar__mobile-menu-card-link" data-mobile-link><strong>Research Projects</strong></a><a href="/Conferences" class="site-navbar__mobile-menu-card-link" data-mobile-link><strong>News & Events</strong></a></div></div><div class="site-navbar__mobile-group" data-mobile-group><button type="button" class="site-navbar__mobile-group-trigger" aria-expanded="false" data-mobile-group-trigger><span>Support</span><svg viewBox="0 0 20 20" fill="none" aria-hidden="true"><path d="m6 8 4 4 4-4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"></path></svg></button><div class="site-navbar__mobile-submenu site-navbar__mobile-submenu--cards" data-mobile-submenu hidden><a href="/documentation" class="site-navbar__mobile-menu-card-link" data-mobile-link><strong>Documentation</strong></a><a href="/remoteSupport" class="site-navbar__mobile-menu-card-link" data-mobile-link><strong>Support services</strong></a><a href="/training" class="site-navbar__mobile-menu-card-link" data-mobile-link><strong>Training</strong></a></div></div><a href="/AboutUs" class="site-navbar__mobile-link" data-mobile-link>About Us</a></div></div></nav>
1135<script> 1136 (() => { 1137 document.querySelectorAll("[data-site-navbar]").forEach((navbar) => { 1138 if (!(navbar instanceof HTMLElement) || navbar.dataset.navReady === "true") return; 1139 navbar.dataset.navReady = "true"; 1140 1141 const menuButton = navbar.querySelector("[data-menu-button]"); 1142 const mobileMenu = navbar.querySelector("[data-mobile-menu]"); 1143 const openIcon = navbar.querySelector("[data-menu-open-icon]"); 1144 const closeIcon = navbar.querySelector("[data-menu-close-icon]"); 1145 const desktopDropdowns = Array.from(navbar.querySelectorAll("[data-desktop-dropdown]")); 1146 const mobileGroups = Array.from(navbar.querySelectorAll("[data-mobile-group]")); 1147 const productNested = Array.from(navbar.querySelectorAll("[data-product-nested]")); 1148 const mobileProductNested = Array.from(navbar.querySelectorAll("[data-mobile-product-nested]")); 1149 1150 const closeProductNestedItem = (item) => { 1151 if (!(item instanceof HTMLElement)) return; 1152 const trigger = item.querySelector("[data-product-submenu-trigger]"); 1153 const submenu = item.querySelector("[data-product-submenu]"); 1154 item.classList.remove("is-open"); 1155 if (trigger instanceof HTMLButtonElement) trigger.setAttribute("aria-expanded", "false"); 1156 if (submenu instanceof HTMLElement) submenu.hidden = true; 1157 }; 1158 1159 const closeProductNested = (except = null) => { 1160 productNested.forEach((item) => { 1161 if (item !== except) closeProductNestedItem(item); 1162 }); 1163 }; 1164 1165 const openProductNestedItem = (item) => { 1166 if (!(item instanceof HTMLElement)) return; 1167 const trigger = item.querySelector("[data-product-submenu-trigger]"); 1168 const submenu = item.querySelector("[data-product-submenu]"); 1169 closeProductNested(item); 1170 item.classList.add("is-open"); 1171 if (trigger instanceof HTMLButtonElement) trigger.setAttribute("aria-expanded", "true"); 1172 if (submenu instanceof HTMLElement) submenu.hidden = false; 1173 }; 1174 1175 const closeDesktopDropdown = (dropdown) => { 1176 if (!(dropdown instanceof HTMLElement)) return; 1177 const trigger = dropdown.querySelector("[data-dropdown-trigger]"); 1178 dropdown.classList.remove("is-open"); 1179 closeProductNested(); 1180 if (trigger instanceof HTMLButtonElement) trigger.setAttribute("aria-expanded", "false"); 1181 }; 1182 1183 const closeDesktopDropdowns = (except = null) => { 1184 desktopDropdowns.forEach((dropdown) => { 1185 if (dropdown !== except) closeDesktopDropdown(dropdown); 1186 }); 1187 }; 1188 1189 productNested.forEach((item) => { 1190 if (!(item instanceof HTMLElement)) return; 1191 const trigger = item.querySelector("[data-product-submenu-trigger]"); 1192 if (!(trigger instanceof HTMLButtonElement)) return; 1193 item.addEventListener("mouseenter", () => openProductNestedItem(item)); 1194 item.addEventListener("mouseleave", () => closeProductNestedItem(item)); 1195 item.addEventListener("focusin", () => openProductNestedItem(item)); 1196 item.addEventListener("focusout", (event) => { 1197 const next = event.relatedTarget; 1198 if (!(next instanceof Node) || !item.contains(next)) closeProductNestedItem(item); 1199 }); 1200 trigger.addEventListener("click", (event) => { 1201 event.preventDefault(); 1202 event.stopPropagation(); 1203 const willOpen = !item.classList.contains("is-open"); 1204 if (willOpen) openProductNestedItem(item); 1205 else closeProductNestedItem(item); 1206 }); 1207 }); 1208 1209 desktopDropdowns.forEach((dropdown) => { 1210 if (!(dropdown instanceof HTMLElement)) return; 1211 const trigger = dropdown.querySelector("[data-dropdown-trigger]"); 1212 if (!(trigger instanceof HTMLButtonElement)) return; 1213 trigger.addEventListener("click", (event) => { 1214 event.stopPropagation(); 1215 const willOpen = !dropdown.classList.contains("is-open"); 1216 closeDesktopDropdowns(dropdown); 1217 dropdown.classList.toggle("is-open", willOpen); 1218 trigger.setAttribute("aria-expanded", String(willOpen)); 1219 }); 1220 }); 1221 1222 const closeMobileGroup = (group) => { 1223 if (!(group instanceof HTMLElement)) return; 1224 const trigger = group.querySelector("[data-mobile-group-trigger]"); 1225 const submenu = group.querySelector("[data-mobile-submenu]"); 1226 group.classList.remove("is-open"); 1227 if (trigger instanceof HTMLButtonElement) trigger.setAttribute("aria-expanded", "false"); 1228 if (submenu instanceof HTMLElement) submenu.hidden = true; 1229 }; 1230 1231 mobileGroups.forEach((group) => { 1232 if (!(group instanceof HTMLElement)) return; 1233 const trigger = group.querySelector("[data-mobile-group-trigger]"); 1234 const submenu = group.querySelector("[data-mobile-submenu]"); 1235 if (!(trigger instanceof HTMLButtonElement) || !(submenu instanceof HTMLElement)) return; 1236 trigger.addEventListener("click", () => { 1237 const willOpen = !group.classList.contains("is-open"); 1238 mobileGroups.forEach((other) => { if (other !== group) closeMobileGroup(other); }); 1239 group.classList.toggle("is-open", willOpen); 1240 trigger.setAttribute("aria-expanded", String(willOpen)); 1241 submenu.hidden = !willOpen; 1242 }); 1243 }); 1244 1245 const closeMobileProductNested = (except = null) => { 1246 mobileProductNested.forEach((item) => { 1247 if (!(item instanceof HTMLElement) || item === except) return; 1248 item.classList.remove("is-open"); 1249 const trigger = item.querySelector("[data-mobile-product-submenu-trigger]"); 1250 const children = item.querySelector("[data-mobile-product-children]"); 1251 if (trigger instanceof HTMLButtonElement) trigger.setAttribute("aria-expanded", "false"); 1252 if (children instanceof HTMLElement) children.hidden = true; 1253 }); 1254 }; 1255 1256 mobileProductNested.forEach((item) => { 1257 if (!(item instanceof HTMLElement)) return; 1258 const trigger = item.querySelector("[data-mobile-product-submenu-trigger]"); 1259 const children = item.querySelector("[data-mobile-product-children]"); 1260 if (!(trigger instanceof HTMLButtonElement) || !(children instanceof HTMLElement)) return; 1261 trigger.addEventListener("click", () => { 1262 const willOpen = !item.classList.contains("is-open"); 1263 closeMobileProductNested(item); 1264 item.classList.toggle("is-open", willOpen); 1265 trigger.setAttribute("aria-expanded", String(willOpen)); 1266 children.hidden = !willOpen; 1267 }); 1268 }); 1269 1270 if (menuButton instanceof HTMLButtonElement && mobileMenu instanceof HTMLElement) { 1271 const setMobileMenu = (isOpen) => { 1272 menuButton.setAttribute("aria-expanded", String(isOpen)); 1273 menuButton.setAttribute("aria-label", isOpen ? "Close navigation menu" : "Open navigation menu"); 1274 mobileMenu.hidden = !isOpen; 1275 if (openIcon instanceof SVGElement) openIcon.hidden = isOpen; 1276 if (closeIcon instanceof SVGElement) closeIcon.hidden = !isOpen; 1277 if (!isOpen) { 1278 mobileGroups.forEach(closeMobileGroup); 1279 closeMobileProductNested(); 1280 } 1281 }; 1282 setMobileMenu(false); 1283 menuButton.addEventListener("click", () => { 1284 setMobileMenu(menuButton.getAttribute("aria-expanded") !== "true"); 1285 closeDesktopDropdowns(); 1286 }); 1287 navbar.querySelectorAll("[data-mobile-link]").forEach((link) => { 1288 link.addEventListener("click", () => setMobileMenu(false)); 1289 }); 1290 window.addEventListener("resize", () => { 1291 if (window.innerWidth >= 1024) setMobileMenu(false); 1292 }); 1293 } 1294 1295 document.addEventListener("click", (event) => { 1296 if (!(event.target instanceof Node)) return; 1297 if (!navbar.contains(event.target)) closeDesktopDropdowns(); 1298 }); 1299 1300 document.addEventListener("keydown", (event) => { 1301 if (event.key !== "Escape") return; 1302 closeDesktopDropdowns(); 1303 if (menuButton instanceof HTMLButtonElement && mobileMenu instanceof HTMLElement) { 1304 menuButton.setAttribute("aria-expanded", "false"); 1305 menuButton.setAttribute("aria-label", "Open navigation menu"); 1306 mobileMenu.hidden = true; 1307 if (openIcon instanceof SVGElement) openIcon.hidden = false;
1308 if (closeIcon instanceof SVGElement) closeIcon.hidden = true; 1309 mobileGroups.forEach(closeMobileGroup); 1310 closeMobileProductNested(); 1311 } 1312 }); 1313 }); 1314 })(); 1315</script>
1315<main class="charon-page" data-astro-cid-adqwrtdf><div class="section-container charon-shell" data-astro-cid-adqwrtdf><aside class="charon-toc" aria-label="Page sections" data-astro-cid-adqwrtdf><div class="charon-toc-inner" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>On this page</span><a href="#overview" class="is-active" data-astro-cid-adqwrtdf>Overview</a><a href="#process" data-astro-cid-adqwrtdf>Process</a><a href="#modularity" data-astro-cid-adqwrtdf>Modularity</a><a href="#specifications" data-astro-cid-adqwrtdf>Specifications</a><a href="#software" data-astro-cid-adqwrtdf>Software</a><a href="#monitoring" data-astro-cid-adqwrtdf>In-situ monitoring</a><a href="#publications" data-astro-cid-adqwrtdf>Publications</a></div></aside><div class="charon-content" data-astro-cid-adqwrtdf><section id="overview" class="charon-hero scroll-section" aria-labelledby="charon-title" data-astro-cid-adqwrtdf><div class="charon-hero-copy" data-astro-cid-adqwrtdf><div class="mb-5 text-sm uppercase tracking-[0.3em] text-blue-400" data-astro-cid-adqwrtdf>Solar-cell manufacturing automation</div><h1 id="charon-title" class="mb-7 text-5xl font-bold leading-[0.95] tracking-tight sm:text-6xl lg:text-7xl" data-astro-cid-adqwrtdf>Charon</h1><p class="max-w-3xl text-xl leading-relaxed text-gray-300" data-astro-cid-adqwrtdf>A modular blade-coating platform combining automated liquid deposition, heated substrate handling and inline drying in a compact system suitable for glovebox operation.</p><div class="mt-10 flex flex-wrap gap-4" data-astro-cid-adqwrtdf><a href="#process" class="rounded-2xl bg-blue-600 px-7 py-4 font-medium transition hover:bg-blue-500" data-astro-cid-adqwrtdf>Explore the system</a><a href="#specifications" class="rounded-2xl border border-blue-500/50 px-7 py-4 font-medium text-blue-300 transition hover:bg-blue-500/10" data-astro-cid-adqwrtdf>Technical data</a></div></div><figure class="charon-hero-image" data-astro-cid-adqwrtdf><button type="button" class="charon-photo-button" data-charon-photo data-full-src="/images/automation/charon-system-overview.png" data-full-alt="Charon blade coating automation system installed in a laboratory" data-full-caption="Charon laboratory system" aria-label="Open Charon system image" data-astro-cid-adqwrtdf><img src="/images/automation/charon-system-overview.png" alt="Charon blade coating automation system installed in a laboratory" loading="eager" data-astro-cid-adqwrtdf></button><figcaption data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>A4 heated vacuum chuck</span><span data-astro-cid-adqwrtdf>Glovebox-compatible format</span></figcaption></figure></section><section id="process" class="scroll-section charon-section" data-astro-cid-adqwrtdf><div class="charon-heading" data-astro-cid-adqwrtdf><div class="text-sm uppercase tracking-[0.3em] text-blue-400" data-astro-cid-adqwrtdf>Automated workflow</div><h2 data-astro-cid-adqwrtdf id="from-liquid-dosing-to-inline-drying">From liquid dosing to inline drying</h2><p data-astro-cid-adqwrtdf>Charon coordinates the principal stages of blade coating inside one programmable process.</p></div><div class="charon-step-grid" data-astro-cid-adqwrtdf><article class="charon-card charon-step-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>01</span><h3 data-astro-cid-adqwrtdf id="fix-the-substrate">Fix the substrate</h3><p data-astro-cid-adqwrtdf>The substrate is held on the heated A4 vacuum chuck throughout the coating process.</p></article><article class="charon-card charon-step-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>02</span><h3 data-astro-cid-adqwrtdf id="dose-the-material">Dose the material</h3><p data-astro-cid-adqwrtdf>An automatic syringe pump dispenses the selected solution at a controlled rate.</p></article><article class="charon-card charon-step-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>03</span><h3 data-astro-cid-adqwrtdf id="apply-the-film">Apply the film</h3><p data-astro-cid-adqwrtdf>The mechanized blade moves across the substrate with programmable speed and gap height.</p></article><article class="charon-card charon-step-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>04</span><h3 data-astro-cid-adqwrtdf id="dry-inline">Dry inline</h3><p data-astro-cid-adqwrtdf>Infrared lamps and heated Air or Nitrogen support controlled solvent removal after coating.</p></article></div></section><section id="modularity" class="scroll-section charon-section" data-astro-cid-adqwrtdf><div class="charon-feature-panel" data-astro-cid-adqwrtdf><div class="charon-feature-copy" data-astro-cid-adqwrtdf><div class="text-sm uppercase tracking-[0.3em] text-blue-400" data-astro-cid-adqwrtdf>Modular architecture</div><h2 data-astro-cid-adqwrtdf id="configured-around-the-deposition-process">Configured around the deposition process</h2><p data-astro-cid-adqwrtdf>Charon follows the same modular philosophy as ARKEO. The platform can combine Cicci Re
1315search modules, compatible ARKEO overhead modules and selected third-party equipment within one integrated system.</p><ul data-astro-cid-adqwrtdf><li data-astro-cid-adqwrtdf>Modular hardware configuration</li><li data-astro-cid-adqwrtdf>Expandable process and monitoring modules</li><li data-astro-cid-adqwrtdf>Unified control from the Charon interface</li><li data-astro-cid-adqwrtdf>Compact integration for glovebox environments</li></ul></div><button type="button" class="charon-feature-image" data-charon-photo data-full-src="/images/automation/charon-compact-system.png" data-full-alt="Compact enclosed version of the Charon automation platform" data-full-caption="Compact modular Charon configuration" aria-label="Open compact Charon image" data-astro-cid-adqwrtdf><img src="/images/automation/charon-compact-system.png" alt="Compact enclosed version of the Charon automation platform" loading="lazy" data-astro-cid-adqwrtdf></button></div></section><section id="specifications" class="scroll-section charon-section" data-astro-cid-adqwrtdf><div class="charon-heading" data-astro-cid-adqwrtdf><div class="text-sm uppercase tracking-[0.3em] text-blue-400" data-astro-cid-adqwrtdf>Technical data</div><h2 data-astro-cid-adqwrtdf id="core-specifications">Core specifications</h2></div><div class="charon-spec-grid" data-astro-cid-adqwrtdf><article class="charon-card charon-spec-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>Working area</span><strong data-astro-cid-adqwrtdf>Up to 300 à 210 mm</strong></article><article class="charon-card charon-spec-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>Vacuum chuck</span><strong data-astro-cid-adqwrtdf>A4 format</strong></article><article class="charon-card charon-spec-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>Chuck temperature</span><strong data-astro-cid-adqwrtdf>25â150 °C</strong></article><article class="charon-card charon-spec-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>Drawing speed</span><strong data-astro-cid-adqwrtdf>0â500 mm/s</strong></article><article class="charon-card charon-spec-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>Speed resolution</span><strong data-astro-cid-adqwrtdf>1 mm/s</strong></article><article class="charon-card charon-spec-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>Speed tolerance</span><strong data-astro-cid-adqwrtdf>1%</strong></article><article class="charon-card charon-spec-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>Gap height</span><strong data-astro-cid-adqwrtdf>15â3000 μm</strong></article><article class="charon-card charon-spec-card" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>Gap tolerance</span><strong data-astro-cid-adqwrtdf>±10 μm</strong></article></div></section><section id="software" class="scroll-section charon-section" data-astro-cid-adqwrtdf><div class="charon-software-grid" data-astro-cid-adqwrtdf><button type="button" class="charon-software-image" data-charon-photo data-full-src="/images/automation/charon-software-interface.png" data-full-alt="Charon LabVIEW touchscreen control interface" data-full-caption="Example interface for coating, heated airflow and infrared-lamp control" aria-label="Open Charon software interface image" data-astro-cid-adqwrtdf><img src="/images/automation/charon-software-interface.png" alt="Charon LabVIEW touchscreen control interface" loading="lazy" data-astro-cid-adqwrtdf></button><div data-astro-cid-adqwrtdf><div class="mb-5 text-sm uppercase tracking-[0.3em] text-blue-400" data-astro-cid-adqwrtdf>Integrated software</div><h2 class="mb-6 text-4xl font-bold leading-tight lg:text-5xl" data-astro-cid-adqwrtdf id="one-interface-for-the-complete-routine">One interface for the complete routine</h2><p class="text-lg leading-relaxed text-gray-300" data-astro-cid-adqwrtdf>The LabVIEW-based software provides real-time monitoring and control of the coating system. Users can build customized recipes that coordinate movement, syringe dispensing, chuck temperature, heated gas flow and infrared drying.</p><div class="charon-software-points" data-astro-cid-adqwrtdf><span data-astro-cid-adqwrtdf>Custom recipes</span><span data-astro-cid-adqwrtdf>Real-time control</span><span data-astro-cid-adqwrtdf>Coordinated modules</span><span data-astro-cid-adqwrtdf>Touchscreen operation</span></div></div></div></section><section id="monitoring" class="scroll-section charon-section" data-astro-cid-adqwrtdf><div class="charon-heading" data-astro-cid-adqwrtdf><div class="text-sm uppercase tracking-[0.3em] text-blue-400" data-astro-cid-adqwrtdf>Optional modules</div><h2 data-astro-cid-adqwrtdf id="in-situ-monitoring-and-characterization">In-situ monitoring and characterization</h2><p data-astro-cid-adqwrtdf>Monitoring accessories can be added according to the required deposition workflow.</p></div><div class="charon-module-grid" data-astro-cid-adqwrtdf><article class="charon-card" data-astro-cid-adqwrtdf><h3 data-astro-cid-adqwrtdf id="scientific-camera">Scientific camera</h3><p data-astro-cid-adqwrtdf>Visual in-situ monitoring of coating and film formation.</p></article><article class="charon-card" data-astro-cid-adqwrtdf><h3 data-astro-cid-adqwrtdf id="spectroradiometer">Spectroradiometer</h3><p data-astro-cid-adqwrtdf>Optical monitoring during deposition and drying.</p></article><article class="charon-card" data-astro-cid-adqwrtdf><h3 data-astro-cid-adqwrtdf id="pl-light-source">PL light source</h3><p data-astro-cid-adqwrtdf>Integrated excitation for in-situ photoluminescence workflows.</p></article><article class="charon-card" data-astro-cid-adqwrtdf><h3 data-astro-cid-adqwrtdf id="third-party-equipment">Third-party equipment</h3><p data-astro-cid-adqwrtdf>Selected external instruments can be integrated into the same control environment.</p></article></div><div class="charon-gallery" aria-label="Charon image gallery" data-astro-cid-adqwrtdf><figure data-astro-cid-adqwrtdf><button type="button" data-charon-photo data-full-src="/images/automation/charon-system-overview.png" data-full-alt="Charon blade coating automation system installed in a laboratory" data-full-caption="Charon laboratory system" aria-label="Open image: Charon laboratory system" data-astro-cid-adqwrtdf><img src="/images/automation/charon-system-overview.png" alt="Charon blade coating automation system installed in a laboratory" loading="lazy" data-astro-cid-adqwrtdf></button><figcaption data-astro-cid-adqwrtdf>Charon laboratory system</figcaption></figure><figure data-astro-cid-adqwrtdf><button type="button" data-charon-photo data-full-src="/images/automation/charon-coating-head.png" data-full-alt="Close view of the Charon heated chuck, coating head and inline dryer" data-full-caption="Coating and inline-drying area" aria-label="Open image: Coating and inline-drying area" data-astro-cid-adqwrtdf><img src="/images/automation/charon-coating-head.png" alt="Close view of the Charon heated chuck, coating head and inline dryer" loading="lazy" data-astro-cid-adqwrtdf></button><figcaption data-astro-cid-adqwrtdf>Coating and inline-drying area</figcaption></figure><figure data-astro-cid-adqwrtdf><button type="button" data-charon-photo data-full-src="/images/automation/charon-compact-system.png" data-full-alt="Compact enclosed version of the Charon automation platform" data-full-caption="Compact modular configuration" aria-label="Open image: Compact modular configuration" data-astro-cid-adqwrtdf><img src="/images/automation/charon-compact-system.png" alt="Compact enclosed version of the Charon automation platform" loading="lazy" data-astro-cid-adqwrtdf></button><figcaption data-astro-cid-adqwrtdf>Compact modular configuration</figcaption></figure><figure data-astro-cid-adqwrtdf><button type="button" data-charon-photo data-full-src="/images/automation/charon-blade-assembly.png" data-full-alt="Mechanical blade coating assembly used in the Charon system" data-full-caption="Mechanical blade-coating assembly" aria-label="Open image: Mechanical blade-coating assembly" data-astro-cid-adqwrtdf><img src="/images/automation/charon-blade-assembly.png" alt="Mechanical blade coating assembly used in the Charon system" loading="lazy" data-astro-cid-adqwrtdf></button><figcaption data-astro-cid-adqwrtdf>Mechanical blade-coating assembly</figcaption></figure><figure data-astro-cid-adqwrtdf><button type="button" data-charon-photo data-full-src="/images/automation/charon-cad-integration.png" data-full-alt="CAD view showing the Charon coating assembly integrated into the automation platform" data-full-caption="CAD view of the modular coating assembly" aria-label="Open image: CAD view of the modular coating assembly" data-astro-cid-adqwrtdf><img src="/images/automation/charon-cad-integration.png" alt="CAD view showing the Charon coating assembly integrated into the automation platform" loading="lazy" data-astro-cid-adqwrtdf></button><figcaption data-astro-cid-adqwrtdf>CAD view of the modular coating assembly</figcaption></figure></div></section><section id="publications" class="scroll-section charon-section" data-charon-publications-root data-astro-cid-adqwrtdf><div class="charon-publications-heading" data-astro-cid-adqwrtdf><div data-astro-cid-adqwrtdf><div class="mb-4 text-sm uppercase tracking-[0.3em] text-blue-400" data-astro-cid-adqwrtdf>Featured Charon</div><h2 data-astro-cid-adqwrtdf id="charon-in-scientific-research">Charon in scientific research</h2><p data-astro-cid-adqwrtdf>Peer-reviewed articles and scientific outputs in which Charon was used for scalable coating, controlled drying or automated device fabrication.</p></div><a href="/publications/" class="charon-publications-all" data-astro-cid-adqwrtdf>View all publications<svg viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-adqwrtdf><path d="M7 17 17 7M8 7h9v9" data-astro-cid-adqwrtdf></path></svg></a></div>
1315<div class="charon-publications-status" aria-live="polite" data-astro-cid-adqwrtdf><span class="charon-publications-loader" aria-hidden="true" data-astro-cid-adqwrtdf></span><span data-charon-publications-status data-astro-cid-adqwrtdf>Loading Charon publicationsâ¦</span></div><div class="charon-publications-grid" data-charon-publications-list aria-busy="true" data-astro-cid-adqwrtdf></div><div class="charon-publications-message" data-charon-publications-empty hidden data-astro-cid-adqwrtdf><strong data-astro-cid-adqwrtdf>No tagged publications found</strong><p data-astro-cid-adqwrtdf>Add the tag <code data-astro-cid-adqwrtdf>Charon</code> to the relevant Zotero records and check that they belong to the collection used by the website.</p></div><div class="charon-publications-message" data-charon-publications-error hidden data-astro-cid-adqwrtdf><strong data-astro-cid-adqwrtdf>Publications could not be loaded</strong><p data-astro-cid-adqwrtdf>The Zotero publication service is temporarily unavailable.</p></div></section><section class="charon-contact" data-astro-cid-adqwrtdf><div data-astro-cid-adqwrtdf><div class="mb-4 text-sm uppercase tracking-[0.3em] text-blue-400" data-astro-cid-adqwrtdf>Configure Charon</div><h2 data-astro-cid-adqwrtdf id="build-the-automation-system-around-your-process">Build the automation system around your process.</h2></div><a href="/cdn-cgi/l/email-protection#d7beb9b1b897b4beb4b4bea5b2a4b2b6a5b4bff9bea3" data-astro-cid-adqwrtdf>Contact Cicci Research</a></section></div></div></main><div class="charon-lightbox" data-charon-lightbox aria-hidden="true" data-astro-cid-adqwrtdf><button type="button" class="charon-lightbox-close" data-charon-lightbox-close aria-label="Close image" data-astro-cid-adqwrtdf>Ã</button><div class="charon-lightbox-content" data-astro-cid-adqwrtdf><img data-charon-lightbox-image src="" alt="" data-astro-cid-adqwrtdf><p data-charon-lightbox-caption data-astro-cid-adqwrtdf></p></div></div>
vendor: 108 bytes, line 1315
1315<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
1315<script> 1316 function initCharonPage() { 1317 const page = document.querySelector(".charon-page"); 1318 1319 if (!page || page.dataset.initialized === "true") { 1320 return; 1321 } 1322 1323 page.dataset.initialized = "true"; 1324 1325 const navLinks = Array.from(document.querySelectorAll(".charon-toc a")); 1326 const sections = Array.from(document.querySelectorAll(".scroll-section")); 1327 1328 if (navLinks.length && sections.length) { 1329 const setActiveLink = (id) => { 1330 navLinks.forEach((link) => { 1331 link.classList.toggle("is-active", link.getAttribute("href") === `#${id}`); 1332 }); 1333 }; 1334 1335 const observer = new IntersectionObserver( 1336 (entries) => { 1337 const visible = entries 1338 .filter((entry) => entry.isIntersecting) 1339 .sort((a, b) => b.intersectionRatio - a.intersectionRatio); 1340 1341 if (visible[0]) { 1342 setActiveLink(visible[0].target.id); 1343 } 1344 }, 1345 { 1346 rootMargin: "-24% 0px -58% 0px", 1347 threshold: [0.05, 0.2, 0.45], 1348 }, 1349 ); 1350 1351 sections.forEach((section) => observer.observe(section)); 1352 } 1353 1354 const lightbox = document.querySelector("[data-charon-lightbox]"); 1355 const lightboxImage = document.querySelector("[data-charon-lightbox-image]"); 1356 const lightboxCaption = document.querySelector("[data-charon-lightbox-caption]"); 1357 1358 const closeLightbox = () => { 1359 if (!lightbox || !lightboxImage || !lightboxCaption) return; 1360 1361 lightbox.classList.remove("is-open"); 1362 lightbox.setAttribute("aria-hidden", "true"); 1363 document.body.classList.remove("charon-lightbox-open"); 1364 lightboxImage.src = ""; 1365 lightboxImage.alt = ""; 1366 lightboxCaption.textContent = ""; 1367 }; 1368 1369 document.addEventListener("click", (event) => { 1370 const target = event.target; 1371 if (!(target instanceof Element)) return; 1372 1373 const photo = target.closest("[data-charon-photo]"); 1374 1375 if (photo && lightbox && lightboxImage && lightboxCaption) { 1376 lightboxImage.src = photo.dataset.fullSrc || ""; 1377 lightboxImage.alt = photo.dataset.fullAlt || ""; 1378 lightboxCaption.textContent = photo.dataset.fullCaption || ""; 1379 1380 lightbox.classList.add("is-open"); 1381 lightbox.setAttribute("aria-hidden", "false"); 1382 document.body.classList.add("charon-lightbox-open"); 1383 return; 1384 } 1385 1386 if ( 1387 target.matches("[data-charon-lightbox-close]") || 1388 target.matches("[data-charon-lightbox]") 1389 ) { 1390 closeLightbox(); 1391 } 1392 }); 1393 1394 document.addEventListener("keydown", (event) => { 1395 if (event.key === "Escape") { 1396 closeLightbox(); 1397 } 1398 }); 1399 1400 const publicationRoot = page.querySelector("[data-charon-publications-root]"); 1401 1402 if (publicationRoot) { 1403 initialiseCharonPublications(publicationRoot); 1404 } 1405 } 1406 1407 function createCharonExternalIcon() { 1408 const namespace = "http://www.w3.org/2000/svg"; 1409 const svg = document.createElementNS(namespace, "svg"); 1410 svg.setAttribute("viewBox", "0 0 24 24"); 1411 svg.setAttribute("aria-hidden", "true"); 1412 1413 const path = document.createElementNS(namespace, "path"); 1414 path.setAttribute("d", "M7 17 17 7M8 7h9v9"); 1415 svg.append(path); 1416 1417 return svg; 1418 } 1419 1420 function createCharonPublicationCard(publication) { 1421 const article = document.createElement("article"); 1422 article.className = "charon-publication-card"; 1423 1424 const topMeta = document.createElement("div"); 1425 topMeta.className = "charon-publication-meta-top"; 1426 1427 const year = document.createElement("span"); 1428 year.className = "charon-publication-year"; 1429 year.textContent = publication.year ? String(publication.year) : "Year unavailable"; 1430 topMeta.append(year); 1431 1432 if (publication.type) { 1433 const type = document.createElement("span"); 1434 type.className = "charon-publication-type"; 1435 type.textContent = publication.type; 1436 topMeta.append(type); 1437 } 1438 1439 const heading = document.createElement("h3"); 1440 1441 if (publication.url) { 1442 const titleLink = document.createElement("a"); 1443 titleLink.href = publication.url; 1444 titleLink.target = "_blank"; 1445 titleLink.rel = "noopener noreferrer"; 1446 titleLink.textContent = publication.title || "Untitled publication"; 1447 heading.append(titleLink); 1448 } else { 1449 heading.textContent = publication.title || "Untitled publication"; 1450 } 1451 1452 article.append(topMeta, heading); 1453 1454 if (publication.authorDisplay) { 1455 const authors = document.createElement("p"); 1456 authors.className = "charon-publication-authors"; 1457 authors.textContent = publication.authorDisplay; 1458 1459 if (Array.isArray(publication.authors) && publication.authors.length) { 1460 authors.title = publication.authors.join(", "); 1461 } 1462 1463 article.append(authors); 1464 } 1465 1466 if (publication.journal) { 1467 const journal = document.createElement("p"); 1468 journal.className = "charon-publication-journal"; 1469 journal.textContent = publication.journal; 1470 article.append(journal); 1471 } 1472 1473 if (publication.charonRole) { 1474 const role = document.createElement("p"); 1475 role.className = "charon-publication-role"; 1476 role.textContent = publication.charonRole; 1477 article.append(role); 1478 } 1479 1480 const actions = document.createElement("div"); 1481 actions.className = "charon-publication-actions"; 1482 1483 const doi = document.createElement("span"); 1484 doi.className = "charon-publication-doi"; 1485 doi.textContent = publication.doi ? `DOI: ${publication.doi}` : "Zotero record"; 1486 actions.append(doi); 1487 1488 if (publication.url) { 1489 const publicationLink = document.createElement("a"); 1490 publicationLink.className = "charon-publication-link"; 1491 publicationLink.href = publication.url; 1492 publicationLink.target = "_blank"; 1493 publicationLink.rel = "noopener noreferrer"; 1494 publicationLink.append( 1495 document.createTextNode("View publication"), 1496 createCharonExternalIcon(), 1497 ); 1498 actions.append(publicationLink); 1499 } 1500 1501 article.append(actions); 1502 return article; 1503 } 1504 1505 async function initialiseCharonPublications(root) { 1506 if (root.dataset.publicationsReady === "true") { 1507 return; 1508 } 1509 1510 root.dataset.publicationsReady = "true"; 1511 1512 const list = root.querySelector("[data-charon-publications-list]"); 1513 const status = root.querySelector("[data-charon-publications-status]"); 1514 const statusContainer = root.querySelector(".charon-publications-status"); 1515 const empty = root.querySelector("[data-charon-publications-empty]"); 1516 const error = root.querySelector("[data-charon-publications-error]"); 1517 1518 if (!list || !status || !statusContainer || !empty || !error) { 1519 return; 1520 } 1521 1522 try { 1523 const response = await fetch("/api/charon-publications", { 1524 headers: { 1525 Accept: "application/json", 1526 }, 1527 cache: "no-store", 1528 }); 1529 1530 if (!response.ok) { 1531 throw new Error(`Publication API returned HTTP ${response.status}`); 1532 } 1533 1534 const data = await response.json(); 1535 const publications = Array.isArray(data.publications) 1536 ? data.publications 1537 : []; 1538 1539 list.replaceChildren(...publications.map(createCharonPublicationCard)); 1540 list.setAttribute("aria-busy", "false"); 1541 statusContainer.classList.add("is-ready"); 1542 status.textContent = `${publications.length} Charon publication${ 1543 publications.length === 1 ? "" : "s" 1544 }`; 1545 empty.hidden = publications.length !== 0; 1546 error.hidden = true; 1547 } catch (publicationError) { 1548 console.error("Unable to load Charon publications:", publicationError); 1549 list.replaceChildren(); 1550 list.setAttribute("aria-busy", "false"); 1551 statusContainer.classList.add("is-error"); 1552 status.textContent = "Publication database unavailable"; 1553 empty.hidden = true; 1554 error.hidden = false;
1555 } 1556 } 1557 1558 if (document.readyState === "loading") { 1559 document.addEventListener("DOMContentLoaded", initCharonPage, { once: true }); 1560 } else { 1561 initCharonPage(); 1562 } 1563 1564 document.addEventListener("astro:page-load", initCharonPage); 1565</script>
1565<footer id="contact" class="border-t border-white/10 bg-black/30 mt-32"><div class="section-container py-20"><div class="grid lg:grid-cols-4 gap-14"><!-- Company --><div><div class="flex items-center gap-3 mb-6"><img src="/logos/logo.png" class="h-12" alt="Cicci Research"><div><div class="font-bold text-2xl tracking-wide">CICCI</div><div class="text-xs text-blue-400 tracking-[0.25em]">RESEARCH</div></div></div><p class="text-gray-400 leading-relaxed text-sm">Advanced scientific instrumentation for photovoltaic, optoelectronic and materials characterization.</p></div><!-- Products --><div><h3 class="text-lg font-semibold mb-6" id="products">Products</h3><div class="flex flex-col gap-4 text-gray-400 text-sm"><a href="https://docs.cicciresearch.com/arkeo-multichannel-manual/" class="hover:text-blue-400 transition">ARKEO Platform</a><a href="https://docs.cicciresearch.com/products/light-sources/" class="hover:text-blue-400 transition">Light Sources</a><a href="https://docs.cicciresearch.com/products/chambers/" class="hover:text-blue-400 transition">Environmental Chambers</a><a href="https://docs.cicciresearch.com/arkeo-manual/software/overview/" class="hover:text-blue-400 transition">Software</a></div></div><!-- Resources --><div><h3 class="text-lg font-semibold mb-6" id="resources">Resources</h3><div class="flex flex-col gap-4 text-gray-400 text-sm"><a href="https://www.cicciresearch.com/documentation/" class="hover:text-blue-400 transition">Documentation</a><a href="https://www.cicciresearch.com/publications/" class="hover:text-blue-400 transition">Publications</a><a href="#" class="hover:text-blue-400 transition">Application Notes</a><a href="/brand-kit/" class="hover:text-blue-400 transition">Downloads</a></div></div><!-- Contact --><div><h3 class="text-lg font-semibold mb-6" id="contact-2">Contact</h3><div class="flex flex-col gap-4 text-gray-400 text-sm"><div>Cicci Research s.r.l.</div><a href="https://maps.app.goo.gl/kVWVQnnuE81SsR6h9" target="_blank" rel="noopener noreferrer" class="hover:text-blue-400 transition">Via Giordania 227, Grosseto, Italy</a><a href="/cdn-cgi/l/email-protection#51383f373e11323832323823342234302332397f3825" class="hover:text-blue-400 transition"><span class="__cf_email__" data-cfemail="afc6c1c9c0efccc6ccccc6ddcadccaceddccc781c6db">[email protected]</span></a><a href="https://wa.me/393429038762" class="hover:text-blue-400 transition">+39 342 903 8762</a><a href="https://www.cicciresearch.com/" class="hover:text-blue-400 transition">www.cicciresearch.com</a></div></div></div><!-- Bottom --><div class="border-t border-white/10 mt-16 pt-8 flex flex-col lg:flex-row justify-between gap-5 text-sm text-gray-500"><div>© 2026 Cicci Research s.r.l. All rights reserved.</div><a href="/privacyCookies">Privacy & Cookies</a><div class="flex gap-6"><a href="https://www.linkedin.com/company/cicci-research-srl" target="_blank" rel="noopener noreferrer" class="hover:text-blue-400 transition">LinkedIn</a><a href="https://github.com/cicciresearch" target="_blank" rel="noopener noreferrer" class="hover:text-blue-400 transition">GitHub</a><a href="https://www.youtube.com/@cicciresearch8847" target="_blank" rel="noopener noreferrer" class="hover:text-blue-400 transition">YouTube</a></div></div></div></footer><!-- Cloudflare Pages Analytics -->
vendor: 108 bytes, line 1565
1565<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
vendor: 99 bytes, line 1565
1565<script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "
15653e98fbbb6cc04595a339effc7746daf7
vendor: 13 bytes, line 1565
1565"}'></script>
1565<!-- Cloudflare Pages Analytics -->
1565<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a472e248ec1a9d52',t:'MTc5MTQzODc5Mw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
1565</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.