Files
docmd-io--docmd/packages/templates/summer/assets/js/summer.js
T
wehub-resource-sync 6db8fca185
docmd CI verification / verify (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:31:55 +08:00

702 lines
26 KiB
JavaScript

/* =========================================================================
@docmd/template-summer — summer.js
Runtime interactions for the Summer template. Vanilla JS, no deps.
What it does (the rest is handled by docmd-main.js, already loaded
by templates/layout.ejs — see packages/ui/assets/js/docmd-main.js for
SPA routing, theme toggle, sidebar drawer, version/project/language
switchers, code-block copy, page copy, banner, cookie consent and
search-trigger event delegation):
Everything is idempotent — per-page wires re-run after every
docmd:page-mounted event (fired by docmd-main.js on SPA nav).
========================================================================= */
(function () {
'use strict';
// -------- Utilities ----------------------------------------------------
function $(sel, root) { return (root || document).querySelector(sel); }
function $$(sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
function ready(fn) {
if (document.readyState !== 'loading') fn();
else document.addEventListener('DOMContentLoaded', fn);
}
function debounce(fn, wait) {
let timer;
return function () {
const args = arguments;
clearTimeout(timer);
timer = setTimeout(function () { fn.apply(null, args); }, wait);
};
}
// -------- TOC: SVG path track (Fumadocs-style) -----------------------
var _tocActiveIdx = -1;
var _tocScrollDir = 1; // 1 = down, -1 = up
var _tocLastScrollY = window.pageYOffset || 0;
function buildTocSvgTrack() {
var list = $('.summer-toc__list');
if (!list) return;
var items = $$('.summer-toc__item', list);
if (!items.length) return;
// === Tweakable layout ===
var BASE_X = 5; // x position of level-1 items (px from track left)
var INDENT = 7; // px indent per level step
// === Tweakable bend shape ===
// Cubic Bézier between consecutive items at different indent levels.
// BEND_HORIZ_MULT — scales with the horizontal |Δx| (typical Δx = INDENT).
// Higher = more curve per indent step.
// BEND_VERT_FRAC — caps the bend as a fraction of the actual vertical
// gap between item centres. 1.0 = max smooth S-curve,
// < 1.0 = subtler. Must stay ≤ 1.0 to avoid overshoot.
var BEND_HORIZ_MULT = 2.0;
var BEND_VERT_FRAC = 1.0;
// Measure each item's actual centre y from the DOM. The track is
// position:absolute at top:0 of the list, so list-relative coords match
// track-local coords and the path lines up with the items exactly.
var positions = items.map(function (li) {
var lvl = parseInt(li.dataset.level || li.className.match(/level-(\d)/)?.[1] || '1', 10);
return {
x: BASE_X + (lvl - 1) * INDENT,
cy: li.offsetTop + li.offsetHeight / 2
};
});
var xPositions = positions.map(function (p) { return p.x; });
var yCentres = positions.map(function (p) { return p.cy; });
// Track height = full list content (top of list to bottom of last item)
var totalH = items[items.length - 1].offsetTop + items[items.length - 1].offsetHeight;
// Build the SVG path d-string
var d = '';
for (var i = 0; i < positions.length; i++) {
var x = positions[i].x;
var y = yCentres[i];
if (i === 0) {
d += 'M ' + x + ' 0 L ' + x + ' ' + y;
} else {
var px = positions[i - 1].x;
var py = yCentres[i - 1];
if (px === x) {
// same level — straight line
d += ' L ' + x + ' ' + y;
} else {
// level change — cubic bezier bend, clamped so the curve never
// overshoots past the destination
var gap = y - py;
var bend = Math.min(gap * BEND_VERT_FRAC, Math.abs(x - px) * BEND_HORIZ_MULT);
d += ' C ' + px + ' ' + (py + bend) + ' ' + x + ' ' + (y - bend) + ' ' + x + ' ' + y;
}
}
}
// extend to bottom
var lastX = positions[positions.length - 1].x;
d += ' L ' + lastX + ' ' + totalH;
var svgW = BASE_X + (4 - 1) * INDENT + 4; // max possible width
// Create track container
var track = document.createElement('div');
track.className = 'summer-toc__track';
track.style.width = svgW + 'px';
track.style.height = totalH + 'px';
var NS = 'http://www.w3.org/2000/svg';
// Full (grey) path
var svgFull = document.createElementNS(NS, 'svg');
svgFull.setAttribute('class', 'summer-toc__track-full');
svgFull.setAttribute('width', svgW);
svgFull.setAttribute('height', totalH);
svgFull.setAttribute('viewBox', '0 0 ' + svgW + ' ' + totalH);
var pathFull = document.createElementNS(NS, 'path');
pathFull.setAttribute('d', d);
svgFull.appendChild(pathFull);
track.appendChild(svgFull);
// Active (accent) path — clipped
var svgActive = document.createElementNS(NS, 'svg');
svgActive.setAttribute('class', 'summer-toc__track-active');
svgActive.setAttribute('width', svgW);
svgActive.setAttribute('height', totalH);
svgActive.setAttribute('viewBox', '0 0 ' + svgW + ' ' + totalH);
svgActive.style.clipPath = 'polygon(0 0, ' + svgW + 'px 0, ' + svgW + 'px 0, 0 0)';
var pathActive = document.createElementNS(NS, 'path');
pathActive.setAttribute('d', d);
svgActive.appendChild(pathActive);
track.appendChild(svgActive);
list.insertBefore(track, list.firstChild);
return { track: track, svgActive: svgActive, xPositions: xPositions, yCentres: yCentres, d: d, totalH: totalH, svgW: svgW };
}
function wireTocScrollSpy() {
var tocLinks = $$('.summer-toc__link');
if (!tocLinks.length) return;
var headings = tocLinks
.map(function (link) {
var id = (link.getAttribute('href') || '').replace(/^#/, '');
if (!id) return null;
return { id: id, el: document.getElementById(id), link: link };
})
.filter(function (x) { return x && x.el; });
if (!headings.length) return;
var track = buildTocSvgTrack();
function setActive(idx) {
if (idx === _tocActiveIdx) return;
_tocActiveIdx = idx;
tocLinks.forEach(function (l) { l.classList.remove('active'); });
if (idx < 0 || idx >= tocLinks.length) return;
tocLinks[idx].classList.add('active');
if (!track) return;
var totalH = track.totalH;
var svgW = track.svgW;
// Fill from top down to the active item's centre Y.
// If the user has scrolled to the bottom of the page (footer visible),
// fill all the way to the bottom of the track so it doesn't appear cut off.
var docH = document.documentElement.scrollHeight;
var winH = window.innerHeight;
var atPageBottom = (window.pageYOffset + winH) >= (docH - 40);
var activeY = (atPageBottom || idx === tocLinks.length - 1)
? totalH
: track.yCentres[idx];
track.svgActive.style.clipPath =
'polygon(0 0, ' + svgW + 'px 0, ' +
svgW + 'px ' + activeY + 'px, 0 ' + activeY + 'px)';
}
// Scroll-spy using IntersectionObserver — each heading fills the TOC
// as soon as it enters the viewport (visible = active).
var TOPBAR_H = parseInt(
getComputedStyle(document.documentElement).getPropertyValue('--summer-topbar-height') || '64', 10
);
var SUBNAV_H = parseInt(
getComputedStyle(document.documentElement).getPropertyValue('--summer-subnav-height') || '44', 10
);
var rootMarginTop = -(TOPBAR_H + SUBNAV_H + 8) + 'px';
// Build a map from heading id → index
var idxMap = {};
headings.forEach(function (h, i) { idxMap[h.id] = i; });
// Track which headings are currently intersecting
var visibleSet = {};
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
visibleSet[entry.target.id] = true;
} else {
delete visibleSet[entry.target.id];
}
});
// Activate the last visible heading (lowest on screen = most advanced)
var bestIdx = -1;
headings.forEach(function (h, i) {
if (visibleSet[h.id] && i >= bestIdx) bestIdx = i;
});
// Fallback: if nothing visible, find last heading above viewport
if (bestIdx === -1) {
for (var i = headings.length - 1; i >= 0; i--) {
var rect = headings[i].el.getBoundingClientRect();
if (rect.bottom < TOPBAR_H + SUBNAV_H + 8) { bestIdx = i; break; }
}
}
if (bestIdx === -1 && headings.length) bestIdx = 0;
setActive(bestIdx);
}, {
rootMargin: rootMarginTop + ' 0px -10% 0px',
threshold: 0
});
headings.forEach(function (h) { observer.observe(h.el); });
// Initial state
updateActiveOnce();
function updateActiveOnce() {
var bestIdx = -1;
for (var i = headings.length - 1; i >= 0; i--) {
var rect = headings[i].el.getBoundingClientRect();
if (rect.top <= TOPBAR_H + SUBNAV_H + 80) { bestIdx = i; break; }
}
if (bestIdx === -1 && headings.length) bestIdx = 0;
setActive(bestIdx);
}
}
function wireTocSmoothScroll() {
$$('.summer-toc__link').forEach(function (link) {
link.addEventListener('click', function (e) {
var href = link.getAttribute('href') || '';
if (!href.startsWith('#')) return;
var target = document.getElementById(href.slice(1));
if (!target) return;
e.preventDefault();
var top = target.getBoundingClientRect().top + window.pageYOffset - 130;
window.scrollTo({ top: top, behavior: 'smooth' });
history.pushState(null, '', href);
});
});
}
// -------- Scroll to top button -----------------------------------------
function wireDrawer() {
// Hamburger toggle: the button lives in the topbar and carries
// data-summer-sidebar-toggle (it also has .sidebar-menu-button which
// the shared docmd-main.js handler picks up for the default
// template; here we just mirror the open state on <body>).
document.addEventListener('click', function (e) {
if (e.target.closest('[data-summer-sidebar-toggle]')) {
document.body.classList.toggle('summer-sidebar-open');
}
});
// Close button: the X inside the drawer (data-summer-sidebar-close).
document.addEventListener('click', function (e) {
if (e.target.closest('[data-summer-sidebar-close]')) {
document.body.classList.remove('summer-sidebar-open');
}
});
// Outside click: the backdrop is a body::after pseudo-element so it
// can't be a real click target. Close the drawer for any click that
// is outside the sidebar and not on the toggle button.
document.addEventListener('click', function (e) {
if (!document.body.classList.contains('summer-sidebar-open')) return;
if (e.target.closest('.summer-sidebar')) return;
if (e.target.closest('[data-summer-sidebar-toggle]')) return;
document.body.classList.remove('summer-sidebar-open');
});
// Escape key closes the drawer.
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && document.body.classList.contains('summer-sidebar-open')) {
document.body.classList.remove('summer-sidebar-open');
}
});
}
function wireScrollToTop() {
var btn = $('.summer-totop');
if (!btn) return;
var onScroll = debounce(function () {
var y = window.pageYOffset || document.documentElement.scrollTop;
btn.classList.toggle('is-visible', y > 480);
}, 50);
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
btn.addEventListener('click', function () {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}
// -------- Inline Header Search & Dropdown ----------------------------
function wireHeaderSearch() {
var headerInput = $('.summer-search-input');
if (!headerInput) return;
var dropdown = $('.summer-search-dropdown');
var resultsWrapper = $('.summer-search-results-wrapper');
var indexInitialized = false;
function initSearchIndex() {
if (indexInitialized) return;
indexInitialized = true;
// Use programmatic click WITHOUT triggering focus shifts that would
// scroll the page. We also restore our scroll position afterwards.
var scrollY = window.pageYOffset;
var trigger = $('.docmd-search-trigger, [data-docmd-search-trigger]');
if (trigger) {
trigger.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
} else {
var dummy = document.createElement('div');
dummy.className = 'docmd-search-trigger';
dummy.style.position = 'fixed';
dummy.style.top = '0';
dummy.style.left = '0';
dummy.style.width = '1px';
dummy.style.height = '1px';
dummy.style.opacity = '0';
dummy.style.pointerEvents = 'none';
document.body.appendChild(dummy);
dummy.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
// Don't remove — the plugin's listener may capture bubbles asynchronously
}
// Restore scroll & refocus our header input (NOT the plugin's input)
requestAnimationFrame(function () {
window.scrollTo(0, scrollY);
headerInput.focus({ preventScroll: true });
});
}
function tryInitPluginSearch() {
var searchModal = $('#docmd-search-modal');
var pluginInput = $('#docmd-search-input');
var pluginResults = $('#docmd-search-results');
if (!searchModal || !pluginInput || !pluginResults) {
setTimeout(tryInitPluginSearch, 100);
return;
}
if (resultsWrapper && pluginResults.parentNode !== resultsWrapper) {
resultsWrapper.appendChild(pluginResults);
}
// Force the modal off-screen (CSS already does this, but also set inline
// as a belt-and-braces measure so it can never auto-scroll into view).
searchModal.style.setProperty('position', 'fixed', 'important');
searchModal.style.setProperty('top', '-9999px', 'important');
searchModal.style.setProperty('left', '-9999px', 'important');
searchModal.style.setProperty('display', 'none', 'important');
searchModal.style.setProperty('opacity', '0', 'important');
searchModal.style.setProperty('visibility', 'hidden', 'important');
searchModal.style.setProperty('pointer-events', 'none', 'important');
// Make the plugin input also off-screen so it cannot grab focus
pluginInput.tabIndex = -1;
pluginInput.setAttribute('aria-hidden', 'true');
}
tryInitPluginSearch();
headerInput.addEventListener('focus', function () {
initSearchIndex();
dropdown.style.display = 'block';
});
headerInput.addEventListener('input', function () {
initSearchIndex();
var pluginInput = $('#docmd-search-input');
if (pluginInput) {
pluginInput.value = headerInput.value;
pluginInput.dispatchEvent(new Event('input', { bubbles: true }));
}
dropdown.style.display = 'block';
});
headerInput.addEventListener('keydown', function (e) {
var pluginInput = $('#docmd-search-input');
if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter') {
if (pluginInput) {
var clone = new KeyboardEvent('keydown', {
key: e.key,
code: e.code,
keyCode: e.keyCode,
bubbles: true,
cancelable: true
});
pluginInput.dispatchEvent(clone);
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
}
}
} else if (e.key === 'Escape') {
headerInput.value = '';
if (pluginInput) {
pluginInput.value = '';
pluginInput.dispatchEvent(new Event('input', { bubbles: true }));
}
headerInput.blur();
dropdown.style.display = 'none';
}
});
document.addEventListener('click', function (e) {
if (!e.target.closest('.summer-search-container')) {
dropdown.style.display = 'none';
}
});
document.addEventListener('keydown', function (e) {
var isK = e.key === 'k' || e.key === 'K';
if (isK && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
headerInput.focus();
}
if (e.key === '/' && !/^(input|textarea|select)$/i.test(e.target.tagName) && !e.target.isContentEditable) {
e.preventDefault();
headerInput.focus();
}
});
}
// -------- Git last-updated popover toggle (keyboard) ---------------
// Hover is handled in CSS; this adds click + keyboard support so the
// popover is reachable without a mouse.
function wireGitPopover() {
$$('.summer-pagefooter__time.has-commits').forEach(function (el) {
el.addEventListener('click', function (e) {
e.preventDefault();
var open = el.classList.toggle('open');
el.setAttribute('aria-expanded', open ? 'true' : 'false');
});
el.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
el.click();
} else if (e.key === 'Escape') {
el.classList.remove('open');
el.setAttribute('aria-expanded', 'false');
el.blur();
}
});
});
document.addEventListener('click', function (e) {
if (!e.target.closest('.summer-pagefooter__time.has-commits')) {
$$('.summer-pagefooter__time.has-commits.open').forEach(function (el) {
el.classList.remove('open');
el.setAttribute('aria-expanded', 'false');
});
}
});
}
// -------- Relative date rendering (lightweight) --------------------
// Renders any [data-timestamp] as a human-readable relative date.
// We avoid pulling in a date library for this tiny feature.
function formatRelative(ts) {
var now = Date.now();
var diff = Math.max(0, now - ts);
var sec = Math.floor(diff / 1000);
if (sec < 45) return 'just now';
var min = Math.floor(sec / 60);
if (min < 60) return min + ' min ago';
var hr = Math.floor(min / 60);
if (hr < 24) return hr + ' hr ago';
var day = Math.floor(hr / 24);
if (day < 7) return day + ' day' + (day === 1 ? '' : 's') + ' ago';
if (day < 30) {
var wk = Math.floor(day / 7);
return wk + ' week' + (wk === 1 ? '' : 's') + ' ago';
}
if (day < 365) {
var mo = Math.floor(day / 30);
return mo + ' month' + (mo === 1 ? '' : 's') + ' ago';
}
var yr = Math.floor(day / 365);
return yr + ' year' + (yr === 1 ? '' : 's') + ' ago';
}
function renderRelativeTimestamps() {
$$('[data-timestamp]').forEach(function (el) {
var raw = el.getAttribute('data-timestamp');
if (!raw) return;
var ts = parseInt(raw, 10);
if (!isFinite(ts) || ts <= 0) return;
// Use a child span if one exists (git popover meta), else replace
var target = el.querySelector('.git-time, .summer-git-popover__date') || el;
if (target !== el && target.children.length > 0) return;
if (!target.__renderedAt || (Date.now() - target.__renderedAt) > 60000) {
target.textContent = formatRelative(ts);
target.__renderedAt = Date.now();
}
});
}
// -------- Codeblocks -------------------------------------------------
// docmd-main.js wraps every <pre> in <div class="code-wrapper"> and
// appends a <button class="copy-code-button">. The parser also wraps
// ```lang "title"``` in <div class="docmd-code-block-wrapper"> with a
// header that holds the title. We turn the bottom-floating copy button
// into a thin title bar at the top of the codeblock.
//
// Left side:
// - icon (always)
// - filename (only when the source had a title, e.g. ```js "file.js"```)
// - lang pill (always — shows the language, or "codeblock" if none)
// Right side:
// - copy button
function summerCodeblocks() {
// 1. Codeblocks WITH a parser-rendered title wrapper.
// The header already exists with a <span class="docmd-code-block-title">.
// We add a lang pill next to it (or replace the header content
// with our own titlebar) and re-home the copy button.
//
// Copy button location depends on how docmd-main.js wires it:
// - Parser title + no .code-wrapper sibling → button is appended
// directly into .docmd-code-block-wrapper
// - Parser title + .code-wrapper → button is inside .code-wrapper
// We handle both.
$$('.docmd-code-block-wrapper').forEach(function (wrap) {
if (wrap.dataset.summerCbWired === '1') return;
wrap.dataset.summerCbWired = '1';
var header = wrap.querySelector('.docmd-code-block-header');
if (!header) return;
var inner = wrap.querySelector('.code-wrapper');
var copyBtn = wrap.querySelector('.copy-code-button');
// Read language from the <code class="language-xxx">
var lang = '';
var pre = inner ? inner.querySelector('pre') : wrap.querySelector('pre');
var code = pre ? pre.querySelector('code') : null;
if (code) {
var m = code.className.match(/language-([\w-]+)/);
if (m) lang = m[1];
}
// Clear header and rebuild as our titlebar.
// docmd-code-block-title already holds the filename (if set).
var titleEl = header.querySelector('.docmd-code-block-title');
var filename = titleEl ? titleEl.textContent : '';
// Strip everything in the header and rebuild
while (header.firstChild) header.removeChild(header.firstChild);
header.classList.add('summer-cb__titlebar');
// LEFT: icon + filename (if any) + lang pill
var left = document.createElement('div');
left.className = 'summer-cb__left';
left.appendChild(makeFileIcon());
if (filename) {
var fname = document.createElement('span');
fname.className = 'summer-cb__filename';
fname.textContent = filename;
left.appendChild(fname);
}
var pill = document.createElement('span');
pill.className = 'summer-cb__lang';
pill.textContent = lang || 'codeblock';
left.appendChild(pill);
header.appendChild(left);
// RIGHT: copy button (re-home from inner wrapper)
if (copyBtn) {
copyBtn.classList.add('summer-cb__copy');
header.appendChild(copyBtn);
}
});
// 2. Codeblocks WITHOUT a parser title — build our own titlebar.
$$('.code-wrapper').forEach(function (wrap) {
if (wrap.dataset.summerCbWired === '1') return;
// Skip ones already inside a parser wrapper (handled above).
if (wrap.closest('.docmd-code-block-wrapper')) {
wrap.dataset.summerCbWired = '1';
return;
}
wrap.dataset.summerCbWired = '1';
var pre = wrap.querySelector('pre');
var copyBtn = wrap.querySelector('.copy-code-button');
if (!pre) return;
// Read language from <code class="language-xxx">
var lang = '';
var code = pre.querySelector('code');
if (code) {
var m = code.className.match(/language-([\w-]+)/);
if (m) lang = m[1];
}
// Build header
var header = document.createElement('div');
header.className = 'summer-cb__titlebar';
// LEFT: icon + lang pill (filename omitted — no source title).
var left = document.createElement('div');
left.className = 'summer-cb__left';
left.appendChild(makeFileIcon());
var pill = document.createElement('span');
pill.className = 'summer-cb__lang';
pill.textContent = lang || 'codeblock';
left.appendChild(pill);
header.appendChild(left);
// RIGHT: copy button (re-home from wrapper bottom)
if (copyBtn) {
copyBtn.classList.add('summer-cb__copy');
wrap.removeChild(copyBtn);
header.appendChild(copyBtn);
}
// Insert header at the top of the wrapper
wrap.insertBefore(header, wrap.firstChild);
wrap.classList.add('summer-cb');
});
}
// Build a small file-icon SVG (shared by both codeblock paths above).
function makeFileIcon() {
var SVG_NS = 'http://www.w3.org/2000/svg';
var icon = document.createElementNS(SVG_NS, 'svg');
icon.setAttribute('viewBox', '0 0 24 24');
icon.setAttribute('fill', 'none');
icon.setAttribute('stroke', 'currentColor');
icon.setAttribute('stroke-width', '2');
icon.setAttribute('stroke-linecap', 'round');
icon.setAttribute('stroke-linejoin', 'round');
var path = document.createElementNS(SVG_NS, 'path');
path.setAttribute('d', 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z');
icon.appendChild(path);
var polyline = document.createElementNS(SVG_NS, 'polyline');
polyline.setAttribute('points', '14 2 14 8 20 8');
icon.appendChild(polyline);
return icon;
}
// -------- Init --------------------------------------------------------
// Re-runnable body of init logic. Idempotent: every wire is guarded
// with a data-attribute check so calling this twice is safe.
//
// Cross-cutting behaviour (theme toggle, sidebar drawer, version /
// project / language switchers, code-block copy, page copy, banner
// close, SPA router) is owned by packages/ui/assets/js/docmd-main.js,
// already loaded by templates/layout.ejs. What stays here is the
// summer-specific stuff: topbar search dropdown (re-homes the
// docmd-search.js modal into the topbar), TOC scroll-spy, git commit
// popover, scroll-to-top button, and relative-date rendering.
function summerInit() {
if (document.documentElement.dataset.summerWired !== '1') {
// First run: bind document-level listeners + topbar/footer wires
document.documentElement.dataset.summerWired = '1';
wireDrawer();
wireScrollToTop();
wireHeaderSearch();
}
// Per-page wires — always re-run after SPA nav (the page content
// was swapped). The header search is also re-attempted on every
// page so its polling can find a modal that didn't exist on the
// first page (e.g. when docmd-search.js loads lazily).
wireTocScrollSpy();
wireTocSmoothScroll();
summerCodeblocks();
wireGitPopover();
renderRelativeTimestamps();
}
ready(function () {
// Mark HTML as ready (reveal the page even if docmd core is slow to set data-theme)
document.documentElement.classList.add('summer-ready');
summerInit();
// Re-wire after SPA navigation (docmd core fires this on the document)
document.addEventListener('docmd:page-mounted', summerInit);
});
})();