Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,20 @@ Static HTML webapp. No build tooling, no framework, no bundler. Dark-themed, ful

```
index.html shell — layout markup, links stylesheet + script
favicon.svg browser tab icon — inline SVG node-graph spelling "ag"
css/style.css global styles, layout, component styles
js/main.js all application logic
```

## Favicon

`favicon.svg` — `viewBox="0 0 33 50"`, default transparent background. Two node clusters:

- **'a & g' cluster** (hue 190, cyan): 5 nodes forming a diamond (top/left/bottom/right) + stem-bottom. Lines: open circle A-B-C-D + descending stem D-E. Cluster polygon: `hsla(190,60%,60%,0.10)` fill.
- **'g' cluster** (hue 30, orange): 3 nodes — 3-node descending hook (E-F-G-H). Cluster polygon: `hsla(30,60%,60%,0.10)` fill.

Nodes are simple filled circles to improve readability.

## Layout

CSS Grid, full viewport (`100vh`, `overflow: hidden`):
Expand Down Expand Up @@ -80,6 +90,15 @@ For each selection item (tag/genre) that appears in at least `clusterMin` connec

The `document` `mousemove` fallback returns early when cursor is over `.sidebar-left`, preventing the per-frame `clearHighlights()` from erasing stat-hover highlights.

## Graph search filter

`#graph-search` text input in topbar. On `input` event: if query non-empty, adds `.filter-active` to `#results` and `.filter-match` to each `.anime-circle` whose `mediaStore[idx]` title (romaji or english) includes the query (case-insensitive). CSS:

- `.filter-active .anime-circle` → `opacity: 0.15`
- `.filter-active .anime-circle.filter-match` → `opacity: 1`, `border-width: 7px`

On clear: removes `.filter-active` + all `.filter-match`. Cleared automatically on new Search. Independent of hover highlights — both can coexist.

## Media type

Three buttons in topbar center: **Anime**, **Manga**, **Novel**. Sets `mediaType` state (`'ANIME'` | `'MANGA'` | `'NOVEL'`). Default: `'ANIME'`.
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# anigraph
# <img src="favicon.svg" height="128" align="middle" alt="anigraph favicon" style="margin-right: 1rem;" /> anigraph

A little webapp for visualising information and statistics about anime.

<img width="1367" height="797" alt="image" src="https://github.com/user-attachments/assets/de7712c8-eaa7-4f5e-8a13-eefef23e458d" />
<img width="1367" height="797" alt="anigraph screenshot" src="https://github.com/user-attachments/assets/de7712c8-eaa7-4f5e-8a13-eefef23e458d" />

*A graph of shared tags among the top 100 popular anime on AniList with highlighted clusters*

Expand All @@ -17,6 +17,7 @@ Open `index.html` in a browser — no build step required.
Then:

1. Use the **media type buttons** in the topbar to select Anime, Manga, or Novel. All input modes and features work for each type.
Use the **filter bar** (topbar, between type buttons and AniList link) to highlight matching circles by title — non-matching entries dim while typing.
2. Use the **right sidebar** to import a `.txt` file (one title per line) and click **Search**.
3. Matching entries appear as **circles** in a live physics simulation — nodes repel each other, shared-selection connections act as springs.
4. **Drag** the center area to pan. **Scroll** over the center area to zoom.
Expand All @@ -32,6 +33,10 @@ Then:

*An image of the full interface of anigraph*

<img width="1372" height="867" alt="image" src="https://github.com/user-attachments/assets/801d91a4-6cf4-49ef-882b-4202e7c3a241" />

*Top 100 Anime being filtered for "Attack on Titan"*

Anime sharing selection items are connected by lines. Spring stiffness and line opacity scale with connection strength (relative to the strongest connection). Configure physics and selection in the right sidebar:

| Setting | Effect |
Expand All @@ -45,10 +50,15 @@ Anime sharing selection items are connected by lines. Spring stiffness and line

Unmatched titles are silently omitted. Feedback (errors, rate limits) appears below the search button.

## Favicon

`favicon.svg` — inline SVG depicting "a" and "g" overlaid as one node-graph.

## Project structure

```
index.html entry point
favicon.svg browser tab icon
css/style.css global styles
js/main.js application logic
```
37 changes: 37 additions & 0 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ body {
.site-title {
margin: 0;
font-size: 1.3rem;
display: flex;
align-items: center;
gap: 0.5rem;
}

.site-icon {
height: 50px;
width: auto;
}

.content-area {
Expand Down Expand Up @@ -133,6 +141,35 @@ body {
background: var(--bg);
}

.graph-search {
width: 180px;
background: transparent;
border: 1px solid #444;
border-radius: 4px;
color: #fff;
font-size: 0.85rem;
padding: 0.3rem 0.6rem;
outline: none;
}

.graph-search:focus {
border-color: #fff;
}

.graph-search::placeholder {
color: #555;
}

#results.filter-active .anime-circle {
opacity: 0.15;
transition: opacity 0.15s;
}

#results.filter-active .anime-circle.filter-match {
opacity: 1;
border-width: 7px;
}

.media-type-switch {
display: flex;
gap: 0.25rem;
Expand Down
33 changes: 33 additions & 0 deletions favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>anigraph</title>
<link rel="icon" href="favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<div class="layout">
<header class="topbar">
<h1 class="site-title">anigraph</h1>
<h1 class="site-title"><img src="favicon.svg" class="site-icon" alt="" />anigraph</h1>
<div class="media-type-switch">
<button class="mode-btn media-type-btn active" data-type="ANIME">Anime</button>
<button class="mode-btn media-type-btn" data-type="MANGA">Manga</button>
<button class="mode-btn media-type-btn" data-type="NOVEL">Novel</button>
</div>
<input id="graph-search" type="text" class="graph-search" placeholder="Filter results…" />
<a class="link-btn anilist-home" href="https://anilist.co" target="_blank" rel="noopener">AniList ↗</a>
</header>
<div class="content-area">
Expand Down
22 changes: 22 additions & 0 deletions js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,26 @@ document.getElementById('vis-labels').addEventListener('change', e => {
document.getElementById('labels-svg').style.display = e.target.checked ? '' : 'none';
});

document.getElementById('graph-search').addEventListener('input', e => {
const q = e.target.value.trim().toLowerCase();
const circleEls = document.querySelectorAll('.anime-circle');
if (!q) {
results.classList.remove('filter-active');
circleEls.forEach(el => el.classList.remove('filter-match'));
return;
}
results.classList.add('filter-active');
circleEls.forEach(el => {
const idx = parseInt(el.dataset.index);
const media = mediaStore[idx];
const match = media && (
(media.title.romaji ?? '').toLowerCase().includes(q) ||
(media.title.english ?? '').toLowerCase().includes(q)
);
el.classList.toggle('filter-match', !!match);
});
});

percentSlider.addEventListener('input', () => {
relevancePercent = parseInt(percentSlider.value);
percentVal.textContent = relevancePercent;
Expand Down Expand Up @@ -849,6 +869,8 @@ function displayResults(mediaArray) {
searchBtn.addEventListener('click', async () => {
stopSimulation();
results.innerHTML = '';
results.classList.remove('filter-active');
document.getElementById('graph-search').value = '';
document.getElementById('connections-svg').innerHTML = '';
document.getElementById('clusters-svg').innerHTML = '';
document.getElementById('labels-svg').innerHTML = '';
Expand Down
Loading