Initial production version
This commit is contained in:
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Fichiers sensibles
|
||||||
|
config.php
|
||||||
|
.env
|
||||||
|
config.local.php
|
||||||
|
*.save
|
||||||
|
# Données runtime
|
||||||
|
data/
|
||||||
|
uploads/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# OS / IDE
|
||||||
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
secrets.*
|
||||||
43
flag.php
Normal file
43
flag.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
session_start();
|
||||||
|
require __DIR__ . "/config.php";
|
||||||
|
|
||||||
|
if (empty($_SESSION["is_admin"])) {
|
||||||
|
http_response_code(403);
|
||||||
|
$allowed = false;
|
||||||
|
} else {
|
||||||
|
$allowed = true;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Flag</title>
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<nav class="topnav">
|
||||||
|
<a href="/">Accueil</a>
|
||||||
|
<a href="/login.php">Login</a>
|
||||||
|
<a class="active" href="/flag.php">Flag</a>
|
||||||
|
<?php if (!empty($_SESSION["is_admin"])): ?>
|
||||||
|
<a href="/logout.php">Logout</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h1>Flag DevWeb</h1>
|
||||||
|
|
||||||
|
<?php if (!$allowed): ?>
|
||||||
|
<div class="alert danger">Accès refusé (admin requis).</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="flagbox"><?php echo htmlspecialchars(DEVWEB_FLAG); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
174
index.php
Normal file
174
index.php
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require __DIR__ . '/config.php';
|
||||||
|
|
||||||
|
$postsFile = __DIR__ . '/data/posts.json';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['csrf'])) {
|
||||||
|
$_SESSION['csrf'] = bin2hex(random_bytes(16));
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_posts($file) {
|
||||||
|
$raw = @file_get_contents($file);
|
||||||
|
$arr = json_decode($raw ?: '[]', true);
|
||||||
|
return is_array($arr) ? $arr : [];
|
||||||
|
}
|
||||||
|
function save_posts($file, $posts) {
|
||||||
|
$tmp = $file . '.tmp';
|
||||||
|
file_put_contents($tmp, json_encode($posts, JSON_PRETTY_PRINT));
|
||||||
|
rename($tmp, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg = '';
|
||||||
|
$err = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
|
||||||
|
$err = "Erreur CSRF.";
|
||||||
|
} else {
|
||||||
|
// Rate limit par IP
|
||||||
|
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||||
|
$lastKey = 'last_post_' . md5($ip);
|
||||||
|
$now = time();
|
||||||
|
$last = $_SESSION[$lastKey] ?? 0;
|
||||||
|
if ($now - $last < RATE_LIMIT_SECONDS) {
|
||||||
|
$err = "Trop rapide : 1 post par minute maximum.";
|
||||||
|
} else {
|
||||||
|
$text = trim($_POST['text'] ?? '');
|
||||||
|
if ($text === '' && empty($_FILES['image']['name'])) {
|
||||||
|
$err = "Il faut un message ou une image.";
|
||||||
|
} else {
|
||||||
|
$imagePath = '';
|
||||||
|
if (!empty($_FILES['image']['name'])) {
|
||||||
|
if ($_FILES['image']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$err = "Erreur upload.";
|
||||||
|
} else if ($_FILES['image']['size'] > MAX_UPLOAD_BYTES) {
|
||||||
|
$err = "Image trop grosse (max ".MAX_UPLOAD_BYTES." bytes).";
|
||||||
|
} else {
|
||||||
|
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||||
|
$mime = $finfo->file($_FILES['image']['tmp_name']);
|
||||||
|
$allowed = [
|
||||||
|
'image/png' => 'png',
|
||||||
|
'image/jpeg' => 'jpg',
|
||||||
|
];
|
||||||
|
if (!isset($allowed[$mime])) {
|
||||||
|
$err = "Format interdit. Autorisé: PNG/JPEG.";
|
||||||
|
} else {
|
||||||
|
$ext = $allowed[$mime];
|
||||||
|
$name = bin2hex(random_bytes(8)) . '.' . $ext;
|
||||||
|
$dest = __DIR__ . '/uploads/' . $name;
|
||||||
|
if (!move_uploaded_file($_FILES['image']['tmp_name'], $dest)) {
|
||||||
|
$err = "Impossible de sauvegarder l'image.";
|
||||||
|
} else {
|
||||||
|
$imagePath = '/uploads/' . $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$err) {
|
||||||
|
$posts = load_posts($postsFile);
|
||||||
|
$posts[] = [
|
||||||
|
'time' => date('c'),
|
||||||
|
'ip' => $ip,
|
||||||
|
'text' => $text,
|
||||||
|
'img' => $imagePath
|
||||||
|
];
|
||||||
|
// plus récents en haut => on affichera en reverse
|
||||||
|
save_posts($postsFile, $posts);
|
||||||
|
$_SESSION[$lastKey] = $now;
|
||||||
|
$msg = "Post publié.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$posts = load_posts($postsFile);
|
||||||
|
$posts = array_reverse($posts); // plus récents en haut
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>DevWeb Forum</title>
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="nav">
|
||||||
|
<div class="brand">
|
||||||
|
<span class="dot"></span>
|
||||||
|
Mini-forum
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="/">Accueil</a>
|
||||||
|
<a href="/login.php">Login</a>
|
||||||
|
<a href="/flag.php">Flag</a>
|
||||||
|
<?php if (!empty($_SESSION['is_admin'])): ?>
|
||||||
|
<a href="/logout.php">Logout</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card header">
|
||||||
|
<h1>Mini-forum</h1>
|
||||||
|
|
||||||
|
<?php if (!empty($msg)): ?>
|
||||||
|
<div class="alert"><?php echo htmlspecialchars($msg); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($err)): ?>
|
||||||
|
<div class="alert danger"><?php echo htmlspecialchars($err); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="csrf" value="<?php echo htmlspecialchars($_SESSION['csrf']); ?>">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="text">Commentaire</label>
|
||||||
|
<textarea id="text" name="text" placeholder="Ton message..."></textarea>
|
||||||
|
<div class="helper">Les posts récents apparaissent en premier.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="file">
|
||||||
|
<label for="image">Image (PNG/JPEG, max <?php echo (int)(MAX_UPLOAD_BYTES/1024/1024); ?> MB)</label>
|
||||||
|
<input id="image" type="file" name="image" accept=".png,.jpg,.jpeg,image/png,image/jpeg">
|
||||||
|
<div class="helper">Taille max côté serveur : <?php echo (int)(MAX_UPLOAD_BYTES/1024/1024); ?> MB.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn primary" type="submit">Publier</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Posts</h2>
|
||||||
|
|
||||||
|
<div class="posts">
|
||||||
|
<?php foreach ($posts as $p): ?>
|
||||||
|
<div class="post">
|
||||||
|
<div class="meta">
|
||||||
|
<span class="badge">📅 <?php echo htmlspecialchars($p['time']); ?></span>
|
||||||
|
<span class="badge">🌐 IP <?php echo htmlspecialchars($p['ip']); ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($p['text'])): ?>
|
||||||
|
<div class="content"><?php echo nl2br(htmlspecialchars($p['text'])); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($p['img'])): ?>
|
||||||
|
<img src="<?php echo htmlspecialchars($p['img']); ?>" alt="Upload">
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">CTF • dev.h.ctf.arrobe.fr</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
60
login.php
Normal file
60
login.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
require __DIR__ . "/config.php";
|
||||||
|
|
||||||
|
$err = "";
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
$user = isset($_POST["user"]) ? trim((string)$_POST["user"]) : "";
|
||||||
|
$pass = isset($_POST["pass"]) ? (string)$_POST["pass"] : "";
|
||||||
|
|
||||||
|
if ($user === "" || $pass === "") {
|
||||||
|
$err = "Merci de renseigner l'identifiant et le mot de passe.";
|
||||||
|
} else {
|
||||||
|
if (hash_equals(ADMIN_USER, $user) && hash_equals(ADMIN_PASS, $pass)) {
|
||||||
|
$_SESSION["is_admin"] = true;
|
||||||
|
$_SESSION["admin_user"] = $user;
|
||||||
|
session_regenerate_id(true);
|
||||||
|
header("Location: /flag.php");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$err = "Identifiants invalides.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Login</title>
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container center">
|
||||||
|
<div class="card auth-card">
|
||||||
|
<div class="kicker">🔐 Connexion admin</div>
|
||||||
|
<h1>Login</h1>
|
||||||
|
|
||||||
|
<?php if ($err !== ""): ?>
|
||||||
|
<div class="alert danger"><?php echo htmlspecialchars($err); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="post" action="/login.php">
|
||||||
|
<label for="user">Identifiant</label>
|
||||||
|
<input id="user" type="text" name="user" autocomplete="username" required>
|
||||||
|
|
||||||
|
<label for="pass">Mot de passe</label>
|
||||||
|
<input id="pass" type="password" name="pass" autocomplete="current-password" required>
|
||||||
|
|
||||||
|
<button class="btn primary" type="submit">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="footer"><a href="/">← Retour accueil</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
logout.php
Normal file
7
logout.php
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
$_SESSION = [];
|
||||||
|
session_destroy();
|
||||||
|
header('Location: /');
|
||||||
|
exit;
|
||||||
|
|
||||||
599
style.css
Normal file
599
style.css
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
/* ===== Mini-forum CSS (clean, responsive) ===== */
|
||||||
|
:root{
|
||||||
|
--bg: #0b1220;
|
||||||
|
--card: rgba(255,255,255,.06);
|
||||||
|
--card2: rgba(255,255,255,.08);
|
||||||
|
--text: #e8eefc;
|
||||||
|
--muted: rgba(232,238,252,.75);
|
||||||
|
--border: rgba(255,255,255,.12);
|
||||||
|
--shadow: 0 18px 55px rgba(0,0,0,.45);
|
||||||
|
--primary: #7aa2ff;
|
||||||
|
--primary2: #5eead4;
|
||||||
|
--danger: #ff5a7a;
|
||||||
|
--success: #22c55e;
|
||||||
|
--radius: 16px;
|
||||||
|
--radius-sm: 12px;
|
||||||
|
--ring: 0 0 0 4px rgba(122,162,255,.20);
|
||||||
|
--font: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
|
||||||
|
}
|
||||||
|
|
||||||
|
*{ box-sizing: border-box; }
|
||||||
|
html,body{ height: 100%; }
|
||||||
|
body{
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font);
|
||||||
|
color: var(--text);
|
||||||
|
background:
|
||||||
|
radial-gradient(1200px 600px at 10% 10%, rgba(122,162,255,.18), transparent 60%),
|
||||||
|
radial-gradient(900px 500px at 90% 0%, rgba(94,234,212,.14), transparent 55%),
|
||||||
|
radial-gradient(900px 700px at 50% 100%, rgba(255,90,122,.10), transparent 60%),
|
||||||
|
linear-gradient(180deg, #070b14, var(--bg));
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* container */
|
||||||
|
.container{
|
||||||
|
width: min(980px, calc(100% - 32px));
|
||||||
|
margin: 28px auto 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* top nav */
|
||||||
|
.nav{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: rgba(255,255,255,.05);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 999px;
|
||||||
|
box-shadow: 0 8px 30px rgba(0,0,0,.25);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand .dot{
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(135deg, var(--primary), var(--primary2));
|
||||||
|
box-shadow: 0 0 0 4px rgba(122,162,255,.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav a{
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
font-weight: 600;
|
||||||
|
opacity: .92;
|
||||||
|
transition: all .15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav a:hover{
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
border-color: rgba(255,255,255,.12);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* page title */
|
||||||
|
h1,h2,h3{
|
||||||
|
margin: 18px 0 10px;
|
||||||
|
line-height: 1.15;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1{
|
||||||
|
font-size: clamp(28px, 3vw, 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2{
|
||||||
|
font-size: clamp(20px, 2.3vw, 28px);
|
||||||
|
margin-top: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cards */
|
||||||
|
.card{
|
||||||
|
margin-top: 18px;
|
||||||
|
padding: 18px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.header{
|
||||||
|
padding: 22px;
|
||||||
|
background: linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
.card + .card{ margin-top: 14px; }
|
||||||
|
|
||||||
|
/* alerts */
|
||||||
|
.alert{
|
||||||
|
margin: 14px 0 0;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid rgba(255,255,255,.14);
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.danger{
|
||||||
|
border-color: rgba(255,90,122,.40);
|
||||||
|
background: rgba(255,90,122,.10);
|
||||||
|
color: #ffd1dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* form */
|
||||||
|
form{
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label{
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea, input[type="text"], input[type="password"]{
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 12px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: rgba(10,16,28,.65);
|
||||||
|
border: 1px solid rgba(255,255,255,.14);
|
||||||
|
color: var(--text);
|
||||||
|
outline: none;
|
||||||
|
transition: .15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea{
|
||||||
|
min-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea:focus, input:focus{
|
||||||
|
border-color: rgba(122,162,255,.55);
|
||||||
|
box-shadow: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.helper{
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: rgba(232,238,252,.65);
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* custom file input */
|
||||||
|
.file{
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"]{
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: rgba(10,16,28,.40);
|
||||||
|
border: 1px dashed rgba(255,255,255,.18);
|
||||||
|
color: rgba(232,238,252,.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"]::file-selector-button{
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #0b1220;
|
||||||
|
background: linear-gradient(135deg, var(--primary), var(--primary2));
|
||||||
|
transition: .15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"]::file-selector-button:hover{
|
||||||
|
transform: translateY(-1px);
|
||||||
|
filter: brightness(1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buttons */
|
||||||
|
.btn{
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 11px 14px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(255,255,255,.16);
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 750;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: .15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.primary{
|
||||||
|
border: 0;
|
||||||
|
color: #0b1220;
|
||||||
|
background: linear-gradient(135deg, var(--primary), var(--primary2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover{
|
||||||
|
transform: translateY(-1px);
|
||||||
|
filter: brightness(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active{
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* posts list */
|
||||||
|
.posts{
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post{
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--card2);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post .meta{
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12.5px;
|
||||||
|
color: rgba(232,238,252,.75);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge{
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(255,255,255,.14);
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post .content{
|
||||||
|
font-size: 15.5px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post img{
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 14px;
|
||||||
|
margin-top: 12px;
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* footer */
|
||||||
|
.footer{
|
||||||
|
margin-top: 26px;
|
||||||
|
padding: 14px;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(232,238,252,.55);
|
||||||
|
font-size: 12.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* mobile tweaks */
|
||||||
|
@media (max-width: 560px){
|
||||||
|
.nav{ border-radius: 18px; padding: 12px 12px; }
|
||||||
|
.nav a{ padding: 7px 9px; }
|
||||||
|
.card{ padding: 14px; }
|
||||||
|
}
|
||||||
|
/* =========================
|
||||||
|
Base
|
||||||
|
========================= */
|
||||||
|
:root{
|
||||||
|
--bg: #0b1020;
|
||||||
|
--panel: rgba(255,255,255,.06);
|
||||||
|
--panel2: rgba(255,255,255,.10);
|
||||||
|
--border: rgba(255,255,255,.12);
|
||||||
|
--text: rgba(255,255,255,.92);
|
||||||
|
--muted: rgba(255,255,255,.66);
|
||||||
|
--muted2: rgba(255,255,255,.45);
|
||||||
|
|
||||||
|
--primary: #7c3aed; /* violet */
|
||||||
|
--primary2:#22c55e; /* green accent */
|
||||||
|
--danger: #ef4444;
|
||||||
|
--warn: #f59e0b;
|
||||||
|
|
||||||
|
--radius: 16px;
|
||||||
|
--shadow: 0 12px 40px rgba(0,0,0,.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
*{ box-sizing:border-box; }
|
||||||
|
html,body{ height:100%; }
|
||||||
|
body{
|
||||||
|
margin:0;
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Apple Color Emoji","Segoe UI Emoji";
|
||||||
|
color: var(--text);
|
||||||
|
background:
|
||||||
|
radial-gradient(1100px 600px at 10% -10%, rgba(124,58,237,.35), transparent 60%),
|
||||||
|
radial-gradient(900px 500px at 90% 0%, rgba(34,197,94,.22), transparent 55%),
|
||||||
|
radial-gradient(900px 700px at 50% 110%, rgba(59,130,246,.18), transparent 60%),
|
||||||
|
var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
a{ color:inherit; text-decoration:none; }
|
||||||
|
a:hover{ text-decoration:underline; }
|
||||||
|
|
||||||
|
.container{
|
||||||
|
width:min(980px, calc(100% - 32px));
|
||||||
|
margin: 28px auto 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Top nav / brand
|
||||||
|
========================= */
|
||||||
|
.nav{
|
||||||
|
display:flex;
|
||||||
|
justify-content:space-between;
|
||||||
|
align-items:center;
|
||||||
|
gap:12px;
|
||||||
|
padding:14px 16px;
|
||||||
|
border:1px solid var(--border);
|
||||||
|
background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03));
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:10px;
|
||||||
|
font-weight: 750;
|
||||||
|
letter-spacing:.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot{
|
||||||
|
width:10px;
|
||||||
|
height:10px;
|
||||||
|
border-radius:999px;
|
||||||
|
background: var(--primary);
|
||||||
|
box-shadow: 0 0 0 6px rgba(124,58,237,.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav a{
|
||||||
|
display:inline-block;
|
||||||
|
padding:8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.nav a:hover{
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Cards / sections
|
||||||
|
========================= */
|
||||||
|
.card{
|
||||||
|
margin-top:18px;
|
||||||
|
padding:18px;
|
||||||
|
border:1px solid var(--border);
|
||||||
|
background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03));
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.header h1{
|
||||||
|
margin:0 0 10px;
|
||||||
|
font-size: clamp(26px, 2.2vw, 36px);
|
||||||
|
letter-spacing:-.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2{
|
||||||
|
margin: 22px 0 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Alerts
|
||||||
|
========================= */
|
||||||
|
.alert{
|
||||||
|
margin: 10px 0 14px;
|
||||||
|
padding: 12px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(34,197,94,.35);
|
||||||
|
background: rgba(34,197,94,.12);
|
||||||
|
color: rgba(255,255,255,.92);
|
||||||
|
}
|
||||||
|
.alert.danger{
|
||||||
|
border-color: rgba(239,68,68,.45);
|
||||||
|
background: rgba(239,68,68,.12);
|
||||||
|
}
|
||||||
|
.alert.warn{
|
||||||
|
border-color: rgba(245,158,11,.45);
|
||||||
|
background: rgba(245,158,11,.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Forms
|
||||||
|
========================= */
|
||||||
|
form{ display:grid; gap:14px; }
|
||||||
|
label{
|
||||||
|
display:block;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--muted);
|
||||||
|
margin: 0 0 6px;
|
||||||
|
}
|
||||||
|
.helper{
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted2);
|
||||||
|
margin-top:6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
textarea{
|
||||||
|
width:100%;
|
||||||
|
padding: 12px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(255,255,255,.16);
|
||||||
|
background: rgba(0,0,0,.25);
|
||||||
|
color: var(--text);
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
textarea{
|
||||||
|
min-height: 110px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
input:focus, textarea:focus{
|
||||||
|
border-color: rgba(124,58,237,.55);
|
||||||
|
box-shadow: 0 0 0 4px rgba(124,58,237,.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file input[type="file"]{
|
||||||
|
width:100%;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px dashed rgba(255,255,255,.22);
|
||||||
|
background: rgba(0,0,0,.18);
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Buttons
|
||||||
|
========================= */
|
||||||
|
.btn{
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
gap:10px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(255,255,255,.14);
|
||||||
|
background: rgba(255,255,255,.06);
|
||||||
|
color: var(--text);
|
||||||
|
cursor:pointer;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
.btn:hover{ background: rgba(255,255,255,.09); }
|
||||||
|
.btn.primary{
|
||||||
|
border-color: rgba(124,58,237,.45);
|
||||||
|
background: linear-gradient(135deg, rgba(124,58,237,.95), rgba(59,130,246,.70));
|
||||||
|
}
|
||||||
|
.btn.primary:hover{ filter: brightness(1.05); }
|
||||||
|
.btn.danger{
|
||||||
|
border-color: rgba(239,68,68,.45);
|
||||||
|
background: linear-gradient(135deg, rgba(239,68,68,.9), rgba(244,63,94,.55));
|
||||||
|
}
|
||||||
|
.btn:active{ transform: translateY(1px); }
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Posts list
|
||||||
|
========================= */
|
||||||
|
.posts{
|
||||||
|
display:grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.post{
|
||||||
|
border:1px solid rgba(255,255,255,.12);
|
||||||
|
background: rgba(255,255,255,.04);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
.meta{
|
||||||
|
display:flex;
|
||||||
|
flex-wrap:wrap;
|
||||||
|
gap:8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.badge{
|
||||||
|
display:inline-flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
background: rgba(0,0,0,.22);
|
||||||
|
}
|
||||||
|
.content{
|
||||||
|
color: rgba(255,255,255,.9);
|
||||||
|
line-height: 1.55;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.post img{
|
||||||
|
display:block;
|
||||||
|
max-width: min(520px, 100%);
|
||||||
|
margin-top: 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Login / Flag special blocks
|
||||||
|
========================= */
|
||||||
|
.center{
|
||||||
|
min-height: calc(100vh - 80px);
|
||||||
|
display:grid;
|
||||||
|
place-items:center;
|
||||||
|
}
|
||||||
|
.auth-card{
|
||||||
|
width: min(520px, 100%);
|
||||||
|
}
|
||||||
|
.kicker{
|
||||||
|
display:inline-flex;
|
||||||
|
gap:8px;
|
||||||
|
align-items:center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
background: rgba(0,0,0,.2);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.flag-box{
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(34,197,94,.35);
|
||||||
|
background: rgba(34,197,94,.10);
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono","Courier New", monospace;
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer{
|
||||||
|
margin-top: 18px;
|
||||||
|
color: var(--muted2);
|
||||||
|
font-size: 12px;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 560px){
|
||||||
|
.nav{ flex-direction: column; align-items:flex-start; }
|
||||||
|
}
|
||||||
6
upload/.htaccess
Executable file
6
upload/.htaccess
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
php_flag engine off
|
||||||
|
Options -Indexes
|
||||||
|
<FilesMatch "\.(php|phtml|php5|php7|phar)$">
|
||||||
|
Order Deny,Allow
|
||||||
|
Deny from all
|
||||||
|
</FilesMatch>
|
||||||
BIN
upload/695fed507639f.png
Executable file
BIN
upload/695fed507639f.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
Reference in New Issue
Block a user