This commit is contained in:
Niki Wix Skaarup 2025-03-29 05:01:26 +01:00
commit 5ae824b63a
Signed by: nikiskaarup
GPG key ID: FC2F1B116F6E788C
36 changed files with 3290 additions and 0 deletions

128
src/build.ts Normal file
View file

@ -0,0 +1,128 @@
import { env } from "bun";
import { SveltePlugin } from "bun-plugin-svelte";
import Tail from "bun-plugin-tailwind";
import path from "path";
const development = env.NODE_ENV !== "production";
const publicPath = development
? "http://localhost:3000/html/"
: "https://userscripts.skaarup.dev/html/";
function replaceUrl(text: string): string {
if (!development) return text;
return text.replaceAll(
"https://userscripts.skaarup.dev",
"http://localhost:3000",
);
}
const tagStart = `// ==UserScript==\n`;
const tagEnd = `// ==/UserScript==\n`;
const tagEndLen = tagEnd.length;
const globScanOptions: Bun.GlobScanOptions = {
absolute: true,
};
const userscripts = new Bun.Glob("./src/userscripts/*/*.user.ts");
for await (const absPath of userscripts.scan(globScanOptions)) {
const parsed = path.parse(absPath);
console.log(`Building ${parsed.base}...`);
const file = Bun.file(absPath);
const fileText = await file.text();
const bannerStart = fileText.indexOf(tagStart);
const bannerEnd = fileText.indexOf(tagEnd) + tagEndLen;
let banner = fileText.slice(bannerStart, bannerEnd);
banner = replaceUrl(banner);
await Bun.build({
entrypoints: [absPath],
banner,
outdir: `./build`,
splitting: false,
minify: false,
env: "PUBLIC_*",
sourcemap: "linked",
packages: "bundle",
target: "browser", // Also supports "bun" or "node" for server-side components
plugins: [
Tail,
SveltePlugin({
development: false, // Enable dev features, set to false for production
}),
],
publicPath,
});
await swapHtmlImports(fileText, `./build/${parsed.name}.js`);
}
const styles = new Bun.Glob("./src/userscripts/*/*.user.css");
for await (const absPath of styles.scan(globScanOptions)) {
const name = path.parse(absPath).base;
console.log(`Building ${name}...`);
const outFile = `./build/${name}`;
await Bun.$`bunx @tailwindcss/cli --optimize -i ${absPath} -o ${outFile}`.quiet();
const file = Bun.file(outFile);
let text = await file.text();
text = replaceUrl(text);
file.writer().write(text);
}
async function swapHtmlImports(fileText: string, filePath: string) {
const imports: string[] = [];
const lines = fileText.split("\n");
for (const line of lines) {
if (
!line.startsWith("import ") ||
(!line.endsWith('.html";') && !line.endsWith(".html';"))
)
continue;
// Extract the import path between the quotes
const match = line.match(/['"]([^'"]+)['"]/);
if (!match) continue;
const importPath = match[1];
// Check if the import path is a .html file
if (!importPath) continue;
imports.push(path.parse(importPath).name);
}
const outFile = Bun.file(filePath);
const text = await outFile.text();
const startIdx = text.indexOf(tagStart);
const endIdx = text.indexOf(tagEnd);
const banner = text.slice(startIdx, endIdx);
let js = text.slice(endIdx);
const htmlFiles = new Bun.Glob("./build/*.html");
for await (const filePath of htmlFiles.scan(globScanOptions)) {
const parsed = path.parse(filePath);
const fileName = parsed.base;
if (!imports.some((x) => fileName.startsWith(x))) continue;
const file = Bun.file(filePath);
const html = await file.text();
js = js.replace(`${publicPath}${fileName}`, btoa(html));
}
outFile.writer().write(`${banner}${js}`);
}

28
src/index.css Normal file
View file

@ -0,0 +1,28 @@
@import 'tailwindcss';
@theme {
--breakout-size: calc((var(--breakpoint-xl) - var(--breakpoint-lg)) / 2);
--ultrawide-val: minmax(calc(var(--spacing) * 4), 1fr);
--breakout-val: minmax(0, var(--breakout-size));
--content-val: min(100% - calc(var(--spacing) * 8), var(--breakpoint-lg));
}
@layer base {
* {
min-width: 0;
}
.content {
grid-template-columns:
[ultrawide-start] var(--ultrawide-val)
[breakout-start] var(--breakout-val)
[content-start] var(--content-val)
[content-end] var(--breakout-val)
[breakout-end] var(--ultrawide-val)
[ultrawide-end];
}
.content > * {
grid-column: content;
}
}

86
src/index.html Normal file

File diff suppressed because one or more lines are too long

0
src/index.ts Normal file
View file

113
src/server.ts Normal file
View file

@ -0,0 +1,113 @@
import { env } from "bun";
import homepage from "./index.html";
import robotsTxt from "./static/robots.txt";
import sitemapTxt from "./static/sitemap.txt";
// @ts-ignore ts2307
import icon from "./static/favicon.png" with { type: "file" };
import path from "path";
const favicon = await Bun.file(icon).bytes();
let htmlFiles: Set<string>;
let scriptFiles: Set<string>;
let cssFiles: Set<string>;
const development = env.NODE_ENV !== "production";
const globScanOptions: Bun.GlobScanOptions = {
absolute: true,
};
async function load() {
await Bun.$`bun run src/build.ts`;
const tempHtml: Set<string> = new Set();
const userHtml = new Bun.Glob("./build/*.html");
for await (const filePath of userHtml.scan(globScanOptions)) {
tempHtml.add(path.parse(filePath).base);
}
htmlFiles = tempHtml;
const tempScripts: Set<string> = new Set();
const userJs = new Bun.Glob("./build/*.user.js");
for await (const filePath of userJs.scan(globScanOptions)) {
tempScripts.add(path.parse(filePath).base);
}
scriptFiles = tempScripts;
const tempStyles: Set<string> = new Set();
const css = new Bun.Glob("./build/*.user.css");
for await (const filePath of css.scan(globScanOptions)) {
tempStyles.add(path.parse(filePath).base);
}
cssFiles = tempStyles;
}
await load();
Bun.serve({
routes: {
"/": homepage,
"/robots.txt": new Response(robotsTxt, {
headers: { "Content-Type": "text/plain" },
}),
"/sitemap.txt": new Response(sitemapTxt, {
headers: { "Content-Type": "text/plain" },
}),
"/favicon.ico": new Response(favicon, {
headers: { "Content-Type": "image/png" },
}),
"/health": new Response("OK"),
"/html/:html": async (req) => {
const html = req.params.html.toLocaleLowerCase();
if (!htmlFiles.has(html)) {
return new Response("Not found", { status: 404 });
}
const file = Bun.file(`./build/${html}`);
return new Response(file, {
headers: { "Content-Type": "text/css" },
});
},
"/scripts/:script": async (req) => {
const script = req.params.script.toLocaleLowerCase();
if (!scriptFiles.has(script)) {
return new Response("Not found", { status: 404 });
}
const file = Bun.file(`./build/${script}`);
return new Response(file, {
headers: { "Content-Type": "application/javascript" },
});
},
"/styles/:style": async (req) => {
const style = req.params.style.toLocaleLowerCase();
if (!cssFiles.has(style)) {
return new Response("Not found", { status: 404 });
}
const file = Bun.file(`./build/${style}`);
return new Response(file, {
headers: { "Content-Type": "text/css" },
});
},
},
development,
// async fetch(req, server) {
// return new Response("Not found", { status: 404 });
// },
});
if (development) {
setInterval(async () => {
try {
await load();
} catch (e) {
console.error(e);
}
}, 2500);
}

File diff suppressed because one or more lines are too long

BIN
src/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

10
src/static/manifest.json Normal file
View file

@ -0,0 +1,10 @@
{
"name": "Userscripts by skaarup.dev",
"short_name": "Userscripts",
"display": "browser",
"background_color": "#141141",
"theme_color": "#371D85",
"description": "Userscripts",
"icons": [],
"related_applications": []
}

4
src/static/robots.txt Normal file
View file

@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://userscripts.skaarup.dev/sitemap.txt

1
src/static/sitemap.txt Normal file
View file

@ -0,0 +1 @@
https://userscripts.skaarup.dev/

7
src/types/configuration-ui.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
namespace configuration_ui {
type globalsType = {
uiInitialized: boolean;
configurationWindowOpen: boolean;
};
}

76
src/types/greasemonkey.d.ts vendored Normal file
View file

@ -0,0 +1,76 @@
// Greasemonkey api
// https://wiki.greasespot.net/Greasemonkey_Manual:API
type gm_responseObjectType = {
readyState: number;
responseHeaders: string;
responseText: string;
status: number;
statusText: string;
context?: unknown;
lengthComputable?: boolean;
loaded?: number;
total?: number;
};
type gm_detailType = {
binary?: boolean;
context?: unknown;
data?: string;
headers?: { [key: string]: string };
method?: string;
overrideMimeType?: string;
password?: string;
responseType?: string;
synchronous?: boolean;
timeout?: number;
upload?: {
onabort?: (event: gm_responseObjectType) => void;
onerror?: (event: gm_responseObjectType) => void;
onload?: (event: gm_responseObjectType) => void;
onprogress?: (event: gm_responseObjectType) => void;
};
url: string;
user?: string;
onabort?: (event: gm_responseObjectType) => void;
onerror?: (event: gm_responseObjectType) => void;
onload?: (event: gm_responseObjectType) => void;
onprogress?: (event: gm_responseObjectType) => void;
onreadystatechange?: (event: gm_responseObjectType) => void;
ontimeout?: (event: gm_responseObjectType) => void;
};
type gm_optionType = {
text: string;
title?: string;
image?: string;
onclick?: () => void;
ondone?: () => void;
};
type gm_resourceType = {
[key: string]: {
name: string;
mimetype: string;
url: string;
};
};
type gm_scriptType = {
description: string;
excludes: string[];
includes: string[];
matches: string[];
name: string;
namespace: string;
resources: gm_resourceType;
"run-at": string;
version: string;
};
type gm_infoType = {
script: gm_scriptType;
scriptMetaStr: string;
scriptHandler: string;
version: string;
};

28
src/types/manga-reading.d.ts vendored Normal file
View file

@ -0,0 +1,28 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
namespace manga_reading {
type At = "neither" | "chapter" | "manga";
type globalsType = {
nextUrl: string;
prevUrl: string;
currentTitle: string;
uiInitialized: boolean;
at: At;
titleList: string[];
ptApi: {
url: string;
bearerToken: string;
};
};
}
type shortcutType = "Global" | "ConfigOpen" | "ConfigClosed";
type shortcutCallback = (event: KeyboardEvent) => Promise<boolean>;
type shortcut = {
name: string;
callback: shortcutCallback;
};
type registeredShortcut = { shortcutType: shortcutType; shortcut: shortcut };

291
src/types/violentmonkey.d.ts vendored Normal file
View file

@ -0,0 +1,291 @@
type vm_responseObjectTypeUnknown = vm_responseObjectType<unknown>;
type vm_responseObjectType<T> = {
status: number;
statusText: string;
readyState: number;
responseHeaders: string;
response: string | Blob | ArrayBuffer | Document | object | null;
responseText: string | undefined;
responseXML: Document | null;
lengthComputable: boolean;
loaded: number;
total: number;
finalUrl: string;
context: T;
};
type vm_detailType<T> = {
url: string;
method?: string;
user?: string;
password?: string;
overrideMimeType?: string;
headers?: { [key: string]: string };
responseType?: string;
timeout?: number;
data?:
| string
| ArrayBuffer
| Blob
| DataView
| FormData
| ReadableStream
| TypedArray
| URLSearchParams;
binary?: boolean;
context?: T;
anonymous?: boolean;
onabort?: (resp: vm_responseObjectType<T>) => void;
onerror?: (resp: vm_responseObjectType<T>) => void;
onload?: (resp: vm_responseObjectType<T>) => void;
onloadend?: (resp: vm_responseObjectType<T>) => void;
onloadstart?: (resp: vm_responseObjectType<T>) => void;
onprogress?: (resp: vm_responseObjectType<T>) => void;
onreadystatechange?: (resp: vm_responseObjectType<T>) => void;
ontimeout?: (resp: vm_responseObjectType<T>) => void;
};
type vm_optionType = {
text: string;
title?: string;
image?: string;
onclick?: () => void;
ondone?: () => void;
};
type vm_resourceType = {
name: string;
url: string;
};
type vm_scriptType = {
description: string;
excludes: Array<string>;
includes: Array<string>;
matches: Array<string>;
name: string;
namespace: string;
resources: Array<vm_resourceType>;
"run-at": string;
version: string;
};
type vm_platformType = {
arch: string;
browserName: string;
browserVersion: string;
fullVersionList: Array<{
brand: string;
version: string;
}>;
mobile: boolean;
os: string;
};
type vm_infoType = {
uuid: string;
scriptMetaStr: string;
scriptWillUpdate: boolean;
scriptHandler: string;
version: string;
isIncognito: boolean;
platform: vm_platformType;
userAgent: string;
userAgentData: {
brands: Array<{ brand: string; version: string }>;
mobile: boolean;
platform: string;
getHighEntropyValues: (
hints: string[],
) => Promise<{ [key: string]: unknown }>;
};
script: vm_scriptType;
injectInto: string;
};
type openInNewTabOptionsType = {
active?: boolean;
container?: number;
insert?: boolean;
pinned?: boolean;
};
type openInNewTabReturnType = {
onclose: () => void;
closed: boolean;
close: () => void;
};
declare namespace GM {
function addStyle(css: string): void;
function addElement(
tagName: string,
attributes?: { [key: string]: unknown },
): HTMLElement;
function addElement(
parent: Node | Element | ShadowRoot,
tagName: string,
attributes?: { [key: string]: unknown },
): HTMLElement;
function registerMenuCommand(
caption: string,
func: (event: MouseEvent | KeyboardEvent) => void,
options?: { id?: string; title?: string; autoClose?: boolean },
): string;
function deleteValue(key: string): Promise<void>;
function deleteValues(keys: string[]): Promise<void>;
function download(options: {
url: string;
name: string;
headers?: object;
timeout?: number;
context?: unknown;
user?: string;
password?: string;
anonymous?: boolean;
onabort?: () => void;
onerror?: () => void;
onload?: () => void;
onloadend?: () => void;
onloadstart?: () => void;
onprogress?: () => void;
onreadystatechange?: () => void;
ontimeout?: () => void;
}): Promise<void>;
function download(url: string, name: string): Promise<void>;
function getResourceUrl(name: string, isBlobUrl?: boolean): Promise<string>;
function getValue<T>(
key: string,
defaultValue?: string | number | boolean,
): Promise<T>;
function getValue(
key: string,
defaultValue?: string | number | boolean,
): Promise<string>;
function getValues<T>(keys: string[]): Promise<{ [key: string]: T }>;
function getValues<T>(obj: {
[key: string]: T;
}): Promise<{ [key: string]: T }>;
const info: vm_infoType;
function listValues(): Promise<string[]>;
function notification(options: {
text: string;
title?: string;
image?: string;
silent?: boolean; // false
tag?: string;
zombieTimeout?: number;
zombieUrl?: string;
onclick?: () => void;
ondone?: () => void;
}): Promise<() => void>;
function notification(
text: string,
title?: string,
image?: string,
onclick?: () => void,
): promise<() => void>;
function openInTab(
url: string,
options?: {
active?: boolean; // true
container?: number; // 0 = default (main) container 1, 2, etc. = internal container index
insert?: boolean; // true
pinned?: boolean; // false
},
): {
onclose?: () => void;
closed: boolean;
close: () => void;
};
function openInTab(
url: string,
openInBackground: boolean,
): {
onclose?: () => void;
closed: boolean;
close: () => void;
};
function setClipboard(data: string, type: string): void;
function setValue(key: string, value: unknown): Promise<void>;
function setValues(obj: { [key: string]: unknown }): Promise<void>;
// function xmlHttpRequest<T>(details: vm_detailType<T>): { abort: () => void };
function xmlHttpRequest<T>(
details: vm_detailType<T>,
): Promise<vm_responseObjectType<T>>;
}
function GM_getValue<t>(key: string, defaultValue?: t): t;
function GM_getValue<t>(
key: string,
defaultValue?: string | number | boolean,
): t;
function GM_getValue(
key: string,
defaultValue?: string | number | boolean,
): string;
function GM_setValue<t>(key: string, value: t): void;
function GM_setValue(key: string, value: string | number | boolean): void;
function GM_deleteValue(key: string): void;
function GM_listValues(): string[];
function GM_addValueChangeListener(
name,
callback: (name, oldValue, newValue, remote) => void,
): string;
function GM_removeValueChangeListener(listenerId: string): void;
function GM_getResourceText(name: string): string;
function GM_getResourceURL(name: string): string;
function GM_getResourceURL(name: string, isBlobUrl: boolean): string;
function GM_addElement(
tagName: string,
attributes?: { [key: string]: string },
): HTMLElement;
function GM_addElement(
parentNode: Node | Element | ShadowRoot,
tagName: string,
attributes?: { [key: string]: string },
): HTMLElement;
function GM_addStyle(css: string): HTMLStyleElement;
function GM_openInTab(
url: string,
options?: openInNewTabOptionsType,
): openInNewTabReturnType;
function GM_openInTab(
url: string,
openInBackground?: boolean,
): openInNewTabReturnType;
function GM_registerMenuCommand(
caption: string,
onClick: (e: MouseEvent | KeyboardEvent) => void,
): void;
function GM_unregisterMenuCommand(caption: string): void;
function GM_notification(option: vm_optionType): vm_notificationReturnType;
function GM_notification(
text: string,
title?: string,
image?: string,
onclick?: () => void,
): vm_notificationReturnType;
function GM_setClipboard(data: string, type: string): void;
function GM_xmlhttpRequest(
request: vm_xmlhttpRequestType,
): Promise<vm_xmlhttpRequestReturnType>;
function GM_download(url: string, name?: string): void;
function GM_download(options: vm_downloadOptionsType): void;

View file

@ -0,0 +1,168 @@
/* overrides */
body {
border-color: currentcolor;
background: rgba(0, 0, 0, 0) url("/images/icons/black_thread.webp") !important;
border-top: 0px !important;
}
body > div,
body > header,
body > footer {
isolation: isolate;
}
body::before {
inset: 0;
content: "";
position: fixed;
z-index: 0; /* Ensure this is below the main content */
pointer-events: none;
backdrop-filter: contrast(1.2) brightness(0.75);
}
body .top-logo img,
body .logo_chapter img {
filter: invert() hue-rotate(180deg) brightness(0.75);
}
body > div::last-of-type {
padding-bottom: 120px;
}
/* overrides chapter */
.container-chapter-reader {
margin-bottom: 60px;
}
.container-chapter-reader > img {
min-width: 630px;
}
.overrides-header-container,
.overrides-footer-container {
background-color: rgba(0, 0, 0, 0.5);
padding-bottom: 60px;
}
.logo_chapter {
background: none;
}
.overrides-header-container > div,
.overrides-footer-container > div {
background: none;
}
/* overrides manga */
body > div.body-site {
padding-bottom: 120px;
}
div#panel-story-info-description {
max-height: none !important;
}
div#panel-description-linear,
span#panel-story-info-description-show-more,
span#panel-story-info-description-show-less {
display: none !important;
}
/* overrides neither chapter nor manga */
div.truyen-list > div.list-truyen-item-wrap > .genres-item-info {
display: flex;
flex-flow: column nowrap;
gap: 4px;
box-sizing: border-box;
padding: 4px;
/*background-color: rgb(24, 26, 27, 0.4);*/
float: none;
flex-grow: 1;
}
div.truyen-list > div.breadcrumb {
grid-column: span 2;
}
div.truyen-list {
display: grid;
grid-gap: 12px;
grid-template-columns: calc(50% - 6px) calc(50% - 6px);
}
div.truyen-list > div.list-truyen-item-wrap * {
float: unset !important;
}
div.truyen-list > div.list-truyen-item-wrap {
backdrop-filter: blur(3px);
margin: 0;
width: auto;
background-color: rgb(24, 26, 27, 0.2);
background: none;
transition: background-color linear 0.15s;
padding: 4px;
display: flex;
gap: 4px;
}
div.truyen-list > div.list-truyen-item-wrap.mr-genre-all-active-item {
background-color: rgb(4, 126, 123, 0.3);
transition: background-color linear 0s;
}
div.truyen-list > div.list-truyen-item-wrap > .genres-item-img {
margin: 0;
}
div.truyen-list > div.list-truyen-item-wrap {
display: grid;
grid-template:
"img title" auto
"img link" auto
"img description" 1fr / 123px 1fr;
}
div.truyen-list > div.list-truyen-item-wrap > .list-story-item {
grid-area: img;
}
div.truyen-list > div.list-truyen-item-wrap > h3 {
grid-area: title;
}
div.truyen-list > div.list-truyen-item-wrap > a.list-story-item-wrap-chapter {
grid-area: link;
}
div.truyen-list > div.list-truyen-item-wrap > p {
grid-area: description;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
height: fit-content;
}
div.truyen-list
> div.list-truyen-item-wrap
> .genres-item-info
> .genres-item-chap,
div.truyen-list
> div.list-truyen-item-wrap
> .genres-item-info
> .genres-item-view-time,
div.truyen-list > div.list-truyen-item-wrap > .genres-item-info > h3 {
min-height: 22px;
height: 22px;
}
div.truyen-list
> div.list-truyen-item-wrap
> .genres-item-info
> .genres-item-description {
height: auto;
flex-grow: 1;
}
div.truyen-list > div.list-truyen-item-wrap > div,
div.truyen-list > div.list-truyen-item-wrap > br,
div.truyen-list > div.list-truyen-item-wrap > a.read-more {
display: none;
}

View file

@ -0,0 +1,6 @@
@import "tailwindcss";
@source "templates/manga-reading-config.html";
@source "templates/manga-reading-notification-button.html";
@source "templates/manga-reading-notification-container.html";
@source "templates/manga-reading-notification.html";
@source "templates/manga-reading-lib-frame.html";

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,73 @@
<div class="flex flex-col gap-2" data-mr-config="">
<p class="border-b border-solid border-b-indigo-400/50 text-sm mt-2">
Current Title:
<span data-mr-title-current=""></span>
</p>
<div
class="flex flex-col gap-0.5 border-b border-solid border-b-indigo-400/50 pb-1 text-sm"
>
<label for="ptApiUrl"> Progress Tracker Api Url: </label>
<input
type="text"
class="border-none bg-transparent pb-2 font-mono text-xs outline-4 outline-transparent focus:outline-indigo-500/80"
id="ptApiUrl"
data-mr-pt-api-url=""
/>
</div>
<div
class="flex flex-col gap-0.5 border-b border-solid border-b-indigo-400/50 pb-1 text-sm"
>
<label for="ptApiBearerToken"> Progress Tracker Api BEARER TOKEN: </label>
<input
type="text"
class="border-none bg-transparent pb-2 font-mono text-xs outline-4 outline-transparent focus:outline-indigo-500/80"
id="ptApiBearerToken"
data-mr-pt-api-bearer-token=""
/>
</div>
<div
class="flex flex-col gap-0.5 border-b border-solid border-b-indigo-400/50 pb-1 text-sm"
>
<label for="titleList"> Titles to remove gaps from: </label>
<textarea
class="min-h-24 resize-y border-none bg-transparent pb-2 font-mono text-xs outline-4 outline-transparent focus:outline-indigo-500/80"
id="titleList"
rows="12"
data-mr-title-list=""
></textarea>
</div>
<div
class="flex justify-between border-b border-solid border-b-indigo-400/50 pb-1"
>
<div class="flex justify-end gap-2">
<button
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
disabled=""
data-mr-title-current-add=""
>
Add
</button>
<button
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
disabled=""
data-mr-title-current-remove=""
>
Remove
</button>
</div>
<div class="flex justify-end gap-2">
<button
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
data-mr-title-list-reset=""
>
Reset
</button>
<button
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
data-mr-title-list-save=""
>
Save
</button>
</div>
</div>
</div>

View file

@ -0,0 +1,38 @@
<div
class="mr-lib-frame fixed inset-0 isolate z-[999999] flex items-center justify-center p-4 font-sans"
>
<div
class="absolute inset-0 -z-10 overscroll-none bg-black/5 backdrop-blur-sm"
data-mr-backdrop=""
></div>
<div
class="z-10 flex max-h-[90dvh] w-full max-w-prose flex-col bg-gray-950/90 p-2 text-gray-200 shadow-gray-950 backdrop-blur-sm"
>
<header class="border-b border-solid border-b-indigo-400/50">
<h1 class="text-base">Manga Reading - Configuration UI</h1>
</header>
<main class="flex flex-col gap-2 overflow-y-auto">
<div class="flex flex-col gap-2" data-mr-sub-config-target=""></div>
</main>
<footer class="flex items-end justify-between pt-1">
<a
class="text-xs text-indigo-400 hover:text-indigo-300"
href="https://userscripts.skaarup.dev"
target="_blank"
referrerpolicy="no-referrer"
>
userscripts.skaarup.dev
</a>
<div class="mr-buttons-container nws">
<div class="flex justify-end gap-2">
<button
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
data-mr-close=""
>
Close
</button>
</div>
</div>
</footer>
</div>
</div>

View file

@ -0,0 +1,4 @@
<button
data-button=""
class="flex h-6 shrink-0 cursor-pointer items-center rounded-sm border-none bg-indigo-700 px-2 text-sm text-gray-100 outline-none transition-[box-shadow] duration-400 hover:bg-indigo-500 focus-visible:shadow-sm disabled:cursor-not-allowed"
></button>

View file

@ -0,0 +1,5 @@
<ol
tabindex="-1"
class="pointer-events-none fixed right-[var(--offset)] bottom-[var(--offset)] z-[var(--z-index)] m-0 box-border flex w-80 list-none flex-col gap-2 p-0 font-sans outline-none"
data-mr-notification-container=""
></ol>

View file

@ -0,0 +1,12 @@
<li
aria-live="polite"
aria-atomic="true"
role="status"
tabindex="0"
class="pointer-events-auto box-border flex w-80 touch-none items-center gap-1.5 text-wrap rounded-sm border border-solid border-gray-100 bg-black p-4 shadow outline-none transition-[box-shadow] duration-400 focus-visible:shadow-sm"
>
<div data-content="" class="flex flex-col gap-0.5">
<div data-title="" class="font-medium leading-normal text-gray-100"></div>
<div data-description="" class="font-normal leading-snug text-gray-300"></div>
</div>
</li>