first commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
data/
|
||||
*.db
|
||||
.DS_Store
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
const DISCORD_ID = '298221448642953217';
|
||||
const SITE_TITLE = '0xa0';
|
||||
|
||||
define('DATA_DIR', getenv('DATA_DIR') ?: '/var/www/data');
|
||||
@@ -0,0 +1,86 @@
|
||||
:root {
|
||||
--bg: #0d0d0d;
|
||||
--fg: #e8e8e8;
|
||||
--muted: #888;
|
||||
--line: #262626;
|
||||
--field: #161616;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin-inline: auto;
|
||||
max-width: 680px;
|
||||
padding: 2rem 1.25rem;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font: 16px/1.6 ui-monospace, "JetBrains Mono", Menlo, monospace;
|
||||
}
|
||||
a { color: var(--fg); text-decoration: none; border-bottom: 1px solid var(--muted); }
|
||||
a:hover { border-color: var(--fg); }
|
||||
|
||||
header nav {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
h1 { font-size: 1.6rem; }
|
||||
h2 { font-size: 1.1rem; color: var(--muted); font-weight: normal; }
|
||||
footer {
|
||||
margin-top: 3rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: .6em; height: .6em;
|
||||
border-radius: 50%;
|
||||
background: var(--muted);
|
||||
vertical-align: middle;
|
||||
}
|
||||
.dot-online { background: #3ba55d; }
|
||||
.dot-idle { background: #faa61a; }
|
||||
.dot-dnd { background: #ed4245; }
|
||||
.dot-offline { background: #747f8d; }
|
||||
|
||||
.status-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .8rem;
|
||||
padding: .8rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
.status-card .avatar { border-radius: 8px; flex: none; }
|
||||
.status-card .who { display: flex; flex-direction: column; gap: .15rem; }
|
||||
.status-card .name { font-weight: bold; }
|
||||
.status-card .state { color: var(--muted); font-size: .9rem; }
|
||||
.now-playing { color: var(--muted); font-size: .9rem; }
|
||||
|
||||
form { display: flex; flex-direction: column; gap: .5rem; margin: 1.5rem 0; }
|
||||
input, textarea {
|
||||
background: var(--field);
|
||||
border: 1px solid var(--line);
|
||||
color: var(--fg);
|
||||
padding: .6rem;
|
||||
font: inherit;
|
||||
border-radius: 4px;
|
||||
}
|
||||
textarea { min-height: 5rem; resize: vertical; }
|
||||
button {
|
||||
align-self: flex-start;
|
||||
background: var(--fg);
|
||||
color: var(--bg);
|
||||
border: 0;
|
||||
padding: .5rem 1.2rem;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.entry { border-top: 1px solid var(--line); padding: .8rem 0; }
|
||||
.entry .date { color: var(--muted); font-size: .8rem; margin-left: .5rem; }
|
||||
.entry p { margin: .3rem 0 0; }
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
@mkdir(DATA_DIR, 0755, true);
|
||||
$db = new PDO('sqlite:' . DATA_DIR . '/guestbook.db');
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$db->exec('CREATE TABLE IF NOT EXISTS entries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
msg TEXT NOT NULL,
|
||||
ts INTEGER NOT NULL
|
||||
)');
|
||||
|
||||
$isHtmx = isset($_SERVER['HTTP_HX_REQUEST']);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$msg = trim($_POST['msg'] ?? '');
|
||||
if ($name !== '' && $msg !== '') {
|
||||
$stmt = $db->prepare('INSERT INTO entries (name, msg, ts) VALUES (?, ?, ?)');
|
||||
$stmt->execute([mb_substr($name, 0, 40), mb_substr($msg, 0, 500), time()]);
|
||||
}
|
||||
if (!$isHtmx) {
|
||||
header('Location: /guestbook.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$rows = $db->query('SELECT name, msg, ts FROM entries ORDER BY id DESC LIMIT 50');
|
||||
|
||||
function render_entries(iterable $rows): void {
|
||||
foreach ($rows as $r) { ?>
|
||||
<div class="entry">
|
||||
<b><?= htmlspecialchars($r['name']) ?></b>
|
||||
<span class="date"><?= date('Y-m-d', $r['ts']) ?></span>
|
||||
<p><?= nl2br(htmlspecialchars($r['msg'])) ?></p>
|
||||
</div>
|
||||
<?php }
|
||||
}
|
||||
|
||||
if ($isHtmx) {
|
||||
render_entries($rows);
|
||||
exit;
|
||||
}
|
||||
|
||||
require __DIR__ . '/inc/header.php';
|
||||
?>
|
||||
<h1>guestbook</h1>
|
||||
|
||||
<form hx-post="/guestbook.php" hx-target="#entries" hx-swap="innerHTML"
|
||||
method="post" action="/guestbook.php">
|
||||
<input name="name" placeholder="name" maxlength="40" required>
|
||||
<textarea name="msg" placeholder="say something…" maxlength="500" required></textarea>
|
||||
<button type="submit">sign</button>
|
||||
</form>
|
||||
|
||||
<div id="entries">
|
||||
<?php render_entries($rows); ?>
|
||||
</div>
|
||||
|
||||
<?php require __DIR__ . '/inc/footer.php'; ?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php $build = trim((string) @file_get_contents(__DIR__ . '/../build.txt')) ?: 'dev'; ?>
|
||||
</main>
|
||||
<footer>
|
||||
<small>© <?= date('Y') ?> · <a href="https://git.0xa0.dev">source</a> · build: <?= htmlspecialchars($build) ?></small>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php require_once __DIR__ . '/../config.php'; ?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= htmlspecialchars(SITE_TITLE) ?></title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<script src="/js/htmx.min.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/">home</a>
|
||||
<a href="/guestbook.php">guestbook</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php require __DIR__ . '/inc/header.php'; ?>
|
||||
|
||||
<h1>hey, I'm 0x</h1>
|
||||
<p>welcome to my corner of the web. no framework, no build step — just PHP printing HTML.</p>
|
||||
|
||||
<section class="status-widget">
|
||||
<h2>status</h2>
|
||||
<div id="status" hx-get="/status.php" hx-trigger="every 5s" hx-swap="innerHTML">
|
||||
<?php include __DIR__ . '/status.php'; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require __DIR__ . '/inc/footer.php'; ?>
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
$cacheFile = DATA_DIR . '/lanyard.json';
|
||||
$ttl = 4;
|
||||
|
||||
if (!is_file($cacheFile) || time() - filemtime($cacheFile) > $ttl) {
|
||||
$ch = curl_init('https://api.lanyard.rest/v1/users/' . DISCORD_ID);
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 4]);
|
||||
$res = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
if ($res) {
|
||||
@mkdir(DATA_DIR, 0755, true);
|
||||
file_put_contents($cacheFile, $res);
|
||||
}
|
||||
}
|
||||
|
||||
$data = json_decode(@file_get_contents($cacheFile), true)['data'] ?? null;
|
||||
$status = $data['discord_status'] ?? 'offline';
|
||||
$spotify = ($data['listening_to_spotify'] ?? false) ? $data['spotify'] : null;
|
||||
|
||||
$user = $data['discord_user'] ?? [];
|
||||
$did = $user['id'] ?? DISCORD_ID;
|
||||
$name = $user['global_name'] ?? $user['username'] ?? '';
|
||||
|
||||
$avatarHash = $user['avatar'] ?? null;
|
||||
$avatar = $avatarHash
|
||||
? "https://cdn.discordapp.com/avatars/$did/$avatarHash." . (str_starts_with($avatarHash, 'a_') ? 'gif' : 'png') . '?size=128'
|
||||
: 'https://cdn.discordapp.com/embed/avatars/0.png';
|
||||
|
||||
$bannerHash = $user['banner'] ?? null;
|
||||
$banner = $bannerHash
|
||||
? "https://cdn.discordapp.com/banners/$did/$bannerHash." . (str_starts_with($bannerHash, 'a_') ? 'gif' : 'png') . '?size=480'
|
||||
: null;
|
||||
?>
|
||||
<div class="status-card"<?= $banner ? ' style="background-image:url(\'' . htmlspecialchars($banner) . '\')"' : '' ?>>
|
||||
<img class="avatar" src="<?= htmlspecialchars($avatar) ?>" alt="" width="56" height="56" loading="lazy">
|
||||
<div class="who">
|
||||
<span class="name"><?= htmlspecialchars($name) ?></span>
|
||||
<span class="state"><span class="dot dot-<?= htmlspecialchars($status) ?>"></span> <?= htmlspecialchars($status) ?></span>
|
||||
<?php if ($spotify): ?>
|
||||
<span class="now-playing">♪ <?= htmlspecialchars($spotify['song']) ?> — <?= htmlspecialchars($spotify['artist']) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user