deploy: cf5ec323c46a3737152abff408d598977d8473d1

This commit is contained in:
everestmcarthur 2026-08-01 18:41:00 +00:00
parent e5f120c2e0
commit c5c6f6c7b3
21 changed files with 766 additions and 0 deletions

View file

@ -187,6 +187,34 @@
"sourceUrl": "https://github.com/everestmcarthur/revenge-plugins/tree/main/plugins/staff-tags",
"issueUrl": "https://github.com/everestmcarthur/revenge-plugins/issues/new?title=%5BStaff%20Tags%5D%20"
},
{
"id": "you-bar-plus",
"name": "YouBar+",
"description": "Adds up to 2 configurable buttons (Direct Messages, Settings, or none) to the YouBar, in whatever order you pick.",
"authors": [
"everestmcarthur",
"Purple_Ξye™"
],
"category": "Moderation & Appearance",
"status": "revived",
"accent": "#5865F2",
"tagline": "Configurable buttons on the YouBar - up to 2 at a time, in your chosen order.",
"note": "Fork of Purple-EyeZ's YouBar+. Fixed a startup race condition: the original did a single one-shot component lookup during onLoad and silently gave up for the rest of the session if it ran before the target had registered in Metro yet (a real risk on a cold app start) - restarting Discord just got lucky on a re-roll. This port polls for the component instead of looking it up once. Also replaced the original's two independent on/off toggles with a real slot-based system: 2 ordered slots, each set to None, Direct Messages, or Settings.",
"howItWorks": "The YouBar is the row near your avatar/status at the top of the app. This plugin patches its notification button area to also render up to 2 extra icon buttons, each pulled from one of 2 configurable slots. Each slot cycles between None, Direct Messages (jumps to your DMs), and Settings (opens user settings) - tap a slot in the plugin's settings to cycle it. Slot order controls left-to-right button order, and each slot is independent, so you can run just one button, both, or neither. Both slots default to Direct Messages then Settings, matching the original plugin's default look.",
"features": [
"Up to 2 buttons on the YouBar, each independently configurable",
"Choices per slot: None, Direct Messages, Settings",
"Slot order controls left-to-right button order",
"Settings changes apply immediately, no restart needed",
"Startup race condition fixed - polls for the target component instead of a single one-shot lookup"
],
"commands": [],
"limitations": "Only wires up to actions with a confirmed, still-working lookup (jump to DMs, open Settings) - no additional slot choices were added beyond what could be verified against a real findByProps target.",
"pageUrl": "https://rp.jarviscli.dev/you-bar-plus/",
"installUrl": "https://rp.jarviscli.dev/you-bar-plus/install/",
"sourceUrl": "https://github.com/everestmcarthur/revenge-plugins/tree/main/plugins/you-bar-plus",
"issueUrl": "https://github.com/everestmcarthur/revenge-plugins/issues/new?title=%5BYouBar%2B%5D%20"
},
{
"id": "message-snippets",
"name": "Message Snippets",

736
you-bar-plus/index.html Normal file
View file

@ -0,0 +1,736 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>revenge-plugins</title>
<meta name="description" content="Plugins for Revenge, the Discord Android client mod.">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%235865f2'/%3E%3Cstop offset='1' stop-color='%239b59b6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='8' fill='url(%23g)'/%3E%3Cpath d='M9 12l7-5 7 5v9l-7 5-7-5z' fill='none' stroke='%23fff' stroke-width='2'/%3E%3Ccircle cx='16' cy='16' r='3' fill='%23fff'/%3E%3C/svg%3E">
<style>
:root {
--bg: #f5f6fb;
--bg-elevated: #ffffff;
--bg-sunken: #eceef6;
--border: #e1e4ef;
--text: #14151f;
--text-muted: #62667a;
--accent: #5865f2;
--accent-text: #ffffff;
--chip-bg: #ececf7;
--chip-bg-active: #5865f2;
--chip-text-active: #ffffff;
--badge-new-bg: #e6f6ec;
--badge-new-text: #1a7f4b;
--badge-revived-bg: #eef0ff;
--badge-revived-text: #4752c4;
--badge-rebuilt-bg: #fff3e0;
--badge-rebuilt-text: #b5680a;
--danger: #f23f42;
--shadow: 0 1px 2px rgba(20, 21, 31, 0.06), 0 10px 30px rgba(20, 21, 31, 0.06);
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0d0e14; --bg-elevated: #171821; --bg-sunken: #12131b; --border: #262837;
--text: #eef0f9; --text-muted: #9297ab; --accent: #838cff; --accent-text: #0d0e14;
--chip-bg: #1e2030; --chip-bg-active: #838cff; --chip-text-active: #0d0e14;
--badge-new-bg: #0f2a1c; --badge-new-text: #5fd493; --badge-revived-bg: #1c1f3a; --badge-revived-text: #a6adff;
--badge-rebuilt-bg: #33240c; --badge-rebuilt-text: #f0b849; --danger: #ff6b6e;
--shadow: 0 1px 2px rgba(0,0,0,0.4), 0 10px 30px rgba(0,0,0,0.5);
color-scheme: dark;
}
}
:root[data-theme="dark"] {
--bg: #0d0e14; --bg-elevated: #171821; --bg-sunken: #12131b; --border: #262837;
--text: #eef0f9; --text-muted: #9297ab; --accent: #838cff; --accent-text: #0d0e14;
--chip-bg: #1e2030; --chip-bg-active: #838cff; --chip-text-active: #0d0e14;
--badge-new-bg: #0f2a1c; --badge-new-text: #5fd493; --badge-revived-bg: #1c1f3a; --badge-revived-text: #a6adff;
--badge-rebuilt-bg: #33240c; --badge-rebuilt-text: #f0b849; --danger: #ff6b6e;
--shadow: 0 1px 2px rgba(0,0,0,0.4), 0 10px 30px rgba(0,0,0,0.5); color-scheme: dark;
}
:root[data-theme="light"] {
--bg: #f5f6fb; --bg-elevated: #ffffff; --bg-sunken: #eceef6; --border: #e1e4ef;
--text: #14151f; --text-muted: #62667a; --accent: #5865f2; --accent-text: #ffffff;
--chip-bg: #ececf7; --chip-bg-active: #5865f2; --chip-text-active: #ffffff;
--badge-new-bg: #e6f6ec; --badge-new-text: #1a7f4b; --badge-revived-bg: #eef0ff; --badge-revived-text: #4752c4;
--badge-rebuilt-bg: #fff3e0; --badge-rebuilt-text: #b5680a; --danger: #f23f42;
--shadow: 0 1px 2px rgba(20,21,31,0.06), 0 10px 30px rgba(20,21,31,0.06); color-scheme: light;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
code, .mono { font-family: var(--mono); }
.wrap { max-width: 1040px; margin: 0 auto; padding: 0 20px; }
a { color: inherit; }
/* ---------- Hero ---------- */
.hero {
position: relative;
overflow: hidden;
border-bottom: 1px solid var(--border);
padding: 64px 0 40px;
}
.hero-mesh {
position: absolute;
inset: -20%;
z-index: 0;
opacity: 0.5;
background:
radial-gradient(38% 45% at 12% 8%, color-mix(in srgb, var(--accent) 55%, transparent), transparent 70%),
radial-gradient(32% 38% at 85% 15%, #9b59b688, transparent 70%),
radial-gradient(30% 40% at 70% 85%, #23a55a55, transparent 70%);
filter: blur(40px);
animation: drift 22s ease-in-out infinite alternate;
pointer-events: none;
}
@keyframes drift {
from { transform: translate3d(0,0,0) scale(1); }
to { transform: translate3d(-2%, 2%, 0) scale(1.08); }
}
.hero-spotlight {
position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: 0;
background: radial-gradient(280px circle at var(--sx, 50%) var(--sy, 50%), color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%);
transition: opacity 0.25s ease;
}
.hero:hover .hero-spotlight { opacity: 1; }
.hero-inner { position: relative; z-index: 1; }
.mark {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 13px;
font-weight: 700;
color: var(--accent);
letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 18px;
}
.mark svg { width: 22px; height: 22px; display: block; }
h1 {
font-size: clamp(32px, 5vw, 46px);
line-height: 1.08;
margin: 0 0 14px;
letter-spacing: -0.02em;
font-weight: 800;
}
h1 .grad {
background: linear-gradient(100deg, var(--accent), #9b59b6 60%, #23a55a);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.hero p.sub { color: var(--text-muted); font-size: 17px; max-width: 640px; margin: 0 0 24px; line-height: 1.55; }
.hero-links { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 32px; }
.hero-links a {
font-size: 14px; font-weight: 600; text-decoration: none;
padding: 9px 15px; border-radius: 9px; border: 1px solid var(--border); background: var(--bg-elevated);
}
.hero-links a.primary { background: var(--accent); color: var(--accent-text); border-color: transparent; }
.kbd-hint {
display: inline-flex; align-items: center; gap: 6px;
font-size: 13px; color: var(--text-muted); font-weight: 600;
}
.kbd-hint kbd {
font-family: var(--mono); font-size: 11px; background: var(--bg-sunken);
border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 5px; padding: 2px 6px;
}
.stats { display: flex; gap: 32px; flex-wrap: wrap; }
.stats .stat-value { font-size: 28px; font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.stats .stat-label { font-size: 12.5px; color: var(--text-muted); font-weight: 600; }
/* ---------- Controls ---------- */
.controls { position: sticky; top: 0; background: var(--bg); padding: 16px 0; z-index: 5; border-bottom: 1px solid transparent; transition: border-color 0.15s; }
.controls.stuck { border-bottom-color: var(--border); }
.search-row { display: flex; gap: 10px; margin-bottom: 12px; }
.search {
flex: 1; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--border);
background: var(--bg-elevated); color: var(--text); font-size: 15px;
}
.search:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.chips { display: flex; gap: 8px; flex-wrap: wrap; }
.chip { font-size: 13px; font-weight: 600; padding: 7px 13px; border-radius: 999px; background: var(--chip-bg); color: var(--text-muted); border: none; cursor: pointer; }
.chip[aria-pressed="true"] { background: var(--chip-bg-active); color: var(--chip-text-active); }
main { padding: 24px 0 64px; }
.count { color: var(--text-muted); font-size: 13px; margin: 0 0 16px; }
/* ---------- Grid / cards ---------- */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
.card {
position: relative;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 16px;
padding: 20px 20px 18px;
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
cursor: pointer;
overflow: hidden;
transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease;
text-align: left;
font: inherit;
color: inherit;
width: 100%;
}
.card::before {
content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
background: var(--card-accent, var(--accent));
}
.card:hover, .card:focus-visible {
transform: translateY(-3px);
border-color: var(--card-accent, var(--accent));
box-shadow: 0 1px 2px rgba(20,21,31,0.06), 0 16px 32px -8px color-mix(in srgb, var(--card-accent, var(--accent)) 35%, transparent);
outline: none;
}
.card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.card h3 { font-size: 18px; margin: 0; letter-spacing: -0.01em; }
.badge { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; padding: 3px 8px; border-radius: 999px; white-space: nowrap; flex-shrink: 0; }
.badge.new { background: var(--badge-new-bg); color: var(--badge-new-text); }
.badge.revived { background: var(--badge-revived-bg); color: var(--badge-revived-text); }
.badge.rebuilt { background: var(--badge-rebuilt-bg); color: var(--badge-rebuilt-text); }
.card .category { font-size: 11.5px; color: var(--card-accent, var(--accent)); font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 10px; }
.card p.tagline { font-size: 14px; color: var(--text-muted); line-height: 1.55; margin: 0 0 14px; flex-grow: 1; }
.card .feature-count { font-size: 12px; color: var(--text-muted); margin-bottom: 12px; display: flex; align-items: center; gap: 6px; }
.card .feature-count svg { width: 13px; height: 13px; opacity: 0.7; }
.card .cta { font-size: 13px; font-weight: 700; color: var(--card-accent, var(--accent)); display: flex; align-items: center; gap: 4px; }
.empty { text-align: center; padding: 60px 20px; color: var(--text-muted); }
footer { border-top: 1px solid var(--border); padding: 28px 0 44px; }
footer p { color: var(--text-muted); font-size: 13px; margin: 0; }
/* ---------- Detail view ---------- */
.detail { display: none; }
.detail.active { display: block; }
.grid-view.hidden, .hero.hidden, .controls.hidden { display: none; }
.detail-banner {
position: relative;
overflow: hidden;
padding: 48px 0 36px;
border-bottom: 1px solid var(--border);
background:
radial-gradient(60% 100% at 0% 0%, color-mix(in srgb, var(--page-accent, var(--accent)) 22%, transparent), transparent),
var(--bg);
}
.back-link {
display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700;
color: var(--text-muted); text-decoration: none; margin-bottom: 20px; background: none; border: none; cursor: pointer; padding: 0; font-family: inherit;
}
.back-link:hover { color: var(--text); }
.detail-top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.detail-top h2 { font-size: clamp(28px, 4vw, 38px); margin: 0; letter-spacing: -0.02em; font-weight: 800; }
.detail-category { font-size: 12.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--page-accent, var(--accent)); margin-bottom: 14px; }
.detail-tagline { font-size: 17px; color: var(--text-muted); max-width: 680px; line-height: 1.55; margin: 0 0 22px; }
.detail-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.detail-actions button, .detail-actions a {
font-size: 14px; font-weight: 700; padding: 10px 16px; border-radius: 10px; border: 1px solid var(--border);
background: var(--bg-elevated); color: var(--text); text-decoration: none; cursor: pointer;
}
.detail-actions button.install { background: var(--page-accent, var(--accent)); color: var(--accent-text); border-color: transparent; }
.detail-body { padding: 36px 0 64px; display: grid; grid-template-columns: 2fr 1fr; gap: 40px; }
@media (max-width: 720px) { .detail-body { grid-template-columns: 1fr; } }
.detail-section { margin-bottom: 32px; }
.detail-section h4 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin: 0 0 14px; }
.detail-section p { font-size: 15px; line-height: 1.7; color: var(--text); margin: 0 0 14px; white-space: pre-line; }
.feature-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.feature-list li { display: flex; gap: 10px; font-size: 14.5px; line-height: 1.5; }
.feature-list svg { flex-shrink: 0; width: 18px; height: 18px; margin-top: 1px; color: var(--page-accent, var(--accent)); }
.cmd-table { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.cmd-row { padding: 12px 14px; border-bottom: 1px solid var(--border); }
.cmd-row:last-child { border-bottom: none; }
.cmd-row code { display: block; font-size: 13.5px; font-weight: 700; color: var(--page-accent, var(--accent)); margin-bottom: 4px; }
.cmd-row span { font-size: 13px; color: var(--text-muted); line-height: 1.5; }
.side-card { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 14px; padding: 18px; margin-bottom: 16px; }
.side-card h5 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin: 0 0 10px; }
.side-card p { font-size: 13.5px; line-height: 1.55; margin: 0; color: var(--text); }
.side-card.warn { border-color: color-mix(in srgb, var(--danger) 40%, var(--border)); }
.side-card.warn h5 { color: var(--danger); }
.authors-line { font-size: 13px; color: var(--text-muted); }
/* ---------- Command palette ---------- */
.palette-overlay {
position: fixed; inset: 0; background: rgba(10,10,16,0.55); backdrop-filter: blur(2px);
display: none; align-items: flex-start; justify-content: center; padding-top: 12vh; z-index: 100;
}
.palette-overlay.open { display: flex; }
.palette {
width: min(560px, 92vw); background: var(--bg-elevated); border: 1px solid var(--border);
border-radius: 14px; box-shadow: 0 20px 60px rgba(0,0,0,0.4); overflow: hidden;
}
.palette input {
width: 100%; border: none; background: transparent; color: var(--text); font-size: 17px;
padding: 18px 20px; border-bottom: 1px solid var(--border);
}
.palette input:focus { outline: none; }
.palette-results { max-height: 50vh; overflow-y: auto; padding: 8px; }
.palette-item {
display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 8px; cursor: pointer; font-size: 14px;
}
.palette-item[aria-selected="true"] { background: var(--bg-sunken); }
.palette-item .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--item-accent, var(--accent)); flex-shrink: 0; }
.palette-item .name { font-weight: 600; }
.palette-item .cat { color: var(--text-muted); font-size: 12px; margin-left: auto; }
.palette-empty { padding: 20px; text-align: center; color: var(--text-muted); font-size: 13px; }
.toast {
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(0);
background: var(--text); color: var(--bg); font-size: 13px; font-weight: 700;
padding: 10px 16px; border-radius: 999px; opacity: 0; pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease; z-index: 50;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(-8px); }
</style>
</head>
<body>
<header class="hero" id="hero">
<div class="hero-mesh" aria-hidden="true"></div>
<div class="hero-spotlight" id="heroSpotlight" aria-hidden="true"></div>
<div class="wrap hero-inner">
<div class="mark">
<svg viewBox="0 0 32 32" fill="none"><path d="M9 12l7-5 7 5v9l-7 5-7-5z" stroke="currentColor" stroke-width="2"/><circle cx="16" cy="16" r="3" fill="currentColor"/></svg>
Revenge Plugins
</div>
<h1>Discord, <span class="grad">your way.</span></h1>
<p class="sub">A hand-built collection of plugins for Revenge, the Discord Android client mod - rebuilt from scratch where the old ecosystem broke, and net-new where nothing existed yet.</p>
<div class="hero-links">
<a class="primary" href="https://github.com/everestmcarthur/revenge-plugins">GitHub repo</a>
<a href="https://github.com/revenge-mod/revenge-bundle">About Revenge</a>
<a href="https://github.com/everestmcarthur/revenge-plugins/issues/new">Report an issue</a>
</div>
<div class="stats" id="stats" aria-hidden="true"></div>
</div>
</header>
<div class="wrap grid-view" id="gridView">
<div class="controls" id="controls">
<div class="search-row">
<input id="search" class="search" type="search" placeholder="Search plugins… (or press / )" aria-label="Search plugins">
<span class="kbd-hint"><kbd>&#8984;</kbd><kbd>K</kbd></span>
</div>
<div class="chips" id="categoryChips" role="group" aria-label="Filter by category"></div>
</div>
<main>
<p class="count" id="count"></p>
<div class="grid" id="grid"></div>
<div class="empty" id="empty" hidden>No plugins match your search.</div>
</main>
</div>
<div class="detail" id="detailView"></div>
<footer>
<div class="wrap">
<p>Built with <a href="https://github.com/everestmcarthur/revenge-plugins">revenge-plugins</a> · not affiliated with Discord or the Revenge project · install links point to this site</p>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<div class="palette-overlay" id="paletteOverlay">
<div class="palette">
<input id="paletteInput" type="text" placeholder="Jump to a plugin…" aria-label="Jump to a plugin">
<div class="palette-results" id="paletteResults"></div>
</div>
</div>
<script>
(function () {
var STATUS_LABEL = { new: "New", revived: "Revived", rebuilt: "Rebuilt" };
var state = { plugins: [], query: "", category: "All" };
var grid = document.getElementById("grid");
var empty = document.getElementById("empty");
var count = document.getElementById("count");
var chipsEl = document.getElementById("categoryChips");
var searchEl = document.getElementById("search");
var toastEl = document.getElementById("toast");
var statsEl = document.getElementById("stats");
var heroEl = document.getElementById("hero");
var controlsEl = document.getElementById("controls");
var gridViewEl = document.getElementById("gridView");
var detailViewEl = document.getElementById("detailView");
var paletteOverlay = document.getElementById("paletteOverlay");
var paletteInput = document.getElementById("paletteInput");
var paletteResults = document.getElementById("paletteResults");
var CHECK_SVG = '<svg viewBox="0 0 20 20" fill="none"><path d="M4 10.5l4 4 8-9" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
function escapeHtml(str) {
return String(str).replace(/[&<>"']/g, function (c) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
});
}
function showToast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(showToast._t);
showToast._t = setTimeout(function () { toastEl.classList.remove("show"); }, 1600);
}
function copyToClipboard(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
var ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); } catch (e) {}
document.body.removeChild(ta);
return Promise.resolve();
}
function installButton(url, extraStyle) {
var btn = document.createElement("button");
btn.className = "install";
btn.textContent = "Install";
if (extraStyle) btn.setAttribute("style", extraStyle);
btn.addEventListener("click", function (e) {
e.stopPropagation();
copyToClipboard(url).then(function () {
var original = btn.textContent;
btn.textContent = "Copied!";
showToast("Install link copied - paste it into Revenge's plugin browser");
setTimeout(function () { btn.textContent = original; }, 1400);
});
});
return btn;
}
function countUp(el, target) {
var start = 0;
var duration = 700;
var startTime = null;
function step(ts) {
if (!startTime) startTime = ts;
var progress = Math.min((ts - startTime) / duration, 1);
var eased = 1 - Math.pow(1 - progress, 3);
el.textContent = Math.round(start + (target - start) * eased);
if (progress < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
function renderStats() {
var categories = Array.from(new Set(state.plugins.map(function (p) { return p.category; })));
var revivedCount = state.plugins.filter(function (p) { return p.status !== "new"; }).length;
var stats = [
{ value: state.plugins.length, label: "plugins" },
{ value: categories.length, label: "categories" },
{ value: revivedCount, label: "revived or rebuilt" }
];
statsEl.innerHTML = stats.map(function (s, i) {
return '<div><div class="stat-value" id="stat-' + i + '">0</div><div class="stat-label">' + s.label + '</div></div>';
}).join("");
stats.forEach(function (s, i) {
countUp(document.getElementById("stat-" + i), s.value);
});
}
/* ---------- Grid ---------- */
function render() {
var q = state.query.trim().toLowerCase();
var filtered = state.plugins.filter(function (p) {
var matchesCategory = state.category === "All" || p.category === state.category;
var haystack = (p.name + " " + p.description + " " + p.category + " " + (p.tagline || "")).toLowerCase();
var matchesQuery = !q || haystack.indexOf(q) !== -1;
return matchesCategory && matchesQuery;
});
count.textContent = filtered.length + " plugin" + (filtered.length === 1 ? "" : "s");
grid.innerHTML = "";
empty.hidden = filtered.length !== 0;
filtered.forEach(function (p) {
var card = document.createElement("button");
card.type = "button";
card.className = "card";
card.style.setProperty("--card-accent", p.accent);
card.innerHTML =
'<div class="card-top">' +
'<h3>' + escapeHtml(p.name) + '</h3>' +
'<span class="badge ' + p.status + '">' + (STATUS_LABEL[p.status] || p.status) + '</span>' +
'</div>' +
'<div class="category">' + escapeHtml(p.category) + '</div>' +
'<p class="tagline">' + escapeHtml(p.tagline || p.description) + '</p>' +
(p.features && p.features.length ? '<div class="feature-count">' + CHECK_SVG.replace("<svg ", '<svg style="width:13px;height:13px" ') + ' ' + p.features.length + ' feature' + (p.features.length === 1 ? "" : "s") + '</div>' : '') +
'<div class="cta">View details &rarr;</div>';
card.addEventListener("click", function () {
navigateToPlugin(p.id);
});
grid.appendChild(card);
});
}
function renderChips() {
var categories = ["All"].concat(
Array.from(new Set(state.plugins.map(function (p) { return p.category; }))).sort()
);
chipsEl.innerHTML = "";
categories.forEach(function (cat) {
var chip = document.createElement("button");
chip.className = "chip";
chip.textContent = cat;
chip.setAttribute("aria-pressed", String(cat === state.category));
chip.addEventListener("click", function () {
state.category = cat;
Array.from(chipsEl.children).forEach(function (c) {
c.setAttribute("aria-pressed", String(c === chip));
});
render();
});
chipsEl.appendChild(chip);
});
}
searchEl.addEventListener("input", function (e) {
state.query = e.target.value;
render();
});
window.addEventListener("scroll", function () {
controlsEl.classList.toggle("stuck", window.scrollY > 8);
});
var heroSpotlight = document.getElementById("heroSpotlight");
heroEl.addEventListener("mousemove", function (e) {
var rect = heroEl.getBoundingClientRect();
heroSpotlight.style.setProperty("--sx", (e.clientX - rect.left) + "px");
heroSpotlight.style.setProperty("--sy", (e.clientY - rect.top) + "px");
});
/* ---------- Detail view ---------- */
function renderDetail(p) {
var featuresHtml = (p.features || []).map(function (f) {
return '<li>' + CHECK_SVG + '<span>' + escapeHtml(f) + '</span></li>';
}).join("");
var commandsHtml = (p.commands && p.commands.length)
? '<div class="detail-section"><h4>Commands</h4><div class="cmd-table">' +
p.commands.map(function (c) {
return '<div class="cmd-row"><code>' + escapeHtml(c.cmd) + '</code><span>' + escapeHtml(c.desc) + '</span></div>';
}).join("") +
'</div></div>'
: "";
var howItWorksHtml = p.howItWorks
? '<div class="detail-section"><h4>How it works</h4><p>' + escapeHtml(p.howItWorks) + '</p></div>'
: "";
var featuresSection = featuresHtml
? '<div class="detail-section"><h4>Features</h4><ul class="feature-list">' + featuresHtml + '</ul></div>'
: "";
detailViewEl.innerHTML =
'<div class="detail-banner" style="--page-accent: ' + escapeHtml(p.accent) + '">' +
'<div class="wrap">' +
'<button class="back-link" id="backLink">&larr; All plugins</button>' +
'<div class="detail-top">' +
'<h2>' + escapeHtml(p.name) + '</h2>' +
'<span class="badge ' + p.status + '">' + (STATUS_LABEL[p.status] || p.status) + '</span>' +
'</div>' +
'<div class="detail-category">' + escapeHtml(p.category) + '</div>' +
'<p class="detail-tagline">' + escapeHtml(p.tagline || p.description) + '</p>' +
'<div class="detail-actions" id="detailActions"></div>' +
'</div>' +
'</div>' +
'<div class="wrap">' +
'<div class="detail-body" style="--page-accent: ' + escapeHtml(p.accent) + '">' +
'<div>' + howItWorksHtml + featuresSection + commandsHtml + '</div>' +
'<div>' +
(p.note ? '<div class="side-card"><h5>Backstory</h5><p>' + escapeHtml(p.note) + '</p></div>' : "") +
(p.limitations ? '<div class="side-card warn"><h5>Known limitations</h5><p>' + escapeHtml(p.limitations) + '</p></div>' : "") +
(p.authors && p.authors.length ? '<div class="side-card"><h5>Author</h5><p class="authors-line">' + escapeHtml(p.authors.join(", ")) + '</p></div>' : "") +
'</div>' +
'</div>' +
'</div>';
var actions = document.getElementById("detailActions");
actions.appendChild(installButton(p.installUrl));
var sourceLink = document.createElement("a");
sourceLink.href = p.sourceUrl;
sourceLink.target = "_blank";
sourceLink.rel = "noopener";
sourceLink.textContent = "Source";
actions.appendChild(sourceLink);
var issueLink = document.createElement("a");
issueLink.href = p.issueUrl;
issueLink.target = "_blank";
issueLink.rel = "noopener";
issueLink.textContent = "Report issue";
actions.appendChild(issueLink);
document.getElementById("backLink").addEventListener("click", function () {
history.pushState(null, "", "/");
showGrid();
});
}
function showDetail(id) {
var plugin = state.plugins.filter(function (p) { return p.id === id; })[0];
if (!plugin) { showGrid(); return; }
renderDetail(plugin);
heroEl.classList.add("hidden");
controlsEl.classList.add("hidden");
gridViewEl.classList.add("hidden");
detailViewEl.classList.add("active");
window.scrollTo(0, 0);
document.title = plugin.name + " · revenge-plugins";
}
function showGrid() {
heroEl.classList.remove("hidden");
controlsEl.classList.remove("hidden");
gridViewEl.classList.remove("hidden");
detailViewEl.classList.remove("active");
document.title = "revenge-plugins";
}
/**
* Each plugin has a real static page at /<id>/ (a copy of this same file), so a direct visit or
* refresh works without any server-side routing - this just avoids a full reload when navigating
* from within an already-loaded page.
*/
function navigateToPlugin(id) {
history.pushState(null, "", "/" + id + "/");
showDetail(id);
}
function routeFromLocation() {
var match = location.pathname.match(/^\/([\w-]+)\/?$/);
if (match && match[1]) {
showDetail(match[1]);
} else {
showGrid();
}
}
window.addEventListener("popstate", routeFromLocation);
/* ---------- Command palette ---------- */
var paletteIndex = 0;
var paletteMatches = [];
function openPalette() {
paletteOverlay.classList.add("open");
paletteInput.value = "";
renderPalette("");
setTimeout(function () { paletteInput.focus(); }, 0);
}
function closePalette() {
paletteOverlay.classList.remove("open");
}
function renderPalette(q) {
q = q.trim().toLowerCase();
paletteMatches = state.plugins.filter(function (p) {
return !q || (p.name + " " + p.category).toLowerCase().indexOf(q) !== -1;
});
paletteIndex = 0;
if (!paletteMatches.length) {
paletteResults.innerHTML = '<div class="palette-empty">No matches</div>';
return;
}
paletteResults.innerHTML = paletteMatches.map(function (p, i) {
return '<div class="palette-item" data-idx="' + i + '" aria-selected="' + (i === 0) + '" style="--item-accent:' + p.accent + '">' +
'<span class="dot"></span><span class="name">' + escapeHtml(p.name) + '</span><span class="cat">' + escapeHtml(p.category) + '</span>' +
'</div>';
}).join("");
Array.from(paletteResults.children).forEach(function (el, i) {
el.addEventListener("click", function () { selectPaletteItem(i); });
});
}
function updatePaletteSelection() {
Array.from(paletteResults.children).forEach(function (el, i) {
el.setAttribute("aria-selected", String(i === paletteIndex));
});
var active = paletteResults.children[paletteIndex];
if (active) active.scrollIntoView({ block: "nearest" });
}
function selectPaletteItem(i) {
var plugin = paletteMatches[i];
if (!plugin) return;
closePalette();
navigateToPlugin(plugin.id);
}
paletteInput.addEventListener("input", function (e) { renderPalette(e.target.value); });
paletteOverlay.addEventListener("click", function (e) { if (e.target === paletteOverlay) closePalette(); });
paletteInput.addEventListener("keydown", function (e) {
if (e.key === "ArrowDown") { e.preventDefault(); paletteIndex = Math.min(paletteIndex + 1, paletteMatches.length - 1); updatePaletteSelection(); }
else if (e.key === "ArrowUp") { e.preventDefault(); paletteIndex = Math.max(paletteIndex - 1, 0); updatePaletteSelection(); }
else if (e.key === "Enter") { e.preventDefault(); selectPaletteItem(paletteIndex); }
else if (e.key === "Escape") { closePalette(); }
});
document.addEventListener("keydown", function (e) {
var isTyping = document.activeElement && (document.activeElement.tagName === "INPUT" || document.activeElement.tagName === "TEXTAREA");
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
e.preventDefault();
openPalette();
return;
}
if (e.key === "/" && !isTyping) {
e.preventDefault();
openPalette();
return;
}
if (e.key === "Escape" && paletteOverlay.classList.contains("open")) {
closePalette();
}
});
/* ---------- Boot ---------- */
fetch("https://rp.jarviscli.dev/plugins-data.json")
.then(function (r) { return r.json(); })
.then(function (data) {
state.plugins = data;
renderStats();
renderChips();
render();
routeFromLocation();
})
.catch(function () {
count.textContent = "";
empty.hidden = false;
empty.textContent = "Couldn't load the plugin list - try refreshing.";
});
})();
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1 @@
(function(f,b,c,d,e,v,g,E,S){"use strict";function w(t,n,r,o){try{return r()}catch(a){return o.error(`[${t}] Failed to apply the "${n}" patch, that surface will be skipped: ${a}`),function(){}}}function N(t,n,r){const o=Object.entries(r).map(function([a,s]){return w(t,a,s,n)});return function(){return o.forEach(function(a){return a()})}}function P(t,n,r=100){let o=!1,a;const s=t();return s?(n(s),{cancel(){}}):(a=setInterval(function(){if(o)return;const i=t();i&&(o=!0,clearInterval(a),n(i))},r),{cancel(){o=!0,a&&clearInterval(a)}})}const p=["none","dms","settings"],I={none:"None",dms:"Direct Messages",settings:"Settings"};let h=function(){};function T(){let t=function(){};const n=P(function(){return d.findByTypeName("YouBarNotificationsButton")},function(r){t=Y(r)});return function(){n.cancel(),t()}}function Y(t){const n=d.findByProps("openUserSettings"),r=d.findByProps("transitionToGuild"),o={dms:g.getAssetIDByName("ChatIcon"),settings:g.getAssetIDByName("SettingsIcon")},a=function(s){s==="dms"?r?.transitionToGuild?.("@me"):s==="settings"&&n?.openUserSettings?.()};return v.instead("type",t,function(s,i){try{const[,D]=e.React.useReducer(function(l){return~l},0);h=function(){return D()};const u=i(...s);if(!u?.props?.children)return u;const L=u.props.children.type,B=u.props.children.props,m=[c.storage.slot1,c.storage.slot2];return e.React.createElement(e.React.Fragment,null,m.map(function(l,H){return l==="none"?null:e.React.createElement(L,{key:`you-bar-plus-${H}`,variant:B?.variant||"tertiary",size:B?.size||"sm",icon:o[l],onPress:function(){return a(l)}})}),u)}catch{return i(...s)}})}const{ScrollView:F,View:O}=e.ReactNative;function U({children:t}){return e.React.createElement(F,{style:{flex:1}},t,e.React.createElement(O,{style:{height:24}}))}const{View:_,Text:A}=e.ReactNative;function V({children:t,style:n}){return e.React.createElement(_,{style:[{marginHorizontal:16,marginVertical:8,padding:10,borderRadius:8,borderWidth:1,borderColor:"rgba(128,128,128,0.25)"},n]},e.React.createElement(A,{style:{fontSize:12.5,lineHeight:18,opacity:.75}},t))}const{FormSection:$,FormRow:k}=S.Forms;function x(t){const n=p.indexOf(t);return p[(n+1)%p.length]}function y({label:t,slotKey:n}){const r=c.storage[n];return e.React.createElement(k,{label:t,subLabel:I[r],onPress:function(){c.storage[n]=x(r),h()}})}function z(){return E.useProxy(c.storage),e.React.useEffect(function(){return function(){return h()}},[]),e.React.createElement(U,null,e.React.createElement(V,null,"YouBar+ adds up to 2 buttons to the YouBar - the row with your avatar and status near the top of the app. Tap a slot below to cycle it through None, Direct Messages, and Settings; the first slot renders on the left. Fork of Purple-EyeZ's YouBar+, ported to Revenge's Vendetta-compat API with a fix for a startup race that could silently skip the button patch for an entire session."),e.React.createElement($,{title:"Buttons"},e.React.createElement(y,{label:"First button",slotKey:"slot1"}),e.React.createElement(y,{label:"Second button",slotKey:"slot2"})))}let R=function(){};var C={onLoad:function(){c.storage.slot1??="dms",c.storage.slot2??="settings",R=N("YouBar+",b.logger,{"YouBar buttons":T})},onUnload:function(){return R()},settings:z};return f.default=C,Object.defineProperty(f,"__esModule",{value:!0}),f})({},vendetta,vendetta.plugin,vendetta.metro,vendetta.metro.common,vendetta.patcher,vendetta.ui.assets,vendetta.storage,vendetta.ui.components);

View file

@ -0,0 +1 @@
{"name":"YouBar+","description":"Adds up to 2 configurable buttons (Direct Messages, Settings, or none) to the YouBar, in whatever order you pick.","authors":[{"name":"everestmcarthur"},{"name":"Purple_Ξye™"}],"main":"index.js","hash":"0732a112d0498fa94fd83b7336564c149f03e957ed6d2164920e8d0b3a6e9fe7"}