Deploy FitCheck UI brick
Browse files- README.md +48 -5
- app.py +274 -0
- requirements.txt +1 -0
- static/app.js +283 -0
- static/icons.js +54 -0
- static/index.html +155 -0
- static/style.css +376 -0
README.md
CHANGED
|
@@ -1,13 +1,56 @@
|
|
| 1 |
---
|
| 2 |
title: FitCheck
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: FitCheck
|
| 3 |
+
emoji: ✅
|
| 4 |
+
colorFrom: indigo
|
| 5 |
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 6.16.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
short_description: Honest, plain answers about what AI your computer can run
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# FitCheck
|
| 15 |
+
|
| 16 |
+
**What AI can your computer actually run?**
|
| 17 |
+
|
| 18 |
+
Tell FitCheck about your computer in plain words. It tells you honestly what AI
|
| 19 |
+
you can run today, from chatbots to object detection, what it will feel like,
|
| 20 |
+
and exactly how to start. No jargon, no hype, no fake promises.
|
| 21 |
+
|
| 22 |
+
Built for the [Build Small hackathon](https://huggingface.co/build-small-hackathon)
|
| 23 |
+
(Backyard AI track).
|
| 24 |
+
|
| 25 |
+
## What makes it different
|
| 26 |
+
|
| 27 |
+
- **Plain words.** Built for people who do not know what VRAM or quantisation
|
| 28 |
+
means, and it explains those words when they appear.
|
| 29 |
+
- **The whole realm, not just chatbots.** Goals span LLMs and specialised deep
|
| 30 |
+
learning: object detection (YOLO), segmentation, 6-DoF pose (FoundationPose),
|
| 31 |
+
image and video generation, speech-to-text, embeddings, fine-tuning, plus a
|
| 32 |
+
custom "describe it" option.
|
| 33 |
+
- **Honest by design.** Three plain bands (Runs great, Tight but works, Will not
|
| 34 |
+
fit) with conservative estimates that under-promise rather than over.
|
| 35 |
+
|
| 36 |
+
## How it is built
|
| 37 |
+
|
| 38 |
+
A hand-built HTML, CSS and JS frontend (no framework, no build step) served by
|
| 39 |
+
Gradio server mode (`gr.Server`, which is a FastAPI app). The interface talks to
|
| 40 |
+
a single connector, `POST /api/advise`.
|
| 41 |
+
|
| 42 |
+
> Note: this is the UI view. The advice endpoint currently returns input-aware
|
| 43 |
+
> placeholder data so the interface is complete and live. A deterministic
|
| 44 |
+
> calculation engine and a small local model plug into the same `/api/advise`
|
| 45 |
+
> contract next, with no frontend changes.
|
| 46 |
+
|
| 47 |
+
## Run it locally
|
| 48 |
+
|
| 49 |
+
```
|
| 50 |
+
python -m venv .venv
|
| 51 |
+
.venv\Scripts\activate
|
| 52 |
+
pip install -r requirements.txt
|
| 53 |
+
python app.py
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
Then open http://127.0.0.1:7860/ (add `?go` for a sample result).
|
app.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
FitCheck — what AI can your computer actually run?
|
| 3 |
+
|
| 4 |
+
This file is the UI BRICK's backend: a `gr.Server` (which IS a FastAPI app)
|
| 5 |
+
that serves the hand-built frontend in static/ and exposes ONE connector
|
| 6 |
+
endpoint, /api/advise.
|
| 7 |
+
|
| 8 |
+
IMPORTANT: /api/advise currently returns input-aware *placeholder* results so
|
| 9 |
+
the interface is complete and feels alive (the gauge moves, bands change). The
|
| 10 |
+
real deterministic engine (a separate brick) will plug into this same contract
|
| 11 |
+
later — the frontend won't need to change. Mock logic is fenced below.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import re
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
import gradio as gr
|
| 18 |
+
from fastapi.responses import FileResponse
|
| 19 |
+
from fastapi.staticfiles import StaticFiles
|
| 20 |
+
from pydantic import BaseModel
|
| 21 |
+
|
| 22 |
+
CATALOGUE_VERSION = "2026-06-07"
|
| 23 |
+
STATIC = Path(__file__).parent / "static"
|
| 24 |
+
|
| 25 |
+
app = gr.Server()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# ==========================================================================
|
| 29 |
+
# PLACEHOLDER engine — to be replaced by the real calculation brick.
|
| 30 |
+
# Numbers are plausible and conservative but are NOT the audited engine yet.
|
| 31 |
+
# ==========================================================================
|
| 32 |
+
|
| 33 |
+
_COLORS = {"model": "#818CF8", "chat": "#A5F3C4", "work": "#868E9C"}
|
| 34 |
+
|
| 35 |
+
# Which broad family each goal belongs to (drives models/tools/commands).
|
| 36 |
+
_FAMILY = {
|
| 37 |
+
"chat": "llm", "writing": "llm", "coding": "llm", "agents": "llm",
|
| 38 |
+
"rag": "llm", "translate": "llm", "finetune": "llm",
|
| 39 |
+
"detect": "vision", "segment": "vision", "pose": "vision",
|
| 40 |
+
"classify": "vision", "depth": "vision", "ocr": "vision", "train-vision": "vision",
|
| 41 |
+
"imagegen": "gen", "inpaint": "gen", "upscale": "gen", "videogen": "gen", "bgremove": "gen",
|
| 42 |
+
"stt": "audio", "tts": "audio", "music": "audio",
|
| 43 |
+
"embed": "data", "forecast": "data", "tabular": "data",
|
| 44 |
+
"custom": "llm",
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
# variant = (name, description, memory-need-GB at sensible setting, setting label)
|
| 48 |
+
_VARIANTS = {
|
| 49 |
+
"llm": [
|
| 50 |
+
("Huge (70B)", "Top open quality. Serious hardware only.", 42, "4-bit"),
|
| 51 |
+
("Very large (32B)", "Near-premium. Wants a strong GPU.", 20, "4-bit"),
|
| 52 |
+
("Large (14B)", "Noticeably smarter and more reliable.", 9, "4-bit"),
|
| 53 |
+
("Medium (7-9B)", "Solid all-rounder: chat, coding, reasoning.", 5.5, "4-bit"),
|
| 54 |
+
("Small (3-4B)", "Surprisingly capable everyday assistant.", 2.5, "4-bit"),
|
| 55 |
+
("Tiny (~1B)", "Quick simple chat. Runs on almost anything.", 1.2, "4-bit"),
|
| 56 |
+
],
|
| 57 |
+
"vision": [
|
| 58 |
+
("Extra-large model", "Highest accuracy, slowest.", 6.0, "full"),
|
| 59 |
+
("Large model", "Great accuracy for real work.", 3.5, "full"),
|
| 60 |
+
("Medium model", "Balanced accuracy and speed.", 2.0, "full"),
|
| 61 |
+
("Small model", "Fast, good for live video.", 1.2, "full"),
|
| 62 |
+
("Nano model", "Real-time even on weak hardware.", 0.6, "full"),
|
| 63 |
+
],
|
| 64 |
+
"gen": [
|
| 65 |
+
("Flux.1 (dev)", "State-of-the-art image quality.", 18, "full"),
|
| 66 |
+
("Flux.1 (schnell)", "Near-top quality, much faster.", 12, "8-bit"),
|
| 67 |
+
("SDXL", "Excellent 1024px images.", 8, "full"),
|
| 68 |
+
("SDXL Turbo", "Fast SDXL, fewer steps.", 7, "full"),
|
| 69 |
+
("Stable Diffusion 1.5", "Light, fast, huge community.", 3.5, "full"),
|
| 70 |
+
],
|
| 71 |
+
"audio": [
|
| 72 |
+
("Whisper large-v3", "Best transcription accuracy.", 5.0, "full"),
|
| 73 |
+
("Whisper medium", "Strong accuracy, lighter.", 3.0, "full"),
|
| 74 |
+
("Whisper small", "Good for clear audio, fast.", 1.5, "full"),
|
| 75 |
+
("Whisper base", "Quick drafts, light hardware.", 0.8, "full"),
|
| 76 |
+
],
|
| 77 |
+
"data": [
|
| 78 |
+
("Large embedder", "Best search relevance.", 2.5, "full"),
|
| 79 |
+
("Base embedder", "Great balance for most search.", 1.2, "full"),
|
| 80 |
+
("Small embedder", "Fast, runs on anything.", 0.5, "full"),
|
| 81 |
+
],
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
_TOOLS = {
|
| 85 |
+
"llm": [
|
| 86 |
+
("Ollama", "Type one line; it downloads and runs the model for you.", "Get it from ollama.com", "Easiest"),
|
| 87 |
+
("LM Studio", "A point-and-click app with a chat window, no commands.", "Download from lmstudio.ai", "Easy"),
|
| 88 |
+
("llama.cpp", "The lightweight engine under the hood. Runs GGUF files directly.", "Releases on GitHub", "Advanced"),
|
| 89 |
+
],
|
| 90 |
+
"vision": [
|
| 91 |
+
("Ultralytics", "One pip install, then detect objects from a webcam or file.", "pip install ultralytics", "Easiest"),
|
| 92 |
+
("Roboflow", "Browser tools to label data and run models, little code.", "roboflow.com", "Easy"),
|
| 93 |
+
("PyTorch", "Full control for custom pipelines and training.", "pytorch.org", "Advanced"),
|
| 94 |
+
],
|
| 95 |
+
"gen": [
|
| 96 |
+
("Fooocus", "Image generation that 'just works': one folder, double-click.", "Download from GitHub", "Easiest"),
|
| 97 |
+
("ComfyUI", "Powerful visual node editor for image/video pipelines.", "Download from GitHub", "Moderate"),
|
| 98 |
+
("Automatic1111", "The classic full-featured Stable Diffusion web UI.", "Download from GitHub", "Moderate"),
|
| 99 |
+
],
|
| 100 |
+
"audio": [
|
| 101 |
+
("faster-whisper", "Fast, accurate transcription with a tiny install.", "pip install faster-whisper", "Easiest"),
|
| 102 |
+
("whisper.cpp", "Runs Whisper efficiently on CPU and small machines.", "Build from GitHub", "Advanced"),
|
| 103 |
+
],
|
| 104 |
+
"data": [
|
| 105 |
+
("sentence-transformers", "Turn text into searchable vectors in a few lines.", "pip install sentence-transformers", "Easiest"),
|
| 106 |
+
("Chroma", "A simple local database to store and search those vectors.", "pip install chromadb", "Easy"),
|
| 107 |
+
],
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
_COMMANDS = {
|
| 111 |
+
"llm": [
|
| 112 |
+
("Easy way (Ollama)", "ollama run llama3.1:8b"),
|
| 113 |
+
("Power way (llama.cpp)", "llama-server -hf bartowski/Meta-Llama-3.1-8B-Instruct-GGUF:Q4_K_M -c 4096"),
|
| 114 |
+
],
|
| 115 |
+
"vision": [
|
| 116 |
+
("Install", "pip install ultralytics"),
|
| 117 |
+
("Detect from your webcam", "yolo predict model=yolo11n.pt source=0 show=True"),
|
| 118 |
+
],
|
| 119 |
+
"gen": [
|
| 120 |
+
("Easiest (Fooocus)", "# Download Fooocus, unzip, double-click run.bat"),
|
| 121 |
+
("Power (ComfyUI)", "git clone https://github.com/comfyanonymous/ComfyUI && cd ComfyUI && python main.py"),
|
| 122 |
+
],
|
| 123 |
+
"audio": [
|
| 124 |
+
("Install", "pip install faster-whisper"),
|
| 125 |
+
("Transcribe a file", 'python -c "from faster_whisper import WhisperModel; m=WhisperModel(\'small\'); print(list(m.transcribe(\'audio.mp3\')[0]))"'),
|
| 126 |
+
],
|
| 127 |
+
"data": [
|
| 128 |
+
("Install", "pip install sentence-transformers"),
|
| 129 |
+
("Make searchable vectors", 'python -c "from sentence_transformers import SentenceTransformer as S; print(S(\'all-MiniLM-L6-v2\').encode(\'hello world\').shape)"'),
|
| 130 |
+
],
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def _parse_vram(label: str) -> float:
|
| 135 |
+
m = re.search(r"(\d+(?:\.\d+)?)\s*GB", label or "")
|
| 136 |
+
return float(m.group(1)) if m else 0.0
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def _budgets(p: dict):
|
| 140 |
+
ram = float(p.get("ram_gb") or 16)
|
| 141 |
+
provider = p.get("provider", "none")
|
| 142 |
+
computer = p.get("computer", "Windows laptop")
|
| 143 |
+
|
| 144 |
+
if provider == "apple":
|
| 145 |
+
fast = round(ram * 0.70, 1)
|
| 146 |
+
return fast, fast, ram, True
|
| 147 |
+
|
| 148 |
+
vram = p.get("vram_gb")
|
| 149 |
+
if not vram:
|
| 150 |
+
vram = _parse_vram(p.get("gpu", ""))
|
| 151 |
+
vram = float(vram or 0)
|
| 152 |
+
|
| 153 |
+
fast = round(vram * 0.85, 1)
|
| 154 |
+
reserve = 1.0 if "Mini PC" in computer else (3.0 if "desktop" in computer.lower() or computer == "Mac" else 3.5)
|
| 155 |
+
total = round(vram + max(0.0, ram - reserve) * 0.9, 1)
|
| 156 |
+
return fast, vram, total, False
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def advise_mock(p: dict) -> dict:
|
| 160 |
+
fam = _FAMILY.get(p.get("usecase", "chat"), "llm")
|
| 161 |
+
variants = _VARIANTS[fam]
|
| 162 |
+
fast, _vram, total, is_apple = _budgets(p)
|
| 163 |
+
is_finetune = p.get("usecase") in ("finetune", "train-vision")
|
| 164 |
+
factor = 2.4 if is_finetune else 1.0
|
| 165 |
+
|
| 166 |
+
# Classify each variant.
|
| 167 |
+
def verdict_for(need):
|
| 168 |
+
need *= factor
|
| 169 |
+
if fast >= 1 and need <= fast * 0.9:
|
| 170 |
+
return "great", need
|
| 171 |
+
if need <= total * 0.9:
|
| 172 |
+
return "tight", need
|
| 173 |
+
return "no", need
|
| 174 |
+
|
| 175 |
+
options = []
|
| 176 |
+
for name, desc, need, setting in variants:
|
| 177 |
+
vd, real_need = verdict_for(need)
|
| 178 |
+
feel = {"great": "Snappy on your graphics card",
|
| 179 |
+
"tight": "Runs, but slower (uses normal memory)",
|
| 180 |
+
"no": "Not enough memory"}[vd]
|
| 181 |
+
options.append({
|
| 182 |
+
"verdict": vd, "model": name, "desc": desc,
|
| 183 |
+
"setting": setting, "memory": ("—" if vd == "no" else f"{round(real_need,1):g} GB"),
|
| 184 |
+
"feel": feel,
|
| 185 |
+
})
|
| 186 |
+
|
| 187 |
+
# Headline = best that runs great, else best that's tight, else smallest.
|
| 188 |
+
great = [o for o in options if o["verdict"] == "great"]
|
| 189 |
+
tight = [o for o in options if o["verdict"] == "tight"]
|
| 190 |
+
head = great[0] if great else (tight[0] if tight else options[-1])
|
| 191 |
+
hv = head["verdict"]
|
| 192 |
+
need_gb = float(re.search(r"[\d.]+", head["memory"]).group()) if head["memory"] != "—" else verdict_for(variants[-1][2])[1]
|
| 193 |
+
|
| 194 |
+
if is_apple and hv == "great":
|
| 195 |
+
where = "on your Mac"
|
| 196 |
+
elif hv == "great" and fast >= 1:
|
| 197 |
+
where = "on your graphics card"
|
| 198 |
+
elif hv == "tight":
|
| 199 |
+
where = "using your computer's memory"
|
| 200 |
+
else:
|
| 201 |
+
where = ""
|
| 202 |
+
verdict_word = {"great": "Runs great", "tight": "Tight, but works", "no": "Won't fit"}[hv]
|
| 203 |
+
if hv == "great":
|
| 204 |
+
headline = f"Yes, you can run the {head['model']} {where}, today."
|
| 205 |
+
elif hv == "tight":
|
| 206 |
+
headline = f"Sort of. The {head['model']} will run {where}, with trade-offs."
|
| 207 |
+
else:
|
| 208 |
+
headline = "This goal is a stretch on this machine. Here's the honest picture."
|
| 209 |
+
|
| 210 |
+
detail = (f"For this goal, the sweet spot is a <b>{head['model']}</b> model "
|
| 211 |
+
f"at the <b>{head['setting']}</b> setting. {head['desc']} "
|
| 212 |
+
f"It needs about <b>{round(need_gb,1):g} GB</b>, and you have roughly "
|
| 213 |
+
f"<b>{fast:g} GB</b> fast / <b>{total:g} GB</b> total to work with.")
|
| 214 |
+
note = ("Fine-tuning needs roughly 2 to 3 times the memory of just using a model. That's baked in above."
|
| 215 |
+
if is_finetune else "")
|
| 216 |
+
|
| 217 |
+
# Gauge
|
| 218 |
+
scale = max(total, need_gb, 1) * 1.05
|
| 219 |
+
gauge = {
|
| 220 |
+
"need_gb": f"{round(need_gb,1):g} GB needed",
|
| 221 |
+
"fast_gb": f"{fast:g} GB", "total_gb": f"{total:g} GB",
|
| 222 |
+
"fill_pct": round(need_gb / scale * 100, 1),
|
| 223 |
+
"mark_pct": round(fast / scale * 100, 1),
|
| 224 |
+
"breakdown": [
|
| 225 |
+
{"label": f"Model {round(need_gb*0.8,1):g} GB", "color": _COLORS["model"]},
|
| 226 |
+
{"label": f"Working space {round(need_gb*0.2,1):g} GB", "color": _COLORS["work"]},
|
| 227 |
+
],
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
tools = [{"name": n, "what": w, "install": i, "tag": t} for n, w, i, t in _TOOLS[fam]]
|
| 231 |
+
cmd_intro = ("These get you a running model in minutes. Pick the easy one or the power one; "
|
| 232 |
+
"they do the same job.")
|
| 233 |
+
commands = {"intro": cmd_intro,
|
| 234 |
+
"items": [{"label": l, "code": c} for l, c in _COMMANDS[fam]]}
|
| 235 |
+
|
| 236 |
+
return {
|
| 237 |
+
"catalogue_version": CATALOGUE_VERSION,
|
| 238 |
+
"verdict": hv, "verdict_word": verdict_word,
|
| 239 |
+
"headline": headline, "detail": detail, "note": note,
|
| 240 |
+
"gauge": gauge, "options": options, "tools": tools, "commands": commands,
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
# ==========================================================================
|
| 245 |
+
# Connector endpoint + static frontend
|
| 246 |
+
# ==========================================================================
|
| 247 |
+
|
| 248 |
+
class AdviseIn(BaseModel):
|
| 249 |
+
computer: str = "Windows laptop"
|
| 250 |
+
ram_gb: float | None = 16
|
| 251 |
+
provider: str = "none"
|
| 252 |
+
gpu: str = ""
|
| 253 |
+
vram_gb: float | None = None
|
| 254 |
+
paste: str = ""
|
| 255 |
+
usecase: str = "chat"
|
| 256 |
+
custom: str = ""
|
| 257 |
+
priority: str = "balanced"
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
@app.post("/api/advise")
|
| 261 |
+
def api_advise(payload: AdviseIn):
|
| 262 |
+
return advise_mock(payload.model_dump())
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
app.mount("/static", StaticFiles(directory=STATIC), name="static")
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
@app.get("/")
|
| 269 |
+
def index():
|
| 270 |
+
return FileResponse(STATIC / "index.html")
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
if __name__ == "__main__":
|
| 274 |
+
app.launch(server_name="0.0.0.0", server_port=7860)
|
requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
gradio==6.16.0
|
static/app.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ============================================================
|
| 2 |
+
FitCheck — frontend logic (UI brick)
|
| 3 |
+
Gathers inputs, calls the /api/advise connector, renders results.
|
| 4 |
+
The backend currently returns input-aware PLACEHOLDER data; the
|
| 5 |
+
real engine plugs into the same /api/advise contract later.
|
| 6 |
+
============================================================ */
|
| 7 |
+
|
| 8 |
+
// ---- Inline icon helpers (icons.js loads first) --------------------------
|
| 9 |
+
function ic(name) { return (window.ICONS && window.ICONS[name]) || ""; }
|
| 10 |
+
function hydrate(root) {
|
| 11 |
+
(root || document).querySelectorAll("[data-ic]").forEach(el => {
|
| 12 |
+
if (!el.dataset.done) { el.innerHTML = ic(el.dataset.ic); el.dataset.done = "1"; }
|
| 13 |
+
});
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
// ---- Use-case taxonomy: the full realm, not just LLMs --------------------
|
| 17 |
+
const USE_CASES = [
|
| 18 |
+
{ icon: "cat-text", name: "Text & chat", items: [
|
| 19 |
+
{ id: "chat", icon: "chat", label: "Chatbot / assistant" },
|
| 20 |
+
{ id: "writing", icon: "writing", label: "Writing & summarising" },
|
| 21 |
+
{ id: "coding", icon: "coding", label: "Coding help" },
|
| 22 |
+
{ id: "agents", icon: "agents", label: "Agents & tool use" },
|
| 23 |
+
{ id: "rag", icon: "rag", label: "Chat with my documents" },
|
| 24 |
+
{ id: "translate", icon: "translate", label: "Translation" },
|
| 25 |
+
]},
|
| 26 |
+
{ icon: "cat-vision", name: "See & understand images", items: [
|
| 27 |
+
{ id: "detect", icon: "detect", label: "Object detection (YOLO)" },
|
| 28 |
+
{ id: "segment", icon: "segment", label: "Image segmentation" },
|
| 29 |
+
{ id: "pose", icon: "pose", label: "Pose / 6-DoF (FoundationPose)" },
|
| 30 |
+
{ id: "classify",icon: "classify", label: "Image classification" },
|
| 31 |
+
{ id: "depth", icon: "depth", label: "Depth estimation" },
|
| 32 |
+
{ id: "ocr", icon: "ocr", label: "Read text from images (OCR)" },
|
| 33 |
+
]},
|
| 34 |
+
{ icon: "cat-gen", name: "Create images & video", items: [
|
| 35 |
+
{ id: "imagegen", icon: "imagegen", label: "Generate images (SD / Flux)" },
|
| 36 |
+
{ id: "inpaint", icon: "inpaint", label: "Edit / inpaint images" },
|
| 37 |
+
{ id: "upscale", icon: "upscale", label: "Upscale / restore" },
|
| 38 |
+
{ id: "videogen", icon: "videogen", label: "Generate video" },
|
| 39 |
+
{ id: "bgremove", icon: "bgremove", label: "Remove backgrounds" },
|
| 40 |
+
]},
|
| 41 |
+
{ icon: "cat-audio", name: "Audio & speech", items: [
|
| 42 |
+
{ id: "stt", icon: "stt", label: "Speech to text (Whisper)" },
|
| 43 |
+
{ id: "tts", icon: "tts", label: "Text to speech / voice" },
|
| 44 |
+
{ id: "music", icon: "music", label: "Generate music" },
|
| 45 |
+
]},
|
| 46 |
+
{ icon: "cat-data", name: "Data & search", items: [
|
| 47 |
+
{ id: "embed", icon: "embed", label: "Semantic search / embeddings" },
|
| 48 |
+
{ id: "forecast", icon: "forecast", label: "Time-series forecasting" },
|
| 49 |
+
{ id: "tabular", icon: "tabular", label: "Predict from spreadsheets" },
|
| 50 |
+
]},
|
| 51 |
+
{ icon: "cat-train", name: "Train your own", items: [
|
| 52 |
+
{ id: "finetune", icon: "finetune", label: "Fine-tune an LLM (LoRA)" },
|
| 53 |
+
{ id: "train-vision", icon: "train-vision", label: "Train a vision model" },
|
| 54 |
+
]},
|
| 55 |
+
{ icon: "cat-custom", name: "Something else", items: [
|
| 56 |
+
{ id: "custom", icon: "custom", label: "Custom: describe it" },
|
| 57 |
+
]},
|
| 58 |
+
];
|
| 59 |
+
|
| 60 |
+
// ---- GPU lists per provider ----------------------------------------------
|
| 61 |
+
const GPUS = {
|
| 62 |
+
none: [],
|
| 63 |
+
unsure: [],
|
| 64 |
+
nvidia: [
|
| 65 |
+
"RTX 5090 (32 GB)","RTX 5080 (16 GB)","RTX 5070 Ti (16 GB)","RTX 5070 (12 GB)","RTX 5060 (8 GB)",
|
| 66 |
+
"RTX 4090 (24 GB)","RTX 4080 (16 GB)","RTX 4070 (12 GB)","RTX 4060 (8 GB)",
|
| 67 |
+
"RTX 3090 (24 GB)","RTX 3080 (10 GB)","RTX 3070 (8 GB)","RTX 3060 (12 GB)","RTX 3050 (8 GB)",
|
| 68 |
+
"GTX 1660 (6 GB)","GTX 1650 (4 GB)","Laptop RTX (not sure of model)",
|
| 69 |
+
],
|
| 70 |
+
amd: ["RX 7900 XTX (24 GB)","RX 7800 XT (16 GB)","RX 7600 (8 GB)","RX 6700 XT (12 GB)","Built-in Radeon"],
|
| 71 |
+
apple: ["M-series base (8 GB)","M-series Pro (16 GB)","M-series Max (32 GB)","M-series Ultra (64 GB)"],
|
| 72 |
+
intel: ["Arc A770 (16 GB)","Arc A750 (8 GB)","Arc A380 (6 GB)","Built-in Intel graphics"],
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
const $ = (s) => document.querySelector(s);
|
| 76 |
+
const state = { computer: "Windows laptop", provider: "none", priority: "balanced", usecase: "chat", checked: false };
|
| 77 |
+
|
| 78 |
+
// ---- Build the use-case picker -------------------------------------------
|
| 79 |
+
function buildPicker() {
|
| 80 |
+
const wrap = $("#usecase-picker");
|
| 81 |
+
wrap.innerHTML = USE_CASES.map(g => `
|
| 82 |
+
<div class="uc-group">
|
| 83 |
+
<div class="uc-cat"><span class="ic" data-ic="${g.icon}"></span>${g.name}</div>
|
| 84 |
+
<div class="uc-grid">
|
| 85 |
+
${g.items.map(it => `
|
| 86 |
+
<button class="uc-pill${it.id === "chat" ? " active" : ""}" data-uc="${it.id}">
|
| 87 |
+
<span class="pic ic" data-ic="${it.icon}"></span>${it.label}
|
| 88 |
+
</button>`).join("")}
|
| 89 |
+
</div>
|
| 90 |
+
</div>`).join("");
|
| 91 |
+
hydrate(wrap);
|
| 92 |
+
|
| 93 |
+
wrap.querySelectorAll(".uc-pill").forEach(p => p.addEventListener("click", () => {
|
| 94 |
+
wrap.querySelectorAll(".uc-pill").forEach(x => x.classList.remove("active"));
|
| 95 |
+
p.classList.add("active");
|
| 96 |
+
state.usecase = p.dataset.uc;
|
| 97 |
+
$("#custom-uc-field").style.display = state.usecase === "custom" ? "block" : "none";
|
| 98 |
+
maybeLiveUpdate();
|
| 99 |
+
}));
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
// ---- Segmented controls ---------------------------------------------------
|
| 103 |
+
function wireSegmented(id, key, after) {
|
| 104 |
+
$(id).querySelectorAll(".seg-btn").forEach(b => b.addEventListener("click", () => {
|
| 105 |
+
$(id).querySelectorAll(".seg-btn").forEach(x => x.classList.remove("active"));
|
| 106 |
+
b.classList.add("active");
|
| 107 |
+
state[key] = b.dataset.val;
|
| 108 |
+
if (after) after();
|
| 109 |
+
maybeLiveUpdate();
|
| 110 |
+
}));
|
| 111 |
+
}
|
| 112 |
+
function setActive(id, val) {
|
| 113 |
+
$(id).querySelectorAll(".seg-btn").forEach(b => b.classList.toggle("active", b.dataset.val === val));
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
// ---- GPU select depends on provider --------------------------------------
|
| 117 |
+
function fillGpu() {
|
| 118 |
+
const list = GPUS[state.provider] || [];
|
| 119 |
+
const sel = $("#gpu");
|
| 120 |
+
if (!list.length) { sel.style.display = "none"; sel.innerHTML = ""; }
|
| 121 |
+
else { sel.style.display = "block"; sel.innerHTML = list.map(g => `<option>${g}</option>`).join(""); }
|
| 122 |
+
}
|
| 123 |
+
function syncProviderForComputer() {
|
| 124 |
+
const isMac = state.computer === "Mac";
|
| 125 |
+
const isPi = state.computer.includes("Mini PC");
|
| 126 |
+
if (isMac) { state.provider = "apple"; setActive("#provider-seg", "apple"); }
|
| 127 |
+
else if (isPi) { state.provider = "none"; setActive("#provider-seg", "none"); }
|
| 128 |
+
fillGpu();
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
// ---- Find-my-specs help text ---------------------------------------------
|
| 132 |
+
function findSpecsText() {
|
| 133 |
+
const c = state.computer;
|
| 134 |
+
if (c === "Mac") return `
|
| 135 |
+
<ul style="margin:0;padding-left:18px">
|
| 136 |
+
<li>Click the Apple menu (top-left), then <b>About This Mac</b>.</li>
|
| 137 |
+
<li>It shows your chip (e.g. <i>Apple M2</i>) and <b>Memory</b> (e.g. <i>16 GB</i>).</li>
|
| 138 |
+
<li>On a Mac that one memory number is all you need. The graphics share it.</li>
|
| 139 |
+
</ul>`;
|
| 140 |
+
if (c === "Linux PC" || c.includes("Mini PC")) return `
|
| 141 |
+
<ul style="margin:0;padding-left:18px">
|
| 142 |
+
<li><b>RAM:</b> run <code>free -h</code> in a terminal.</li>
|
| 143 |
+
<li><b>Graphics card:</b> <code>nvidia-smi</code> (NVIDIA) or <code>lspci | grep VGA</code>.</li>
|
| 144 |
+
</ul>`;
|
| 145 |
+
return `
|
| 146 |
+
<ul style="margin:0;padding-left:18px">
|
| 147 |
+
<li><b>RAM:</b> press <code>Ctrl + Shift + Esc</code>, then <b>Performance</b>, then <b>Memory</b>.</li>
|
| 148 |
+
<li><b>Graphics card:</b> same window, <b>GPU</b>. The name is top-right (e.g. <i>NVIDIA RTX 3060</i>).</li>
|
| 149 |
+
<li>No real GPU listed? You have built-in graphics. Pick “None / built-in”.</li>
|
| 150 |
+
</ul>`;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
// ---- Gather inputs & call the connector ----------------------------------
|
| 154 |
+
function gather() {
|
| 155 |
+
const sel = $("#gpu");
|
| 156 |
+
return {
|
| 157 |
+
computer: state.computer,
|
| 158 |
+
ram_gb: parseFloat($("#ram").value),
|
| 159 |
+
provider: state.provider,
|
| 160 |
+
gpu: sel.style.display === "none" ? "" : sel.value,
|
| 161 |
+
vram_gb: $("#vram").value ? parseFloat($("#vram").value) : null,
|
| 162 |
+
paste: $("#paste").value.trim(),
|
| 163 |
+
usecase: state.usecase,
|
| 164 |
+
custom: $("#custom-uc").value.trim(),
|
| 165 |
+
priority: state.priority,
|
| 166 |
+
};
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
let debounce;
|
| 170 |
+
function maybeLiveUpdate() {
|
| 171 |
+
if (!state.checked) return;
|
| 172 |
+
clearTimeout(debounce);
|
| 173 |
+
debounce = setTimeout(check, 200);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
async function check() {
|
| 177 |
+
state.checked = true;
|
| 178 |
+
try {
|
| 179 |
+
const res = await fetch("/api/advise", {
|
| 180 |
+
method: "POST", headers: { "Content-Type": "application/json" },
|
| 181 |
+
body: JSON.stringify(gather()),
|
| 182 |
+
});
|
| 183 |
+
render(await res.json());
|
| 184 |
+
} catch (e) {
|
| 185 |
+
$("#results").innerHTML = `<div class="empty-state"><div class="big"><span class="ic" data-ic="monitor"></span></div>
|
| 186 |
+
<p>Couldn't reach the advisor. Is the server running?</p></div>`;
|
| 187 |
+
hydrate($("#results"));
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
// ---- Render results -------------------------------------------------------
|
| 192 |
+
const VMAP = {
|
| 193 |
+
great: { cls: "var(--ok)", soft: "var(--ok-soft)", word: "Runs great", em: "✓" },
|
| 194 |
+
tight: { cls: "var(--warn)", soft: "var(--warn-soft)", word: "Tight, but works", em: "≈" },
|
| 195 |
+
no: { cls: "var(--no)", soft: "var(--no-soft)", word: "Won't fit", em: "✕" },
|
| 196 |
+
};
|
| 197 |
+
|
| 198 |
+
function render(d) {
|
| 199 |
+
const v = VMAP[d.verdict] || VMAP.tight;
|
| 200 |
+
const g = d.gauge || {};
|
| 201 |
+
$("#cat-version").textContent = d.catalogue_version || "—";
|
| 202 |
+
|
| 203 |
+
const opts = (d.options || []).map(o => {
|
| 204 |
+
const ov = VMAP[o.verdict] || VMAP.tight;
|
| 205 |
+
return `<div class="opt" style="--status:${ov.cls};--status-soft:${ov.soft}">
|
| 206 |
+
<div class="vdot">${ov.em}</div>
|
| 207 |
+
<div><div class="name">${o.model}</div><div class="desc">${o.desc}</div></div>
|
| 208 |
+
<div class="meta"><b>${o.memory}</b><div class="feel">${o.setting} · ${o.feel}</div></div>
|
| 209 |
+
</div>`;
|
| 210 |
+
}).join("");
|
| 211 |
+
|
| 212 |
+
const tools = (d.tools || []).map((t, i) => `
|
| 213 |
+
<div class="tool">
|
| 214 |
+
<div class="tool-head"><span class="tname">${t.name}</span>
|
| 215 |
+
<span class="tag ${i===0?"best":"mid"}">${i===0?"Start here":t.tag}</span></div>
|
| 216 |
+
<div class="twhat">${t.what}</div>
|
| 217 |
+
<div class="tinstall"><span class="ic" data-ic="download"></span>${t.install}</div>
|
| 218 |
+
</div>`).join("");
|
| 219 |
+
|
| 220 |
+
const cmds = (d.commands?.items || []).map((c) => `
|
| 221 |
+
<div class="cmd-box">
|
| 222 |
+
<div class="cmd-label">${c.label}<button class="copy-btn" data-code="${encodeURIComponent(c.code)}">Copy</button></div>
|
| 223 |
+
<pre><code>${c.code}</code></pre>
|
| 224 |
+
</div>`).join("");
|
| 225 |
+
|
| 226 |
+
$("#results").innerHTML = `
|
| 227 |
+
<div class="reveal">
|
| 228 |
+
<div class="verdict" style="--status:${v.cls};--status-soft:${v.soft}">
|
| 229 |
+
<span class="badge"><span class="dot"></span>${d.verdict_word || v.word}</span>
|
| 230 |
+
<h2>${d.headline || ""}</h2>
|
| 231 |
+
<p>${d.detail || ""}</p>
|
| 232 |
+
${d.note ? `<div class="note">${d.note}</div>` : ""}
|
| 233 |
+
</div>
|
| 234 |
+
|
| 235 |
+
${g.fill_pct != null ? `
|
| 236 |
+
<div class="gauge-card" style="--status:${v.cls}">
|
| 237 |
+
<div class="gauge-top"><span class="label" style="margin:0">Memory needed vs. what you have</span>
|
| 238 |
+
<span class="need">${g.need_gb}</span></div>
|
| 239 |
+
<div class="gauge" style="--pct:${g.fill_pct};--gcolor:${v.cls};--needpct:${g.mark_pct}">
|
| 240 |
+
<div class="gauge-fill"></div><div class="gauge-need"></div>
|
| 241 |
+
</div>
|
| 242 |
+
<div class="gauge-legend">
|
| 243 |
+
${(g.breakdown||[]).map(b=>`<span class="item"><span class="sw" style="background:${b.color}"></span>${b.label}</span>`).join("")}
|
| 244 |
+
<span class="item marker">Fast: ${g.fast_gb}</span>
|
| 245 |
+
<span class="item marker">Total: ${g.total_gb}</span>
|
| 246 |
+
</div>
|
| 247 |
+
</div>` : ""}
|
| 248 |
+
|
| 249 |
+
${opts ? `<div class="section-title">What you can run <span class="sub">honestly, biggest to smallest</span></div>
|
| 250 |
+
<div class="opt-grid">${opts}</div>` : ""}
|
| 251 |
+
|
| 252 |
+
${tools ? `<div class="section-title">How to actually run it</div>
|
| 253 |
+
<div class="tool-grid">${tools}</div>` : ""}
|
| 254 |
+
|
| 255 |
+
${cmds ? `<div class="section-title">Copy-paste to get started</div>
|
| 256 |
+
<p class="cmd-intro">${d.commands.intro || ""}</p>
|
| 257 |
+
<div class="cmd">${cmds}</div>` : ""}
|
| 258 |
+
</div>`;
|
| 259 |
+
|
| 260 |
+
hydrate($("#results"));
|
| 261 |
+
$("#results").querySelectorAll(".copy-btn").forEach(b => b.addEventListener("click", () => {
|
| 262 |
+
navigator.clipboard.writeText(decodeURIComponent(b.dataset.code));
|
| 263 |
+
b.textContent = "Copied ✓"; b.classList.add("done");
|
| 264 |
+
setTimeout(() => { b.textContent = "Copy"; b.classList.remove("done"); }, 1500);
|
| 265 |
+
}));
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
// ---- Init -----------------------------------------------------------------
|
| 269 |
+
function init() {
|
| 270 |
+
hydrate(document);
|
| 271 |
+
buildPicker();
|
| 272 |
+
wireSegmented("#computer-seg", "computer", () => { syncProviderForComputer(); $("#find-specs-body").innerHTML = findSpecsText(); });
|
| 273 |
+
wireSegmented("#provider-seg", "provider", fillGpu);
|
| 274 |
+
wireSegmented("#priority-seg", "priority");
|
| 275 |
+
["#ram","#gpu","#vram","#custom-uc"].forEach(s => $(s).addEventListener("change", maybeLiveUpdate));
|
| 276 |
+
$("#paste").addEventListener("input", maybeLiveUpdate);
|
| 277 |
+
$("#check-btn").addEventListener("click", check);
|
| 278 |
+
fillGpu();
|
| 279 |
+
$("#find-specs-body").innerHTML = findSpecsText();
|
| 280 |
+
// Pre-filled share/preview links: /?go renders results immediately.
|
| 281 |
+
if (new URLSearchParams(location.search).has("go")) check();
|
| 282 |
+
}
|
| 283 |
+
init();
|
static/icons.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Auto-generated by build_icons.py. Inline icons = offline-safe. */
|
| 2 |
+
window.ICONS = {
|
| 3 |
+
"chat": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719\" /> </svg>",
|
| 4 |
+
"writing": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" /> <path d=\"M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z\" /> </svg>",
|
| 5 |
+
"coding": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m18 16 4-4-4-4\" /> <path d=\"m6 8-4 4 4 4\" /> <path d=\"m14.5 4-5 16\" /> </svg>",
|
| 6 |
+
"agents": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 8V4H8\" /> <rect x=\"4\" y=\"8\" rx=\"2\" /> <path d=\"M2 14h2\" /> <path d=\"M20 14h2\" /> <path d=\"M15 13v2\" /> <path d=\"M9 13v2\" /> </svg>",
|
| 7 |
+
"rag": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 7v14\" /> <path d=\"M16 12h2\" /> <path d=\"M16 8h2\" /> <path d=\"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\" /> <path d=\"M6 12h2\" /> <path d=\"M6 8h2\" /> </svg>",
|
| 8 |
+
"translate": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m5 8 6 6\" /> <path d=\"m4 14 6-6 2-3\" /> <path d=\"M2 5h12\" /> <path d=\"M7 2h1\" /> <path d=\"m22 22-5-10-5 10\" /> <path d=\"M14 18h6\" /> </svg>",
|
| 9 |
+
"detect": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M3 7V5a2 2 0 0 1 2-2h2\" /> <path d=\"M17 3h2a2 2 0 0 1 2 2v2\" /> <path d=\"M21 17v2a2 2 0 0 1-2 2h-2\" /> <path d=\"M7 21H5a2 2 0 0 1-2-2v-2\" /> <circle cx=\"12\" cy=\"12\" r=\"3\" /> <path d=\"m16 16-1.9-1.9\" /> </svg>",
|
| 10 |
+
"segment": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z\" /> <path d=\"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12\" /> <path d=\"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17\" /> </svg>",
|
| 11 |
+
"pose": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\" /> <path d=\"m3.3 7 8.7 5 8.7-5\" /> <path d=\"M12 22V12\" /> </svg>",
|
| 12 |
+
"classify": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M13.172 2a2 2 0 0 1 1.414.586l6.71 6.71a2.4 2.4 0 0 1 0 3.408l-4.592 4.592a2.4 2.4 0 0 1-3.408 0l-6.71-6.71A2 2 0 0 1 6 9.172V3a1 1 0 0 1 1-1z\" /> <path d=\"M2 7v6.172a2 2 0 0 0 .586 1.414l6.71 6.71a2.4 2.4 0 0 0 3.191.193\" /> <circle cx=\"10.5\" cy=\"6.5\" r=\".5\" fill=\"currentColor\" /> </svg>",
|
| 13 |
+
"depth": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m8 3 4 8 5-5 5 15H2L8 3z\" /> </svg>",
|
| 14 |
+
"ocr": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M3 7V5a2 2 0 0 1 2-2h2\" /> <path d=\"M17 3h2a2 2 0 0 1 2 2v2\" /> <path d=\"M21 17v2a2 2 0 0 1-2 2h-2\" /> <path d=\"M7 21H5a2 2 0 0 1-2-2v-2\" /> <path d=\"M7 8h8\" /> <path d=\"M7 12h10\" /> <path d=\"M7 16h6\" /> </svg>",
|
| 15 |
+
"imagegen": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <rect x=\"3\" y=\"3\" rx=\"2\" ry=\"2\" /> <circle cx=\"9\" cy=\"9\" r=\"2\" /> <path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\" /> </svg>",
|
| 16 |
+
"inpaint": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72\" /> <path d=\"m14 7 3 3\" /> <path d=\"M5 6v4\" /> <path d=\"M19 14v4\" /> <path d=\"M10 2v2\" /> <path d=\"M7 8H3\" /> <path d=\"M21 16h-4\" /> <path d=\"M11 3H9\" /> </svg>",
|
| 17 |
+
"upscale": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\" /> <path d=\"M14 15H9v-5\" /> <path d=\"M16 3h5v5\" /> <path d=\"M21 3 9 15\" /> </svg>",
|
| 18 |
+
"videogen": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <rect x=\"3\" y=\"3\" rx=\"2\" /> <path d=\"M7 3v18\" /> <path d=\"M3 7.5h4\" /> <path d=\"M3 12h18\" /> <path d=\"M3 16.5h4\" /> <path d=\"M17 3v18\" /> <path d=\"M17 7.5h4\" /> <path d=\"M17 16.5h4\" /> </svg>",
|
| 19 |
+
"bgremove": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <circle cx=\"6\" cy=\"6\" r=\"3\" /> <path d=\"M8.12 8.12 12 12\" /> <path d=\"M20 4 8.12 15.88\" /> <circle cx=\"6\" cy=\"18\" r=\"3\" /> <path d=\"M14.8 14.8 20 20\" /> </svg>",
|
| 20 |
+
"stt": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 19v3\" /> <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\" /> <rect x=\"9\" y=\"2\" rx=\"3\" /> </svg>",
|
| 21 |
+
"tts": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M2 10v3\" /> <path d=\"M6 6v11\" /> <path d=\"M10 3v18\" /> <path d=\"M14 8v7\" /> <path d=\"M18 5v13\" /> <path d=\"M22 10v3\" /> </svg>",
|
| 22 |
+
"music": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M9 18V5l12-2v13\" /> <circle cx=\"6\" cy=\"18\" r=\"3\" /> <circle cx=\"18\" cy=\"16\" r=\"3\" /> </svg>",
|
| 23 |
+
"embed": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <circle cx=\"12\" cy=\"12\" r=\"10\" /> <path d=\"m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z\" /> </svg>",
|
| 24 |
+
"forecast": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M16 7h6v6\" /> <path d=\"m22 7-8.5 8.5-5-5L2 17\" /> </svg>",
|
| 25 |
+
"tabular": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 3v18\" /> <rect x=\"3\" y=\"3\" rx=\"2\" /> <path d=\"M3 9h18\" /> <path d=\"M3 15h18\" /> </svg>",
|
| 26 |
+
"finetune": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z\" /> <path d=\"M22 10v6\" /> <path d=\"M6 12.5V16a6 3 0 0 0 12 0v-3.5\" /> </svg>",
|
| 27 |
+
"train-vision": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z\" /> </svg>",
|
| 28 |
+
"custom": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\" /> <path d=\"m15 5 4 4\" /> </svg>",
|
| 29 |
+
"cat-text": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z\" /> </svg>",
|
| 30 |
+
"cat-vision": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\" /> <circle cx=\"12\" cy=\"12\" r=\"3\" /> </svg>",
|
| 31 |
+
"cat-gen": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z\" /> <circle cx=\"13.5\" cy=\"6.5\" r=\".5\" fill=\"currentColor\" /> <circle cx=\"17.5\" cy=\"10.5\" r=\".5\" fill=\"currentColor\" /> <circle cx=\"6.5\" cy=\"12.5\" r=\".5\" fill=\"currentColor\" /> <circle cx=\"8.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\" /> </svg>",
|
| 32 |
+
"cat-audio": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M2 13a2 2 0 0 0 2-2V7a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0V4a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0v-4a2 2 0 0 1 2-2\" /> </svg>",
|
| 33 |
+
"cat-data": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M3 3v16a2 2 0 0 0 2 2h16\" /> <path d=\"M18 17V9\" /> <path d=\"M13 17V5\" /> <path d=\"M8 17v-3\" /> </svg>",
|
| 34 |
+
"cat-train": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z\" /> <path d=\"M22 10v6\" /> <path d=\"M6 12.5V16a6 3 0 0 0 12 0v-3.5\" /> </svg>",
|
| 35 |
+
"cat-custom": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z\" /> <path d=\"m15 5 4 4\" /> </svg>",
|
| 36 |
+
"check": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M20 6 9 17l-5-5\" /> </svg>",
|
| 37 |
+
"search": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m21 21-4.34-4.34\" /> <circle cx=\"11\" cy=\"11\" r=\"8\" /> </svg>",
|
| 38 |
+
"book": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 7v14\" /> <path d=\"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\" /> </svg>",
|
| 39 |
+
"balanced": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 3v18\" /> <path d=\"m19 8 3 8a5 5 0 0 1-6 0zV7\" /> <path d=\"M3 7h1a17 17 0 0 0 8-2 17 17 0 0 0 8 2h1\" /> <path d=\"m5 8 3 8a5 5 0 0 1-6 0zV7\" /> <path d=\"M7 21h10\" /> </svg>",
|
| 40 |
+
"quality": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z\" /> <path d=\"M20 2v4\" /> <path d=\"M22 4h-4\" /> <circle cx=\"4\" cy=\"20\" r=\"2\" /> </svg>",
|
| 41 |
+
"speed": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z\" /> </svg>",
|
| 42 |
+
"offline": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\" /> </svg>",
|
| 43 |
+
"monitor": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m9 10 2 2 4-4\" /> <rect x=\"2\" y=\"3\" rx=\"2\" /> <path d=\"M12 17v4\" /> <path d=\"M8 21h8\" /> </svg>",
|
| 44 |
+
"download": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M12 15V3\" /> <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\" /> <path d=\"m7 10 5 5 5-5\" /> </svg>",
|
| 45 |
+
"chevron": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"m6 9 6 6 6-6\" /> </svg>",
|
| 46 |
+
"arrow": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" > <path d=\"M5 12h14\" /> <path d=\"m12 5 7 7-7 7\" /> </svg>",
|
| 47 |
+
"brand-windows": "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"3\" y=\"3\" width=\"8\" height=\"8\" rx=\"1\"/><rect x=\"13\" y=\"3\" width=\"8\" height=\"8\" rx=\"1\"/><rect x=\"3\" y=\"13\" width=\"8\" height=\"8\" rx=\"1\"/><rect x=\"13\" y=\"13\" width=\"8\" height=\"8\" rx=\"1\"/></svg>",
|
| 48 |
+
"brand-apple": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701\"/></svg>",
|
| 49 |
+
"brand-linux": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.504 0c-.155 0-.315.008-.48.021-4.226.333-3.105 4.807-3.17 6.298-.076 1.092-.3 1.953-1.05 3.02-.885 1.051-2.127 2.75-2.716 4.521-.278.832-.41 1.684-.287 2.489a.424.424 0 00-.11.135c-.26.268-.45.6-.663.839-.199.199-.485.267-.797.4-.313.136-.658.269-.864.68-.09.189-.136.394-.132.602 0 .199.027.4.055.536.058.399.116.728.04.97-.249.68-.28 1.145-.106 1.484.174.334.535.47.94.601.81.2 1.91.135 2.774.6.926.466 1.866.67 2.616.47.526-.116.97-.464 1.208-.946.587-.003 1.23-.269 2.26-.334.699-.058 1.574.267 2.577.2.025.134.063.198.114.333l.003.003c.391.778 1.113 1.132 1.884 1.071.771-.06 1.592-.536 2.257-1.306.631-.765 1.683-1.084 2.378-1.503.348-.199.629-.469.649-.853.023-.4-.2-.811-.714-1.376v-.097l-.003-.003c-.17-.2-.25-.535-.338-.926-.085-.401-.182-.786-.492-1.046h-.003c-.059-.054-.123-.067-.188-.135a.357.357 0 00-.19-.064c.431-1.278.264-2.55-.173-3.694-.533-1.41-1.465-2.638-2.175-3.483-.796-1.005-1.576-1.957-1.56-3.368.026-2.152.236-6.133-3.544-6.139zm.529 3.405h.013c.213 0 .396.062.584.198.19.135.33.332.438.533.105.259.158.459.166.724 0-.02.006-.04.006-.06v.105a.086.086 0 01-.004-.021l-.004-.024a1.807 1.807 0 01-.15.706.953.953 0 01-.213.335.71.71 0 00-.088-.042c-.104-.045-.198-.064-.284-.133a1.312 1.312 0 00-.22-.066c.05-.06.146-.133.183-.198.053-.128.082-.264.088-.402v-.02a1.21 1.21 0 00-.061-.4c-.045-.134-.101-.2-.183-.333-.084-.066-.167-.132-.267-.132h-.016c-.093 0-.176.03-.262.132a.8.8 0 00-.205.334 1.18 1.18 0 00-.09.4v.019c.002.089.008.179.02.267-.193-.067-.438-.135-.607-.202a1.635 1.635 0 01-.018-.2v-.02a1.772 1.772 0 01.15-.768c.082-.22.232-.406.43-.533a.985.985 0 01.594-.2zm-2.962.059h.036c.142 0 .27.048.399.135.146.129.264.288.344.465.09.199.14.4.153.667v.004c.007.134.006.2-.002.266v.08c-.03.007-.056.018-.083.024-.152.055-.274.135-.393.2.012-.09.013-.18.003-.267v-.015c-.012-.133-.04-.2-.082-.333a.613.613 0 00-.166-.267.248.248 0 00-.183-.064h-.021c-.071.006-.13.04-.186.132a.552.552 0 00-.12.27.944.944 0 00-.023.33v.015c.012.135.037.2.08.334.046.134.098.2.166.268.01.009.02.018.034.024-.07.057-.117.07-.176.136a.304.304 0 01-.131.068 2.62 2.62 0 01-.275-.402 1.772 1.772 0 01-.155-.667 1.759 1.759 0 01.08-.668 1.43 1.43 0 01.283-.535c.128-.133.26-.2.418-.2zm1.37 1.706c.332 0 .733.065 1.216.399.293.2.523.269 1.052.468h.003c.255.136.405.266.478.399v-.131a.571.571 0 01.016.47c-.123.31-.516.643-1.063.842v.002c-.268.135-.501.333-.775.465-.276.135-.588.292-1.012.267a1.139 1.139 0 01-.448-.067 3.566 3.566 0 01-.322-.198c-.195-.135-.363-.332-.612-.465v-.005h-.005c-.4-.246-.616-.512-.686-.71-.07-.268-.005-.47.193-.6.224-.135.38-.271.483-.336.104-.074.143-.102.176-.131h.002v-.003c.169-.202.436-.47.839-.601.139-.036.294-.065.466-.065zm2.8 2.142c.358 1.417 1.196 3.475 1.735 4.473.286.534.855 1.659 1.102 3.024.156-.005.33.018.513.064.646-1.671-.546-3.467-1.089-3.966-.22-.2-.232-.335-.123-.335.59.534 1.365 1.572 1.646 2.757.13.535.16 1.104.021 1.67.067.028.135.06.205.067 1.032.534 1.413.938 1.23 1.537v-.043c-.06-.003-.12 0-.18 0h-.016c.151-.467-.182-.825-1.065-1.224-.915-.4-1.646-.336-1.77.465-.008.043-.013.066-.018.135-.068.023-.139.053-.209.064-.43.268-.662.669-.793 1.187-.13.533-.17 1.156-.205 1.869v.003c-.02.334-.17.838-.319 1.35-1.5 1.072-3.58 1.538-5.348.334a2.645 2.645 0 00-.402-.533 1.45 1.45 0 00-.275-.333c.182 0 .338-.03.465-.067a.615.615 0 00.314-.334c.108-.267 0-.697-.345-1.163-.345-.467-.931-.995-1.788-1.521-.63-.4-.986-.87-1.15-1.396-.165-.534-.143-1.085-.015-1.645.245-1.07.873-2.11 1.274-2.763.107-.065.037.135-.408.974-.396.751-1.14 2.497-.122 3.854a8.123 8.123 0 01.647-2.876c.564-1.278 1.743-3.504 1.836-5.268.048.036.217.135.289.202.218.133.38.333.59.465.21.201.477.335.876.335.039.003.075.006.11.006.412 0 .73-.134.997-.268.29-.134.52-.334.74-.4h.005c.467-.135.835-.402 1.044-.7zm2.185 8.958c.037.6.343 1.245.882 1.377.588.134 1.434-.333 1.791-.765l.211-.01c.315-.007.577.01.847.268l.003.003c.208.199.305.53.391.876.085.4.154.78.409 1.066.486.527.645.906.636 1.14l.003-.007v.018l-.003-.012c-.015.262-.185.396-.498.595-.63.401-1.746.712-2.457 1.57-.618.737-1.37 1.14-2.036 1.191-.664.053-1.237-.2-1.574-.898l-.005-.003c-.21-.4-.12-1.025.056-1.69.176-.668.428-1.344.463-1.897.037-.714.076-1.335.195-1.814.12-.465.308-.797.641-.984l.045-.022zm-10.814.049h.01c.053 0 .105.005.157.014.376.055.706.333 1.023.752l.91 1.664.003.003c.243.533.754 1.064 1.189 1.637.434.598.77 1.131.729 1.57v.006c-.057.744-.48 1.148-1.125 1.294-.645.135-1.52.002-2.395-.464-.968-.536-2.118-.469-2.857-.602-.369-.066-.61-.2-.723-.4-.11-.2-.113-.602.123-1.23v-.004l.002-.003c.117-.334.03-.752-.027-1.118-.055-.401-.083-.71.043-.94.16-.334.396-.4.69-.533.294-.135.64-.202.915-.47h.002v-.002c.256-.268.445-.601.668-.838.19-.201.38-.336.663-.336zm7.159-9.074c-.435.201-.945.535-1.488.535-.542 0-.97-.267-1.28-.466-.154-.134-.28-.268-.373-.335-.164-.134-.144-.333-.074-.333.109.016.129.134.199.2.096.066.215.2.36.333.292.2.68.467 1.167.467.485 0 1.053-.267 1.398-.466.195-.135.445-.334.648-.467.156-.136.149-.267.279-.267.128.016.034.134-.147.332a8.097 8.097 0 01-.69.468zm-1.082-1.583V5.64c-.006-.02.013-.042.029-.05.074-.043.18-.027.26.004.063 0 .16.067.15.135-.006.049-.085.066-.135.066-.055 0-.092-.043-.141-.068-.052-.018-.146-.008-.163-.065zm-.551 0c-.02.058-.113.049-.166.066-.047.025-.086.068-.14.068-.05 0-.13-.02-.136-.068-.01-.066.088-.133.15-.133.08-.031.184-.047.259-.005.019.009.036.03.03.05v.02h.003z\"/></svg>",
|
| 50 |
+
"brand-raspberrypi": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m19.8955 10.8961-.1726-.3028c.0068-2.1746-1.0022-3.061-2.1788-3.7348.356-.0938.7237-.1711.8245-.6182.6118-.1566.7397-.4398.8011-.7398.16-.1066.6955-.4061.6394-.9211.2998-.2069.4669-.4725.3819-.8487.3222-.3515.407-.6419.2702-.9096.3868-.4805.2152-.7295.05-.9817.2897-.5254.0341-1.0887-.7758-.9944-.3221-.4733-1.0244-.3659-1.133-.3637-.1215-.1519-.2819-.2821-.7755-.219-.3197-.2851-.6771-.2364-1.0458-.0964-.4378-.3403-.7275-.0675-1.0584.0356-.53-.1706-.6513.0631-.9117.1583-.5781-.1203-.7538.1416-1.0309.4182l-.3224-.0063c-.8719.5061-1.305 1.5366-1.4585 2.0664-.1536-.5299-.5858-1.5604-1.4575-2.0664l-.3223.0063C9.942.5014 9.7663.2394 9.1883.3597 8.9279.2646 8.807.0309 8.2766.2015c-.2172-.0677-.417-.2084-.6522-.2012l.0004.0002C7.5017.0041 7.369.049 7.2185.166c-.3688-.1401-.7262-.1887-1.0459.0964-.4936-.0631-.654.0671-.7756.219C5.2887.4791 4.5862.3717 4.264.845c-.8096-.0943-1.0655.4691-.7756.9944-.1653.2521-.3366.5013.05.9819-.1367.2677-.0519.5581.2703.9096-.085.3763.0822.6418.3819.8487-.0561.515.4795.8144.6394.9211.0614.3001.1894.5832.8011.7398.1008.4472.4685.5244.8245.6183-1.1766.6737-2.1856 1.56-2.1788 3.7348l-.1724.3028c-1.3491.8082-2.5629 3.4056-.6648 5.5167.124.6609.3319 1.1355.5171 1.6609.2769 2.117 2.0841 3.1082 2.5608 3.2255.6984.524 1.4423 1.0212 2.449 1.3696.949.964 1.977 1.3314 3.0107 1.3308.0152 0 .0306.0002.0457 0 1.0337.0006 2.0618-.3668 3.0107-1.3308 1.0067-.3483 1.7506-.8456 2.4491-1.3696.4766-.1173 2.2838-1.1085 2.5607-3.2255.1851-.5253.3931-1 .517-1.6609 1.8981-2.1113.6843-4.7089-.6649-5.517zm-1.0386-.3715c-.0704.8759-4.6354-3.0504-3.8472-3.1808 2.1391-.3558 3.9191.896 3.8472 3.1808zm-2.0155 4.3649c-1.1481.7409-2.8025.2626-3.6953-1.0681-.8928-1.3306-.6858-3.0101.4623-3.7509 1.1481-.7409 2.8025-.2627 3.6953 1.068.8927 1.3307.6858 3.0101-.4623 3.751zM13.6591 1.3721c.0396.1967.0843.321.1354.3577.2537-.272.4611-.5506.7878-.8123.0011.1537-.0776.3205.1169.4425.1752-.2356.4119-.4459.7263-.6244-.1514.2611-.026.3404.0554.4486.24-.2059.4681-.4144.9109-.5759-.121.1474-.2902.2914-.1108.4607.2473-.1544.496-.3086 1.0833-.4183-.1323.1475-.4059.295-.2401.4426.3104-.1186.6539-.2047 1.034-.2546-.182.1496-.3337.2963-.1846.4122.3323-.1022.7899-.2398 1.2372-.1212l-.2832.2849c-.0314.0382.6623.0297 1.1202.0364-.167.2321-.3375.4562-.437.8548.0454.0459.2723.0204.4862 0-.2194.4618-.6004.5783-.6893.776.134.1015.32.075.5232.006-.158.3254-.4892.5484-.7509.8123.0662.047.1818.075.4555.0425-.2418.257-.5339.492-.8802.7032.0614.0708.2722.0681.4678.0727-.3136.3069-.7173.466-1.0955.6668.1885.1288.3234.0988.4678.097-.2676.2198-.7225.3342-1.1448.4668.0803.1249.1607.1589.3324.194-.447.2473-1.0873.1343-1.2679.2607.0435.1243.1665.2053.3139.2728-.7197.0418-2.6879-.0262-3.0652-1.5156.7367-.8094 2.0813-1.7593 4.394-2.934-1.7994.6022-3.4229 1.405-4.7817 2.5096-1.5978-.7436-.4965-2.6197.283-3.3645zm-1.6126 5.3718c1.1329-.0123 2.5356.8325 2.53 1.6286-.005.7027-.9851 1.2715-2.5213 1.2607-1.5043-.0177-2.5172-.7148-2.5137-1.3957.003-.5603 1.2282-1.5263 2.505-1.4936zm-5.7646-.6006c.1717-.0351.252-.0692.3323-.194-.4223-.1327-.8772-.247-1.1448-.4668.1444.0018.2792.0318.4678-.097-.3783-.2008-.782-.3599-1.0956-.6668.1955-.0048.4064-.002.4677-.0728-.3462-.2113-.6383-.4463-.8801-.7033.2738.0325.3893.0045.4555-.0425-.2617-.264-.593-.487-.7509-.8123.2032.069.3892.0954.5232-.006-.089-.1977-.47-.3142-.6894-.776.214.0204.4409.0459.4863 0-.0994-.3985-.2698-.6226-.4369-.8547.4579-.0067 1.1516.0018 1.1202-.0364l-.2831-.2849c.4472-.1186.9049.019 1.2371.1213.1492-.1159-.0026-.2626-.1847-.4123.3801.05.7236.1361 1.034.2547.1659-.1476-.1076-.2951-.24-.4426.5872.1097.8361.2639 1.0833.4183.1794-.1694.0103-.3133-.1108-.4607.4428.1615.6709.37.911.5759.0814-.1082.2068-.1875.0554-.4486.3143.1785.5511.3888.7263.6244.1945-.122.1159-.2888.1169-.4426.3267.2618.534.5404.7879.8124.0511-.0366.0959-.161.1354-.3577.7794.7448 1.8807 2.6208.2831 3.3646-1.3589-1.1039-2.9817-1.9064-4.78-2.5086 2.3115 1.174 3.6556 2.1239 4.392 2.9328-.3773 1.4895-2.3455 1.5575-3.0651 1.5157.1473-.0676.2703-.1485.3139-.2728-.1806-.1264-.8209-.0134-1.2679-.2607zm2.8175 1.1334c.7881.1304-3.7769 4.0567-3.8472 3.1809-.0719-2.2846 1.7079-3.5367 3.8472-3.1809zm-4.847 8.7567c-1.1094-.8789-1.4668-3.4529.5901-4.6097 1.2394-.3273.4184 5.051-.5901 4.6097zm4.2656 4.5989c-.6257.3719-2.1452.2187-3.2252-1.3095-.7283-1.2823-.6345-2.5872-.123-2.9705.7648-.4589 1.9464.1609 2.8559 1.2003.7923.9405 1.1536 2.5927.4923 3.0797zm-1.2415-5.6086c-1.1481-.7409-1.3551-2.4203-.4623-3.7511.8928-1.3307 2.5472-1.8089 3.6952-1.068 1.1481.7409 1.3551 2.4203.4623 3.7509-.8926 1.3308-2.5471 1.809-3.6952 1.0682zm4.7948 8.2279c-1.3763.0584-2.7258-1.1105-2.7081-1.5157-.0206-.594 1.6758-1.0578 2.782-1.0306 1.1131-.0479 2.6068.3531 2.6097.8851.0184.5166-1.3547 1.6838-2.6836 1.6612zm2.7584-5.8578c.0081 1.3899-1.226 2.5225-2.7562 2.5299-1.5302.0073-2.7773-1.1135-2.7854-2.5033v-.0265c-.008-1.3899 1.2259-2.5226 2.7562-2.5299 1.5302-.0073 2.7773 1.1134 2.7853 2.5033a.7794.7794 0 0 1 .0001.0265zm3.855 2.0029c-1.186 1.6208-2.7916 1.684-3.3896 1.2325-.6255-.5811-.148-2.3854.7094-3.3747v-.0003c.9812-1.0912 2.0302-1.8037 2.7609-1.2469.4919.4828.7805 2.3008-.0807 3.3894zm1.0724-3.4301c-1.0086.4413-1.8298-4.9372-.5901-4.61 2.0568 1.1569 1.6994 3.731.5901 4.61zm-.0256-8.3279h.2985v-.5304h.2986c.1502 0 .2053.0624.2262.2052.0152.1088.0113.2395.0477.3253h.2984c-.0533-.0763-.0515-.2358-.0571-.3213-.0097-.1373-.0513-.2796-.1977-.3176v-.0037c.1502-.061.2149-.1807.2149-.341 0-.2048-.1539-.3738-.3974-.3738h-.732v1.3573zm.2985-1.1255h.3269c.1333 0 .2054.0573.2054.188 0 .1369-.0721.1942-.2054.1942H20.03v-.3822zm-1.0337.4633c0 .7009.5682 1.2694 1.2695 1.2694s1.2695-.5684 1.2695-1.2694c0-.7013-.5683-1.2697-1.2695-1.2697-.7013 0-1.2695.5684-1.2695 1.2697zm2.3275 0c0 .5845-.4737 1.058-1.058 1.058s-1.058-.4735-1.058-1.058c0-.5849.4737-1.058 1.058-1.058s1.058.4731 1.058 1.058z\"/></svg>",
|
| 51 |
+
"brand-nvidia": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.948 8.798v-1.43a6.7 6.7 0 0 1 .424-.018c3.922-.124 6.493 3.374 6.493 3.374s-2.774 3.851-5.75 3.851c-.398 0-.787-.062-1.158-.185v-4.346c1.528.185 1.837.857 2.747 2.385l2.04-1.714s-1.492-1.952-4-1.952a6.016 6.016 0 0 0-.796.035m0-4.735v2.138l.424-.027c5.45-.185 9.01 4.47 9.01 4.47s-4.08 4.964-8.33 4.964c-.37 0-.733-.035-1.095-.097v1.325c.3.035.61.062.91.062 3.957 0 6.82-2.023 9.593-4.408.459.371 2.34 1.263 2.73 1.652-2.633 2.208-8.772 3.984-12.253 3.984-.335 0-.653-.018-.971-.053v1.864H24V4.063zm0 10.326v1.131c-3.657-.654-4.673-4.46-4.673-4.46s1.758-1.944 4.673-2.262v1.237H8.94c-1.528-.186-2.73 1.245-2.73 1.245s.68 2.412 2.739 3.11M2.456 10.9s2.164-3.197 6.5-3.533V6.201C4.153 6.59 0 10.653 0 10.653s2.35 6.802 8.948 7.42v-1.237c-4.84-.6-6.492-5.936-6.492-5.936z\"/></svg>",
|
| 52 |
+
"brand-amd": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M18.324 9.137l1.559 1.56h2.556v2.557L24 14.814V9.137zM2 9.52l-2 4.96h1.309l.37-.982H3.9l.408.982h1.338L3.432 9.52zm4.209 0v4.955h1.238v-3.092l1.338 1.562h.188l1.338-1.556v3.091h1.238V9.52H10.47l-1.592 1.845L7.287 9.52zm6.283 0v4.96h2.057c1.979 0 2.88-1.046 2.88-2.472 0-1.36-.937-2.488-2.747-2.488zm1.237.91h.792c1.17 0 1.63.711 1.63 1.57 0 .728-.372 1.572-1.616 1.572h-.806zm-10.985.273l.791 1.932H2.008zm17.137.307l-1.604 1.603v2.25h2.246l1.604-1.607h-2.246z\"/></svg>",
|
| 53 |
+
"brand-intel": "<svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M20.42 7.345v9.18h1.651v-9.18zM0 7.475v1.737h1.737V7.474zm9.78.352v6.053c0 .513.044.945.13 1.292.087.34.235.618.44.828.203.21.475.359.803.451.334.093.754.136 1.255.136h.216v-1.533c-.24 0-.445-.012-.593-.037a.672.672 0 0 1-.39-.173.693.693 0 0 1-.173-.377 4.002 4.002 0 0 1-.037-.606v-2.182h1.193v-1.416h-1.193V7.827zm-3.505 2.312c-.396 0-.76.08-1.082.241-.327.161-.6.384-.822.668l-.087.117v-.902H2.658v6.256h1.639v-3.214c.018-.588.16-1.02.433-1.299.29-.297.642-.445 1.044-.445.476 0 .841.149 1.082.433.235.284.359.686.359 1.2v3.324h1.663V12.97c.006-.89-.229-1.595-.686-2.09-.458-.495-1.1-.742-1.917-.742zm10.065.006a3.252 3.252 0 0 0-2.306.946c-.29.29-.525.637-.692 1.033a3.145 3.145 0 0 0-.254 1.273c0 .452.08.878.241 1.274.161.395.39.742.674 1.032.284.29.637.526 1.045.693.408.173.86.26 1.342.26 1.397 0 2.262-.637 2.782-1.23l-1.187-.904c-.248.297-.841.699-1.583.699-.464 0-.847-.105-1.138-.321a1.588 1.588 0 0 1-.593-.872l-.019-.056h4.915v-.587c0-.451-.08-.872-.235-1.267a3.393 3.393 0 0 0-.661-1.033 3.013 3.013 0 0 0-1.02-.692 3.345 3.345 0 0 0-1.311-.248zm-16.297.118v6.256h1.651v-6.256zm16.278 1.286c1.132 0 1.664.797 1.664 1.255l-3.32.006c0-.458.525-1.255 1.656-1.261zm7.073 3.814a.606.606 0 0 0-.606.606.606.606 0 0 0 .606.606.606.606 0 0 0 .606-.606.606.606 0 0 0-.606-.606zm-.008.105a.5.5 0 0 1 .002 0 .5.5 0 0 1 .5.501.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .498-.5zm-.233.155v.699h.13v-.285h.093l.173.285h.136l-.18-.297a.191.191 0 0 0 .118-.056c.03-.03.05-.074.05-.136 0-.068-.02-.117-.063-.154-.037-.038-.105-.056-.185-.056zm.13.099h.154c.019 0 .037.006.056.012a.064.064 0 0 1 .037.031c.013.013.012.031.012.056a.124.124 0 0 1-.012.055.164.164 0 0 1-.037.031c-.019.006-.037.013-.056.013h-.154Z\"/></svg>"
|
| 54 |
+
};
|
static/index.html
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
+
<title>FitCheck — what AI can your computer run?</title>
|
| 7 |
+
<meta name="description" content="Tell FitCheck about your computer in plain words. Get an honest answer about what AI you can actually run on it, from chatbots to object detection, and how to start." />
|
| 8 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@600;700;800&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
| 10 |
+
<link rel="stylesheet" href="/static/style.css" />
|
| 11 |
+
</head>
|
| 12 |
+
<body>
|
| 13 |
+
<header class="topbar">
|
| 14 |
+
<div class="logo">
|
| 15 |
+
<span class="logo-mark"><span class="ic" data-ic="check"></span></span>
|
| 16 |
+
<span>Fit<span class="tick">Check</span></span>
|
| 17 |
+
</div>
|
| 18 |
+
<div class="spacer"></div>
|
| 19 |
+
<nav>
|
| 20 |
+
<a href="#how">How it works</a>
|
| 21 |
+
<a href="#glossary-anchor">Jargon, explained</a>
|
| 22 |
+
</nav>
|
| 23 |
+
</header>
|
| 24 |
+
|
| 25 |
+
<main class="container">
|
| 26 |
+
<section class="hero">
|
| 27 |
+
<h1>What AI can <span class="accent">your computer</span> actually run?</h1>
|
| 28 |
+
<p>Tell FitCheck about your setup in plain words. It tells you honestly what you
|
| 29 |
+
can run today, from chatbots to object detection, what it will feel like, and exactly how to start.</p>
|
| 30 |
+
</section>
|
| 31 |
+
|
| 32 |
+
<div class="layout">
|
| 33 |
+
<!-- ============================== INPUTS ============================== -->
|
| 34 |
+
<aside class="panel form-panel">
|
| 35 |
+
<!-- Step 1: machine -->
|
| 36 |
+
<div class="step">
|
| 37 |
+
<div class="step-head"><span class="step-num">1</span><h2>Your computer</h2></div>
|
| 38 |
+
|
| 39 |
+
<div class="field">
|
| 40 |
+
<span class="label">What kind of computer?</span>
|
| 41 |
+
<div class="segmented" id="computer-seg">
|
| 42 |
+
<button class="seg-btn active" data-val="Windows laptop"><span class="ic brand" data-ic="brand-windows"></span>Windows laptop</button>
|
| 43 |
+
<button class="seg-btn" data-val="Windows desktop"><span class="ic brand" data-ic="brand-windows"></span>Windows desktop</button>
|
| 44 |
+
<button class="seg-btn" data-val="Mac"><span class="ic brand" data-ic="brand-apple"></span>Mac</button>
|
| 45 |
+
<button class="seg-btn" data-val="Linux PC"><span class="ic brand" data-ic="brand-linux"></span>Linux PC</button>
|
| 46 |
+
<button class="seg-btn" data-val="Mini PC / Raspberry Pi" style="grid-column:1/-1"><span class="ic brand" data-ic="brand-raspberrypi"></span>Mini PC / Raspberry Pi</button>
|
| 47 |
+
</div>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div class="field">
|
| 51 |
+
<span class="label">Memory (RAM)</span>
|
| 52 |
+
<select id="ram">
|
| 53 |
+
<option>2 GB</option><option>4 GB</option><option>8 GB</option>
|
| 54 |
+
<option selected>16 GB</option><option>24 GB</option><option>32 GB</option>
|
| 55 |
+
<option>48 GB</option><option>64 GB</option><option>96 GB</option><option>128 GB</option>
|
| 56 |
+
</select>
|
| 57 |
+
<div class="hint">Not sure? See “How do I find my specs?” below.</div>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<div class="field" id="provider-field">
|
| 61 |
+
<span class="label">Graphics (GPU)</span>
|
| 62 |
+
<div class="segmented cols-3" id="provider-seg">
|
| 63 |
+
<button class="seg-btn active" data-val="none">None / built-in</button>
|
| 64 |
+
<button class="seg-btn" data-val="nvidia"><span class="ic brand" data-ic="brand-nvidia"></span>NVIDIA</button>
|
| 65 |
+
<button class="seg-btn" data-val="amd"><span class="ic brand" data-ic="brand-amd"></span>AMD</button>
|
| 66 |
+
<button class="seg-btn" data-val="apple"><span class="ic brand" data-ic="brand-apple"></span>Apple</button>
|
| 67 |
+
<button class="seg-btn" data-val="intel"><span class="ic brand" data-ic="brand-intel"></span>Intel</button>
|
| 68 |
+
<button class="seg-btn" data-val="unsure">Not sure</button>
|
| 69 |
+
</div>
|
| 70 |
+
<select id="gpu" style="margin-top:var(--s-3); display:none"></select>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<details class="disc" id="advanced-disc">
|
| 74 |
+
<summary>Advanced: enter exact numbers <span class="chev ic" data-ic="chevron"></span></summary>
|
| 75 |
+
<div class="disc-body">
|
| 76 |
+
<div class="field">
|
| 77 |
+
<span class="label">Graphics memory (VRAM), if you know it</span>
|
| 78 |
+
<input type="number" id="vram" placeholder="e.g. 8" min="0" max="200" step="1" />
|
| 79 |
+
<div class="hint">Overrides the GPU picker above. Leave blank to use the picker.</div>
|
| 80 |
+
</div>
|
| 81 |
+
<div class="field" style="margin-bottom:0">
|
| 82 |
+
<span class="label">Or paste / describe your specs</span>
|
| 83 |
+
<textarea id="paste" placeholder="Paste output from 'dxdiag' or 'Task Manager → Performance', or just describe it: 'Dell XPS, RTX 3050, 16GB'…"></textarea>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</details>
|
| 87 |
+
</div>
|
| 88 |
+
|
| 89 |
+
<!-- Step 2: goal -->
|
| 90 |
+
<div class="step">
|
| 91 |
+
<div class="step-head"><span class="step-num">2</span><h2>What do you want to do?</h2></div>
|
| 92 |
+
<div id="usecase-picker"><!-- rendered by app.js --></div>
|
| 93 |
+
<div class="field" id="custom-uc-field" style="display:none; margin-top:var(--s-3)">
|
| 94 |
+
<span class="label">Describe what you want to build</span>
|
| 95 |
+
<input type="text" id="custom-uc" placeholder="e.g. real-time hand tracking from my webcam" />
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
<!-- Step 3: preference -->
|
| 100 |
+
<div class="step">
|
| 101 |
+
<div class="step-head"><span class="step-num">3</span><h2>What matters most? <span class="optional">(optional)</span></h2></div>
|
| 102 |
+
<div class="field" style="margin-bottom:0">
|
| 103 |
+
<div class="segmented" id="priority-seg">
|
| 104 |
+
<button class="seg-btn active" data-val="balanced"><span class="ic" data-ic="balanced"></span>Balanced</button>
|
| 105 |
+
<button class="seg-btn" data-val="quality"><span class="ic" data-ic="quality"></span>Best quality</button>
|
| 106 |
+
<button class="seg-btn" data-val="speed"><span class="ic" data-ic="speed"></span>Fastest</button>
|
| 107 |
+
<button class="seg-btn" data-val="offline"><span class="ic" data-ic="offline"></span>Fully offline</button>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
</div>
|
| 111 |
+
|
| 112 |
+
<button class="cta" id="check-btn">Check my setup <span class="ic" data-ic="arrow"></span></button>
|
| 113 |
+
|
| 114 |
+
<details class="disc" id="find-specs">
|
| 115 |
+
<summary><span class="ic sum-ic" data-ic="search"></span>How do I find my specs? <span class="chev ic" data-ic="chevron"></span></summary>
|
| 116 |
+
<div class="disc-body" id="find-specs-body"><!-- filled by app.js per computer type --></div>
|
| 117 |
+
</details>
|
| 118 |
+
|
| 119 |
+
<span id="glossary-anchor"></span>
|
| 120 |
+
<details class="disc" id="glossary">
|
| 121 |
+
<summary><span class="ic sum-ic" data-ic="book"></span>What do these words mean? <span class="chev ic" data-ic="chevron"></span></summary>
|
| 122 |
+
<div class="disc-body">
|
| 123 |
+
<dl>
|
| 124 |
+
<div><dt>Model</dt><dd>The AI’s “brain”. Bigger means smarter, but heavier.</dd></div>
|
| 125 |
+
<div><dt>Parameters (e.g. 7B)</dt><dd>How big the brain is. 7B is 7 billion. More means smarter and hungrier for memory.</dd></div>
|
| 126 |
+
<div><dt>Quantisation (4-bit, 8-bit)</dt><dd>Shrinking a model so it fits. 4-bit is the popular sweet spot: much smaller, barely-noticeable quality loss.</dd></div>
|
| 127 |
+
<div><dt>VRAM</dt><dd>The fast memory on a graphics card. The biggest factor in what runs quickly.</dd></div>
|
| 128 |
+
<div><dt>RAM</dt><dd>Your computer’s normal memory. Models can use it too, but it is slower.</dd></div>
|
| 129 |
+
<div><dt>GGUF</dt><dd>A single-file model format made for running locally.</dd></div>
|
| 130 |
+
</dl>
|
| 131 |
+
</div>
|
| 132 |
+
</details>
|
| 133 |
+
</aside>
|
| 134 |
+
|
| 135 |
+
<!-- ============================== RESULTS ============================== -->
|
| 136 |
+
<section class="panel results-panel" id="results">
|
| 137 |
+
<div class="empty-state">
|
| 138 |
+
<div class="big"><span class="ic" data-ic="monitor"></span></div>
|
| 139 |
+
<h2>Your honest answer appears here</h2>
|
| 140 |
+
<p>Fill in your computer and what you want to do, then press <b>Check my setup</b>. Results update as you change things.</p>
|
| 141 |
+
</div>
|
| 142 |
+
</section>
|
| 143 |
+
</div>
|
| 144 |
+
|
| 145 |
+
<footer class="foot" id="how">
|
| 146 |
+
<p>FitCheck gives <b>conservative</b> estimates from a transparent rules engine.
|
| 147 |
+
It would rather under-promise than over-promise. Real speed depends on your exact chip, drivers and settings.</p>
|
| 148 |
+
<p style="margin-top:8px">Catalogue last updated <b id="cat-version">—</b>. Built for the Hugging Face Build Small hackathon.</p>
|
| 149 |
+
</footer>
|
| 150 |
+
</main>
|
| 151 |
+
|
| 152 |
+
<script src="/static/icons.js"></script>
|
| 153 |
+
<script src="/static/app.js"></script>
|
| 154 |
+
</body>
|
| 155 |
+
</html>
|
static/style.css
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ============================================================
|
| 2 |
+
FitCheck — design system
|
| 3 |
+
Dark-first. Every text/status colour clears WCAG AA on the
|
| 4 |
+
card surface (#181B20). Tokens verified by research agent.
|
| 5 |
+
============================================================ */
|
| 6 |
+
|
| 7 |
+
:root {
|
| 8 |
+
/* Background layers */
|
| 9 |
+
--bg-base: #0F1115;
|
| 10 |
+
--bg-raised: #181B20;
|
| 11 |
+
--bg-inset: #22262E;
|
| 12 |
+
--border: #2A2F38;
|
| 13 |
+
--border-hi: #3A4150;
|
| 14 |
+
|
| 15 |
+
/* Text */
|
| 16 |
+
--text-primary: #ECEEF2;
|
| 17 |
+
--text-secondary: #B4BAC4;
|
| 18 |
+
--text-muted: #868E9C;
|
| 19 |
+
|
| 20 |
+
/* Accent — warm indigo */
|
| 21 |
+
--accent: #818CF8;
|
| 22 |
+
--accent-strong: #6366F1;
|
| 23 |
+
--accent-soft: rgba(129,140,248,0.14);
|
| 24 |
+
|
| 25 |
+
/* Status */
|
| 26 |
+
--ok: #4ADE80;
|
| 27 |
+
--warn: #FBBF24;
|
| 28 |
+
--no: #F87171;
|
| 29 |
+
--ok-soft: rgba(74,222,128,0.13);
|
| 30 |
+
--warn-soft: rgba(251,191,36,0.13);
|
| 31 |
+
--no-soft: rgba(248,113,113,0.13);
|
| 32 |
+
|
| 33 |
+
/* Spacing scale (4px base) */
|
| 34 |
+
--s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px;
|
| 35 |
+
--s-5:24px; --s-6:32px; --s-7:48px; --s-8:64px;
|
| 36 |
+
|
| 37 |
+
/* Radius */
|
| 38 |
+
--r-sm:8px; --r-md:14px; --r-lg:20px; --r-pill:999px;
|
| 39 |
+
|
| 40 |
+
/* Shadow */
|
| 41 |
+
--shadow-sm: 0 1px 2px rgba(0,0,0,0.30);
|
| 42 |
+
--shadow-md: 0 4px 16px rgba(0,0,0,0.35);
|
| 43 |
+
--shadow-lg: 0 12px 32px rgba(0,0,0,0.45);
|
| 44 |
+
--glow: 0 0 0 1px var(--accent-soft), 0 8px 24px rgba(99,102,241,0.22);
|
| 45 |
+
|
| 46 |
+
--font-head: 'Plus Jakarta Sans', system-ui, sans-serif;
|
| 47 |
+
--font-body: 'Inter', system-ui, sans-serif;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
*, *::before, *::after { box-sizing: border-box; }
|
| 51 |
+
* { margin: 0; }
|
| 52 |
+
|
| 53 |
+
html { scroll-behavior: smooth; }
|
| 54 |
+
body {
|
| 55 |
+
font-family: var(--font-body);
|
| 56 |
+
background:
|
| 57 |
+
radial-gradient(1200px 600px at 80% -10%, rgba(99,102,241,0.10), transparent 60%),
|
| 58 |
+
radial-gradient(1000px 500px at -10% 10%, rgba(74,222,128,0.05), transparent 55%),
|
| 59 |
+
var(--bg-base);
|
| 60 |
+
color: var(--text-primary);
|
| 61 |
+
font-size: 16px;
|
| 62 |
+
line-height: 1.6;
|
| 63 |
+
-webkit-font-smoothing: antialiased;
|
| 64 |
+
min-height: 100vh;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
h1,h2,h3 { font-family: var(--font-head); line-height: 1.2; letter-spacing: -0.02em; }
|
| 68 |
+
a { color: var(--accent); text-decoration: none; }
|
| 69 |
+
a:hover { text-decoration: underline; }
|
| 70 |
+
button { font-family: inherit; cursor: pointer; }
|
| 71 |
+
|
| 72 |
+
.label {
|
| 73 |
+
display: block;
|
| 74 |
+
font-size: 13px; font-weight: 600;
|
| 75 |
+
text-transform: uppercase; letter-spacing: 0.04em;
|
| 76 |
+
color: var(--text-secondary);
|
| 77 |
+
margin-bottom: var(--s-2);
|
| 78 |
+
}
|
| 79 |
+
.hint { font-size: 13px; color: var(--text-muted); margin-top: var(--s-1); }
|
| 80 |
+
|
| 81 |
+
/* Icons (inline SVG; lucide uses stroke, brand logos use fill) */
|
| 82 |
+
.ic { display: inline-flex; align-items: center; justify-content: center; line-height: 0; flex: none; }
|
| 83 |
+
.ic svg { width: 1em; height: 1em; display: block; }
|
| 84 |
+
.optional { font-weight: 500; color: var(--text-muted); font-size: 13px; }
|
| 85 |
+
|
| 86 |
+
/* ----------------------------------------------------------- Topbar */
|
| 87 |
+
.topbar {
|
| 88 |
+
display: flex; align-items: center; gap: var(--s-3);
|
| 89 |
+
padding: var(--s-4) var(--s-5);
|
| 90 |
+
max-width: 1180px; margin: 0 auto;
|
| 91 |
+
}
|
| 92 |
+
.logo {
|
| 93 |
+
display: flex; align-items: center; gap: var(--s-2);
|
| 94 |
+
font-family: var(--font-head); font-weight: 800; font-size: 20px;
|
| 95 |
+
letter-spacing: -0.03em;
|
| 96 |
+
}
|
| 97 |
+
.logo-mark {
|
| 98 |
+
width: 32px; height: 32px; border-radius: 9px;
|
| 99 |
+
background: linear-gradient(135deg, var(--accent), var(--accent-strong));
|
| 100 |
+
display: grid; place-items: center;
|
| 101 |
+
box-shadow: var(--glow);
|
| 102 |
+
color: #fff;
|
| 103 |
+
}
|
| 104 |
+
.logo-mark .ic { font-size: 18px; }
|
| 105 |
+
.logo-mark .ic svg { stroke-width: 3; }
|
| 106 |
+
.logo .tick { color: var(--ok); }
|
| 107 |
+
.topbar .spacer { flex: 1; }
|
| 108 |
+
.topbar nav a { color: var(--text-secondary); font-size: 14px; font-weight: 500; margin-left: var(--s-4); }
|
| 109 |
+
.topbar nav a:hover { color: var(--text-primary); text-decoration: none; }
|
| 110 |
+
|
| 111 |
+
/* ----------------------------------------------------------- Layout */
|
| 112 |
+
.container { max-width: 1180px; margin: 0 auto; padding: 0 var(--s-5) var(--s-8); }
|
| 113 |
+
|
| 114 |
+
.hero { text-align: center; padding: var(--s-6) 0 var(--s-7); }
|
| 115 |
+
.hero .eyebrow {
|
| 116 |
+
display: inline-flex; align-items: center; gap: var(--s-2);
|
| 117 |
+
font-size: 13px; font-weight: 600; color: var(--accent);
|
| 118 |
+
background: var(--accent-soft); padding: 5px 12px; border-radius: var(--r-pill);
|
| 119 |
+
margin-bottom: var(--s-4);
|
| 120 |
+
}
|
| 121 |
+
.hero h1 { font-size: clamp(28px, 4vw, 44px); font-weight: 800; }
|
| 122 |
+
.hero h1 .accent { color: var(--accent); }
|
| 123 |
+
.hero p { color: var(--text-secondary); font-size: clamp(15px,1.6vw,18px); max-width: 620px; margin: var(--s-4) auto 0; }
|
| 124 |
+
|
| 125 |
+
.layout {
|
| 126 |
+
display: grid;
|
| 127 |
+
grid-template-columns: 420px 1fr;
|
| 128 |
+
gap: var(--s-5);
|
| 129 |
+
align-items: start;
|
| 130 |
+
}
|
| 131 |
+
@media (max-width: 900px) { .layout { grid-template-columns: 1fr; } }
|
| 132 |
+
|
| 133 |
+
.panel {
|
| 134 |
+
background: var(--bg-raised);
|
| 135 |
+
border: 1px solid var(--border);
|
| 136 |
+
border-radius: var(--r-lg);
|
| 137 |
+
box-shadow: var(--shadow-md);
|
| 138 |
+
}
|
| 139 |
+
.form-panel { padding: var(--s-5); position: sticky; top: var(--s-4); }
|
| 140 |
+
@media (max-width: 900px) { .form-panel { position: static; } }
|
| 141 |
+
|
| 142 |
+
.step { margin-bottom: var(--s-5); }
|
| 143 |
+
.step-head { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
|
| 144 |
+
.step-num {
|
| 145 |
+
width: 26px; height: 26px; border-radius: var(--r-pill); flex: none;
|
| 146 |
+
background: var(--accent-soft); color: var(--accent);
|
| 147 |
+
display: grid; place-items: center; font-weight: 700; font-size: 14px;
|
| 148 |
+
font-family: var(--font-head);
|
| 149 |
+
}
|
| 150 |
+
.step-head h2 { font-size: 17px; font-weight: 700; }
|
| 151 |
+
|
| 152 |
+
.field { margin-bottom: var(--s-4); }
|
| 153 |
+
|
| 154 |
+
/* Segmented control */
|
| 155 |
+
.segmented { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }
|
| 156 |
+
.segmented.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
| 157 |
+
.seg-btn {
|
| 158 |
+
background: var(--bg-inset); border: 1px solid var(--border);
|
| 159 |
+
color: var(--text-secondary); border-radius: var(--r-sm);
|
| 160 |
+
padding: 10px 12px; font-size: 14px; font-weight: 500;
|
| 161 |
+
display: flex; align-items: center; gap: var(--s-2); justify-content: center;
|
| 162 |
+
transition: transform .15s, border-color .15s, color .15s, background .15s;
|
| 163 |
+
}
|
| 164 |
+
.seg-btn:hover { border-color: var(--border-hi); color: var(--text-primary); }
|
| 165 |
+
.seg-btn.active {
|
| 166 |
+
background: var(--accent-soft); border-color: var(--accent);
|
| 167 |
+
color: var(--text-primary); box-shadow: var(--shadow-sm);
|
| 168 |
+
}
|
| 169 |
+
.seg-btn .ic { font-size: 17px; color: var(--text-muted); }
|
| 170 |
+
.seg-btn.active .ic { color: var(--accent); }
|
| 171 |
+
.seg-btn .ic.brand { color: var(--text-secondary); }
|
| 172 |
+
.seg-btn.active .ic.brand { color: var(--text-primary); }
|
| 173 |
+
|
| 174 |
+
/* Native select / input styling */
|
| 175 |
+
select, input[type="text"], input[type="number"], textarea {
|
| 176 |
+
width: 100%; font-family: inherit; font-size: 15px;
|
| 177 |
+
color: var(--text-primary);
|
| 178 |
+
background: var(--bg-inset);
|
| 179 |
+
border: 1px solid var(--border); border-radius: var(--r-sm);
|
| 180 |
+
padding: 11px 13px;
|
| 181 |
+
transition: border-color .15s, box-shadow .15s;
|
| 182 |
+
}
|
| 183 |
+
select:hover, input:hover, textarea:hover { border-color: var(--border-hi); }
|
| 184 |
+
select:focus, input:focus, textarea:focus {
|
| 185 |
+
outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
|
| 186 |
+
}
|
| 187 |
+
select {
|
| 188 |
+
appearance: none;
|
| 189 |
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23868E9C' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
| 190 |
+
background-repeat: no-repeat; background-position: right 12px center;
|
| 191 |
+
padding-right: 38px;
|
| 192 |
+
}
|
| 193 |
+
textarea { resize: vertical; min-height: 70px; line-height: 1.5; }
|
| 194 |
+
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
|
| 195 |
+
|
| 196 |
+
/* Use-case picker */
|
| 197 |
+
.uc-group { margin-bottom: var(--s-4); }
|
| 198 |
+
.uc-cat {
|
| 199 |
+
font-size: 12px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
|
| 200 |
+
color: var(--text-muted); margin-bottom: var(--s-2); display: flex; align-items: center; gap: 6px;
|
| 201 |
+
}
|
| 202 |
+
.uc-grid { display: flex; flex-wrap: wrap; gap: var(--s-2); }
|
| 203 |
+
.uc-pill {
|
| 204 |
+
background: var(--bg-inset); border: 1px solid var(--border);
|
| 205 |
+
color: var(--text-secondary); border-radius: var(--r-pill);
|
| 206 |
+
padding: 7px 13px; font-size: 13.5px; font-weight: 500;
|
| 207 |
+
display: inline-flex; align-items: center; gap: 6px;
|
| 208 |
+
transition: transform .15s, border-color .15s, color .15s, background .15s;
|
| 209 |
+
}
|
| 210 |
+
.uc-pill:hover { border-color: var(--border-hi); color: var(--text-primary); transform: translateY(-1px); }
|
| 211 |
+
.uc-pill.active {
|
| 212 |
+
background: var(--accent-soft); border-color: var(--accent); color: var(--text-primary);
|
| 213 |
+
}
|
| 214 |
+
.uc-pill .pic { font-size: 15px; color: var(--text-muted); }
|
| 215 |
+
.uc-pill:hover .pic { color: var(--text-secondary); }
|
| 216 |
+
.uc-pill.active .pic { color: var(--accent); }
|
| 217 |
+
.uc-cat .ic { font-size: 14px; color: var(--text-muted); }
|
| 218 |
+
|
| 219 |
+
/* Disclosure (advanced / accordions) */
|
| 220 |
+
details.disc {
|
| 221 |
+
border: 1px solid var(--border); border-radius: var(--r-md);
|
| 222 |
+
background: var(--bg-inset); margin-top: var(--s-3); overflow: hidden;
|
| 223 |
+
}
|
| 224 |
+
details.disc > summary {
|
| 225 |
+
list-style: none; cursor: pointer; padding: 12px 14px;
|
| 226 |
+
font-size: 14px; font-weight: 600; color: var(--text-secondary);
|
| 227 |
+
display: flex; align-items: center; gap: var(--s-2);
|
| 228 |
+
}
|
| 229 |
+
details.disc > summary::-webkit-details-marker { display: none; }
|
| 230 |
+
details.disc > summary .chev { margin-left: auto; transition: transform .2s; color: var(--text-muted); font-size: 16px; }
|
| 231 |
+
details.disc[open] > summary .chev { transform: rotate(180deg); }
|
| 232 |
+
details.disc > summary .sum-ic { font-size: 16px; color: var(--text-muted); }
|
| 233 |
+
details.disc > summary:hover { color: var(--text-primary); }
|
| 234 |
+
.disc-body { padding: 0 14px 14px; font-size: 14px; color: var(--text-secondary); line-height: 1.6; }
|
| 235 |
+
.disc-body code {
|
| 236 |
+
background: var(--bg-base); border: 1px solid var(--border); border-radius: 5px;
|
| 237 |
+
padding: 1px 6px; font-size: 13px; color: var(--text-primary);
|
| 238 |
+
}
|
| 239 |
+
.disc-body dl { display: grid; gap: var(--s-3); }
|
| 240 |
+
.disc-body dt { font-weight: 700; color: var(--text-primary); font-family: var(--font-head); }
|
| 241 |
+
.disc-body dd { color: var(--text-muted); }
|
| 242 |
+
|
| 243 |
+
/* Primary CTA */
|
| 244 |
+
.cta {
|
| 245 |
+
width: 100%; border: none; border-radius: var(--r-md);
|
| 246 |
+
background: linear-gradient(135deg, var(--accent), var(--accent-strong));
|
| 247 |
+
color: #fff; font-weight: 700; font-size: 16px; font-family: var(--font-head);
|
| 248 |
+
padding: 14px; margin-top: var(--s-2);
|
| 249 |
+
display: flex; align-items: center; justify-content: center; gap: var(--s-2);
|
| 250 |
+
transition: transform .15s, box-shadow .15s, filter .15s;
|
| 251 |
+
}
|
| 252 |
+
.cta:hover { transform: translateY(-2px); box-shadow: var(--glow); filter: brightness(1.05); }
|
| 253 |
+
.cta:active { transform: translateY(0); }
|
| 254 |
+
.cta .ic { font-size: 18px; }
|
| 255 |
+
|
| 256 |
+
/* ----------------------------------------------------------- Results */
|
| 257 |
+
.results-panel { padding: var(--s-5); min-height: 400px; }
|
| 258 |
+
|
| 259 |
+
.empty-state { text-align: center; color: var(--text-muted); padding: var(--s-8) var(--s-4); }
|
| 260 |
+
.empty-state .big { font-size: 46px; margin-bottom: var(--s-3); color: var(--text-muted); display: flex; justify-content: center; }
|
| 261 |
+
.empty-state p { max-width: 360px; margin: var(--s-2) auto 0; }
|
| 262 |
+
|
| 263 |
+
.verdict {
|
| 264 |
+
border: 1px solid var(--border); border-left: 4px solid var(--status, var(--accent));
|
| 265 |
+
border-radius: var(--r-md); background: var(--bg-raised);
|
| 266 |
+
padding: var(--s-5); margin-bottom: var(--s-5);
|
| 267 |
+
}
|
| 268 |
+
.verdict .badge {
|
| 269 |
+
display: inline-flex; align-items: center; gap: 7px;
|
| 270 |
+
padding: 5px 12px; border-radius: var(--r-pill);
|
| 271 |
+
background: var(--status-soft, var(--accent-soft)); color: var(--status, var(--accent));
|
| 272 |
+
font: 700 13px/1 var(--font-head); text-transform: uppercase; letter-spacing: .04em;
|
| 273 |
+
margin-bottom: var(--s-3);
|
| 274 |
+
}
|
| 275 |
+
.verdict .badge .dot { width: 8px; height: 8px; border-radius: 50%; background: currentColor; }
|
| 276 |
+
.verdict h2 { font-size: 22px; margin-bottom: var(--s-3); }
|
| 277 |
+
.verdict p { color: var(--text-secondary); margin-bottom: var(--s-2); }
|
| 278 |
+
.verdict .note {
|
| 279 |
+
font-size: 13.5px; color: var(--text-muted); margin-top: var(--s-3);
|
| 280 |
+
padding-top: var(--s-3); border-top: 1px solid var(--border);
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
.section-title {
|
| 284 |
+
font-size: 15px; font-weight: 700; font-family: var(--font-head);
|
| 285 |
+
color: var(--text-primary); margin: var(--s-5) 0 var(--s-3);
|
| 286 |
+
display: flex; align-items: center; gap: var(--s-2);
|
| 287 |
+
}
|
| 288 |
+
.section-title .sub { font-size: 13px; font-weight: 500; color: var(--text-muted); font-family: var(--font-body); }
|
| 289 |
+
|
| 290 |
+
/* Gauge */
|
| 291 |
+
.gauge-card { background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--s-4) var(--s-4) var(--s-3); }
|
| 292 |
+
.gauge-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; }
|
| 293 |
+
.gauge-top .need { font-weight: 700; color: var(--status, var(--text-primary)); font-family: var(--font-head); }
|
| 294 |
+
.gauge {
|
| 295 |
+
position: relative; height: 16px; border-radius: var(--r-pill);
|
| 296 |
+
background: var(--bg-base); overflow: hidden; border: 1px solid var(--border);
|
| 297 |
+
}
|
| 298 |
+
.gauge-fill {
|
| 299 |
+
height: 100%; width: calc(var(--pct,0) * 1%); border-radius: var(--r-pill);
|
| 300 |
+
background: linear-gradient(90deg, var(--gcolor, var(--accent)), color-mix(in srgb, var(--gcolor, var(--accent)) 65%, #fff));
|
| 301 |
+
transition: width .8s cubic-bezier(.22,1,.36,1), background .4s;
|
| 302 |
+
}
|
| 303 |
+
.gauge-need {
|
| 304 |
+
position: absolute; top: -4px; width: 2px; height: 24px;
|
| 305 |
+
background: var(--text-primary); border-radius: 2px; opacity: .85;
|
| 306 |
+
left: calc(var(--needpct,50)*1%); transition: left .8s cubic-bezier(.22,1,.36,1);
|
| 307 |
+
}
|
| 308 |
+
.gauge-legend { display: flex; flex-wrap: wrap; gap: var(--s-4); margin-top: 12px; font-size: 12.5px; color: var(--text-muted); }
|
| 309 |
+
.gauge-legend .item { display: inline-flex; align-items: center; gap: 6px; }
|
| 310 |
+
.gauge-legend .sw { width: 10px; height: 10px; border-radius: 3px; }
|
| 311 |
+
.gauge-legend .marker { font-weight: 600; color: var(--text-secondary); }
|
| 312 |
+
|
| 313 |
+
/* Option cards (the matrix, re-imagined) */
|
| 314 |
+
.opt-grid { display: grid; gap: var(--s-3); }
|
| 315 |
+
.opt {
|
| 316 |
+
display: grid; grid-template-columns: 40px 1fr auto; align-items: center; gap: var(--s-3);
|
| 317 |
+
background: var(--bg-raised); border: 1px solid var(--border);
|
| 318 |
+
border-left: 4px solid var(--status, var(--border)); border-radius: var(--r-md);
|
| 319 |
+
padding: var(--s-4);
|
| 320 |
+
transition: transform .2s, box-shadow .2s, border-color .2s;
|
| 321 |
+
}
|
| 322 |
+
.opt:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
|
| 323 |
+
.opt .vdot {
|
| 324 |
+
width: 30px; height: 30px; border-radius: 50%;
|
| 325 |
+
background: var(--status-soft); color: var(--status);
|
| 326 |
+
display: grid; place-items: center; font-size: 15px; font-weight: 700;
|
| 327 |
+
}
|
| 328 |
+
.opt .name { font-weight: 700; font-family: var(--font-head); font-size: 15px; }
|
| 329 |
+
.opt .desc { font-size: 13px; color: var(--text-muted); margin-top: 2px; }
|
| 330 |
+
.opt .meta { text-align: right; font-size: 13px; color: var(--text-secondary); white-space: nowrap; }
|
| 331 |
+
.opt .meta b { color: var(--text-primary); font-family: var(--font-head); }
|
| 332 |
+
.opt .feel { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }
|
| 333 |
+
|
| 334 |
+
/* Tool cards */
|
| 335 |
+
.tool-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px,1fr)); gap: var(--s-3); }
|
| 336 |
+
.tool {
|
| 337 |
+
background: var(--bg-raised); border: 1px solid var(--border);
|
| 338 |
+
border-radius: var(--r-md); padding: var(--s-4);
|
| 339 |
+
transition: transform .2s, box-shadow .2s, border-color .2s;
|
| 340 |
+
}
|
| 341 |
+
.tool:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--border-hi); }
|
| 342 |
+
.tool .tool-head { display: flex; align-items: center; justify-content: space-between; }
|
| 343 |
+
.tool .tname { font-weight: 700; font-family: var(--font-head); font-size: 15px; }
|
| 344 |
+
.tool .tag { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; padding: 3px 9px; border-radius: var(--r-pill); }
|
| 345 |
+
.tool .tag.best { background: var(--ok-soft); color: var(--ok); }
|
| 346 |
+
.tool .tag.mid { background: var(--accent-soft); color: var(--accent); }
|
| 347 |
+
.tool .twhat { font-size: 13.5px; color: var(--text-secondary); margin: var(--s-2) 0; line-height: 1.5; }
|
| 348 |
+
.tool .tinstall { font-size: 12.5px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; }
|
| 349 |
+
.tool .tinstall .ic { font-size: 14px; }
|
| 350 |
+
|
| 351 |
+
/* Command block */
|
| 352 |
+
.cmd-intro { font-size: 14px; color: var(--text-secondary); margin: var(--s-2) 0 var(--s-3); }
|
| 353 |
+
.cmd { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
|
| 354 |
+
@media (max-width: 620px) { .cmd { grid-template-columns: 1fr; } }
|
| 355 |
+
.cmd-box { background: var(--bg-base); border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
|
| 356 |
+
.cmd-box .cmd-label { font-size: 12px; font-weight: 600; color: var(--text-muted); padding: 10px 13px 0; display:flex; justify-content:space-between; align-items:center; }
|
| 357 |
+
.cmd-box pre { padding: 8px 13px 13px; overflow-x: auto; }
|
| 358 |
+
.cmd-box code { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 13px; color: #a5f3c4; }
|
| 359 |
+
.copy-btn {
|
| 360 |
+
background: var(--bg-inset); border: 1px solid var(--border); color: var(--text-secondary);
|
| 361 |
+
border-radius: 6px; padding: 3px 9px; font-size: 12px; font-weight: 600;
|
| 362 |
+
}
|
| 363 |
+
.copy-btn:hover { color: var(--text-primary); border-color: var(--border-hi); }
|
| 364 |
+
.copy-btn.done { color: var(--ok); border-color: var(--ok); }
|
| 365 |
+
|
| 366 |
+
/* Footer */
|
| 367 |
+
.foot { text-align: center; color: var(--text-muted); font-size: 13px; margin-top: var(--s-7); line-height: 1.6; }
|
| 368 |
+
.foot b { color: var(--text-secondary); }
|
| 369 |
+
|
| 370 |
+
/* Reveal animation */
|
| 371 |
+
.reveal { animation: reveal .5s cubic-bezier(.22,1,.36,1) both; }
|
| 372 |
+
@keyframes reveal { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
|
| 373 |
+
|
| 374 |
+
@media (prefers-reduced-motion: reduce) {
|
| 375 |
+
*, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
|
| 376 |
+
}
|