diff --git a/station-navigation/app.js b/station-navigation/app.js index 5289672b..c0d9aacb 100644 --- a/station-navigation/app.js +++ b/station-navigation/app.js @@ -1,4 +1,4 @@ -const state = { stations: [], query: '', searchText: '', locationFilters: [], suggestions: [], activeSuggestion: -1, searchDebounce: null, stationFilter: 'all', selected: null, userLocation: null, map: null, markers: [], userMarker: null, pitch: true }; +const state = { stations: [], query: '', searchText: '', locationFilters: [], suggestions: [], activeSuggestion: -1, searchDebounce: null, stationFilter: 'all', selected: null, userLocation: null, map: null, markers: [], userMarker: null, pitch: true, regionPicker: null }; document.addEventListener('DOMContentLoaded', () => { initClock(); initMap(); loadStations(); }); document.addEventListener('pointerdown', event => { if (!event.target?.closest?.('.map-explore-toolbar')) closeStationSearchSuggestions(); }); @@ -45,7 +45,7 @@ function stationMatchesLocation(station) { return !state.locationFilters.length function stationTypeScope() { return state.stations.filter(station => state.stationFilter !== 'partner' || station.cooperative); } function filteredStations() { return stationTypeScope().filter(station => stationMatchesLocation(station) && stationMatchesQuery(station)); } function stationSearchScore(station, query) { - const fields = [station.name, station.shortName, station.province, station.city, station.district, station.address].filter(Boolean); + const fields = [station.name, station.shortName, station.address].filter(Boolean); for (const value of fields) { const text = normalize(value); if (text.includes(query)) return 3; const pinyin = toPinyin(value); if (pinyin.includes(query) || initials(pinyin).includes(query)) return 2; } return 0; } @@ -123,7 +123,8 @@ function stationLocationOptions() { const add = (level, station) => { const province = station.province || '', city = station.city || '', district = station.district || ''; const key = level === 'province' ? `province|${province}` : level === 'city' ? `city|${province}|${city}` : `district|${province}|${city}|${district}`; - if (!groups.has(key)) groups.set(key, { type: 'location', level, province, city, district, stations: [] }); + const filter = level === 'province' ? { province, city: '', district: '' } : level === 'city' ? { province, city, district: '' } : { province, city, district }; + if (!groups.has(key)) groups.set(key, { type: 'location', level, ...filter, stations: [] }); groups.get(key).stations.push(station); }; for (const station of stationTypeScope()) { const district = stationDistrictName(station); if (station.province) add('province', station); if (station.province && station.city) add('city', station); if (station.province && station.city && district) add('district', { ...station, district }); } @@ -145,13 +146,9 @@ function stationEntitySuggestions(query) { } function renderStationSearchSuggestions() { const box = document.getElementById('stationSearchSuggestions'); if (!box || box.hidden) return; - const locations = stationLocationSuggestions(state.searchText), stations = stationEntitySuggestions(state.searchText); - state.suggestions = [...locations, ...stations]; state.activeSuggestion = Math.min(state.activeSuggestion, state.suggestions.length - 1); - if (!state.suggestions.length) { box.innerHTML = '未找到匹配的位置或站点'; return; } - let html = ''; - if (locations.length) html += `位置${locations.map((item, index) => ``).join('')}`; - if (stations.length) html += `加氢站${stations.map((item, offset) => { const index = locations.length + offset, station = item.station; return ``; }).join('')}`; - box.innerHTML = html; + state.suggestions = stationEntitySuggestions(state.searchText); state.activeSuggestion = Math.min(state.activeSuggestion, state.suggestions.length - 1); + if (!state.suggestions.length) { box.innerHTML = '未找到匹配的站点或详细地址'; return; } + box.innerHTML = `加氢站${state.suggestions.map((item, index) => { const station = item.station; return ``; }).join('')}`; } function openStationSearchSuggestions() { const box = document.getElementById('stationSearchSuggestions'); if (!box) return; box.hidden = false; renderStationSearchSuggestions(); } function closeStationSearchSuggestions() { const box = document.getElementById('stationSearchSuggestions'); if (box) box.hidden = true; state.activeSuggestion = -1; } @@ -160,6 +157,7 @@ function renderLocationFilterChips() { const rail = document.getElementById('selectedLocationFilters'); if (!rail) return; rail.hidden = !state.locationFilters.length; rail.innerHTML = state.locationFilters.map(filter => { const key = locationFilterKey(filter), label = locationFilterLabel(filter); return ``; }).join(''); + const count = document.getElementById('regionPickerTriggerCount'); if (count) { count.hidden = !state.locationFilters.length; count.textContent = state.locationFilters.length; } } function removeLocationFilter(key) { state.locationFilters = state.locationFilters.filter(filter => locationFilterKey(filter) !== key); @@ -171,6 +169,55 @@ function addLocationFilter(option) { if (!state.locationFilters.some(current => locationFilterKey(current) === locationFilterKey(filter))) state.locationFilters.push(filter); renderLocationFilterChips(); } +function cloneLocationFilter(filter) { return { level: filter.level, province: filter.province || '', city: filter.city || '', district: filter.district || '', label: filter.label || '' }; } +function openRegionPicker() { + state.regionPicker = { level: 'province', province: '', city: '', search: '', draft: state.locationFilters.map(cloneLocationFilter), options: [] }; + const modal = document.getElementById('regionPickerModal'); if (modal) modal.hidden = false; + renderRegionPicker(); window.setTimeout(() => document.getElementById('regionPickerSearch')?.focus(), 0); +} +function closeRegionPicker() { state.regionPicker = null; const modal = document.getElementById('regionPickerModal'); if (modal) modal.hidden = true; } +function regionPickerOptions() { + const picker = state.regionPicker; if (!picker) return []; + const all = stationLocationOptions(); const needle = normalize(picker.search); + if (needle) return all.map(option => ({ ...option, score: Math.min(fuzzySearchScore(option.label, needle), fuzzySearchScore(option.path, needle)) })).filter(option => Number.isFinite(option.score)).sort((left, right) => left.score - right.score || left.label.localeCompare(right.label, 'zh-CN')).slice(0, 80); + if (picker.level === 'province') return all.filter(option => option.level === 'province'); + if (picker.level === 'city') return all.filter(option => option.level === 'city' && option.province === picker.province); + return all.filter(option => option.level === 'district' && option.province === picker.province && option.city === picker.city); +} +function regionPickerOptionPath(option) { + const parts = option.level === 'province' ? [option.province] : option.level === 'city' ? [option.province, option.city] : [option.province, option.city, option.district]; + return `${compactPath(parts)} · ${formatNumber(option.stations.length)} 座站点`; +} +function regionPickerOptionSelected(option) { return state.regionPicker?.draft.some(filter => locationFilterKey(filter) === locationFilterKey(option)); } +function renderRegionPicker() { + const picker = state.regionPicker; if (!picker) return; + const search = document.getElementById('regionPickerSearch'); if (search && search.value !== picker.search) search.value = picker.search; + const clear = document.getElementById('regionPickerSearchClear'); if (clear) clear.hidden = !picker.search; + const selected = document.getElementById('regionPickerSelected'); + if (selected) selected.innerHTML = picker.draft.length ? `已选${picker.draft.map((filter, index) => ``).join('')}` : '可同时选择多个省 / 市 / 区县'; + const path = document.getElementById('regionPickerPath'); + if (path) { + const crumbs = [{ label: '全部省份', level: 'province' }]; + if (picker.province) crumbs.push({ label: stationGroupName({ province: picker.province }, 'province'), level: 'city' }); + if (picker.city) crumbs.push({ label: stationGroupName({ city: picker.city }, 'city'), level: 'district' }); + path.innerHTML = picker.search ? '搜索结果' : crumbs.map((crumb, index) => ``).join(''); + } + const options = regionPickerOptions(); picker.options = options; + const list = document.getElementById('regionPickerList'); + if (list) list.innerHTML = options.length ? options.map((option, index) => { + const selectedOption = regionPickerOptionSelected(option), canDescend = !picker.search && option.level !== 'district'; + return `
${canDescend ? `` : ''}
`; + }).join('') : '
没有可选择的运营区域
'; + const summary = document.getElementById('regionPickerSummary'); if (summary) summary.textContent = `已选 ${picker.draft.length} 项`; + const confirm = document.querySelector('.region-picker-confirm'); if (confirm) confirm.textContent = picker.draft.length ? `确认(${picker.draft.length})` : '确认'; +} +function setRegionPickerSearch(value) { if (!state.regionPicker) return; state.regionPicker.search = value; renderRegionPicker(); } +function navigateRegionPicker(level) { if (!state.regionPicker) return; state.regionPicker.search = ''; if (level === 'province') { state.regionPicker.level = 'province'; state.regionPicker.province = ''; state.regionPicker.city = ''; } else if (level === 'city') { state.regionPicker.level = 'city'; state.regionPicker.city = ''; } else state.regionPicker.level = 'district'; renderRegionPicker(); } +function descendRegionPicker(index) { const option = state.regionPicker?.options[index]; if (!option || option.level === 'district') return; state.regionPicker.search = ''; state.regionPicker.level = option.level === 'province' ? 'city' : 'district'; state.regionPicker.province = option.province; state.regionPicker.city = option.city || ''; renderRegionPicker(); } +function toggleRegionPickerOption(index) { const option = state.regionPicker?.options[index]; if (!option) return; const key = locationFilterKey(option), draft = state.regionPicker.draft; const found = draft.findIndex(filter => locationFilterKey(filter) === key); if (found >= 0) draft.splice(found, 1); else draft.push(cloneLocationFilter(option)); renderRegionPicker(); } +function removeRegionPickerDraft(index) { if (!state.regionPicker) return; state.regionPicker.draft.splice(index, 1); renderRegionPicker(); } +function clearRegionPickerDraft() { if (!state.regionPicker) return; state.regionPicker.draft = []; renderRegionPicker(); } +function confirmRegionPicker() { if (!state.regionPicker) return; state.locationFilters = state.regionPicker.draft.map(cloneLocationFilter); state.query = ''; state.searchText = ''; const input = document.getElementById('stationSearch'); if (input) input.value = ''; const clear = document.getElementById('clearSearch'); if (clear) clear.hidden = true; state.selected = null; closeDetail(); closeStationSearchSuggestions(); renderLocationFilterChips(); closeRegionPicker(); render(); if (state.locationFilters.length) { focusSearchResults(); document.getElementById('selectedRegionHint').textContent = `视角: 已筛选 ${state.locationFilters.map(locationFilterLabel).join('、')}`; } else resetMap(); } function focusSearchResults() { const stations = filteredStations(); if (!state.map || !stations.length) return; const longitudes = stations.map(station => Number(station.longitude)), latitudes = stations.map(station => Number(station.latitude)); @@ -182,7 +229,6 @@ function focusSearchResults() { function handleStationSearchInput(value) { state.searchText = value; document.getElementById('clearSearch').hidden = !normalize(value); state.activeSuggestion = -1; openStationSearchSuggestions(); window.clearTimeout(state.searchDebounce); state.searchDebounce = window.setTimeout(() => { state.query = value; state.selected = null; closeDetail(); render(); if (normalize(value)) focusSearchResults(); }, 180); } function selectStationSearchSuggestion(index) { const item = state.suggestions[index]; if (!item) return; - if (item.type === 'location') { addLocationFilter(item); state.query = ''; state.searchText = ''; document.getElementById('stationSearch').value = ''; document.getElementById('clearSearch').hidden = true; state.map?.setZoomAndCenter(item.level === 'province' ? 7.2 : item.level === 'city' ? 10.5 : 13, item.lnglat); document.getElementById('selectedRegionHint').textContent = `视角: 已筛选 ${state.locationFilters.map(locationFilterLabel).join('、')}`; closeStationSearchSuggestions(); render(); return; } closeStationSearchSuggestions(); selectStation(item.station, true); } function setStationFilter(filter) { if (!['all', 'partner'].includes(filter) || state.stationFilter === filter) return; state.stationFilter = filter; state.selected = null; closeDetail(); document.querySelectorAll('[data-station-filter]').forEach(button => button.classList.toggle('is-active', button.dataset.stationFilter === filter)); render(); renderStationSearchSuggestions(); } diff --git a/station-navigation/index.html b/station-navigation/index.html index decae925..2201bac1 100644 --- a/station-navigation/index.html +++ b/station-navigation/index.html @@ -40,7 +40,8 @@
正在同步加氢站数据…
-
+ +
全部 0 @@ -61,6 +62,16 @@
+ diff --git a/station-navigation/styles.css b/station-navigation/styles.css index ce36b532..027f6b39 100644 --- a/station-navigation/styles.css +++ b/station-navigation/styles.css @@ -6,6 +6,51 @@ .station-navigation-shell .map-filter-chip { min-height: 27px; padding: 0 9px; border: 1px solid var(--glass-border); border-radius: 8px; background: var(--pill-bg); color: var(--text-muted); font-size: 10px; font-weight: 700; cursor: pointer; } .station-navigation-shell .map-filter-chip.is-active { border-color: rgba(0,113,67,.24); background: rgba(0,113,67,.1); color: var(--accent-primary); } .station-navigation-shell .filter-chip-rail { display: flex; gap: 6px; } +.station-navigation-shell .region-picker-trigger { display: inline-flex; align-items: center; gap: 5px; min-height: 29px; padding: 0 9px; border: 1px solid var(--glass-border); border-radius: 9px; background: var(--pill-bg); color: var(--text-main); font-size: 10px; font-weight: 700; white-space: nowrap; cursor: pointer; } +.station-navigation-shell .region-picker-trigger:hover { border-color: rgba(0,113,67,.32); color: var(--accent-primary); } +.station-navigation-shell .region-picker-trigger-icon { color: var(--accent-primary); font-size: 14px; line-height: 1; } +.station-navigation-shell .region-picker-trigger b { display: grid; min-width: 15px; height: 15px; place-items: center; border-radius: 99px; background: var(--accent-primary); color: #fff; font-size: 9px; } +.region-picker-backdrop { position: fixed; z-index: 1000; inset: 0; display: grid; place-items: center; padding: 20px; background: rgba(15,23,42,.38); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); } +.region-picker-backdrop[hidden] { display: none; } +.region-picker-dialog { display: flex; width: min(500px, 100%); max-height: min(720px, calc(100dvh - 40px)); flex-direction: column; overflow: hidden; border: 1px solid var(--glass-border); border-radius: 18px; background: var(--glass-bg); box-shadow: 0 28px 70px rgba(15,23,42,.28); } +.region-picker-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 16px 17px 12px; border-bottom: 1px solid var(--glass-border); } +.region-picker-eyebrow { display: block; margin-bottom: 3px; color: var(--text-muted); font-size: 9px; font-weight: 700; letter-spacing: .07em; } +.region-picker-header h2 { margin: 0; color: var(--text-main); font-size: 16px; letter-spacing: -.02em; } +.region-picker-header h2 em { margin-left: 5px; color: var(--accent-primary); font-size: 11px; font-style: normal; font-weight: 700; } +.region-picker-close { display: grid; width: 30px; height: 30px; flex: 0 0 30px; place-items: center; border: 1px solid var(--glass-border); border-radius: 9px; background: var(--pill-bg); color: var(--text-muted); font: 400 20px/1 -apple-system, BlinkMacSystemFont, sans-serif; cursor: pointer; } +.region-picker-selected { display: flex; min-height: 38px; align-items: center; gap: 6px; overflow-x: auto; padding: 8px 17px; border-bottom: 1px solid var(--glass-border); scrollbar-width: none; } +.region-picker-selected::-webkit-scrollbar { display: none; } +.region-picker-selected-label { flex: 0 0 auto; color: var(--text-muted); font-size: 10px; font-weight: 700; } +.region-picker-selected-placeholder { color: var(--text-muted); font-size: 11px; } +.region-picker-selected-chip { display: inline-flex; min-height: 25px; flex: 0 0 auto; align-items: center; gap: 6px; padding: 0 8px; border: 1px solid rgba(0,113,67,.2); border-radius: 99px; background: rgba(0,113,67,.09); color: var(--accent-primary); font-size: 10px; font-weight: 700; cursor: pointer; } +.region-picker-selected-chip b { font: 700 14px/1 -apple-system, BlinkMacSystemFont, sans-serif; } +.region-picker-search { display: flex; height: 37px; align-items: center; gap: 8px; margin: 12px 17px 9px; padding: 0 10px; border: 1px solid var(--glass-border); border-radius: 10px; background: var(--pill-bg); color: var(--text-muted); } +.region-picker-search span { font-size: 17px; line-height: 1; } +.region-picker-search input { width: 100%; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--text-main); font: 11px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } +.region-picker-search button { display: grid; width: 19px; height: 19px; place-items: center; border: 0; border-radius: 50%; background: var(--segmented-bg); color: var(--text-muted); font-size: 14px; cursor: pointer; } +.region-picker-search button[hidden] { display: none; } +.region-picker-path { display: flex; min-height: 28px; align-items: center; gap: 6px; padding: 0 17px 7px; color: var(--text-muted); font-size: 10px; } +.region-picker-path button { padding: 0; border: 0; background: transparent; color: var(--text-muted); font-size: inherit; cursor: pointer; } +.region-picker-path button.is-current { color: var(--text-main); font-weight: 700; cursor: default; } +.region-picker-path i { color: var(--text-sub); font-style: normal; } +.region-picker-list { min-height: 240px; flex: 1 1 auto; overflow-y: auto; padding: 2px 9px 10px; } +.region-picker-row { display: grid; grid-template-columns: 31px minmax(0, 1fr) 32px; min-height: 54px; align-items: center; border-bottom: 1px solid color-mix(in srgb, var(--glass-border) 72%, transparent); } +.region-picker-row.is-selected { background: rgba(0,113,67,.045); } +.region-picker-check { display: grid; width: 19px; height: 19px; margin-left: 6px; place-items: center; border: 1px solid var(--glass-border); border-radius: 5px; background: var(--pill-bg); color: #fff; font-size: 12px; cursor: pointer; } +.region-picker-row.is-selected .region-picker-check { border-color: var(--accent-primary); background: var(--accent-primary); } +.region-picker-row-main { min-width: 0; padding: 8px 3px; border: 0; background: transparent; color: var(--text-main); text-align: left; cursor: pointer; } +.region-picker-row-main strong { display: block; overflow: hidden; font-size: 12px; line-height: 1.3; text-overflow: ellipsis; white-space: nowrap; } +.region-picker-row-main small { display: block; overflow: hidden; margin-top: 2px; color: var(--text-muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; } +.region-picker-next { display: grid; width: 30px; height: 30px; place-items: center; border: 0; background: transparent; color: var(--text-muted); font: 400 22px/1 -apple-system, BlinkMacSystemFont, sans-serif; cursor: pointer; } +.region-picker-next:hover { color: var(--accent-primary); } +.region-picker-next.is-empty { visibility: hidden; } +.region-picker-empty { display: grid; min-height: 170px; place-items: center; color: var(--text-muted); font-size: 11px; } +.region-picker-footer { display: flex; min-height: 60px; align-items: center; justify-content: space-between; gap: 14px; padding: 10px 17px; border-top: 1px solid var(--glass-border); } +.region-picker-footer > span { color: var(--text-muted); font-size: 11px; } +.region-picker-footer > div { display: flex; align-items: center; gap: 8px; } +.region-picker-text-button, .region-picker-confirm { min-height: 32px; padding: 0 12px; border-radius: 8px; font-size: 11px; font-weight: 700; cursor: pointer; } +.region-picker-text-button { border: 1px solid var(--glass-border); background: var(--pill-bg); color: var(--text-muted); } +.region-picker-confirm { border: 1px solid var(--accent-primary); background: var(--accent-primary); color: #fff; } .station-navigation-shell .selected-location-filter-rail { display: flex; align-items: center; gap: 6px; min-width: 0; max-width: min(360px, 30vw); overflow-x: auto; scrollbar-width: none; } .station-navigation-shell .selected-location-filter-rail::-webkit-scrollbar { display: none; } .station-navigation-shell .selected-location-filter-rail[hidden] { display: none; } @@ -76,6 +121,9 @@ border-radius: 14px; } + .station-navigation-shell .region-picker-trigger { order: 2; min-height: 36px; padding: 0 10px; border-radius: 10px; } + .station-navigation-shell .explore-search-shell { order: 3; flex: 1 1 150px; } + .station-navigation-shell .locate-btn { flex-basis: 36px; width: 36px; @@ -89,10 +137,18 @@ } .station-navigation-shell .entity-search-field input { font-size: 12px; } - .station-navigation-shell .selected-location-filter-rail { order: 3; flex: 1 0 100%; max-width: none; padding-bottom: 1px; } - .station-navigation-shell .filter-chip-rail { order: 4; max-width: none; overflow: visible; } + .station-navigation-shell .selected-location-filter-rail { order: 4; flex: 1 0 100%; max-width: none; padding-bottom: 1px; } + .station-navigation-shell .filter-chip-rail { order: 5; max-width: none; overflow: visible; } .station-navigation-shell .map-filter-chip { min-height: 30px; padding: 0 10px; font-size: 10px; } - .station-navigation-shell .filter-result-meta { order: 5; padding-right: 5px; font-size: 9px; } + .station-navigation-shell .filter-result-meta { order: 6; padding-right: 5px; font-size: 9px; } + + .region-picker-backdrop { align-items: end; padding: 0; } + .region-picker-dialog { width: 100%; max-height: min(82dvh, 700px); border-radius: 20px 20px 0 0; } + .region-picker-header { padding: 14px 15px 10px; } + .region-picker-selected { padding: 8px 15px; } + .region-picker-search { margin: 10px 15px 7px; } + .region-picker-path { padding: 0 15px 6px; } + .region-picker-footer { padding: 10px 15px calc(10px + env(safe-area-inset-bottom)); } .station-navigation-shell .explore-suggestions { width: calc(100vw - 32px); diff --git a/station-navigation/tests/test_app.mjs b/station-navigation/tests/test_app.mjs index 1f9415ba..ca825082 100644 --- a/station-navigation/tests/test_app.mjs +++ b/station-navigation/tests/test_app.mjs @@ -41,12 +41,28 @@ vm.runInNewContext(` globalThis.cityNodeCount = buildStationNodes(filteredStations(), 'city').length; globalThis.stationNodeCount = buildStationNodes(filteredStations(), 'station').length; globalThis.locationOptionCount = stationLocationOptions().length; + globalThis.provinceOptionScope = stationLocationOptions().find(option => option.level === 'province' && option.province === '广东省'); + globalThis.cityOptionScope = stationLocationOptions().find(option => option.level === 'city' && option.city === '广州市'); `, sandbox); assert.equal(sandbox.hierarchy.join(','), 'province,city,station'); assert.equal(sandbox.provinceNodeCount, 2); assert.equal(sandbox.cityNodeCount, 3); assert.equal(sandbox.stationNodeCount, 3); assert.equal(sandbox.locationOptionCount, 8); +assert.equal(sandbox.provinceOptionScope.city, ''); +assert.equal(sandbox.provinceOptionScope.district, ''); +assert.equal(sandbox.cityOptionScope.district, ''); +vm.runInNewContext(` + state.regionPicker = { level: 'province', province: '', city: '', search: '', draft: [], options: [] }; + globalThis.regionProvinceLabels = regionPickerOptions().map(option => option.label).sort().join(','); + state.regionPicker = { level: 'city', province: '广东省', city: '', search: '', draft: [], options: [] }; + globalThis.regionCityLabels = regionPickerOptions().map(option => option.label).sort().join(','); + state.regionPicker = { level: 'province', province: '', city: '', search: '嘉兴', draft: [], options: [] }; + globalThis.regionSearchLabels = regionPickerOptions().map(option => option.label).join(','); +`, sandbox); +assert.equal(sandbox.regionProvinceLabels, '广东,浙江'); +assert.equal(sandbox.regionCityLabels, '佛山,广州'); +assert.match(sandbox.regionSearchLabels, /嘉兴/); vm.runInNewContext(` globalThis.inferredDistrict = stationDistrictName({ province: '浙江省', city: '嘉兴市', address: '嘉兴市海盐县海盐经济开发区' }); state.locationFilters = []; @@ -78,6 +94,7 @@ assert.match(source, /stationSearchSuggestions/); assert.match(source, /handleStationSearchInput/); assert.match(source, /locationFilters/); assert.match(source, /renderLocationFilterChips/); +assert.match(source, /openRegionPicker/); assert.match(source, /kpiTotal|kpiPartner/); const index = fs.readFileSync(new URL('../index.html', import.meta.url), 'utf8'); assert.match(index, /station-summary-strip/);