mirror of
https://github.com/baairon/torlink.git
synced 2026-07-08 18:28:22 +02:00
feat: wordmark + progress-bar sheen, dark-violet svg base, readme refinements
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 bairon.dev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -2,7 +2,7 @@
|
||||
<img src="preview/splash.svg" alt="torlink, curated torrents straight from your terminal" style="max-width: 832px; width: 100%; height: auto;">
|
||||
</p>
|
||||
|
||||
torlink is a torrent finder that lives in your terminal, with zero setup and nothing to configure. One search checks a short, curated list of reputable sources at once, and whatever you pick downloads straight to your computer. The files are yours, saved to your downloads folder.
|
||||
torlink is a torrent finder that lives in your terminal, with zero setup and nothing to configure. One search checks a short, curated list of reputable sources at once, and whatever you pick downloads straight to your computer, never seeding back out. The files are yours, saved to your downloads folder.
|
||||
|
||||
## Get started
|
||||
|
||||
@@ -22,16 +22,9 @@ npx torlink "<path/to/file.torrent>" # open a .torrent file
|
||||
npm install -g torlink # install for good, then run: torlink
|
||||
```
|
||||
|
||||
## What it does
|
||||
|
||||
- **One search, every source.** Type once and torlink checks them all together, showing each result's size and how many people are sharing it.
|
||||
- **Curated, not a free-for-all.** A small, vetted set of sources, tagged so you always know where a result came from.
|
||||
- **Downloads only, never seeds.** Files come to your computer and stop there. torlink never sends them back out.
|
||||
- **Nothing to configure.** It opens straight to a search box, ready the moment it starts.
|
||||
|
||||
## Finding something
|
||||
|
||||
Type what you're looking for and press Enter. torlink checks every source at once, and results show up as each one answers, so you're never stuck waiting on a slow one. Each result lists its size and how many people are sharing it, so you can see which one will come down fast. Arrow to the one you want, press `d`, and it saves to your downloads folder. Leave the box empty and press Enter to just see what's new across every source.
|
||||
Type what you're looking for and press Enter. Results stream in from every source as they answer, tagged with size and how many people are sharing each one, so you can see what'll come down fast. Arrow to what you want and press `d` to save it.
|
||||
|
||||
<p align="center">
|
||||
<img src="preview/browse.svg" alt="torlink's browse view: the sidebar, the search bar, and merged results from every source" style="max-width: 832px; width: 100%; height: auto;">
|
||||
@@ -39,7 +32,9 @@ Type what you're looking for and press Enter. torlink checks every source at onc
|
||||
|
||||
## Your downloads
|
||||
|
||||
Your active downloads and the stuff you've already grabbed share one pane. Whatever's still coming down sits up top with its progress, speed, and time left. When a file finishes it drops into Recently downloaded just below, so the list stays tidy instead of piling up. Anything there is one key from downloading again, and it's all still waiting when you come back, resuming anything that got interrupted.
|
||||
Active downloads sit up top with their progress, speed, and time left; when one finishes it drops into Recently downloaded just below, so the list stays tidy. Everything's still there when you come back, and anything interrupted picks up where it left off.
|
||||
|
||||
Downloads run in the background while you keep searching, so you can queue up as many as you want. They save to your downloads folder, and the Downloads pane keeps tabs on each one.
|
||||
|
||||
<p align="center">
|
||||
<img src="preview/downloads.svg" alt="torlink's Downloads pane: live progress on top, recently downloaded below" style="max-width: 832px; width: 100%; height: auto;">
|
||||
@@ -56,7 +51,7 @@ A short, hand-picked list of trusted sources:
|
||||
| TV | EZTV, SolidTorrents, The Pirate Bay, 1337x |
|
||||
| Anime | Nyaa, SubsPlease |
|
||||
|
||||
Games are the only category that can run code, so they come from FitGirl alone, a repacker with a long, trusted track record; everything else is plain video and subtitles. A source being down never stops a search, the others keep going and torlink tells you which one is offline.
|
||||
Games are the only category that can run code, so they come from FitGirl alone, a repacker with a long, trusted track record; everything else is plain video and subtitles. If a source is down, the search carries on without it, and torlink tells you which one is offline.
|
||||
|
||||
## Local Development
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="832" height="540" viewBox="0 0 832 540" style="max-width: 832px; width: 100%; height: auto;" role="img">
|
||||
<rect width="100%" height="100%" rx="14" fill="#0c0b14"/>
|
||||
<rect x="0.5" y="0.5" width="831" height="539" rx="14" fill="none" stroke="#272430" stroke-width="1"/>
|
||||
<rect width="100%" height="100%" rx="14" fill="#0a0810"/>
|
||||
<rect x="0.5" y="0.5" width="831" height="539" rx="14" fill="none" stroke="#2d2b31" stroke-width="1"/>
|
||||
<text x="416" y="23" text-anchor="middle" font-family='ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace' font-size="13" fill="#8a8a8a">torlink</text>
|
||||
<g font-family='ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace' font-size="16" fill="#ddd8ea">
|
||||
<text x="99.2" y="84" textLength="9.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#5ae87a" font-weight="600">𐓏</text>
|
||||
@@ -47,7 +47,7 @@
|
||||
<text x="60.8" y="216" textLength="48" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill-opacity="0.55">Games</text>
|
||||
<rect x="208.9" y="200" width="1.4" height="22" fill="#6b6577"/>
|
||||
<text x="224" y="216" textLength="9.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#a78bfa">❯</text>
|
||||
<text x="243.2" y="216" textLength="249.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill-opacity="0.55">Search, or paste a magnet…</text>
|
||||
<text x="243.2" y="216" textLength="288" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill-opacity="0.55">Search or paste a magnet link…</text>
|
||||
<rect x="784.9" y="200" width="1.4" height="22" fill="#6b6577"/>
|
||||
<text x="60.8" y="238" textLength="57.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill-opacity="0.55">Movies</text>
|
||||
<text x="204.8" y="238" textLength="585.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#6b6577">╰───────────────────────────────────────────────────────────╯</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
+44
-2
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="832" height="452" viewBox="0 0 832 452" style="max-width: 832px; width: 100%; height: auto;" role="img">
|
||||
<rect width="100%" height="100%" rx="14" fill="#0c0b14"/>
|
||||
<rect x="0.5" y="0.5" width="831" height="451" rx="14" fill="none" stroke="#272430" stroke-width="1"/>
|
||||
<rect width="100%" height="100%" rx="14" fill="#0a0810"/>
|
||||
<rect x="0.5" y="0.5" width="831" height="451" rx="14" fill="none" stroke="#2d2b31" stroke-width="1"/>
|
||||
<text x="416" y="23" text-anchor="middle" font-family='ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace' font-size="13" fill="#8a8a8a">torlink</text>
|
||||
<g font-family='ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace' font-size="16" fill="#ddd8ea">
|
||||
<text x="99.2" y="84" textLength="9.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#5ae87a" font-weight="600">𐓏</text>
|
||||
@@ -108,4 +108,46 @@
|
||||
<text x="416" y="414" textLength="9.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#b9a7e6">?</text>
|
||||
<text x="435.2" y="414" textLength="38.4" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill-opacity="0.55">Keys</text>
|
||||
</g>
|
||||
<rect x="262.4" y="222" width="9.6" height="22" fill="#7c5cd6">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#7c5cd6;#7f5fd7;#8669da;#9277de;#a18ae3;#b29ee8;#c3b3ee;#d2c5f3;#ded4f7;#e5ddfa;#e8e0fb;#e5ddfa;#ded4f7;#d2c5f3;#c3b3ee;#b29ee8;#a18ae3;#9277de;#8669da;#7f5fd7;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6;#7c5cd6" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="272" y="222" width="9.6" height="22" fill="#8060d9">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#8060d9;#8060d9;#8060d9;#8262da;#886adc;#9377df;#a188e4;#b19ce9;#c1b0ee;#d0c2f3;#dcd2f7;#e5dcfa;#e8e1fb;#e7dffb;#e0d7f9;#d6caf5;#c8b8f1;#b8a5eb;#a891e6;#997ee1;#8c6fdd;#8465da;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9;#8060d9" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="281.6" y="222" width="9.6" height="22" fill="#8465dd">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8566dd;#8a6cdf;#9378e2;#a088e6;#af9aea;#bfaeef;#cec0f3;#dbd0f7;#e4dbfa;#e8e1fb;#e8e0fb;#e3dafa;#d9cef7;#ccbef3;#bdacee;#ae98ea;#9f86e5;#9276e1;#896bdf;#8466dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd;#8465dd" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="291.2" y="222" width="9.6" height="22" fill="#8869e0">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8c6ee1;#9478e4;#a087e7;#ae99eb;#beacef;#ccbef4;#d9cef7;#e3dafa;#e8e0fc;#e9e1fc;#e5dcfb;#ddd2f8;#d1c3f5;#c3b2f1;#b49fec;#a58de8;#987de5;#8f71e2;#896ae0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0;#8869e0" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="300.8" y="222" width="9.6" height="22" fill="#8c6ee4">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8f72e5;#967ae7;#a188e9;#ae98ed;#bcaaf1;#cbbcf4;#d8ccf8;#e2d8fa;#e8e0fc;#eae2fc;#e7dffc;#e0d6fa;#d5c9f7;#c8b8f3;#b9a6f0;#ab94ec;#9e84e9;#9478e6;#8e70e4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4;#8c6ee4" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="310.4" y="222" width="9.6" height="22" fill="#9072e7">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9274e7;#987ce9;#a188eb;#ae97ee;#bba8f1;#c9baf5;#d6caf8;#e0d7fa;#e7dffc;#eae2fd;#e8e0fc;#e2d9fb;#d9cdf8;#ccbdf5;#bfacf2;#b19bef;#a48bec;#9a7ee9;#9375e8;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7;#9072e7" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="320" y="222" width="9.6" height="22" fill="#9577eb">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9678eb;#9b7eec;#a389ee;#ae97f0;#bba7f3;#c9b8f6;#d5c8f9;#e0d5fb;#e7defc;#eae3fd;#eae2fd;#e5dcfc;#dcd1fa;#d1c3f8;#c4b3f5;#b7a2f2;#aa92f0;#a085ed;#997cec;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb;#9577eb" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="329.6" y="222" width="9.6" height="22" fill="#997bee">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997cee;#9d80ef;#a48af0;#af97f2;#bba6f4;#c8b6f7;#d4c6f9;#ded3fb;#e6ddfc;#eae3fd;#ebe3fd;#e7defd;#dfd5fb;#d5c8f9;#c9b8f7;#bca8f5;#b098f2;#a68bf0;#9e81ef;#9a7cee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee;#997bee" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="339.2" y="222" width="9.6" height="22" fill="#9d80f1">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#a084f1;#a68cf2;#af97f4;#bba6f6;#c7b5f8;#d3c5fa;#ddd2fb;#e5dcfd;#eae2fd;#ebe4fe;#e8e0fd;#e2d8fc;#d9ccfb;#cebef9;#c1aef7;#b59ff5;#ab92f3;#a388f2;#9e81f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1;#9d80f1" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="348.8" y="222" width="9.6" height="22" fill="#a184f5">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a386f5;#a88df6;#b098f7;#bba5f8;#c6b4fa;#d2c3fb;#dcd0fc;#e5dbfd;#eae2fe;#ece4fe;#eae2fe;#e5dbfd;#dcd0fc;#d2c3fb;#c6b4fa;#bba5f8;#b098f7;#a88df6;#a386f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5;#a184f5" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="358.4" y="222" width="9.6" height="22" fill="#a589f8">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a68af8;#aa90f8;#b299f9;#bba6fa;#c6b4fb;#d1c2fc;#dbcffd;#e4dafe;#eae1fe;#ece5fe;#ebe3fe;#e7defe;#dfd4fd;#d6c8fc;#cbbafb;#c0acfa;#b69ff9;#ad94f9;#a88cf8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8;#a589f8" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="368" y="222" width="9.6" height="22" fill="#a98dfa">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#aa8efa;#ad92fa;#b39afb;#bca6fb;#c6b3fc;#d1c1fd;#dbcefd;#e3d9fe;#e9e1fe;#ece5fe;#ece5fe;#e9e0fe;#e2d8fe;#daccfd;#cfbffd;#c5b1fc;#bba4fb;#b299fb;#ac91fa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa;#a98dfa" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="377.6" y="222" width="9.6" height="22" fill="#ae91fb">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#b195fb;#b69cfb;#bea6fc;#c7b3fc;#d1c0fd;#dacdfe;#e3d8fe;#e9e0fe;#ece5ff;#ede5ff;#eae2fe;#e5dbfe;#ddd0fe;#d4c4fd;#cab7fd;#c1aafc;#b89ffc;#b297fb;#af92fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb;#ae91fb" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="387.2" y="222" width="9.6" height="22" fill="#b395fb">
|
||||
<animate attributeName="fill" calcMode="discrete" dur="3.48s" values="#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b597fb;#b99efb;#c0a7fc;#c8b2fc;#d1bffd;#dacbfd;#e2d6fe;#e8dffe;#ece4ff;#ede6ff;#ece4fe;#e7ddfe;#e1d4fe;#d8c9fd;#cfbcfd;#c6b0fc;#bea5fc;#b89cfb;#b497fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb;#b395fb" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 23 KiB |
+4
-4
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="832" height="386" viewBox="0 0 832 386" style="max-width: 832px; width: 100%; height: auto;" role="img">
|
||||
<rect width="100%" height="100%" rx="14" fill="#0c0b14"/>
|
||||
<rect x="0.5" y="0.5" width="831" height="385" rx="14" fill="none" stroke="#272430" stroke-width="1"/>
|
||||
<rect width="100%" height="100%" rx="14" fill="#0a0810"/>
|
||||
<rect x="0.5" y="0.5" width="831" height="385" rx="14" fill="none" stroke="#2d2b31" stroke-width="1"/>
|
||||
<text x="416" y="23" text-anchor="middle" font-family='ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace' font-size="13" fill="#8a8a8a">torlink</text>
|
||||
<g font-family='ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace' font-size="16" fill="#ddd8ea">
|
||||
<text x="339.2" y="84" textLength="9.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#5ae87a" font-weight="600">𐓏</text>
|
||||
@@ -46,8 +46,8 @@
|
||||
<rect x="122.5" y="266" width="1.4" height="22" fill="#a78bfa"/>
|
||||
<text x="137.6" y="282" textLength="9.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#a78bfa">❯</text>
|
||||
<rect x="156.8" y="266" width="9.6" height="22" fill="#ddd8ea"/>
|
||||
<text x="156.8" y="282" textLength="9.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#0c0b14">S</text>
|
||||
<text x="166.4" y="282" textLength="240" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill-opacity="0.55">earch, or paste a magnet…</text>
|
||||
<text x="156.8" y="282" textLength="9.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#0a0810">S</text>
|
||||
<text x="166.4" y="282" textLength="278.4" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill-opacity="0.55">earch or paste a magnet link…</text>
|
||||
<rect x="708.1" y="266" width="1.4" height="22" fill="#a78bfa"/>
|
||||
<text x="118.4" y="304" textLength="595.2" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#a78bfa">╰────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="233.6" y="348" textLength="9.6" lengthAdjust="spacingAndGlyphs" xml:space="preserve" fill="#b9a7e6">↵</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
+74
-3
@@ -1,8 +1,20 @@
|
||||
import { lerpHex } from "../src/ui/theme";
|
||||
import {
|
||||
SHEEN_PEAK,
|
||||
SHEEN_TICK_MS,
|
||||
sheenCenter,
|
||||
sheenIntensity,
|
||||
sheenLoopTicks,
|
||||
sheenPeriod,
|
||||
} from "../src/ui/sheen";
|
||||
|
||||
export interface AnsiToSvgOptions {
|
||||
cols: number;
|
||||
bg?: string;
|
||||
title?: string;
|
||||
maxWidth?: number;
|
||||
/** Animate a sweeping highlight over progress-bar fills (rows with a ░ track). */
|
||||
shimmer?: boolean;
|
||||
}
|
||||
|
||||
interface Style {
|
||||
@@ -26,7 +38,7 @@ const LINE_H = 22;
|
||||
const PAD = 32;
|
||||
const HEADER_H = 36;
|
||||
const RADIUS = 14;
|
||||
const BG = "#0c0b14";
|
||||
const BG = "#0a0810";
|
||||
const FG_DEFAULT = "#ddd8ea";
|
||||
const DIM_OPACITY = 0.55;
|
||||
const FONT_STACK =
|
||||
@@ -144,7 +156,7 @@ function escapeXml(s: string): string {
|
||||
const fmt = (n: number): string => String(Math.round(n * 100) / 100);
|
||||
|
||||
export function ansiToSvg(frame: string, opts: AnsiToSvgOptions): string {
|
||||
const { cols, bg = BG, title, maxWidth } = opts;
|
||||
const { cols, bg = BG, title, maxWidth, shimmer = false } = opts;
|
||||
const lines = frame.replace(/\r/g, "").split("\n");
|
||||
const isBlank = (l: string): boolean =>
|
||||
l.replace(/\x1b\[[0-9;]*m/g, "").trim() === "";
|
||||
@@ -162,7 +174,7 @@ export function ansiToSvg(frame: string, opts: AnsiToSvgOptions): string {
|
||||
);
|
||||
out.push(` <rect width="100%" height="100%" rx="${RADIUS}" fill="${bg}"/>`);
|
||||
out.push(
|
||||
` <rect x="0.5" y="0.5" width="${fmt(width - 1)}" height="${fmt(height - 1)}" rx="${RADIUS}" fill="none" stroke="#272430" stroke-width="1"/>`,
|
||||
` <rect x="0.5" y="0.5" width="${fmt(width - 1)}" height="${fmt(height - 1)}" rx="${RADIUS}" fill="none" stroke="#2d2b31" stroke-width="1"/>`,
|
||||
);
|
||||
if (title) {
|
||||
out.push(
|
||||
@@ -173,6 +185,9 @@ export function ansiToSvg(frame: string, opts: AnsiToSvgOptions): string {
|
||||
` <g font-family='${FONT_STACK}' font-size="${FONT_SIZE}" fill="${FG_DEFAULT}">`,
|
||||
);
|
||||
|
||||
const barRuns: { row: number; col: number; n: number; fg: string }[] = [];
|
||||
const trackLen = new Map<number, number>();
|
||||
|
||||
const state = { style: freshStyle() };
|
||||
lines.forEach((line, row) => {
|
||||
const runs = parseLine(line, state, cols);
|
||||
@@ -195,6 +210,11 @@ export function ansiToSvg(frame: string, opts: AnsiToSvgOptions): string {
|
||||
const x = PAD + col * CHAR_W;
|
||||
const w = n * CHAR_W;
|
||||
const fg = st.fg ?? FG_DEFAULT;
|
||||
if (shimmer) {
|
||||
if (/^█+$/.test(text)) barRuns.push({ row, col, n, fg });
|
||||
const dim = (text.match(/░/g) ?? []).length;
|
||||
if (dim > 0) trackLen.set(row, (trackLen.get(row) ?? 0) + dim);
|
||||
}
|
||||
if (/^[│┃]+$/.test(text)) {
|
||||
const barW = text[0] === "┃" ? 2.8 : 1.4;
|
||||
for (let k = 0; k < n; k++) {
|
||||
@@ -251,6 +271,57 @@ export function ansiToSvg(frame: string, opts: AnsiToSvgOptions): string {
|
||||
});
|
||||
|
||||
out.push(" </g>");
|
||||
|
||||
if (shimmer && barRuns.length > 0) {
|
||||
// Reproduce the live ProgressBar sheen 1:1: for each filled cell, animate
|
||||
// its fill through the exact bell-tinted colors, snapping frame-to-frame
|
||||
// (calcMode="discrete") like the terminal repaint, so it stays pixelated.
|
||||
const byRow = new Map<number, { col: number; n: number; fg: string }[]>();
|
||||
for (const r of barRuns) {
|
||||
if (!trackLen.has(r.row)) continue;
|
||||
const arr = byRow.get(r.row) ?? [];
|
||||
arr.push({ col: r.col, n: r.n, fg: r.fg });
|
||||
byRow.set(r.row, arr);
|
||||
}
|
||||
|
||||
for (const [row, parts] of byRow) {
|
||||
parts.sort((a, b) => a.col - b.col);
|
||||
// Merge contiguous █ runs into one region, expanded to per-cell colors.
|
||||
const regions: { col: number; colors: string[] }[] = [];
|
||||
for (const p of parts) {
|
||||
const last = regions[regions.length - 1];
|
||||
const cells = Array.from({ length: p.n }, () => p.fg);
|
||||
if (last && last.col + last.colors.length === p.col) last.colors.push(...cells);
|
||||
else regions.push({ col: p.col, colors: cells });
|
||||
}
|
||||
|
||||
const yTop = HEADER_H + PAD + row * LINE_H;
|
||||
for (const region of regions) {
|
||||
const len = region.colors.length;
|
||||
const fullCells = len + (trackLen.get(row) ?? 0);
|
||||
const period = sheenPeriod(fullCells);
|
||||
const frames = sheenLoopTicks(period);
|
||||
const dur = (frames * SHEEN_TICK_MS) / 1000;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const base = region.colors[i]!;
|
||||
const values: string[] = [];
|
||||
for (let t = 0; t < frames; t++) {
|
||||
const intensity = sheenIntensity(i, sheenCenter(t, period));
|
||||
values.push(intensity > 0 ? lerpHex(base, SHEEN_PEAK, intensity) : base);
|
||||
}
|
||||
const x = PAD + (region.col + i) * CHAR_W;
|
||||
out.push(
|
||||
` <rect x="${fmt(x)}" y="${fmt(yTop)}" width="${fmt(CHAR_W)}" height="${LINE_H}" fill="${base}">`,
|
||||
);
|
||||
out.push(
|
||||
` <animate attributeName="fill" calcMode="discrete" dur="${fmt(dur)}s" values="${values.join(";")}" repeatCount="indefinite"/>`,
|
||||
);
|
||||
out.push(` </rect>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.push("</svg>");
|
||||
return out.join("\n");
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Downloads } from "../src/ui/components/Downloads";
|
||||
import { footerHints } from "../src/ui/keymap";
|
||||
import { sourcesByGroup } from "../src/sources/registry";
|
||||
import { cleanText, formatBytes, formatRelative } from "../src/util/format";
|
||||
import { ansiToSvg } from "./ansi-to-svg";
|
||||
import { ansiToSvg, type AnsiToSvgOptions } from "./ansi-to-svg";
|
||||
import type { Config } from "../src/config/config";
|
||||
import type { DownloadQueue } from "../src/download/queue";
|
||||
import type { QueueItem } from "../src/download/types";
|
||||
@@ -97,7 +97,12 @@ function makeStore(
|
||||
};
|
||||
}
|
||||
|
||||
function save(name: string, store: Store, node: React.ReactNode): void {
|
||||
function save(
|
||||
name: string,
|
||||
store: Store,
|
||||
node: React.ReactNode,
|
||||
extra: Partial<AnsiToSvgOptions> = {},
|
||||
): void {
|
||||
const { lastFrame, unmount } = render(
|
||||
<StoreContext.Provider value={store}>{node}</StoreContext.Provider>,
|
||||
);
|
||||
@@ -106,7 +111,10 @@ function save(name: string, store: Store, node: React.ReactNode): void {
|
||||
if (!/\x1b\[/.test(frame)) {
|
||||
throw new Error(`${name}: frame has no ANSI colors (FORCE_COLOR didn't take)`);
|
||||
}
|
||||
writeFileSync(join(OUT_DIR, `${name}.svg`), ansiToSvg(frame, { cols: COLS, title: "torlink" }));
|
||||
writeFileSync(
|
||||
join(OUT_DIR, `${name}.svg`),
|
||||
ansiToSvg(frame, { cols: COLS, title: "torlink", ...extra }),
|
||||
);
|
||||
console.log(`preview/${name}.svg`);
|
||||
}
|
||||
|
||||
@@ -126,7 +134,7 @@ save(
|
||||
<Text dimColor>{CATEGORIES}</Text>
|
||||
</Box>
|
||||
<Box marginTop={1} width={62}>
|
||||
<SearchBar width={62} value="" editing placeholder="Search, or paste a magnet…" onSubmit={() => {}} />
|
||||
<SearchBar width={62} value="" editing placeholder="Search or paste a magnet link…" onSubmit={() => {}} />
|
||||
</Box>
|
||||
<Box marginTop={1}>
|
||||
<Text>
|
||||
@@ -159,7 +167,7 @@ save(
|
||||
<Box height={14} marginTop={1}>
|
||||
<Sidebar />
|
||||
<Box flexGrow={1} flexDirection="column">
|
||||
<SearchBar width={CONTENT_WIDTH} value="" editing={false} placeholder="Search, or paste a magnet…" onSubmit={() => {}} />
|
||||
<SearchBar width={CONTENT_WIDTH} value="" editing={false} placeholder="Search or paste a magnet link…" onSubmit={() => {}} />
|
||||
<Box marginTop={1}>
|
||||
<Panel title="latest" width={CONTENT_WIDTH} focused count={`(${browseResults.length})`} height={9}>
|
||||
<Box><Text dimColor>newest across all sources</Text></Box>
|
||||
@@ -237,4 +245,5 @@ save(
|
||||
</Box>
|
||||
<Footer hints={footerHints("content", "downloads")} />
|
||||
</Box>,
|
||||
{ shimmer: true },
|
||||
);
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Text } from "ink";
|
||||
import { COLOR, RULE, lerpHex } from "../theme";
|
||||
import { SHEEN_PEAK, SHEEN_TICK_MS, sheenCenter, sheenIntensity, sheenPeriod } from "../sheen";
|
||||
|
||||
const DEEP = "#7c5cd6";
|
||||
const SHEEN_PEAK = "#f4efff";
|
||||
const RADIUS = 3.5;
|
||||
const GAP = 8;
|
||||
const MAX_SHEEN = 0.9;
|
||||
const TICK_MS = 90;
|
||||
|
||||
interface Run {
|
||||
color: string;
|
||||
@@ -55,7 +51,7 @@ export function ProgressBar({
|
||||
const [tick, setTick] = useState(0);
|
||||
useEffect(() => {
|
||||
if (!animate) return;
|
||||
const timer = setInterval(() => setTick((v) => v + 1), TICK_MS);
|
||||
const timer = setInterval(() => setTick((v) => v + 1), SHEEN_TICK_MS);
|
||||
timer.unref?.();
|
||||
return () => clearInterval(timer);
|
||||
}, [animate]);
|
||||
@@ -76,15 +72,12 @@ export function ProgressBar({
|
||||
);
|
||||
}
|
||||
|
||||
const period = Math.ceil(width + RADIUS * 2) + GAP;
|
||||
const center = (tick % period) - RADIUS;
|
||||
const period = sheenPeriod(width);
|
||||
const center = sheenCenter(tick, period);
|
||||
const cells = Array.from({ length: filled }, (_, i) => {
|
||||
let c = ramp(i / denom, DEEP, COLOR.accent, COLOR.bright);
|
||||
const d = Math.abs(i - center);
|
||||
if (d < RADIUS) {
|
||||
const intensity = 0.5 * (1 + Math.cos((Math.PI * d) / RADIUS)) * MAX_SHEEN;
|
||||
c = lerpHex(c, SHEEN_PEAK, intensity);
|
||||
}
|
||||
const intensity = sheenIntensity(i, center);
|
||||
if (intensity > 0) c = lerpHex(c, SHEEN_PEAK, intensity);
|
||||
return c;
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { Source, TorrentResult } from "../../sources/types";
|
||||
|
||||
type Mode = "list" | "search" | "detail";
|
||||
|
||||
const PLACEHOLDER = "Search, or paste a magnet…";
|
||||
const PLACEHOLDER = "Search or paste a magnet link…";
|
||||
|
||||
function DetailRow({ label, value }: { label: string; value: ReactNode }) {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Single source of truth for the animated progress-bar sheen, shared by the
|
||||
// live TUI (src/ui/components/ProgressBar.tsx) and the README SVG generator
|
||||
// (scripts/ansi-to-svg.ts) so the preview renders 1:1 with the terminal.
|
||||
//
|
||||
// The highlight is a cosine bell that sweeps across the filled cells. Position
|
||||
// advances by a fractional SHEEN_SPEED per tick at a fast SHEEN_TICK_MS, so the
|
||||
// peak glides between cells (intensity-interpolated) instead of stepping a whole
|
||||
// cell at a time, while the cells themselves stay discrete (pixelated).
|
||||
|
||||
export const SHEEN_PEAK = "#f4efff";
|
||||
export const SHEEN_RADIUS = 4.5; // bell half-width, in cells
|
||||
export const SHEEN_GAP = 8; // dark cells between sweeps
|
||||
export const SHEEN_TICK_MS = 40; // frame interval (~25fps)
|
||||
export const SHEEN_SPEED = 0.45; // cells advanced per tick (~11 cells/sec)
|
||||
export const SHEEN_MAX = 0.9; // peak blend toward SHEEN_PEAK
|
||||
|
||||
/** Total sweep cycle length in cells, for a bar `width` cells wide. */
|
||||
export function sheenPeriod(width: number): number {
|
||||
return Math.ceil(width + SHEEN_RADIUS * 2) + SHEEN_GAP;
|
||||
}
|
||||
|
||||
/** Fractional center of the bell at a given tick (loops over `period`). */
|
||||
export function sheenCenter(tick: number, period: number): number {
|
||||
return ((tick * SHEEN_SPEED) % period) - SHEEN_RADIUS;
|
||||
}
|
||||
|
||||
/** Sheen intensity (0..SHEEN_MAX) for cell `i` given the bell `center`. */
|
||||
export function sheenIntensity(i: number, center: number): number {
|
||||
const d = Math.abs(i - center);
|
||||
if (d >= SHEEN_RADIUS) return 0;
|
||||
return 0.5 * (1 + Math.cos((Math.PI * d) / SHEEN_RADIUS)) * SHEEN_MAX;
|
||||
}
|
||||
|
||||
/** Ticks in one full visual loop, for sampling the SVG flipbook 1:1. */
|
||||
export function sheenLoopTicks(period: number): number {
|
||||
return Math.round(period / SHEEN_SPEED);
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export function Splash() {
|
||||
width={barWidth}
|
||||
value=""
|
||||
editing
|
||||
placeholder="Search, or paste a magnet…"
|
||||
placeholder="Search or paste a magnet link…"
|
||||
onSubmit={submitQuery}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user