next-site: bold/kinetic redesign, source/issues links, install instructions
This commit is contained in:
parent
f91874d676
commit
3bab720f8f
4 changed files with 109 additions and 54 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { htmlShell } from '../theme'
|
import { htmlShell, renderHeader } from '../theme'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single-page admin UI: a token field gates everything else client-side (the real gate is
|
* Single-page admin UI: a token field gates everything else client-side (the real gate is
|
||||||
|
|
@ -7,13 +7,7 @@ import { htmlShell } from '../theme'
|
||||||
*/
|
*/
|
||||||
export function renderAdminPage(): string {
|
export function renderAdminPage(): string {
|
||||||
const body = `
|
const body = `
|
||||||
<header>
|
${renderHeader({ brandName: 'Admin' })}
|
||||||
<div class="brand">
|
|
||||||
<a href="/" class="brand-mark" style="text-decoration:none;">N</a>
|
|
||||||
<div class="brand-name">Admin</div>
|
|
||||||
</div>
|
|
||||||
<nav><a href="/">Back to site</a></nav>
|
|
||||||
</header>
|
|
||||||
<div class="wrap" style="padding-top:22px;">
|
<div class="wrap" style="padding-top:22px;">
|
||||||
<div id="gate">
|
<div id="gate">
|
||||||
<div class="page-sub">Admin token</div>
|
<div class="page-sub">Admin token</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { escapeHtml, htmlShell } from '../theme'
|
import { escapeHtml, htmlShell, renderHeader } from '../theme'
|
||||||
import type { Override } from '../db'
|
import type { Override } from '../db'
|
||||||
import type { BaseIndex } from '../publicIndex'
|
import type { BaseIndex } from '../publicIndex'
|
||||||
|
|
||||||
|
|
@ -30,16 +30,15 @@ export function renderBrowsePage(
|
||||||
.join('')
|
.join('')
|
||||||
|
|
||||||
const body = `
|
const body = `
|
||||||
<header>
|
${renderHeader({ brandName: index.name })}
|
||||||
<div class="brand">
|
|
||||||
<div class="brand-mark">N</div>
|
|
||||||
<div class="brand-name">${escapeHtml(index.name)}</div>
|
|
||||||
</div>
|
|
||||||
<nav><span>Plugins</span></nav>
|
|
||||||
</header>
|
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<div class="page-title" style="margin-top: 22px;">Plugins</div>
|
<h1>Build different<span class="accent">.</span></h1>
|
||||||
<div class="page-sub">${entries.length} available · installs directly into Revenge Next</div>
|
<div class="page-sub">Plugins for Revenge Next, made to be installed in seconds and to actually feel finished.</div>
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat"><b>${entries.length}</b><span>Plugins</span></div>
|
||||||
|
<div class="stat"><b>100%</b><span>Open source</span></div>
|
||||||
|
<div class="stat"><b>1-tap</b><span>Install</span></div>
|
||||||
|
</div>
|
||||||
<div class="grid">${cards}</div>
|
<div class="grid">${cards}</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
import { escapeHtml, htmlShell } from '../theme'
|
import {
|
||||||
|
escapeHtml,
|
||||||
|
htmlShell,
|
||||||
|
ISSUES_URL,
|
||||||
|
renderHeader,
|
||||||
|
SOURCE_URL,
|
||||||
|
} from '../theme'
|
||||||
import type { Override } from '../db'
|
import type { Override } from '../db'
|
||||||
import type { IndexPlugin } from '../publicIndex'
|
import type { IndexPlugin } from '../publicIndex'
|
||||||
|
|
||||||
|
|
@ -14,21 +20,28 @@ export function renderDetailPage(
|
||||||
? `<ul>${override.features.map(f => `<li>${escapeHtml(f)}</li>`).join('')}</ul>`
|
? `<ul>${override.features.map(f => `<li>${escapeHtml(f)}</li>`).join('')}</ul>`
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
|
const repoUrl = 'https://next.jarviscli.dev/index.json'
|
||||||
|
|
||||||
const body = `
|
const body = `
|
||||||
<header>
|
${renderHeader({ brandName: plugin.name })}
|
||||||
<div class="brand">
|
|
||||||
<a href="/" class="brand-mark" style="text-decoration:none;">N</a>
|
|
||||||
<div class="brand-name">${escapeHtml(plugin.name)}</div>
|
|
||||||
</div>
|
|
||||||
<nav><a href="/">All plugins</a></nav>
|
|
||||||
</header>
|
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<div class="page-title" style="margin-top: 22px;">${escapeHtml(plugin.name)}</div>
|
<h1 class="page-title" style="font-size:42px;">${escapeHtml(plugin.name)}</h1>
|
||||||
<div class="page-sub">${escapeHtml(tagline)}</div>
|
<div class="page-sub">${escapeHtml(tagline)}</div>
|
||||||
<p style="font-size:13px;line-height:1.6;color:rgba(255,255,255,0.75);">${escapeHtml(description)}</p>
|
<p style="font-size:14.5px;line-height:1.65;color:rgba(255,255,255,0.75);max-width:560px;">${escapeHtml(description)}</p>
|
||||||
${featuresHtml}
|
${featuresHtml}
|
||||||
<div class="card-meta" style="margin: 16px 0;">${escapeHtml(plugin.author)} · v${escapeHtml(version ?? '?')}</div>
|
<div class="card-meta" style="margin: 18px 0 26px;">${escapeHtml(plugin.author)} · v${escapeHtml(version ?? '?')}</div>
|
||||||
<a class="btn" href="/index.json">Add repository in Revenge Next</a>
|
|
||||||
|
<div class="card" style="max-width:560px;margin-bottom:24px;">
|
||||||
|
<div class="card-name" style="font-size:15px;">Install</div>
|
||||||
|
<div class="card-desc">In Revenge Next, add this repository by URL:</div>
|
||||||
|
<div style="background:#0b0c10;border:1px solid rgba(255,255,255,0.1);border-radius:8px;padding:10px 12px;margin-top:10px;font-family:ui-monospace,monospace;font-size:12.5px;color:#b6acff;word-break:break-all;">${escapeHtml(repoUrl)}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;gap:12px;">
|
||||||
|
<a class="btn" href="${escapeHtml(repoUrl)}">Add repository</a>
|
||||||
|
<a class="btn-ghost" href="${SOURCE_URL}" target="_blank" rel="noopener">View source</a>
|
||||||
|
<a class="btn-ghost" href="${ISSUES_URL}" target="_blank" rel="noopener">Report an issue</a>
|
||||||
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
|
||||||
return htmlShell({
|
return htmlShell({
|
||||||
|
|
|
||||||
|
|
@ -2,56 +2,105 @@ export const PAGE_CSS = `
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: #0b0c10;
|
background: linear-gradient(180deg, #0c0d12, #08090c);
|
||||||
color: #e8e9ed;
|
color: #fff;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif;
|
||||||
}
|
}
|
||||||
a { color: inherit; text-decoration: none; }
|
a { color: inherit; text-decoration: none; }
|
||||||
.wrap { max-width: 960px; margin: 0 auto; padding: 0 24px; }
|
.wrap { max-width: 1040px; margin: 0 auto; padding: 0 32px; }
|
||||||
header {
|
header {
|
||||||
display: flex; align-items: center; justify-content: space-between;
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
padding: 16px 24px; border-bottom: 1px solid rgba(255,255,255,0.07);
|
padding: 20px 32px; border-bottom: 1px solid rgba(255,255,255,0.07);
|
||||||
}
|
}
|
||||||
.brand { display: flex; align-items: center; gap: 10px; }
|
.brand { display: flex; align-items: center; gap: 10px; }
|
||||||
.brand-mark {
|
.brand-mark {
|
||||||
width: 22px; height: 22px; border-radius: 6px; background: #15161c;
|
width: 24px; height: 24px; border-radius: 7px; background: #15161c;
|
||||||
border: 1px solid rgba(255,255,255,0.12); display: flex; align-items: center;
|
border: 1px solid rgba(255,255,255,0.12); display: flex; align-items: center;
|
||||||
justify-content: center; font-size: 11px; font-weight: 700; color: #9a8cff;
|
justify-content: center; font-size: 12px; font-weight: 700; color: #9a8cff;
|
||||||
}
|
}
|
||||||
.brand-name { font-weight: 600; font-size: 13.5px; letter-spacing: -0.01em; }
|
.brand-name { font-weight: 650; font-size: 14.5px; letter-spacing: -0.01em; }
|
||||||
nav { display: flex; align-items: center; gap: 18px; font-size: 12px; color: rgba(255,255,255,0.5); }
|
nav { display: flex; align-items: center; gap: 22px; font-size: 13px; color: rgba(255,255,255,0.5); }
|
||||||
.page-title { font-size: 19px; font-weight: 650; letter-spacing: -0.015em; margin-bottom: 4px; }
|
nav a:hover { color: rgba(255,255,255,0.85); }
|
||||||
.page-sub { font-size: 12.5px; color: rgba(255,255,255,0.45); margin-bottom: 16px; }
|
|
||||||
|
h1 { font-size: 58px; font-weight: 800; letter-spacing: -0.03em; line-height: 1; margin: 0 0 16px; }
|
||||||
|
h1 .accent { color: #9a8cff; }
|
||||||
|
.page-title { font-size: 58px; font-weight: 800; letter-spacing: -0.03em; line-height: 1; margin: 44px 0 16px; }
|
||||||
|
.page-sub { font-size: 16.5px; color: rgba(255,255,255,0.55); max-width: 520px; line-height: 1.55; margin-bottom: 30px; }
|
||||||
|
|
||||||
|
.stats { display: flex; gap: 36px; margin-bottom: 44px; padding-bottom: 30px; border-bottom: 1px solid rgba(255,255,255,0.08); }
|
||||||
|
.stat b { display: block; font-size: 30px; font-weight: 750; }
|
||||||
|
.stat span { font-size: 12px; color: rgba(255,255,255,0.45); text-transform: uppercase; letter-spacing: 0.04em; }
|
||||||
|
|
||||||
.chip {
|
.chip {
|
||||||
background: transparent; border: 1px solid rgba(255,255,255,0.07); border-radius: 7px;
|
background: transparent; border: 1px solid rgba(255,255,255,0.07); border-radius: 7px;
|
||||||
padding: 5px 11px; font-size: 11.5px; color: rgba(255,255,255,0.4); display: inline-block;
|
padding: 6px 13px; font-size: 12.5px; color: rgba(255,255,255,0.4); display: inline-block;
|
||||||
|
transition: border-color 0.2s ease, color 0.2s ease;
|
||||||
}
|
}
|
||||||
.chip.active { background: #17181e; border-color: rgba(255,255,255,0.09); color: rgba(255,255,255,0.85); }
|
.chip.active { background: #17181e; border-color: rgba(255,255,255,0.09); color: rgba(255,255,255,0.85); }
|
||||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 4px 0 24px; }
|
|
||||||
|
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; padding: 4px 0 40px; }
|
||||||
.card {
|
.card {
|
||||||
background: linear-gradient(180deg, rgba(255,255,255,0.045), rgba(255,255,255,0.015));
|
background: #131318; border: 1px solid rgba(255,255,255,0.07); border-radius: 16px; padding: 24px;
|
||||||
border: 1px solid rgba(255,255,255,0.08); border-radius: 11px; padding: 15px;
|
position: relative; overflow: hidden;
|
||||||
|
transition: transform 0.3s cubic-bezier(.2,.8,.2,1), border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
.card-top { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 10px; }
|
.card::before {
|
||||||
|
content: ''; position: absolute; inset: 0; opacity: 0; transition: opacity 0.3s ease; pointer-events: none;
|
||||||
|
background: radial-gradient(160px 120px at 20% 0%, rgba(154,140,255,0.22), transparent 70%);
|
||||||
|
}
|
||||||
|
.card:hover { transform: translateY(-6px) scale(1.01); border-color: rgba(154,140,255,0.45); }
|
||||||
|
.card:hover::before { opacity: 1; }
|
||||||
|
.card-top { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 14px; }
|
||||||
.card-icon {
|
.card-icon {
|
||||||
width: 30px; height: 30px; border-radius: 8px; background: #1a1b22;
|
width: 34px; height: 34px; border-radius: 9px; background: #1a1b22;
|
||||||
border: 1px solid rgba(255,255,255,0.1); display: flex; align-items: center;
|
border: 1px solid rgba(255,255,255,0.1); display: flex; align-items: center;
|
||||||
justify-content: center; font-size: 13px;
|
justify-content: center; font-size: 15px;
|
||||||
}
|
}
|
||||||
.badge {
|
.badge {
|
||||||
background: rgba(154,140,255,0.12); color: #b6acff; border: 1px solid rgba(154,140,255,0.25);
|
background: rgba(154,140,255,0.12); color: #b6acff; border: 1px solid rgba(154,140,255,0.25);
|
||||||
font-size: 9.5px; font-weight: 600; padding: 2px 7px; border-radius: 5px;
|
font-size: 10px; font-weight: 650; padding: 3px 8px; border-radius: 5px;
|
||||||
}
|
}
|
||||||
.card-name { font-size: 13.5px; font-weight: 600; letter-spacing: -0.005em; }
|
.card-name { position: relative; font-size: 18px; font-weight: 700; letter-spacing: -0.01em; }
|
||||||
.card-desc { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 3px; line-height: 1.4; }
|
.card-desc { position: relative; font-size: 13.5px; color: rgba(255,255,255,0.55); margin-top: 6px; line-height: 1.5; }
|
||||||
.card-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; }
|
.card-foot { position: relative; display: flex; align-items: center; justify-content: space-between; margin-top: 18px; }
|
||||||
.card-meta { font-size: 10px; color: rgba(255,255,255,0.35); }
|
.card-meta { font-size: 11.5px; color: rgba(255,255,255,0.4); }
|
||||||
.btn {
|
.btn {
|
||||||
background: #e8e9ed; color: #0b0c10; font-size: 10.5px; font-weight: 600;
|
background: #e8e9ed; color: #0b0c10; font-size: 13px; font-weight: 650;
|
||||||
padding: 4px 10px; border-radius: 6px; display: inline-block;
|
padding: 8px 16px; border-radius: 8px; display: inline-block;
|
||||||
|
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
.btn:hover { transform: translateY(-2px); opacity: 0.92; }
|
||||||
|
.btn-ghost {
|
||||||
|
border: 1px solid rgba(255,255,255,0.16); color: #fff; font-size: 13px; font-weight: 600;
|
||||||
|
padding: 8px 16px; border-radius: 8px; display: inline-block;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const SOURCE_URL =
|
||||||
|
'https://github.com/everestmcarthur/revenge-next-plugs'
|
||||||
|
export const ISSUES_URL =
|
||||||
|
'https://github.com/everestmcarthur/revenge-next-plugs/issues'
|
||||||
|
|
||||||
|
/** Shared header markup - same nav (Plugins/Source/Issues) on every page for consistency. */
|
||||||
|
export function renderHeader(opts: {
|
||||||
|
brandName: string
|
||||||
|
brandHref?: string
|
||||||
|
}): string {
|
||||||
|
const brandHref = opts.brandHref ?? '/'
|
||||||
|
return `
|
||||||
|
<header>
|
||||||
|
<div class="brand">
|
||||||
|
<a href="${brandHref}" class="brand-mark" style="text-decoration:none;">N</a>
|
||||||
|
<div class="brand-name">${escapeHtml(opts.brandName)}</div>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<a href="/">Plugins</a>
|
||||||
|
<a href="${SOURCE_URL}" target="_blank" rel="noopener">Source</a>
|
||||||
|
<a href="${ISSUES_URL}" target="_blank" rel="noopener">Issues</a>
|
||||||
|
</nav>
|
||||||
|
</header>`
|
||||||
|
}
|
||||||
|
|
||||||
export function htmlShell(opts: {
|
export function htmlShell(opts: {
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue