Init
This commit is contained in:
11
rss/php/autoload.php
Normal file
11
rss/php/autoload.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
spl_autoload_register(function ($class){
|
||||
$class = str_replace('App\\', '', $class);
|
||||
$class = str_replace('\\', '/', $class);
|
||||
|
||||
$file = $_SERVER['DOCUMENT_ROOT'] . '/rss/php/class/'.$class.'.php';
|
||||
|
||||
if(file_exists($file)){
|
||||
require_once($file);
|
||||
}
|
||||
});
|
||||
15
rss/php/class/Main.php
Normal file
15
rss/php/class/Main.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace App;
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use Exception;
|
||||
|
||||
class Main extends Sys{
|
||||
|
||||
|
||||
public function __construct(){
|
||||
|
||||
}
|
||||
}
|
||||
34
rss/php/class/Sys.php
Normal file
34
rss/php/class/Sys.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace App;
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php');
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use Exception;
|
||||
use stdClass;
|
||||
|
||||
class Sys{
|
||||
protected $conn;
|
||||
|
||||
public function __construct(){
|
||||
global $conn;
|
||||
$this->conn = $conn;
|
||||
}
|
||||
|
||||
public function validateVar($val){
|
||||
if(isset($val) && !empty($val) && $val){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function createResponse($status, $message = null, $info = null){
|
||||
$resp = new stdClass();
|
||||
$resp->status = $status;
|
||||
$resp->message = $message;
|
||||
$resp->info = $info;
|
||||
return $resp;
|
||||
}
|
||||
}
|
||||
178
rss/php/class/Users.php
Normal file
178
rss/php/class/Users.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
namespace App;
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php');
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use Exception;
|
||||
|
||||
class Users extends Sys{
|
||||
public $id;
|
||||
public $fName;
|
||||
public $lName;
|
||||
public $email;
|
||||
public $cemail;
|
||||
public $psw;
|
||||
public $cpsw;
|
||||
private $loginState;
|
||||
|
||||
public function __construct(){
|
||||
global $conn;
|
||||
$this->conn = $conn;
|
||||
}
|
||||
|
||||
public function login(){
|
||||
if($this->isAuth()){
|
||||
return $this->createResponse('success', 'Logged in');
|
||||
}else{
|
||||
if($this->validateVar($this->email) && $this->validateVar($this->psw)){
|
||||
$stmt = $this->conn->prepare('SELECT id, psw FROM users WHERE email = :email');
|
||||
$stmt->bindValue(':email', $this->email);
|
||||
$stmt->execute();
|
||||
if($user = $stmt->fetch(PDO::FETCH_ASSOC)){
|
||||
if(password_verify($this->psw, $user['psw'])){
|
||||
$auth_hash = bin2hex(random_bytes(16));
|
||||
$this->id = intval($user['id']);
|
||||
setcookie("user_hash", $auth_hash, [
|
||||
'expires' => time() + 2678400,
|
||||
'path' => '/',
|
||||
// 'secure' => true, // Enable on publish
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
'domain' => $_SERVER['HTTP_HOST']
|
||||
]);
|
||||
|
||||
setcookie("usid", $this->id, [
|
||||
'expires' => time() + 2678400,
|
||||
'path' => '/',
|
||||
// 'secure' => true, // Enable on publish
|
||||
'httponly' => false,
|
||||
'samesite' => 'Strict',
|
||||
'domain' => $_SERVER['HTTP_HOST']
|
||||
]);
|
||||
|
||||
$stmt = $this->conn->prepare('INSERT INTO user_sessions (user_hash, user_id, login_ip) VALUES (:user_hash, :user_id, :login_ip)');
|
||||
$stmt->bindValue(':user_hash', $auth_hash);
|
||||
$stmt->bindValue(':user_id', $user['id']);
|
||||
$stmt->bindValue(':login_ip', $_SERVER['REMOTE_ADDR']);
|
||||
$stmt->execute();
|
||||
return $this->createResponse('success', 'Logged in');
|
||||
}else{
|
||||
return $this->createResponse('fail', 'Incorrect details');
|
||||
}
|
||||
}else{
|
||||
return $this->createResponse('fail', 'Incorrect details');
|
||||
}
|
||||
}else{
|
||||
return $this->createResponse('fail', 'Please fill out all the fields');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function logout(){
|
||||
if(!$this->isAuth()){
|
||||
return true;
|
||||
}
|
||||
|
||||
$stmt = $this->conn->prepare('DELETE FROM user_sessions WHERE user_hash = :user_hash AND user_id = :user_id AND login_ip = :login_ip');
|
||||
$stmt->bindValue(':user_hash', $_COOKIE['user_hash']);
|
||||
$stmt->bindValue(':user_id', intval($_COOKIE['usid']));
|
||||
$stmt->bindValue(':login_ip', $_SERVER['REMOTE_ADDR']);
|
||||
$stmt->execute();
|
||||
|
||||
if(isset($_SERVER['HTTP_COOKIE'])) {
|
||||
$cookies = explode(';', $_SERVER['HTTP_COOKIE']);
|
||||
foreach ($cookies as $cookie) {
|
||||
$parts = explode('=', $cookie);
|
||||
$name = trim($parts[0]);
|
||||
setcookie($name, '', time() - 1000);
|
||||
setcookie($name, '', time() - 1000, '/');
|
||||
setcookie($name, '', time() - 1000, '/', $_SERVER['HTTP_HOST']);
|
||||
}
|
||||
session_destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public function create_user(){
|
||||
// Validate inputs
|
||||
if(!$this->validateVar($this->fName)){
|
||||
return $this->createResponse('fail', 'Please enter your first name');
|
||||
}
|
||||
|
||||
if(!$this->validateVar($this->lName)){
|
||||
return $this->createResponse('fail', 'Please enter your last name');
|
||||
}
|
||||
|
||||
if(!$this->validateVar($this->email)){
|
||||
return $this->createResponse('fail', 'Please enter your email');
|
||||
}
|
||||
|
||||
if($this->email != $this->cemail){
|
||||
return $this->createResponse('fail', 'The email adresses do not match');
|
||||
}
|
||||
|
||||
if(!filter_var($this->email, FILTER_VALIDATE_EMAIL)){
|
||||
return $this->createResponse('fail', 'Please enter a valid email');
|
||||
}
|
||||
|
||||
if($this->psw != $this->cpsw){
|
||||
return $this->createResponse('fail', 'Please enter a valid password');
|
||||
}
|
||||
|
||||
if(strlen($this->psw) < 6){
|
||||
return $this->createResponse('fail', 'Your password needs to be at least 6 characters');
|
||||
}
|
||||
|
||||
$stm = $this->conn->prepare('SELECT * FROM users WHERE email = :email');
|
||||
$stm->bindValue(':email', $this->email);
|
||||
$stm->execute();
|
||||
|
||||
if($stm->rowCount()){
|
||||
return $this->createResponse('fail', 'Email already exists');
|
||||
}
|
||||
|
||||
$password = password_hash(trim($this->psw), PASSWORD_BCRYPT, array('cost' => 12));
|
||||
|
||||
$stmt = $this->conn->prepare('INSERT INTO users (fname, lname, email, psw) VALUES (:fname, :lname, :email, :psw)');
|
||||
$stmt->bindValue(':fname', trim($this->fName));
|
||||
$stmt->bindValue(':lname', trim($this->lName));
|
||||
$stmt->bindValue(':email', trim($this->email));
|
||||
$stmt->bindValue(':psw', $password);
|
||||
$stmt->execute();
|
||||
|
||||
if($stmt->rowCount()){
|
||||
return $this->createResponse('success', 'Account has been created');
|
||||
}else{
|
||||
return $this->createResponse('fail', 'Something went wrong. Please try again or contact support');
|
||||
}
|
||||
}
|
||||
|
||||
public function isAuth(){
|
||||
if(isset($_COOKIE['user_hash']) && !empty($_COOKIE['user_hash'])){
|
||||
$user_ip = $_SERVER['REMOTE_ADDR'];
|
||||
$user_hash = $_COOKIE['user_hash'];
|
||||
$user_id = intval($_COOKIE['usid']);
|
||||
$stmt = $this->conn->prepare('SELECT * FROM user_sessions WHERE user_hash = :user_hash AND user_id = :user_id AND login_ip = :user_ip');
|
||||
$stmt->bindValue(':user_hash', $user_hash);
|
||||
$stmt->bindValue(':user_id', $user_id);
|
||||
$stmt->bindValue(':user_ip', $user_ip);
|
||||
$stmt->execute();
|
||||
return $stmt->rowCount();
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getMyself(){
|
||||
if($this->isAuth()){
|
||||
$user_id = intval($_COOKIE['usid']);
|
||||
$stmt = $this->conn->prepare('SELECT * FROM users WHERE id = :id');
|
||||
$stmt->bindValue(':id', $user_id);
|
||||
$stmt->execute();
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
unset($user['psw']);
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
rss/php/conf.php
Normal file
36
rss/php/conf.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/.env')){
|
||||
$lines = file($_SERVER['DOCUMENT_ROOT'] . '/.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
|
||||
if(isset($lines) && !empty($lines)){
|
||||
foreach($lines as $line){
|
||||
if(strpos(trim($line), '#') === 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
list($name, $value) = explode('=', $line, 2);
|
||||
$name = trim($name);
|
||||
$value = trim($value);
|
||||
|
||||
$value = trim($value, '"\'');
|
||||
|
||||
putenv("$name=$value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
$db_user = getenv("DB_USER");
|
||||
$db_pass = getenv("DB_PASS");
|
||||
$db_server = getenv("DB_SERVER");
|
||||
$db_name = getenv("DB_NAME");
|
||||
|
||||
$conn = new PDO("mysql:host=$db_server;dbname=$db_name;charset=utf8mb4", $db_user, $db_pass);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
}catch(PDOException $e){
|
||||
var_dump($e);
|
||||
echo json_encode('Connection exception');
|
||||
exit();
|
||||
}
|
||||
|
||||
?>
|
||||
60
rss/php/handler.php
Normal file
60
rss/php/handler.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
|
||||
use App\Main;
|
||||
use App\Users;
|
||||
|
||||
$classList = array(
|
||||
'Main' => Main::class,
|
||||
'Users' => Users::class
|
||||
);
|
||||
|
||||
session_start();
|
||||
|
||||
if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php')){
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php');
|
||||
}
|
||||
|
||||
if(isset($_POST['function']) && !empty($_POST['function']) && isset($_POST['target']) && !empty($_POST['target'])){
|
||||
$t_class = trim($_POST['target']);
|
||||
$t_func = trim($_POST['function']);
|
||||
if(array_key_exists($t_class, $classList) && class_exists($classList[$t_class])){
|
||||
if(method_exists($classList[$t_class], $t_func)){
|
||||
$_class = new $classList[$t_class];
|
||||
|
||||
if(count($_POST) > 2){
|
||||
foreach($_POST as $prop => $val){
|
||||
if(property_exists($classList[$t_class], $prop)){
|
||||
$_class->$prop = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($_FILES) && !empty($_FILES)){
|
||||
foreach($_FILES as $prop => $val){
|
||||
if(property_exists($classList[$t_class], $prop)){
|
||||
$_class->$prop = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response = $_class->$t_func();
|
||||
if($response){
|
||||
$msg = $response;
|
||||
}else{
|
||||
$msg = new stdClass();
|
||||
$msg->status = 'fail';
|
||||
$msg->message = $response;
|
||||
}
|
||||
}else{
|
||||
$msg = new stdClass();
|
||||
$msg->status = 'fail';
|
||||
$msg->message = 'Invalid function';
|
||||
}
|
||||
}else{
|
||||
$msg = new stdClass();
|
||||
$msg->status = 'fail';
|
||||
$msg->message = 'Invalid class';
|
||||
}
|
||||
}
|
||||
|
||||
die(json_encode($msg));
|
||||
11
rss/php/includes/footer.php
Normal file
11
rss/php/includes/footer.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
global $pageInfo;
|
||||
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/rss/js/' . $pageInfo['template'] . '.js')) {
|
||||
echo '<script src="/rss/js/' . $pageInfo['template'] . '.js"></script>';
|
||||
}
|
||||
?>
|
||||
|
||||
<footer>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
79
rss/php/includes/header.php
Normal file
79
rss/php/includes/header.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
$page = isset($_GET['page']) ? basename($_GET['page']) : 'index';
|
||||
$jsonFile = $_SERVER['DOCUMENT_ROOT'] . "/rss/json/pages/{$page}.json";
|
||||
$pageData = file_exists($jsonFile) ? json_decode(file_get_contents($jsonFile), true) : [
|
||||
'title' => 'SITE',
|
||||
'long_desc' => '',
|
||||
'robots' => 'index, follow'
|
||||
];
|
||||
|
||||
// Set default values
|
||||
$siteUrl = 'https://DOMAIN.com';
|
||||
$pageUrl = $page === 'index' ? $siteUrl . '/' : $siteUrl . '/' . $page . '/';
|
||||
$title = $pageData['title'] ?? 'SITE';
|
||||
$description = $pageData['long_desc'] ?? '';
|
||||
$keywords = '';
|
||||
$robots = $pageData['robots'] ?? 'index, follow';
|
||||
|
||||
$currentPage = $page === 'index' ? 'Home' : ucwords(str_replace('-', ' ', $page));
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Defaults -->
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WEBSITE | <?php echo htmlspecialchars($pageData['title'], ENT_QUOTES, 'UTF-8'); ?></title>
|
||||
<meta name="description" content="<?php echo htmlspecialchars($description, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<meta name="author" content="WEBSITE">
|
||||
<meta name="keywords" content="<?php echo htmlspecialchars($keywords, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<meta name="robots" content="<?php echo htmlspecialchars($robots, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<link rel="canonical" href="<?php echo htmlspecialchars($pageUrl, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="<?php echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<meta property="og:description" content="<?php echo htmlspecialchars($description, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="<?php echo htmlspecialchars($pageUrl, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<meta property="og:image" content="<?php echo $siteUrl; ?>/IMAGE">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="<?php echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<meta name="twitter:description" content="<?php echo htmlspecialchars($description, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<meta name="twitter:image" content="<?php echo $siteUrl; ?>/IMAGE">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="/rss/img/favicon/favicon.ico" type="image/x-icon">
|
||||
<link rel="apple-touch-icon" href="/rss/img/favicon/apple-touch-icon.png">
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="preload" href="/rss/css/main.css" as="style">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/rss/css/main.css">
|
||||
<link rel="stylesheet" href="/rss/css/theme.css" media="print" onload="this.media='all'">
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="/rss/js/main.js" defer></script>
|
||||
|
||||
<!-- Structured Data (JSON-LD) -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebPage",
|
||||
"name": "<?php echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); ?>",
|
||||
"url": "<?php echo htmlspecialchars($pageUrl, ENT_QUOTES, 'UTF-8'); ?>",
|
||||
"description": "<?php echo htmlspecialchars($description, ENT_QUOTES, 'UTF-8'); ?>",
|
||||
"isPartOf": {
|
||||
"@type": "WebSite",
|
||||
"name": "SITE NAME",
|
||||
"url": "<?php echo $siteUrl; ?>/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
|
||||
</header>
|
||||
43
rss/php/pageHandler.php
Normal file
43
rss/php/pageHandler.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/autoload.php');
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/rss/php/conf.php');
|
||||
$page = isset($_GET['page']) && !empty($_GET['page']) ? str_replace('.php', '', $_GET['page']) : 'home';
|
||||
if($page == '404'){
|
||||
http_response_code(404);
|
||||
}
|
||||
|
||||
if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/rss/json/pages/' . $page . '.json')){
|
||||
// Load page configuration
|
||||
$jsonInfo = file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/rss/json/pages/' . $page .'.json');
|
||||
$pageInfo = json_decode($jsonInfo, true);
|
||||
$gU = new App\Users();
|
||||
$userAuth = $gU->isAuth();
|
||||
|
||||
if($pageInfo['restricted'] && !$userAuth){
|
||||
header('location:/login/');
|
||||
}
|
||||
|
||||
if($pageInfo['redirect_login'] && $userAuth){
|
||||
header('location:/account/');
|
||||
}
|
||||
|
||||
}else{
|
||||
http_response_code(404);
|
||||
header('location:/404');
|
||||
die('Not found');
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
function loadContents(){
|
||||
global $pageInfo;
|
||||
if(file_exists($_SERVER['DOCUMENT_ROOT'] . '/rss/php/templates/'.$pageInfo['template'].'.php')){
|
||||
include($_SERVER['DOCUMENT_ROOT'] . '/rss/php/templates/'.$pageInfo['template'].'.php');
|
||||
}else{
|
||||
http_response_code(404);
|
||||
header('location:/404');
|
||||
exit();
|
||||
}
|
||||
}
|
||||
?>
|
||||
15
rss/php/templates/404.php
Normal file
15
rss/php/templates/404.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<main>
|
||||
<div class="home-banner">
|
||||
<div class="home-banner-inner">
|
||||
<div class="container">
|
||||
<div class="d-flex justify-center align-center">
|
||||
<div class="text-col">
|
||||
<h1 class="title mb-3">Oops! <span class="text-primary">Page Not Found</span></h1>
|
||||
<p class="lead mb-3">Looks like this page got lost in the search for love. Don’t worry—let’s get you back to finding your perfect match!</p>
|
||||
<a href="/" class="mb-3 btn btn-dark cta-btn">Back to home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
696
rss/php/templates/index.php
Normal file
696
rss/php/templates/index.php
Normal file
@@ -0,0 +1,696 @@
|
||||
<div class="container">
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Typography -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Typography</h2>
|
||||
<p class="text-sm">Type scale, weight, alignment & transforms — each example followed by its code.</p>
|
||||
|
||||
<!-- Example: Type scale -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Type scale</h3>
|
||||
<div class="border rounded p-3 bg-accent-white stack-1">
|
||||
<div class="text-xxl fw-bold lh-heading">Heading XXL</div>
|
||||
<div class="text-xl fw-semibold lh-heading">Heading XL</div>
|
||||
<div class="text-lg fw-medium lh-heading">Heading LG</div>
|
||||
<div class="text-base">Body Base</div>
|
||||
<div class="text-sm text-muted">Small muted text</div>
|
||||
<div class="text-xs text-uppercase">Extra small uppercase</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="text-xxl fw-bold lh-heading">Heading XXL</div>
|
||||
<div class="text-xl fw-semibold lh-heading">Heading XL</div>
|
||||
<div class="text-lg fw-medium lh-heading">Heading LG</div>
|
||||
<div class="text-base">Body Base</div>
|
||||
<div class="text-sm text-muted">Small muted text</div>
|
||||
<div class="text-xs text-uppercase">Extra small uppercase</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Alignment -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Alignment</h3>
|
||||
<div class="border rounded p-3 bg-accent-white stack-1">
|
||||
<div class="text-left">Left aligned</div>
|
||||
<div class="text-center">Center aligned</div>
|
||||
<div class="text-right">Right aligned</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="text-left">Left aligned</div>
|
||||
<div class="text-center">Center aligned</div>
|
||||
<div class="text-right">Right aligned</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Weights & transforms -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Weights & transforms</h3>
|
||||
<div class="border rounded p-3 bg-accent-white stack-1">
|
||||
<div class="fw-light">Light</div>
|
||||
<div class="fw-normal">Normal</div>
|
||||
<div class="fw-semibold">Semibold</div>
|
||||
<div class="fw-bold text-uppercase">Uppercase bold</div>
|
||||
<div class="text-underline">Underline</div>
|
||||
<div class="text-line-through">Line-through</div>
|
||||
<div class="text-no-decoration">No decoration</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="fw-light">Light</div>
|
||||
<div class="fw-normal">Normal</div>
|
||||
<div class="fw-semibold">Semibold</div>
|
||||
<div class="fw-bold text-uppercase">Uppercase bold</div>
|
||||
<div class="text-underline">Underline</div>
|
||||
<div class="text-line-through">Line-through</div>
|
||||
<div class="text-no-decoration">No decoration</div></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Color Utilities -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Color Utilities</h2>
|
||||
<p class="text-sm">Text, background, border & spinner colors.</p>
|
||||
|
||||
<!-- Example: Text colors -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Text</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
|
||||
<div class="text-primary">Primary</div>
|
||||
<div class="text-success">Success</div>
|
||||
<div class="text-danger">Danger</div>
|
||||
<div class="text-info">Info</div>
|
||||
<div class="text-gray">Gray</div>
|
||||
<div class="text-muted">Muted</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="text-primary">Primary</div>
|
||||
<div class="text-success">Success</div>
|
||||
<div class="text-danger">Danger</div>
|
||||
<div class="text-info">Info</div>
|
||||
<div class="text-gray">Gray</div>
|
||||
<div class="text-muted">Muted</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Background colors -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Background</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2">
|
||||
<div class="p-2 bg-primary text-white rounded-sm text-center">Primary</div>
|
||||
<div class="p-2 bg-success text-white rounded-sm text-center">Success</div>
|
||||
<div class="p-2 bg-warning rounded-sm text-center">Warning</div>
|
||||
<div class="p-2 bg-danger text-white rounded-sm text-center">Danger</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="p-2 bg-primary text-white rounded-sm">Primary</div>
|
||||
<div class="p-2 bg-success text-white rounded-sm">Success</div>
|
||||
<div class="p-2 bg-warning rounded-sm">Warning</div>
|
||||
<div class="p-2 bg-danger text-white rounded-sm">Danger</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Border colors -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Border</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
|
||||
<div class="p-2 border border-primary rounded-sm text-center">Primary</div>
|
||||
<div class="p-2 border border-success rounded-sm text-center">Success</div>
|
||||
<div class="p-2 border border-danger rounded-sm text-center">Danger</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="p-2 border border-primary rounded-sm">Primary</div>
|
||||
<div class="p-2 border border-success rounded-sm">Success</div>
|
||||
<div class="p-2 border border-danger rounded-sm">Danger</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Spinner colors -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Spinner</h3>
|
||||
<div class="border rounded p-3 bg-accent-white flex items-center gap-3">
|
||||
<span class="spinner spinner-primary"></span>
|
||||
<span class="spinner spinner-success"></span>
|
||||
<span class="spinner spinner-danger"></span>
|
||||
<span class="spinner spinner-info"></span>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><span class="spinner spinner-primary"></span>
|
||||
<span class="spinner spinner-success"></span>
|
||||
<span class="spinner spinner-danger"></span>
|
||||
<span class="spinner spinner-info"></span></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Spacing & Sizing -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Spacing & Sizing</h2>
|
||||
<p class="text-sm">Margin/padding utilities and width/height tokens.</p>
|
||||
|
||||
<!-- Example: Margin & Padding -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Margin & Padding</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2">
|
||||
<div class="bg-accent-white border rounded p-2 m-1 text-center">m-1 p-2</div>
|
||||
<div class="bg-accent-white border rounded p-3 mx-2 my-1 text-center">mx-2 my-1</div>
|
||||
<div class="bg-accent-white border rounded px-4 py-2 text-center">px-4 py-2</div>
|
||||
<div class="bg-accent-white border rounded pt-5 pb-1 text-center">pt-5 pb-1</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="m-1 p-2">...</div>
|
||||
<div class="mx-2 my-1 p-3">...</div>
|
||||
<div class="px-4 py-2">...</div>
|
||||
<div class="pt-5 pb-1">...</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Percentage widths -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Percentage widths</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2 items-center">
|
||||
<div class="bg-info rounded text-center text-sm p-1 w-25">w-25</div>
|
||||
<div class="bg-info rounded text-center text-sm p-1 w-50">w-50</div>
|
||||
<div class="bg-info rounded text-center text-sm p-1 w-75">w-75</div>
|
||||
<div class="bg-info rounded text-center text-sm p-1 w-100">w-100</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="w-25">...</div>
|
||||
<div class="w-50">...</div>
|
||||
<div class="w-75">...</div>
|
||||
<div class="w-100">...</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Fixed & viewport sizes -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Fixed & viewport sizes</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2 items-center">
|
||||
<div class="bg-warning rounded text-center text-sm p-1 wpx-64">wpx-64</div>
|
||||
<div class="bg-warning rounded text-center text-sm p-1 wrem-4">wrem-4</div>
|
||||
<div class="bg-warning rounded text-center text-sm p-1 wvw-25">wvw-25</div>
|
||||
<div class="bg-warning rounded text-center text-sm p-1 hvh-25">hvh-25</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="wpx-64">...</div>
|
||||
<div class="wrem-4">...</div>
|
||||
<div class="wvw-25">...</div>
|
||||
<div class="hvh-25">...</div></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Layout (Containers, Flex Grid, CSS Grid) -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Layout</h2>
|
||||
<p class="text-sm">Containers, flex row/cols, and CSS grid helpers.</p>
|
||||
|
||||
<!-- Example: Containers -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Containers</h3>
|
||||
<div class="border rounded p-3 bg-accent-white stack-1">
|
||||
<div class="container-narrow border rounded p-2">.container-narrow</div>
|
||||
<div class="container-wide border rounded p-2">.container-wide</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="container-narrow">...</div>
|
||||
<div class="container-wide">...</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Flex grid -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Flex grid (.row / .col)</h3>
|
||||
<div class="border rounded p-3 bg-accent-white">
|
||||
<div class="row">
|
||||
<div class="col-4 p-2"><div class="border rounded p-2 text-center">col-4</div></div>
|
||||
<div class="col-4 p-2"><div class="border rounded p-2 text-center">col-4</div></div>
|
||||
<div class="col-4 p-2"><div class="border rounded p-2 text-center">col-4</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="row">
|
||||
<div class="col-4">...</div>
|
||||
<div class="col-4">...</div>
|
||||
<div class="col-4">...</div>
|
||||
</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: CSS Grid -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">CSS Grid</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
|
||||
<div class="border rounded p-2 text-center">1</div>
|
||||
<div class="border rounded p-2 text-center">2</div>
|
||||
<div class="border rounded p-2 text-center">3</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="grid grid-cols-3 gap-2">
|
||||
<div>1</div>
|
||||
<div>2</div>
|
||||
<div>3</div>
|
||||
</div></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Flexbox: Alignment & Justify -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2">
|
||||
<h2 class="text-xl fw-semibold">Flexbox: Alignment & Justify</h2>
|
||||
<p class="text-sm text-muted">Use <code>.flex-*</code>, <code>.items-*</code>, and <code>.justify-*</code> to control layout.</p>
|
||||
|
||||
<!-- Live example -->
|
||||
<div class="card p-3 stack-2">
|
||||
<div class="fw-semibold">Center both axes</div>
|
||||
<div class="border rounded p-3 bg-accent-white" style="height:120px;">
|
||||
<div class="flex items-center justify-center h-100">
|
||||
<span class="pill">.flex .items-center .justify-center</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fw-semibold">Space between horizontally, start vertically</div>
|
||||
<div class="border rounded p-3 bg-accent-white">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<span class="pill">A</span><span class="pill">B</span><span class="pill">C</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fw-semibold">Column + center</div>
|
||||
<div class="border rounded p-3 bg-accent-white">
|
||||
<div class="flex-column items-center gap-1">
|
||||
<span class="pill">1</span><span class="pill">2</span><span class="pill">3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Code -->
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="flex items-center justify-center">...</div>
|
||||
<div class="flex items-start justify-between">...</div>
|
||||
<div class="flex-column items-center">...</div></code></pre>
|
||||
</section>
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Grid: Item Placement (Span & Order) -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2">
|
||||
<h2 class="text-xl fw-semibold">Grid: Item Placement</h2>
|
||||
<p class="text-sm text-muted">Span columns/rows and reorder visually when needed.</p>
|
||||
|
||||
<div class="card p-3 stack-2">
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-4 gap-2">
|
||||
<div class="card p-2">1</div>
|
||||
<div class="card p-2" style="grid-column: span 2;">2 spans 2 cols</div>
|
||||
<div class="card p-2">3</div>
|
||||
<div class="card p-2" style="grid-column: 1 / -1;">4 full row</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="grid grid-cols-4">
|
||||
<div>1</div>
|
||||
<div style="grid-column: span 2;">2</div>
|
||||
<div>3</div>
|
||||
<div style="grid-column: 1 / -1;">4</div>
|
||||
</div></code></pre>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Responsive Flex vs Grid (When to use which) -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2">
|
||||
<h2 class="text-xl fw-semibold">Flex vs Grid (Quick Patterns)</h2>
|
||||
<p class="text-sm text-muted">Use flex for 1-dimensional alignment; use grid for true 2-D layouts.</p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="card p-3 stack-2">
|
||||
<div class="fw-semibold">Nav bar (Flex)</div>
|
||||
<div class="border rounded p-2 bg-accent-white flex items-center justify-between">
|
||||
<span class="pill">Logo</span>
|
||||
<div class="flex gap-1">
|
||||
<span class="pill">Link</span><span class="pill">Link</span><span class="pill">Link</span>
|
||||
</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="flex items-center justify-between">...</div></code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card p-3 stack-2">
|
||||
<div class="fw-semibold">Card gallery (Grid)</div>
|
||||
<div class="border rounded p-2 bg-accent-white grid grid-auto-fit-md gap-2">
|
||||
<article class="card p-2">Card</article>
|
||||
<article class="card p-2">Card</article>
|
||||
<article class="card p-2">Card</article>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="grid grid-auto-fit-md gap-2">...</div></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Borders, Radius, Shadows -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Borders, Radius, Shadows</h2>
|
||||
<p class="text-sm">Border widths, corner radii, and shadow levels.</p>
|
||||
|
||||
<!-- Example: Borders & Radius -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Borders & radius</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
|
||||
<div class="border rounded-sm p-2 text-center">.border .rounded-sm</div>
|
||||
<div class="border-2 rounded-lg p-2 text-center">.border-2 .rounded-lg</div>
|
||||
<div class="border-3 rounded-xl p-2 text-center">.border-3 .rounded-xl</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="border rounded-sm">...</div>
|
||||
<div class="border-2 rounded-lg">...</div>
|
||||
<div class="border-3 rounded-xl">...</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Card shadows -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Card shadows</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
|
||||
<article class="card p-2">.card</article>
|
||||
<article class="card card-shadow-4 p-2">.card .card-shadow-4</article>
|
||||
<article class="card shadow-none p-2">.card .shadow-none</article>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><article class="card">...</article>
|
||||
<article class="card card-shadow-4">...</article>
|
||||
<article class="card shadow-none">...</article></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Controls (Inputs & Buttons) -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Controls</h2>
|
||||
<p class="text-sm">Shared blueprint, sizes, and variants.</p>
|
||||
|
||||
<!-- Example: Inputs -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Inputs</h3>
|
||||
<div class="border rounded p-3 bg-accent-white stack-1">
|
||||
<input class="input input-sm" placeholder="Small" />
|
||||
<input class="input input-md" placeholder="Medium" />
|
||||
<input class="input input-lg" placeholder="Large" />
|
||||
<input class="input input-xl" placeholder="XL" />
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><input class="input input-sm" placeholder="Small" />
|
||||
<input class="input input-md" placeholder="Medium" />
|
||||
<input class="input input-lg" placeholder="Large" />
|
||||
<input class="input input-xl" placeholder="XL" /></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Buttons -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Buttons</h3>
|
||||
<div class="border rounded p-3 bg-accent-white stack-1">
|
||||
<button class="btn btn-sm btn-primary">Primary</button>
|
||||
<button class="btn btn-md btn-success">Success</button>
|
||||
<button class="btn btn-lg btn-outline-danger">Outline Danger</button>
|
||||
<button class="btn btn-xl btn-ghost-info">Ghost Info</button>
|
||||
<button class="btn btn-md" disabled>Disabled</button>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><button class="btn btn-sm btn-primary">Primary</button>
|
||||
<button class="btn btn-md btn-success">Success</button>
|
||||
<button class="btn btn-lg btn-outline-danger">Outline Danger</button>
|
||||
<button class="btn btn-xl btn-ghost-info">Ghost Info</button>
|
||||
<button class="btn btn-md" disabled>Disabled</button></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Alerts -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Alerts</h2>
|
||||
<p class="text-sm">Contextual feedback banners.</p>
|
||||
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Variants</h3>
|
||||
<div class="border rounded p-3 bg-accent-white stack-1">
|
||||
<div class="alert">Base alert</div>
|
||||
<div class="alert alert-primary">Primary alert</div>
|
||||
<div class="alert alert-success">Success alert</div>
|
||||
<div class="alert alert-warning">Warning alert</div>
|
||||
<div class="alert alert-danger">Danger alert</div>
|
||||
<div class="alert alert-info">Info alert</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="alert">Base alert</div>
|
||||
<div class="alert alert-primary">Primary alert</div>
|
||||
<div class="alert alert-success">Success alert</div>
|
||||
<div class="alert alert-warning">Warning alert</div>
|
||||
<div class="alert alert-danger">Danger alert</div>
|
||||
<div class="alert alert-info">Info alert</div></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Cards -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Cards</h2>
|
||||
<p class="text-sm">Surface container with border/shadow variants.</p>
|
||||
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Examples</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
|
||||
<article class="card p-2">Default card</article>
|
||||
<article class="card card-border-primary p-2">Bordered (primary)</article>
|
||||
<article class="card card-shadow-4 p-2">Shadow level 4</article>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><article class="card">...</article>
|
||||
<article class="card card-border-primary">...</article>
|
||||
<article class="card card-shadow-4">...</article></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Tabs & Tooltips -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Tabs (With Panels)</h2>
|
||||
<p class="text-sm text-muted">
|
||||
Each tab points to a content panel by <code>href="#panel-id"</code>.
|
||||
The JS shows the matching panel and hides the others.
|
||||
</p>
|
||||
|
||||
<!-- Live example -->
|
||||
<div class="card p-3 stack-2">
|
||||
<div class="tabs">
|
||||
<a class="tab active" href="#tab-panel-1">One</a>
|
||||
<a class="tab" href="#tab-panel-2">Two</a>
|
||||
<a class="tab" href="#tab-panel-3">Three</a>
|
||||
</div>
|
||||
|
||||
<div class="stack-2 mt-2">
|
||||
<div class="tab-panel card p-2" id="tab-panel-1">Content for tab one.</div>
|
||||
<div class="tab-panel card p-2" id="tab-panel-2" hidden>Content for tab two.</div>
|
||||
<div class="tab-panel card p-2" id="tab-panel-3" hidden>Content for tab three.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Copy-paste code -->
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="tabs">
|
||||
<a class="tab active" href="#tab-panel-1">One</a>
|
||||
<a class="tab" href="#tab-panel-2">Two</a>
|
||||
<a class="tab" href="#tab-panel-3">Three</a>
|
||||
</div>
|
||||
|
||||
<div class="stack-2 mt-2">
|
||||
<div class="tab-panel card p-2" id="tab-panel-1">Content for tab one.</div>
|
||||
<div class="tab-panel card p-2" id="tab-panel-2" hidden>Content for tab two.</div>
|
||||
<div class="tab-panel card p-2" id="tab-panel-3" hidden>Content for tab three.</div>
|
||||
</div></code></pre>
|
||||
|
||||
<ul class="text-sm text-muted stack-1">
|
||||
<li>Make sure each tab’s <code>href</code> matches a panel’s <code>id</code>.</li>
|
||||
<li>Only one panel should be visible — the others have <code>hidden</code>.</li>
|
||||
<li>Keep classes exactly as shown: <code>.tabs</code>, <code>.tab</code>, <code>.tab-panel</code>.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Modal -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Modal</h2>
|
||||
<p class="text-sm text-muted">
|
||||
Click the button to open a modal. Close it by clicking the overlay or the ✕ button.
|
||||
Works with the class-based JS you added (looks for <code>.modal-open</code>, <code>.modal__overlay</code>, <code>.modal__close</code>).
|
||||
</p>
|
||||
|
||||
<!-- Live example -->
|
||||
<div class="card p-3 stack-2">
|
||||
<button class="btn btn-md btn-primary modal-open" href="#demoModal">Open demo modal</button>
|
||||
|
||||
<div class="modal" id="demoModal" aria-hidden="true" role="dialog" aria-modal="true">
|
||||
<div class="modal__overlay"></div>
|
||||
<div class="modal__dialog stack-2" role="document">
|
||||
<div class="modal__header">
|
||||
<h3 class="modal__title">Demo modal</h3>
|
||||
<button class="modal__close" aria-label="Close">✕</button>
|
||||
</div>
|
||||
<div class="stack-2">
|
||||
<p>Put your content here.</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<input class="input input-md" placeholder="Your name" />
|
||||
<button class="btn btn-md btn-success">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Copy-paste code -->
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><button class="btn btn-md btn-primary modal-open" href="#demoModal">Open demo modal</button>
|
||||
|
||||
<div class="modal" id="demoModal" aria-hidden="true" role="dialog" aria-modal="true">
|
||||
<div class="modal__overlay"></div>
|
||||
<div class="modal__dialog" role="document">
|
||||
<div class="modal__header">
|
||||
<h3 class="modal__title">Demo modal</h3>
|
||||
<button class="modal__close" aria-label="Close">✕</button>
|
||||
</div>
|
||||
<div>
|
||||
<p>Put your content here.</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<input class="input input-md" placeholder="Your name" />
|
||||
<button class="btn btn-md btn-success">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div></code></pre>
|
||||
|
||||
<!-- Tips -->
|
||||
<ul class="text-sm text-muted stack-1">
|
||||
<li>Trigger: <code>.modal-open</code> on a button/link. Use <code>href="#modalId"</code> or <code>data-target="#modalId"</code>.</li>
|
||||
<li>Close: add <code>.modal__close</code> to a button or click the <code>.modal__overlay</code>.</li>
|
||||
<li>Only change the <code>id</code> and the content — keep the classes the same.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Utilities -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">Utilities</h2>
|
||||
<p class="text-sm">Motion, overflow, ratio, cursors & opacity.</p>
|
||||
|
||||
<!-- Example: Motion & text -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Motion & text</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
|
||||
<div class="p-2 border rounded hover-raise text-center">.hover-raise</div>
|
||||
<div class="p-2 border rounded hover-glow text-center">.hover-glow</div>
|
||||
<div class="p-2 border rounded truncate w-50">This is a very long line that will be truncated.</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="hover-raise">...</div>
|
||||
<div class="hover-glow">...</div>
|
||||
<div class="truncate w-50">Long long text...</div></code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Example: Effects, overflow, ratios -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">Effects, overflow, ratios</h3>
|
||||
<div class="border rounded p-3 bg-accent-white grid grid-cols-3 gap-2">
|
||||
<div class="p-2 border rounded opacity-50 text-center">.opacity-50</div>
|
||||
<div class="p-2 border rounded cursor-not-allowed text-center">.cursor-not-allowed</div>
|
||||
<div class="p-2 border rounded overflow-scroll" style="max-height:3rem">
|
||||
.overflow-scroll<br/>Line 2<br/>Line 3<br/>Line 4
|
||||
</div>
|
||||
<div class="p-2 border rounded ratio ratio-16x9"><img class="object-cover" src="https://picsum.photos/640/360" alt=""></div>
|
||||
<div class="p-2 border rounded ratio ratio-1x1"><img class="object-contain" src="https://picsum.photos/512" alt=""></div>
|
||||
<div class="p-2 border rounded blur-sm text-center">.blur-sm</div>
|
||||
</div>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code><div class="opacity-50">...</div>
|
||||
<div class="cursor-not-allowed">...</div>
|
||||
<div class="overflow-scroll">...</div>
|
||||
<div class="ratio ratio-16x9"><img class="object-cover" ... /></div>
|
||||
<div class="ratio ratio-1x1"><img class="object-contain" ... /></div>
|
||||
<div class="blur-sm">...</div></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- JavaScript Helpers (Reference) -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h2 class="text-xl fw-semibold">JavaScript Helpers (Reference)</h2>
|
||||
<p class="text-sm">
|
||||
Lightweight utilities used across the docs and components. Keep logic simple and colocated.
|
||||
This card describes the functions and expected usage; copy snippets as needed.
|
||||
</p>
|
||||
|
||||
<!-- HTTP -->
|
||||
<div class="card p-3 stack-2">
|
||||
<h3 class="text-lg fw-medium">HTTP</h3>
|
||||
<p class="text-sm">Post JSON-ish data via <code>FormData</code> to the PHP handler.</p>
|
||||
|
||||
<div class="stack-1">
|
||||
<div>
|
||||
<div class="fw-semibold">_post(formObj, target, func)</div>
|
||||
<p class="text-sm text-gray">Builds a <code>FormData</code> payload and POSTs to <code>/rss/php/handler.php</code>, returning JSON.</p>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
|
||||
const res = await _post(
|
||||
{ name: 'Ada', roles: ['admin','editor'] },
|
||||
'users',
|
||||
'create'
|
||||
);
|
||||
// res → parsed JSON (or { ok:false, error:"..." } on failure)</code></pre>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="fw-semibold">createFormData(data, target, func)</div>
|
||||
<p class="text-sm text-gray">Converts plain objects/arrays into <code>FormData</code> and appends <code>target</code>/<code>function</code>.</p>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
|
||||
const fd = await createFormData({ tags: ['a','b'], avatar: file }, 'profile', 'update');</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- DOM -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h3 class="text-lg fw-medium">DOM</h3>
|
||||
<p class="text-sm text-muted">Small helpers to create elements and IDs.</p>
|
||||
|
||||
<div class="stack-2">
|
||||
<div class="card p-3 stack-1">
|
||||
<div class="fw-semibold">createEl(tag, classArr?, text?, dataAttrs?)</div>
|
||||
<p class="text-sm text-gray">Create an element, add classes, text, and <code>data-*</code> attributes.</p>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
|
||||
const btn = createEl('button', ['btn','btn-md','btn-primary'], 'Save', { action: 'save' });
|
||||
document.body.append(btn);</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card p-3 stack-1">
|
||||
<div class="fw-semibold">getRandomID()</div>
|
||||
<p class="text-sm text-gray">Generates a unique id (uses <code>crypto.randomUUID()</code> if available).</p>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
|
||||
const id = getRandomID(); // "c1c0f33a-..." or base36 fallback</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Alerts -->
|
||||
<!-- ====================================================================== -->
|
||||
<section class="card p-3 stack-2 my-2">
|
||||
<h3 class="text-lg fw-medium">Alerts</h3>
|
||||
<p class="text-sm text-muted">Create and remove framework-styled alerts using <code>.alert</code> and <code>.alert-{color}</code>.</p>
|
||||
|
||||
<div class="card p-3 stack-2">
|
||||
<div class="fw-semibold">generateAlert(type, text, options?) → HTMLElement</div>
|
||||
<p class="text-sm text-gray">
|
||||
Types align with your color tokens: <code>primary</code>, <code>success</code>, <code>warning</code>, <code>danger</code>, <code>info</code>, etc.
|
||||
</p>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Create and insert
|
||||
const alertEl = await generateAlert('warning', 'Heads up!', { withClose: true, dismissAfter: 4000 });
|
||||
document.querySelector('#alertHost').append(alertEl);
|
||||
|
||||
// Common types
|
||||
await generateAlert('success', 'Saved!');
|
||||
await generateAlert('danger', 'Something went wrong', { role: 'alert' });</code></pre>
|
||||
|
||||
<div class="fw-semibold">removeAlert(scope?)</div>
|
||||
<p class="text-sm text-gray">Remove all alerts in <code>scope</code> (default: <code>document</code>).</p>
|
||||
<pre class="border rounded p-2 bg-accent-white text-sm"><code>// Example
|
||||
removeAlert(); // or removeAlert(document.querySelector('#alertHost'));</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user