feat: implement evolution and gradient systems with settings modal

- Add EvolutionController to manage spawning and physics of icons.
- Introduce GradientController for animated gradient backgrounds.
- Create iconLoader for fetching and caching SVG icons.
- Implement modal functionality for settings adjustments.
- Connect UI controls to controllers via SettingsController.
- Add CSS styles for gradient animations, icons, and modal.
- Create utility functions for color manipulation in gradients.
This commit is contained in:
sha
2026-02-21 13:53:46 +02:00
commit 899d9d7c13
59 changed files with 1765 additions and 0 deletions
+230
View File
@@ -0,0 +1,230 @@
# devpage — Agent Context File
This file is a compact reference for AI agents working on this project.
Read it before making changes. It covers purpose, architecture, patterns, and current state.
---
## What this project is
A personal developer page — a single-page app with an animated gradient background
and a floating icon "evolution" system. Icons spawn at random intervals, drift
around the screen with chaotic physics, and bounce off viewport edges.
A settings modal lets the user tune the gradient colour, animation speed, icon
movement speed, and gradient rotation.
**It is intentionally a growing project.** The base page and evolution system are
done; future work will extend icon variety, add icon interactions, and build out
the main content area.
---
## Tech constraints
- Vanilla JS — no framework, no bundler, no build step.
- Pure ES modules (`type="module"` in HTML). All imports use relative paths.
- Requires a local HTTP server — `fetch()` calls fail on `file://`.
Run with `npx serve .` or `python3 -m http.server` then open `localhost`.
- Browser target: modern evergreen (CSS `@property`, `backdrop-filter`, `scale`
as standalone property, private class fields all required).
---
## File map
```
index.html ← HTML shell + all CSS links + module entry point
src/
js/
main.js ← Entry point. Instantiates all controllers, boots them.
constants.js ← Single source of truth for all numeric/flag defaults.
gradient.js ← GradientController. Drives CSS vars on <html>.
modal.js ← ModalController. Open/close, focus trap, Escape dismiss.
settings.js ← SettingsController. UI → controller bridge (no own state).
evolution.js ← EvolutionController. Spawn scheduler + rAF physics loop.
entity.js ← Entity class. One floating icon: physics + DOM lifecycle.
iconLoader.js ← fetch() + Map cache for SVGs. normalizeSvg() strips attrs.
utils/
colorUtils.js ← hexToHSL, hslToHex, deriveGradientPair.
styles/
main.css ← Base reset, typography (DM Mono), settings button.
gradient.css ← @property --gradient-angle, keyframes, body animation.
modal.css ← Glass morphism overlay, controls, toggle switch.
icons.css ← .evolution-container, .icon-entity, appear transition.
data/
icons.json ← Icon registry: groups, per-icon metadata, type colours, spawn config.
icons/
*.svg ← 34 SVG icons (Solar icon set, 24×24 viewBox, currentColor).
```
---
## Architecture
### Controller pattern
Each domain has one class. No shared mutable globals.
```
main.js
├── GradientController gradient.js state: color, speed, rotating
├── ModalController modal.js state: open/closed, trigger ref
├── SettingsController settings.js no state — reads DOM, forwards to others
└── EvolutionController evolution.js state: entities[], moveSpeed, timers
└── Entity[] entity.js state: x, y, vx, vy, alive, DOM refs
```
### Data flow
```
User interaction (slider/picker/toggle)
→ settings.js listener
→ gradient.setColor() / gradient.setSpeed() / evolution.setMoveSpeed()
→ CSS custom property on <html> OR evolution speed multiplier updated live
```
### CSS custom properties (set on `<html>` by GradientController)
```
--color-1 first gradient colour (derived from user's base colour)
--color-2 second gradient colour (hue-shifted companion)
--anim-duration animation duration in seconds (maps speed 1-10 → ~32s-3.5s)
--gradient-angle registered @property <angle>, animated by gradientRotate keyframe
```
---
## Key patterns and why
### Two-div entity structure
Each entity uses an outer div for JS position (`transform: translate()`) and an
inner div for the CSS appear animation (`scale` standalone property). Separating
them prevents JS transform writes from interrupting the CSS transition.
```html
<div class="icon-entity"> ← JS sets transform: translate(x, y) every frame
<div class="icon-entity__body"> ← CSS transition: scale 0→1 on [data-state="alive"]
<svg></svg>
</div>
</div>
```
### Appear animation trigger
`bodyEl.dataset.state = 'spawning'` is set synchronously before mount.
`dataset.state = 'alive'` is set two rAF ticks later. This guarantees the
browser has painted the initial `scale: 0` state before the transition fires.
### Rotation animation without restart
Both `gradientFlow` and `gradientRotate` animations are always declared on `<body>`.
Rotation starts with `animation-play-state: paused`. Toggling `.gradient-rotating`
class switches it to `running`. This avoids restarting `gradientFlow` on toggle.
### Edge bounce
`Math.abs()` trick ensures correct direction regardless of penetration depth:
```js
if (x - h <= 0) { x = h; vx = Math.abs(vx); } // left wall → go right
if (x + h >= vw) { x = vw - h; vx = -Math.abs(vx); } // right wall → go left
```
### Colour derivation
`deriveGradientPair(hex)` in `colorUtils.js` produces a two-colour gradient from
one base colour using HSL math. Colour 1: same hue, deeper (sat ×1.15, light ×0.62).
Colour 2: hue +35°, lighter (sat ×0.88, light ×1.48, max 78).
### SVG loading
`iconLoader.js` fetches each SVG once via `fetch()` and caches in a `Map`.
`normalizeSvg()` strips: XML declaration, HTML comments, `width=` attr, `height=` attr.
This lets CSS control icon size. All icons use `fill="currentColor"` (except
`display-line-duotone.svg` which uses `stroke="currentColor"`).
### icons.json loaded at runtime
`import … assert { type: 'json' }` has inconsistent browser support without a bundler.
`EvolutionController` loads `icons.json` via `fetch()` inside `#loadIconsData()`.
---
## Defaults (source of truth: `src/js/constants.js`)
```js
GRADIENT_COLOR: '#4d22b3' // deep purple
GRADIENT_SPEED: 2 // slider value 1-10
GRADIENT_ROTATION: false
SPAWN_DELAY_MIN: 3_000 // ms before first/next icon spawns
SPAWN_DELAY_MAX: 20_000 // ms
ICON_SIZE: 24 // px
ICON_HALF: 12 // px, used in edge bounce math
MOVE_SPEED: 5 // slider default; multiplier = sliderValue / MOVE_SPEED
BASE_SPEED: 0.8 // px/frame at multiplier 1.0
MAX_SPEED_FACTOR: 2.5 // max speed = BASE_SPEED × MAX_SPEED_FACTOR
APPEAR_DURATION: 600 // ms, scale 0→1 CSS transition
DRIFT_CHANCE: 0.02 // probability per frame of a velocity kick
DRIFT_MAGNITUDE: 0.25 // max |Δv| per kick
```
---
## Icon inventory
34 SVG files in `src/icons/`. 9 are registered in `src/data/icons.json`.
The other 25 are present on disk but not yet wired into the evolution system.
### Registered in icons.json
| filename | label | type | group |
|----------------------------------|-------------|---------|----------|
| dna-bold-duotone.svg | DNA | good | biology |
| bug-bold-duotone.svg | Bug | bad | tech |
| buildings-3-bold-duotone.svg | Buildings | good | economy |
| chat-round-dots-bold-duotone.svg | Chat | neutral | social |
| chat-round-money-bold-duotone.svg| Chat Money | neutral | economy |
| chat-round-unread-bold-duotone.svg| Unread Chat| neutral | social |
| database-bold-duotone.svg | Database | good | tech |
| delivery-bold-duotone.svg | Delivery | good | economy |
| display-line-duotone.svg | Display | neutral | tech |
**Note:** `display-line-duotone.svg` uses `stroke` not `fill`. Tinting via `color`
CSS property works, but the visual style differs from the fill-based icons.
### On disk, not yet in icons.json (25 files)
android-old, angular, app-store, apple-brand, claude, code-1, docker, donut-bold-duotone,
face-scan-square-bold-duotone, filters-bold-duotone, gamepad-bold-duotone, git, github,
go, javascript, kubernetes, laravel, mysql, nodejs, open-ai, php, postgresql, python,
signal-app, vs-code
To register any of these: add an entry to `icons.icons` in `icons.json` following the
existing schema `{ "label": "…", "type": "good|bad|neutral", "group": "…" }`.
Create a new group in `icons.groups` if needed.
---
## Current spawn behaviour
Only `dna-bold-duotone` spawns. The icon is set in `icons.json → spawn.initial`.
`EvolutionController.#spawnEntity()` always uses `spawn.initial` — it does not yet
pick from the full icon registry. This is intentional: the spawn logic is the next
thing to extend.
---
## Settings modal controls
| Element ID | Type | Default | Wired to |
|--------------------|---------|---------|-------------------------------------|
| `colorPicker` | color | #4d22b3 | `gradient.setColor(hex)` |
| `speedSlider` | range 1-10 | 2 | `gradient.setSpeed(n)` |
| `moveSpeedSlider` | range 1-10 | 5 | `evolution.setMoveSpeed(n)` |
| `rotationToggle` | checkbox| false | `gradient.toggleRotation(bool)` |
---
## Known gaps / next steps
1. **Spawn variety**`#spawnEntity` always spawns `spawn.initial`. Extend to pick
randomly from `icons.icons` (or weighted by type) to get diverse entities.
2. **icons.json completeness** — 25 icons on disk are unregistered. Add them to
unlock their use in spawning.
3. **Icon interactions** — no collision detection between entities yet.
4. **Main content area**`<main class="page-main">` is empty. Reserved for content.
5. **Entity cap** — no maximum entity count; they accumulate indefinitely.
6. **Persistence** — settings reset on page reload. No localStorage yet.
+103
View File
@@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="devpage" />
<title>devpage</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500&display=swap" rel="stylesheet" />
<!-- Styles (order matters: base → gradient → modal → icons) -->
<link rel="stylesheet" href="src/styles/main.css" />
<link rel="stylesheet" href="src/styles/gradient.css" />
<link rel="stylesheet" href="src/styles/modal.css" />
<link rel="stylesheet" href="src/styles/icons.css" />
</head>
<body>
<!-- ── Evolution stage (icon entities mount here) ──────── -->
<div class="evolution-container" id="evolutionContainer" aria-hidden="true"></div>
<!-- ── Settings trigger ─────────────────────────────────── -->
<button class="settings-btn" id="settingsBtn" aria-label="Open settings" aria-haspopup="dialog">
#
</button>
<!-- ── Main content area (grows with future features) ───── -->
<main class="page-main" id="pageMain"></main>
<!-- ── Settings modal ──────────────────────────────────── -->
<div
class="modal-overlay"
id="modalOverlay"
role="dialog"
aria-modal="true"
aria-labelledby="modalTitle"
aria-hidden="true"
>
<div class="modal">
<div class="modal-header">
<span class="modal-title" id="modalTitle">settings</span>
<button class="modal-close" id="modalClose" aria-label="Close settings">&times;</button>
</div>
<div class="modal-body">
<!-- Background color -->
<div class="setting-group">
<label class="setting-label" for="colorPicker">background color</label>
<div class="color-input-wrapper">
<input type="color" id="colorPicker" value="#4d22b3" class="color-picker" />
<span class="color-value" id="colorValue">#4d22b3</span>
</div>
</div>
<!-- Gradient animation speed -->
<div class="setting-group">
<label class="setting-label" for="speedSlider">animation speed</label>
<div class="slider-wrapper">
<span class="slider-label">slow</span>
<input type="range" id="speedSlider" min="1" max="10" value="2" class="slider" />
<span class="slider-label">fast</span>
</div>
</div>
<!-- Icon movement speed -->
<div class="setting-group">
<label class="setting-label" for="moveSpeedSlider">icon movement speed</label>
<div class="slider-wrapper">
<span class="slider-label">slow</span>
<input type="range" id="moveSpeedSlider" min="1" max="10" value="5" class="slider" />
<span class="slider-label">fast</span>
</div>
</div>
<!-- Gradient rotation -->
<div class="setting-group">
<span class="setting-label">gradient rotation</span>
<div class="toggle-wrapper">
<span class="toggle-label">off</span>
<label class="toggle" for="rotationToggle" aria-label="Toggle gradient rotation">
<input type="checkbox" id="rotationToggle" />
<span class="toggle-track">
<span class="toggle-thumb"></span>
</span>
</label>
<span class="toggle-label">on</span>
</div>
</div>
</div>
</div>
</div>
<!-- ── App entry point (ES module) ─────────────────────── -->
<script type="module" src="src/js/main.js"></script>
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
{
"_comment": "Icon registry for the evolution system. Each icon has a type (good/bad/neutral) and belongs to a group. Types define the visual colour tint. Spawn config controls initial entity creation.",
"groups": [
{ "id": "biology", "label": "Biology", "icons": ["dna-bold-duotone"] },
{ "id": "tech", "label": "Technology", "icons": ["database-bold-duotone", "display-line-duotone", "bug-bold-duotone"] },
{ "id": "social", "label": "Social", "icons": ["chat-round-dots-bold-duotone", "chat-round-unread-bold-duotone"] },
{ "id": "economy", "label": "Economy", "icons": ["chat-round-money-bold-duotone", "delivery-bold-duotone", "buildings-3-bold-duotone"] }
],
"icons": {
"dna-bold-duotone": { "label": "DNA", "type": "good", "group": "biology" },
"bug-bold-duotone": { "label": "Bug", "type": "bad", "group": "tech" },
"buildings-3-bold-duotone": { "label": "Buildings", "type": "good", "group": "economy" },
"chat-round-dots-bold-duotone": { "label": "Chat", "type": "neutral", "group": "social" },
"chat-round-money-bold-duotone": { "label": "Chat Money", "type": "neutral", "group": "economy" },
"chat-round-unread-bold-duotone": { "label": "Unread Chat", "type": "neutral", "group": "social" },
"database-bold-duotone": { "label": "Database", "type": "good", "group": "tech" },
"delivery-bold-duotone": { "label": "Delivery", "type": "good", "group": "economy" },
"display-line-duotone": { "label": "Display", "type": "neutral", "group": "tech" }
},
"types": {
"good": { "label": "Beneficial", "color": "#a8ffb8" },
"bad": { "label": "Harmful", "color": "#ffb0a8" },
"neutral": { "label": "Neutral", "color": "#c4d8ff" }
},
"spawn": {
"initial": "dna-bold-duotone"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M4.776 8.478q-.517 0-.877.36q-.36.362-.36.866v5.168q0 .517.36.878q.36.36.877.36q.516 0 .872-.36q.354-.36.354-.878V9.704q0-.505-.36-.865a1.18 1.18 0 0 0-.866-.36m9.952-4.64l.853-1.573q.085-.158-.06-.24q-.156-.074-.24.071l-.865 1.587A5.9 5.9 0 0 0 12 3.178q-1.275 0-2.416.505l-.865-1.587q-.086-.144-.24-.072q-.146.085-.06.24l.853 1.575a5.27 5.27 0 0 0-2.068 1.845a4.66 4.66 0 0 0-.769 2.59h11.118q0-1.405-.77-2.59a5.3 5.3 0 0 0-2.055-1.845m-4.934 2.29a.45.45 0 0 1-.33.14a.43.43 0 0 1-.325-.14a.46.46 0 0 1-.132-.33q0-.192.132-.33a.43.43 0 0 1 .324-.138q.193 0 .331.138a.45.45 0 0 1 .138.33a.45.45 0 0 1-.138.33m5.066 0a.43.43 0 0 1-.324.14a.45.45 0 0 1-.331-.14a.45.45 0 0 1-.138-.33a.45.45 0 0 1 .138-.33a.45.45 0 0 1 .33-.138q.193 0 .325.138a.46.46 0 0 1 .132.33a.46.46 0 0 1-.132.33M6.483 16.712q0 .555.384.938q.385.384.938.384h.89l.011 2.729q0 .517.36.878q.362.36.866.36q.517 0 .877-.36q.36-.361.361-.878v-2.729h1.659v2.729q0 .517.36.878q.36.36.877.36t.878-.36t.36-.878v-2.729h.902q.54 0 .925-.384t.385-.937V8.706H6.483zm12.74-8.233q-.505 0-.865.355q-.36.354-.36.871v5.168q0 .517.36.878q.36.36.865.36q.517 0 .878-.36q.36-.36.36-.878V9.704q0-.516-.36-.871a1.2 1.2 0 0 0-.878-.355"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M10.306 12.548h3.42l-1.742-4.096zM11.984 2L2.726 5.323l1.451 12.322L11.984 22l7.87-4.355l1.42-12.322zm5.806 15.226h-2.193l-1.13-2.903H9.532l-1.194 2.903h-2.16l5.806-13z"/></svg>

After

Width:  |  Height:  |  Size: 289 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m12 6.548l.355-.645a.816.816 0 0 1 1.129-.322c.42.225.548.742.322 1.129l-3.548 6.097h2.549c.806 0 1.258.967.967 1.645H6.258a.8.8 0 0 1-.806-.807c0-.451.354-.806.806-.806h2.097l2.677-4.71l-.87-1.452c-.227-.419-.098-.903.322-1.129s.903-.096 1.129.323zm-3.194 8.807l-.774 1.355a.816.816 0 0 1-1.129.322a.816.816 0 0 1-.322-1.129l.58-1.032q1.065-.34 1.645.484m6.84-2.484h2.128c.452 0 .807.355.807.806a.8.8 0 0 1-.807.807h-1.193l.806 1.42c.226.419.097.902-.323 1.128c-.419.226-.903.097-1.129-.322c-1.354-2.355-2.354-4.065-3.032-5.258c-.677-1.194-.193-2.355.258-2.775c.58.904 1.387 2.323 2.484 4.194M12 2C6.452 2 2 6.452 2 12s4.452 10 10 10s10-4.452 10-10S17.548 2 12 2m8.742 10c0 4.774-3.871 8.742-8.742 8.742c-4.774 0-8.742-3.871-8.742-8.742c0-4.774 3.871-8.742 8.742-8.742c4.774 0 8.742 3.871 8.742 8.742"/></svg>

After

Width:  |  Height:  |  Size: 922 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M19.741 8.819c-.117.09-2.198 1.244-2.198 3.81c0 2.968 2.647 4.019 2.727 4.045c-.013.064-.421 1.438-1.396 2.838c-.87 1.232-1.778 2.462-3.159 2.462s-1.737-.79-3.332-.79c-1.554 0-2.106.816-3.37.816c-1.263 0-2.145-1.14-3.159-2.54c-1.174-1.644-2.123-4.199-2.123-6.623c0-3.888 2.568-5.95 5.095-5.95c1.343 0 2.462.868 3.306.868c.802 0 2.053-.92 3.58-.92c.58 0 2.66.051 4.03 1.984m-4.753-3.63c.632-.739 1.078-1.763 1.078-2.787A2 2 0 0 0 16.029 2c-1.028.038-2.251.674-2.988 1.516c-.58.648-1.12 1.672-1.12 2.71c0 .156.027.312.039.362c.065.012.17.026.276.026c.922 0 2.082-.608 2.753-1.426"/></svg>

After

Width:  |  Height:  |  Size: 698 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M10.5 10a3.5 3.5 0 1 0 0 7a3.5 3.5 0 1 0 0-7"/><path fill="currentColor" d="M17.73 2.05c-1.13-.16-2.24.05-3.22.61a26.2 26.2 0 0 1-6.33 2.66c-3.17.89-5.55 3.55-6.07 6.76c-.45 2.76.42 5.47 2.38 7.42a8.42 8.42 0 0 0 6 2.49c.47 0 .94-.04 1.42-.12c3.22-.52 5.87-2.9 6.76-6.06l.03-.11c.56-2.09 1.45-4.18 2.63-6.23c.56-.97.77-2.08.61-3.22a4.985 4.985 0 0 0-4.21-4.21Zm1.87 6.43c-1.27 2.2-2.22 4.46-2.85 6.79c-.67 2.39-2.75 4.25-5.16 4.64c-2.12.34-4.19-.32-5.69-1.82s-2.16-3.57-1.81-5.69c.39-2.41 2.26-4.49 4.72-5.18c2.25-.61 4.51-1.56 6.71-2.83c.45-.26.96-.4 1.48-.4q.225 0 .45.03c1.28.18 2.33 1.24 2.52 2.52c.1.68-.03 1.35-.36 1.93Z"/></svg>

After

Width:  |  Height:  |  Size: 747 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m19.59 5.82l1.12-1.12l-1.41-1.41l-1.12 1.12c-.64-.43-1.38-.71-2.18-.83V2h-2v1.65c-.73.17-1.4.5-1.98.95l-1.06-1.06l-1.41 1.41l1.14 1.14c-.43.72-.69 1.55-.73 2.44L8.71 7.28L7.3 8.69l1.25 1.25c-.89.04-1.72.3-2.44.73L4.97 9.53l-1.41 1.41L4.62 12c-.45.58-.78 1.25-.95 1.98H2.02v2H3.6c.12.79.4 1.54.83 2.18l-1.12 1.12l1.41 1.41l1.12-1.12c.64.43 1.38.71 2.18.83v1.58h2v-1.56c1.01-.1 1.97-.35 2.88-.69l.76 1.27l1.71-1.03l-.66-1.1c.78-.46 1.5-1 2.15-1.62l.94.94l1.41-1.41l-1.03-1.03c.46-.61.85-1.27 1.18-1.96l1.19.6l.89-1.79l-1.38-.69c.17-.62.31-1.26.37-1.91h1.56V8h-1.58a5.35 5.35 0 0 0-.83-2.18ZM8.79 18.5c-1.81 0-3.29-1.47-3.29-3.29s1.47-3.29 3.29-3.29s3.14-1.41 3.14-3.14s1.47-3.29 3.29-3.29s3.29 1.47 3.29 3.29c0 5.36-4.36 9.71-9.71 9.71Z"/><path fill="currentColor" d="M9 13.5a1.5 1.5 0 1 0 0 3a1.5 1.5 0 1 0 0-3m4-1.5a1 1 0 1 0 0 2a1 1 0 1 0 0-2m-.5 3a.5.5 0 1 0 0 1a.5.5 0 1 0 0-1M15 7.5a1.5 1.5 0 1 0 0 3a1.5 1.5 0 1 0 0-3m.5 4.5a.5.5 0 1 0 0 1a.5.5 0 1 0 0-1"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M6.154 4.065c-1.141 0-1.986.995-1.948 2.074c.037 1.037-.01 2.38-.35 3.474c-.34 1.098-.916 1.794-1.856 1.883v1.008c.94.09 1.516.785 1.856 1.883c.34 1.095.387 2.437.35 3.474c-.038 1.078.807 2.074 1.948 2.074h11.693c1.142 0 1.986-.995 1.948-2.074c-.036-1.037.01-2.38.35-3.474c.34-1.098.915-1.794 1.855-1.883v-1.008c-.94-.09-1.514-.785-1.855-1.883c-.34-1.095-.386-2.437-.35-3.474c.038-1.079-.806-2.074-1.948-2.074zm9.405 9.769c0 1.486-1.112 2.387-2.958 2.387H9.458a.34.34 0 0 1-.34-.338V8.117a.337.337 0 0 1 .34-.337h3.125c1.54 0 2.55.83 2.55 2.105c0 .895-.68 1.697-1.546 1.837v.047c1.179.129 1.972.942 1.972 2.065M12.258 8.85h-1.792v2.521h1.51c1.166 0 1.81-.468 1.81-1.304c0-.784-.554-1.217-1.528-1.217m-1.792 3.521v2.779h1.858c1.215 0 1.858-.486 1.858-1.398c0-.913-.662-1.38-1.936-1.38z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 945 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M19 11.938V15a7 7 0 0 1-6.25 6.96V15a.75.75 0 0 0-1.5 0v6.96A7 7 0 0 1 5 15v-3.062A3.94 3.94 0 0 1 8.938 8h6.124A3.94 3.94 0 0 1 19 11.938" clip-rule="evenodd" opacity="0.5"/><path fill="currentColor" d="M19 14.75v-1.5h3a.75.75 0 0 1 0 1.5zm-1.504 4.586c.31-.393.58-.82.801-1.276l2.538 1.27a.75.75 0 1 1-.67 1.34zM5.703 18.06q.333.684.801 1.276l-2.669 1.335a.75.75 0 0 1-.67-1.342zM5 13.25H2a.75.75 0 0 0 0 1.5h3zm12.354-4.515l2.81-1.406a.75.75 0 1 1 .671 1.341L18.42 9.88a4 4 0 0 0-1.065-1.144M6.647 8.735c-.427.306-.79.695-1.067 1.144L3.165 8.67a.75.75 0 0 1 .67-1.341zM16.5 8.27V7.5a4.5 4.5 0 1 0-9 0v.77A3.9 3.9 0 0 1 8.938 8h6.124c.508 0 .993.096 1.438.27"/><path fill="currentColor" d="M6.376 1.584a.75.75 0 0 0 .208 1.04l2.36 1.573a4.5 4.5 0 0 1 1.387-.877L7.416 1.376a.75.75 0 0 0-1.04.208m8.68 2.613a4.5 4.5 0 0 0-1.387-.877l2.915-1.944a.75.75 0 1 1 .832 1.248z" opacity="0.5"/><path fill="currentColor" fill-rule="evenodd" d="M12 14.25a.75.75 0 0 1 .75.75v7h-1.5v-7a.75.75 0 0 1 .75-.75" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M10.75 2h2c1.886 0 2.828 0 3.414.586S16.75 4.114 16.75 6v15.25h5a.75.75 0 0 1 0 1.5h-20a.75.75 0 0 1 0-1.5h5V6c0-1.886 0-2.828.586-3.414S8.864 2 10.75 2M9 12a.75.75 0 0 1 .75-.75h4a.75.75 0 0 1 0 1.5h-4A.75.75 0 0 1 9 12m0 3a.75.75 0 0 1 .75-.75h4a.75.75 0 0 1 0 1.5h-4A.75.75 0 0 1 9 15m2.75 3.25a.75.75 0 0 1 .75.75v2.25H11V19a.75.75 0 0 1 .75-.75M9.25 7a2.75 2.75 0 1 1 5.5 0a2.75 2.75 0 0 1-5.5 0" clip-rule="evenodd"/><path fill="currentColor" d="M10.75 7a1.25 1.25 0 1 1 2.5 0a1.25 1.25 0 0 1-2.5 0" opacity="0.5"/><path fill="currentColor" d="M20.913 5.889c.337.504.337 1.206.337 2.611v12.75h.5a.75.75 0 0 1 0 1.5h-20a.75.75 0 1 1 0-1.5h.5V8.5c0-1.405 0-2.107.337-2.611a2 2 0 0 1 .552-.552c.441-.295 2.537-.332 3.618-.336q-.005.437-.004.91V7.25H4.25a.75.75 0 1 0 0 1.5h2.503v1.5H4.25a.75.75 0 0 0 0 1.5h2.503v1.5H4.25a.75.75 0 0 0 0 1.5h2.503v6.5h10v-6.5h2.497a.75.75 0 1 0 0-1.5h-2.497v-1.5h2.497a.75.75 0 1 0 0-1.5h-2.497v-1.5h2.497a.75.75 0 0 0 0-1.5h-2.497V5.91q.001-.471-.004-.91c1.081.005 3.17.042 3.612.337a2 2 0 0 1 .552.552" opacity="0.5"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 23c6.075 0 11-4.925 11-11S18.075 1 12 1S1 5.925 1 12c0 1.76.413 3.423 1.148 4.898c.195.392.26.84.147 1.263l-.655 2.448a1.43 1.43 0 0 0 1.75 1.751l2.45-.655a1.8 1.8 0 0 1 1.262.147A10.96 10.96 0 0 0 12 23" opacity="0.5"/><path fill="currentColor" d="M10.9 12a1.1 1.1 0 1 0 2.2 0a1.1 1.1 0 0 0-2.2 0m-4.4 0a1.1 1.1 0 1 0 2.2 0a1.1 1.1 0 0 0-2.2 0m8.8 0a1.1 1.1 0 1 0 2.2 0a1.1 1.1 0 0 0-2.2 0"/></svg>

After

Width:  |  Height:  |  Size: 515 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2S2 6.477 2 12c0 1.6.376 3.112 1.043 4.453c.178.356.237.763.134 1.148l-.595 2.226a1.3 1.3 0 0 0 1.591 1.592l2.226-.596a1.63 1.63 0 0 1 1.149.133A9.96 9.96 0 0 0 12 22" opacity="0.5"/><path fill="currentColor" d="M12.75 8a.75.75 0 0 0-1.5 0v.01c-1.089.275-2 1.133-2 2.323c0 1.457 1.365 2.417 2.75 2.417c.824 0 1.25.533 1.25.917s-.426.916-1.25.916s-1.25-.532-1.25-.916a.75.75 0 0 0-1.5 0c0 1.19.911 2.049 2 2.323V16a.75.75 0 0 0 1.5 0v-.01c1.089-.274 2-1.133 2-2.323c0-1.457-1.365-2.417-2.75-2.417c-.824 0-1.25-.533-1.25-.917s.426-.916 1.25-.916s1.25.532 1.25.916a.75.75 0 0 0 1.5 0c0-1.19-.911-2.048-2-2.323z"/></svg>

After

Width:  |  Height:  |  Size: 770 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M22 5a3 3 0 1 1-6 0a3 3 0 0 1 6 0"/><path fill="currentColor" d="M15.235 2.535A10 10 0 0 0 12 2C6.477 2 2 6.477 2 12c0 1.6.376 3.112 1.043 4.453c.178.356.237.763.134 1.148l-.595 2.226a1.3 1.3 0 0 0 1.591 1.592l2.226-.596a1.63 1.63 0 0 1 1.149.133A9.96 9.96 0 0 0 12 22c5.523 0 10-4.477 10-10c0-1.132-.188-2.22-.535-3.235a4.5 4.5 0 0 1-6.23-6.23" opacity="0.5"/></svg>

After

Width:  |  Height:  |  Size: 479 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="25" viewBox="0 0 24 25"><path fill="currentColor" d="m5.929 16.218l3.931-2.206l.066-.192l-.066-.106h-.192l-.658-.04l-2.246-.061l-1.948-.082l-1.887-.1l-.476-.102l-.445-.587l.045-.293l.4-.268l.572.05l1.265.086l1.897.132l1.376.08l2.039.213h.324l.045-.131l-.11-.081l-.087-.081l-1.963-1.33l-2.125-1.407l-1.113-.81l-.602-.41l-.304-.384l-.131-.84l.546-.602l.734.05l.187.051l.744.572l1.588 1.23l2.075 1.527l.303.253l.122-.086l.015-.06l-.137-.228l-1.128-2.04l-1.204-2.074l-.536-.86l-.142-.516a2.5 2.5 0 0 1-.086-.607l.622-.845l.344-.111l.83.111l.35.304l.515 1.179l.835 1.856l1.295 2.525l.38.749l.202.693l.076.213h.131v-.122l.107-1.422l.197-1.745l.192-2.247l.066-.632l.314-.759l.622-.41l.486.233l.4.572l-.056.37l-.238 1.542l-.465 2.419l-.304 1.619h.177l.203-.203l.82-1.087l1.375-1.72l.608-.684l.708-.754l.455-.359h.86l.633.941l-.284.972l-.885 1.123l-.734.951l-1.052 1.417l-.658 1.133l.06.091l.158-.015l2.378-.506l1.285-.233l1.533-.263l.693.324l.076.329l-.273.673l-1.64.405l-1.922.384l-2.864.678l-.035.025l.04.05l1.29.122l.552.03h1.35l2.515.188l.658.435l.395.531l-.066.405l-1.012.516l-1.366-.324l-3.187-.759l-1.093-.273h-.152v.091l.91.89l1.67 1.508l2.09 1.943l.106.48l-.268.38l-.284-.04l-1.836-1.381l-.708-.623l-1.604-1.35h-.107v.141l.37.541l1.953 2.935l.101.9l-.142.294l-.506.177l-.556-.101l-1.144-1.604l-1.179-1.806l-.95-1.62l-.117.066l-.562 6.047l-.263.308l-.607.233l-.506-.385l-.268-.622l.268-1.23l.324-1.603l.263-1.275l.238-1.584l.142-.526l-.01-.035l-.117.015l-1.194 1.639l-1.816 2.454l-1.437 1.538l-.344.137l-.597-.31l.055-.55l.334-.491l1.989-2.53l1.199-1.569l.774-.905l-.005-.132h-.046l-5.282 3.43l-.94.122l-.406-.38l.051-.622l.192-.202l1.589-1.093z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="24" viewBox="0 0 25 24"><path fill="currentColor" d="M15.058 4.163a.75.75 0 1 0-1.464-.326l-3.556 16a.75.75 0 1 0 1.465.326zM7.83 7.47a.75.75 0 0 1 0 1.06L4.36 12l3.47 3.47a.75.75 0 1 1-1.061 1.06l-4-4a.75.75 0 0 1 0-1.06l4-4a.75.75 0 0 1 1.06 0m9.44 0a.75.75 0 0 0 0 1.06l3.47 3.47l-3.47 3.47a.75.75 0 1 0 1.06 1.06l4-4a.75.75 0 0 0 0-1.06l-4-4a.75.75 0 0 0-1.06 0"/></svg>

After

Width:  |  Height:  |  Size: 433 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M3.232 2.24c0 .134.339 4.05.757 8.705c.41 4.647.765 8.59.774 8.759l.027.302l3.605.997L11.99 22l.72-.196l3.588-.988c2.456-.676 2.875-.819 2.91-.97c.045-.214 1.558-17.365 1.558-17.65V2H3.232zm14.304 3.783c-.036.214-.267 2.742-.516 5.634c-.258 2.884-.472 5.27-.49 5.287c-.036.045-4.406 1.255-4.53 1.255c-.09 0-2.475-.65-4.104-1.112l-.454-.134l-.106-1.175a76 76 0 0 0-.16-1.744l-.054-.552h1.113c1.255 0 1.22-.018 1.228.757c0 .195.027.498.053.676l.054.32l1.22.32l1.21.321l1.201-.32c.659-.178 1.21-.33 1.21-.33c.01-.008.063-.534.117-1.166c.053-.64.115-1.263.142-1.406l.044-.24H7.078l-.045-.205c-.027-.107-.08-.623-.116-1.157l-.062-.952h8.02l.053-.606c.036-.329.089-.836.116-1.121c.035-.365.026-.507-.045-.463c-.053.027-1.949.027-4.228-.009l-4.138-.053l-.054-.632c-.035-.347-.08-.846-.107-1.104L6.42 5.65h11.16z"/></svg>

After

Width:  |  Height:  |  Size: 925 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 10c4.418 0 8-1.79 8-4s-3.582-4-8-4s-8 1.79-8 4s3.582 4 8 4"/><path fill="currentColor" d="M4 12v6c0 2.21 3.582 4 8 4s8-1.79 8-4v-6c0 2.21-3.582 4-8 4s-8-1.79-8-4" opacity="0.5"/><path fill="currentColor" d="M4 6v6c0 2.21 3.582 4 8 4s8-1.79 8-4V6c0 2.21-3.582 4-8 4S4 8.21 4 6" opacity="0.7"/></svg>

After

Width:  |  Height:  |  Size: 414 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M2.277 5.247a.75.75 0 0 1 .924-.522l1.703.472A2.71 2.71 0 0 1 6.8 7.075l2.151 7.786l.158.547a2.96 2.96 0 0 1 1.522 1.267l.31-.096l8.87-2.305a.75.75 0 1 1 .378 1.452l-8.837 2.296l-.33.102c-.006 1.27-.883 2.432-2.21 2.776c-1.59.414-3.225-.502-3.651-2.044s.518-3.129 2.108-3.542q.119-.03.237-.052L5.354 7.474a1.21 1.21 0 0 0-.85-.831L2.8 6.17a.75.75 0 0 1-.523-.923"/><path fill="currentColor" d="m9.564 8.73l.515 1.863c.485 1.755.727 2.633 1.44 3.032c.713.4 1.618.164 3.428-.306l1.92-.5c1.81-.47 2.715-.705 3.127-1.396c.412-.692.17-1.57-.316-3.325l-.514-1.862c-.485-1.756-.728-2.634-1.44-3.033c-.714-.4-1.619-.164-3.429.307l-1.92.498c-1.81.47-2.715.706-3.126 1.398c-.412.691-.17 1.569.315 3.324" opacity="0.5"/></svg>

After

Width:  |  Height:  |  Size: 827 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 9c0-2.828 0-4.243.879-5.121C3.757 3 5.172 3 8 3h8c2.828 0 4.243 0 5.121.879C22 4.757 22 6.172 22 9v1c0 2.828 0 4.243-.879 5.121C20.243 16 18.828 16 16 16H8c-2.828 0-4.243 0-5.121-.879C2 14.243 2 12.828 2 10z"/><path stroke-linecap="round" d="M12 19v-2.5m0 2.5l6 2m-6-2l-6 2" opacity="0.5"/></g></svg>

After

Width:  |  Height:  |  Size: 451 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15.03 1.47a.75.75 0 0 1 0 1.06c-2.558 2.558-3.523 6.113-2.591 9.031c2.918.932 6.473-.033 9.03-2.591a.75.75 0 1 1 1.061 1.06c-2.622 2.623-6.264 3.854-9.556 3.213c.75 3.175-.4 6.744-2.944 9.287a.75.75 0 1 1-1.06-1.06c2.416-2.417 3.3-5.788 2.36-8.516l-.072-.212l-.212-.072c-2.727-.94-6.099-.056-8.516 2.36a.75.75 0 0 1-1.06-1.06c2.543-2.544 6.112-3.693 9.287-2.944c-.64-3.292.59-6.934 3.213-9.556a.75.75 0 0 1 1.06 0" clip-rule="evenodd"/><path fill="currentColor" d="M20.085 10.136L16.809 6.86a.75.75 0 0 0-1.061 1.061l3.008 3.008q.684-.34 1.329-.793M13.108 5.17l.948.947a.75.75 0 1 0 1.06-1.06L13.91 3.85q-.456.639-.802 1.32m-2.211 13.554L9.835 17.66a.75.75 0 0 0-1.06 1.06l1.353 1.354a9.4 9.4 0 0 0 .77-1.351m-7.041-4.805l3.113 3.113a.75.75 0 0 0 1.06-1.061L5.2 13.139a9.4 9.4 0 0 0-1.343.78m2.644-1.279l4.812 4.812q.022.023.048.044a8.2 8.2 0 0 0 .322-1.795L8.3 12.317a8.2 8.2 0 0 0-1.799.323m11.049-1.209a1 1 0 0 0-.097-.118l-4.878-4.878a9 9 0 0 0-.407 1.714l3.683 3.683a9 9 0 0 0 1.698-.4" opacity="0.5"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12.988 11.321h-2.035V9.448h2.035zm0-6.363h-2.035v1.906h2.035zm2.455 4.554h-2.035v1.842h2.035zM10.566 7.22H8.53v1.873h2.034zm2.422 0h-2.035v1.873h2.035zm8.689 3.133c-.452-.323-1.486-.42-2.261-.258c-.097-.775-.55-1.421-1.26-2.003l-.452-.258l-.258.452c-.55.872-.743 2.326-.13 3.262a3.4 3.4 0 0 1-1.485.356H2.07c-.259 1.582.193 3.682 1.356 5.103c1.13 1.357 2.907 2.035 5.168 2.035c4.91 0 8.592-2.26 10.272-6.395c.646 0 2.132 0 2.875-1.422c.032-.032.226-.42.258-.549zm-15.989-.84H3.621v1.842h2.035V9.512zm2.423 0H6.076v1.842H8.11zm2.454 0H8.532v1.842h2.034zM8.111 7.22H6.076v1.873H8.11z"/></svg>

After

Width:  |  Height:  |  Size: 703 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2C6.477 2 2 6.477 2 12q0 .531.054 1.048C2.404 13.352 4.367 15 6 15c1.212 0 2.606-.908 3.387-1.5l.01-.009a3 3 0 1 1 4.61.739c.47.412 1.084.77 1.798.77c1.69 0 1.69-2 3.38-2c1.077 0 1.925.814 2.399 1.403l.092.132c.211-.81.324-1.659.324-2.535c0-5.523-4.477-10-10-10" opacity="0.5"/><path fill="currentColor" d="M9.388 13.5C8.607 14.092 7.212 15 6 15c-1.633 0-3.596-1.648-3.945-1.952C2.579 18.078 6.832 22 12 22c4.647 0 8.554-3.17 9.676-7.465l-.092-.132c-.473-.59-1.322-1.403-2.4-1.403c-1.689 0-1.689 2-3.378 2c-.714 0-1.328-.357-1.798-.77a3 3 0 0 1-4.61-.739zm10.14-8.083l-.058.053l-1 1a.75.75 0 1 0 1.06 1.06l.905-.904q-.409-.64-.907-1.209M5.417 4.472q.025.03.053.058l1 1a.75.75 0 0 0 1.06-1.06l-.904-.905q-.64.41-1.209.907m5.053.058a.75.75 0 1 1 1.06-1.06l1 1a.75.75 0 1 1-1.06 1.06zm6.13.92a.75.75 0 1 0-1.2-.9l-1.5 2a.75.75 0 0 0 1.2.9zM8.41 7.56a.75.75 0 0 0 .918.53l1.366-.366a.75.75 0 1 0-.388-1.448l-1.366.366a.75.75 0 0 0-.53.918m9.056 2.794a.75.75 0 1 1-1.499.07l-.066-1.412a.75.75 0 0 1 1.498-.07zm.971 1.705a.75.75 0 0 0 1.059.067l1.678-1.478a.75.75 0 1 0-.992-1.126L18.504 11a.75.75 0 0 0-.067 1.059M5.525 8.167a.75.75 0 1 1 1.365-.62l.585 1.286a.75.75 0 1 1-1.365.621z"/><path fill="currentColor" d="M6.943 10.895a.75.75 0 0 1 .162 1.048l-.835 1.141a.75.75 0 1 1-1.21-.886l.835-1.14a.75.75 0 0 1 1.048-.163M2.856 8.98a.75.75 0 0 1 1.497-.084l.079 1.412a.75.75 0 0 1-1.498.083z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M21.302 13.163c.386 0 .698.312.698.697v.053c0 1.71 0 3.064-.142 4.123c-.147 1.09-.456 1.974-1.152 2.67s-1.58 1.005-2.67 1.152c-1.06.142-2.414.142-4.123.142h-.053a.698.698 0 0 1 0-1.395c1.774 0 3.034-.002 3.99-.13c.936-.126 1.476-.362 1.87-.756c.393-.393.629-.933.755-1.869c.128-.956.13-2.216.13-3.99c0-.385.312-.697.697-.697m-18.604 0c.385 0 .697.312.697.697c0 1.774.002 3.034.13 3.99c.126.936.362 1.476.756 1.87c.394.393.933.629 1.869.755c.956.128 2.216.13 3.99.13a.698.698 0 1 1 0 1.395h-.053c-1.71 0-3.064 0-4.123-.142c-1.09-.147-1.974-.456-2.67-1.152s-1.005-1.58-1.152-2.67C2 16.976 2 15.622 2 13.913v-.053c0-.385.312-.697.698-.697M10.087 2h.053a.698.698 0 1 1 0 1.395c-1.774 0-3.034.002-3.99.13c-.936.126-1.475.362-1.87.756c-.393.394-.629.933-.755 1.869c-.128.956-.13 2.216-.13 3.99a.698.698 0 0 1-1.395 0v-.053c0-1.71 0-3.064.142-4.123c.147-1.09.456-1.974 1.152-2.67s1.58-1.005 2.67-1.152C7.024 2 8.378 2 10.087 2m7.763 1.525c-.956-.128-2.216-.13-3.99-.13a.698.698 0 0 1 0-1.395h.053c1.71 0 3.064 0 4.123.142c1.09.147 1.974.456 2.67 1.152s1.005 1.58 1.152 2.67C22 7.024 22 8.378 22 10.087v.053a.698.698 0 1 1-1.395 0c0-1.774-.002-3.034-.13-3.99c-.126-.936-.362-1.475-.756-1.87c-.393-.393-.933-.629-1.869-.755" clip-rule="evenodd"/><path fill="currentColor" d="M10.373 10.43c0 .675-.364 1.222-.814 1.222s-.814-.547-.814-1.221s.365-1.221.814-1.221c.45 0 .814.547.814 1.22m4.884 0c0 .675-.365 1.222-.814 1.222c-.45 0-.814-.547-.814-1.221s.364-1.221.814-1.221s.814.547.814 1.22m-5.334 3.987a.61.61 0 0 0-.727.981a4.7 4.7 0 0 0 2.805.934a4.7 4.7 0 0 0 2.805-.934a.61.61 0 1 0-.727-.98a3.47 3.47 0 0 1-2.078.693c-.77 0-1.486-.254-2.078-.694"/><g fill="currentColor" opacity="0.5"><path d="M10.373 10.43c0 .675-.365 1.222-.814 1.222c-.45 0-.814-.547-.814-1.221c0-.675.364-1.221.814-1.221s.814.546.814 1.22m4.883 0c0 .675-.364 1.222-.814 1.222s-.814-.547-.814-1.221c0-.675.365-1.221.814-1.221c.45 0 .814.546.814 1.22m-5.334 3.987a.61.61 0 0 0-.727.98c.792.588 1.76.935 2.806.935a4.7 4.7 0 0 0 2.805-.934a.61.61 0 1 0-.727-.981a3.47 3.47 0 0 1-2.078.694c-.77 0-1.486-.255-2.079-.694"/><path fill-rule="evenodd" d="M10.14 4.559h3.72c2.632 0 3.948 0 4.765.817s.817 2.133.817 4.764v3.72c0 2.632 0 3.948-.817 4.765s-2.133.817-4.764.817H10.14c-2.631 0-3.947 0-4.764-.817s-.817-2.133-.817-4.764V10.14c0-2.631 0-3.947.817-4.764s2.133-.817 4.764-.817m-1.072 9.985a.61.61 0 0 1 .854-.127c.593.44 1.308.694 2.079.694c.77 0 1.485-.255 2.078-.694a.61.61 0 1 1 .727.98a4.7 4.7 0 0 1-2.805.935a4.7 4.7 0 0 1-2.806-.934a.61.61 0 0 1-.127-.854m5.374-2.892c.45 0 .814-.547.814-1.221c0-.675-.364-1.221-.814-1.221s-.814.546-.814 1.22c0 .675.365 1.222.814 1.222m-4.883 0c.45 0 .814-.547.814-1.221c0-.675-.365-1.221-.814-1.221c-.45 0-.814.546-.814 1.22c0 .675.364 1.222.814 1.222" clip-rule="evenodd"/></g></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M18 8A6 6 0 1 1 6 8a6 6 0 0 1 12 0"/><path fill="currentColor" d="M13.58 13.79a6 6 0 0 1-7.16-3.58a6 6 0 1 0 7.16 3.58" opacity="0.7"/><path fill="currentColor" d="M13.58 13.79c.271.684.42 1.43.42 2.21a6 6 0 0 1-2 4.472a6 6 0 1 0 5.58-10.262a6.01 6.01 0 0 1-4 3.58" opacity="0.4"/></svg>

After

Width:  |  Height:  |  Size: 399 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m10.667 6.134l-.502-.355A4.24 4.24 0 0 0 7.715 5h-.612c-.405 0-.813.025-1.194.16c-2.383.846-4.022 3.935-3.903 10.943c.024 1.412.354 2.972 1.628 3.581A3.2 3.2 0 0 0 5.027 20a2.74 2.74 0 0 0 1.53-.437c.41-.268.77-.616 1.13-.964c.444-.43.888-.86 1.424-1.138a4.1 4.1 0 0 1 1.89-.461H13c.658 0 1.306.158 1.89.46c.536.279.98.709 1.425 1.139c.36.348.72.696 1.128.964c.39.256.895.437 1.531.437a3.2 3.2 0 0 0 1.393-.316c1.274-.609 1.604-2.17 1.628-3.581c.119-7.008-1.52-10.097-3.903-10.942C17.71 5.025 17.3 5 16.897 5h-.612a4.24 4.24 0 0 0-2.45.78l-.502.354a2.31 2.31 0 0 1-2.666 0" opacity="0.5"/><path fill="currentColor" d="M16.75 9a.75.75 0 1 1 0 1.5a.75.75 0 0 1 0-1.5m-9.25.25a.75.75 0 0 1 .75.75v.75H9a.75.75 0 0 1 0 1.5h-.75V13a.75.75 0 0 1-1.5 0v-.75H6a.75.75 0 0 1 0-1.5h.75V10a.75.75 0 0 1 .75-.75m11.5 2a.75.75 0 1 1-1.5 0a.75.75 0 0 1 1.5 0m-3.75.75a.75.75 0 1 0 0-1.5a.75.75 0 0 0 0 1.5m2.25.75a.75.75 0 1 0-1.5 0a.75.75 0 0 0 1.5 0"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m21.838 11.677l-9.549-9.58c-.129-.13-.451-.13-.645 0L9 4.742l2.452 2.452c.193-.097.419-.13.645-.13c.903 0 1.58.742 1.58 1.581c0 .226-.032.452-.129.645l1.968 1.968c.194-.097.42-.129.645-.129c.904 0 1.58.742 1.58 1.58c0 .904-.741 1.581-1.58 1.581c-.903 0-1.58-.742-1.58-1.58c0-.226.032-.452.129-.646l-1.968-1.967h-.032v3.71c.58.258 1 .806 1 1.483c0 .904-.742 1.581-1.581 1.581c-.903 0-1.58-.742-1.58-1.58c0-.678.419-1.259 1-1.485v-3.612c-.581-.259-1-.807-1-1.484c0-.226.032-.452.128-.645L8.225 5.613l-6.097 6.064c-.129.13-.129.452 0 .646l9.58 9.58c.13.13.452.13.646 0l9.548-9.58a.59.59 0 0 0-.064-.646"/></svg>

After

Width:  |  Height:  |  Size: 720 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2.249c-5.484 0-10 4.452-10 10c0 4.387 2.871 8.13 6.871 9.484c.516.097.677-.226.677-.452s0-.87-.032-1.742c-2.774.645-3.355-1.355-3.355-1.355c-.451-1.129-1.129-1.451-1.129-1.451c-.903-.645.033-.645.033-.645c1 .032 1.548 1.032 1.548 1.032c.87 1.548 2.355 1.097 2.903.806c.097-.645.355-1.096.645-1.354c-2.193-.226-4.548-1.097-4.548-4.904c0-1.096.42-1.967 1.032-2.645c-.097-.226-.451-1.258.097-2.645c0 0 .87-.258 2.774 1.032a9.3 9.3 0 0 1 2.516-.355c.871 0 1.742.097 2.516.355c1.904-1.258 2.742-1.032 2.742-1.032c.549 1.355.226 2.42.097 2.645c.645.678 1.032 1.58 1.032 2.645c0 3.807-2.355 4.678-4.548 4.904c.355.322.677.967.677 1.87c0 1.355-.032 2.42-.032 2.742c0 .259.194.549.678.452C19.129 20.314 22 16.604 22 12.185c-.032-5.484-4.516-9.936-10-9.936"/></svg>

After

Width:  |  Height:  |  Size: 871 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M3.51 10.503c-.04 0-.05-.02-.03-.05l.205-.266c.02-.03.068-.05.107-.05h3.476c.04 0 .049.03.03.06l-.166.257c-.02.03-.068.059-.097.059zm-1.471.91c-.039 0-.049-.02-.03-.05l.205-.267c.02-.03.068-.05.107-.05h4.44c.04 0 .06.03.05.06l-.078.237c-.01.04-.049.06-.088.06zm2.357.91c-.04 0-.049-.03-.03-.06l.137-.247c.02-.03.058-.06.097-.06h1.948c.039 0 .058.03.058.07l-.02.237c0 .04-.038.07-.067.07zm10.108-1.998c-.614.158-1.032.277-1.636.435c-.146.04-.156.05-.283-.099c-.146-.168-.253-.277-.457-.376c-.614-.306-1.208-.217-1.763.149c-.662.435-1.003 1.078-.993 1.879c.01.791.545 1.444 1.315 1.553c.662.089 1.217-.149 1.655-.653c.088-.109.165-.228.263-.366h-1.88c-.204 0-.253-.129-.184-.297c.126-.306.36-.82.496-1.078a.26.26 0 0 1 .243-.158h3.545c-.02.267-.02.534-.058.801a4.25 4.25 0 0 1-.799 1.939c-.7.94-1.616 1.523-2.775 1.68c-.954.13-1.84-.059-2.62-.652q-1.078-.831-1.236-2.196c-.127-1.077.185-2.047.827-2.897c.692-.92 1.607-1.504 2.727-1.711c.915-.168 1.792-.06 2.58.484c.517.346.887.821 1.13 1.395c.059.089.02.138-.097.168"/><path fill="currentColor" d="M17.726 15.794c-.886-.02-1.694-.277-2.376-.87a3.12 3.12 0 0 1-1.052-1.91c-.175-1.117.127-2.106.79-2.986c.71-.95 1.567-1.444 2.726-1.652c.993-.178 1.928-.079 2.775.505c.77.534 1.247 1.256 1.373 2.205c.166 1.335-.214 2.423-1.12 3.353a4.44 4.44 0 0 1-2.337 1.266c-.263.05-.526.06-.779.09m2.318-3.996c-.01-.128-.01-.227-.03-.326c-.175-.98-1.06-1.533-1.986-1.315c-.905.207-1.49.79-1.704 1.72c-.175.772.195 1.553.896 1.87c.535.237 1.071.207 1.587-.06c.77-.405 1.188-1.038 1.237-1.889"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M6.233 2h.902v.89h.824V2h.902v2.697h-.902v-.903h-.824v.903h-.902zm3.813.894h-.793V2h2.489v.894h-.794v1.803h-.902zM12.136 2h.94l.579.948l.578-.948h.94v2.697h-.898V3.36l-.62.96h-.015l-.621-.96v1.337h-.882zm3.486 0h.901v1.806h1.268v.891h-2.17z"/><path fill="currentColor" fill-rule="evenodd" d="m4.915 5.93l1.29 14.464L11.99 22l5.802-1.609l1.291-14.46zm11.202 6.547H9.652l-.162-1.816h6.788l.159-1.774H7.552l.478 5.364h6.148l-.206 2.3l-1.978.535h-.002l-1.976-.533l-.126-1.415H8.11l.248 2.785l3.633 1.009l.009-.002l3.63-1.007l.443-4.97z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 672 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M3 3v18h18V3zm9.813 14.023c0 1.77-1.045 2.584-2.555 2.584c-1.335 0-2.12-.697-2.526-1.568l1.394-.871c.232.493.493.87 1.103.87c.58 0 .9-.203.9-1.073v-5.69h1.684zm4.006 2.584c-1.567 0-2.583-.784-3.106-1.713l1.394-.813c.377.58.87 1.016 1.683 1.016c.697 0 1.133-.32 1.133-.871c0-.58-.436-.784-1.22-1.133l-.406-.174c-1.22-.493-2.033-1.19-2.033-2.584c0-1.277.93-2.206 2.468-2.206c1.075 0 1.83.378 2.38 1.364l-1.305.871c-.29-.493-.581-.725-1.104-.725c-.493 0-.813.32-.813.726c0 .493.32.696 1.017 1.016l.406.174c1.422.61 2.264 1.22 2.264 2.67c.03 1.54-1.16 2.381-2.758 2.381"/></svg>

After

Width:  |  Height:  |  Size: 686 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m12.003 13.121l.605-.289l.138-.646l-.413-.523h-.674l-.413.523l.152.646zm4.292-.331c.043-.464.01-.934-.096-1.389a4.4 4.4 0 0 0-.523-1.307l-1.664 1.5a.33.33 0 0 0-.083.151a.373.373 0 0 0 .261.44zm-3.081-2.242l1.802-1.279a4.18 4.18 0 0 0-2.476-1.197l.123 2.243c.014.054.028.11.07.15c.123.152.33.18.48.083M11.246 8.1l-.22.04a4.2 4.2 0 0 0-2.05 1.129l1.83 1.307c.041.027.11.041.165.041a.377.377 0 0 0 .372-.358l.123-2.2zm-1.293 3.453l-1.637-1.459a4.43 4.43 0 0 0-.619 2.697l2.132-.62a.27.27 0 0 0 .165-.11a.366.366 0 0 0-.04-.508m.178 1.898l-2.187.372a4.34 4.34 0 0 0 1.72 2.16l.839-2.023a.4.4 0 0 0 .013-.22a.37.37 0 0 0-.385-.289m1.871 3.219c.33 0 .647-.04.963-.11q.092-.027.182-.049q.113-.028.217-.06l-1.06-1.913a.6.6 0 0 0-.164-.151c-.152-.083-.33-.028-.44.096l-1.087 1.967c.44.138.908.22 1.389.22m2.325-.687a4.4 4.4 0 0 0 1.32-1.335c.18-.261.317-.537.4-.84l-2.201-.37a.44.44 0 0 0-.193.04a.37.37 0 0 0-.193.427z"/><path fill="currentColor" d="M12.003 2.294c.193 0 .385.041.564.124l6.961 3.315c.358.18.633.51.716.895l1.72 7.47c.096.399 0 .811-.262 1.128l-4.815 5.984a1.31 1.31 0 0 1-1.032.496H8.137a1.31 1.31 0 0 1-1.032-.495L2.29 15.226a1.38 1.38 0 0 1-.261-1.128l1.72-7.47c.096-.4.357-.73.715-.895l6.961-3.329c.18-.069.385-.11.578-.11m6.961 11.212c-.013 0-.027 0-.027-.014a.2.2 0 0 1-.041-.006q-.021-.006-.042-.007l-.036-.005a2 2 0 0 0-.225-.023c-.041 0-.083 0-.138-.014h-.013c-.29-.027-.523-.054-.743-.123a.27.27 0 0 1-.152-.152q0-.01-.007-.013q-.005-.005-.006-.014l-.18-.055a5.4 5.4 0 0 0-.096-1.94a5.7 5.7 0 0 0-.77-1.802l.138-.124v-.028a.3.3 0 0 1 .068-.206c.158-.145.354-.264.588-.407l.032-.02l.061-.034q.031-.014.062-.034a2 2 0 0 0 .234-.138l.023-.018q.022-.013.046-.037l.015-.011q.011-.006.012-.016c.193-.165.234-.44.097-.62a.39.39 0 0 0-.317-.15a.5.5 0 0 0-.302.11l-.028.027q-.02.011-.034.027t-.035.028v.001a2 2 0 0 0-.205.22a.4.4 0 0 1-.07.068a3.5 3.5 0 0 1-.55.495a.22.22 0 0 1-.124.041c-.027 0-.055 0-.082-.014h-.028l-.165.11a7 7 0 0 0-.564-.522a5.5 5.5 0 0 0-2.875-1.142l-.014-.179l-.028-.027l-.015-.016a.26.26 0 0 1-.095-.163c-.013-.205.01-.434.036-.687l.005-.056v-.014c0-.041.014-.096.028-.138c.014-.082.028-.165.041-.26v-.125c0-.247-.192-.454-.426-.454c-.11 0-.22.055-.303.138a.44.44 0 0 0-.124.316v.11c0 .097.014.18.042.262q.01.032.014.069q.004.03.013.068v.014l.018.166c.024.21.045.402.023.577a.26.26 0 0 1-.094.163l-.016.016l-.027.027l-.014.18a5.3 5.3 0 0 0-3.467 1.65l-.137-.096h-.028q-.02 0-.041.007a.2.2 0 0 1-.041.006a.22.22 0 0 1-.124-.04a3.7 3.7 0 0 1-.577-.54c-.02-.022-.042-.047-.07-.066a2 2 0 0 0-.179-.193l-.023-.018q-.021-.013-.045-.037l-.015-.011q-.013-.005-.013-.016a.48.48 0 0 0-.303-.11a.39.39 0 0 0-.316.151c-.138.179-.096.454.096.62q.01 0 .014.006t.014.007q.02.011.034.027t.034.028c.083.055.152.096.234.138a.6.6 0 0 1 .124.068l.03.019c.235.143.431.263.59.408c.068.069.068.137.068.206v.028l.138.123l-.025.034q-.028.036-.044.077a5.36 5.36 0 0 0-.77 3.632l-.18.055q0 .009-.006.013q-.006.005-.007.014a.32.32 0 0 1-.152.151c-.206.07-.453.097-.742.124h-.014c-.041 0-.096 0-.138.014c-.072 0-.144.01-.225.022l-.036.005q-.021.001-.041.007a.2.2 0 0 1-.042.007c-.013 0-.027 0-.041.014a.45.45 0 0 0-.358.509c.042.193.22.316.44.316c.042 0 .07 0 .11-.013c.042 0 .07-.028.11-.028a1.3 1.3 0 0 0 .248-.096l.062-.027q.03-.016.062-.028h.014c.261-.097.495-.179.715-.207h.028c.077 0 .13.036.17.063l.009.006q.01.001.013.007q.005.007.014.007l.193-.027a5.44 5.44 0 0 0 1.802 2.586c.193.151.385.275.592.399l-.083.179q.001.01.007.014t.007.013c.027.055.055.124.027.22a4 4 0 0 1-.357.647v.014q-.021.03-.042.055l-.04.055c-.042.05-.075.102-.112.159l-.04.06a.3.3 0 0 0-.042.07c0 .013-.013.027-.013.027c-.11.234-.028.495.179.592q.082.04.165.041c.165 0 .33-.11.412-.261c0-.014.014-.028.014-.028c.014-.027.028-.055.041-.069c.028-.064.043-.116.059-.168l.024-.08l.041-.123l.033-.093c.08-.233.147-.425.256-.595a.3.3 0 0 1 .143-.108l.036-.016c.014 0 .014 0 .027-.014l.097-.178a5.4 5.4 0 0 0 1.926.357c.399 0 .812-.041 1.197-.137q.363-.082.715-.207l.083.152c.014 0 .014 0 .027.013a.26.26 0 0 1 .179.124c.101.178.18.39.266.627l.023.061v.014l.041.124q.014.041.025.082c.015.055.03.11.058.165l.02.035q.01.014.021.034c0 .014.014.028.014.028a.47.47 0 0 0 .412.261q.083 0 .166-.041a.4.4 0 0 0 .206-.248a.5.5 0 0 0-.027-.344q-.001-.01-.007-.014q-.007-.004-.007-.013a.3.3 0 0 0-.042-.07a1.2 1.2 0 0 0-.15-.22l-.042-.054l-.041-.055v-.014l-.025-.039c-.142-.218-.268-.413-.333-.608a.3.3 0 0 1 .004-.178l.01-.042q0-.01.006-.014q.006-.003.007-.013l-.068-.165a5.3 5.3 0 0 0 1.816-1.775a5.3 5.3 0 0 0 .577-1.238l.165.027q.01 0 .014-.007q.005-.005.014-.006l.04-.023a.25.25 0 0 1 .139-.046h.027c.22.027.454.11.716.206h.013q.032.011.062.027q.031.016.062.028c.083.041.152.069.248.096a.2.2 0 0 1 .041.007q.021.007.041.007c.014 0 .028 0 .042.014a.463.463 0 0 0 .55-.303c0-.165-.151-.385-.399-.454z"/></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m21.705 6.556l-.02-.044l-.02-.052c-.012-.02-.024-.032-.04-.052q-.012-.022-.036-.044c-.008-.008-.02-.016-.044-.036a.2.2 0 0 0-.044-.032l-3.715-2.14a.45.45 0 0 0-.428 0L13.64 6.3s-.024.016-.048.036l-.036.028l-.044.056l-.024.036s-.02.036-.032.068l-.012.032q-.014.059-.016.112v3.995l-2.855 1.643V4.564c0-.036 0-.076-.012-.108a.3.3 0 0 0-.02-.048a.3.3 0 0 0-.024-.052c-.008-.02-.02-.032-.036-.052a.3.3 0 0 0-.036-.044c-.008-.008-.02-.016-.044-.036c-.012-.008-.024-.02-.04-.028l-3.72-2.139a.43.43 0 0 0-.428 0L2.498 4.192s-.032.024-.052.044l-.04.028s-.02.028-.04.052l-.024.036s-.02.044-.028.068l-.016.036c-.008.036-.016.072-.016.112v12.725c0 .072.02.148.06.212c.036.068.092.12.156.16l7.434 4.279l.064.028l.036.012q.053.014.112.016q.054-.002.108-.016l.036-.012q.037-.01.068-.032l7.434-4.275a.44.44 0 0 0 .212-.372v-3.995l3.5-2.015a.43.43 0 0 0 .16-.156a.46.46 0 0 0 .055-.216V6.668c0-.036 0-.072-.012-.112m-4.13 1.755l-2.856-1.643l2.855-1.64l2.856 1.64zM6.428 2.925L9.28 4.568L6.43 6.212L3.573 4.568zm3.283 2.383v7.494l-1.735 1l-1.12.644v-7.49zm0 15.516l-6.57-3.783V5.308l2.855 1.648v8.234l.008.056q.001.025.008.056c.004.016.012.028.02.052q.012.03.024.048a.4.4 0 0 0 .036.056l.032.036s.02.02.048.04l3.54 2.007zm.432-4.027l-2.851-1.611l6.566-3.78l2.852 1.644zm6.998.244l-6.57 3.787v-3.287l6.57-3.75zm0-4.735l-2.855-1.643V7.412l2.855 1.643zm3.716-1.643l-2.856 1.643v-3.25l2.856-1.644z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M20.422 17.337c-1.088-.03-1.932.081-2.64.379c-.203.082-.53.082-.557.338c.11.108.122.284.218.433c.163.27.449.635.707.824l.87.622c.531.325 1.13.514 1.647.838c.299.19.598.433.898.636c.152.108.244.284.435.352v-.041c-.095-.122-.123-.297-.217-.433l-.409-.392a6.4 6.4 0 0 0-1.415-1.365c-.435-.298-1.387-.703-1.564-1.203l-.027-.03c.299-.03.653-.136.939-.217c.463-.121.884-.095 1.36-.216l.653-.19v-.12c-.245-.244-.422-.569-.68-.798a18 18 0 0 0-2.245-1.663c-.422-.27-.966-.447-1.415-.676c-.164-.081-.435-.122-.53-.257c-.246-.297-.381-.69-.558-1.041l-1.116-2.353c-.245-.527-.395-1.054-.694-1.54c-1.4-2.3-2.925-3.692-5.265-5.058c-.503-.284-1.101-.406-1.738-.554l-1.02-.055c-.218-.094-.436-.351-.626-.473c-.775-.487-2.775-1.541-3.347-.151c-.368.878.544 1.743.854 2.19c.231.31.53.662.694 1.014c.091.23.122.473.217.716c.218.595.422 1.258.708 1.812c.152.284.312.582.503.839c.109.151.3.216.34.46c-.19.27-.204.675-.313 1.014c-.49 1.528-.3 3.42.395 4.545c.218.338.731 1.082 1.428.798c.613-.244.476-1.014.653-1.69c.041-.162.014-.27.095-.379v.03l.558 1.123c.422.662 1.157 1.352 1.769 1.812c.326.243.584.662.992.81v-.04h-.026c-.082-.121-.205-.176-.314-.27a6.6 6.6 0 0 1-.707-.812a17.4 17.4 0 0 1-1.523-2.46c-.218-.42-.409-.879-.585-1.298c-.083-.162-.083-.406-.218-.487c-.205.297-.503.555-.654.92c-.258.58-.285 1.297-.38 2.041c-.055.014-.03 0-.055.03c-.435-.107-.585-.554-.748-.932c-.408-.96-.476-2.501-.123-3.61c.096-.284.504-1.177.341-1.447c-.082-.257-.354-.405-.504-.608a5.5 5.5 0 0 1-.49-.865c-.325-.758-.489-1.596-.843-2.353c-.163-.352-.449-.717-.68-1.041c-.259-.365-.544-.622-.748-1.055c-.068-.151-.163-.392-.054-.554c.026-.108.081-.152.19-.176c.176-.151.68.04.857.121c.503.203.925.392 1.347.676c.19.135.394.392.64.46h.285c.436.095.925.03 1.333.152c.72.23 1.374.567 1.96.933a12 12 0 0 1 4.244 4.624c.163.311.23.595.38.92c.287.662.64 1.338.926 1.987c.286.636.558 1.285.966 1.812c.204.284 1.02.433 1.387.582c.272.12.694.23.94.378c.461.284.924.609 1.359.92c.217.162.898.5.939.77zM6.548 5.588a2.2 2.2 0 0 0-.557.068v.03h.027c.109.216.3.365.435.555l.313.649l.027-.03c.19-.136.286-.352.286-.676c-.082-.095-.095-.19-.163-.284c-.082-.135-.259-.203-.368-.311" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M11.903 22c-.258 0-.548-.032-.774-.226l-2.452-1.451c-.355-.226-.193-.259-.032-.323c.484-.129.58-.226 1.097-.484c.032-.032.129-.032.193.032l1.871 1.13c.033.032.13.032.226 0l7.323-4.226c.032-.033.129-.13.129-.194V7.774c0-.097-.033-.129-.13-.193L12 3.419c-.032-.032-.13-.032-.226 0l-7.29 4.226c-.033.032-.13.13-.13.194v8.42c0 .096.033.128.13.193l2 1.129c1.097.548 1.774-.097 1.774-.775V8.548a.22.22 0 0 1 .226-.225h.935a.22.22 0 0 1 .226.225v8.323c0 1.452-.774 2.258-2.129 2.258c-.452 0-.71 0-1.677-.452l-1.904-1.064a1.57 1.57 0 0 1-.774-1.355V7.774c0-.548.258-1.032.774-1.355l7.258-4.225a1.65 1.65 0 0 1 1.549 0l7.322 4.258c.452.258.775.774.775 1.354v8.42c0 .548-.258 1.032-.775 1.355l-7.29 4.193c-.322.194-.58.226-.87.226m5.903-8.323c0-1.58-1.032-2-3.322-2.322s-2.484-.452-2.484-1c0-.452.193-1.032 1.87-1.032c1.485 0 2.033.322 2.259 1.354c.032.097.097.13.226.13h.935c.032 0 .13-.033.13-.033s.031-.097.031-.129c-.129-1.774-1.322-2.548-3.612-2.548c-2.097 0-3.355.87-3.355 2.355c0 1.58 1.258 2.032 3.226 2.225c2.387.226 2.58.581 2.58 1.033c0 .806-.645 1.129-2.161 1.129c-1.936 0-2.355-.484-2.484-1.452c-.032-.097-.097-.193-.226-.193h-.87a.22.22 0 0 0-.227.225c0 1.226.646 2.71 3.84 2.71c2.322.065 3.644-.839 3.644-2.452"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="25" viewBox="0 0 24 25"><path fill="currentColor" d="M20.557 10.634a5.07 5.07 0 0 0-.42-4.099c-1.087-1.901-3.284-2.864-5.432-2.42c-.939-1.061-2.321-1.654-3.754-1.654a5.07 5.07 0 0 0-4.814 3.481a5 5 0 0 0-3.334 2.42a5.07 5.07 0 0 0 .618 5.901a5.06 5.06 0 0 0 .444 4.1a5.025 5.025 0 0 0 5.432 2.419a5.07 5.07 0 0 0 3.753 1.679a5.07 5.07 0 0 0 4.815-3.481a5 5 0 0 0 3.333-2.42a5.07 5.07 0 0 0-.642-5.926M13.05 21.152a3.66 3.66 0 0 1-2.395-.864c.025-.025.099-.05.124-.074l3.975-2.296a.65.65 0 0 0 .321-.568v-5.605l1.679.963c.025 0 .025.024.025.05v4.641a3.716 3.716 0 0 1-3.729 3.753M5 17.72c-.444-.765-.592-1.654-.444-2.518c.025.024.075.05.124.074l3.975 2.296a.6.6 0 0 0 .642 0l4.864-2.815v1.95c0 .026 0 .05-.024.05l-4.025 2.321c-1.778 1.037-4.074.42-5.111-1.358M3.965 9.03a3.88 3.88 0 0 1 1.95-1.654v4.74c0 .223.124.445.321.568l4.865 2.815l-1.68.963c-.024 0-.049.025-.049 0L5.347 14.14a3.714 3.714 0 0 1-1.383-5.111m13.827 3.21l-4.864-2.815l1.679-.963c.024 0 .05-.025.05 0l4.024 2.32a3.727 3.727 0 0 1 1.358 5.112a3.72 3.72 0 0 1-1.95 1.63v-4.716a.61.61 0 0 0-.297-.568m1.654-2.519a.5.5 0 0 0-.123-.074L15.347 7.35a.6.6 0 0 0-.642 0L9.84 10.165V8.214c0-.025 0-.05.025-.05l4.025-2.32A3.73 3.73 0 0 1 19 7.226c.445.741.593 1.63.445 2.494M8.927 13.177l-1.68-.963c-.024 0-.024-.025-.024-.05v-4.64a3.75 3.75 0 0 1 3.753-3.753a3.66 3.66 0 0 1 2.395.864a.5.5 0 0 1-.123.074L9.273 7.004a.65.65 0 0 0-.321.568v5.605zm.913-1.975l2.173-1.26l2.173 1.26v2.493l-2.173 1.26l-2.173-1.26z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 7.306c5.355 0 9.452 2.226 9.452 4.742S17.355 16.822 12 16.822s-9.452-2.258-9.452-4.774S6.645 7.306 12 7.306m0-.548c-5.548 0-10 2.355-10 5.258s4.452 5.226 10 5.226s10-2.323 10-5.226s-4.452-5.258-10-5.258m-3.194 4.87c-.225 1.259-1.129 1.13-2.193 1.13l.452-2.226c1.193 0 1.967-.129 1.741 1.097m-3.774 3.356h1.162l.258-1.42c1.258 0 2.096.097 2.806-.58c.807-.775 1.032-2.097.452-2.742c-.323-.355-.775-.549-1.452-.549H6.032zm5.839-6.678H12l-.258 1.42c.968 0 1.903-.033 2.355.354c.451.452.226.968-.226 3.549h-1.194c.452-2.452.549-2.678.42-2.871s-.645-.194-1.549-.194l-.58 3.065h-1.13zm6.903 3.323c-.226 1.258-1.129 1.129-2.193 1.129l.451-2.226c1.194 0 1.968-.129 1.742 1.097M14 14.984h1.129l.258-1.355c1.355 0 2.097.097 2.807-.58c.806-.775 1.032-2.098.451-2.743c-.322-.355-.774-.548-1.451-.548h-2.226z"/></svg>

After

Width:  |  Height:  |  Size: 919 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M21.57 9.429c-.354-1.355-1-2.42-2.355-2.42H17.41v2.13c0 1.645-1.42 3.032-2.936 3.032H9.665c-1.322 0-2.355 1.13-2.355 2.452v4.55c0 1.257 1.13 2.032 2.355 2.451c1.485.452 2.936.549 4.775 0c1.194-.355 2.356-1.032 2.356-2.452v-1.807h-4.743v-.58h7.162c1.355 0 1.904-.968 2.355-2.42c.484-1.581.484-3.033 0-4.936m-6.84 9.033c.485 0 .904.42.904.904s-.42.903-.903.903c-.484.032-.904-.42-.904-.903c-.032-.484.387-.904.904-.904m-5.29-6.904h4.775c1.322 0 2.355-1.097 2.355-2.452V4.621c0-1.258-1.097-2.226-2.356-2.452c-1.58-.225-3.323-.225-4.774 0c-2.033.355-2.356 1.097-2.356 2.452v1.807h4.775v.58H5.342c-1.355 0-2.581.872-2.936 2.42c-.452 1.807-.452 2.936 0 4.808c.355 1.42 1.13 2.42 2.549 2.42h1.549v-2.162c-.033-1.581 1.355-2.936 2.936-2.936m-.29-6.356a.923.923 0 0 1-.904-.903c0-.484.42-.904.903-.904s.904.42.904.904s-.42.903-.904.903"/></svg>

After

Width:  |  Height:  |  Size: 947 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M18.323 9.007c-.226-.033-.42-.13-.646-.194c.033-.129.033-.258.097-.452c.452-2.322.13-4.16-.903-4.806c-1.032-.58-2.71.032-4.387 1.484c-.13.129-.355.258-.484.451c-.097-.096-.226-.193-.323-.322c-1.774-1.58-3.516-2.226-4.612-1.613c-1.033.58-1.355 2.355-.904 4.516c.033.226.097.452.13.645c-.226.033-.485.13-.71.226C3.484 9.684 2 10.878 2 12.007c0 1.225 1.58 2.451 3.742 3.16c.193.033.355.13.548.13c-.032.226-.129.451-.129.71c-.42 2.129-.096 3.838.936 4.419c1.032.645 2.838 0 4.516-1.548c.129-.13.258-.226.42-.355a4 4 0 0 0 .548.451c1.613 1.452 3.29 2.033 4.355 1.452c1.032-.645 1.387-2.452.935-4.71c-.032-.129-.032-.354-.13-.548c.13-.032.227-.032.356-.13C20.355 14.362 22 13.137 22 11.91c-.032-1-1.58-2.161-3.677-2.903m-5.29-3.29c1.45-1.259 2.806-1.71 3.45-1.388c.646.355.904 1.903.485 3.936c-.032.129-.032.225-.097.419c-.871-.194-1.71-.355-2.613-.42c-.484-.709-1.032-1.45-1.613-2.032a1.8 1.8 0 0 1 .387-.516M8.547 14.07c.194.355.42.678.646 1a10.6 10.6 0 0 1-1.807-.258a8 8 0 0 1 .645-1.71c.13.29.355.678.516.968m-1.16-4.645a22 22 0 0 1 1.773-.323c-.226.323-.42.646-.58 1c-.162.355-.355.646-.549 1.033c-.29-.613-.451-1.162-.645-1.71m1.031 2.677c.226-.548.549-1.032.871-1.58c.323-.549.645-1.033.936-1.549c.58-.032 1.161-.032 1.774-.032c.58 0 1.226.032 1.774.032c.355.484.645 1 .936 1.484c.322.484.58 1.033.87 1.58c-.225.55-.548 1.033-.87 1.582c-.323.548-.646 1.032-.936 1.548c-.58.032-1.161.032-1.806.032s-1.226-.032-1.774-.032c-.355-.484-.646-1-.936-1.548c-.29-.549-.548-.968-.839-1.517m7.033 1.968c.193-.355.354-.677.548-1.032c.226.548.452 1.129.645 1.71c-.58.129-1.226.225-1.87.322c.257-.322.483-.677.677-1m.58-2.968c-.193-.354-.355-.677-.548-1.032s-.42-.645-.58-.935a22 22 0 0 1 1.773.322c-.193.549-.419 1.097-.645 1.645M12 6.75c.42.451.774.903 1.129 1.387a28 28 0 0 0-2.322 0c.419-.516.838-.968 1.193-1.387M7.484 4.36c.645-.354 2.129.13 3.613 1.549c.097.097.193.193.322.258c-.58.645-1.129 1.355-1.677 2.032a23 23 0 0 0-2.613.42c-.032-.194-.097-.42-.129-.581c-.355-1.903-.129-3.323.484-3.678M6.58 14.62c-.13-.033-.323-.097-.484-.13a7.2 7.2 0 0 1-2.484-1.225c-.452-.323-.678-.71-.774-1.162c0-.71 1.258-1.612 3.032-2.258c.226-.096.452-.129.677-.225c.258.87.581 1.677.936 2.483c-.323.807-.678 1.613-.903 2.517m4.484 3.774a7.1 7.1 0 0 1-2.226 1.355a1.54 1.54 0 0 1-1.355.032c-.645-.355-.871-1.71-.549-3.58c.033-.227.097-.452.13-.646c.87.194 1.774.323 2.612.355c.484.71 1.097 1.451 1.678 2.097c-.032.193-.162.29-.29.387m.967-.903c-.42-.452-.774-.904-1.161-1.388c.355 0 .71.033 1.129.033s.806 0 1.161-.033c-.354.42-.71.871-1.129 1.387m5.097 1.128c-.032.452-.258.936-.645 1.226c-.645.355-1.936-.096-3.355-1.354c-.129-.13-.322-.259-.484-.452a17 17 0 0 0 1.613-2.097c.903-.032 1.774-.226 2.613-.42c.032.13.032.323.097.452c.226.904.226 1.807.161 2.645m.71-4.16c-.097.032-.226.032-.355.096c-.258-.871-.645-1.677-1-2.484c.355-.774.677-1.613.935-2.451c.226.032.42.129.581.193c1.806.645 3.064 1.548 3.064 2.258c.033.742-1.29 1.742-3.225 2.387M12 13.844c1 0 1.774-.806 1.774-1.774A1.79 1.79 0 0 0 12 10.297c-.935 0-1.774.806-1.774 1.774S11 13.845 12 13.845"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="m9.637 2.292l.221.91A8.8 8.8 0 0 0 7.385 4.24l-.474-.803a9.7 9.7 0 0 1 2.726-1.145m4.726 0l-.222.91a8.8 8.8 0 0 1 2.474 1.038l.477-.803a9.7 9.7 0 0 0-2.73-1.145M3.57 6.831c-.512.86-.892 1.793-1.128 2.768l.895.225A9.1 9.1 0 0 1 4.36 7.312zm-.493 5.168q0-.682.1-1.357l-.912-.141a10.2 10.2 0 0 0 0 2.997l.912-.141q-.1-.675-.1-1.358m14.011 8.562l-.473-.803c-.768.47-1.6.821-2.47 1.039l.22.91a9.7 9.7 0 0 0 2.723-1.146m3.834-8.562q0 .684-.1 1.358l.912.14c.148-.993.148-2.003 0-2.996l-.912.14q.1.676.1 1.358m.635 2.4l-.895-.225a9.1 9.1 0 0 1-1.023 2.512l.79.485a10 10 0 0 0 1.128-2.772m-8.22 6.562a8.9 8.9 0 0 1-2.674 0l-.139.927a9.7 9.7 0 0 0 2.951 0zm5.845-3.586a9 9 0 0 1-1.89 1.919l.547.755a10 10 0 0 0 2.086-2.113zm-1.89-12.67a9 9 0 0 1 1.89 1.92l.743-.563a10 10 0 0 0-2.08-2.112zM4.817 6.624a9 9 0 0 1 1.89-1.92l-.553-.755a10 10 0 0 0-2.08 2.112zm15.613.206l-.79.481a9.1 9.1 0 0 1 1.022 2.51l.895-.226A10 10 0 0 0 20.43 6.83m-9.767-3.792a8.9 8.9 0 0 1 2.673 0l.139-.927a9.7 9.7 0 0 0-2.95 0zM5.29 20.297l-1.906.451l.445-1.935l-.899-.214l-.444 1.936a.95.95 0 0 0 .246.876a.92.92 0 0 0 .863.25l1.904-.444zm-2.168-2.534l.899.212l.308-1.342a9.1 9.1 0 0 1-.993-2.459l-.895.225c.2.829.506 1.627.908 2.376zm4.308 2.03l-1.322.313l.21.913l.972-.23a9.7 9.7 0 0 0 2.34.922l.221-.91a8.8 8.8 0 0 1-2.415-1.013zM12 3.876c-1.43 0-2.833.39-4.063 1.128A8.07 8.07 0 0 0 5 8.071a8.23 8.23 0 0 0 .23 8.251l-.77 3.333l3.282-.781a7.89 7.89 0 0 0 7.111.717a8 8 0 0 0 3.04-2.092a8.16 8.16 0 0 0 1.799-3.25a8.25 8.25 0 0 0 .18-3.724a8.2 8.2 0 0 0-1.477-3.413a8 8 0 0 0-2.822-2.385A7.9 7.9 0 0 0 12 3.875"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M11.096 2.89c2.61-1.69 6.223-.902 8.052 1.756v.003a5.75 5.75 0 0 1 .958 4.307a5.4 5.4 0 0 1-.787 1.996a5.72 5.72 0 0 1 .545 3.608a5.4 5.4 0 0 1-.815 2.046a5.3 5.3 0 0 1-1.561 1.535l-4.582 2.97c-2.603 1.689-6.223.9-8.052-1.757a5.7 5.7 0 0 1-.908-2.055a5.8 5.8 0 0 1-.054-2.252a5.4 5.4 0 0 1 .787-1.997a5.7 5.7 0 0 1-.64-2.614q0-.504.087-1a5.4 5.4 0 0 1 .82-2.044A5.3 5.3 0 0 1 6.51 5.86zM9.731 19.723q.465 0 .917-.118c.33-.09.644-.231.932-.417l4.579-2.967c.372-.239.692-.553.938-.924s.413-.79.491-1.23q.054-.3.053-.605c0-.714-.22-1.41-.628-1.991a3.66 3.66 0 0 0-1.304-1.169A3.6 3.6 0 0 0 13.103 10c-.33.09-.644.23-.931.415l-1.75 1.139a1 1 0 0 1-.283.125q-.135.037-.275.036a1.08 1.08 0 0 1-.9-.478a1.05 1.05 0 0 1-.19-.601q.005-.087.02-.172a1 1 0 0 1 .432-.652l4.587-2.967a1 1 0 0 1 .282-.125q.133-.039.272-.043a1.08 1.08 0 0 1 .9.479c.124.177.19.388.19.604v.09l-.016.172l.17.054a5.9 5.9 0 0 1 1.795.911l.234.174l.086-.267q.07-.213.11-.434q.052-.3.052-.6a3.46 3.46 0 0 0-.62-1.993a3.66 3.66 0 0 0-1.308-1.17a3.63 3.63 0 0 0-2.61-.302c-.33.09-.644.231-.932.417L7.832 7.78a3.2 3.2 0 0 0-.938.925a3.26 3.26 0 0 0-.543 1.828c0 .714.22 1.41.628 1.99a3.66 3.66 0 0 0 1.304 1.169a3.6 3.6 0 0 0 2.605.303c.33-.09.643-.23.931-.415l1.747-1.133q.13-.086.28-.125q.136-.037.276-.036a1.08 1.08 0 0 1 .902.478c.125.175.193.385.194.601a.99.99 0 0 1-.447.829l-4.582 2.983a1 1 0 0 1-.558.161a1.08 1.08 0 0 1-.9-.488a1.05 1.05 0 0 1-.19-.602v-.09l.017-.172l-.17-.053a5.9 5.9 0 0 1-1.795-.911l-.234-.18l-.087.266a3 3 0 0 0-.11.435a3.46 3.46 0 0 0 .577 2.59c.334.49.782.892 1.303 1.17s1.1.422 1.689.421" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M22 11h-.17c-1.05 0-1.96-.67-2.36-1.64l-.02-.05c-.41-.98-.25-2.1.5-2.85l.12-.12a.996.996 0 1 0-1.41-1.41l-.12.12c-.75.75-1.87.91-2.85.5l-.05-.02C14.67 5.13 14 4.22 14 3.17v-.08c0-.64-.45-1.08-1-1.08s-1 .45-1 1v.04c0 1.1-.66 2.08-1.67 2.5s-2.18.19-2.95-.59l-.02-.03a.996.996 0 1 0-1.41 1.41l.04.04c.77.77 1 1.92.58 2.93l-.03.06C6.13 10.36 5.17 11 4.1 11c-.64 0-1.08.45-1.08 1s.45 1 1 1h.07c1.08 0 2.05.65 2.46 1.64l.02.05c.42 1 .19 2.16-.58 2.93l-.04.04a.996.996 0 1 0 1.41 1.41l.04-.04c.77-.77 1.92-1 2.93-.58l.14.06c.94.39 1.55 1.3 1.55 2.32v.09c0 .64.45 1.09 1 1.09s1-.45 1-1v-.17c0-1.01.61-1.93 1.55-2.32l.25-.1c.94-.39 2.02-.17 2.74.55l.12.12a.996.996 0 1 0 1.41-1.41l-.12-.12c-.75-.75-.91-1.87-.5-2.85l.02-.05c.4-.97 1.31-1.64 2.36-1.64h.08c.64 0 1.09-.45 1.09-1s-.45-1-1-1ZM9 13c-.55 0-1-.45-1-1s.45-1 1-1s1 .45 1 1s-.45 1-1 1m5 3.5c-.55 0-1-.45-1-1s.45-1 1-1s1 .45 1 1s-.45 1-1 1m1-4.5c-1.1 0-2-.9-2-2s.9-2 2-2s2 .9 2 2s-.9 2-2 2"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M21.95 4.903a1 1 0 0 0-.06-.166a1.2 1.2 0 0 0-.31-.425a1.2 1.2 0 0 0-.29-.197l-4.118-1.994a1.27 1.27 0 0 0-.75-.103a1.26 1.26 0 0 0-.672.347L9.106 9.75L5.228 6.553l-.337-.281a.8.8 0 0 0-.413-.19q-.033-.006-.066-.007q-.029-.004-.059-.003q-.046 0-.09.003a.3.3 0 0 0-.079.013a.7.7 0 0 0-.156.046l-1.515.629a.87.87 0 0 0-.372.306a.85.85 0 0 0-.141.463v8.936c0 .163.05.325.14.463c.091.134.222.24.373.306l1.515.638a.85.85 0 0 0 .45.056a.85.85 0 0 0 .413-.19l.337-.294l3.878-3.198l6.644 7.386q.034.033.072.066q.004.005.01.006a1.25 1.25 0 0 0 1.34.172l4.119-1.994a1 1 0 0 0 .153-.088c.097-.065.187-.147.262-.231q.057-.07.103-.144c.125-.2.191-.431.191-.669V5.247q0-.175-.05-.344M4.5 14.874V9.126l2.584 2.876zm7.334-2.873L17 7.742v8.518z"/></svg>

After

Width:  |  Height:  |  Size: 848 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M17.4 3.442h-3.262l-2.126 3.543l-2.135-3.542l-2.369-.001H2l10.026 17.116L22 3.442zm-5.378 13.566L5.125 5.232h2.528l4.375 7.528l4.34-7.528h2.516z"/></svg>

After

Width:  |  Height:  |  Size: 265 B

+43
View File
@@ -0,0 +1,43 @@
/**
* constants.js
* Single source of truth for all application defaults and tuneable values.
* Import from here — never hardcode magic numbers elsewhere.
*/
export const DEFAULTS = {
// ── Gradient background ────────────────────────────────────
/** Base colour the gradient is derived from */
GRADIENT_COLOR: '#4d22b3',
/** Flow animation speed, slider 110 */
GRADIENT_SPEED: 2,
/** Clockwise rotation off by default */
GRADIENT_ROTATION: true,
// ── Icon evolution ─────────────────────────────────────────
/** Min ms before the first / next icon spawns */
SPAWN_DELAY_MIN: 3_000,
/** Max ms before the first / next icon spawns */
SPAWN_DELAY_MAX: 20_000,
/** Final rendered icon size in px */
ICON_SIZE: 24,
/** Half of ICON_SIZE — used for edge/collision math */
ICON_HALF: 12,
/** Movement speed slider default (110) */
MOVE_SPEED: 5,
/** Base px-per-frame at MOVE_SPEED */
BASE_SPEED: 0.8,
/** Hard cap: max speed = BASE_SPEED × this factor */
MAX_SPEED_FACTOR: 2.5,
/** ms for dot → full-icon grow animation */
APPEAR_DURATION: 600,
/** Probability per frame of a random velocity "kick" (chaotic drift) */
DRIFT_CHANCE: 0.02,
/** Max |Δv| applied on each drift kick */
DRIFT_MAGNITUDE: 0.25,
};
+157
View File
@@ -0,0 +1,157 @@
/**
* entity.js
* A single icon living on screen — owns its own physics state and DOM node.
*
* Physics model:
* - Constant velocity with small random "drift kicks" each frame for
* organic, chaotic-feeling motion (not perfectly straight lines).
* - Edge detection: velocity component is reflected (±abs) on contact,
* ensuring the bounce always sends the icon back into the viewport.
*
* DOM structure:
* <div class="icon-entity"> ← position via JS transform
* <div class="icon-entity__body"> ← scale animation via CSS transition
* <svg>…</svg> ← injected SVG, sized by CSS
* </div>
* </div>
*
* Separating the position element from the scale element avoids any
* conflict between JS-driven transform updates and CSS transitions.
*/
import { loadIcon } from './iconLoader.js';
import { DEFAULTS } from './constants.js';
export class Entity {
// ── Physics ──────────────────────────────────────────────
/** @type {number} */ x;
/** @type {number} */ y;
/** @type {number} */ vx;
/** @type {number} */ vy;
// ── Identity ─────────────────────────────────────────────
/** @type {string} */ name;
/** @type {string} */ type;
/** @type {string} */ color;
/** @type {boolean} */ alive = true;
// ── DOM ───────────────────────────────────────────────────
/** @type {HTMLElement|null} */ el = null;
/** @type {HTMLElement|null} */ bodyEl = null;
/**
* @param {{ name: string, type: string, color: string,
* x: number, y: number, vx: number, vy: number }} config
*/
constructor({ name, type, color, x, y, vx, vy }) {
this.name = name;
this.type = type;
this.color = color;
this.x = x; this.y = y;
this.vx = vx; this.vy = vy;
}
// ── Public API ─────────────────────────────────────────────
/**
* Build the DOM element, fetch the SVG, attach to container.
* Triggers the dot → icon appear animation after mount.
*
* @param {HTMLElement} container
*/
async mount(container) {
// Outer div: physics position handle
this.el = document.createElement('div');
this.el.className = 'icon-entity';
// Inner div: CSS scale transition target
this.bodyEl = document.createElement('div');
this.bodyEl.className = 'icon-entity__body';
this.bodyEl.dataset.state = 'spawning';
// Inject SVG
try {
this.bodyEl.innerHTML = await loadIcon(this.name);
} catch (err) {
console.warn(`[Entity] Could not load icon "${this.name}":`, err);
this.alive = false;
return;
}
this.el.appendChild(this.bodyEl);
container.appendChild(this.el);
// Set initial colour and position
this.el.style.color = this.color;
this._applyTransform();
// Two rAF ticks ensure the browser has painted the spawning state
// before adding the 'alive' class, guaranteeing the CSS transition fires.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (this.bodyEl) this.bodyEl.dataset.state = 'alive';
});
});
}
/**
* Advance physics by one frame.
* Call this inside your rAF loop.
*
* @param {number} speedMultiplier scales BASE_SPEED (e.g. slider / 5)
*/
update(speedMultiplier) {
if (!this.el || !this.alive) return;
// Move
this.x += this.vx * speedMultiplier;
this.y += this.vy * speedMultiplier;
// Bounce off viewport edges
const { ICON_HALF: h } = DEFAULTS;
const vw = window.innerWidth;
const vh = window.innerHeight;
if (this.x - h <= 0) { this.x = h; this.vx = Math.abs(this.vx); }
if (this.x + h >= vw) { this.x = vw - h; this.vx = -Math.abs(this.vx); }
if (this.y - h <= 0) { this.y = h; this.vy = Math.abs(this.vy); }
if (this.y + h >= vh) { this.y = vh - h; this.vy = -Math.abs(this.vy); }
// Chaotic drift: small random velocity kick, applied with low probability
if (Math.random() < DEFAULTS.DRIFT_CHANCE) {
this.vx += (Math.random() - 0.5) * DEFAULTS.DRIFT_MAGNITUDE;
this.vy += (Math.random() - 0.5) * DEFAULTS.DRIFT_MAGNITUDE;
// Clamp to max speed so drift can't accelerate indefinitely
const spd = Math.hypot(this.vx, this.vy);
const max = DEFAULTS.BASE_SPEED * DEFAULTS.MAX_SPEED_FACTOR;
if (spd > max) {
this.vx = (this.vx / spd) * max;
this.vy = (this.vy / spd) * max;
}
}
this._applyTransform();
}
/** Remove this entity from the DOM and mark it as dead. */
destroy() {
this.alive = false;
this.el?.remove();
this.el = null;
this.bodyEl = null;
}
// ── Private ────────────────────────────────────────────────
/**
* Write the current (x, y) position to the DOM via transform.
* Using transform keeps this on the compositor thread (no layout).
* We offset by ICON_HALF so (x, y) represents the icon's centre point.
*/
_applyTransform() {
if (!this.el) return;
const { ICON_HALF: h } = DEFAULTS;
this.el.style.transform = `translate(${this.x - h}px, ${this.y - h}px)`;
}
}
+152
View File
@@ -0,0 +1,152 @@
/**
* evolution.js
* Orchestrates the icon evolution system:
*
* 1. Spawning — after a random interval (SPAWN_DELAY_MIN … SPAWN_DELAY_MAX),
* a dna-bold-duotone icon appears at a random viewport position.
* After each spawn the timer resets for the next one.
*
* 2. Physics loop — a single rAF loop drives all live entities each frame.
*
* 3. Speed control — setMoveSpeed(110) scales entity velocity in real time.
*
* The icon to spawn first is declared in icons.json under spawn.initial.
* All icon metadata (type, colour) is also read from icons.json.
*/
import { Entity } from './entity.js';
import { preloadIcons } from './iconLoader.js';
import { DEFAULTS } from './constants.js';
const ICONS_DATA_URL = 'src/data/icons.json';
export class EvolutionController {
/** @type {Entity[]} */ #entities = [];
/** @type {HTMLElement|null} */ #container = null;
/** @type {number} */ #moveSpeed = DEFAULTS.MOVE_SPEED;
/** @type {ReturnType<typeof setTimeout>|null} */ #spawnTimer = null;
/** @type {number|null} */ #animFrame = null;
/** @type {boolean} */ #running = false;
/** @type {object|null} */ #iconsData = null;
// ── Public API ─────────────────────────────────────────────
/**
* Initialise: load icon data, warm SVG cache, kick off spawning & loop.
*
* @param {HTMLElement} container All entity DOM nodes are appended here.
*/
async init(container) {
this.#container = container;
this.#iconsData = await this.#loadIconsData();
// Warm the SVG cache for the initial icon so first spawn is instant
const initialName = this.#iconsData.spawn.initial;
preloadIcons([initialName]);
this.#scheduleNextSpawn();
this.#startLoop();
}
/**
* Update the movement speed multiplier used by all entities.
* Maps slider value (110) to a physics multiplier around 1.0 at speed 5.
*
* @param {number} speed integer 110
*/
setMoveSpeed(speed) {
this.#moveSpeed = speed;
}
/** Stop all timers and the rAF loop; remove all entities from the DOM. */
stop() {
this.#running = false;
if (this.#spawnTimer !== null) clearTimeout(this.#spawnTimer);
if (this.#animFrame !== null) cancelAnimationFrame(this.#animFrame);
this.#entities.forEach(e => e.destroy());
this.#entities = [];
}
/** Read-only access to current entity count (useful for debugging). */
get entityCount() { return this.#entities.length; }
// ── Spawning ────────────────────────────────────────────────
/**
* Schedule the next spawn after a random delay within the configured range.
* Self-resets after each spawn so the process continues indefinitely.
*/
#scheduleNextSpawn() {
const min = DEFAULTS.SPAWN_DELAY_MIN;
const max = DEFAULTS.SPAWN_DELAY_MAX;
const delay = min + Math.random() * (max - min);
this.#spawnTimer = setTimeout(async () => {
await this.#spawnEntity();
this.#scheduleNextSpawn();
}, delay);
}
/** Create and mount one icon entity at a random viewport position. */
async #spawnEntity() {
if (!this.#container || !this.#iconsData) return;
const name = this.#iconsData.spawn.initial;
const iconMeta = this.#iconsData.icons[name];
const typeMeta = this.#iconsData.types[iconMeta.type];
// Random spawn position — keep a margin so the icon starts fully on-screen
const margin = DEFAULTS.ICON_SIZE * 2;
const vw = window.innerWidth;
const vh = window.innerHeight;
const x = margin + Math.random() * (vw - margin * 2);
const y = margin + Math.random() * (vh - margin * 2);
// Random initial direction, normalised to BASE_SPEED
const angle = Math.random() * Math.PI * 2;
const vx = Math.cos(angle) * DEFAULTS.BASE_SPEED;
const vy = Math.sin(angle) * DEFAULTS.BASE_SPEED;
const entity = new Entity({
name, type: iconMeta.type, color: typeMeta.color,
x, y, vx, vy,
});
await entity.mount(this.#container);
if (entity.alive) {
this.#entities.push(entity);
}
}
// ── Physics loop ────────────────────────────────────────────
#startLoop() {
this.#running = true;
const tick = () => {
if (!this.#running) return;
// speedMultiplier: normalised so that slider=5 → multiplier=1.0
const multiplier = this.#moveSpeed / DEFAULTS.MOVE_SPEED;
for (const entity of this.#entities) {
entity.update(multiplier);
}
this.#animFrame = requestAnimationFrame(tick);
};
this.#animFrame = requestAnimationFrame(tick);
}
// ── Data loading ────────────────────────────────────────────
/** Fetch and return the icons.json configuration. */
async #loadIconsData() {
const res = await fetch(ICONS_DATA_URL);
if (!res.ok) throw new Error(`[EvolutionController] Failed to load ${ICONS_DATA_URL}`);
return res.json();
}
}
+80
View File
@@ -0,0 +1,80 @@
/**
* gradient.js
* Controls the animated gradient background.
* Owns all gradient state and applies it via CSS custom properties on <html>.
*/
import { deriveGradientPair } from '../utils/colorUtils.js';
/** Root element — CSS custom properties live here */
const ROOT = document.documentElement;
/**
* Speed → animation duration mapping.
* speed 1 → ~32 s (very slow, meditative)
* speed 5 → ~19 s (default, comfortable)
* speed 10 → ~3.5 s (fast, energetic)
*
* @param {number} speed integer 110
* @returns {number} duration in seconds
*/
function speedToDuration(speed) {
return Math.round(35 - speed * 3.15);
}
export class GradientController {
/** @type {string} */ #color = '#4d22b3';
/** @type {number} */ #speed = 2;
/** @type {boolean} */ #rotating = false;
/**
* Initialise gradient with a base colour.
* Call once on app start.
*
* @param {string} hex
*/
init(hex) {
this.setColor(hex);
this.setSpeed(this.#speed);
}
/**
* Set the base gradient colour.
* Automatically derives the companion colour and updates the CSS vars.
*
* @param {string} hex
*/
setColor(hex) {
this.#color = hex;
const [c1, c2] = deriveGradientPair(hex);
ROOT.style.setProperty('--color-1', c1);
ROOT.style.setProperty('--color-2', c2);
}
/**
* Set animation speed (1 = slowest, 10 = fastest).
* Updates --anim-duration on the root element.
*
* @param {number} speed integer 110
*/
setSpeed(speed) {
this.#speed = speed;
ROOT.style.setProperty('--anim-duration', `${speedToDuration(speed)}s`);
}
/**
* Enable or disable the clockwise rotation animation.
* Toggles the `.gradient-rotating` class on <body>.
*
* @param {boolean} enabled
*/
toggleRotation(enabled) {
this.#rotating = enabled;
document.body.classList.toggle('gradient-rotating', enabled);
}
// ── Read-only state accessors ──────────────────────────────
get color() { return this.#color; }
get speed() { return this.#speed; }
get rotating() { return this.#rotating; }
}
+63
View File
@@ -0,0 +1,63 @@
/**
* iconLoader.js
* Fetches SVG icon files on demand and caches them in memory.
* Returns normalised SVG strings ready for innerHTML injection.
*
* Note: requires a local HTTP server — fetch() does not work on file:// URLs.
*/
const ICONS_PATH = 'src/icons';
/** @type {Map<string, string>} */
const cache = new Map();
/**
* Normalise an SVG string for safe inline use:
* - Strip the XML declaration (breaks innerHTML injection)
* - Remove fixed width / height attributes (sizing is handled by CSS)
* - Add a neutral aria-hidden so inline SVGs don't pollute the a11y tree
*
* @param {string} raw
* @returns {string}
*/
function normalizeSvg(raw) {
return raw
.replace(/<\?xml[^>]*\?>\s*/i, '') // remove XML declaration
.replace(/<!--[\s\S]*?-->/g, '') // remove XML comments
.replace(/\s+width="[^"]*"/, '') // remove fixed width
.replace(/\s+height="[^"]*"/, '') // remove fixed height
.replace(/<svg/, '<svg aria-hidden="true"') // mark decorative
.trim();
}
/**
* Load and cache an SVG icon by name.
* Subsequent calls for the same name are synchronous (cache hit).
*
* @param {string} name Filename without extension, e.g. 'dna-bold-duotone'
* @returns {Promise<string>} Normalised SVG markup
*/
export async function loadIcon(name) {
if (cache.has(name)) return cache.get(name);
const url = `${ICONS_PATH}/${name}.svg`;
const res = await fetch(url);
if (!res.ok) {
throw new Error(`[iconLoader] Could not load icon "${name}" from ${url} (${res.status})`);
}
const svg = normalizeSvg(await res.text());
cache.set(name, svg);
return svg;
}
/**
* Warm the cache for a list of icon names (non-blocking).
* Call this at startup to avoid fetch latency on first spawn.
*
* @param {string[]} names
*/
export function preloadIcons(names) {
names.forEach(name => loadIcon(name).catch(() => {}));
}
+37
View File
@@ -0,0 +1,37 @@
/**
* main.js
* Application entry point.
*
* Responsibilities:
* - Instantiate all controllers
* - Wire the settings button to the modal
* - Kick off the gradient and evolution system
*
* Keep this file thin — business logic lives in the controllers.
*/
import { GradientController } from './gradient.js';
import { ModalController } from './modal.js';
import { SettingsController } from './settings.js';
import { EvolutionController } from './evolution.js';
import { DEFAULTS } from './constants.js';
// ── Initialise controllers ─────────────────────────────────
const gradient = new GradientController();
const modal = new ModalController();
const evolution = new EvolutionController();
// SettingsController bridges UI → gradient + evolution
// eslint-disable-next-line no-unused-vars
const settings = new SettingsController(gradient, evolution);
// ── Wire up settings button ────────────────────────────────
document.getElementById('settingsBtn').addEventListener('click', (e) => {
modal.open(/** @type {HTMLElement} */ (e.currentTarget));
});
// ── Boot gradient ──────────────────────────────────────────
gradient.init(DEFAULTS.GRADIENT_COLOR);
// ── Boot evolution (async — loads icons.json + warms SVG cache) ──
evolution.init(document.getElementById('evolutionContainer'));
+77
View File
@@ -0,0 +1,77 @@
/**
* modal.js
* Controls the settings modal — open, close, and all dismissal paths.
* Keeps focus management and accessibility in sync with visibility state.
*/
export class ModalController {
/** @type {HTMLElement} */ #overlay;
/** @type {boolean} */ #isOpen = false;
/** @type {HTMLElement|null} */ #triggerEl = null;
constructor() {
this.#overlay = document.getElementById('modalOverlay');
this.#bindEvents();
}
// ── Public API ─────────────────────────────────────────────
/**
* Open the modal.
* Remembers which element triggered it so focus can be restored on close.
*
* @param {HTMLElement} [trigger] the element that opened the modal
*/
open(trigger) {
if (this.#isOpen) return;
this.#triggerEl = trigger ?? document.activeElement;
this.#isOpen = true;
this.#overlay.classList.add('modal-visible');
this.#overlay.setAttribute('aria-hidden', 'false');
// Move focus into the modal after the CSS transition starts
requestAnimationFrame(() => {
const firstFocusable = this.#overlay.querySelector(
'button, input, [tabindex]:not([tabindex="-1"])',
);
firstFocusable?.focus();
});
}
/** Close the modal and restore focus to the trigger element. */
close() {
if (!this.#isOpen) return;
this.#isOpen = false;
this.#overlay.classList.remove('modal-visible');
this.#overlay.setAttribute('aria-hidden', 'true');
this.#triggerEl?.focus();
this.#triggerEl = null;
}
get isOpen() { return this.#isOpen; }
// ── Private ────────────────────────────────────────────────
#bindEvents() {
// Close button
document.getElementById('modalClose')
.addEventListener('click', () => this.close());
// Click outside panel → close
this.#overlay.addEventListener('click', (e) => {
if (e.target === this.#overlay) this.close();
});
// Escape key → close
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.#isOpen) {
e.preventDefault();
this.close();
}
});
}
}
+60
View File
@@ -0,0 +1,60 @@
/**
* settings.js
* Connects the settings UI controls to GradientController and EvolutionController.
* Owns no state of its own — it only reads from the DOM and
* forwards values to the relevant controller.
*/
export class SettingsController {
/** @type {import('./gradient.js').GradientController} */
#gradient;
/** @type {import('./evolution.js').EvolutionController} */
#evolution;
/**
* @param {import('./gradient.js').GradientController} gradient
* @param {import('./evolution.js').EvolutionController} evolution
*/
constructor(gradient, evolution) {
this.#gradient = gradient;
this.#evolution = evolution;
this.#bindEvents();
}
// ── Private ────────────────────────────────────────────────
#bindEvents() {
const colorPicker = /** @type {HTMLInputElement} */ (document.getElementById('colorPicker'));
const colorValue = /** @type {HTMLElement} */ (document.getElementById('colorValue'));
const speedSlider = /** @type {HTMLInputElement} */ (document.getElementById('speedSlider'));
const moveSpeedSlider = /** @type {HTMLInputElement} */ (document.getElementById('moveSpeedSlider'));
const rotToggle = /** @type {HTMLInputElement} */ (document.getElementById('rotationToggle'));
// ── Colour picker ──────────────────────────────────────
// 'input' fires on every pointer move — real-time background preview
colorPicker.addEventListener('input', (e) => {
const hex = /** @type {HTMLInputElement} */ (e.target).value;
colorValue.textContent = hex;
this.#gradient.setColor(hex);
});
// ── Gradient animation speed ───────────────────────────
speedSlider.addEventListener('input', (e) => {
const speed = Number(/** @type {HTMLInputElement} */ (e.target).value);
this.#gradient.setSpeed(speed);
});
// ── Icon movement speed ────────────────────────────────
moveSpeedSlider.addEventListener('input', (e) => {
const speed = Number(/** @type {HTMLInputElement} */ (e.target).value);
this.#evolution.setMoveSpeed(speed);
});
// ── Rotation toggle ────────────────────────────────────
rotToggle.addEventListener('change', (e) => {
const enabled = /** @type {HTMLInputElement} */ (e.target).checked;
this.#gradient.toggleRotation(enabled);
});
}
}
+58
View File
@@ -0,0 +1,58 @@
/* ════════════════════════════════════════════════════════════
gradient.css — animated background gradient + rotation
════════════════════════════════════════════════════════════ */
/* ── Register animatable CSS custom property (Houdini) ───── */
/* Allows smooth CSS animation of the gradient angle. */
/* Fallback: browsers without @property support still show */
/* the gradient, just without animated rotation. */
@property --gradient-angle {
syntax: '<angle>';
inherits: false;
initial-value: 135deg;
}
/* ── Keyframe: position-based flow (always active) ───────── */
/* Creates a fluid "breathing" motion by shifting the large */
/* (300%) background across the viewport. */
@keyframes gradientFlow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* ── Keyframe: clockwise angle rotation (toggle-gated) ───── */
/* Adds 360° to the starting angle for one full CW rotation. */
@keyframes gradientRotate {
from { --gradient-angle: 135deg; }
to { --gradient-angle: 495deg; }
}
/* ── Background application ──────────────────────────────── */
body {
background: linear-gradient(
var(--gradient-angle),
var(--color-1) 0%,
var(--color-2) 50%,
var(--color-1) 100%
);
background-size: 300% 300%;
/*
* Both animations are always declared here.
* gradientRotate starts PAUSED so it costs nothing until
* the user enables rotation — then JS adds .gradient-rotating
* which flips its play-state to running.
* This avoids restarting gradientFlow when the class toggles.
*/
animation:
gradientFlow var(--anim-duration) ease-in-out infinite,
gradientRotate var(--rotation-duration) linear infinite;
animation-play-state: running, paused;
}
/* ── Rotation enabled ────────────────────────────────────── */
body.gradient-rotating {
animation-play-state: running, running;
}
+72
View File
@@ -0,0 +1,72 @@
/* ════════════════════════════════════════════════════════════
icons.css — evolution container and floating icon entities
════════════════════════════════════════════════════════════ */
/* ── Evolution stage ─────────────────────────────────────── */
/*
* Fixed full-viewport layer that holds all icon entities.
* pointer-events: none so it never blocks page interaction.
*/
.evolution-container {
position: fixed;
inset: 0;
z-index: 10;
pointer-events: none;
overflow: hidden; /* clip any entity that overshoots during bounce */
}
/* ── Icon entity — outer (physics position) ──────────────── */
/*
* JS writes: el.style.transform = 'translate(Xpx, Ypx)' each frame.
* Size is fixed; position is driven entirely by the JS physics loop.
* will-change: transform hints the browser to promote this to its own
* compositor layer for smooth, layout-free animation.
*/
.icon-entity {
position: absolute;
top: 0;
left: 0;
width: 24px;
height: 24px;
will-change: transform;
/* colour is set inline by Entity.mount() and inherited by the SVG */
}
/* ── Icon entity — inner (appear scale animation) ────────── */
/*
* CSS `scale` is a standalone transform property — independent from the
* parent's `transform: translate()`. This means the appear transition
* runs without interfering with the per-frame position updates above.
*
* Easing: cubic-bezier(0.34, 1.56, 0.64, 1) is a spring-style curve
* that overshoots slightly, giving the icon a satisfying "pop" entry.
*/
.icon-entity__body {
width: 100%;
height: 100%;
/* Start as an invisible dot */
scale: 0;
opacity: 0;
transition:
scale 0.6s cubic-bezier(0.34, 1.56, 0.64, 1),
opacity 0.25s ease;
/* Subtle shadow so icons are readable over any gradient colour */
filter: drop-shadow(0 1px 6px rgba(0, 0, 0, 0.35));
}
/* Alive state — triggered by JS setting data-state="alive" */
.icon-entity__body[data-state="alive"] {
scale: 1;
opacity: 1;
}
/* ── SVG inside the body ─────────────────────────────────── */
.icon-entity__body svg {
width: 100%;
height: 100%;
display: block;
/* SVGs use currentColor — colour is inherited from .icon-entity */
}
+106
View File
@@ -0,0 +1,106 @@
/* ════════════════════════════════════════════════════════════
main.css — reset, design tokens, global layout, settings btn
════════════════════════════════════════════════════════════ */
/* ── Reset ───────────────────────────────────────────────── */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* ── Design Tokens ───────────────────────────────────────── */
:root {
/* Gradient colors — overridden immediately by JS on load */
--color-1: #1a7040;
--color-2: #4db8c4;
/* Animation timing — overridden by JS (default = speed 5) */
--anim-duration: 19s;
--rotation-duration: 22s;
/* Modal surface */
--modal-surface: rgba(10, 10, 14, 0.82);
--modal-border: rgba(255, 255, 255, 0.08);
--modal-divider: rgba(255, 255, 255, 0.06);
/* Text */
--text-hi: rgba(255, 255, 255, 0.92);
--text-lo: rgba(255, 255, 255, 0.42);
/* Misc surface tints */
--surface-tint: rgba(255, 255, 255, 0.10);
/* Font */
--font-mono: 'DM Mono', 'Courier New', monospace;
}
/* ── Body ────────────────────────────────────────────────── */
body {
min-height: 100dvh;
font-family: var(--font-mono);
overflow: hidden;
}
/* ── Page content area (grows with future features) ──────── */
.page-main {
position: relative;
z-index: 1;
min-height: 100dvh;
display: flex;
align-items: center;
justify-content: center;
}
/* ── Settings Button ─────────────────────────────────────── */
.settings-btn {
position: fixed;
top: 22px;
right: 26px;
z-index: 100;
/* Strip all button decoration */
appearance: none;
background: transparent;
border: none;
outline: none;
padding: 0;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
/* Shape & layout */
width: 38px;
height: 38px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
/* Typography */
font-family: var(--font-mono);
font-size: 1.05rem;
font-weight: 500;
line-height: 1;
letter-spacing: -0.02em;
color: rgba(255, 255, 255, 0.65);
transition:
color 0.18s ease,
background 0.18s ease;
}
.settings-btn:hover {
color: rgba(255, 255, 255, 0.95);
background: rgba(255, 255, 255, 0.14);
}
.settings-btn:active {
color: #fff;
background: rgba(255, 255, 255, 0.22);
}
/* Focus-visible ring for keyboard nav */
.settings-btn:focus-visible {
outline: 2px solid rgba(255, 255, 255, 0.5);
outline-offset: 2px;
}
+335
View File
@@ -0,0 +1,335 @@
/* ════════════════════════════════════════════════════════════
modal.css — overlay, panel, and all settings controls
════════════════════════════════════════════════════════════ */
/* ── Overlay ─────────────────────────────────────────────── */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 200;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.28);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
/* Hidden by default */
opacity: 0;
pointer-events: none;
transition: opacity 0.26s ease;
}
.modal-overlay.modal-visible {
opacity: 1;
pointer-events: all;
}
/* ── Panel ───────────────────────────────────────────────── */
.modal {
width: 310px;
background: var(--modal-surface);
border: 1px solid var(--modal-border);
border-radius: 18px;
overflow: hidden;
/* Glass blur effect */
backdrop-filter: blur(48px) saturate(1.5);
-webkit-backdrop-filter: blur(48px) saturate(1.5);
box-shadow:
0 32px 80px rgba(0, 0, 0, 0.45),
0 2px 12px rgba(0, 0, 0, 0.30);
/* Entry animation: rises and scales into place */
transform: translateY(18px) scale(0.96);
transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform;
}
.modal-overlay.modal-visible .modal {
transform: translateY(0) scale(1);
}
/* ── Header ──────────────────────────────────────────────── */
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 20px 15px;
border-bottom: 1px solid var(--modal-divider);
}
.modal-title {
font-family: var(--font-mono);
font-size: 0.65rem;
font-weight: 400;
letter-spacing: 0.18em;
text-transform: lowercase;
color: var(--text-lo);
user-select: none;
}
.modal-close {
appearance: none;
background: transparent;
border: none;
outline: none;
cursor: pointer;
padding: 0;
-webkit-tap-highlight-color: transparent;
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.15rem;
line-height: 1;
color: var(--text-lo);
transition:
color 0.18s ease,
background 0.18s ease;
}
.modal-close:hover {
color: var(--text-hi);
background: var(--surface-tint);
}
.modal-close:focus-visible {
outline: 2px solid rgba(255, 255, 255, 0.4);
outline-offset: 2px;
}
/* ── Body ────────────────────────────────────────────────── */
.modal-body {
padding: 20px;
display: flex;
flex-direction: column;
gap: 22px;
}
/* ── Setting group (label + control) ─────────────────────── */
.setting-group {
display: flex;
flex-direction: column;
gap: 10px;
}
.setting-label {
font-family: var(--font-mono);
font-size: 0.60rem;
font-weight: 400;
letter-spacing: 0.15em;
text-transform: lowercase;
color: var(--text-lo);
user-select: none;
}
/* ══════════════════════════════════════════════════════════
Color Picker
══════════════════════════════════════════════════════════ */
.color-input-wrapper {
display: flex;
align-items: center;
gap: 14px;
}
.color-picker {
width: 52px;
height: 30px;
padding: 3px;
border: 1px solid var(--modal-border);
border-radius: 8px;
background: transparent;
cursor: pointer;
outline: none;
transition: border-color 0.18s ease;
}
.color-picker:hover {
border-color: rgba(255, 255, 255, 0.22);
}
/* Remove inner chrome swatch border on WebKit */
.color-picker::-webkit-color-swatch-wrapper { padding: 0; }
.color-picker::-webkit-color-swatch {
border: none;
border-radius: 5px;
}
.color-picker::-moz-color-swatch {
border: none;
border-radius: 5px;
}
.color-value {
font-family: var(--font-mono);
font-size: 0.72rem;
letter-spacing: 0.06em;
color: var(--text-hi);
}
/* ══════════════════════════════════════════════════════════
Speed Slider
══════════════════════════════════════════════════════════ */
.slider-wrapper {
display: flex;
align-items: center;
gap: 10px;
}
.slider-label {
font-family: var(--font-mono);
font-size: 0.56rem;
letter-spacing: 0.09em;
color: var(--text-lo);
user-select: none;
min-width: 26px;
}
.slider-label:last-child {
text-align: right;
}
/* Reset */
.slider {
flex: 1;
-webkit-appearance: none;
appearance: none;
background: transparent;
border: none;
outline: none;
cursor: pointer;
height: 18px; /* tap target height */
display: flex;
align-items: center;
}
/* Track — WebKit */
.slider::-webkit-slider-runnable-track {
height: 2px;
border-radius: 1px;
background: rgba(255, 255, 255, 0.15);
}
/* Thumb — WebKit */
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
margin-top: -6px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.88);
cursor: pointer;
transition:
transform 0.15s ease,
background 0.15s ease;
}
.slider::-webkit-slider-thumb:hover {
transform: scale(1.35);
background: #fff;
}
/* Track — Firefox */
.slider::-moz-range-track {
height: 2px;
border-radius: 1px;
background: rgba(255, 255, 255, 0.15);
}
/* Thumb — Firefox */
.slider::-moz-range-thumb {
width: 14px;
height: 14px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.88);
border: none;
cursor: pointer;
}
/* ══════════════════════════════════════════════════════════
Toggle Switch
══════════════════════════════════════════════════════════ */
.toggle-wrapper {
display: flex;
align-items: center;
gap: 10px;
}
.toggle-label {
font-family: var(--font-mono);
font-size: 0.56rem;
letter-spacing: 0.09em;
color: var(--text-lo);
user-select: none;
}
/* The <label> wrapping the whole toggle */
.toggle {
display: inline-flex;
cursor: pointer;
position: relative;
}
/* Hide native checkbox, keep it accessible */
.toggle input[type="checkbox"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
pointer-events: none;
}
/* Track pill */
.toggle-track {
position: relative;
display: block;
width: 42px;
height: 24px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.08);
transition:
background 0.28s ease,
border-color 0.28s ease;
}
/* Checked: brighten track */
.toggle input:checked + .toggle-track {
background: rgba(255, 255, 255, 0.34);
border-color: rgba(255, 255, 255, 0.20);
}
/* Thumb dot */
.toggle-thumb {
position: absolute;
top: 4px;
left: 4px;
width: 14px;
height: 14px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.82);
transition:
transform 0.28s cubic-bezier(0.16, 1, 0.3, 1),
background 0.28s ease;
}
/* Checked: slide thumb to the right */
.toggle input:checked + .toggle-track .toggle-thumb {
transform: translateX(18px);
background: #fff;
}
/* Focus ring on the label for keyboard nav */
.toggle input:focus-visible + .toggle-track {
outline: 2px solid rgba(255, 255, 255, 0.45);
outline-offset: 2px;
}
+117
View File
@@ -0,0 +1,117 @@
/**
* colorUtils.js
* Pure colour-math utilities used by the gradient system.
* No DOM dependencies — safe to import anywhere.
*/
/**
* Convert a CSS hex colour string to HSL components.
*
* @param {string} hex e.g. '#2d9e6b'
* @returns {{ h: number, s: number, l: number }}
* h ∈ [0, 360), s ∈ [0, 100], l ∈ [0, 100]
*/
export function hexToHSL(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
const l = (max + min) / 2;
let h = 0;
let s = 0;
if (delta !== 0) {
s = delta / (1 - Math.abs(2 * l - 1));
switch (max) {
case r: h = ((g - b) / delta + (g < b ? 6 : 0)) / 6; break;
case g: h = ((b - r) / delta + 2) / 6; break;
case b: h = ((r - g) / delta + 4) / 6; break;
}
}
return { h: h * 360, s: s * 100, l: l * 100 };
}
/**
* Convert HSL components to a CSS hex colour string.
*
* @param {number} h hue (0360, wraps automatically)
* @param {number} s saturation (0100, clamped)
* @param {number} l lightness (0100, clamped)
* @returns {string} e.g. '#1d7c51'
*/
export function hslToHex(h, s, l) {
// Normalise inputs
h = ((h % 360) + 360) % 360;
s = Math.max(0, Math.min(100, s));
l = Math.max(0, Math.min(100, l));
const sn = s / 100;
const ln = l / 100;
/** @param {number} p @param {number} q @param {number} t */
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
let r, g, b;
if (sn === 0) {
r = g = b = ln; // achromatic
} else {
const q = ln < 0.5 ? ln * (1 + sn) : ln + sn - ln * sn;
const p = 2 * ln - q;
r = hue2rgb(p, q, h / 360 + 1 / 3);
g = hue2rgb(p, q, h / 360);
b = hue2rgb(p, q, h / 360 - 1 / 3);
}
return '#' + [r, g, b]
.map(x => Math.round(x * 255).toString(16).padStart(2, '0'))
.join('');
}
/**
* Derive a harmonious two-colour gradient pair from a single base colour.
*
* Strategy — "same palette, analogous shift":
* • Color 1: deepen the base (↑ saturation, ↓ lightness) for a rich anchor
* • Color 2: shift hue +35° into the adjacent palette zone, lighten it
* so it reads as a natural sibling, not a contrasting accent
*
* Examples:
* green (#2d9e6b) → deep forest green + soft teal
* blue (#2d6b9e) → deep navy + sky periwinkle
* orange (#e07030) → burnt sienna + warm amber
*
* @param {string} baseHex
* @returns {[string, string]} [color1, color2]
*/
export function deriveGradientPair(baseHex) {
const { h, s, l } = hexToHSL(baseHex);
// Color 1 — darker, richer anchor
const c1 = hslToHex(
h,
Math.min(s * 1.15, 100),
Math.max(l * 0.62, 8),
);
// Color 2 — analogous hue shift, lifted lightness
const c2 = hslToHex(
(h + 35) % 360,
s * 0.88,
Math.min(l * 1.48, 78),
);
return [c1, c2];
}