| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Animal.js Documentation</title>
- <style>
- :root {
- --sidebar-bg: #111;
- --main-bg: #111;
- --border-color: #222;
- --text-color: #888;
- --text-hover: #ccc;
- --active-color: #ff4b4b; /* Red/Orange for active item */
- --header-color: #666;
- --middle-col-bg: #161616;
- --tree-line-color: #333;
- --accent-color: #ff9f43;
- }
-
- body {
- margin: 0;
- padding: 0;
- height: 100vh;
- display: flex;
- background: var(--main-bg);
- color: var(--text-color);
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
- overflow: hidden;
- font-size: 14px;
- }
- /* 1. LEFT SIDEBAR - MAIN NAVIGATION */
- .sidebar-left {
- width: 260px;
- background: var(--sidebar-bg);
-
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- flex-shrink: 0;
- padding-bottom: 40px;
- }
- .logo-area {
- padding: 24px 20px;
- font-size: 18px;
- font-weight: bold;
- color: #fff;
- display: flex;
- align-items: center;
- gap: 10px;
- margin-bottom: 10px;
- }
- .logo-circle {
- width: 10px;
- height: 10px;
- background: #fff;
- border-radius: 50%;
- }
- /* Tree Structure */
- .nav-tree {
- padding: 0 20px;
- }
- .tree-group {
- margin-bottom: 5px;
- }
- .tree-header {
- padding: 8px 0;
- color: var(--header-color);
- font-weight: 600;
- cursor: pointer;
- transition: color 0.2s;
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
-
- .tree-header:hover {
- color: var(--text-hover);
- }
-
- .tree-header.active-header {
- color: #e69f3c; /* Highlight parent if needed, usually just children */
- }
- .tree-children {
- padding-left: 14px; /* Indent */
- border-left: 1px solid var(--tree-line-color);
- margin-left: 2px;
- display: none; /* Collapsed by default */
- flex-direction: column;
- }
-
- .tree-children.expanded {
- display: flex;
- }
- .nav-item {
- display: block;
- padding: 6px 0 6px 15px;
- color: var(--text-color);
- text-decoration: none;
- cursor: pointer;
- transition: all 0.2s;
- position: relative;
- }
- .nav-item:hover {
- color: var(--text-hover);
- }
- /* Active Item Style (Reference: Red text, maybe red border left overlaying the gray line?) */
- .nav-item.active {
- color: var(--active-color);
- }
-
- /* Optional: Active marker on the left line */
- .nav-item.active::before {
- content: '';
- position: absolute;
- left: -1px; /* Overlap the border */
- top: 0;
- bottom: 0;
- width: 2px;
- background: var(--active-color);
- }
- /* Search Box in sidebar */
- .sidebar-search {
- margin: 0 20px 20px 20px;
- background: #1a1a1a;
- border: 1px solid #333;
- border-radius: 4px;
- padding: 8px 12px;
- color: #fff;
- font-size: 13px;
- cursor: pointer;
- }
- .sidebar-search:hover {
- border-color: #555;
- }
- /* 2. MIDDLE SIDEBAR */
- .sidebar-middle {
- width: 320px;
- background: var(--middle-col-bg);
-
- display: flex;
- flex-direction: column;
- overflow: hidden;
- flex-shrink: 0;
- }
- #middle-frame {
- width: 100%;
- height: 100%;
- border: none;
- background: transparent;
- }
- /* 3. RIGHT CONTENT */
- .content-right {
- flex: 1;
- display: flex;
- flex-direction: column;
- background: #000;
- overflow: hidden;
- }
- #content-frame {
- flex: 1;
- border: none;
- width: 100%;
- height: 100%;
- }
- /* Connection line between middle and right columns */
- .column-connection-line {
- position: fixed;
- inset: 0;
- width: 100vw;
- height: 100vh;
- pointer-events: none;
- z-index: 20;
- opacity: 0.85;
- }
- .column-connection-path {
- stroke: var(--accent-color);
- stroke-width: 2;
- stroke-dasharray: 6 6;
- stroke-linecap: round;
- fill: none;
- opacity: 0.35;
- animation: dashFlow 1.1s linear infinite;
- filter: drop-shadow(0 0 2px rgba(255, 159, 67, 0.25));
- }
- @keyframes dashFlow {
- to { stroke-dashoffset: -12; }
- }
- </style>
- </head>
- <body>
- <!-- 1. Left Sidebar -->
- <div class="sidebar-left">
- <div class="logo-area">
- <div class="logo-circle"></div>
- Animal.js
- </div>
-
- <div class="sidebar-search" onclick="selectCategory('search.html', 'test_on_update.html', null)">
- Search
- </div>
-
- <div class="nav-tree">
- <!-- Group: Getting Started -->
- <div class="tree-group">
- <div class="tree-header" onclick="toggleTree(this)">Getting started</div>
- <div class="tree-children">
- <div class="nav-item" onclick="selectCategory('search.html', 'test_css_selector.html', this)">Installation</div>
- <div class="nav-item" onclick="selectCategory('search.html', 'test_css_selector.html', this)">Module imports</div>
- </div>
- </div>
- <!-- Group: Timer -->
- <div class="tree-group">
- <div class="tree-header" onclick="toggleTree(this)">Timer</div>
- <div class="tree-children">
- <div class="nav-item">Settings</div>
- </div>
- </div>
- <!-- Group: Animation -->
- <div class="tree-group">
- <div class="tree-header active-header" onclick="toggleTree(this)">Animation</div>
- <div class="tree-children expanded">
- <div class="nav-item active" onclick="selectCategory('targets/list.html', 'targets/overview.html', this)">Targets</div>
- <div class="nav-item" onclick="selectCategory('animatable_properties/list.html', 'animatable_properties/test_css_props.html', this)">Animatable properties</div>
- <div class="nav-item" onclick="selectCategory('tween_value_types/list.html', 'tween_value_types/test_numerical.html', this)">Tween value types</div>
- <div class="nav-item">Tween parameters</div>
- <div class="nav-item">Keyframes</div>
- <div class="nav-item">Playback settings</div>
-
- <!-- Callbacks Nested? Or separate? Reference shows Callbacks under Animation -->
- <div class="nav-item" onclick="selectCategory('list_callbacks.html', 'test_on_update.html', this)">Callbacks</div>
-
- <!-- Examples -->
- <div class="nav-item" onclick="selectCategory('examples/list.html', 'examples/controls.html', this)">Examples</div>
-
- <div class="nav-item">Methods</div>
- <div class="nav-item">Properties</div>
- </div>
- </div>
-
- <!-- Group: Timeline -->
- <div class="tree-group">
- <div class="tree-header" onclick="toggleTree(this)">Timeline</div>
- <div class="tree-children">
- <div class="nav-item">Basics</div>
- </div>
- </div>
-
- <!-- Group: Animatable -->
- <div class="tree-group">
- <div class="tree-header" onclick="toggleTree(this)">Animatable</div>
- <div class="tree-children">
- <div class="nav-item">Properties</div>
- </div>
- </div>
- </div>
- </div>
- <!-- 2. Middle Sidebar -->
- <div class="sidebar-middle">
- <iframe id="middle-frame" src="targets/list.html"></iframe>
- </div>
- <!-- 3. Right Content -->
- <div class="content-right">
- <iframe id="content-frame" src="targets/test_css_selector.html"></iframe>
- </div>
- <!-- SVG overlay for the middle→right connection line -->
- <svg id="column-connection-line" class="column-connection-line" aria-hidden="true">
- <path id="column-connection-path" class="column-connection-path" d=""></path>
- </svg>
- <script>
- function toggleTree(header) {
- const children = header.nextElementSibling;
- if (children) {
- children.classList.toggle('expanded');
-
- // Optional: Highlight header when expanded?
- // header.classList.toggle('active-header');
- }
- }
- function selectCategory(listUrl, defaultContentUrl, el) {
- // 1. Highlight Nav Item
- document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));
- if (el) el.classList.add('active');
- // 2. Load Middle Column (List)
- const middleFrame = document.getElementById('middle-frame');
- if (middleFrame.getAttribute('src') !== listUrl) {
- middleFrame.src = listUrl;
- }
- // 3. Load Right Column (Default Content)
- loadContent(defaultContentUrl);
- }
- function loadContent(url) {
- const contentFrame = document.getElementById('content-frame');
- if (contentFrame.getAttribute('src') !== url) {
- contentFrame.src = url;
- }
- // Keep middle list selection in sync when possible
- try {
- setMiddleActive(url);
- } catch {}
- // Update connection line after navigation
- scheduleConnectionLineUpdate();
- }
- // Called by content iframe pages (and also internally after navigation)
- function setMiddleActive(contentUrl) {
- const middleFrame = document.getElementById('middle-frame');
- if (!middleFrame) return;
- const win = middleFrame.contentWindow;
- if (win && typeof win.setActiveByContentUrl === 'function') {
- win.setActiveByContentUrl(contentUrl);
- }
- // Update connection line when active item changes
- scheduleConnectionLineUpdate();
- }
- // =============================
- // Middle ↔ Right connection line
- // =============================
- const connectionSvg = document.getElementById('column-connection-line');
- const connectionPath = document.getElementById('column-connection-path');
- let _connRaf = 0;
- function scheduleConnectionLineUpdate() {
- if (_connRaf) return;
- _connRaf = requestAnimationFrame(() => {
- _connRaf = 0;
- updateConnectionLine();
- });
- }
- function getActiveElementInFrame(frameEl) {
- try {
- const doc = frameEl?.contentDocument;
- if (!doc) return null;
- // Middle list pages use .card.active / .header-card.active; fall back to .active
- return (
- doc.querySelector('.card.active') ||
- doc.querySelector('.header-card.active') ||
- doc.querySelector('.nav-item.active') ||
- doc.querySelector('.active')
- );
- } catch {
- return null;
- }
- }
- function getAnchorElementInContentFrame(frameEl) {
- try {
- const doc = frameEl?.contentDocument;
- if (!doc) return null;
- // Prefer a *visible* code area anchor (in-viewport), then fall back
- const candidates = [
- '.box-container .box-header',
- '.code-view.active, .html-view.active, .css-view.active',
- '.box-container',
- 'h1',
- '.container',
- 'body'
- ];
- for (const sel of candidates) {
- const el = doc.querySelector(sel);
- if (!el) continue;
- // If it's visible in the iframe viewport, use it; else keep looking
- const r = el.getBoundingClientRect();
- const vh = frameEl?.clientHeight || 0;
- if (!vh) return el;
- const visibleTop = Math.max(0, r.top);
- const visibleBottom = Math.min(vh, r.bottom);
- if (visibleBottom - visibleTop > 8) return el;
- }
- return doc.body;
- } catch {
- return null;
- }
- }
- function clamp(n, min, max) {
- return Math.min(Math.max(n, min), max);
- }
- function getVisibleCenterInFrame(frameEl, elRect) {
- const w = frameEl?.clientWidth || 0;
- const h = frameEl?.clientHeight || 0;
- if (!w || !h) return null;
- const visibleLeft = Math.max(0, elRect.left);
- const visibleRight = Math.min(w, elRect.right);
- const visibleTop = Math.max(0, elRect.top);
- const visibleBottom = Math.min(h, elRect.bottom);
- if (visibleRight - visibleLeft <= 1 || visibleBottom - visibleTop <= 1) {
- return null;
- }
- return {
- x: (visibleLeft + visibleRight) / 2,
- y: (visibleTop + visibleBottom) / 2
- };
- }
- function updateConnectionLine() {
- if (!connectionSvg || !connectionPath) return;
- const middleFrame = document.getElementById('middle-frame');
- const contentFrame = document.getElementById('content-frame');
- if (!middleFrame || !contentFrame) return;
- const midRect = middleFrame.getBoundingClientRect();
- const rightRect = contentFrame.getBoundingClientRect();
- const active = getActiveElementInFrame(middleFrame);
- const anchor = getAnchorElementInContentFrame(contentFrame);
- if (!active || !anchor) {
- connectionPath.setAttribute('d', '');
- connectionSvg.style.opacity = '0';
- return;
- }
- const activeRect = active.getBoundingClientRect();
- const anchorRect = anchor.getBoundingClientRect();
- // Use the *visible* part of the element within its iframe viewport
- const activeCenter = getVisibleCenterInFrame(middleFrame, activeRect);
- const anchorCenter = getVisibleCenterInFrame(contentFrame, anchorRect);
- if (!activeCenter || !anchorCenter) {
- // If either side isn't visible in its iframe, hide the line (prevents weird drifting)
- connectionPath.setAttribute('d', '');
- connectionSvg.style.opacity = '0';
- return;
- }
- // Convert iframe-viewport coordinates to parent viewport coordinates
- const startX = midRect.left + clamp(activeRect.right, 0, middleFrame.clientWidth);
- const startY = midRect.top + activeCenter.y;
- // Point into the code area a bit (not exactly at left edge)
- const endX = rightRect.left + clamp(anchorRect.left + 18, 12, contentFrame.clientWidth - 12);
- const endY = rightRect.top + anchorCenter.y;
- // Draw an L-shaped polyline.
- // Keep the vertical segment aligned with the column divider (middle ↔ right).
- const dividerX = rightRect.left + 0.5; // half-pixel for crisper stroke alignment
- const x1 = Math.max(startX + 12, dividerX);
- const d = [
- `M ${startX} ${startY}`,
- `L ${x1} ${startY}`,
- `L ${x1} ${endY}`,
- `L ${endX} ${endY}`
- ].join(' ');
- connectionPath.setAttribute('d', d);
- connectionSvg.style.opacity = '0.85';
- }
- function attachFrameScrollListeners(frameEl) {
- if (!frameEl) return;
- const onScroll = () => {
- markFrameScrolling(frameEl);
- scheduleConnectionLineUpdate();
- };
- try {
- // Window scroll inside iframe
- frameEl.contentWindow?.addEventListener('scroll', onScroll, { passive: true });
- // Some pages scroll on document/body; capture to be safe
- frameEl.contentDocument?.addEventListener('scroll', onScroll, true);
- frameEl.contentDocument?.documentElement?.addEventListener?.('scroll', onScroll, { passive: true });
- frameEl.contentDocument?.body?.addEventListener?.('scroll', onScroll, { passive: true });
- } catch {}
- }
- const _scrollHideTimers = new WeakMap();
- function markFrameScrolling(frameEl) {
- try {
- const doc = frameEl?.contentDocument;
- if (!doc) return;
- doc.documentElement?.classList.add('scrolling');
- doc.body?.classList.add('scrolling');
- const prev = _scrollHideTimers.get(frameEl);
- if (prev) clearTimeout(prev);
- _scrollHideTimers.set(frameEl, setTimeout(() => {
- try {
- doc.documentElement?.classList.remove('scrolling');
- doc.body?.classList.remove('scrolling');
- } catch {}
- }, 500));
- } catch {}
- }
- // Always-on lightweight loop to keep the line aligned even
- // if iframe scroll events were missed due to timing.
- let _connLoopStarted = false;
- function startConnectionLineLoop() {
- if (_connLoopStarted) return;
- _connLoopStarted = true;
- const loop = () => {
- // Only bother when we have a path (avoids work when hidden)
- if (connectionPath?.getAttribute('d')) {
- scheduleConnectionLineUpdate();
- }
- requestAnimationFrame(loop);
- };
- requestAnimationFrame(loop);
- }
- // Make initial active state robust against iframe load ordering
- window.addEventListener('load', () => {
- const contentFrame = document.getElementById('content-frame');
- const src = contentFrame?.getAttribute('src');
- if (src) setMiddleActive(src);
- const middleFrame = document.getElementById('middle-frame');
- if (middleFrame) {
- middleFrame.addEventListener('load', () => {
- const current = document.getElementById('content-frame')?.getAttribute('src');
- if (current) setMiddleActive(current);
- attachFrameScrollListeners(middleFrame);
- scheduleConnectionLineUpdate();
- });
- }
- if (contentFrame) {
- contentFrame.addEventListener('load', () => {
- const current = contentFrame.getAttribute('src');
- if (current) setMiddleActive(current);
- attachFrameScrollListeners(contentFrame);
- scheduleConnectionLineUpdate();
- });
- }
- // If the iframes were already loaded before we attached 'load' listeners,
- // still attach scroll listeners immediately.
- attachFrameScrollListeners(middleFrame);
- attachFrameScrollListeners(contentFrame);
- window.addEventListener('resize', scheduleConnectionLineUpdate, { passive: true });
- scheduleConnectionLineUpdate();
- startConnectionLineLoop();
- });
- </script>
- </body>
- </html>
|