new styles
This commit is contained in:
parent
ef673b2074
commit
de9aa6abf9
@ -28,3 +28,4 @@ services:
|
||||
networks:
|
||||
postgres-network:
|
||||
driver: bridge
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 89 KiB |
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
BIN
public/new-banner.webp
Normal file
BIN
public/new-banner.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 174 KiB |
1137
public/new-styles.css
Normal file
1137
public/new-styles.css
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,69 +0,0 @@
|
||||
### Hi, I'm Yuzu, I am one of many developers who intend to write Discord bots and other bloatware that uses proprietary API's, I do it because I love Discord as a platform, and it'd be a shame to let this amazing community rot. I write code and I dislike wasting time, especially when it comes to the three B's, broken, bloated and badly-operated, this is most likely a rant than anything; however, you must keep up with this article if you want to choose your tech stack to a clean and maintainable endeavour.
|
||||
|
||||
---
|
||||
## Discord.js: the evil
|
||||
First, you must understand what the Latin word *malum* (noun) means, this word, appears innocent, but deeply in its roots, it holds a secret semantic depth that perhaps you are not aware of, if you are a native English speaker, which most likely, if you're reading my blog, you aren't.
|
||||
The word *malum*, in any romance language, means *bad* and is a synonym of *evil*. Now you must understand what real Evil is, so I'll be writing Evil from now on capitalised.
|
||||
Evil is not something wicked, Evil is something not pleasing to the eyes, something anti-æsthetic. For it is not limited to moral depravity.
|
||||
To call something *malum* in the classical sense is not merely to virtue-signal somebody, but to diagnose it as malformed--a deviation from order, reason, and beauty--the ancient Roman conception of malum inherently included the idea of functional and aesthetic failure. Something could be malum because it was ugly, jarring, inelegant, or brutish in how it was constructed or behaved. This understanding aligns far more closely with the way devs talk about code smell, bad ux, bad libraries. It is akin of calling someone who writes all day Excel documents or works in SAP a wagie, or if he's old, a dinosaur.
|
||||
Evil, then, is not always malevolence. Sometimes evil is simply negligence. Sometimes it's sloppiness. Sometimes it is the arrogance of assuming you're smarter than the end user, which in this case, is you.
|
||||
|
||||
### 1. `broadcastEval()` is a nuclear footgun
|
||||
If you’ve ever looked into [sharding in Discord.js](https://discordjs.guide/sharding/#broadcasteval), you know about `broadcastEval()`. What they don't tell you is that (dramatic pause) **this thing runs `eval()` on your worker threads**. Yes, **actual JavaScript `eval()`** over IPC. One wrong move and you're executing arbitrary code across your cluster. It’s not clever. It’s dangerous.
|
||||
This facilitates exploits in your logic, unsanitised inputs might break havoc, they might crash your bot on a syntax error if one of these fools whom we call "maintainers" inserts a backdoor in your codebase, whether accidental or malicious. These "malicious" actors are something I'll discuss later on.
|
||||
On a different note, what you **actually** want is a Redis cache or any kind of stateless, independent service that can share data across shards without turning your whole app into a Fallout-level hazard. BroadcastEval is stupid, do avoid, and please write a dockercompose for your cache service and your bot so you can deploy them altogether.
|
||||
|
||||
### 2. The hostility of the bad Actors
|
||||
You think the code is bad? Wait until you interact with the maintainers. Claim your library is better and they'll be in your Github issues forms [threatening legal action.](https://github.com/tiramisulabs/seyfert/issues/174#issue-2226994786) They’ve got an ego complex and take any form of comparison as slander. It's open source, not a cult. Grow up.
|
||||
|
||||
### 3. 15MB of Bloatware
|
||||
Discord.js is like downloading an entire operating system just to say "hi" to a user. 15MB bundle size, over **25+ nested npm packages, huge node_modules folder**, and yet it *removes* basic methods like `isButton()` or `isChatInputCommand()` on a whim. Then after backlash, they *add it back*. Who's managing this, children?
|
||||
|
||||
### 4. It Hates Frameworks
|
||||
Discord.js has no support for decorators, adapters, or any semblance of framework facilities (even if the word might be misused herein) And it never will. TypeScript adoption came late. Updates rarely. Cutting-edge features? Forget ab' it. You’re stuck in 2020 tech--even calling it 2020 is disrespectful, because back then we were better off--dealing with breakage every time Discord updates its API, which is often because Discord is stupid. Sapphire is so dumb that I will not waste any braincells witht them.
|
||||
|
||||
### 5. Semver is a Lie
|
||||
It’s supposed to help, but when you’re building on a proprietary API, but it's a breaking-change mess. And for a library scared of supporting selfbots, you’re also left with the MIT license, which means anyone can fork your bot and make it closed source. That’s not freedom. That’s a liability.
|
||||
|
||||
---
|
||||
|
||||
## Eris
|
||||
|
||||
### 1. Eris is Dead
|
||||
Long live Eris. Or not. It was deprecated and rebranded as Oceanic, which is very ridiculous, perhaps they copied my framework--which was called similarly back then--which is somehow even worse, subnautic? romantic?, more like gigantic, they switched to TypeScript, which is also a bloated piece of garbage.
|
||||
|
||||
### 2. Oceanic Devs Are Petty
|
||||
Let’s be real: the Oceanic maintainer has a vendetta. Against me, against others, who knows. What I do know is they’re needlessly antagonistic, and they docs drama like teenage girls, and don't get me wrong, at least teenage girls act their own age and are not trying to dress-up as anthropomorphic creatures to lure/induct children to their brainwash.
|
||||
|
||||
### 3. No Cluster Support
|
||||
To its credit, Oceanic is more lightweight than Discord.js. But no cluster support? In 2025? That’s laughable. You’re writing your own shard manager or hoping your single threaded JS won’t ignite himself. Good luck.
|
||||
|
||||
---
|
||||
|
||||
## Libraries That Are Actually Worth Your Time
|
||||
|
||||
### 1. `discord.py-self`
|
||||
Python-based. Supports selfbots. Maintained. Featureful. It’s hefty, but you’re getting something solid. Plus, the maintainer isn’t toxic—at least not publicly. The only downside of this library is that it is usually a trap so you get your token and cookies grabbed, so don't fork or download anybody's selfbot, or your account will start spamming Steam-looking links.
|
||||
|
||||
### 2. `Seyfert` (My Baby)
|
||||
Yeah, I’m plugging my former library. [It’s clean, it’s efficient](https://github.com/tiramisulabs/seyfert/commit/cac85ae0635155fe2e5024d49e6ea021b94c8316), and it doesn’t try to be everything. If you’re not using it, you’re wasting cycles and brain cells, the only downside of this thing is that it uses TypeScript and used to depend on API-types of Discord.js, hence why the legal action. But the Seyfert team has moved pass that.
|
||||
|
||||
### 3. `discord.zig` (Also Mine)
|
||||
Zig is fun. It’s fast. And this library reflects that. If you like the idea of no runtime and no BS, give it a go, or find a language that actually pays off your bills instead of Zig.
|
||||
|
||||
### 4. `Discordeno`
|
||||
This one’s built with care. The maintainer is the kind of guy who builds things because he believes in them—family man, grounded, and technically sharp. Discordeno supports cluster sharding, HTTP/WebSocket proxies, and it’s built for scale. Zero dependencies. Small footprint. Respect.
|
||||
This is also the library Seyfert was originally based off of and I was once a contributor to it. *Probot* runs on this thing.
|
||||
|
||||
### 5. Anything That Isn’t Run by Degenerates
|
||||
Honestly, if a library is updated regularly (once a month is fine) and isn't made by someone trying to run a para-social dungeon, give it a shot. And if the devs are sane, support them--financially if you can. Don’t let quality open source rot.
|
||||
|
||||
---
|
||||
|
||||
## Final Word
|
||||
In contrast, the Greeks spoke of *kalon*, that which is good, beautiful, and true, authentic.
|
||||
We don’t call Discord.js bad to insult, but to warn. It has lost the shape of what it was meant to be. And rather than reform, it clings to status, mate, just chill the hell out.
|
||||
Let the code speak for itself.
|
||||
|
||||
---
|
||||
|
BIN
public/zmpl.png
BIN
public/zmpl.png
Binary file not shown.
Before Width: | Height: | Size: 8.1 KiB |
20
src/app/views/about-me.zig
Normal file
20
src/app/views/about-me.zig
Normal file
@ -0,0 +1,20 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub const layout = "panel";
|
||||
|
||||
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
const cookies = try request.cookies();
|
||||
const allowed = blk: {
|
||||
const session = cookies.get("session") orelse break :blk false;
|
||||
|
||||
const session_query = jetzig.database.Query(.Session)
|
||||
.findBy(.{ .session_id = session.value });
|
||||
_ = request.repo.execute(session_query) catch break :blk false;
|
||||
break :blk true;
|
||||
};
|
||||
|
||||
const root = try data.object();
|
||||
try root.put("allowed", allowed);
|
||||
return request.render(.ok);
|
||||
}
|
43
src/app/views/about-me/index.zmpl
Normal file
43
src/app/views/about-me/index.zmpl
Normal file
@ -0,0 +1,43 @@
|
||||
<div class="max-w-3xl mx-auto px-4 py-12 font-sans text-win7-text bg-win7-bg rounded-xl shadow border border-win7-border">
|
||||
<h2 class="text-3xl font-bold mb-6 pb-2 border-b border-win7-border">
|
||||
About Me
|
||||
</h2>
|
||||
|
||||
<p class="mb-4 text-lg leading-relaxed">
|
||||
I perform sorcery in the software craftmanship endeavours, I do things, quiet things — mostly in text editors, occasionally in thought (I am able to play Balatro in my brain,) and sometimes in places unseen. This site is my collection unbestowed experiments, side quests, and digital breadcrumbs.
|
||||
</p>
|
||||
|
||||
<p class="mb-4 text-lg leading-relaxed">
|
||||
I do not sleep — I suspend. I do not dream — I garbage collect. My working hours are loosely tied to the gravitational pull of niche programming languages and the smell of old documentation, I talk about documentation like a synesthetic metaphor, I am very into skeuomorphism, so you will watch me build awful UIs.
|
||||
</p>
|
||||
|
||||
<p class="mb-4 text-lg leading-relaxed">
|
||||
You'll find posts here about software, personal projects, systems I’m poking at, and the occasional half-serious exploration into how things break and why that is interesting.
|
||||
</p>
|
||||
|
||||
<p class="text-lg leading-relaxed">
|
||||
If you'd like to challenge me to a duel, discuss obscure software, or just say hello — I'm usually somewhere near Discord, or off refactoring something that didn't ask for it. May your stack traces be short.
|
||||
</p>
|
||||
|
||||
<div class="my-6">
|
||||
<button
|
||||
onclick="copyIRC()"
|
||||
class="bg-win7-link text-white px-4 py-2 rounded-md shadow hover:bg-win7-hover transition"
|
||||
>
|
||||
Meet me here on IRC.
|
||||
</button>
|
||||
<span id="irc-copied" class="ml-3 text-sm text-green-600 hidden">Copied!</span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyIRC() {
|
||||
const ircInfo = "irc://sv.yuzucchii.xyz:6697/#the-graveyard";
|
||||
navigator.clipboard.writeText(ircInfo).then(() => {
|
||||
const msg = document.getElementById("irc-copied");
|
||||
msg.classList.remove("hidden");
|
||||
setTimeout(() => msg.classList.add("hidden"), 2000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
@ -136,6 +136,7 @@ pub fn get(id: []const u8, request: *jetzig.Request) !jetzig.View {
|
||||
};
|
||||
|
||||
try root.put("allowed", allowed);
|
||||
try root.put("description", blog.blob);
|
||||
|
||||
try request.process();
|
||||
return request.render(.ok);
|
||||
|
@ -1,53 +1,55 @@
|
||||
@args allowed: bool
|
||||
<div class="max-w-3xl mx-auto mt-10 space-y-6">
|
||||
@for (.blogs) |blog| {
|
||||
<div class="p-4 border border-gray-200 rounded-xl shadow-sm hover:shadow transition">
|
||||
<div class="flex justify-between items-center">
|
||||
<a href="/blogs/{{blog.id}}" class="text-xl font-semibold text-blue-600 hover:underline">
|
||||
{{blog.title}}
|
||||
</a>
|
||||
|
||||
@if ($.allowed)
|
||||
<button
|
||||
id="delete-post-{{blog.id}}"
|
||||
class="text-sm text-red-600 hover:text-red-800"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
@end
|
||||
</div>
|
||||
<div class="max-w-3xl mx-auto mt-10 space-y-6 font-sans">
|
||||
@for (.blogs) |blog| {
|
||||
<div class="p4 bg-win7-bg border border-win7-border rounded-xl px-5 py-4 shadow-sm hover:shadow-md transition duration-150">
|
||||
<div class="flex justify-between items-center">
|
||||
<a href="/blogs/{{blog.id}}" class="text-xl font-semibold text-win7-link hover:underline transition">
|
||||
{{blog.title}}
|
||||
</a>
|
||||
|
||||
<p class="text-gray-500 text-sm mt-1">
|
||||
{{zmpl.fmt.datetime(blog.get("created_at"), "%Y-%m-%d %H:%M")}}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
@if ($.allowed)
|
||||
<button
|
||||
id="delete-post-{{blog.id}}"
|
||||
class="text-sm text-win7-red hover:text-red-800 transition"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
@end
|
||||
</div>
|
||||
|
||||
@if ($.allowed)
|
||||
<div class="pt-4">
|
||||
<a href="/blogs/new" class="inline-block px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">
|
||||
+ New Blog
|
||||
</a>
|
||||
</div>
|
||||
@end
|
||||
<p class="text-xs text-[#4a5c75] mt-2">
|
||||
{{zmpl.fmt.datetime(blog.get("created_at"), "%Y-%m-%d %H:%M")}}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if ($.allowed)
|
||||
<div class="pt-4">
|
||||
<a href="/blogs/new"
|
||||
class="inline-block px-4 py-2 bg-win7-link text-white rounded-md hover:bg-win7-hover shadow transition duration-150">
|
||||
+ New Blog
|
||||
</a>
|
||||
</div>
|
||||
@end
|
||||
</div>
|
||||
|
||||
@if ($.allowed)
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.querySelectorAll('[id^="delete-post-"]').forEach(button => {
|
||||
button.addEventListener("click", () => {
|
||||
const id = button.id.replace("delete-post-", "");
|
||||
fetch(`/blogs/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
})
|
||||
.then(() => window.location.reload())
|
||||
.catch(err => console.error("Failed to delete post:", err));
|
||||
});
|
||||
document.querySelectorAll('[id^="delete-post-"]').forEach(button => {
|
||||
button.addEventListener("click", () => {
|
||||
const id = button.id.replace("delete-post-", "");
|
||||
fetch(`/blogs/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
})
|
||||
.then(() => window.location.reload())
|
||||
.catch(err => console.error("Failed to delete post:", err));
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@end
|
||||
|
@ -1,23 +1,44 @@
|
||||
<div class="max-w-2xl mx-auto mt-10 p-6 bg-white shadow-md rounded-2xl">
|
||||
<form action="/blogs" method="POST" class="space-y-6">
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-gray-700">Title</label>
|
||||
<input name="title" id="title" class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" />
|
||||
</div>
|
||||
<div class="max-w-2xl mx-auto mt-10 p-6 bg-white border border-win7-border shadow-md rounded-2xl font-sans text-win7-text">
|
||||
<form action="/blogs" method="POST" class="space-y-6">
|
||||
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium mb-1">Title</label>
|
||||
<input
|
||||
name="title"
|
||||
id="title"
|
||||
required
|
||||
class="mt-1 w-full px-4 py-2 bg-white border border-win7-border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-win7-hover focus:border-win7-hover transition"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="content" class="block text-sm font-medium text-gray-700">Content</label>
|
||||
<textarea name="content" id="content" rows="8" class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="content" class="block text-sm font-medium mb-1">Content</label>
|
||||
<textarea
|
||||
name="content"
|
||||
id="content"
|
||||
rows="8"
|
||||
required
|
||||
class="mt-1 w-full px-4 py-2 bg-white border border-win7-border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-win7-hover focus:border-win7-hover transition"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="preview" class="block text-sm font-medium text-gray-700">Preview</label>
|
||||
<textarea name="preview" id="preview" rows="3" class="mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="preview" class="block text-sm font-medium mb-1">Preview</label>
|
||||
<textarea
|
||||
name="preview"
|
||||
id="preview"
|
||||
rows="3"
|
||||
class="mt-1 w-full px-4 py-2 bg-white border border-win7-border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-win7-hover focus:border-win7-hover transition"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="submit" value="Publish" class="px-4 py-2 bg-blue-600 text-white font-semibold rounded-md shadow hover:bg-blue-700 transition-colors" />
|
||||
</div>
|
||||
</form>
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
value="Publish"
|
||||
class="px-4 py-2 bg-win7-link text-white font-semibold rounded-md shadow hover:bg-win7-hover transition"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -7,24 +7,24 @@
|
||||
.{ .href = "https://discord.gg/pvraBkepUP", .title = "Discord" },
|
||||
};
|
||||
}
|
||||
|
||||
<footer class="mt-12 border-t pt-6 text-center text-sm text-gray-600">
|
||||
<div class="max-w-xl mx-auto px-4">
|
||||
|
||||
<div class="bg-yellow-100 text-yellow-800 border border-yellow-200 rounded-md px-4 py-3 mb-6">
|
||||
<p class="text-sm md:text-base">
|
||||
This website is not reliant on JavaScript. All scripts are optional and publicly available.
|
||||
</p>
|
||||
</div>
|
||||
<footer class="mt-12 bg-win7-bg border-t border-win7-border pt-6 text-center text-sm text-win7-text font-sans">
|
||||
<div class="max-w-xl mx-auto px-4">
|
||||
|
||||
<div class="flex justify-center flex-wrap gap-4 mb-4 text-blue-500">
|
||||
@zig {
|
||||
inline for (links) |link| {
|
||||
<a href="{{link.href}}" class="hover:underline">{{link.title}}</a>
|
||||
<div class="bg-warnbox-bg text-warnbox-text border border-warnbox-border rounded-md px-4 py-3 mb-6 shadow-inner">
|
||||
<p class="text-sm md:text-base">
|
||||
This website is not reliant on JavaScript. All scripts are optional and publicly available.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center flex-wrap gap-4 mb-4 text-win7-link">
|
||||
@zig {
|
||||
inline for (links) |link| {
|
||||
<a href="{{link.href}}" class="hover:underline">{{link.title}}</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-gray-500 padding-bottom-5">© 2025 yuzucchii.xyz</p>
|
||||
</div>
|
||||
<p class="text-xs text-[#4a5c75] pb-5">© 2025 yuzucchii.xyz</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
@ -1,34 +1,49 @@
|
||||
@args allowed: bool
|
||||
@args allowed: bool, description: ?[]const u8
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/main.css" />
|
||||
<title>yuzucchii.xyz</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
@if ($.description) |desc|
|
||||
<meta name="description" content="{{desc}}" />
|
||||
@end
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/new-styles.css" />
|
||||
<title>yuzucchii.xyz</title>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="mx-auto max-w-3xl mt-sm">
|
||||
<br/>
|
||||
<h1 class="text-3xl font-bold mb-4">Hello, I like doing things.</h1>
|
||||
<body>
|
||||
<nav class="bg-win7-bg shadow-sm border-b border-win7-border py-4 font-sans">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<div class="text-2xl font-bold text-win7-text mb-2">
|
||||
<a href="/" class="hover:text-win7-hover transition">yuzucchii.xyz</a>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center gap-4 text-sm sm:text-base text-win7-link">
|
||||
<a href="/blogs" class="hover:underline">Blog</a>
|
||||
<a href="/about-me" class="hover:underline">About Me</a>
|
||||
@if ($.allowed)
|
||||
<a href="/logout" class="hover:underline">Logout</a>
|
||||
@else
|
||||
<a href="/login" class="hover:underline">Login</a>
|
||||
@end
|
||||
<a href="https://git.yuzucchii.xyz" class="hover:underline" rel="noopener">Gitea</a>
|
||||
<a href="https://watcharr.yuzucchii.xyz" class="hover:underline">Watcharr</a>
|
||||
<a href="/rss" class="hover:underline">RSS</a>
|
||||
<a href="https://searx.yuzucchii.xyz" class="hover:underline">SearX</a>
|
||||
<a href="/files" class="text-win7-red hover:underline">Files</a>
|
||||
<a href="mailto:me@yuzucchii.xyz" class="hover:underline">E-mail</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="mb-8">
|
||||
<a href="/blogs" class="text-blue-500 hover:underline">Blog</a>
|
||||
@if ($.allowed)
|
||||
<a href="/logout" class="text-blue-500 hover:underline ml-4">Logout</a>
|
||||
@else
|
||||
<a href="/login" class="text-blue-500 hover:underline ml-4">Login</a>
|
||||
@end
|
||||
<a href="https://git.yuzucchii.xyz" class="text-blue-500 hover:underline ml-4" rel="noopener">Gitea</a>
|
||||
<a href="/rss" class="text-blue-500 hover:underline ml-4">RSS feed</a>
|
||||
<a href="https://searx.yuzucchii.xyz" class="text-blue-500 hover:underline ml-4">SearX</a>
|
||||
<a href="/files" class="text-red-500 hover:underline ml-4">Files</a>
|
||||
<a href="mailto:me@yuzucchii.xyz" class="text-blue-500 hover:underline ml-4">E-mail</a>
|
||||
</div>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="max-w-3xl mx-auto px-4 mt-8">
|
||||
<h1 class="text-3xl font-bold mb-6">Hello, I like doing things.</h1>
|
||||
{{zmpl.content}}
|
||||
</main>
|
||||
</main>
|
||||
|
||||
@partial layouts/footer
|
||||
</body>
|
||||
@partial layouts/footer
|
||||
</html>
|
||||
|
||||
|
@ -1,45 +1,59 @@
|
||||
<title>Login panel for admins</title>
|
||||
<body>
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-100">
|
||||
<form id="login-form" class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-sm space-y-6">
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Discrete password</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-black text-white font-semibold py-2 rounded-md hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="min-h-screen flex items-center justify-center bg-win7-bg font-sans text-win7-text">
|
||||
|
||||
<form id="login-form" class="bg-white p-8 border border-win7-border rounded-2xl shadow-xl w-full max-w-sm space-y-6">
|
||||
|
||||
<script>
|
||||
document.querySelector('#login-form').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
const password = document.querySelector('input[name="password"]').value;
|
||||
<h1 class="text-xl font-semibold text-center mb-2 text-win7-link">Admin Login</h1>
|
||||
|
||||
<div>
|
||||
<label for="username" class="block text-sm font-medium mb-1">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
name="username"
|
||||
type="text"
|
||||
required
|
||||
class="w-full px-4 py-2 border border-win7-border rounded-md shadow-sm bg-white focus:outline-none focus:ring-2 focus:ring-win7-hover focus:border-win7-hover transition"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium mb-1">Discrete password</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
class="w-full px-4 py-2 border border-win7-border rounded-md shadow-sm bg-white focus:outline-none focus:ring-2 focus:ring-win7-hover focus:border-win7-hover transition"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-win7-link text-white font-semibold py-2 rounded-md hover:bg-win7-hover shadow transition"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.querySelector('#login-form').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
const password = document.querySelector('input[name="password"]').value;
|
||||
|
||||
fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ password })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
window.location.href = '/blogs';
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Login error:', err);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
fetch('/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ password })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
window.location.href = '/blogs';
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Login error:', err);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
@ -28,14 +28,6 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
|
||||
|
||||
try root.put("allowed", allowed);
|
||||
|
||||
// Set arbitrary response headers as required. `content-type` is automatically assigned for
|
||||
// HTML, JSON responses.
|
||||
//
|
||||
// Static files located in `public/` in the root of your project directory are accessible
|
||||
// from the root path (e.g. `public/jetzig.png`) is available at `/jetzig.png` and the
|
||||
// content type is inferred from the extension using MIME types.
|
||||
try request.response.headers.append("x-example-header", "example header value");
|
||||
|
||||
// Render the response and set the response code.
|
||||
return request.render(.ok);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<section class="max-w-3xl mx-auto px-4 py-8">
|
||||
<!-- Banner -->
|
||||
<div class="mb-8">
|
||||
<img src="/banner.jpg" alt="Banner" class="w-full rounded-xl shadow-md">
|
||||
<img src="/new-banner.webp" alt="Banner" class="w-full rounded-xl shadow-md">
|
||||
</div>
|
||||
|
||||
<!-- Introduction -->
|
||||
|
@ -6,6 +6,7 @@
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-white text-gray-800 antialiased leading-relaxed;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
|
@ -1,22 +1,40 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/app/views/**/*.{zmpl,html,js}", "./src/main.zig"],
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
content: ["./src/app/views/**/*.{zmpl,html,js}", "./src/main.zig"],
|
||||
theme: {
|
||||
extend: {
|
||||
typography: ({ theme }) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
color: theme('colors.gray.800'),
|
||||
a: { color: theme('colors.blue.600') },
|
||||
pre: { backgroundColor: theme('colors.gray.100') },
|
||||
blockquote: {
|
||||
borderLeftColor: theme('colors.yellow.400'),
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
colors: {
|
||||
win7: {
|
||||
bg: '#eaf3ff', // Background base
|
||||
border: '#b5cde4', // Borders (like nav/footer lines)
|
||||
text: '#003c75', // Base text (e.g., headings)
|
||||
link: '#004f9f', // Link color
|
||||
red: '#cc0000', // Accent red (e.g., for "Files")
|
||||
hover: '#005dc1', // Link hover
|
||||
},
|
||||
warnbox: {
|
||||
bg: '#fff8d8', // Background of JS-free box
|
||||
text: '#7a5b00', // Text for warning
|
||||
border: '#f5e8a3', // Border for warning
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['"Segoe UI"', 'Tahoma', 'Geneva', 'Verdana', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
typography: ({ theme }) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
color: theme('colors.gray.800'),
|
||||
a: { color: theme('colors.blue.600') },
|
||||
pre: { backgroundColor: theme('colors.gray.100') },
|
||||
blockquote: {
|
||||
borderLeftColor: theme('colors.yellow.400'),
|
||||
fontStyle: 'italic',
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user