wqInitial Commit
This commit is contained in:
BIN
templates/.index.html.swp
Normal file
BIN
templates/.index.html.swp
Normal file
Binary file not shown.
@@ -1,52 +1,30 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}About{% endblock %}
|
||||
|
||||
{% block title %}About — {{ brand }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
|
||||
<h1 class="text-5xl font-extrabold mb-4">About Me</h1>
|
||||
<p class="text-xl mb-6">Learn more about who I am, what I do, and what drives me.</p>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="py-16 bg-black text-white">
|
||||
<div class="max-w-4xl mx-auto px-4 space-y-12">
|
||||
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-orange-600 mb-4">Who Am I?</h2>
|
||||
<p class="text-lg leading-relaxed">
|
||||
I'm a passionate developer and tech enthusiast focused on building useful, secure, and accessible digital experiences.
|
||||
Whether it’s creating full-stack web applications, designing clean UIs, or managing network infrastructure — I love what I do.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-orange-600 mb-4">Skills & Tools</h2>
|
||||
<ul class="grid sm:grid-cols-2 gap-4 text-lg list-disc pl-6">
|
||||
<li>Python & Flask</li>
|
||||
<li>HTML CSS and Javascript</li>
|
||||
<li>SQL (MYSQL, SQLite)</li>
|
||||
<li>Linux system administration</li>
|
||||
<li>Networking & Security (WireGuard, Samba, DNS-Server)</li>
|
||||
<li>Git & Deployment </li>
|
||||
<li>API integrations</li>
|
||||
<section class="max-w-7xl mx-auto px-6 py-16">
|
||||
<h1 class="text-3xl font-bold">About BrookHaven</h1>
|
||||
<p class="mt-4 text-white/80 max-w-3xl">
|
||||
We’re a small, senior team that prototypes fast and ships safely. We love systems thinking,
|
||||
clean UX, and deployments that don’t wake you up at 3am.
|
||||
</p>
|
||||
<div class="mt-8 grid md:grid-cols-2 gap-6">
|
||||
<div class="rounded-2xl bg-bh.card/70 border border-bh.ring p-6">
|
||||
<h3 class="font-semibold">What we value</h3>
|
||||
<ul class="mt-3 space-y-2 text-white/75 text-sm">
|
||||
<li>• Production realism over slideware</li>
|
||||
<li>• Data-in/data-out from day one</li>
|
||||
<li>• Accessibility & performance as defaults</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Philosophy or Interests -->
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-orange-600 mb-4">What I'm About</h2>
|
||||
<p class="text-lg leading-relaxed">
|
||||
I believe technology should empower people — not confuse them. I enjoy creating tools that are intuitive, helpful, and secure.
|
||||
I'm always learning, whether it’s diving into cybersecurity, mastering backend frameworks, or exploring automation and DevOps.
|
||||
</p>
|
||||
<div class="rounded-2xl bg-bh.card/70 border border-bh.ring p-6">
|
||||
<h3 class="font-semibold">How we work</h3>
|
||||
<ul class="mt-3 space-y-2 text-white/75 text-sm">
|
||||
<li>• Short sprints to real demos</li>
|
||||
<li>• Clear handoffs to internal teams</li>
|
||||
<li>• Infra that fits your stack</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,100 +1,98 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Admin{% endblock %}
|
||||
{% block title %}Admin Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
|
||||
<h1 class="text-5xl font-extrabold mb-4">Admin Dashboard</h1>
|
||||
<p class="text-xl">Manage your blog posts and projects here.</p>
|
||||
<!-- Hero -->
|
||||
<section class="py-20 bg-gradient-to-br from-orange-600 to-red-800 text-white text-center shadow-inner">
|
||||
<h1 class="text-5xl font-extrabold tracking-tight mb-4">Admin Dashboard</h1>
|
||||
<p class="text-lg opacity-90">Manage your posts and projects with ease.</p>
|
||||
</section>
|
||||
|
||||
<div class="max-w-6xl mx-auto py-12 px-4 space-y-16">
|
||||
|
||||
<!-- Projects Section -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-3xl font-bold text-orange-600">Project Options</h2>
|
||||
<a href="{{ url_for('new_project') }}"
|
||||
class="bg-orange-600 text-white px-4 py-2 rounded hover:bg-orange-700 transition shadow">+ New Project</a>
|
||||
<!-- Main Admin Content -->
|
||||
<div class="container-page py-16 space-y-16">
|
||||
<!-- Project Management -->
|
||||
<div class="card">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8">
|
||||
<h2 class="text-3xl font-bold text-[rgb(var(--accent))] mb-4 sm:mb-0">Projects</h2>
|
||||
<a href="{{ url_for('new_project') }}"
|
||||
class="btn btn-primary shadow-glass">
|
||||
<i class="fa fa-plus mr-2"></i> New Project
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full table-auto border border-gray-300 bg-white rounded-lg shadow">
|
||||
<thead class="bg-gray-100">
|
||||
<tr class="text-left text-gray-700 uppercase text-sm">
|
||||
<th class="px-4 py-3">Title</th>
|
||||
<th class="px-4 py-3">Category</th>
|
||||
<th class="px-4 py-3">Actions</th>
|
||||
<div class="overflow-x-auto rounded-xl border border-[rgb(var(--border))]">
|
||||
<table class="min-w-full text-sm text-left">
|
||||
<thead class="bg-[rgb(var(--card))] border-b border-[rgb(var(--border))]">
|
||||
<tr class="text-[rgb(var(--muted))] uppercase text-xs tracking-wider">
|
||||
<th class="px-5 py-3 font-semibold">Title</th>
|
||||
<th class="px-5 py-3 font-semibold">Category</th>
|
||||
<th class="px-5 py-3 font-semibold">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for projectpost in projectpost %}
|
||||
<tr class="border-t border-gray-200">
|
||||
<td class="text-black px-4 py-3 font-medium">{{ projectpost.title }}</td>
|
||||
<td class="text-black px-4 py-3">{{ projectpost.category }}</td>
|
||||
<td class="px-4 py-3 space-x-2">
|
||||
<a href="{{ url_for('view_project', slug=projectpost.slug) }}"
|
||||
class="inline-block bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">View</a>
|
||||
<a href="{{ url_for('edit_project', slug=projectpost.slug) }}"
|
||||
class="inline-block bg-yellow-500 text-white px-3 py-1 rounded hover:bg-yellow-600">Edit</a>
|
||||
<form action="{{ url_for('delete_project', slug=projectpost.slug) }}" method="POST" class="inline">
|
||||
<button type="submit"
|
||||
onclick="return confirm('Delete this Project?');"
|
||||
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
|
||||
Delete
|
||||
</button>
|
||||
<tr class="border-b border-[rgb(var(--border))] hover:bg-[rgb(var(--card))]/60 transition">
|
||||
<td class="px-5 py-3 text-[rgb(var(--fg))] font-medium">{{ projectpost.title }}</td>
|
||||
<td class="px-5 py-3 text-[rgb(var(--muted))]">{{ projectpost.category }}</td>
|
||||
<td class="px-5 py-3 flex flex-wrap gap-2">
|
||||
<a href="{{ url_for('view_project', slug=projectpost.slug) }}" class="btn btn-ghost border-none bg-blue-600 hover:bg-blue-700 text-white">View</a>
|
||||
<a href="{{ url_for('edit_project', slug=projectpost.slug) }}" class="btn btn-ghost border-none bg-yellow-500 hover:bg-yellow-600 text-white">Edit</a>
|
||||
<form action="{{ url_for('delete_project', slug=projectpost.slug) }}" method="POST" onsubmit="return confirm('Delete this project?');">
|
||||
<button type="submit" class="btn btn-ghost border-none bg-red-600 hover:bg-red-700 text-white">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3" class="px-5 py-4 text-center text-[rgb(var(--muted))] italic">No projects found.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blog Section -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-3xl font-bold text-orange-600">Blog Options</h2>
|
||||
<a href="{{ url_for('new_blog') }}"
|
||||
class="bg-orange-600 text-white px-4 py-2 rounded hover:bg-orange-700 transition shadow">+ New Blog Post</a>
|
||||
<!-- Blog Management -->
|
||||
<div class="card">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8">
|
||||
<h2 class="text-3xl font-bold text-[rgb(var(--accent))] mb-4 sm:mb-0">Blog Posts</h2>
|
||||
<a href="{{ url_for('new_blog') }}"
|
||||
class="btn btn-primary shadow-glass">
|
||||
<i class="fa fa-plus mr-2"></i> New Blog Post
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full table-auto border border-gray-300 bg-white rounded-lg shadow">
|
||||
<thead class="bg-gray-100">
|
||||
<tr class="text-left text-gray-700 uppercase text-sm">
|
||||
<th class="px-4 py-3">Title</th>
|
||||
<th class="px-4 py-3">Category</th>
|
||||
<th class="px-4 py-3">Actions</th>
|
||||
<div class="overflow-x-auto rounded-xl border border-[rgb(var(--border))]">
|
||||
<table class="min-w-full text-sm text-left">
|
||||
<thead class="bg-[rgb(var(--card))] border-b border-[rgb(var(--border))]">
|
||||
<tr class="text-[rgb(var(--muted))] uppercase text-xs tracking-wider">
|
||||
<th class="px-5 py-3 font-semibold">Title</th>
|
||||
<th class="px-5 py-3 font-semibold">Category</th>
|
||||
<th class="px-5 py-3 font-semibold">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for blogpost in blogpost %}
|
||||
<tr class="border-t border-gray-200">
|
||||
<td class="text-black px-4 py-3 font-medium">{{ blogpost.title }}</td>
|
||||
<td class="text-black px-4 py-3">{{ blogpost.category }}</td>
|
||||
<td class="px-4 py-3 space-x-2">
|
||||
<a href="{{ url_for('view_blog', slug=blogpost.slug) }}"
|
||||
class="inline-block bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">View</a>
|
||||
<a href="{{ url_for('edit_blog', slug=blogpost.slug) }}"
|
||||
class="inline-block bg-yellow-500 text-white px-3 py-1 rounded hover:bg-yellow-600">Edit</a>
|
||||
<form action="{{ url_for('delete_blog', slug=blogpost.slug) }}" method="POST" class="inline">
|
||||
<button type="submit"
|
||||
onclick="return confirm('Delete this post?');"
|
||||
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
|
||||
Delete
|
||||
</button>
|
||||
<tr class="border-b border-[rgb(var(--border))] hover:bg-[rgb(var(--card))]/60 transition">
|
||||
<td class="px-5 py-3 text-[rgb(var(--fg))] font-medium">{{ blogpost.title }}</td>
|
||||
<td class="px-5 py-3 text-[rgb(var(--muted))]">{{ blogpost.category }}</td>
|
||||
<td class="px-5 py-3 flex flex-wrap gap-2">
|
||||
<a href="{{ url_for('view_blog', slug=blogpost.slug) }}" class="btn btn-ghost border-none bg-blue-600 hover:bg-blue-700 text-white">View</a>
|
||||
<a href="{{ url_for('edit_blog', slug=blogpost.slug) }}" class="btn btn-ghost border-none bg-yellow-500 hover:bg-yellow-600 text-white">Edit</a>
|
||||
<form action="{{ url_for('delete_blog', slug=blogpost.slug) }}" method="POST" onsubmit="return confirm('Delete this post?');">
|
||||
<button type="submit" class="btn btn-ghost border-none bg-red-600 hover:bg-red-700 text-white">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3" class="px-5 py-4 text-center text-[rgb(var(--muted))] italic">No blog posts found.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
45
templates/admin_inquiries.html
Normal file
45
templates/admin_inquiries.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Admin — Inquiries{% endblock %}
|
||||
{% block content %}
|
||||
<section class="max-w-7xl mx-auto px-6 py-10">
|
||||
<h1 class="text-2xl font-bold">Inquiries ({{ total }})</h1>
|
||||
<div class="mt-4 overflow-x-auto">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead class="bg-bh.card/80 border border-bh.ring">
|
||||
<tr>
|
||||
<th class="p-2 text-left">ID</th>
|
||||
<th class="p-2 text-left">Name</th>
|
||||
<th class="p-2 text-left">Email</th>
|
||||
<th class="p-2 text-left">NDA</th>
|
||||
<th class="p-2 text-left">Message</th>
|
||||
<th class="p-2 text-left">UA</th>
|
||||
<th class="p-2 text-left">IP</th>
|
||||
<th class="p-2 text-left">Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr class="border-b border-bh.ring/50">
|
||||
<td class="p-2">{{ r.id }}</td>
|
||||
<td class="p-2">{{ r.name }}</td>
|
||||
<td class="p-2">{{ r.email }}</td>
|
||||
<td class="p-2">{{ r.nda }}</td>
|
||||
<td class="p-2">{{ r.message }}</td>
|
||||
<td class="p-2">{{ r.meta.ua if r.meta else '' }}</td>
|
||||
<td class="p-2">{{ r.meta.ip if r.meta else '' }}</td>
|
||||
<td class="p-2 whitespace-nowrap">{{ r.created_at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex items-center gap-3">
|
||||
{% if page > 1 %}<a class="px-3 py-1 rounded bg-bh.card/80 border border-bh.ring" href="?page={{ page-1 }}">Prev</a>{% endif %}
|
||||
<div class="opacity-75">Page {{ page }} / {{ pages }}</div>
|
||||
{% if page < pages %}<a class="px-3 py-1 rounded bg-bh.card/80 border border-bh.ring" href="?page={{ page+1 }}">Next</a>{% endif %}
|
||||
<a class="ml-auto px-3 py-1 rounded bg-bh-accent/90 text-black font-semibold" href="{{ url_for('admin_inquiries_csv') }}">Export CSV</a>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,85 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-...snip..." crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<!doctype html>
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{% block title %}Benjamin Mosley{% endblock %}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="icon" href="{{ url_for('static', filename='BM.png') }}">
|
||||
<!-- Tailwind (compiled) -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">
|
||||
|
||||
<title>{% block title %}Benjamin Mosley{% endblock %}</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Inter:wght@400;600;800&display=swap" rel="stylesheet">
|
||||
|
||||
</head>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body class="bg-black text-white font-sans min-h-screen flex flex-col">
|
||||
<body class="min-h-screen bg-[rgb(var(--bg))] text-[rgb(var(--fg))] font-sans flex flex-col">
|
||||
<!-- Header -->
|
||||
<header class="border-b border-[rgb(var(--border))]">
|
||||
<div class="container-page h-16 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center gap-3">
|
||||
<div class="h-8 w-8 rounded-xl bg-accent shadow-glass"></div>
|
||||
<span class="font-archivo font-semibold text-2xl tracking-wide">Benjamin Mosley</span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop nav -->
|
||||
<nav class="hidden sm:flex items-center gap-6">
|
||||
<a class="nav-link" href="/blog">Blog</a>
|
||||
<a class="nav-link" href="/resume">Resume</a>
|
||||
<a class="nav-link" href="/projects">Projects</a>
|
||||
<a class="nav-link" href="/contact">Contact</a>
|
||||
</nav>
|
||||
|
||||
<header class="bg-black text-white shadow" x-data="{ open: false }">
|
||||
<div class="max-w-7xl mx-auto flex justify-between items-center p-6">
|
||||
|
||||
<h1>
|
||||
<a href="/" class="text-xl font-bold">Benjamin Mosley</a>
|
||||
</h1>
|
||||
|
||||
<!-- Hamburger button (shown on small screens) -->
|
||||
<button @click="open = !open" class="sm:hidden focus:outline-none">
|
||||
<svg x-show="!open" x-cloak xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
<svg x-show="open" x-cloak xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Desktop nav -->
|
||||
<nav class="hidden sm:flex space-x-4">
|
||||
<a href="/blog" class="p-3 hover:text-blue-400">Blog</a>
|
||||
<a href="/resume" class="p-3 hover:text-blue-400">Resume</a>
|
||||
<a href="/projects" class="p-3 hover:text-blue-400">Projects</a>
|
||||
<a href="/contact" class="p-3 hover:text-blue-400">Contact</a>
|
||||
<a href="/about" class="p-3 hover:text-blue-400">About</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Mobile nav -->
|
||||
<div x-show="open" x-cloak class="sm:hidden bg-black border-t border-gray-800">
|
||||
<nav class="flex flex-col p-4 space-y-2">
|
||||
<a href="/blog" class="hover:text-blue-400">Blog</a>
|
||||
<a href="/resume" class="hover:text-blue-400">Resume</a>
|
||||
<a href="/projects" class="hover:text-blue-400">Projects</a>
|
||||
<a href="/contact" class="hover:text-blue-400">Contact</a>
|
||||
<a href="/about" class="hover:text-blue-400">About</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Mobile menu button -->
|
||||
<button id="menuBtn" class="sm:hidden btn btn-ghost" aria-controls="mobileNav" aria-expanded="false">
|
||||
Menu
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile nav -->
|
||||
<nav id="mobileNav" class="hidden sm:hidden border-t border-[rgb(var(--border))]">
|
||||
<div class="container-page py-3 flex flex-col gap-2">
|
||||
<a class="nav-link" href="/blog">Blog</a>
|
||||
<a class="nav-link" href="/resume">Resume</a>
|
||||
<a class="nav-link" href="/projects">Projects</a>
|
||||
<a class="nav-link" href="/contact">Contact</a>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="flex-1">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="border-t border-[rgb(var(--border))]">
|
||||
<div class="container-page py-8 text-sm text-[rgb(var(--muted))]">
|
||||
<div class="flex flex-col sm:flex-row items-center justify-between gap-3">
|
||||
<p>© {{ now().year }} Benjamin Mosley.</p>
|
||||
<div class="flex items-center gap-4">
|
||||
<a class="nav-link" href="https://github.com/Benny-ui-ux" target="_blank" rel="noopener">GitHub</a>
|
||||
<a class="nav-link" href="https://bennyshouse.net">Business Site</a>
|
||||
<a class="nav-link" href="mailto:ben@bennyshouse.net">Email</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Tiny JS: mobile menu + theme toggle hook -->
|
||||
<script>
|
||||
(function () {
|
||||
const btn = document.getElementById('menuBtn');
|
||||
const nav = document.getElementById('mobileNav');
|
||||
if (btn && nav) {
|
||||
btn.addEventListener('click', () => {
|
||||
const isOpen = !nav.classList.contains('hidden');
|
||||
nav.classList.toggle('hidden');
|
||||
btn.setAttribute('aria-expanded', String(!isOpen));
|
||||
});
|
||||
}
|
||||
// Optional theme persistence
|
||||
const key = 'bm-theme';
|
||||
const root = document.documentElement;
|
||||
const saved = localStorage.getItem(key);
|
||||
if (saved) root.setAttribute('data-theme', saved);
|
||||
window.toggleTheme = function () {
|
||||
const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
||||
root.setAttribute('data-theme', next);
|
||||
localStorage.setItem(key, next);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<main class="flex-grow">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="bg-black text-white text-center py-6 border-t border-gray-800">
|
||||
<div class="max-w-4xl mx-auto px-4">
|
||||
<p class="text-sm">2025 Benjamin Mosley.</p>
|
||||
<div class="flex justify-center gap-4 mt-2 text-sm">
|
||||
<a href="https://github.com/Benny-ui-ux" target="_blank" class="hover:text-white transition">GitHub</a>
|
||||
<a href="https://bennyshouse.net" class="hover:text-white transition">Business Site</a>
|
||||
<a href="mailto:ben@bennyshouse.net" class="hover:text-white transition">Email</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -5,34 +5,57 @@
|
||||
{% block content %}
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
|
||||
<h1 class="text-5xl font-extrabold mb-4">Blog</h1>
|
||||
<p class="text-xl mb-6">Insights, tutorials, and personal thoughts — straight from my mind to the page.</p>
|
||||
<section class="relative text-center py-24 text-white bg-gradient-to-br from-orange-500/20 via-rose-500/10 to-fuchsia-500/10 backdrop-blur border-b border-white/10">
|
||||
<div class="max-w-3xl mx-auto px-6">
|
||||
<h1 class="text-5xl md:text-6xl font-archivo font-extrabold mb-4">
|
||||
Blog
|
||||
</h1>
|
||||
<p class="text-lg md:text-xl text-white/80 leading-relaxed">
|
||||
Insights, tutorials, and thoughts — straight from my keyboard to the world.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Blog Posts Grid -->
|
||||
<section id="posts" class="py-16 bg-black text-white">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{% for blogpost in blogpost %}
|
||||
<div class="bg-white rounded-lg shadow p-6 hover:shadow-lg transition">
|
||||
<h2 class="text-2xl font-bold text-red-800 mb-2">
|
||||
<a href="{{ url_for('view_blog', slug=blogpost.slug) }}" class="hover:underline">{{ blogpost.title }}</a>
|
||||
</h2>
|
||||
<p class="text-sm text-orange-500 font-medium mb-4">{{ blogpost.category }}</p>
|
||||
<section id="posts" class="py-20 bg-black text-white">
|
||||
<div class="max-w-7xl mx-auto px-6">
|
||||
{% if blogpost %}
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{% for post in blogpost %}
|
||||
<article class="bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl p-6 hover:shadow-xl hover:border-orange-400/30 transition-all duration-300">
|
||||
<header class="mb-4">
|
||||
<h2 class="text-2xl font-semibold font-archivo tracking-tight mb-2">
|
||||
<a href="{{ url_for('view_blog', slug=post.slug) }}" class="hover:text-orange-400 transition">
|
||||
{{ post.title }}
|
||||
</a>
|
||||
</h2>
|
||||
<p class="text-sm text-orange-400 font-medium">
|
||||
{{ post.category }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for tag in blogpost.tags.split(',') %}
|
||||
<a href="{{ url_for('view_tag', tag=tag.strip()) }}"
|
||||
class="inline-block bg-gray-800 text-white text-sm px-3 py-1 rounded-full hover:bg-gray-700 transition">
|
||||
#{{ tag.strip() }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="text-white/70 line-clamp-4 mb-6">
|
||||
{{ post.content[:200] | safe }}{% if post.content|length > 200 %}...{% endif %}
|
||||
</div>
|
||||
|
||||
<footer class="flex flex-wrap gap-2">
|
||||
{% for tag in post.tags.split(',') %}
|
||||
<a href="{{ url_for('view_tag', tag=tag.strip()) }}"
|
||||
class="inline-block bg-orange-500/10 text-orange-300 text-xs px-3 py-1 rounded-full border border-orange-500/20 hover:bg-orange-500/20 transition">
|
||||
#{{ tag.strip() }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</footer>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-20">
|
||||
<p class="text-white/60 text-lg">No blog posts yet — stay tuned for new entries soon!</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,58 +1,64 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Contact{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
|
||||
<!-- Hero -->
|
||||
<section class="text-center py-20 bg-gradient-to-br from-orange-500/20 via-rose-500/10 to-fuchsia-500/10 text-white shadow-inner">
|
||||
<h1 class="text-5xl font-extrabold mb-4">Contact Me</h1>
|
||||
<p class="text-xl mb-6">Looking to connect? Here's where you can reach me!</p>
|
||||
<p class="text-lg opacity-90">Here’s my professional contact card — let’s connect!</p>
|
||||
</section>
|
||||
|
||||
<section class="bg-black text-white py-16 px-6 md:px-20">
|
||||
<!-- vCard Section -->
|
||||
<section class="py-16 px-6 md:px-20 bg-black text-white">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="grid md:grid-cols-2 gap-12">
|
||||
|
||||
<div class="card flex flex-col md:flex-row items-center md:items-start gap-8">
|
||||
|
||||
<!-- Avatar / Photo -->
|
||||
<div class="flex-shrink-0">
|
||||
<img src="{{ url_for('static', filename='Headshot.jpg') }}" alt="{{ CONTACT.name }}"
|
||||
class="w-40 h-40 rounded-full border-4 border-[rgb(var(--accent))] shadow-glass object-cover">
|
||||
</div>
|
||||
|
||||
<!-- Contact Info -->
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold mb-4 text-orange-600">Get in Touch</h2>
|
||||
<p class="mb-4">Feel free to drop me a message via the form or through any of the channels below:</p>
|
||||
<ul class="space-y-3 text-lg">
|
||||
<li><strong>Email:</strong> <a href="mailto:you@example.com" class="text-blue-600 hover:underline">ben@bennyshouse.net</a></li>
|
||||
<li><strong>Business Website:</strong> <a href="https://bennyshouse.net" class="text-blue-600 hover:underline">bennyshouse.net</a></li>
|
||||
<li><strong>Location:</strong> Canyon, Texas</li>
|
||||
</ul>
|
||||
<div class="mt-6 flex space-x-4">
|
||||
|
||||
<a href="https://www.instagram.com/bennyshousefr/" class="text-orange-600 hover:text-orange-800 transition"><i class="fab fa-instagram fa-lg"></i></a>
|
||||
<a href="https://www.linkedin.com/in/benjamin-mosley-849643329/" class="text-orange-600 hover:text-orange-800 transition"><i class="fab fa-linkedin fa-lg"></i></a>
|
||||
<a href="https://github.com/Benny-ui-ux" class="text-orange-600 hover:text-orange-800 transition"><i class="fab fa-github fa-lg"></i></a>
|
||||
<div class="flex-1 space-y-4">
|
||||
<h2 class="text-3xl font-bold">{{ CONTACT.name }}</h2>
|
||||
<p class="text-[rgb(var(--muted))]">{{ CONTACT.title }}</p>
|
||||
|
||||
<div class="grid sm:grid-cols-2 gap-4 text-sm sm:text-base">
|
||||
<div>
|
||||
<p class="font-semibold text-[rgb(var(--accent))]">Email</p>
|
||||
<a href="mailto:{{ CONTACT.email }}" class="hover:underline">{{ CONTACT.email }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-[rgb(var(--accent))]">Phone</p>
|
||||
<a href="tel:{{ CONTACT.phone|replace(' ', '') }}" class="hover:underline">{{ CONTACT.phone }}</a>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-[rgb(var(--accent))]">City / Region</p>
|
||||
<p>{{ CONTACT.city }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold text-[rgb(var(--accent))]">Hours</p>
|
||||
<p>{{ CONTACT.hours }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-4 flex flex-wrap gap-3">
|
||||
<a href="{{ CONTACT.site }}" target="_blank" class="btn btn-primary items-center">
|
||||
<i class="fa fa-globe mr-2 text-[rgb(var(--accent))]"></i> Website
|
||||
</a>
|
||||
<a href="{{ CONTACT.link }}" target="_blank" class="btn btn-primary border-none bg-[rgb(var(--card))] hover:bg-[rgb(var(--border))]">
|
||||
<i class="fab fa-linkedin mr-2 text-[rgb(var(--accent))]"></i> LinkedIn
|
||||
</a>
|
||||
<a href="{{ CONTACT.cal }}" target="_blank" class="btn btn-primary border-none bg-[rgb(var(--card))] hover:bg-[rgb(var(--border))]">
|
||||
<i class="fa fa-calendar mr-2 text-[rgb(var(--accent))]"></i> Schedule Meeting
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<form method="POST" action="/contact" class="space-y-4">
|
||||
<div>
|
||||
<label for="name" class="block font-semibold mb-1">Name</label>
|
||||
<input type="text" id="name" name="name" required class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
</div>
|
||||
<div>
|
||||
<label for="email" class="block font-semibold mb-1">Email</label>
|
||||
<input type="email" id="email" name="email" required class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
</div>
|
||||
<div>
|
||||
<label for="message" class="block font-semibold mb-1">Message</label>
|
||||
<textarea id="message" name="message" rows="5" required class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none"></textarea>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<button type="submit" class="bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">Send</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -23,4 +23,4 @@
|
||||
<button type="submit">Save Changes</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
134
templates/form.html
Normal file
134
templates/form.html
Normal file
@@ -0,0 +1,134 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>United Supermarkets · Cyber Sale Survey</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-slate-950 text-white min-h-screen">
|
||||
<header class="max-w-3xl mx-auto px-4 py-6">
|
||||
<a href="/" class="text-slate-300 underline">← Back</a>
|
||||
<h1 class="mt-2 text-3xl font-extrabold">Quick Survey</h1>
|
||||
<p class="text-slate-300 mt-1">Help us improve <span class="font-semibold">E-Commerce & Digital Coupons</span> at United.</p>
|
||||
</header>
|
||||
|
||||
<main class="max-w-3xl mx-auto px-4">
|
||||
<form method="post" class="bg-slate-900/60 border border-slate-800 rounded-2xl p-5 grid gap-6">
|
||||
<!-- Contact (optional) -->
|
||||
<div class="grid sm:grid-cols-2 gap-4">
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-300">Name (optional)</span>
|
||||
<input name="name" class="text-black p-2 rounded" placeholder="Your name" />
|
||||
</label>
|
||||
<label class="grid gap-1">
|
||||
<span class="text-sm text-slate-300">Email (optional for follow-ups)</span>
|
||||
<input type="email" name="email" class="text-black p-2 rounded" placeholder="you@example.com" />
|
||||
</label>
|
||||
</div>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-slate-300">
|
||||
<input type="checkbox" name="email_consent" value="yes" class="h-4 w-4" />
|
||||
I agree United may contact me about e-commerce and coupons.
|
||||
</label>
|
||||
|
||||
<hr class="border-slate-800">
|
||||
|
||||
<!-- Shopping frequency -->
|
||||
<div class="grid gap-2">
|
||||
<div class="text-sm font-semibold">How often do you shop for groceries online with United?</div>
|
||||
<div class="grid sm:grid-cols-4 gap-2">
|
||||
<label class="flex items-center gap-2"><input type="radio" name="shop_online_freq" value="never" required> Never</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="shop_online_freq" value="rarely"> Rarely</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="shop_online_freq" value="monthly"> Monthly</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="shop_online_freq" value="weekly+"> Weekly+</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fulfillment preference -->
|
||||
<div class="grid gap-2">
|
||||
<div class="text-sm font-semibold">Preferred way to get your order?</div>
|
||||
<div class="grid sm:grid-cols-3 gap-2">
|
||||
<label class="flex items-center gap-2"><input type="radio" name="fulfillment_preference" value="pickup" required> Curbside Pickup</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="fulfillment_preference" value="delivery"> Delivery</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="fulfillment_preference" value="in-store"> In-store Shopping</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Digital coupons -->
|
||||
<div class="grid gap-2">
|
||||
<div class="text-sm font-semibold">Do you use digital coupons?</div>
|
||||
<div class="grid sm:grid-cols-4 gap-2">
|
||||
<label class="flex items-center gap-2"><input type="radio" name="digital_coupons_use" value="often" required> Often</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="digital_coupons_use" value="sometimes"> Sometimes</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="digital_coupons_use" value="tried"> I’ve tried them</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="digital_coupons_use" value="never"> Never</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preference: Digital vs Paper -->
|
||||
<div class="grid gap-2">
|
||||
<div class="text-sm font-semibold">Coupons preference</div>
|
||||
<div class="grid sm:grid-cols-4 gap-2">
|
||||
<label class="flex items-center gap-2"><input type="radio" name="coupons_preference" value="digital" required> Digital</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="coupons_preference" value="paper"> Paper</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="coupons_preference" value="both"> Both</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="coupons_preference" value="none"> None</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- What matters most -->
|
||||
<div class="grid gap-2">
|
||||
<div class="text-sm font-semibold">What matters most when ordering online? <span class="opacity-70">(pick all that apply)</span></div>
|
||||
<div class="grid sm:grid-cols-3 gap-2">
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="what_matters" value="price"> Low Prices</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="what_matters" value="fees"> Low Fees</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="what_matters" value="timeslots"> Convenient Time Slots</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="what_matters" value="speed"> Fast Pickup/Delivery</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="what_matters" value="substitutions"> Good Substitutions</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="what_matters" value="coupons"> Easy Digital Coupons</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Barriers -->
|
||||
<div class="grid gap-2">
|
||||
<div class="text-sm font-semibold">What keeps you from ordering online more often? <span class="opacity-70">(pick all that apply)</span></div>
|
||||
<div class="grid sm:grid-cols-3 gap-2">
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="barriers" value="fees"> Fees</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="barriers" value="pricing"> Prices</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="barriers" value="substitutions"> Substitutions quality</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="barriers" value="availability"> Item availability</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="barriers" value="timeslots"> Time slot availability</label>
|
||||
<label class="flex items-center gap-2"><input type="checkbox" name="barriers" value="app"> App/website usability</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device -->
|
||||
<div class="grid gap-2">
|
||||
<div class="text-sm font-semibold">What device do you usually use?</div>
|
||||
<div class="grid sm:grid-cols-3 gap-2">
|
||||
<label class="flex items-center gap-2"><input type="radio" name="device" value="phone" required> Phone</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="device" value="tablet"> Tablet</label>
|
||||
<label class="flex items-center gap-2"><input type="radio" name="device" value="desktop"> Desktop/Laptop</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Free text -->
|
||||
<div class="grid gap-1">
|
||||
<label class="text-sm font-semibold">Anything we could improve for online shopping?</label>
|
||||
<textarea name="feedback" rows="3" class="text-black p-2 rounded" placeholder="Tell us what would make United online shopping even better."></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<p class="text-xs text-slate-400">Note: This is a quick promo survey; answers help us improve E-Commerce and coupons. </p>
|
||||
<button class="bg-emerald-500 hover:bg-emerald-600 px-6 py-3 rounded-lg font-semibold text-black">Start the Game</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<footer class="max-w-3xl mx-auto px-4 py-8 text-center text-slate-400 text-xs">
|
||||
© United Supermarkets — Cyber Sale Tailgate
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
49
templates/host.html
Normal file
49
templates/host.html
Normal file
@@ -0,0 +1,49 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white">
|
||||
<div class="max-w-3xl mx-auto p-6">
|
||||
<h1 class="text-3xl font-bold mb-4">Tapdown Showdown – Room <span id="room"></span></h1>
|
||||
<div class="mb-4 flex gap-2">
|
||||
<button id="startBtn" class="px-4 py-2 bg-emerald-500 rounded">Start Match</button>
|
||||
</div>
|
||||
<div class="relative h-40 rounded bg-slate-800 overflow-hidden">
|
||||
<div class="absolute inset-y-0 left-1/2 w-0.5 bg-slate-600"></div>
|
||||
<div id="puck" class="absolute top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-white shadow"></div>
|
||||
<div class="absolute inset-y-0 left-0 w-1 bg-red-500"></div>
|
||||
<div class="absolute inset-y-0 right-0 w-1 bg-blue-500"></div>
|
||||
</div>
|
||||
<p id="status" class="mt-4 text-lg"></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const room = "{{ room }}";
|
||||
document.getElementById("room").textContent = room;
|
||||
|
||||
const socket = io("/game");
|
||||
socket.emit("join_room", { room, player_id: "host", side: null });
|
||||
|
||||
document.getElementById("startBtn").onclick = () => {
|
||||
socket.emit("start", { room });
|
||||
document.getElementById("status").textContent = "Fight!";
|
||||
};
|
||||
|
||||
socket.on("state", ({ puck }) => {
|
||||
const box = document.querySelector(".relative.h-40");
|
||||
const puckEl = document.getElementById("puck");
|
||||
const w = box.clientWidth;
|
||||
const x = (puck / 100) * (w/2 - 10); // map -100..100 to pixels
|
||||
puckEl.style.left = `calc(50% + ${x}px)`;
|
||||
});
|
||||
|
||||
socket.on("match_end", ({ winner }) => {
|
||||
document.getElementById("status").textContent = winner.toUpperCase() + " WINS!";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
76
templates/host_lobby.html
Normal file
76
templates/host_lobby.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white p-6">
|
||||
<h1 class="text-2xl font-bold mb-4">Lobby</h1>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h2 class="font-semibold mb-2">Waiting Players</h2>
|
||||
<ul id="waiting" class="space-y-2"></ul>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="font-semibold mb-2">Create Room</h2>
|
||||
<div class="flex gap-2 items-center mb-3">
|
||||
<input id="code" class="text-black p-2 rounded" placeholder="Room code (e.g., 4321)">
|
||||
<input id="left" class="text-black p-2 rounded" placeholder="Left PID">
|
||||
<input id="right" class="text-black p-2 rounded" placeholder="Right PID">
|
||||
<button id="create" class="bg-emerald-500 px-3 py-2 rounded">Assign</button>
|
||||
</div>
|
||||
<h2 class="font-semibold mb-2">Rooms</h2>
|
||||
<ul id="rooms" class="space-y-2"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const socket = io("/game");
|
||||
socket.emit("lobby_subscribe");
|
||||
|
||||
const waitingEl = document.getElementById("waiting");
|
||||
const roomsEl = document.getElementById("rooms");
|
||||
|
||||
socket.on("waiting_list", ({players}) => {
|
||||
waitingEl.innerHTML = "";
|
||||
players.forEach(pid => {
|
||||
const li = document.createElement("li");
|
||||
li.className = "bg-slate-800 rounded px-3 py-2 cursor-pointer";
|
||||
li.textContent = pid;
|
||||
li.onclick = () => {
|
||||
const left = document.getElementById("left");
|
||||
const right = document.getElementById("right");
|
||||
if(!left.value) left.value = pid;
|
||||
else if(!right.value) right.value = pid;
|
||||
else left.value = pid;
|
||||
};
|
||||
waitingEl.appendChild(li);
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("rooms_list", ({rooms}) => {
|
||||
roomsEl.innerHTML = "";
|
||||
Object.entries(rooms).forEach(([code, info]) => {
|
||||
const li = document.createElement("li");
|
||||
li.className = "bg-slate-800 rounded px-3 py-2 flex justify-between";
|
||||
li.innerHTML = `<div><b>${code}</b> — L:${info.left ?? "-"} R:${info.right ?? "-"} ${info.running ? "🟢" : "⚪"}</div>
|
||||
<a class="underline" href="/host/${code}" target="_blank">Open Host</a>`;
|
||||
roomsEl.appendChild(li);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("create").onclick = () => {
|
||||
const code = document.getElementById("code").value.trim() || (Math.random()*1e4|0).toString().padStart(4,"0");
|
||||
const left = document.getElementById("left").value.trim();
|
||||
const right = document.getElementById("right").value.trim();
|
||||
if(!left || !right) return alert("Pick two players.");
|
||||
socket.emit("assign_to_room", { code, left, right });
|
||||
document.getElementById("code").value = "";
|
||||
document.getElementById("left").value = "";
|
||||
document.getElementById("right").value = "";
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
69
templates/host_responses.html
Normal file
69
templates/host_responses.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Survey Responses</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-slate-950 text-white p-6">
|
||||
<h1 class="text-2xl font-bold mb-4">Survey Responses</h1>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead class="bg-slate-900">
|
||||
<tr>
|
||||
<th class="p-2 text-left">ID</th>
|
||||
<th class="p-2 text-left">PID</th>
|
||||
<th class="p-2 text-left">Name</th>
|
||||
<th class="p-2 text-left">Persona</th>
|
||||
<th class="p-2 text-left">Online Frequency</th>
|
||||
<th class="p-2 text-left">Fulfillment</th>
|
||||
<th class="p-2 text-left">Coupons Use</th>
|
||||
<th class="p-2 text-left">Pref</th>
|
||||
<th class="p-2 text-left">Matters</th>
|
||||
<th class="p-2 text-left">Barriers</th>
|
||||
<th class="p-2 text-left">Device</th>
|
||||
<th class="p-2 text-left">Email</th>
|
||||
<th class="p-2 text-left">Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
{% set a = r.answers %}
|
||||
<tr class="border-b border-slate-800">
|
||||
<td class="p-2">{{ r.id }}</td>
|
||||
<td class="p-2">{{ r.pid }}</td>
|
||||
<td class="p-2">{{ r.name }}</td>
|
||||
<td class="p-2">{{ r.persona }}</td>
|
||||
<td class="p-2">{{ a.shop_online_freq or '-' }}</td>
|
||||
<td class="p-2">{{ a.fulfillment_preference or '-' }}</td>
|
||||
<td class="p-2">{{ a.digital_coupons_use or '-' }}</td>
|
||||
<td class="p-2">{{ a.coupons_preference or '-' }}</td>
|
||||
<td class="p-2">{{ (a.what_matters or [])|join(', ') }}</td>
|
||||
<td class="p-2">{{ (a.barriers or [])|join(', ') }}</td>
|
||||
<td class="p-2">{{ a.device or '-' }}</td>
|
||||
<td class="p-2">
|
||||
{% if a.email_consent and a.email %}{{ a.email }}{% else %}-{% endif %}
|
||||
</td>
|
||||
<td class="p-2 whitespace-nowrap">{{ r.created_at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% set pages = (total // per_page) + (1 if total % per_page else 0) %}
|
||||
<div class="mt-4 flex gap-2">
|
||||
{% if page > 1 %}
|
||||
<a class="px-3 py-1 bg-slate-900 rounded" href="?page={{ page - 1 }}">Prev</a>
|
||||
{% endif %}
|
||||
<span class="opacity-75">Page {{ page }} / {{ pages }}</span>
|
||||
{% if page < pages %}
|
||||
<a class="px-3 py-1 bg-slate-900 rounded" href="?page={{ page + 1 }}">Next</a>
|
||||
{% endif %}
|
||||
<a class="ml-auto px-3 py-1 bg-emerald-600 rounded font-semibold text-black" href="/host/responses.csv">Export CSV</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
53
templates/host_room.html
Normal file
53
templates/host_room.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white p-6">
|
||||
<h1 class="text-2xl font-bold mb-3">Room <span id="code">{{ code }}</span></h1>
|
||||
<div id="info" class="opacity-80 mb-3"></div>
|
||||
<button id="start" class="bg-emerald-500 px-4 py-2 rounded mb-4">Start Match</button>
|
||||
|
||||
<div class="relative h-40 rounded bg-slate-800 overflow-hidden">
|
||||
<div class="absolute inset-y-0 left-1/2 w-0.5 bg-slate-600"></div>
|
||||
<div id="puck" class="absolute top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-white shadow"></div>
|
||||
<div class="absolute inset-y-0 left-0 w-1 bg-red-500"></div>
|
||||
<div class="absolute inset-y-0 right-0 w-1 bg-blue-500"></div>
|
||||
</div>
|
||||
<p id="status" class="mt-3 text-lg"></p>
|
||||
|
||||
<script>
|
||||
const code = "{{ code }}";
|
||||
const socket = io("/game", { transports: ["websocket"] });
|
||||
socket.emit("host_subscribe", { code });
|
||||
|
||||
document.getElementById("start").onclick = () => socket.emit("start_match", { code });
|
||||
|
||||
socket.on("room_state", ({exists, left, right, running, puck}) => {
|
||||
if(!exists) return;
|
||||
document.getElementById("info").textContent = `Left: ${left ?? "-"} | Right: ${right ?? "-"} | ${running ? "LIVE" : "Idle"}`;
|
||||
if(typeof puck === "number") updatePuck(puck);
|
||||
});
|
||||
|
||||
socket.on("state", ({code: c, puck}) => {
|
||||
if(c !== code) return;
|
||||
updatePuck(puck);
|
||||
});
|
||||
socket.on("match_end", ({code: c, winner}) => {
|
||||
if(c !== code) return;
|
||||
document.getElementById("status").textContent = winner.toUpperCase() + " WINS!";
|
||||
});
|
||||
|
||||
function updatePuck(p) {
|
||||
const box = document.querySelector(".relative.h-40");
|
||||
const puckEl = document.getElementById("puck");
|
||||
const w = box.clientWidth;
|
||||
const x = (p / 100) * (w/2 - 10);
|
||||
puckEl.style.left = `calc(50% + ${x}px)`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,46 +1,94 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Benjamin Mosley{% endblock %}
|
||||
{% block title %}Home — Benjamin Mosley{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<section class="text-center py-16 bg-gradient-to-br from-red-800 to-orange-400 text-white">
|
||||
<h1 class="mb-3 text-5xl font-extrabold mb-4">Benjamin Mosley's Personal Portfolio</h1>
|
||||
<img src="{{ url_for('static', filename='Ben.png') }}" alt="Benjamin Mosley"
|
||||
class="h-32 w-32 mx-auto shadow-lg border-4 border-white mb-6">
|
||||
<p class="text-xlmb-6">I am Benjamin Mosley, a Computer Information Systems student with
|
||||
a passion for building, creating, and learning.</p>
|
||||
<!-- Hero -->
|
||||
<section class="relative overflow-hidden">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-orange-500/20 via-rose-500/10 to-fuchsia-500/10 pointer-events-none"></div>
|
||||
<div class="container-page py-16 sm:py-24 relative">
|
||||
<div class="grid lg:grid-cols-[1.1fr_0.9fr] items-center gap-10">
|
||||
<div>
|
||||
<h1 class="heading-hero">
|
||||
Builder. Operator. <span class="text-accent">Problem-solver.</span>
|
||||
</h1>
|
||||
<p class="mt-4 subtle max-w-2xl text-lg">
|
||||
I’m Ben — CIS student, full-stack web dev, and infrastructure tinkerer.
|
||||
I ship useful, secure, and approachable tools for real people.
|
||||
</p>
|
||||
<div class="mt-8 flex flex-wrap gap-3">
|
||||
<a href="/projects" class="btn btn-primary">See Projects</a>
|
||||
<a href="/resume" class="btn btn-ghost">View Resume</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="justify-self-center">
|
||||
<img src="{{ url_for('static', filename='Headshot.jpg') }}"
|
||||
alt="Portrait of Benjamin Mosley"
|
||||
class="h-40 w-40 sm:h-52 sm:w-52 rounded-2xl border border-[rgb(var(--border))] shadow-glass object-cover">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid grid-cols-1 md:grid-cols-3 gap-6 p-10 text-white">
|
||||
<div class="bg-gradient-to-br from-red-600 to-orange-300 p-6 rounded shadow text-center">
|
||||
<h3 class="text-lg font-bold mb-2">Contact Me</h3>
|
||||
<p>Have any questions? Reach out to me here</p>
|
||||
<!-- About -->
|
||||
<section class="container-page py-16">
|
||||
<div class="grid md:grid-cols-2 gap-10">
|
||||
<article class="card">
|
||||
<h2 class="text-2xl font-semibold">Who Am I?</h2>
|
||||
<p class="mt-3">
|
||||
I’m a developer and tech enthusiast focused on building useful, secure, and accessible digital experiences.
|
||||
From full-stack web apps to clean UI systems and network infrastructure — I love the whole stack.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<br>
|
||||
<article class="card">
|
||||
<h2 class="text-2xl font-semibold">What I’m About</h2>
|
||||
<p class="mt-3">
|
||||
Technology should empower people, not confuse them. I care about clarity, security, and maintainability —
|
||||
and I’m always learning: cybersecurity, backend frameworks, automation, and DevOps.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<a href="/contact" class="bg-blue-400 inline-block text-white font-semibold px-4 py-2 rounded shadpw hpver:bg-gray-100 transition">Learn More</a>
|
||||
</div>
|
||||
<!-- Skills -->
|
||||
<section class="container-page pb-4">
|
||||
<h2 class="text-2xl font-semibold">Skills & Tools</h2>
|
||||
<ul class="mt-4 grid sm:grid-cols-2 lg:grid-cols-3 gap-3 list-disc pl-5">
|
||||
<li>Python & Flask</li>
|
||||
<li>HTML, CSS, JavaScript</li>
|
||||
<li>SQL (MariaDB/MySQL, SQLite)</li>
|
||||
<li>Linux sysadmin</li>
|
||||
<li>Networking & Security (WireGuard, Samba, DNS)</li>
|
||||
<li>Git & deployment</li>
|
||||
<li>API integrations</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div class="bg-gradient-to-br from-red-600 to-orange-300 p-6 rounded shadow text-center">
|
||||
<h3 class="text-lg font-bold mb-2">My Projects</h3>
|
||||
<p>Check out my latest projects!</p>
|
||||
<!-- CTA Cards -->
|
||||
<section class="container-page py-12">
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<article class="card text-center">
|
||||
<h3 class="font-semibold text-lg">Contact Me</h3>
|
||||
<p class="subtle mt-2">Have questions or an idea? Let’s talk.</p>
|
||||
<a href="/contact" class="btn btn-primary mt-4">Reach Out</a>
|
||||
</article>
|
||||
|
||||
<br>
|
||||
|
||||
<a href="/projects" class="bg-blue-400 inline-block text-white font-semibold px-4 py-2 rounded shadpw hpver:bg-gray-100 transition">Learn More</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-br from-red-600 to-orange-300 p-6 rounded shadow text-center">
|
||||
<h3 class="text-lg font-bold mb-2">My Blog</h3>
|
||||
<p>Read my latest Blog-Posts!</p>
|
||||
|
||||
<br>
|
||||
|
||||
<a href="/blog" class="bg-blue-400 inline-block text-white font-semibold px-4 py-2 rounded shadpw hpver:bg-gray-100 transition">Learn More</a>
|
||||
</div>
|
||||
<article class="card text-center">
|
||||
<h3 class="font-semibold text-lg">My Projects</h3>
|
||||
<p class="subtle mt-2">Explore things I’ve shipped and shipped-in-progress.</p>
|
||||
<a href="/projects" class="btn btn-primary mt-4">Browse Projects</a>
|
||||
</article>
|
||||
|
||||
<article class="card text-center">
|
||||
<h3 class="font-semibold text-lg">My Blog</h3>
|
||||
<p class="subtle mt-2">Notes on building, learning, and systems.</p>
|
||||
<a href="/blog" class="btn btn-primary mt-4">Read Posts</a>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,66 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
||||
<title>Login</title>
|
||||
<style>
|
||||
<head>
|
||||
<link rel="icon" href="{{ url_for('static', filename='BM.png') }}">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
||||
<title>Login</title>
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(to right, #667eea, #764ba2);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.login-container {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
.logo-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: #ddd;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto 1rem;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
background: linear-gradient(to right, #667eea, #764ba2);
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 100px; /* Adjust size as needed */
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0 auto 1rem; /* Centers the logo */
|
||||
}
|
||||
.login-container {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<img src="{{ url_for('static', filename='Netdeploy.jpg') }}" alt="Logo" class="logo">
|
||||
h2 {
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.logo-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: #ddd;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0 auto 1rem;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
<h2 class="mb-3">Login</h2>
|
||||
<form action="/login" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
.logo {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
input[type="username"],
|
||||
input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
input[type="email"]:focus,
|
||||
input[type="password"]:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #4c60c4;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 1rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="logo-placeholder">
|
||||
<img src="{{ url_for('static', filename='BM.png') }}" alt="Logo" class="logo">
|
||||
</div>
|
||||
<h2>Login</h2>
|
||||
<form method="POST" action="{{ url_for('login') }}">
|
||||
<label for="username">Username:</label>
|
||||
<input type="username" id="username" name="username" required>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,52 +4,54 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
|
||||
<h1 class="text-5xl font-extrabold mb-2">Create a New Blog Post</h1>
|
||||
<p class="text-lg">Share your thoughts, tutorials, or experiences with the world.</p>
|
||||
</section>
|
||||
|
||||
<!-- Form -->
|
||||
<section class="py-16 bg-black text-white">
|
||||
<div class="max-w-3xl mx-auto px-6">
|
||||
<div class="max-w-3xl mx-auto px-6 max-h-[85vh] overflow-y-auto">
|
||||
<form action="{{ url_for('new_blog') }}" method="post" enctype="multipart/form-data" class="space-y-6">
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<div>
|
||||
<label for="title" class="block font-semibold mb-1">Post Title</label>
|
||||
<input type="text" id="title" name="title" required
|
||||
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Content -->
|
||||
<div>
|
||||
<label for="content" class="block font-semibold mb-1">Post Content</label>
|
||||
<textarea id="content" name="content" required rows="6"
|
||||
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none resize-none"></textarea>
|
||||
<textarea id="content" name="content" required
|
||||
class="text-black w-full border border-gray-300 rounded px-4 py-2 leading-relaxed text-base focus:ring-2 focus:ring-orange-400 focus:outline-none resize-none overflow-hidden"
|
||||
rows="1"></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Category -->
|
||||
<div>
|
||||
<label for="category" class="block font-semibold mb-1">Category</label>
|
||||
<input type="text" id="category" name="category" placeholder="What's This Post About?"
|
||||
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Tags -->
|
||||
<div>
|
||||
<label for="tags" class="block font-semibold mb-1">Tags (comma-separated)</label>
|
||||
<input type="text" id="tags" name="tags" placeholder="e.g., Flask, SQLite, API"
|
||||
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Images -->
|
||||
<div>
|
||||
<label for="images" class="block font-semibold mb-1">Upload Images</label>
|
||||
<input type="file" name="images" id="images" multiple
|
||||
class="text-black w-full border border-gray-300 rounded px-3 py-2 text-gray-700 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:bg-orange-600 file:text-white hover:file:bg-orange-700">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Submit -->
|
||||
<div class="pt-4">
|
||||
<button type="submit"
|
||||
class="bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">
|
||||
@@ -58,6 +60,7 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Back link -->
|
||||
<div class="mt-8 text-center">
|
||||
<a href="{{ url_for('blog') }}" class="text-blue-600 hover:underline">← Back to Blog</a>
|
||||
</div>
|
||||
@@ -68,11 +71,16 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const textarea = document.getElementById('content');
|
||||
textarea.addEventListener('input', function () {
|
||||
this.style.height = 'auto';
|
||||
this.style.height = this.scrollHeight + 'px';
|
||||
});
|
||||
if (textarea) {
|
||||
const resize = () => {
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = textarea.scrollHeight + 'px';
|
||||
};
|
||||
resize(); // Initial sizing
|
||||
textarea.addEventListener('input', resize);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -4,52 +4,54 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
|
||||
<h1 class="text-5xl font-extrabold mb-2">Create a New Project Post</h1>
|
||||
<p class="text-lg">Share your projects with the world.</p>
|
||||
</section>
|
||||
|
||||
<!-- Form Section -->
|
||||
<section class="py-16 bg-black text-white">
|
||||
<div class="max-w-3xl mx-auto px-6">
|
||||
<div class="max-w-3xl mx-auto px-6 max-h-[85vh] overflow-y-auto">
|
||||
<form action="{{ url_for('new_project') }}" method="post" enctype="multipart/form-data" class="space-y-6">
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<div>
|
||||
<label for="title" class="block font-semibold mb-1">Post Title</label>
|
||||
<input type="text" id="title" name="title" required
|
||||
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
class="text-black bg-white w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Content -->
|
||||
<div>
|
||||
<label for="content" class="block font-semibold mb-1">Post Content</label>
|
||||
<textarea id="content" name="content" required rows="6"
|
||||
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none resize-none"></textarea>
|
||||
<textarea id="content" name="content" required
|
||||
class="text-black bg-white w-full border border-gray-300 rounded px-4 py-2 leading-relaxed text-base focus:ring-2 focus:ring-orange-400 focus:outline-none resize-none overflow-hidden"
|
||||
rows="1"></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Category -->
|
||||
<div>
|
||||
<label for="category" class="block font-semibold mb-1">Category</label>
|
||||
<input type="text" id="category" name="category" placeholder="What's This Post About?"
|
||||
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
class="text-black bg-white w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Tags -->
|
||||
<div>
|
||||
<label for="tags" class="block font-semibold mb-1">Tags (comma-separated)</label>
|
||||
<input type="text" id="tags" name="tags" placeholder="e.g., Flask, SQLite, API"
|
||||
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
class="text-black bg-white w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Image Upload -->
|
||||
<div>
|
||||
<label for="images" class="block font-semibold mb-1">Upload Images</label>
|
||||
<input type="file" name="images" id="images" multiple
|
||||
class="text-black w-full border border-gray-300 rounded px-3 py-2 text-gray-700 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:bg-orange-600 file:text-white hover:file:bg-orange-700">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Submit -->
|
||||
<div class="pt-4">
|
||||
<button type="submit"
|
||||
class="bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">
|
||||
@@ -64,15 +66,20 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Textarea autoresize -->
|
||||
<!-- Textarea Auto-Expand Script -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const textarea = document.getElementById('content');
|
||||
textarea.addEventListener('input', function () {
|
||||
this.style.height = 'auto';
|
||||
this.style.height = this.scrollHeight + 'px';
|
||||
});
|
||||
if (textarea) {
|
||||
const resize = () => {
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = textarea.scrollHeight + 'px';
|
||||
};
|
||||
resize(); // Set initial height
|
||||
textarea.addEventListener('input', resize);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
1
templates/node_modules/.bin/cssesc
generated
vendored
Symbolic link
1
templates/node_modules/.bin/cssesc
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../cssesc/bin/cssesc
|
||||
57
templates/node_modules/.package-lock.json
generated
vendored
Normal file
57
templates/node_modules/.package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "templates",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
||||
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
21
templates/node_modules/@tailwindcss/typography/LICENSE
generated
vendored
Normal file
21
templates/node_modules/@tailwindcss/typography/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Tailwind Labs, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
443
templates/node_modules/@tailwindcss/typography/README.md
generated
vendored
Normal file
443
templates/node_modules/@tailwindcss/typography/README.md
generated
vendored
Normal file
@@ -0,0 +1,443 @@
|
||||
<p>
|
||||
<a href="https://tailwindcss.com/docs/typography-plugin" target="_blank">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss-typography/HEAD/.github/logo-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss-typography/HEAD/.github/logo-light.svg">
|
||||
<img alt="Tailwind CSS Typography" src="https://raw.githubusercontent.com/tailwindlabs/tailwindcss-typography/HEAD/.github/logo-light.svg" width="450" height="70" style="max-width: 100%;">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
The official Tailwind CSS Typography plugin provides a set of `prose` classes you can use to add beautiful typographic defaults to any vanilla HTML you don’t control, like HTML rendered from Markdown, or pulled from a CMS.
|
||||
|
||||
```html
|
||||
<article class="prose lg:prose-xl">{{ markdown }}</article>
|
||||
```
|
||||
|
||||
To see what it looks like in action, check out our [live demo](https://play.tailwindcss.com/uj1vGACRJA?layout=preview) on Tailwind Play.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Install the plugin from npm:
|
||||
|
||||
```shell
|
||||
npm install -D @tailwindcss/typography
|
||||
```
|
||||
|
||||
Then add the plugin to your main `style.css` file:
|
||||
|
||||
```diff
|
||||
@import "tailwindcss";
|
||||
+ @plugin "@tailwindcss/typography";
|
||||
```
|
||||
|
||||
If you are still using **Tailwind CSS v3**, add the plugin to your `tailwind.config.js` file:
|
||||
|
||||
```js
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
theme: {
|
||||
// ...
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'),
|
||||
// ...
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Basic usage
|
||||
|
||||
Now you can use the `prose` classes to add sensible typography styles to any vanilla HTML:
|
||||
|
||||
```html
|
||||
<article class="prose lg:prose-xl">
|
||||
<h1>Garlic bread with cheese: What the science tells us</h1>
|
||||
<p>
|
||||
For years parents have espoused the health benefits of eating garlic bread with cheese to their
|
||||
children, with the food earning such an iconic status in our culture that kids will often dress
|
||||
up as warm, cheesy loaf for Halloween.
|
||||
</p>
|
||||
<p>
|
||||
But a recent study shows that the celebrated appetizer may be linked to a series of rabies cases
|
||||
springing up around the country.
|
||||
</p>
|
||||
<!-- ... -->
|
||||
</article>
|
||||
```
|
||||
|
||||
### Choosing a gray scale
|
||||
|
||||
This plugin includes a modifier class for each of the five gray scales Tailwind includes by default so you can easily style your content to match the grays you're using in your project.
|
||||
|
||||
```html
|
||||
<article class="prose prose-slate">{{ markdown }}</article>
|
||||
```
|
||||
|
||||
Here are the classes that are generated using a totally default Tailwind CSS v2.0 build:
|
||||
|
||||
| Class | Gray scale |
|
||||
| ------------------------ | ---------- |
|
||||
| `prose-gray` _(default)_ | Gray |
|
||||
| `prose-slate` | Slate |
|
||||
| `prose-zinc` | Zinc |
|
||||
| `prose-neutral` | Neutral |
|
||||
| `prose-stone` | Stone |
|
||||
|
||||
Modifier classes are designed to be used with the [multi-class modifier pattern](http://nicolasgallagher.com/about-html-semantics-front-end-architecture/#component-modifiers) and must be used in conjunction with the base `prose` class.
|
||||
|
||||
> [!NOTE]
|
||||
> Always include the `prose` class when adding a gray scale modifier
|
||||
|
||||
```html
|
||||
<article class="prose prose-stone">{{ markdown }}</article>
|
||||
```
|
||||
|
||||
To learn about creating your own color themes, read the [adding custom color themes](#adding-custom-color-themes) documentation.
|
||||
|
||||
### Applying a type scale
|
||||
|
||||
Size modifiers allow you to adjust the overall size of your typography for different contexts.
|
||||
|
||||
```html
|
||||
<article class="prose prose-xl">{{ markdown }}</article>
|
||||
```
|
||||
|
||||
Five different typography sizes are included out of the box:
|
||||
|
||||
| Class | Body font size |
|
||||
| ------------------------ | ----------------- |
|
||||
| `prose-sm` | 0.875rem _(14px)_ |
|
||||
| `prose-base` _(default)_ | 1rem _(16px)_ |
|
||||
| `prose-lg` | 1.125rem _(18px)_ |
|
||||
| `prose-xl` | 1.25rem _(20px)_ |
|
||||
| `prose-2xl` | 1.5rem _(24px)_ |
|
||||
|
||||
These can be used in combination with Tailwind's [breakpoint modifiers](https://tailwindcss.com/docs/responsive-design) to change the overall font size of a piece of content at different viewport sizes:
|
||||
|
||||
```html
|
||||
<article class="prose md:prose-lg lg:prose-xl">{{ markdown }}</article>
|
||||
```
|
||||
|
||||
Everything about the provided size modifiers has been hand-tuned by professional designers to look as beautiful as possible, including the relationships between font sizes, heading spacing, code block padding, and more.
|
||||
|
||||
Size modifiers are designed to be used with the [multi-class modifier pattern](http://nicolasgallagher.com/about-html-semantics-front-end-architecture/#component-modifiers) and must be used in conjunction with the base `prose` class.
|
||||
|
||||
> [!NOTE]
|
||||
> Always include the `prose` class when adding a size modifier
|
||||
|
||||
```html
|
||||
<article class="prose prose-lg">{{ markdown }}</article>
|
||||
```
|
||||
|
||||
To learn about customizing the included type scales, read the documentation on [customizing the CSS](#customizing-the-css).
|
||||
|
||||
### Adapting to dark mode
|
||||
|
||||
Each default color theme includes a hand-designed dark mode version that you can trigger by adding the `prose-invert` class:
|
||||
|
||||
```html
|
||||
<article class="prose dark:prose-invert">{{ markdown }}</article>
|
||||
```
|
||||
|
||||
To learn about creating your own color themes, read the [adding custom color themes](#adding-custom-color-themes) documentation.
|
||||
|
||||
### Element modifiers
|
||||
|
||||
Use element modifiers to customize the style of individual elements in your content directly in your HTML:
|
||||
|
||||
```html
|
||||
<article class="prose prose-img:rounded-xl prose-headings:underline prose-a:text-blue-600">
|
||||
{{ markdown }}
|
||||
</article>
|
||||
```
|
||||
|
||||
This makes it easy to do things like style links to match your brand, add a border radius to images, and tons more.
|
||||
|
||||
Here's a complete list of available element modifiers:
|
||||
|
||||
| Modifier | Target |
|
||||
| ---------------------------- | ---------------------------- |
|
||||
| `prose-headings:{utility}` | `h1`, `h2`, `h3`, `h4`, `th` |
|
||||
| `prose-lead:{utility}` | `[class~="lead"]` |
|
||||
| `prose-h1:{utility}` | `h1` |
|
||||
| `prose-h2:{utility}` | `h2` |
|
||||
| `prose-h3:{utility}` | `h3` |
|
||||
| `prose-h4:{utility}` | `h4` |
|
||||
| `prose-p:{utility}` | `p` |
|
||||
| `prose-a:{utility}` | `a` |
|
||||
| `prose-blockquote:{utility}` | `blockquote` |
|
||||
| `prose-figure:{utility}` | `figure` |
|
||||
| `prose-figcaption:{utility}` | `figcaption` |
|
||||
| `prose-strong:{utility}` | `strong` |
|
||||
| `prose-em:{utility}` | `em` |
|
||||
| `prose-kbd:{utility}` | `kbd` |
|
||||
| `prose-code:{utility}` | `code` |
|
||||
| `prose-pre:{utility}` | `pre` |
|
||||
| `prose-ol:{utility}` | `ol` |
|
||||
| `prose-ul:{utility}` | `ul` |
|
||||
| `prose-li:{utility}` | `li` |
|
||||
| `prose-dl:{utility}` | `dl` |
|
||||
| `prose-dt:{utility}` | `dt` |
|
||||
| `prose-dd:{utiddty}` | `dd` |
|
||||
| `prose-table:{utility}` | `table` |
|
||||
| `prose-thead:{utility}` | `thead` |
|
||||
| `prose-tr:{utility}` | `tr` |
|
||||
| `prose-th:{utility}` | `th` |
|
||||
| `prose-td:{utility}` | `td` |
|
||||
| `prose-img:{utility}` | `img` |
|
||||
| `prose-picture:{utility}` | `picture` |
|
||||
| `prose-video:{utility}` | `video` |
|
||||
| `prose-hr:{utility}` | `hr` |
|
||||
|
||||
When stacking these modifiers with other modifiers like `hover`, you most likely want the other modifier to come last:
|
||||
|
||||
```html
|
||||
<article class="prose prose-a:text-blue-600 prose-a:hover:text-blue-500">{{ markdown }}</article>
|
||||
```
|
||||
|
||||
If you are still using in Tailwind CSS v3, the modifier order is the opposite:
|
||||
|
||||
```html
|
||||
<article class="prose prose-a:text-blue-600 hover:prose-a:text-blue-500">{{ markdown }}</article>
|
||||
```
|
||||
|
||||
Read the Tailwind CSS documentation on [stacked modifiers](https://tailwindcss.com/docs/hover-focus-and-other-states) to learn more.
|
||||
|
||||
### Overriding max-width
|
||||
|
||||
Each size modifier comes with a baked in `max-width` designed to keep the content as readable as possible. This isn't always what you want though, and sometimes you'll want the content to just fill the width of its container.
|
||||
|
||||
In those cases, all you need to do is add `max-w-none` to your content to override the embedded max-width:
|
||||
|
||||
```html
|
||||
<div class="grid grid-cols-4">
|
||||
<div class="col-span-1">
|
||||
<!-- ... -->
|
||||
</div>
|
||||
<div class="col-span-3">
|
||||
<article class="prose max-w-none">{{ markdown }}</article>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced topics
|
||||
|
||||
### Undoing typography styles
|
||||
|
||||
If you have a block of markup embedded in some content that shouldn't inherit the `prose` styles, use the `not-prose` class to sandbox it:
|
||||
|
||||
```html
|
||||
<article class="prose">
|
||||
<h1>My Heading</h1>
|
||||
<p>...</p>
|
||||
|
||||
<div class="not-prose">
|
||||
<!-- Some example or demo that needs to be prose-free -->
|
||||
</div>
|
||||
|
||||
<p>...</p>
|
||||
<!-- ... -->
|
||||
</article>
|
||||
```
|
||||
|
||||
Note that you can't nest new `prose` instances within a `not-prose` block at this time.
|
||||
|
||||
Even when using a prefix for your utilities `not-prose` should not have a prefix.
|
||||
|
||||
### Adding custom color themes
|
||||
|
||||
To customize the color theme beyond simple CSS overrides, add a `@utility` directive to your CSS file:
|
||||
|
||||
```css
|
||||
@utility prose-pink {
|
||||
--tw-prose-body: var(--color-pink-800);
|
||||
--tw-prose-headings: var(--color-pink-900);
|
||||
--tw-prose-lead: var(--color-pink-700);
|
||||
--tw-prose-links: var(--color-pink-900);
|
||||
--tw-prose-bold: var(--color-pink-900);
|
||||
--tw-prose-counters: var(--color-pink-600);
|
||||
--tw-prose-bullets: var(--color-pink-400);
|
||||
--tw-prose-hr: var(--color-pink-300);
|
||||
--tw-prose-quotes: var(--color-pink-900);
|
||||
--tw-prose-quote-borders: var(--color-pink-300);
|
||||
--tw-prose-captions: var(--color-pink-700);
|
||||
--tw-prose-code: var(--color-pink-900);
|
||||
--tw-prose-pre-code: var(--color-pink-100);
|
||||
--tw-prose-pre-bg: var(--color-pink-900);
|
||||
--tw-prose-th-borders: var(--color-pink-300);
|
||||
--tw-prose-td-borders: var(--color-pink-200);
|
||||
--tw-prose-invert-body: var(--color-pink-200);
|
||||
--tw-prose-invert-headings: var(--color-white);
|
||||
--tw-prose-invert-lead: var(--color-pink-300);
|
||||
--tw-prose-invert-links: var(--color-white);
|
||||
--tw-prose-invert-bold: var(--color-white);
|
||||
--tw-prose-invert-counters: var(--color-pink-400);
|
||||
--tw-prose-invert-bullets: var(--color-pink-600);
|
||||
--tw-prose-invert-hr: var(--color-pink-700);
|
||||
--tw-prose-invert-quotes: var(--color-pink-100);
|
||||
--tw-prose-invert-quote-borders: var(--color-pink-700);
|
||||
--tw-prose-invert-captions: var(--color-pink-400);
|
||||
--tw-prose-invert-code: var(--color-white);
|
||||
--tw-prose-invert-pre-code: var(--color-pink-300);
|
||||
--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);
|
||||
--tw-prose-invert-th-borders: var(--color-pink-600);
|
||||
--tw-prose-invert-td-borders: var(--color-pink-700);
|
||||
}
|
||||
```
|
||||
|
||||
For Tailwind v3, update the `typography` section in the JavaScript config file and provide your colors under the `css` key:
|
||||
|
||||
```js {{ filename: 'tailwind.config.js' }}
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
typography: () => ({
|
||||
pink: {
|
||||
css: {
|
||||
'--tw-prose-body': 'var(--color-pink-800)',
|
||||
'--tw-prose-headings': 'var(--color-pink-900)',
|
||||
'--tw-prose-lead': 'var(--color-pink-700)',
|
||||
'--tw-prose-links': 'var(--color-pink-900)',
|
||||
'--tw-prose-bold': 'var(--color-pink-900)',
|
||||
'--tw-prose-counters': 'var(--color-pink-600)',
|
||||
'--tw-prose-bullets': 'var(--color-pink-400)',
|
||||
'--tw-prose-hr': 'var(--color-pink-300)',
|
||||
'--tw-prose-quotes': 'var(--color-pink-900)',
|
||||
'--tw-prose-quote-borders': 'var(--color-pink-300)',
|
||||
'--tw-prose-captions': 'var(--color-pink-700)',
|
||||
'--tw-prose-code': 'var(--color-pink-900)',
|
||||
'--tw-prose-pre-code': 'var(--color-pink-100)',
|
||||
'--tw-prose-pre-bg': 'var(--color-pink-900)',
|
||||
'--tw-prose-th-borders': 'var(--color-pink-300)',
|
||||
'--tw-prose-td-borders': 'var(--color-pink-200)',
|
||||
'--tw-prose-invert-body': 'var(--color-pink-200)',
|
||||
'--tw-prose-invert-headings': 'var(--color-white)',
|
||||
'--tw-prose-invert-lead': 'var(--color-pink-300)',
|
||||
'--tw-prose-invert-links': 'var(--color-white)',
|
||||
'--tw-prose-invert-bold': 'var(--color-white)',
|
||||
'--tw-prose-invert-counters': 'var(--color-pink-400)',
|
||||
'--tw-prose-invert-bullets': 'var(--color-pink-600)',
|
||||
'--tw-prose-invert-hr': 'var(--color-pink-700)',
|
||||
'--tw-prose-invert-quotes': 'var(--color-pink-100)',
|
||||
'--tw-prose-invert-quote-borders': 'var(--color-pink-700)',
|
||||
'--tw-prose-invert-captions': 'var(--color-pink-400)',
|
||||
'--tw-prose-invert-code': 'var(--color-white)',
|
||||
'--tw-prose-invert-pre-code': 'var(--color-pink-300)',
|
||||
'--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
|
||||
'--tw-prose-invert-th-borders': 'var(--color-pink-600)',
|
||||
'--tw-prose-invert-td-borders': 'var(--color-pink-700)',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
See our internal [style definitions](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/src/styles.js) for some more examples.
|
||||
|
||||
### Changing the default class name
|
||||
|
||||
If you need to use a class name other than `prose` for any reason, you can do so using the `className` option when registering the plugin:
|
||||
|
||||
```css
|
||||
@import 'tailwindcss';
|
||||
@plugin "@tailwindcss/typography" {
|
||||
classname: wysiwyg;
|
||||
}
|
||||
```
|
||||
|
||||
Now every instance of `prose` in the default class names will be replaced by your custom class name:
|
||||
|
||||
```html
|
||||
<article class="wysiwyg wysiwyg-slate lg:wysiwyg-xl">
|
||||
<h1>My Heading</h1>
|
||||
<p>...</p>
|
||||
|
||||
<div class="not-wysiwyg">
|
||||
<!-- Some example or demo that needs to be prose-free -->
|
||||
</div>
|
||||
|
||||
<p>...</p>
|
||||
<!-- ... -->
|
||||
</article>
|
||||
```
|
||||
|
||||
### Customizing the CSS
|
||||
|
||||
If you want to customize the raw CSS generated by this plugin, you can use the JavaScript based theme API. To do that, use the `@config` directive:
|
||||
|
||||
```diff
|
||||
@import "tailwindcss";
|
||||
@plugin "@tailwindcss/typography";
|
||||
+ @config "./tailwind.config.js";
|
||||
```
|
||||
|
||||
You can then create your own config by adding a new `tailwind.config.js` file with the `typography` section and providing your styles under the `css` key:
|
||||
|
||||
```js {{ filename: 'tailwind.config.js' }}
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
color: '#333',
|
||||
a: {
|
||||
color: '#3182ce',
|
||||
'&:hover': {
|
||||
color: '#2c5282',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Like with all theme customizations in Tailwind, you can use CSS variables if you need access to access your theme configuration:
|
||||
|
||||
```js {{ filename: 'tailwind.config.js' }}
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
color: 'var(--color-gray-800)',
|
||||
// ...
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Customizations should be applied to a specific modifier like `DEFAULT` or `xl`, and must be added under the `css` property. Customizations are authored in the same [CSS-in-JS syntax](https://v3.tailwindcss.com/docs/plugins#css-in-js-syntax) used to write Tailwind v3 plugins.
|
||||
|
||||
See [the default styles](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/src/styles.js) for this plugin for more in-depth examples of configuring each modifier.
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
|
||||
|
||||
[Discuss the Tailwind CSS Typography plugin on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions)
|
||||
|
||||
For casual chit-chat with others using the framework:
|
||||
|
||||
[Join the Tailwind CSS Discord Server](https://tailwindcss.com/discord)
|
||||
58
templates/node_modules/@tailwindcss/typography/package.json
generated
vendored
Normal file
58
templates/node_modules/@tailwindcss/typography/package.json
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@tailwindcss/typography",
|
||||
"version": "0.5.19",
|
||||
"description": "A Tailwind CSS plugin for automatically styling plain HTML content with beautiful typographic defaults.",
|
||||
"main": "src/index.js",
|
||||
"types": "src/index.d.ts",
|
||||
"files": [
|
||||
"src/*.js",
|
||||
"src/*.d.ts",
|
||||
"dist/"
|
||||
],
|
||||
"repository": "https://github.com/tailwindlabs/tailwindcss-typography",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 100,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"dev": "next dev demo",
|
||||
"build": "next build demo",
|
||||
"export": "next export demo",
|
||||
"start": "next start demo",
|
||||
"release-channel": "node ./scripts/release-channel.js",
|
||||
"release-notes": "node ./scripts/release-notes.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mdx-js/loader": "^1.0.19",
|
||||
"@mdx-js/mdx": "^1.6.6",
|
||||
"@next/mdx": "^8.1.0",
|
||||
"autoprefixer": "^10.2.1",
|
||||
"highlight.js": "^10.4.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-diff": "^27.3.1",
|
||||
"next": "^12.0.1",
|
||||
"postcss": "^8.2.3",
|
||||
"prettier": "^2.1.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"tailwindcss": "^3.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"jest": {
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/jest/customMatchers.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
9
templates/node_modules/@tailwindcss/typography/src/index.d.ts
generated
vendored
Normal file
9
templates/node_modules/@tailwindcss/typography/src/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
declare function plugin(options?: Partial<{ className: string; target: 'modern' | 'legacy' }>): {
|
||||
handler: () => void
|
||||
}
|
||||
|
||||
declare namespace plugin {
|
||||
const __isOptionsFunction: true
|
||||
}
|
||||
|
||||
export = plugin
|
||||
138
templates/node_modules/@tailwindcss/typography/src/index.js
generated
vendored
Normal file
138
templates/node_modules/@tailwindcss/typography/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
const plugin = require('tailwindcss/plugin')
|
||||
const styles = require('./styles')
|
||||
const { commonTrailingPseudos, isObject, isPlainObject, merge, castArray } = require('./utils')
|
||||
|
||||
const computed = {
|
||||
// Reserved for future "magic properties", for example:
|
||||
// bulletColor: (color) => ({ 'ul > li::before': { backgroundColor: color } }),
|
||||
}
|
||||
|
||||
function inWhere(selector, { className, modifier, prefix }) {
|
||||
let prefixedNot = prefix(`.not-${className}`).slice(1)
|
||||
let selectorPrefix = selector.startsWith('>')
|
||||
? `${modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`} `
|
||||
: ''
|
||||
|
||||
// Parse the selector, if every component ends in the same pseudo element(s) then move it to the end
|
||||
let [trailingPseudo, rebuiltSelector] = commonTrailingPseudos(selector)
|
||||
|
||||
if (trailingPseudo) {
|
||||
return `:where(${selectorPrefix}${rebuiltSelector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))${trailingPseudo}`
|
||||
}
|
||||
|
||||
return `:where(${selectorPrefix}${selector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))`
|
||||
}
|
||||
|
||||
function configToCss(config = {}, { target, className, modifier, prefix }) {
|
||||
function updateSelector(k, v) {
|
||||
if (target === 'legacy') {
|
||||
return [k, v]
|
||||
}
|
||||
|
||||
if (Array.isArray(v)) {
|
||||
return [k, v]
|
||||
}
|
||||
|
||||
if (isObject(v)) {
|
||||
let nested = Object.values(v).some(isObject)
|
||||
if (nested) {
|
||||
return [
|
||||
inWhere(k, { className, modifier, prefix }),
|
||||
v,
|
||||
Object.fromEntries(Object.entries(v).map(([k, v]) => updateSelector(k, v))),
|
||||
]
|
||||
}
|
||||
|
||||
return [inWhere(k, { className, modifier, prefix }), v]
|
||||
}
|
||||
|
||||
return [k, v]
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(
|
||||
merge(
|
||||
{},
|
||||
...Object.keys(config)
|
||||
.filter((key) => computed[key])
|
||||
.map((key) => computed[key](config[key])),
|
||||
...castArray(config.css || {})
|
||||
)
|
||||
).map(([k, v]) => updateSelector(k, v))
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = plugin.withOptions(
|
||||
({ className = 'prose', target = 'modern' } = {}) => {
|
||||
return function ({ addVariant, addComponents, theme, prefix }) {
|
||||
let modifiers = theme('typography')
|
||||
|
||||
let options = { className, prefix }
|
||||
|
||||
for (let [name, ...selectors] of [
|
||||
['headings', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'th'],
|
||||
['h1'],
|
||||
['h2'],
|
||||
['h3'],
|
||||
['h4'],
|
||||
['h5'],
|
||||
['h6'],
|
||||
['p'],
|
||||
['a'],
|
||||
['blockquote'],
|
||||
['figure'],
|
||||
['figcaption'],
|
||||
['strong'],
|
||||
['em'],
|
||||
['kbd'],
|
||||
['code'],
|
||||
['pre'],
|
||||
['ol'],
|
||||
['ul'],
|
||||
['li'],
|
||||
['dl'],
|
||||
['dt'],
|
||||
['dd'],
|
||||
['table'],
|
||||
['thead'],
|
||||
['tr'],
|
||||
['th'],
|
||||
['td'],
|
||||
['img'],
|
||||
['picture'],
|
||||
['video'],
|
||||
['hr'],
|
||||
['lead', '[class~="lead"]'],
|
||||
]) {
|
||||
selectors = selectors.length === 0 ? [name] : selectors
|
||||
|
||||
let selector =
|
||||
target === 'legacy' ? selectors.map((selector) => `& ${selector}`) : selectors.join(', ')
|
||||
|
||||
addVariant(
|
||||
`${className}-${name}`,
|
||||
target === 'legacy' ? selector : `& :is(${inWhere(selector, options)})`
|
||||
)
|
||||
}
|
||||
|
||||
addComponents(
|
||||
Object.keys(modifiers).map((modifier) => ({
|
||||
[modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`]: configToCss(
|
||||
modifiers[modifier],
|
||||
{
|
||||
target,
|
||||
className,
|
||||
modifier,
|
||||
prefix,
|
||||
}
|
||||
),
|
||||
}))
|
||||
)
|
||||
}
|
||||
},
|
||||
() => {
|
||||
return {
|
||||
theme: { typography: styles },
|
||||
}
|
||||
}
|
||||
)
|
||||
1442
templates/node_modules/@tailwindcss/typography/src/index.test.js
generated
vendored
Normal file
1442
templates/node_modules/@tailwindcss/typography/src/index.test.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1641
templates/node_modules/@tailwindcss/typography/src/styles.js
generated
vendored
Normal file
1641
templates/node_modules/@tailwindcss/typography/src/styles.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
120
templates/node_modules/@tailwindcss/typography/src/utils.js
generated
vendored
Normal file
120
templates/node_modules/@tailwindcss/typography/src/utils.js
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
const parser = require('postcss-selector-parser')
|
||||
const parseSelector = parser()
|
||||
|
||||
function isObject(value) {
|
||||
return typeof value === 'object' && value !== null
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (Object.prototype.toString.call(value) !== '[object Object]') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (Object.getPrototypeOf(value) === null) {
|
||||
return true
|
||||
}
|
||||
|
||||
let proto = value
|
||||
while (Object.getPrototypeOf(proto) !== null) {
|
||||
proto = Object.getPrototypeOf(proto)
|
||||
}
|
||||
|
||||
return Object.getPrototypeOf(value) === proto
|
||||
}
|
||||
|
||||
function merge(target, ...sources) {
|
||||
if (!sources.length) return target
|
||||
const source = sources.shift()
|
||||
|
||||
if (isObject(target) && isObject(source)) {
|
||||
for (const key in source) {
|
||||
if (Array.isArray(source[key])) {
|
||||
if (!target[key]) target[key] = []
|
||||
source[key].forEach((item, index) => {
|
||||
if (isPlainObject(item) && isPlainObject(target[key][index])) {
|
||||
target[key][index] = merge(target[key][index], item)
|
||||
} else {
|
||||
target[key][index] = item
|
||||
}
|
||||
})
|
||||
} else if (isPlainObject(source[key])) {
|
||||
if (!target[key]) target[key] = {}
|
||||
merge(target[key], source[key])
|
||||
} else {
|
||||
target[key] = source[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return merge(target, ...sources)
|
||||
}
|
||||
|
||||
function castArray(value) {
|
||||
return Array.isArray(value) ? value : [value]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isObject,
|
||||
isPlainObject,
|
||||
merge,
|
||||
castArray,
|
||||
isUsableColor(color, values) {
|
||||
return isPlainObject(values) && color !== 'gray' && values[600]
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
*/
|
||||
commonTrailingPseudos(selector) {
|
||||
let ast = parseSelector.astSync(selector)
|
||||
|
||||
/** @type {import('postcss-selector-parser').Pseudo[][]} */
|
||||
let matrix = []
|
||||
|
||||
// Put the pseudo elements in reverse order in a sparse, column-major 2D array
|
||||
for (let [i, sel] of ast.nodes.entries()) {
|
||||
for (const [j, child] of [...sel.nodes].reverse().entries()) {
|
||||
// We only care about pseudo elements
|
||||
if (child.type !== 'pseudo' || !child.value.startsWith('::')) {
|
||||
break
|
||||
}
|
||||
|
||||
matrix[j] = matrix[j] || []
|
||||
matrix[j][i] = child
|
||||
}
|
||||
}
|
||||
|
||||
let trailingPseudos = parser.selector()
|
||||
|
||||
// At this point the pseudo elements are in a column-major 2D array
|
||||
// This means each row contains one "column" of pseudo elements from each selector
|
||||
// We can compare all the pseudo elements in a row to see if they are the same
|
||||
for (const pseudos of matrix) {
|
||||
// It's a sparse 2D array so there are going to be holes in the rows
|
||||
// We skip those
|
||||
if (!pseudos) {
|
||||
continue
|
||||
}
|
||||
|
||||
let values = new Set(pseudos.map((p) => p.value))
|
||||
|
||||
// The pseudo elements are not the same
|
||||
if (values.size > 1) {
|
||||
break
|
||||
}
|
||||
|
||||
pseudos.forEach((pseudo) => pseudo.remove())
|
||||
trailingPseudos.prepend(pseudos[0])
|
||||
}
|
||||
|
||||
if (trailingPseudos.nodes.length) {
|
||||
return [trailingPseudos.toString(), ast.toString()]
|
||||
}
|
||||
|
||||
return [null, selector]
|
||||
},
|
||||
}
|
||||
20
templates/node_modules/cssesc/LICENSE-MIT.txt
generated
vendored
Normal file
20
templates/node_modules/cssesc/LICENSE-MIT.txt
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright Mathias Bynens <https://mathiasbynens.be/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
201
templates/node_modules/cssesc/README.md
generated
vendored
Normal file
201
templates/node_modules/cssesc/README.md
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
# cssesc [](https://travis-ci.org/mathiasbynens/cssesc) [](https://codecov.io/gh/mathiasbynens/cssesc)
|
||||
|
||||
A JavaScript library for escaping CSS strings and identifiers while generating the shortest possible ASCII-only output.
|
||||
|
||||
This is a JavaScript library for [escaping text for use in CSS strings or identifiers](https://mathiasbynens.be/notes/css-escapes) while generating the shortest possible valid ASCII-only output. [Here’s an online demo.](https://mothereff.in/css-escapes)
|
||||
|
||||
[A polyfill for the CSSOM `CSS.escape()` method is available in a separate repository.](https://mths.be/cssescape) (In comparison, _cssesc_ is much more powerful.)
|
||||
|
||||
Feel free to fork if you see possible improvements!
|
||||
|
||||
## Installation
|
||||
|
||||
Via [npm](https://www.npmjs.com/):
|
||||
|
||||
```bash
|
||||
npm install cssesc
|
||||
```
|
||||
|
||||
In a browser:
|
||||
|
||||
```html
|
||||
<script src="cssesc.js"></script>
|
||||
```
|
||||
|
||||
In [Node.js](https://nodejs.org/):
|
||||
|
||||
```js
|
||||
const cssesc = require('cssesc');
|
||||
```
|
||||
|
||||
In Ruby using [the `ruby-cssesc` wrapper gem](https://github.com/borodean/ruby-cssesc):
|
||||
|
||||
```bash
|
||||
gem install ruby-cssesc
|
||||
```
|
||||
|
||||
```ruby
|
||||
require 'ruby-cssesc'
|
||||
CSSEsc.escape('I ♥ Ruby', is_identifier: true)
|
||||
```
|
||||
|
||||
In Sass using [`sassy-escape`](https://github.com/borodean/sassy-escape):
|
||||
|
||||
```bash
|
||||
gem install sassy-escape
|
||||
```
|
||||
|
||||
```scss
|
||||
body {
|
||||
content: escape('I ♥ Sass', $is-identifier: true);
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `cssesc(value, options)`
|
||||
|
||||
This function takes a value and returns an escaped version of the value where any characters that are not printable ASCII symbols are escaped using the shortest possible (but valid) [escape sequences for use in CSS strings or identifiers](https://mathiasbynens.be/notes/css-escapes).
|
||||
|
||||
```js
|
||||
cssesc('Ich ♥ Bücher');
|
||||
// → 'Ich \\2665 B\\FC cher'
|
||||
|
||||
cssesc('foo 𝌆 bar');
|
||||
// → 'foo \\1D306 bar'
|
||||
```
|
||||
|
||||
By default, `cssesc` returns a string that can be used as part of a CSS string. If the target is a CSS identifier rather than a CSS string, use the `isIdentifier: true` setting (see below).
|
||||
|
||||
The optional `options` argument accepts an object with the following options:
|
||||
|
||||
#### `isIdentifier`
|
||||
|
||||
The default value for the `isIdentifier` option is `false`. This means that the input text will be escaped for use in a CSS string literal. If you want to use the result as a CSS identifier instead (in a selector, for example), set this option to `true`.
|
||||
|
||||
```js
|
||||
cssesc('123a2b');
|
||||
// → '123a2b'
|
||||
|
||||
cssesc('123a2b', {
|
||||
'isIdentifier': true
|
||||
});
|
||||
// → '\\31 23a2b'
|
||||
```
|
||||
|
||||
#### `quotes`
|
||||
|
||||
The default value for the `quotes` option is `'single'`. This means that any occurences of `'` in the input text will be escaped as `\'`, so that the output can be used in a CSS string literal wrapped in single quotes.
|
||||
|
||||
```js
|
||||
cssesc('Lorem ipsum "dolor" sit \'amet\' etc.');
|
||||
// → 'Lorem ipsum "dolor" sit \\\'amet\\\' etc.'
|
||||
// → "Lorem ipsum \"dolor\" sit \\'amet\\' etc."
|
||||
|
||||
cssesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
|
||||
'quotes': 'single'
|
||||
});
|
||||
// → 'Lorem ipsum "dolor" sit \\\'amet\\\' etc.'
|
||||
// → "Lorem ipsum \"dolor\" sit \\'amet\\' etc."
|
||||
```
|
||||
|
||||
If you want to use the output as part of a CSS string literal wrapped in double quotes, set the `quotes` option to `'double'`.
|
||||
|
||||
```js
|
||||
cssesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
|
||||
'quotes': 'double'
|
||||
});
|
||||
// → 'Lorem ipsum \\"dolor\\" sit \'amet\' etc.'
|
||||
// → "Lorem ipsum \\\"dolor\\\" sit 'amet' etc."
|
||||
```
|
||||
|
||||
#### `wrap`
|
||||
|
||||
The `wrap` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, the output will be a valid CSS string literal wrapped in quotes. The type of quotes can be specified through the `quotes` setting.
|
||||
|
||||
```js
|
||||
cssesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
|
||||
'quotes': 'single',
|
||||
'wrap': true
|
||||
});
|
||||
// → '\'Lorem ipsum "dolor" sit \\\'amet\\\' etc.\''
|
||||
// → "\'Lorem ipsum \"dolor\" sit \\\'amet\\\' etc.\'"
|
||||
|
||||
cssesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
|
||||
'quotes': 'double',
|
||||
'wrap': true
|
||||
});
|
||||
// → '"Lorem ipsum \\"dolor\\" sit \'amet\' etc."'
|
||||
// → "\"Lorem ipsum \\\"dolor\\\" sit \'amet\' etc.\""
|
||||
```
|
||||
|
||||
#### `escapeEverything`
|
||||
|
||||
The `escapeEverything` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, all the symbols in the output will be escaped, even printable ASCII symbols.
|
||||
|
||||
```js
|
||||
cssesc('lolwat"foo\'bar', {
|
||||
'escapeEverything': true
|
||||
});
|
||||
// → '\\6C\\6F\\6C\\77\\61\\74\\"\\66\\6F\\6F\\\'\\62\\61\\72'
|
||||
// → "\\6C\\6F\\6C\\77\\61\\74\\\"\\66\\6F\\6F\\'\\62\\61\\72"
|
||||
```
|
||||
|
||||
#### Overriding the default options globally
|
||||
|
||||
The global default settings can be overridden by modifying the `css.options` object. This saves you from passing in an `options` object for every call to `encode` if you want to use the non-default setting.
|
||||
|
||||
```js
|
||||
// Read the global default setting for `escapeEverything`:
|
||||
cssesc.options.escapeEverything;
|
||||
// → `false` by default
|
||||
|
||||
// Override the global default setting for `escapeEverything`:
|
||||
cssesc.options.escapeEverything = true;
|
||||
|
||||
// Using the global default setting for `escapeEverything`, which is now `true`:
|
||||
cssesc('foo © bar ≠ baz 𝌆 qux');
|
||||
// → '\\66\\6F\\6F\\ \\A9\\ \\62\\61\\72\\ \\2260\\ \\62\\61\\7A\\ \\1D306\\ \\71\\75\\78'
|
||||
```
|
||||
|
||||
### `cssesc.version`
|
||||
|
||||
A string representing the semantic version number.
|
||||
|
||||
### Using the `cssesc` binary
|
||||
|
||||
To use the `cssesc` binary in your shell, simply install cssesc globally using npm:
|
||||
|
||||
```bash
|
||||
npm install -g cssesc
|
||||
```
|
||||
|
||||
After that you will be able to escape text for use in CSS strings or identifiers from the command line:
|
||||
|
||||
```bash
|
||||
$ cssesc 'föo ♥ bår 𝌆 baz'
|
||||
f\F6o \2665 b\E5r \1D306 baz
|
||||
```
|
||||
|
||||
If the output needs to be a CSS identifier rather than part of a string literal, use the `-i`/`--identifier` option:
|
||||
|
||||
```bash
|
||||
$ cssesc --identifier 'föo ♥ bår 𝌆 baz'
|
||||
f\F6o\ \2665\ b\E5r\ \1D306\ baz
|
||||
```
|
||||
|
||||
See `cssesc --help` for the full list of options.
|
||||
|
||||
## Support
|
||||
|
||||
This library supports the Node.js and browser versions mentioned in [`.babelrc`](https://github.com/mathiasbynens/cssesc/blob/master/.babelrc). For a version that supports a wider variety of legacy browsers and environments out-of-the-box, [see v0.1.0](https://github.com/mathiasbynens/cssesc/releases/tag/v0.1.0).
|
||||
|
||||
## Author
|
||||
|
||||
| [](https://twitter.com/mathias "Follow @mathias on Twitter") |
|
||||
|---|
|
||||
| [Mathias Bynens](https://mathiasbynens.be/) |
|
||||
|
||||
## License
|
||||
|
||||
This library is available under the [MIT](https://mths.be/mit) license.
|
||||
116
templates/node_modules/cssesc/bin/cssesc
generated
vendored
Executable file
116
templates/node_modules/cssesc/bin/cssesc
generated
vendored
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const cssesc = require('../cssesc.js');
|
||||
const strings = process.argv.splice(2);
|
||||
const stdin = process.stdin;
|
||||
const options = {};
|
||||
const log = console.log;
|
||||
|
||||
const main = function() {
|
||||
const option = strings[0];
|
||||
|
||||
if (/^(?:-h|--help|undefined)$/.test(option)) {
|
||||
log(
|
||||
'cssesc v%s - https://mths.be/cssesc',
|
||||
cssesc.version
|
||||
);
|
||||
log([
|
||||
'\nUsage:\n',
|
||||
'\tcssesc [string]',
|
||||
'\tcssesc [-i | --identifier] [string]',
|
||||
'\tcssesc [-s | --single-quotes] [string]',
|
||||
'\tcssesc [-d | --double-quotes] [string]',
|
||||
'\tcssesc [-w | --wrap] [string]',
|
||||
'\tcssesc [-e | --escape-everything] [string]',
|
||||
'\tcssesc [-v | --version]',
|
||||
'\tcssesc [-h | --help]',
|
||||
'\nExamples:\n',
|
||||
'\tcssesc \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'',
|
||||
'\tcssesc --identifier \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'',
|
||||
'\tcssesc --escape-everything \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'',
|
||||
'\tcssesc --double-quotes --wrap \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\'',
|
||||
'\techo \'f\xF6o \u2665 b\xE5r \uD834\uDF06 baz\' | cssesc'
|
||||
].join('\n'));
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
if (/^(?:-v|--version)$/.test(option)) {
|
||||
log('v%s', cssesc.version);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
strings.forEach(function(string) {
|
||||
// Process options
|
||||
if (/^(?:-i|--identifier)$/.test(string)) {
|
||||
options.isIdentifier = true;
|
||||
return;
|
||||
}
|
||||
if (/^(?:-s|--single-quotes)$/.test(string)) {
|
||||
options.quotes = 'single';
|
||||
return;
|
||||
}
|
||||
if (/^(?:-d|--double-quotes)$/.test(string)) {
|
||||
options.quotes = 'double';
|
||||
return;
|
||||
}
|
||||
if (/^(?:-w|--wrap)$/.test(string)) {
|
||||
options.wrap = true;
|
||||
return;
|
||||
}
|
||||
if (/^(?:-e|--escape-everything)$/.test(string)) {
|
||||
options.escapeEverything = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Process string(s)
|
||||
let result;
|
||||
try {
|
||||
result = cssesc(string, options);
|
||||
log(result);
|
||||
} catch (exception) {
|
||||
log(exception.message + '\n');
|
||||
log('Error: failed to escape.');
|
||||
log('If you think this is a bug in cssesc, please report it:');
|
||||
log('https://github.com/mathiasbynens/cssesc/issues/new');
|
||||
log(
|
||||
'\nStack trace using cssesc@%s:\n',
|
||||
cssesc.version
|
||||
);
|
||||
log(exception.stack);
|
||||
return process.exit(1);
|
||||
}
|
||||
});
|
||||
// Return with exit status 0 outside of the `forEach` loop, in case
|
||||
// multiple strings were passed in.
|
||||
return process.exit(0);
|
||||
|
||||
};
|
||||
|
||||
if (stdin.isTTY) {
|
||||
// handle shell arguments
|
||||
main();
|
||||
} else {
|
||||
let timeout;
|
||||
// Either the script is called from within a non-TTY context, or `stdin`
|
||||
// content is being piped in.
|
||||
if (!process.stdout.isTTY) {
|
||||
// The script was called from a non-TTY context. This is a rather uncommon
|
||||
// use case we don’t actively support. However, we don’t want the script
|
||||
// to wait forever in such cases, so…
|
||||
timeout = setTimeout(function() {
|
||||
// …if no piped data arrived after a whole minute, handle shell
|
||||
// arguments instead.
|
||||
main();
|
||||
}, 60000);
|
||||
}
|
||||
let data = '';
|
||||
stdin.on('data', function(chunk) {
|
||||
clearTimeout(timeout);
|
||||
data += chunk;
|
||||
});
|
||||
stdin.on('end', function() {
|
||||
strings.push(data.trim());
|
||||
main();
|
||||
});
|
||||
stdin.resume();
|
||||
}
|
||||
110
templates/node_modules/cssesc/cssesc.js
generated
vendored
Normal file
110
templates/node_modules/cssesc/cssesc.js
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
/*! https://mths.be/cssesc v3.0.0 by @mathias */
|
||||
'use strict';
|
||||
|
||||
var object = {};
|
||||
var hasOwnProperty = object.hasOwnProperty;
|
||||
var merge = function merge(options, defaults) {
|
||||
if (!options) {
|
||||
return defaults;
|
||||
}
|
||||
var result = {};
|
||||
for (var key in defaults) {
|
||||
// `if (defaults.hasOwnProperty(key) { … }` is not needed here, since
|
||||
// only recognized option names are used.
|
||||
result[key] = hasOwnProperty.call(options, key) ? options[key] : defaults[key];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
var regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/;
|
||||
var regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/;
|
||||
var regexAlwaysEscape = /['"\\]/;
|
||||
var regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g;
|
||||
|
||||
// https://mathiasbynens.be/notes/css-escapes#css
|
||||
var cssesc = function cssesc(string, options) {
|
||||
options = merge(options, cssesc.options);
|
||||
if (options.quotes != 'single' && options.quotes != 'double') {
|
||||
options.quotes = 'single';
|
||||
}
|
||||
var quote = options.quotes == 'double' ? '"' : '\'';
|
||||
var isIdentifier = options.isIdentifier;
|
||||
|
||||
var firstChar = string.charAt(0);
|
||||
var output = '';
|
||||
var counter = 0;
|
||||
var length = string.length;
|
||||
while (counter < length) {
|
||||
var character = string.charAt(counter++);
|
||||
var codePoint = character.charCodeAt();
|
||||
var value = void 0;
|
||||
// If it’s not a printable ASCII character…
|
||||
if (codePoint < 0x20 || codePoint > 0x7E) {
|
||||
if (codePoint >= 0xD800 && codePoint <= 0xDBFF && counter < length) {
|
||||
// It’s a high surrogate, and there is a next character.
|
||||
var extra = string.charCodeAt(counter++);
|
||||
if ((extra & 0xFC00) == 0xDC00) {
|
||||
// next character is low surrogate
|
||||
codePoint = ((codePoint & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000;
|
||||
} else {
|
||||
// It’s an unmatched surrogate; only append this code unit, in case
|
||||
// the next code unit is the high surrogate of a surrogate pair.
|
||||
counter--;
|
||||
}
|
||||
}
|
||||
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
||||
} else {
|
||||
if (options.escapeEverything) {
|
||||
if (regexAnySingleEscape.test(character)) {
|
||||
value = '\\' + character;
|
||||
} else {
|
||||
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
||||
}
|
||||
} else if (/[\t\n\f\r\x0B]/.test(character)) {
|
||||
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
||||
} else if (character == '\\' || !isIdentifier && (character == '"' && quote == character || character == '\'' && quote == character) || isIdentifier && regexSingleEscape.test(character)) {
|
||||
value = '\\' + character;
|
||||
} else {
|
||||
value = character;
|
||||
}
|
||||
}
|
||||
output += value;
|
||||
}
|
||||
|
||||
if (isIdentifier) {
|
||||
if (/^-[-\d]/.test(output)) {
|
||||
output = '\\-' + output.slice(1);
|
||||
} else if (/\d/.test(firstChar)) {
|
||||
output = '\\3' + firstChar + ' ' + output.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove spaces after `\HEX` escapes that are not followed by a hex digit,
|
||||
// since they’re redundant. Note that this is only possible if the escape
|
||||
// sequence isn’t preceded by an odd number of backslashes.
|
||||
output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) {
|
||||
if ($1 && $1.length % 2) {
|
||||
// It’s not safe to remove the space, so don’t.
|
||||
return $0;
|
||||
}
|
||||
// Strip the space.
|
||||
return ($1 || '') + $2;
|
||||
});
|
||||
|
||||
if (!isIdentifier && options.wrap) {
|
||||
return quote + output + quote;
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
// Expose default options (so they can be overridden globally).
|
||||
cssesc.options = {
|
||||
'escapeEverything': false,
|
||||
'isIdentifier': false,
|
||||
'quotes': 'single',
|
||||
'wrap': false
|
||||
};
|
||||
|
||||
cssesc.version = '3.0.0';
|
||||
|
||||
module.exports = cssesc;
|
||||
70
templates/node_modules/cssesc/man/cssesc.1
generated
vendored
Normal file
70
templates/node_modules/cssesc/man/cssesc.1
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
.Dd August 9, 2013
|
||||
.Dt cssesc 1
|
||||
.Sh NAME
|
||||
.Nm cssesc
|
||||
.Nd escape text for use in CSS string literals or identifiers
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl i | -identifier Ar string
|
||||
.br
|
||||
.Op Fl s | -single-quotes Ar string
|
||||
.br
|
||||
.Op Fl d | -double-quotes Ar string
|
||||
.br
|
||||
.Op Fl w | -wrap Ar string
|
||||
.br
|
||||
.Op Fl e | -escape-everything Ar string
|
||||
.br
|
||||
.Op Fl v | -version
|
||||
.br
|
||||
.Op Fl h | -help
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
escapes strings for use in CSS string literals or identifiers while generating the shortest possible valid ASCII-only output.
|
||||
.Sh OPTIONS
|
||||
.Bl -ohang -offset
|
||||
.It Sy "-s, --single-quotes"
|
||||
Escape any occurences of ' in the input string as \\', so that the output can be used in a CSS string literal wrapped in single quotes.
|
||||
.It Sy "-d, --double-quotes"
|
||||
Escape any occurences of " in the input string as \\", so that the output can be used in a CSS string literal wrapped in double quotes.
|
||||
.It Sy "-w, --wrap"
|
||||
Make sure the output is a valid CSS string literal wrapped in quotes. The type of quotes can be specified using the
|
||||
.Ar -s | --single-quotes
|
||||
or
|
||||
.Ar -d | --double-quotes
|
||||
settings.
|
||||
.It Sy "-e, --escape-everything"
|
||||
Escape all the symbols in the output, even printable ASCII symbols.
|
||||
.It Sy "-v, --version"
|
||||
Print cssesc's version.
|
||||
.It Sy "-h, --help"
|
||||
Show the help screen.
|
||||
.El
|
||||
.Sh EXIT STATUS
|
||||
The
|
||||
.Nm cssesc
|
||||
utility exits with one of the following values:
|
||||
.Pp
|
||||
.Bl -tag -width flag -compact
|
||||
.It Li 0
|
||||
.Nm
|
||||
successfully escaped the given text and printed the result.
|
||||
.It Li 1
|
||||
.Nm
|
||||
wasn't instructed to escape anything (for example, the
|
||||
.Ar --help
|
||||
flag was set); or, an error occurred.
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
.Bl -ohang -offset
|
||||
.It Sy "cssesc 'foo bar baz'"
|
||||
Print an escaped version of the given text.
|
||||
.It Sy echo\ 'foo bar baz'\ |\ cssesc
|
||||
Print an escaped version of the text that gets piped in.
|
||||
.El
|
||||
.Sh BUGS
|
||||
cssesc's bug tracker is located at <https://github.com/mathiasbynens/cssesc/issues>.
|
||||
.Sh AUTHOR
|
||||
Mathias Bynens <https://mathiasbynens.be/>
|
||||
.Sh WWW
|
||||
<https://mths.be/cssesc>
|
||||
51
templates/node_modules/cssesc/package.json
generated
vendored
Normal file
51
templates/node_modules/cssesc/package.json
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "cssesc",
|
||||
"version": "3.0.0",
|
||||
"description": "A JavaScript library for escaping CSS strings and identifiers while generating the shortest possible ASCII-only output.",
|
||||
"homepage": "https://mths.be/cssesc",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"main": "cssesc.js",
|
||||
"bin": "bin/cssesc",
|
||||
"man": "man/cssesc.1",
|
||||
"keywords": [
|
||||
"css",
|
||||
"escape",
|
||||
"identifier",
|
||||
"string",
|
||||
"tool"
|
||||
],
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Mathias Bynens",
|
||||
"url": "https://mathiasbynens.be/"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mathiasbynens/cssesc.git"
|
||||
},
|
||||
"bugs": "https://github.com/mathiasbynens/cssesc/issues",
|
||||
"files": [
|
||||
"LICENSE-MIT.txt",
|
||||
"cssesc.js",
|
||||
"bin/",
|
||||
"man/"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "grunt template && babel cssesc.js -o cssesc.js",
|
||||
"test": "mocha tests",
|
||||
"cover": "istanbul cover --report html node_modules/.bin/_mocha tests -- -u exports -R spec"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"codecov": "^1.0.1",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-template": "^1.0.0",
|
||||
"istanbul": "^0.4.4",
|
||||
"mocha": "^2.5.3",
|
||||
"regenerate": "^1.2.1",
|
||||
"requirejs": "^2.1.16"
|
||||
}
|
||||
}
|
||||
873
templates/node_modules/postcss-selector-parser/API.md
generated
vendored
Normal file
873
templates/node_modules/postcss-selector-parser/API.md
generated
vendored
Normal file
@@ -0,0 +1,873 @@
|
||||
# API Documentation
|
||||
|
||||
*Please use only this documented API when working with the parser. Methods
|
||||
not documented here are subject to change at any point.*
|
||||
|
||||
## `parser` function
|
||||
|
||||
This is the module's main entry point.
|
||||
|
||||
```js
|
||||
const parser = require('postcss-selector-parser');
|
||||
```
|
||||
|
||||
### `parser([transform], [options])`
|
||||
|
||||
Creates a new `processor` instance
|
||||
|
||||
```js
|
||||
const processor = parser();
|
||||
```
|
||||
|
||||
Or, with optional transform function
|
||||
|
||||
```js
|
||||
const transform = selectors => {
|
||||
selectors.walkUniversals(selector => {
|
||||
selector.remove();
|
||||
});
|
||||
};
|
||||
|
||||
const processor = parser(transform)
|
||||
|
||||
// Example
|
||||
const result = processor.processSync('*.class');
|
||||
// => .class
|
||||
```
|
||||
|
||||
[See processor documentation](#processor)
|
||||
|
||||
Arguments:
|
||||
|
||||
* `transform (function)`: Provide a function to work with the parsed AST.
|
||||
* `options (object)`: Provide default options for all calls on the returned `Processor`.
|
||||
|
||||
### `parser.attribute([props])`
|
||||
|
||||
Creates a new attribute selector.
|
||||
|
||||
```js
|
||||
parser.attribute({attribute: 'href'});
|
||||
// => [href]
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.className([props])`
|
||||
|
||||
Creates a new class selector.
|
||||
|
||||
```js
|
||||
parser.className({value: 'button'});
|
||||
// => .button
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.combinator([props])`
|
||||
|
||||
Creates a new selector combinator.
|
||||
|
||||
```js
|
||||
parser.combinator({value: '+'});
|
||||
// => +
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
Notes:
|
||||
* **Descendant Combinators** The value of descendant combinators created by the
|
||||
parser always just a single space (`" "`). For descendant selectors with no
|
||||
comments, additional space is now stored in `node.spaces.before`. Depending
|
||||
on the location of comments, additional spaces may be stored in
|
||||
`node.raws.spaces.before`, `node.raws.spaces.after`, or `node.raws.value`.
|
||||
* **Named Combinators** Although, nonstandard and unlikely to ever become a standard,
|
||||
named combinators like `/deep/` and `/for/` are parsed as combinators. The
|
||||
`node.value` is name after being unescaped and normalized as lowercase. The
|
||||
original value for the combinator name is stored in `node.raws.value`.
|
||||
|
||||
|
||||
### `parser.comment([props])`
|
||||
|
||||
Creates a new comment.
|
||||
|
||||
```js
|
||||
parser.comment({value: '/* Affirmative, Dave. I read you. */'});
|
||||
// => /* Affirmative, Dave. I read you. */
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.id([props])`
|
||||
|
||||
Creates a new id selector.
|
||||
|
||||
```js
|
||||
parser.id({value: 'search'});
|
||||
// => #search
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.nesting([props])`
|
||||
|
||||
Creates a new nesting selector.
|
||||
|
||||
```js
|
||||
parser.nesting();
|
||||
// => &
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.pseudo([props])`
|
||||
|
||||
Creates a new pseudo selector.
|
||||
|
||||
```js
|
||||
parser.pseudo({value: '::before'});
|
||||
// => ::before
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.root([props])`
|
||||
|
||||
Creates a new root node.
|
||||
|
||||
```js
|
||||
parser.root();
|
||||
// => (empty)
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.selector([props])`
|
||||
|
||||
Creates a new selector node.
|
||||
|
||||
```js
|
||||
parser.selector();
|
||||
// => (empty)
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.string([props])`
|
||||
|
||||
Creates a new string node.
|
||||
|
||||
```js
|
||||
parser.string();
|
||||
// => (empty)
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.tag([props])`
|
||||
|
||||
Creates a new tag selector.
|
||||
|
||||
```js
|
||||
parser.tag({value: 'button'});
|
||||
// => button
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
### `parser.universal([props])`
|
||||
|
||||
Creates a new universal selector.
|
||||
|
||||
```js
|
||||
parser.universal();
|
||||
// => *
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `props (object)`: The new node's properties.
|
||||
|
||||
## Node types
|
||||
|
||||
### `node.type`
|
||||
|
||||
A string representation of the selector type. It can be one of the following;
|
||||
`attribute`, `class`, `combinator`, `comment`, `id`, `nesting`, `pseudo`,
|
||||
`root`, `selector`, `string`, `tag`, or `universal`. Note that for convenience,
|
||||
these constants are exposed on the main `parser` as uppercased keys. So for
|
||||
example you can get `id` by querying `parser.ID`.
|
||||
|
||||
```js
|
||||
parser.attribute({attribute: 'href'}).type;
|
||||
// => 'attribute'
|
||||
```
|
||||
|
||||
### `node.parent`
|
||||
|
||||
Returns the parent node.
|
||||
|
||||
```js
|
||||
root.nodes[0].parent === root;
|
||||
```
|
||||
|
||||
### `node.toString()`, `String(node)`, or `'' + node`
|
||||
|
||||
Returns a string representation of the node.
|
||||
|
||||
```js
|
||||
const id = parser.id({value: 'search'});
|
||||
console.log(String(id));
|
||||
// => #search
|
||||
```
|
||||
|
||||
### `node.next()` & `node.prev()`
|
||||
|
||||
Returns the next/previous child of the parent node.
|
||||
|
||||
```js
|
||||
const next = id.next();
|
||||
if (next && next.type !== 'combinator') {
|
||||
throw new Error('Qualified IDs are not allowed!');
|
||||
}
|
||||
```
|
||||
|
||||
### `node.replaceWith(node)`
|
||||
|
||||
Replace a node with another.
|
||||
|
||||
```js
|
||||
const attr = selectors.first.first;
|
||||
const className = parser.className({value: 'test'});
|
||||
attr.replaceWith(className);
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `node`: The node to substitute the original with.
|
||||
|
||||
### `node.remove()`
|
||||
|
||||
Removes the node from its parent node.
|
||||
|
||||
```js
|
||||
if (node.type === 'id') {
|
||||
node.remove();
|
||||
}
|
||||
```
|
||||
|
||||
### `node.clone()`
|
||||
|
||||
Returns a copy of a node, detached from any parent containers that the
|
||||
original might have had.
|
||||
|
||||
```js
|
||||
const cloned = parser.id({value: 'search'});
|
||||
String(cloned);
|
||||
|
||||
// => #search
|
||||
```
|
||||
|
||||
### `node.isAtPosition(line, column)`
|
||||
|
||||
Return a `boolean` indicating whether this node includes the character at the
|
||||
position of the given line and column. Returns `undefined` if the nodes lack
|
||||
sufficient source metadata to determine the position.
|
||||
|
||||
Arguments:
|
||||
|
||||
* `line`: 1-index based line number relative to the start of the selector.
|
||||
* `column`: 1-index based column number relative to the start of the selector.
|
||||
|
||||
### `node.spaces`
|
||||
|
||||
Extra whitespaces around the node will be moved into `node.spaces.before` and
|
||||
`node.spaces.after`. So for example, these spaces will be moved as they have
|
||||
no semantic meaning:
|
||||
|
||||
```css
|
||||
h1 , h2 {}
|
||||
```
|
||||
|
||||
For descendent selectors, the value is always a single space.
|
||||
|
||||
```css
|
||||
h1 h2 {}
|
||||
```
|
||||
|
||||
Additional whitespace is found in either the `node.spaces.before` and `node.spaces.after` depending on the presence of comments or other whitespace characters. If the actual whitespace does not start or end with a single space, the node's raw value is set to the actual space(s) found in the source.
|
||||
|
||||
### `node.source`
|
||||
|
||||
An object describing the node's start/end, line/column source position.
|
||||
|
||||
Within the following CSS, the `.bar` class node ...
|
||||
|
||||
```css
|
||||
.foo,
|
||||
.bar {}
|
||||
```
|
||||
|
||||
... will contain the following `source` object.
|
||||
|
||||
```js
|
||||
source: {
|
||||
start: {
|
||||
line: 2,
|
||||
column: 3
|
||||
},
|
||||
end: {
|
||||
line: 2,
|
||||
column: 6
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `node.sourceIndex`
|
||||
|
||||
The zero-based index of the node within the original source string.
|
||||
|
||||
Within the following CSS, the `.baz` class node will have a `sourceIndex` of `12`.
|
||||
|
||||
```css
|
||||
.foo, .bar, .baz {}
|
||||
```
|
||||
|
||||
## Container types
|
||||
|
||||
The `root`, `selector`, and `pseudo` nodes have some helper methods for working
|
||||
with their children.
|
||||
|
||||
### `container.nodes`
|
||||
|
||||
An array of the container's children.
|
||||
|
||||
```js
|
||||
// Input: h1 h2
|
||||
selectors.at(0).nodes.length // => 3
|
||||
selectors.at(0).nodes[0].value // => 'h1'
|
||||
selectors.at(0).nodes[1].value // => ' '
|
||||
```
|
||||
|
||||
### `container.first` & `container.last`
|
||||
|
||||
The first/last child of the container.
|
||||
|
||||
```js
|
||||
selector.first === selector.nodes[0];
|
||||
selector.last === selector.nodes[selector.nodes.length - 1];
|
||||
```
|
||||
|
||||
### `container.at(index)`
|
||||
|
||||
Returns the node at position `index`.
|
||||
|
||||
```js
|
||||
selector.at(0) === selector.first;
|
||||
selector.at(0) === selector.nodes[0];
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `index`: The index of the node to return.
|
||||
|
||||
### `container.atPosition(line, column)`
|
||||
|
||||
Returns the node at the source position `index`.
|
||||
|
||||
```js
|
||||
selector.at(0) === selector.first;
|
||||
selector.at(0) === selector.nodes[0];
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `index`: The index of the node to return.
|
||||
|
||||
### `container.index(node)`
|
||||
|
||||
Return the index of the node within its container.
|
||||
|
||||
```js
|
||||
selector.index(selector.nodes[2]) // => 2
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `node`: A node within the current container.
|
||||
|
||||
### `container.length`
|
||||
|
||||
Proxy to the length of the container's nodes.
|
||||
|
||||
```js
|
||||
container.length === container.nodes.length
|
||||
```
|
||||
|
||||
### `container` Array iterators
|
||||
|
||||
The container class provides proxies to certain Array methods; these are:
|
||||
|
||||
* `container.map === container.nodes.map`
|
||||
* `container.reduce === container.nodes.reduce`
|
||||
* `container.every === container.nodes.every`
|
||||
* `container.some === container.nodes.some`
|
||||
* `container.filter === container.nodes.filter`
|
||||
* `container.sort === container.nodes.sort`
|
||||
|
||||
Note that these methods only work on a container's immediate children; recursive
|
||||
iteration is provided by `container.walk`.
|
||||
|
||||
### `container.each(callback)`
|
||||
|
||||
Iterate the container's immediate children, calling `callback` for each child.
|
||||
You may return `false` within the callback to break the iteration.
|
||||
|
||||
```js
|
||||
let className;
|
||||
selectors.each((selector, index) => {
|
||||
if (selector.type === 'class') {
|
||||
className = selector.value;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Note that unlike `Array#forEach()`, this iterator is safe to use whilst adding
|
||||
or removing nodes from the container.
|
||||
|
||||
Arguments:
|
||||
|
||||
* `callback (function)`: A function to call for each node, which receives `node`
|
||||
and `index` arguments.
|
||||
|
||||
### `container.walk(callback)`
|
||||
|
||||
Like `container#each`, but will also iterate child nodes as long as they are
|
||||
`container` types.
|
||||
|
||||
```js
|
||||
selectors.walk((selector, index) => {
|
||||
// all nodes
|
||||
});
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `callback (function)`: A function to call for each node, which receives `node`
|
||||
and `index` arguments.
|
||||
|
||||
This iterator is safe to use whilst mutating `container.nodes`,
|
||||
like `container#each`.
|
||||
|
||||
### `container.walk` proxies
|
||||
|
||||
The container class provides proxy methods for iterating over types of nodes,
|
||||
so that it is easier to write modules that target specific selectors. Those
|
||||
methods are:
|
||||
|
||||
* `container.walkAttributes`
|
||||
* `container.walkClasses`
|
||||
* `container.walkCombinators`
|
||||
* `container.walkComments`
|
||||
* `container.walkIds`
|
||||
* `container.walkNesting`
|
||||
* `container.walkPseudos`
|
||||
* `container.walkTags`
|
||||
* `container.walkUniversals`
|
||||
|
||||
### `container.split(callback)`
|
||||
|
||||
This method allows you to split a group of nodes by returning `true` from
|
||||
a callback. It returns an array of arrays, where each inner array corresponds
|
||||
to the groups that you created via the callback.
|
||||
|
||||
```js
|
||||
// (input) => h1 h2>>h3
|
||||
const list = selectors.first.split(selector => {
|
||||
return selector.type === 'combinator';
|
||||
});
|
||||
|
||||
// (node values) => [['h1', ' '], ['h2', '>>'], ['h3']]
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `callback (function)`: A function to call for each node, which receives `node`
|
||||
as an argument.
|
||||
|
||||
### `container.prepend(node)` & `container.append(node)`
|
||||
|
||||
Add a node to the start/end of the container. Note that doing so will set
|
||||
the parent property of the node to this container.
|
||||
|
||||
```js
|
||||
const id = parser.id({value: 'search'});
|
||||
selector.append(id);
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `node`: The node to add.
|
||||
|
||||
### `container.insertBefore(old, new)` & `container.insertAfter(old, new)`
|
||||
|
||||
Add a node before or after an existing node in a container:
|
||||
|
||||
```js
|
||||
selectors.walk(selector => {
|
||||
if (selector.type !== 'class') {
|
||||
const className = parser.className({value: 'theme-name'});
|
||||
selector.parent.insertAfter(selector, className);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `old`: The existing node in the container.
|
||||
* `new`: The new node to add before/after the existing node.
|
||||
|
||||
### `container.removeChild(node)`
|
||||
|
||||
Remove the node from the container. Note that you can also use
|
||||
`node.remove()` if you would like to remove just a single node.
|
||||
|
||||
```js
|
||||
selector.length // => 2
|
||||
selector.remove(id)
|
||||
selector.length // => 1;
|
||||
id.parent // undefined
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `node`: The node to remove.
|
||||
|
||||
### `container.removeAll()` or `container.empty()`
|
||||
|
||||
Remove all children from the container.
|
||||
|
||||
```js
|
||||
selector.removeAll();
|
||||
selector.length // => 0
|
||||
```
|
||||
|
||||
## Root nodes
|
||||
|
||||
A root node represents a comma separated list of selectors. Indeed, all
|
||||
a root's `toString()` method does is join its selector children with a ','.
|
||||
Other than this, it has no special functionality and acts like a container.
|
||||
|
||||
### `root.trailingComma`
|
||||
|
||||
This will be set to `true` if the input has a trailing comma, in order to
|
||||
support parsing of legacy CSS hacks.
|
||||
|
||||
## Selector nodes
|
||||
|
||||
A selector node represents a single complex selector. For example, this
|
||||
selector string `h1 h2 h3, [href] > p`, is represented as two selector nodes.
|
||||
It has no special functionality of its own.
|
||||
|
||||
## Pseudo nodes
|
||||
|
||||
A pseudo selector extends a container node; if it has any parameters of its
|
||||
own (such as `h1:not(h2, h3)`), they will be its children. Note that the pseudo
|
||||
`value` will always contain the colons preceding the pseudo identifier. This
|
||||
is so that both `:before` and `::before` are properly represented in the AST.
|
||||
|
||||
## Attribute nodes
|
||||
|
||||
### `attribute.quoted`
|
||||
|
||||
Returns `true` if the attribute's value is wrapped in quotation marks, false if it is not.
|
||||
Remains `undefined` if there is no attribute value.
|
||||
|
||||
```css
|
||||
[href=foo] /* false */
|
||||
[href='foo'] /* true */
|
||||
[href="foo"] /* true */
|
||||
[href] /* undefined */
|
||||
```
|
||||
|
||||
### `attribute.qualifiedAttribute`
|
||||
|
||||
Returns the attribute name qualified with the namespace if one is given.
|
||||
|
||||
### `attribute.offsetOf(part)`
|
||||
|
||||
Returns the offset of the attribute part specified relative to the
|
||||
start of the node of the output string. This is useful in raising
|
||||
error messages about a specific part of the attribute, especially
|
||||
in combination with `attribute.sourceIndex`.
|
||||
|
||||
Returns `-1` if the name is invalid or the value doesn't exist in this
|
||||
attribute.
|
||||
|
||||
The legal values for `part` are:
|
||||
|
||||
* `"ns"` - alias for "namespace"
|
||||
* `"namespace"` - the namespace if it exists.
|
||||
* `"attribute"` - the attribute name
|
||||
* `"attributeNS"` - the start of the attribute or its namespace
|
||||
* `"operator"` - the match operator of the attribute
|
||||
* `"value"` - The value (string or identifier)
|
||||
* `"insensitive"` - the case insensitivity flag
|
||||
|
||||
### `attribute.raws.unquoted`
|
||||
|
||||
Returns the unquoted content of the attribute's value.
|
||||
Remains `undefined` if there is no attribute value.
|
||||
|
||||
```css
|
||||
[href=foo] /* foo */
|
||||
[href='foo'] /* foo */
|
||||
[href="foo"] /* foo */
|
||||
[href] /* undefined */
|
||||
```
|
||||
|
||||
### `attribute.spaces`
|
||||
|
||||
Like `node.spaces` with the `before` and `after` values containing the spaces
|
||||
around the element, the parts of the attribute can also have spaces before
|
||||
and after them. The for each of `attribute`, `operator`, `value` and
|
||||
`insensitive` there is corresponding property of the same nam in
|
||||
`node.spaces` that has an optional `before` or `after` string containing only
|
||||
whitespace.
|
||||
|
||||
Note that corresponding values in `attributes.raws.spaces` contain values
|
||||
including any comments. If set, these values will override the
|
||||
`attribute.spaces` value. Take care to remove them if changing
|
||||
`attribute.spaces`.
|
||||
|
||||
### `attribute.raws`
|
||||
|
||||
The raws object stores comments and other information necessary to re-render
|
||||
the node exactly as it was in the source.
|
||||
|
||||
If a comment is embedded within the identifiers for the `namespace`, `attribute`
|
||||
or `value` then a property is placed in the raws for that value containing the full source of the propery including comments.
|
||||
|
||||
If a comment is embedded within the space between parts of the attribute
|
||||
then the raw for that space is set accordingly.
|
||||
|
||||
Setting an attribute's property `raws` value to be deleted.
|
||||
|
||||
For now, changing the spaces required also updating or removing any of the
|
||||
raws values that override them.
|
||||
|
||||
Example: `[ /*before*/ href /* after-attr */ = /* after-operator */ te/*inside-value*/st/* wow */ /*omg*/i/*bbq*/ /*whodoesthis*/]` would parse as:
|
||||
|
||||
```js
|
||||
{
|
||||
attribute: "href",
|
||||
operator: "=",
|
||||
value: "test",
|
||||
spaces: {
|
||||
before: '',
|
||||
after: '',
|
||||
attribute: { before: ' ', after: ' ' },
|
||||
operator: { after: ' ' },
|
||||
value: { after: ' ' },
|
||||
insensitive: { after: ' ' }
|
||||
},
|
||||
raws: {
|
||||
spaces: {
|
||||
attribute: { before: ' /*before*/ ', after: ' /* after-attr */ ' },
|
||||
operator: { after: ' /* after-operator */ ' },
|
||||
value: { after: '/* wow */ /*omg*/' },
|
||||
insensitive: { after: '/*bbq*/ /*whodoesthis*/' }
|
||||
},
|
||||
unquoted: 'test',
|
||||
value: 'te/*inside-value*/st'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `Processor`
|
||||
|
||||
### `ProcessorOptions`
|
||||
|
||||
* `lossless` - When `true`, whitespace is preserved. Defaults to `true`.
|
||||
* `updateSelector` - When `true`, if any processor methods are passed a postcss
|
||||
`Rule` node instead of a string, then that Rule's selector is updated
|
||||
with the results of the processing. Defaults to `true`.
|
||||
|
||||
### `process|processSync(selectors, [options])`
|
||||
|
||||
Processes the `selectors`, returning a string from the result of processing.
|
||||
|
||||
Note: when the `updateSelector` option is set, the rule's selector
|
||||
will be updated with the resulting string.
|
||||
|
||||
**Example:**
|
||||
|
||||
```js
|
||||
const parser = require("postcss-selector-parser");
|
||||
const processor = parser();
|
||||
|
||||
let result = processor.processSync(' .class');
|
||||
console.log(result);
|
||||
// => .class
|
||||
|
||||
// Asynchronous operation
|
||||
let promise = processor.process(' .class').then(result => {
|
||||
console.log(result)
|
||||
// => .class
|
||||
});
|
||||
|
||||
// To have the parser normalize whitespace values, utilize the options
|
||||
result = processor.processSync(' .class ', {lossless: false});
|
||||
console.log(result);
|
||||
// => .class
|
||||
|
||||
// For better syntax errors, pass a PostCSS Rule node.
|
||||
const postcss = require('postcss');
|
||||
rule = postcss.rule({selector: ' #foo > a, .class '});
|
||||
processor.process(rule, {lossless: false, updateSelector: true}).then(result => {
|
||||
console.log(result);
|
||||
// => #foo>a,.class
|
||||
console.log("rule:", rule.selector);
|
||||
// => rule: #foo>a,.class
|
||||
})
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `selectors (string|postcss.Rule)`: Either a selector string or a PostCSS Rule
|
||||
node.
|
||||
* `[options] (object)`: Process options
|
||||
|
||||
|
||||
### `ast|astSync(selectors, [options])`
|
||||
|
||||
Like `process()` and `processSync()` but after
|
||||
processing the `selectors` these methods return the `Root` node of the result
|
||||
instead of a string.
|
||||
|
||||
Note: when the `updateSelector` option is set, the rule's selector
|
||||
will be updated with the resulting string.
|
||||
|
||||
### `transform|transformSync(selectors, [options])`
|
||||
|
||||
Like `process()` and `processSync()` but after
|
||||
processing the `selectors` these methods return the value returned by the
|
||||
processor callback.
|
||||
|
||||
Note: when the `updateSelector` option is set, the rule's selector
|
||||
will be updated with the resulting string.
|
||||
|
||||
### Error Handling Within Selector Processors
|
||||
|
||||
The root node passed to the selector processor callback
|
||||
has a method `error(message, options)` that returns an
|
||||
error object. This method should always be used to raise
|
||||
errors relating to the syntax of selectors. The options
|
||||
to this method are passed to postcss's error constructor
|
||||
([documentation](http://api.postcss.org/Container.html#error)).
|
||||
|
||||
#### Async Error Example
|
||||
|
||||
```js
|
||||
let processor = (root) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
root.walkClasses((classNode) => {
|
||||
if (/^(.*)[-_]/.test(classNode.value)) {
|
||||
let msg = "classes may not have underscores or dashes in them";
|
||||
reject(root.error(msg, {
|
||||
index: classNode.sourceIndex + RegExp.$1.length + 1,
|
||||
word: classNode.value
|
||||
}));
|
||||
}
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const postcss = require("postcss");
|
||||
const parser = require("postcss-selector-parser");
|
||||
const selectorProcessor = parser(processor);
|
||||
const plugin = postcss.plugin('classValidator', (options) => {
|
||||
return (root) => {
|
||||
let promises = [];
|
||||
root.walkRules(rule => {
|
||||
promises.push(selectorProcessor.process(rule));
|
||||
});
|
||||
return Promise.all(promises);
|
||||
};
|
||||
});
|
||||
postcss(plugin()).process(`
|
||||
.foo-bar {
|
||||
color: red;
|
||||
}
|
||||
`.trim(), {from: 'test.css'}).catch((e) => console.error(e.toString()));
|
||||
|
||||
// CssSyntaxError: classValidator: ./test.css:1:5: classes may not have underscores or dashes in them
|
||||
//
|
||||
// > 1 | .foo-bar {
|
||||
// | ^
|
||||
// 2 | color: red;
|
||||
// 3 | }
|
||||
```
|
||||
|
||||
#### Synchronous Error Example
|
||||
|
||||
```js
|
||||
let processor = (root) => {
|
||||
root.walkClasses((classNode) => {
|
||||
if (/.*[-_]/.test(classNode.value)) {
|
||||
let msg = "classes may not have underscores or dashes in them";
|
||||
throw root.error(msg, {
|
||||
index: classNode.sourceIndex,
|
||||
word: classNode.value
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const postcss = require("postcss");
|
||||
const parser = require("postcss-selector-parser");
|
||||
const selectorProcessor = parser(processor);
|
||||
const plugin = postcss.plugin('classValidator', (options) => {
|
||||
return (root) => {
|
||||
root.walkRules(rule => {
|
||||
selectorProcessor.processSync(rule);
|
||||
});
|
||||
};
|
||||
});
|
||||
postcss(plugin()).process(`
|
||||
.foo-bar {
|
||||
color: red;
|
||||
}
|
||||
`.trim(), {from: 'test.css'}).catch((e) => console.error(e.toString()));
|
||||
|
||||
// CssSyntaxError: classValidator: ./test.css:1:5: classes may not have underscores or dashes in them
|
||||
//
|
||||
// > 1 | .foo-bar {
|
||||
// | ^
|
||||
// 2 | color: red;
|
||||
// 3 | }
|
||||
```
|
||||
513
templates/node_modules/postcss-selector-parser/CHANGELOG.md
generated
vendored
Normal file
513
templates/node_modules/postcss-selector-parser/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,513 @@
|
||||
# 6.0.10
|
||||
|
||||
- Fixed: `isPseudoElement()` supports `:first-letter` and `:first-line`
|
||||
|
||||
# 6.0.9
|
||||
|
||||
- Fixed: `Combinator.raws` property type
|
||||
|
||||
# 6.0.8
|
||||
|
||||
- Fixed: reduced size
|
||||
|
||||
# 6.0.7
|
||||
|
||||
- Fixed: parse animation percents
|
||||
|
||||
# 6.0.6
|
||||
|
||||
- Fixed: parse quoted attributes containing a newline correctly
|
||||
|
||||
# 6.0.5
|
||||
|
||||
- Perf: rework unesc for a 63+% performance boost
|
||||
|
||||
# 6.0.4
|
||||
|
||||
- Fixed: ts errors
|
||||
|
||||
# 6.0.3
|
||||
|
||||
- Fixed: replace node built-in "util" module with "util-deprecate"
|
||||
- Fixed: handle uppercase pseudo elements
|
||||
- Fixed: do not create invalid combinator before comment
|
||||
|
||||
# 6.0.2
|
||||
|
||||
- Fixed an issue with parsing and stringifying an empty attribute value
|
||||
|
||||
# 6.0.1
|
||||
|
||||
- Fixed an issue with unicode surrogate pair parsing
|
||||
|
||||
# 6.0.0
|
||||
|
||||
- Updated: `cssesc` to 3.0.0 (major)
|
||||
- Fixed: Issues with escaped `id` and `class` selectors
|
||||
|
||||
# 5.0.0
|
||||
|
||||
- Allow escaped dot within class name.
|
||||
- Update PostCSS to 7.0.7 (patch)
|
||||
|
||||
# 5.0.0-rc.4
|
||||
|
||||
- Fixed an issue where comments immediately after an insensitive (in attribute)
|
||||
were not parsed correctly.
|
||||
- Updated `cssesc` to 2.0.0 (major).
|
||||
- Removed outdated integration tests.
|
||||
- Added tests for custom selectors, tags with attributes, the universal
|
||||
selector with pseudos, and tokens after combinators.
|
||||
|
||||
# 5.0.0-rc.1
|
||||
|
||||
To ease adoption of the v5.0 release, we have relaxed the node version
|
||||
check performed by npm at installation time to allow for node 4, which
|
||||
remains officially unsupported, but likely to continue working for the
|
||||
time being.
|
||||
|
||||
# 5.0.0-rc.0
|
||||
|
||||
This release has **BREAKING CHANGES** that were required to fix regressions
|
||||
in 4.0.0 and to make the Combinator Node API consistent for all combinator
|
||||
types. Please read carefully.
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
* The way a descendent combinator that isn't a single space character (E.g. `.a .b`) is stored in the AST has changed.
|
||||
* Named Combinators (E.g. `.a /for/ .b`) are now properly parsed as a combinator.
|
||||
* It is now possible to look up a node based on the source location of a character in that node and to query nodes if they contain some character.
|
||||
* Several bug fixes that caused the parser to hang and run out of memory when a `/` was encountered have been fixed.
|
||||
* The minimum supported version of Node is now `v6.0.0`.
|
||||
|
||||
### Changes to the Descendent Combinator
|
||||
|
||||
In prior releases, the value of a descendant combinator with multiple spaces included all the spaces.
|
||||
|
||||
* `.a .b`: Extra spaces are now stored as space before.
|
||||
- Old & Busted:
|
||||
- `combinator.value === " "`
|
||||
- New hotness:
|
||||
- `combinator.value === " " && combinator.spaces.before === " "`
|
||||
* `.a /*comment*/.b`: A comment at the end of the combinator causes extra space to become after space.
|
||||
- Old & Busted:
|
||||
- `combinator.value === " "`
|
||||
- `combinator.raws.value === " /*comment/"`
|
||||
- New hotness:
|
||||
- `combinator.value === " "`
|
||||
- `combinator.spaces.after === " "`
|
||||
- `combinator.raws.spaces.after === " /*comment*/"`
|
||||
* `.a<newline>.b`: whitespace that doesn't start or end with a single space character is stored as a raw value.
|
||||
- Old & Busted:
|
||||
- `combinator.value === "\n"`
|
||||
- `combinator.raws.value === undefined`
|
||||
- New hotness:
|
||||
- `combinator.value === " "`
|
||||
- `combinator.raws.value === "\n"`
|
||||
|
||||
### Support for "Named Combinators"
|
||||
|
||||
Although, nonstandard and unlikely to ever become a standard, combinators like `/deep/` and `/for/` are now properly supported.
|
||||
|
||||
Because they've been taken off the standardization track, there is no spec-official name for combinators of the form `/<ident>/`. However, I talked to [Tab Atkins](https://twitter.com/tabatkins) and we agreed to call them "named combinators" so now they are called that.
|
||||
|
||||
Before this release such named combinators were parsed without intention and generated three nodes of type `"tag"` where the first and last nodes had a value of `"/"`.
|
||||
|
||||
* `.a /for/ .b` is parsed as a combinator.
|
||||
- Old & Busted:
|
||||
- `root.nodes[0].nodes[1].type === "tag"`
|
||||
- `root.nodes[0].nodes[1].value === "/"`
|
||||
- New hotness:
|
||||
- `root.nodes[0].nodes[1].type === "combinator"`
|
||||
- `root.nodes[0].nodes[1].value === "/for/"`
|
||||
* `.a /F\6fR/ .b` escapes are handled and uppercase is normalized.
|
||||
- Old & Busted:
|
||||
- `root.nodes[0].nodes[2].type === "tag"`
|
||||
- `root.nodes[0].nodes[2].value === "F\\6fR"`
|
||||
- New hotness:
|
||||
- `root.nodes[0].nodes[1].type === "combinator"`
|
||||
- `root.nodes[0].nodes[1].value === "/for/"`
|
||||
- `root.nodes[0].nodes[1].raws.value === "/F\\6fR/"`
|
||||
|
||||
### Source position checks and lookups
|
||||
|
||||
A new API was added to look up a node based on the source location.
|
||||
|
||||
```js
|
||||
const selectorParser = require("postcss-selector-parser");
|
||||
// You can find the most specific node for any given character
|
||||
let combinator = selectorParser.astSync(".a > .b").atPosition(1,4);
|
||||
combinator.toString() === " > ";
|
||||
// You can check if a node includes a specific character
|
||||
// Whitespace surrounding the node that is owned by that node
|
||||
// is included in the check.
|
||||
[2,3,4,5,6].map(column => combinator.isAtPosition(1, column));
|
||||
// => [false, true, true, true, false]
|
||||
```
|
||||
|
||||
# 4.0.0
|
||||
|
||||
This release has **BREAKING CHANGES** that were required to fix bugs regarding values with escape sequences. Please read carefully.
|
||||
|
||||
* **Identifiers with escapes** - CSS escape sequences are now hidden from the public API by default.
|
||||
The normal value of a node like a class name or ID, or an aspect of a node such as attribute
|
||||
selector's value, is unescaped. Escapes representing Non-ascii characters are unescaped into
|
||||
unicode characters. For example: `bu\tton, .\31 00, #i\2764\FE0Fu, [attr="value is \"quoted\""]`
|
||||
will parse respectively to the values `button`, `100`, `i❤️u`, `value is "quoted"`.
|
||||
The original escape sequences for these values can be found in the corresponding property name
|
||||
in `node.raws`. Where possible, deprecation warnings were added, but the nature
|
||||
of escape handling makes it impossible to detect what is escaped or not. Our expectation is
|
||||
that most users are neither expecting nor handling escape sequences in their use of this library,
|
||||
and so for them, this is a bug fix. Users who are taking care to handle escapes correctly can
|
||||
now update their code to remove the escape handling and let us do it for them.
|
||||
|
||||
* **Mutating values with escapes** - When you make an update to a node property that has escape handling
|
||||
The value is assumed to be unescaped, and any special characters are escaped automatically and
|
||||
the corresponding `raws` value is immediately updated. This can result in changes to the original
|
||||
escape format. Where the exact value of the escape sequence is important there are methods that
|
||||
allow both values to be set in conjunction. There are a number of new convenience methods for
|
||||
manipulating values that involve escapes, especially for attributes values where the quote mark
|
||||
is involved. See https://github.com/postcss/postcss-selector-parser/pull/133 for an extensive
|
||||
write-up on these changes.
|
||||
|
||||
|
||||
**Upgrade/API Example**
|
||||
|
||||
In `3.x` there was no unescape handling and internal consistency of several properties was the caller's job to maintain. It was very easy for the developer
|
||||
to create a CSS file that did not parse correctly when some types of values
|
||||
were in use.
|
||||
|
||||
```js
|
||||
const selectorParser = require("postcss-selector-parser");
|
||||
let attr = selectorParser.attribute({attribute: "id", operator: "=", value: "a-value"});
|
||||
attr.value; // => "a-value"
|
||||
attr.toString(); // => [id=a-value]
|
||||
// Add quotes to an attribute's value.
|
||||
// All these values have to be set by the caller to be consistent:
|
||||
// no internal consistency is maintained.
|
||||
attr.raws.unquoted = attr.value
|
||||
attr.value = "'" + attr.value + "'";
|
||||
attr.value; // => "'a-value'"
|
||||
attr.quoted = true;
|
||||
attr.toString(); // => "[id='a-value']"
|
||||
```
|
||||
|
||||
In `4.0` there is a convenient API for setting and mutating values
|
||||
that may need escaping. Especially for attributes.
|
||||
|
||||
```js
|
||||
const selectorParser = require("postcss-selector-parser");
|
||||
|
||||
// The constructor requires you specify the exact escape sequence
|
||||
let className = selectorParser.className({value: "illegal class name", raws: {value: "illegal\\ class\\ name"}});
|
||||
className.toString(); // => '.illegal\\ class\\ name'
|
||||
|
||||
// So it's better to set the value as a property
|
||||
className = selectorParser.className();
|
||||
// Most properties that deal with identifiers work like this
|
||||
className.value = "escape for me";
|
||||
className.value; // => 'escape for me'
|
||||
className.toString(); // => '.escape\\ for\\ me'
|
||||
|
||||
// emoji and all non-ascii are escaped to ensure it works in every css file.
|
||||
className.value = "😱🦄😍";
|
||||
className.value; // => '😱🦄😍'
|
||||
className.toString(); // => '.\\1F631\\1F984\\1F60D'
|
||||
|
||||
// you can control the escape sequence if you want, or do bad bad things
|
||||
className.setPropertyAndEscape('value', 'xxxx', 'yyyy');
|
||||
className.value; // => "xxxx"
|
||||
className.toString(); // => ".yyyy"
|
||||
|
||||
// Pass a value directly through to the css output without escaping it.
|
||||
className.setPropertyWithoutEscape('value', '$REPLACE_ME$');
|
||||
className.value; // => "$REPLACE_ME$"
|
||||
className.toString(); // => ".$REPLACE_ME$"
|
||||
|
||||
// The biggest changes are to the Attribute class
|
||||
// passing quoteMark explicitly is required to avoid a deprecation warning.
|
||||
let attr = selectorParser.attribute({attribute: "id", operator: "=", value: "a-value", quoteMark: null});
|
||||
attr.toString(); // => "[id=a-value]"
|
||||
// Get the value with quotes on it and any necessary escapes.
|
||||
// This is the same as reading attr.value in 3.x.
|
||||
attr.getQuotedValue(); // => "a-value";
|
||||
attr.quoteMark; // => null
|
||||
|
||||
// Add quotes to an attribute's value.
|
||||
attr.quoteMark = "'"; // This is all that's required.
|
||||
attr.toString(); // => "[id='a-value']"
|
||||
attr.quoted; // => true
|
||||
// The value is still the same, only the quotes have changed.
|
||||
attr.value; // => a-value
|
||||
attr.getQuotedValue(); // => "'a-value'";
|
||||
|
||||
// deprecated assignment, no warning because there's no escapes
|
||||
attr.value = "new-value";
|
||||
// no quote mark is needed so it is removed
|
||||
attr.getQuotedValue(); // => "new-value";
|
||||
|
||||
// deprecated assignment,
|
||||
attr.value = "\"a 'single quoted' value\"";
|
||||
// > (node:27859) DeprecationWarning: Assigning an attribute a value containing characters that might need to be escaped is deprecated. Call attribute.setValue() instead.
|
||||
attr.getQuotedValue(); // => '"a \'single quoted\' value"';
|
||||
// quote mark inferred from first and last characters.
|
||||
attr.quoteMark; // => '"'
|
||||
|
||||
// setValue takes options to make manipulating the value simple.
|
||||
attr.setValue('foo', {smart: true});
|
||||
// foo doesn't require any escapes or quotes.
|
||||
attr.toString(); // => '[id=foo]'
|
||||
attr.quoteMark; // => null
|
||||
|
||||
// An explicit quote mark can be specified
|
||||
attr.setValue('foo', {quoteMark: '"'});
|
||||
attr.toString(); // => '[id="foo"]'
|
||||
|
||||
// preserves quote mark by default
|
||||
attr.setValue('bar');
|
||||
attr.toString(); // => '[id="bar"]'
|
||||
attr.quoteMark = null;
|
||||
attr.toString(); // => '[id=bar]'
|
||||
|
||||
// with no arguments, it preserves quote mark even when it's not a great idea
|
||||
attr.setValue('a value \n that should be quoted');
|
||||
attr.toString(); // => '[id=a\\ value\\ \\A\\ that\\ should\\ be\\ quoted]'
|
||||
|
||||
// smart preservation with a specified default
|
||||
attr.setValue('a value \n that should be quoted', {smart: true, preferCurrentQuoteMark: true, quoteMark: "'"});
|
||||
// => "[id='a value \\A that should be quoted']"
|
||||
attr.quoteMark = '"';
|
||||
// => '[id="a value \\A that should be quoted"]'
|
||||
|
||||
// this keeps double quotes because it wants to quote the value and the existing value has double quotes.
|
||||
attr.setValue('this should be quoted', {smart: true, preferCurrentQuoteMark: true, quoteMark: "'"});
|
||||
// => '[id="this should be quoted"]'
|
||||
|
||||
// picks single quotes because the value has double quotes
|
||||
attr.setValue('a "double quoted" value', {smart: true, preferCurrentQuoteMark: true, quoteMark: "'"});
|
||||
// => "[id='a "double quoted" value']"
|
||||
|
||||
// setPropertyAndEscape lets you do anything you want. Even things that are a bad idea and illegal.
|
||||
attr.setPropertyAndEscape('value', 'xxxx', 'the password is 42');
|
||||
attr.value; // => "xxxx"
|
||||
attr.toString(); // => "[id=the password is 42]"
|
||||
|
||||
// Pass a value directly through to the css output without escaping it.
|
||||
attr.setPropertyWithoutEscape('value', '$REPLACEMENT$');
|
||||
attr.value; // => "$REPLACEMENT$"
|
||||
attr.toString(); // => "[id=$REPLACEMENT$]"
|
||||
```
|
||||
|
||||
# 3.1.2
|
||||
|
||||
* Fix: Removed dot-prop dependency since it's no longer written in es5.
|
||||
|
||||
# 3.1.1
|
||||
|
||||
* Fix: typescript definitions weren't in the published package.
|
||||
|
||||
# 3.1.0
|
||||
|
||||
* Fixed numerous bugs in attribute nodes relating to the handling of comments
|
||||
and whitespace. There's significant changes to `attrNode.spaces` and `attrNode.raws` since the `3.0.0` release.
|
||||
* Added `Attribute#offsetOf(part)` to get the offset location of
|
||||
attribute parts like `"operator"` and `"value"`. This is most
|
||||
often added to `Attribute#sourceIndex` for error reporting.
|
||||
|
||||
# 3.0.0
|
||||
|
||||
## Breaking changes
|
||||
|
||||
* Some tweaks to the tokenizer/attribute selector parsing mean that whitespace
|
||||
locations might be slightly different to the 2.x code.
|
||||
* Better attribute selector parsing with more validation; postcss-selector-parser
|
||||
no longer uses regular expressions to parse attribute selectors.
|
||||
* Added an async API (thanks to @jacobp100); the default `process` API is now
|
||||
async, and the sync API is now accessed through `processSync` instead.
|
||||
* `process()` and `processSync()` now return a string instead of the Processor
|
||||
instance.
|
||||
* Tweaks handling of Less interpolation (thanks to @jwilsson).
|
||||
* Removes support for Node 0.12.
|
||||
|
||||
## Other changes
|
||||
|
||||
* `ast()` and `astSync()` methods have been added to the `Processor`. These
|
||||
return the `Root` node of the selectors after processing them.
|
||||
* `transform()` and `transformSync()` methods have been added to the
|
||||
`Processor`. These return the value returned by the processor callback
|
||||
after processing the selectors.
|
||||
* Set the parent when inserting a node (thanks to @chriseppstein).
|
||||
* Correctly adjust indices when using insertBefore/insertAfter (thanks to @tivac).
|
||||
* Fixes handling of namespaces with qualified tag selectors.
|
||||
* `process`, `ast` and `transform` (and their sync variants) now accept a
|
||||
`postcss` rule node. When provided, better errors are generated and selector
|
||||
processing is automatically set back to the rule selector (unless the `updateSelector` option is set to `false`.)
|
||||
* Now more memory efficient when tokenizing selectors.
|
||||
|
||||
### Upgrade hints
|
||||
|
||||
The pattern of:
|
||||
|
||||
`rule.selector = processor.process(rule.selector).result.toString();`
|
||||
|
||||
is now:
|
||||
|
||||
`processor.processSync(rule)`
|
||||
|
||||
# 2.2.3
|
||||
|
||||
* Resolves an issue where the parser would not reduce multiple spaces between an
|
||||
ampersand and another simple selector in lossy mode (thanks to @adam-26).
|
||||
|
||||
# 2.2.2
|
||||
|
||||
* No longer hangs on an unescaped semicolon; instead the parser will throw
|
||||
an exception for these cases.
|
||||
|
||||
# 2.2.1
|
||||
|
||||
* Allows a consumer to specify whitespace tokens when creating a new Node
|
||||
(thanks to @Semigradsky).
|
||||
|
||||
# 2.2.0
|
||||
|
||||
* Added a new option to normalize whitespace when parsing the selector string
|
||||
(thanks to @adam-26).
|
||||
|
||||
# 2.1.1
|
||||
|
||||
* Better unquoted value handling within attribute selectors
|
||||
(thanks to @evilebottnawi).
|
||||
|
||||
# 2.1.0
|
||||
|
||||
* Added: Use string constants for all node types & expose them on the main
|
||||
parser instance (thanks to @Aweary).
|
||||
|
||||
# 2.0.0
|
||||
|
||||
This release contains the following breaking changes:
|
||||
|
||||
* Renamed all `eachInside` iterators to `walk`. For example, `eachTag` is now
|
||||
`walkTags`, and `eachInside` is now `walk`.
|
||||
* Renamed `Node#removeSelf()` to `Node#remove()`.
|
||||
* Renamed `Container#remove()` to `Container#removeChild()`.
|
||||
* Renamed `Node#raw` to `Node#raws` (thanks to @davidtheclark).
|
||||
* Now parses `&` as the *nesting* selector, rather than a *tag* selector.
|
||||
* Fixes misinterpretation of Sass interpolation (e.g. `#{foo}`) as an
|
||||
id selector (thanks to @davidtheclark).
|
||||
|
||||
and;
|
||||
|
||||
* Fixes parsing of attribute selectors with equals signs in them
|
||||
(e.g. `[data-attr="foo=bar"]`) (thanks to @montmanu).
|
||||
* Adds `quoted` and `raw.unquoted` properties to attribute nodes
|
||||
(thanks to @davidtheclark).
|
||||
|
||||
# 1.3.3
|
||||
|
||||
* Fixes an infinite loop on `)` and `]` tokens when they had no opening pairs.
|
||||
Now postcss-selector-parser will throw when it encounters these lone tokens.
|
||||
|
||||
# 1.3.2
|
||||
|
||||
* Now uses plain integers rather than `str.charCodeAt(0)` for compiled builds.
|
||||
|
||||
# 1.3.1
|
||||
|
||||
* Update flatten to v1.x (thanks to @shinnn).
|
||||
|
||||
# 1.3.0
|
||||
|
||||
* Adds a new node type, `String`, to fix a crash on selectors such as
|
||||
`foo:bar("test")`.
|
||||
|
||||
# 1.2.1
|
||||
|
||||
* Fixes a crash when the parser encountered a trailing combinator.
|
||||
|
||||
# 1.2.0
|
||||
|
||||
* A more descriptive error is thrown when the parser expects to find a
|
||||
pseudo-class/pseudo-element (thanks to @ashelley).
|
||||
* Adds support for line/column locations for selector nodes, as well as a
|
||||
`Node#sourceIndex` method (thanks to @davidtheclark).
|
||||
|
||||
# 1.1.4
|
||||
|
||||
* Fixes a crash when a selector started with a `>` combinator. The module will
|
||||
now no longer throw if a selector has a leading/trailing combinator node.
|
||||
|
||||
# 1.1.3
|
||||
|
||||
* Fixes a crash on `@` tokens.
|
||||
|
||||
# 1.1.2
|
||||
|
||||
* Fixes an infinite loop caused by using parentheses in a non-pseudo element
|
||||
context.
|
||||
|
||||
# 1.1.1
|
||||
|
||||
* Fixes a crash when a backslash ended a selector string.
|
||||
|
||||
# 1.1.0
|
||||
|
||||
* Adds support for replacing multiple nodes at once with `replaceWith`
|
||||
(thanks to @jonathantneal).
|
||||
* Parser no longer throws on sequential IDs and trailing commas, to support
|
||||
parsing of selector hacks.
|
||||
|
||||
# 1.0.1
|
||||
|
||||
* Fixes using `insertAfter` and `insertBefore` during iteration.
|
||||
|
||||
# 1.0.0
|
||||
|
||||
* Adds `clone` and `replaceWith` methods to nodes.
|
||||
* Adds `insertBefore` and `insertAfter` to containers.
|
||||
* Stabilises API.
|
||||
|
||||
# 0.0.5
|
||||
|
||||
* Fixes crash on extra whitespace inside a pseudo selector's parentheses.
|
||||
* Adds sort function to the container class.
|
||||
* Enables the parser to pass its input through without transforming.
|
||||
* Iteration-safe `each` and `eachInside`.
|
||||
|
||||
# 0.0.4
|
||||
|
||||
* Tidy up redundant duplication.
|
||||
* Fixes a bug where the parser would loop infinitely on universal selectors
|
||||
inside pseudo selectors.
|
||||
* Adds `length` getter and `eachInside`, `map`, `reduce` to the container class.
|
||||
* When a selector has been removed from the tree, the root node will no longer
|
||||
cast it to a string.
|
||||
* Adds node type iterators to the container class (e.g. `eachComment`).
|
||||
* Adds filter function to the container class.
|
||||
* Adds split function to the container class.
|
||||
* Create new node types by doing `parser.id(opts)` etc.
|
||||
* Adds support for pseudo classes anywhere in the selector.
|
||||
|
||||
# 0.0.3
|
||||
|
||||
* Adds `next` and `prev` to the node class.
|
||||
* Adds `first` and `last` getters to the container class.
|
||||
* Adds `every` and `some` iterators to the container class.
|
||||
* Add `empty` alias for `removeAll`.
|
||||
* Combinators are now types of node.
|
||||
* Fixes the at method so that it is not an alias for `index`.
|
||||
* Tidy up creation of new nodes in the parser.
|
||||
* Refactors how namespaces are handled for consistency & less redundant code.
|
||||
* Refactors AST to use `nodes` exclusively, and eliminates excessive nesting.
|
||||
* Fixes nested pseudo parsing.
|
||||
* Fixes whitespace parsing.
|
||||
|
||||
# 0.0.2
|
||||
|
||||
* Adds support for namespace selectors.
|
||||
* Adds support for selectors joined by escaped spaces - such as `.\31\ 0`.
|
||||
|
||||
# 0.0.1
|
||||
|
||||
* Initial release.
|
||||
22
templates/node_modules/postcss-selector-parser/LICENSE-MIT
generated
vendored
Normal file
22
templates/node_modules/postcss-selector-parser/LICENSE-MIT
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) Ben Briggs <beneb.info@gmail.com> (http://beneb.info)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
49
templates/node_modules/postcss-selector-parser/README.md
generated
vendored
Normal file
49
templates/node_modules/postcss-selector-parser/README.md
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# postcss-selector-parser [](https://travis-ci.org/postcss/postcss-selector-parser)
|
||||
|
||||
> Selector parser with built in methods for working with selector strings.
|
||||
|
||||
## Install
|
||||
|
||||
With [npm](https://npmjs.com/package/postcss-selector-parser) do:
|
||||
|
||||
```
|
||||
npm install postcss-selector-parser
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```js
|
||||
const parser = require('postcss-selector-parser');
|
||||
const transform = selectors => {
|
||||
selectors.walk(selector => {
|
||||
// do something with the selector
|
||||
console.log(String(selector))
|
||||
});
|
||||
};
|
||||
|
||||
const transformed = parser(transform).processSync('h1, h2, h3');
|
||||
```
|
||||
|
||||
To normalize selector whitespace:
|
||||
|
||||
```js
|
||||
const parser = require('postcss-selector-parser');
|
||||
const normalized = parser().processSync('h1, h2, h3', {lossless: false});
|
||||
// -> h1,h2,h3
|
||||
```
|
||||
|
||||
Async support is provided through `parser.process` and will resolve a Promise
|
||||
with the resulting selector string.
|
||||
|
||||
## API
|
||||
|
||||
Please see [API.md](API.md).
|
||||
|
||||
## Credits
|
||||
|
||||
* Huge thanks to Andrey Sitnik (@ai) for work on PostCSS which helped
|
||||
accelerate this module's development.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
78
templates/node_modules/postcss-selector-parser/package.json
generated
vendored
Normal file
78
templates/node_modules/postcss-selector-parser/package.json
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "postcss-selector-parser",
|
||||
"version": "6.0.10",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.11.6",
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/eslint-parser": "^7.11.5",
|
||||
"@babel/eslint-plugin": "^7.11.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/preset-env": "^7.11.5",
|
||||
"@babel/register": "^7.11.5",
|
||||
"ava": "^3.12.1",
|
||||
"babel-plugin-add-module-exports": "^1.0.4",
|
||||
"coveralls": "^3.1.0",
|
||||
"del-cli": "^3.0.1",
|
||||
"eslint": "^7.9.0",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"glob": "^7.1.6",
|
||||
"minimist": "^1.2.5",
|
||||
"nyc": "^15.1.0",
|
||||
"postcss": "^8.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "postcss-selector-parser.d.ts",
|
||||
"files": [
|
||||
"API.md",
|
||||
"CHANGELOG.md",
|
||||
"LICENSE-MIT",
|
||||
"dist",
|
||||
"postcss-selector-parser.d.ts",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"scripts": {
|
||||
"pretest": "eslint src && tsc --noEmit postcss-selector-parser.d.ts",
|
||||
"prepare": "del-cli dist && BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/",
|
||||
"lintfix": "eslint --fix src",
|
||||
"report": "nyc report --reporter=html",
|
||||
"test": "nyc ava src/__tests__/*.js ",
|
||||
"testone": "ava"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"homepage": "https://github.com/postcss/postcss-selector-parser",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Ben Briggs",
|
||||
"email": "beneb.info@gmail.com",
|
||||
"url": "http://beneb.info"
|
||||
},
|
||||
{
|
||||
"name": "Chris Eppstein",
|
||||
"email": "chris@eppsteins.net",
|
||||
"url": "http://twitter.com/chriseppstein"
|
||||
}
|
||||
],
|
||||
"repository": "postcss/postcss-selector-parser",
|
||||
"ava": {
|
||||
"require": [
|
||||
"@babel/register"
|
||||
],
|
||||
"concurrency": 5,
|
||||
"timeout": "25s"
|
||||
},
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/__tests__"
|
||||
]
|
||||
}
|
||||
}
|
||||
555
templates/node_modules/postcss-selector-parser/postcss-selector-parser.d.ts
generated
vendored
Normal file
555
templates/node_modules/postcss-selector-parser/postcss-selector-parser.d.ts
generated
vendored
Normal file
@@ -0,0 +1,555 @@
|
||||
// Type definitions for postcss-selector-parser 2.2.3
|
||||
// Definitions by: Chris Eppstein <chris@eppsteins.net>
|
||||
|
||||
/*~ Note that ES6 modules cannot directly export callable functions.
|
||||
*~ This file should be imported using the CommonJS-style:
|
||||
*~ import x = require('someLibrary');
|
||||
*~
|
||||
*~ Refer to the documentation to understand common
|
||||
*~ workarounds for this limitation of ES6 modules.
|
||||
*/
|
||||
|
||||
/*~ This declaration specifies that the function
|
||||
*~ is the exported object from the file
|
||||
*/
|
||||
export = parser;
|
||||
|
||||
// A type that's T but not U.
|
||||
type Diff<T, U> = T extends U ? never : T;
|
||||
|
||||
// TODO: Conditional types in TS 1.8 will really clean this up.
|
||||
declare function parser(): parser.Processor<never>;
|
||||
declare function parser<Transform>(processor: parser.AsyncProcessor<Transform>): parser.Processor<Transform, never>;
|
||||
declare function parser(processor: parser.AsyncProcessor<void>): parser.Processor<never, never>;
|
||||
declare function parser<Transform>(processor: parser.SyncProcessor<Transform>): parser.Processor<Transform>;
|
||||
declare function parser(processor: parser.SyncProcessor<void>): parser.Processor<never>;
|
||||
declare function parser<Transform>(processor?: parser.SyncProcessor<Transform> | parser.AsyncProcessor<Transform>): parser.Processor<Transform>;
|
||||
|
||||
/*~ If you want to expose types from your module as well, you can
|
||||
*~ place them in this block. Often you will want to describe the
|
||||
*~ shape of the return type of the function; that type should
|
||||
*~ be declared in here, as this example shows.
|
||||
*/
|
||||
declare namespace parser {
|
||||
/* copied from postcss -- so we don't need to add a dependency */
|
||||
type ErrorOptions = {
|
||||
plugin?: string;
|
||||
word?: string;
|
||||
index?: number
|
||||
};
|
||||
/* the bits we use of postcss.Rule, copied from postcss -- so we don't need to add a dependency */
|
||||
type PostCSSRuleNode = {
|
||||
selector: string
|
||||
/**
|
||||
* @returns postcss.CssSyntaxError but it's a complex object, caller
|
||||
* should cast to it if they have a dependency on postcss.
|
||||
*/
|
||||
error(message: string, options?: ErrorOptions): Error;
|
||||
};
|
||||
/** Accepts a string */
|
||||
type Selectors = string | PostCSSRuleNode
|
||||
type ProcessorFn<ReturnType = void> = (root: parser.Root) => ReturnType;
|
||||
type SyncProcessor<Transform = void> = ProcessorFn<Transform>;
|
||||
type AsyncProcessor<Transform = void> = ProcessorFn<PromiseLike<Transform>>;
|
||||
|
||||
const TAG: "tag";
|
||||
const STRING: "string";
|
||||
const SELECTOR: "selector";
|
||||
const ROOT: "root";
|
||||
const PSEUDO: "pseudo";
|
||||
const NESTING: "nesting";
|
||||
const ID: "id";
|
||||
const COMMENT: "comment";
|
||||
const COMBINATOR: "combinator";
|
||||
const CLASS: "class";
|
||||
const ATTRIBUTE: "attribute";
|
||||
const UNIVERSAL: "universal";
|
||||
|
||||
interface NodeTypes {
|
||||
tag: Tag,
|
||||
string: String,
|
||||
selector: Selector,
|
||||
root: Root,
|
||||
pseudo: Pseudo,
|
||||
nesting: Nesting,
|
||||
id: Identifier,
|
||||
comment: Comment,
|
||||
combinator: Combinator,
|
||||
class: ClassName,
|
||||
attribute: Attribute,
|
||||
universal: Universal
|
||||
}
|
||||
|
||||
type Node = NodeTypes[keyof NodeTypes];
|
||||
|
||||
function isNode(node: any): node is Node;
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* Preserve whitespace when true. Default: false;
|
||||
*/
|
||||
lossless: boolean;
|
||||
/**
|
||||
* When true and a postcss.Rule is passed, set the result of
|
||||
* processing back onto the rule when done. Default: false.
|
||||
*/
|
||||
updateSelector: boolean;
|
||||
}
|
||||
class Processor<
|
||||
TransformType = never,
|
||||
SyncSelectorsType extends Selectors | never = Selectors
|
||||
> {
|
||||
res: Root;
|
||||
readonly result: String;
|
||||
ast(selectors: Selectors, options?: Partial<Options>): Promise<Root>;
|
||||
astSync(selectors: SyncSelectorsType, options?: Partial<Options>): Root;
|
||||
transform(selectors: Selectors, options?: Partial<Options>): Promise<TransformType>;
|
||||
transformSync(selectors: SyncSelectorsType, options?: Partial<Options>): TransformType;
|
||||
process(selectors: Selectors, options?: Partial<Options>): Promise<string>;
|
||||
processSync(selectors: SyncSelectorsType, options?: Partial<Options>): string;
|
||||
}
|
||||
interface ParserOptions {
|
||||
css: string;
|
||||
error: (message: string, options: ErrorOptions) => Error;
|
||||
options: Options;
|
||||
}
|
||||
class Parser {
|
||||
input: ParserOptions;
|
||||
lossy: boolean;
|
||||
position: number;
|
||||
root: Root;
|
||||
selectors: string;
|
||||
current: Selector;
|
||||
constructor(input: ParserOptions);
|
||||
/**
|
||||
* Raises an error, if the processor is invoked on
|
||||
* a postcss Rule node, a better error message is raised.
|
||||
*/
|
||||
error(message: string, options?: ErrorOptions): void;
|
||||
}
|
||||
interface NodeSource {
|
||||
start?: {
|
||||
line: number,
|
||||
column: number
|
||||
},
|
||||
end?: {
|
||||
line: number,
|
||||
column: number
|
||||
}
|
||||
}
|
||||
interface SpaceAround {
|
||||
before: string;
|
||||
after: string;
|
||||
}
|
||||
interface Spaces extends SpaceAround {
|
||||
[spaceType: string]: string | Partial<SpaceAround> | undefined;
|
||||
}
|
||||
interface NodeOptions<Value = string> {
|
||||
value: Value;
|
||||
spaces?: Partial<Spaces>;
|
||||
source?: NodeSource;
|
||||
sourceIndex?: number;
|
||||
}
|
||||
interface Base<
|
||||
Value extends string | undefined = string,
|
||||
ParentType extends Container | undefined = Container | undefined
|
||||
> {
|
||||
type: keyof NodeTypes;
|
||||
parent: ParentType;
|
||||
value: Value;
|
||||
spaces: Spaces;
|
||||
source?: NodeSource;
|
||||
sourceIndex: number;
|
||||
rawSpaceBefore: string;
|
||||
rawSpaceAfter: string;
|
||||
remove(): Node;
|
||||
replaceWith(...nodes: Node[]): Node;
|
||||
next(): Node;
|
||||
prev(): Node;
|
||||
clone(opts: {[override: string]:any}): Node;
|
||||
/**
|
||||
* Return whether this node includes the character at the position of the given line and column.
|
||||
* Returns undefined if the nodes lack sufficient source metadata to determine the position.
|
||||
* @param line 1-index based line number relative to the start of the selector.
|
||||
* @param column 1-index based column number relative to the start of the selector.
|
||||
*/
|
||||
isAtPosition(line: number, column: number): boolean | undefined;
|
||||
/**
|
||||
* Some non-standard syntax doesn't follow normal escaping rules for css,
|
||||
* this allows the escaped value to be specified directly, allowing illegal characters to be
|
||||
* directly inserted into css output.
|
||||
* @param name the property to set
|
||||
* @param value the unescaped value of the property
|
||||
* @param valueEscaped optional. the escaped value of the property.
|
||||
*/
|
||||
setPropertyAndEscape(name: string, value: any, valueEscaped: string): void;
|
||||
/**
|
||||
* When you want a value to passed through to CSS directly. This method
|
||||
* deletes the corresponding raw value causing the stringifier to fallback
|
||||
* to the unescaped value.
|
||||
* @param name the property to set.
|
||||
* @param value The value that is both escaped and unescaped.
|
||||
*/
|
||||
setPropertyWithoutEscape(name: string, value: any): void;
|
||||
/**
|
||||
* Some non-standard syntax doesn't follow normal escaping rules for css.
|
||||
* This allows non standard syntax to be appended to an existing property
|
||||
* by specifying the escaped value. By specifying the escaped value,
|
||||
* illegal characters are allowed to be directly inserted into css output.
|
||||
* @param {string} name the property to set
|
||||
* @param {any} value the unescaped value of the property
|
||||
* @param {string} valueEscaped optional. the escaped value of the property.
|
||||
*/
|
||||
appendToPropertyAndEscape(name: string, value: any, valueEscaped: string): void;
|
||||
toString(): string;
|
||||
}
|
||||
interface ContainerOptions extends NodeOptions {
|
||||
nodes?: Array<Node>;
|
||||
}
|
||||
interface Container<
|
||||
Value extends string | undefined = string,
|
||||
Child extends Node = Node
|
||||
> extends Base<Value> {
|
||||
nodes: Array<Child>;
|
||||
append(selector: Selector): this;
|
||||
prepend(selector: Selector): this;
|
||||
at(index: number): Child;
|
||||
/**
|
||||
* Return the most specific node at the line and column number given.
|
||||
* The source location is based on the original parsed location, locations aren't
|
||||
* updated as selector nodes are mutated.
|
||||
*
|
||||
* Note that this location is relative to the location of the first character
|
||||
* of the selector, and not the location of the selector in the overall document
|
||||
* when used in conjunction with postcss.
|
||||
*
|
||||
* If not found, returns undefined.
|
||||
* @param line The line number of the node to find. (1-based index)
|
||||
* @param col The column number of the node to find. (1-based index)
|
||||
*/
|
||||
atPosition(line: number, column: number): Child;
|
||||
index(child: Child): number;
|
||||
readonly first: Child;
|
||||
readonly last: Child;
|
||||
readonly length: number;
|
||||
removeChild(child: Child): this;
|
||||
removeAll(): Container;
|
||||
empty(): Container;
|
||||
insertAfter(oldNode: Child, newNode: Child): this;
|
||||
insertBefore(oldNode: Child, newNode: Child): this;
|
||||
each(callback: (node: Child) => boolean | void): boolean | undefined;
|
||||
walk(
|
||||
callback: (node: Node) => boolean | void
|
||||
): boolean | undefined;
|
||||
walkAttributes(
|
||||
callback: (node: Attribute) => boolean | void
|
||||
): boolean | undefined;
|
||||
walkClasses(
|
||||
callback: (node: ClassName) => boolean | void
|
||||
): boolean | undefined;
|
||||
walkCombinators(
|
||||
callback: (node: Combinator) => boolean | void
|
||||
): boolean | undefined;
|
||||
walkComments(
|
||||
callback: (node: Comment) => boolean | void
|
||||
): boolean | undefined;
|
||||
walkIds(
|
||||
callback: (node: Identifier) => boolean | void
|
||||
): boolean | undefined;
|
||||
walkNesting(
|
||||
callback: (node: Nesting) => boolean | void
|
||||
): boolean | undefined;
|
||||
walkPseudos(
|
||||
callback: (node: Pseudo) => boolean | void
|
||||
): boolean | undefined;
|
||||
walkTags(callback: (node: Tag) => boolean | void): boolean | undefined;
|
||||
split(callback: (node: Child) => boolean): [Child[], Child[]];
|
||||
map<T>(callback: (node: Child) => T): T[];
|
||||
reduce(
|
||||
callback: (
|
||||
previousValue: Child,
|
||||
currentValue: Child,
|
||||
currentIndex: number,
|
||||
array: readonly Child[]
|
||||
) => Child
|
||||
): Child;
|
||||
reduce(
|
||||
callback: (
|
||||
previousValue: Child,
|
||||
currentValue: Child,
|
||||
currentIndex: number,
|
||||
array: readonly Child[]
|
||||
) => Child,
|
||||
initialValue: Child
|
||||
): Child;
|
||||
reduce<T>(
|
||||
callback: (
|
||||
previousValue: T,
|
||||
currentValue: Child,
|
||||
currentIndex: number,
|
||||
array: readonly Child[]
|
||||
) => T,
|
||||
initialValue: T
|
||||
): T;
|
||||
every(callback: (node: Child) => boolean): boolean;
|
||||
some(callback: (node: Child) => boolean): boolean;
|
||||
filter(callback: (node: Child) => boolean): Child[];
|
||||
sort(callback: (nodeA: Child, nodeB: Child) => number): Child[];
|
||||
toString(): string;
|
||||
}
|
||||
function isContainer(node: any): node is Root | Selector | Pseudo;
|
||||
|
||||
interface NamespaceOptions<Value extends string | undefined = string> extends NodeOptions<Value> {
|
||||
namespace?: string | true;
|
||||
}
|
||||
interface Namespace<Value extends string | undefined = string> extends Base<Value> {
|
||||
/** alias for namespace */
|
||||
ns: string | true;
|
||||
/**
|
||||
* namespace prefix.
|
||||
*/
|
||||
namespace: string | true;
|
||||
/**
|
||||
* If a namespace exists, prefix the value provided with it, separated by |.
|
||||
*/
|
||||
qualifiedName(value: string): string;
|
||||
/**
|
||||
* A string representing the namespace suitable for output.
|
||||
*/
|
||||
readonly namespaceString: string;
|
||||
}
|
||||
function isNamespace(node: any): node is Attribute | Tag;
|
||||
|
||||
interface Root extends Container<undefined, Selector> {
|
||||
type: "root";
|
||||
/**
|
||||
* Raises an error, if the processor is invoked on
|
||||
* a postcss Rule node, a better error message is raised.
|
||||
*/
|
||||
error(message: string, options?: ErrorOptions): Error;
|
||||
nodeAt(line: number, column: number): Node
|
||||
}
|
||||
function root(opts: ContainerOptions): Root;
|
||||
function isRoot(node: any): node is Root;
|
||||
|
||||
interface _Selector<S> extends Container<string, Diff<Node, S>> {
|
||||
type: "selector";
|
||||
}
|
||||
type Selector = _Selector<Selector>;
|
||||
function selector(opts: ContainerOptions): Selector;
|
||||
function isSelector(node: any): node is Selector;
|
||||
|
||||
interface CombinatorRaws {
|
||||
value?: string;
|
||||
spaces?: {
|
||||
before?: string;
|
||||
after?: string;
|
||||
};
|
||||
}
|
||||
interface Combinator extends Base {
|
||||
type: "combinator";
|
||||
raws?: CombinatorRaws;
|
||||
}
|
||||
function combinator(opts: NodeOptions): Combinator;
|
||||
function isCombinator(node: any): node is Combinator;
|
||||
|
||||
interface ClassName extends Base {
|
||||
type: "class";
|
||||
}
|
||||
function className(opts: NamespaceOptions): ClassName;
|
||||
function isClassName(node: any): node is ClassName;
|
||||
|
||||
type AttributeOperator = "=" | "~=" | "|=" | "^=" | "$=" | "*=";
|
||||
type QuoteMark = '"' | "'" | null;
|
||||
interface PreferredQuoteMarkOptions {
|
||||
quoteMark?: QuoteMark;
|
||||
preferCurrentQuoteMark?: boolean;
|
||||
}
|
||||
interface SmartQuoteMarkOptions extends PreferredQuoteMarkOptions {
|
||||
smart?: boolean;
|
||||
}
|
||||
interface AttributeOptions extends NamespaceOptions<string | undefined> {
|
||||
attribute: string;
|
||||
operator?: AttributeOperator;
|
||||
insensitive?: boolean;
|
||||
quoteMark?: QuoteMark;
|
||||
/** @deprecated Use quoteMark instead. */
|
||||
quoted?: boolean;
|
||||
spaces?: {
|
||||
before?: string;
|
||||
after?: string;
|
||||
attribute?: Partial<SpaceAround>;
|
||||
operator?: Partial<SpaceAround>;
|
||||
value?: Partial<SpaceAround>;
|
||||
insensitive?: Partial<SpaceAround>;
|
||||
}
|
||||
raws: {
|
||||
unquoted?: string;
|
||||
attribute?: string;
|
||||
operator?: string;
|
||||
value?: string;
|
||||
insensitive?: string;
|
||||
spaces?: {
|
||||
attribute?: Partial<Spaces>;
|
||||
operator?: Partial<Spaces>;
|
||||
value?: Partial<Spaces>;
|
||||
insensitive?: Partial<Spaces>;
|
||||
}
|
||||
};
|
||||
}
|
||||
interface Attribute extends Namespace<string | undefined> {
|
||||
type: "attribute";
|
||||
attribute: string;
|
||||
operator?: AttributeOperator;
|
||||
insensitive?: boolean;
|
||||
quoteMark: QuoteMark;
|
||||
quoted?: boolean;
|
||||
spaces: {
|
||||
before: string;
|
||||
after: string;
|
||||
attribute?: Partial<Spaces>;
|
||||
operator?: Partial<Spaces>;
|
||||
value?: Partial<Spaces>;
|
||||
insensitive?: Partial<Spaces>;
|
||||
}
|
||||
raws: {
|
||||
/** @deprecated The attribute value is unquoted, use that instead.. */
|
||||
unquoted?: string;
|
||||
attribute?: string;
|
||||
operator?: string;
|
||||
/** The value of the attribute with quotes and escapes. */
|
||||
value?: string;
|
||||
insensitive?: string;
|
||||
spaces?: {
|
||||
attribute?: Partial<Spaces>;
|
||||
operator?: Partial<Spaces>;
|
||||
value?: Partial<Spaces>;
|
||||
insensitive?: Partial<Spaces>;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* The attribute name after having been qualified with a namespace.
|
||||
*/
|
||||
readonly qualifiedAttribute: string;
|
||||
|
||||
/**
|
||||
* The case insensitivity flag or an empty string depending on whether this
|
||||
* attribute is case insensitive.
|
||||
*/
|
||||
readonly insensitiveFlag : 'i' | '';
|
||||
|
||||
/**
|
||||
* Returns the attribute's value quoted such that it would be legal to use
|
||||
* in the value of a css file. The original value's quotation setting
|
||||
* used for stringification is left unchanged. See `setValue(value, options)`
|
||||
* if you want to control the quote settings of a new value for the attribute or
|
||||
* `set quoteMark(mark)` if you want to change the quote settings of the current
|
||||
* value.
|
||||
*
|
||||
* You can also change the quotation used for the current value by setting quoteMark.
|
||||
**/
|
||||
getQuotedValue(options?: SmartQuoteMarkOptions): string;
|
||||
|
||||
/**
|
||||
* Set the unescaped value with the specified quotation options. The value
|
||||
* provided must not include any wrapping quote marks -- those quotes will
|
||||
* be interpreted as part of the value and escaped accordingly.
|
||||
* @param value
|
||||
*/
|
||||
setValue(value: string, options?: SmartQuoteMarkOptions): void;
|
||||
|
||||
/**
|
||||
* Intelligently select a quoteMark value based on the value's contents. If
|
||||
* the value is a legal CSS ident, it will not be quoted. Otherwise a quote
|
||||
* mark will be picked that minimizes the number of escapes.
|
||||
*
|
||||
* If there's no clear winner, the quote mark from these options is used,
|
||||
* then the source quote mark (this is inverted if `preferCurrentQuoteMark` is
|
||||
* true). If the quoteMark is unspecified, a double quote is used.
|
||||
**/
|
||||
smartQuoteMark(options: PreferredQuoteMarkOptions): QuoteMark;
|
||||
|
||||
/**
|
||||
* Selects the preferred quote mark based on the options and the current quote mark value.
|
||||
* If you want the quote mark to depend on the attribute value, call `smartQuoteMark(opts)`
|
||||
* instead.
|
||||
*/
|
||||
preferredQuoteMark(options: PreferredQuoteMarkOptions): QuoteMark
|
||||
|
||||
/**
|
||||
* returns the offset of the attribute part specified relative to the
|
||||
* start of the node of the output string.
|
||||
*
|
||||
* * "ns" - alias for "namespace"
|
||||
* * "namespace" - the namespace if it exists.
|
||||
* * "attribute" - the attribute name
|
||||
* * "attributeNS" - the start of the attribute or its namespace
|
||||
* * "operator" - the match operator of the attribute
|
||||
* * "value" - The value (string or identifier)
|
||||
* * "insensitive" - the case insensitivity flag;
|
||||
* @param part One of the possible values inside an attribute.
|
||||
* @returns -1 if the name is invalid or the value doesn't exist in this attribute.
|
||||
*/
|
||||
offsetOf(part: "ns" | "namespace" | "attribute" | "attributeNS" | "operator" | "value" | "insensitive"): number;
|
||||
}
|
||||
function attribute(opts: AttributeOptions): Attribute;
|
||||
function isAttribute(node: any): node is Attribute;
|
||||
|
||||
interface Pseudo extends Container<string, Selector> {
|
||||
type: "pseudo";
|
||||
}
|
||||
function pseudo(opts: ContainerOptions): Pseudo;
|
||||
/**
|
||||
* Checks wether the node is the Psuedo subtype of node.
|
||||
*/
|
||||
function isPseudo(node: any): node is Pseudo;
|
||||
|
||||
/**
|
||||
* Checks wether the node is, specifically, a pseudo element instead of
|
||||
* pseudo class.
|
||||
*/
|
||||
function isPseudoElement(node: any): node is Pseudo;
|
||||
|
||||
/**
|
||||
* Checks wether the node is, specifically, a pseudo class instead of
|
||||
* pseudo element.
|
||||
*/
|
||||
function isPseudoClass(node: any): node is Pseudo;
|
||||
|
||||
|
||||
interface Tag extends Namespace {
|
||||
type: "tag";
|
||||
}
|
||||
function tag(opts: NamespaceOptions): Tag;
|
||||
function isTag(node: any): node is Tag;
|
||||
|
||||
interface Comment extends Base {
|
||||
type: "comment";
|
||||
}
|
||||
function comment(opts: NodeOptions): Comment;
|
||||
function isComment(node: any): node is Comment;
|
||||
|
||||
interface Identifier extends Base {
|
||||
type: "id";
|
||||
}
|
||||
function id(opts: any): any;
|
||||
function isIdentifier(node: any): node is Identifier;
|
||||
|
||||
interface Nesting extends Base {
|
||||
type: "nesting";
|
||||
}
|
||||
function nesting(opts: any): any;
|
||||
function isNesting(node: any): node is Nesting;
|
||||
|
||||
interface String extends Base {
|
||||
type: "string";
|
||||
}
|
||||
function string(opts: NodeOptions): String;
|
||||
function isString(node: any): node is String;
|
||||
|
||||
interface Universal extends Base {
|
||||
type: "universal";
|
||||
}
|
||||
function universal(opts?: NamespaceOptions): any;
|
||||
function isUniversal(node: any): node is Universal;
|
||||
}
|
||||
21
templates/node_modules/tailwindcss/LICENSE
generated
vendored
Normal file
21
templates/node_modules/tailwindcss/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Tailwind Labs, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
36
templates/node_modules/tailwindcss/README.md
generated
vendored
Normal file
36
templates/node_modules/tailwindcss/README.md
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<p align="center">
|
||||
<a href="https://tailwindcss.com" target="_blank">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-light.svg">
|
||||
<img alt="Tailwind CSS" src="https://raw.githubusercontent.com/tailwindlabs/tailwindcss/HEAD/.github/logo-light.svg" width="350" height="70" style="max-width: 100%;">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
A utility-first CSS framework for rapidly building custom user interfaces.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/tailwindlabs/tailwindcss/actions"><img src="https://img.shields.io/github/actions/workflow/status/tailwindlabs/tailwindcss/ci.yml?branch=next" alt="Build Status"></a>
|
||||
<a href="https://www.npmjs.com/package/tailwindcss"><img src="https://img.shields.io/npm/dt/tailwindcss.svg" alt="Total Downloads"></a>
|
||||
<a href="https://github.com/tailwindcss/tailwindcss/releases"><img src="https://img.shields.io/npm/v/tailwindcss.svg" alt="Latest Release"></a>
|
||||
<a href="https://github.com/tailwindcss/tailwindcss/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/tailwindcss.svg" alt="License"></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
For full documentation, visit [tailwindcss.com](https://tailwindcss.com).
|
||||
|
||||
## Community
|
||||
|
||||
For help, discussion about best practices, or feature ideas:
|
||||
|
||||
[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions)
|
||||
|
||||
## Contributing
|
||||
|
||||
If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindcss/tailwindcss/blob/next/.github/CONTRIBUTING.md) **before submitting a pull request**.
|
||||
896
templates/node_modules/tailwindcss/index.css
generated
vendored
Normal file
896
templates/node_modules/tailwindcss/index.css
generated
vendored
Normal file
@@ -0,0 +1,896 @@
|
||||
@layer theme, base, components, utilities;
|
||||
|
||||
@layer theme {
|
||||
@theme default {
|
||||
--font-sans:
|
||||
ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
|
||||
--color-red-50: oklch(97.1% 0.013 17.38);
|
||||
--color-red-100: oklch(93.6% 0.032 17.717);
|
||||
--color-red-200: oklch(88.5% 0.062 18.334);
|
||||
--color-red-300: oklch(80.8% 0.114 19.571);
|
||||
--color-red-400: oklch(70.4% 0.191 22.216);
|
||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||
--color-red-600: oklch(57.7% 0.245 27.325);
|
||||
--color-red-700: oklch(50.5% 0.213 27.518);
|
||||
--color-red-800: oklch(44.4% 0.177 26.899);
|
||||
--color-red-900: oklch(39.6% 0.141 25.723);
|
||||
--color-red-950: oklch(25.8% 0.092 26.042);
|
||||
|
||||
--color-orange-50: oklch(98% 0.016 73.684);
|
||||
--color-orange-100: oklch(95.4% 0.038 75.164);
|
||||
--color-orange-200: oklch(90.1% 0.076 70.697);
|
||||
--color-orange-300: oklch(83.7% 0.128 66.29);
|
||||
--color-orange-400: oklch(75% 0.183 55.934);
|
||||
--color-orange-500: oklch(70.5% 0.213 47.604);
|
||||
--color-orange-600: oklch(64.6% 0.222 41.116);
|
||||
--color-orange-700: oklch(55.3% 0.195 38.402);
|
||||
--color-orange-800: oklch(47% 0.157 37.304);
|
||||
--color-orange-900: oklch(40.8% 0.123 38.172);
|
||||
--color-orange-950: oklch(26.6% 0.079 36.259);
|
||||
|
||||
--color-amber-50: oklch(98.7% 0.022 95.277);
|
||||
--color-amber-100: oklch(96.2% 0.059 95.617);
|
||||
--color-amber-200: oklch(92.4% 0.12 95.746);
|
||||
--color-amber-300: oklch(87.9% 0.169 91.605);
|
||||
--color-amber-400: oklch(82.8% 0.189 84.429);
|
||||
--color-amber-500: oklch(76.9% 0.188 70.08);
|
||||
--color-amber-600: oklch(66.6% 0.179 58.318);
|
||||
--color-amber-700: oklch(55.5% 0.163 48.998);
|
||||
--color-amber-800: oklch(47.3% 0.137 46.201);
|
||||
--color-amber-900: oklch(41.4% 0.112 45.904);
|
||||
--color-amber-950: oklch(27.9% 0.077 45.635);
|
||||
|
||||
--color-yellow-50: oklch(98.7% 0.026 102.212);
|
||||
--color-yellow-100: oklch(97.3% 0.071 103.193);
|
||||
--color-yellow-200: oklch(94.5% 0.129 101.54);
|
||||
--color-yellow-300: oklch(90.5% 0.182 98.111);
|
||||
--color-yellow-400: oklch(85.2% 0.199 91.936);
|
||||
--color-yellow-500: oklch(79.5% 0.184 86.047);
|
||||
--color-yellow-600: oklch(68.1% 0.162 75.834);
|
||||
--color-yellow-700: oklch(55.4% 0.135 66.442);
|
||||
--color-yellow-800: oklch(47.6% 0.114 61.907);
|
||||
--color-yellow-900: oklch(42.1% 0.095 57.708);
|
||||
--color-yellow-950: oklch(28.6% 0.066 53.813);
|
||||
|
||||
--color-lime-50: oklch(98.6% 0.031 120.757);
|
||||
--color-lime-100: oklch(96.7% 0.067 122.328);
|
||||
--color-lime-200: oklch(93.8% 0.127 124.321);
|
||||
--color-lime-300: oklch(89.7% 0.196 126.665);
|
||||
--color-lime-400: oklch(84.1% 0.238 128.85);
|
||||
--color-lime-500: oklch(76.8% 0.233 130.85);
|
||||
--color-lime-600: oklch(64.8% 0.2 131.684);
|
||||
--color-lime-700: oklch(53.2% 0.157 131.589);
|
||||
--color-lime-800: oklch(45.3% 0.124 130.933);
|
||||
--color-lime-900: oklch(40.5% 0.101 131.063);
|
||||
--color-lime-950: oklch(27.4% 0.072 132.109);
|
||||
|
||||
--color-green-50: oklch(98.2% 0.018 155.826);
|
||||
--color-green-100: oklch(96.2% 0.044 156.743);
|
||||
--color-green-200: oklch(92.5% 0.084 155.995);
|
||||
--color-green-300: oklch(87.1% 0.15 154.449);
|
||||
--color-green-400: oklch(79.2% 0.209 151.711);
|
||||
--color-green-500: oklch(72.3% 0.219 149.579);
|
||||
--color-green-600: oklch(62.7% 0.194 149.214);
|
||||
--color-green-700: oklch(52.7% 0.154 150.069);
|
||||
--color-green-800: oklch(44.8% 0.119 151.328);
|
||||
--color-green-900: oklch(39.3% 0.095 152.535);
|
||||
--color-green-950: oklch(26.6% 0.065 152.934);
|
||||
|
||||
--color-emerald-50: oklch(97.9% 0.021 166.113);
|
||||
--color-emerald-100: oklch(95% 0.052 163.051);
|
||||
--color-emerald-200: oklch(90.5% 0.093 164.15);
|
||||
--color-emerald-300: oklch(84.5% 0.143 164.978);
|
||||
--color-emerald-400: oklch(76.5% 0.177 163.223);
|
||||
--color-emerald-500: oklch(69.6% 0.17 162.48);
|
||||
--color-emerald-600: oklch(59.6% 0.145 163.225);
|
||||
--color-emerald-700: oklch(50.8% 0.118 165.612);
|
||||
--color-emerald-800: oklch(43.2% 0.095 166.913);
|
||||
--color-emerald-900: oklch(37.8% 0.077 168.94);
|
||||
--color-emerald-950: oklch(26.2% 0.051 172.552);
|
||||
|
||||
--color-teal-50: oklch(98.4% 0.014 180.72);
|
||||
--color-teal-100: oklch(95.3% 0.051 180.801);
|
||||
--color-teal-200: oklch(91% 0.096 180.426);
|
||||
--color-teal-300: oklch(85.5% 0.138 181.071);
|
||||
--color-teal-400: oklch(77.7% 0.152 181.912);
|
||||
--color-teal-500: oklch(70.4% 0.14 182.503);
|
||||
--color-teal-600: oklch(60% 0.118 184.704);
|
||||
--color-teal-700: oklch(51.1% 0.096 186.391);
|
||||
--color-teal-800: oklch(43.7% 0.078 188.216);
|
||||
--color-teal-900: oklch(38.6% 0.063 188.416);
|
||||
--color-teal-950: oklch(27.7% 0.046 192.524);
|
||||
|
||||
--color-cyan-50: oklch(98.4% 0.019 200.873);
|
||||
--color-cyan-100: oklch(95.6% 0.045 203.388);
|
||||
--color-cyan-200: oklch(91.7% 0.08 205.041);
|
||||
--color-cyan-300: oklch(86.5% 0.127 207.078);
|
||||
--color-cyan-400: oklch(78.9% 0.154 211.53);
|
||||
--color-cyan-500: oklch(71.5% 0.143 215.221);
|
||||
--color-cyan-600: oklch(60.9% 0.126 221.723);
|
||||
--color-cyan-700: oklch(52% 0.105 223.128);
|
||||
--color-cyan-800: oklch(45% 0.085 224.283);
|
||||
--color-cyan-900: oklch(39.8% 0.07 227.392);
|
||||
--color-cyan-950: oklch(30.2% 0.056 229.695);
|
||||
|
||||
--color-sky-50: oklch(97.7% 0.013 236.62);
|
||||
--color-sky-100: oklch(95.1% 0.026 236.824);
|
||||
--color-sky-200: oklch(90.1% 0.058 230.902);
|
||||
--color-sky-300: oklch(82.8% 0.111 230.318);
|
||||
--color-sky-400: oklch(74.6% 0.16 232.661);
|
||||
--color-sky-500: oklch(68.5% 0.169 237.323);
|
||||
--color-sky-600: oklch(58.8% 0.158 241.966);
|
||||
--color-sky-700: oklch(50% 0.134 242.749);
|
||||
--color-sky-800: oklch(44.3% 0.11 240.79);
|
||||
--color-sky-900: oklch(39.1% 0.09 240.876);
|
||||
--color-sky-950: oklch(29.3% 0.066 243.157);
|
||||
|
||||
--color-blue-50: oklch(97% 0.014 254.604);
|
||||
--color-blue-100: oklch(93.2% 0.032 255.585);
|
||||
--color-blue-200: oklch(88.2% 0.059 254.128);
|
||||
--color-blue-300: oklch(80.9% 0.105 251.813);
|
||||
--color-blue-400: oklch(70.7% 0.165 254.624);
|
||||
--color-blue-500: oklch(62.3% 0.214 259.815);
|
||||
--color-blue-600: oklch(54.6% 0.245 262.881);
|
||||
--color-blue-700: oklch(48.8% 0.243 264.376);
|
||||
--color-blue-800: oklch(42.4% 0.199 265.638);
|
||||
--color-blue-900: oklch(37.9% 0.146 265.522);
|
||||
--color-blue-950: oklch(28.2% 0.091 267.935);
|
||||
|
||||
--color-indigo-50: oklch(96.2% 0.018 272.314);
|
||||
--color-indigo-100: oklch(93% 0.034 272.788);
|
||||
--color-indigo-200: oklch(87% 0.065 274.039);
|
||||
--color-indigo-300: oklch(78.5% 0.115 274.713);
|
||||
--color-indigo-400: oklch(67.3% 0.182 276.935);
|
||||
--color-indigo-500: oklch(58.5% 0.233 277.117);
|
||||
--color-indigo-600: oklch(51.1% 0.262 276.966);
|
||||
--color-indigo-700: oklch(45.7% 0.24 277.023);
|
||||
--color-indigo-800: oklch(39.8% 0.195 277.366);
|
||||
--color-indigo-900: oklch(35.9% 0.144 278.697);
|
||||
--color-indigo-950: oklch(25.7% 0.09 281.288);
|
||||
|
||||
--color-violet-50: oklch(96.9% 0.016 293.756);
|
||||
--color-violet-100: oklch(94.3% 0.029 294.588);
|
||||
--color-violet-200: oklch(89.4% 0.057 293.283);
|
||||
--color-violet-300: oklch(81.1% 0.111 293.571);
|
||||
--color-violet-400: oklch(70.2% 0.183 293.541);
|
||||
--color-violet-500: oklch(60.6% 0.25 292.717);
|
||||
--color-violet-600: oklch(54.1% 0.281 293.009);
|
||||
--color-violet-700: oklch(49.1% 0.27 292.581);
|
||||
--color-violet-800: oklch(43.2% 0.232 292.759);
|
||||
--color-violet-900: oklch(38% 0.189 293.745);
|
||||
--color-violet-950: oklch(28.3% 0.141 291.089);
|
||||
|
||||
--color-purple-50: oklch(97.7% 0.014 308.299);
|
||||
--color-purple-100: oklch(94.6% 0.033 307.174);
|
||||
--color-purple-200: oklch(90.2% 0.063 306.703);
|
||||
--color-purple-300: oklch(82.7% 0.119 306.383);
|
||||
--color-purple-400: oklch(71.4% 0.203 305.504);
|
||||
--color-purple-500: oklch(62.7% 0.265 303.9);
|
||||
--color-purple-600: oklch(55.8% 0.288 302.321);
|
||||
--color-purple-700: oklch(49.6% 0.265 301.924);
|
||||
--color-purple-800: oklch(43.8% 0.218 303.724);
|
||||
--color-purple-900: oklch(38.1% 0.176 304.987);
|
||||
--color-purple-950: oklch(29.1% 0.149 302.717);
|
||||
|
||||
--color-fuchsia-50: oklch(97.7% 0.017 320.058);
|
||||
--color-fuchsia-100: oklch(95.2% 0.037 318.852);
|
||||
--color-fuchsia-200: oklch(90.3% 0.076 319.62);
|
||||
--color-fuchsia-300: oklch(83.3% 0.145 321.434);
|
||||
--color-fuchsia-400: oklch(74% 0.238 322.16);
|
||||
--color-fuchsia-500: oklch(66.7% 0.295 322.15);
|
||||
--color-fuchsia-600: oklch(59.1% 0.293 322.896);
|
||||
--color-fuchsia-700: oklch(51.8% 0.253 323.949);
|
||||
--color-fuchsia-800: oklch(45.2% 0.211 324.591);
|
||||
--color-fuchsia-900: oklch(40.1% 0.17 325.612);
|
||||
--color-fuchsia-950: oklch(29.3% 0.136 325.661);
|
||||
|
||||
--color-pink-50: oklch(97.1% 0.014 343.198);
|
||||
--color-pink-100: oklch(94.8% 0.028 342.258);
|
||||
--color-pink-200: oklch(89.9% 0.061 343.231);
|
||||
--color-pink-300: oklch(82.3% 0.12 346.018);
|
||||
--color-pink-400: oklch(71.8% 0.202 349.761);
|
||||
--color-pink-500: oklch(65.6% 0.241 354.308);
|
||||
--color-pink-600: oklch(59.2% 0.249 0.584);
|
||||
--color-pink-700: oklch(52.5% 0.223 3.958);
|
||||
--color-pink-800: oklch(45.9% 0.187 3.815);
|
||||
--color-pink-900: oklch(40.8% 0.153 2.432);
|
||||
--color-pink-950: oklch(28.4% 0.109 3.907);
|
||||
|
||||
--color-rose-50: oklch(96.9% 0.015 12.422);
|
||||
--color-rose-100: oklch(94.1% 0.03 12.58);
|
||||
--color-rose-200: oklch(89.2% 0.058 10.001);
|
||||
--color-rose-300: oklch(81% 0.117 11.638);
|
||||
--color-rose-400: oklch(71.2% 0.194 13.428);
|
||||
--color-rose-500: oklch(64.5% 0.246 16.439);
|
||||
--color-rose-600: oklch(58.6% 0.253 17.585);
|
||||
--color-rose-700: oklch(51.4% 0.222 16.935);
|
||||
--color-rose-800: oklch(45.5% 0.188 13.697);
|
||||
--color-rose-900: oklch(41% 0.159 10.272);
|
||||
--color-rose-950: oklch(27.1% 0.105 12.094);
|
||||
|
||||
--color-slate-50: oklch(98.4% 0.003 247.858);
|
||||
--color-slate-100: oklch(96.8% 0.007 247.896);
|
||||
--color-slate-200: oklch(92.9% 0.013 255.508);
|
||||
--color-slate-300: oklch(86.9% 0.022 252.894);
|
||||
--color-slate-400: oklch(70.4% 0.04 256.788);
|
||||
--color-slate-500: oklch(55.4% 0.046 257.417);
|
||||
--color-slate-600: oklch(44.6% 0.043 257.281);
|
||||
--color-slate-700: oklch(37.2% 0.044 257.287);
|
||||
--color-slate-800: oklch(27.9% 0.041 260.031);
|
||||
--color-slate-900: oklch(20.8% 0.042 265.755);
|
||||
--color-slate-950: oklch(12.9% 0.042 264.695);
|
||||
|
||||
--color-gray-50: oklch(98.5% 0.002 247.839);
|
||||
--color-gray-100: oklch(96.7% 0.003 264.542);
|
||||
--color-gray-200: oklch(92.8% 0.006 264.531);
|
||||
--color-gray-300: oklch(87.2% 0.01 258.338);
|
||||
--color-gray-400: oklch(70.7% 0.022 261.325);
|
||||
--color-gray-500: oklch(55.1% 0.027 264.364);
|
||||
--color-gray-600: oklch(44.6% 0.03 256.802);
|
||||
--color-gray-700: oklch(37.3% 0.034 259.733);
|
||||
--color-gray-800: oklch(27.8% 0.033 256.848);
|
||||
--color-gray-900: oklch(21% 0.034 264.665);
|
||||
--color-gray-950: oklch(13% 0.028 261.692);
|
||||
|
||||
--color-zinc-50: oklch(98.5% 0 0);
|
||||
--color-zinc-100: oklch(96.7% 0.001 286.375);
|
||||
--color-zinc-200: oklch(92% 0.004 286.32);
|
||||
--color-zinc-300: oklch(87.1% 0.006 286.286);
|
||||
--color-zinc-400: oklch(70.5% 0.015 286.067);
|
||||
--color-zinc-500: oklch(55.2% 0.016 285.938);
|
||||
--color-zinc-600: oklch(44.2% 0.017 285.786);
|
||||
--color-zinc-700: oklch(37% 0.013 285.805);
|
||||
--color-zinc-800: oklch(27.4% 0.006 286.033);
|
||||
--color-zinc-900: oklch(21% 0.006 285.885);
|
||||
--color-zinc-950: oklch(14.1% 0.005 285.823);
|
||||
|
||||
--color-neutral-50: oklch(98.5% 0 0);
|
||||
--color-neutral-100: oklch(97% 0 0);
|
||||
--color-neutral-200: oklch(92.2% 0 0);
|
||||
--color-neutral-300: oklch(87% 0 0);
|
||||
--color-neutral-400: oklch(70.8% 0 0);
|
||||
--color-neutral-500: oklch(55.6% 0 0);
|
||||
--color-neutral-600: oklch(43.9% 0 0);
|
||||
--color-neutral-700: oklch(37.1% 0 0);
|
||||
--color-neutral-800: oklch(26.9% 0 0);
|
||||
--color-neutral-900: oklch(20.5% 0 0);
|
||||
--color-neutral-950: oklch(14.5% 0 0);
|
||||
|
||||
--color-stone-50: oklch(98.5% 0.001 106.423);
|
||||
--color-stone-100: oklch(97% 0.001 106.424);
|
||||
--color-stone-200: oklch(92.3% 0.003 48.717);
|
||||
--color-stone-300: oklch(86.9% 0.005 56.366);
|
||||
--color-stone-400: oklch(70.9% 0.01 56.259);
|
||||
--color-stone-500: oklch(55.3% 0.013 58.071);
|
||||
--color-stone-600: oklch(44.4% 0.011 73.639);
|
||||
--color-stone-700: oklch(37.4% 0.01 67.558);
|
||||
--color-stone-800: oklch(26.8% 0.007 34.298);
|
||||
--color-stone-900: oklch(21.6% 0.006 56.043);
|
||||
--color-stone-950: oklch(14.7% 0.004 49.25);
|
||||
|
||||
--color-black: #000;
|
||||
--color-white: #fff;
|
||||
|
||||
--spacing: 0.25rem;
|
||||
|
||||
--breakpoint-sm: 40rem;
|
||||
--breakpoint-md: 48rem;
|
||||
--breakpoint-lg: 64rem;
|
||||
--breakpoint-xl: 80rem;
|
||||
--breakpoint-2xl: 96rem;
|
||||
|
||||
--container-3xs: 16rem;
|
||||
--container-2xs: 18rem;
|
||||
--container-xs: 20rem;
|
||||
--container-sm: 24rem;
|
||||
--container-md: 28rem;
|
||||
--container-lg: 32rem;
|
||||
--container-xl: 36rem;
|
||||
--container-2xl: 42rem;
|
||||
--container-3xl: 48rem;
|
||||
--container-4xl: 56rem;
|
||||
--container-5xl: 64rem;
|
||||
--container-6xl: 72rem;
|
||||
--container-7xl: 80rem;
|
||||
|
||||
--text-xs: 0.75rem;
|
||||
--text-xs--line-height: calc(1 / 0.75);
|
||||
--text-sm: 0.875rem;
|
||||
--text-sm--line-height: calc(1.25 / 0.875);
|
||||
--text-base: 1rem;
|
||||
--text-base--line-height: calc(1.5 / 1);
|
||||
--text-lg: 1.125rem;
|
||||
--text-lg--line-height: calc(1.75 / 1.125);
|
||||
--text-xl: 1.25rem;
|
||||
--text-xl--line-height: calc(1.75 / 1.25);
|
||||
--text-2xl: 1.5rem;
|
||||
--text-2xl--line-height: calc(2 / 1.5);
|
||||
--text-3xl: 1.875rem;
|
||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
||||
--text-4xl: 2.25rem;
|
||||
--text-4xl--line-height: calc(2.5 / 2.25);
|
||||
--text-5xl: 3rem;
|
||||
--text-5xl--line-height: 1;
|
||||
--text-6xl: 3.75rem;
|
||||
--text-6xl--line-height: 1;
|
||||
--text-7xl: 4.5rem;
|
||||
--text-7xl--line-height: 1;
|
||||
--text-8xl: 6rem;
|
||||
--text-8xl--line-height: 1;
|
||||
--text-9xl: 8rem;
|
||||
--text-9xl--line-height: 1;
|
||||
|
||||
--font-weight-thin: 100;
|
||||
--font-weight-extralight: 200;
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
--font-weight-black: 900;
|
||||
|
||||
--tracking-tighter: -0.05em;
|
||||
--tracking-tight: -0.025em;
|
||||
--tracking-normal: 0em;
|
||||
--tracking-wide: 0.025em;
|
||||
--tracking-wider: 0.05em;
|
||||
--tracking-widest: 0.1em;
|
||||
|
||||
--leading-tight: 1.25;
|
||||
--leading-snug: 1.375;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
--leading-loose: 2;
|
||||
|
||||
--radius-xs: 0.125rem;
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
--radius-2xl: 1rem;
|
||||
--radius-3xl: 1.5rem;
|
||||
--radius-4xl: 2rem;
|
||||
|
||||
--shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
|
||||
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-md:
|
||||
0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg:
|
||||
0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--shadow-xl:
|
||||
0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
||||
|
||||
--inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05);
|
||||
--inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05);
|
||||
--inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05);
|
||||
|
||||
--drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05);
|
||||
--drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15);
|
||||
--drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12);
|
||||
--drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);
|
||||
--drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1);
|
||||
--drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15);
|
||||
|
||||
--text-shadow-2xs: 0px 1px 0px rgb(0 0 0 / 0.15);
|
||||
--text-shadow-xs: 0px 1px 1px rgb(0 0 0 / 0.2);
|
||||
--text-shadow-sm:
|
||||
0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075),
|
||||
0px 2px 2px rgb(0 0 0 / 0.075);
|
||||
--text-shadow-md:
|
||||
0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1),
|
||||
0px 2px 4px rgb(0 0 0 / 0.1);
|
||||
--text-shadow-lg:
|
||||
0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1),
|
||||
0px 4px 8px rgb(0 0 0 / 0.1);
|
||||
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
--animate-spin: spin 1s linear infinite;
|
||||
--animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
--animate-bounce: bounce 1s infinite;
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ping {
|
||||
75%,
|
||||
100% {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(-25%);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: none;
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
--blur-xs: 4px;
|
||||
--blur-sm: 8px;
|
||||
--blur-md: 12px;
|
||||
--blur-lg: 16px;
|
||||
--blur-xl: 24px;
|
||||
--blur-2xl: 40px;
|
||||
--blur-3xl: 64px;
|
||||
|
||||
--perspective-dramatic: 100px;
|
||||
--perspective-near: 300px;
|
||||
--perspective-normal: 500px;
|
||||
--perspective-midrange: 800px;
|
||||
--perspective-distant: 1200px;
|
||||
|
||||
--aspect-video: 16 / 9;
|
||||
|
||||
--default-transition-duration: 150ms;
|
||||
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--default-font-family: --theme(--font-sans, initial);
|
||||
--default-font-feature-settings: --theme(
|
||||
--font-sans--font-feature-settings,
|
||||
initial
|
||||
);
|
||||
--default-font-variation-settings: --theme(
|
||||
--font-sans--font-variation-settings,
|
||||
initial
|
||||
);
|
||||
--default-mono-font-family: --theme(--font-mono, initial);
|
||||
--default-mono-font-feature-settings: --theme(
|
||||
--font-mono--font-feature-settings,
|
||||
initial
|
||||
);
|
||||
--default-mono-font-variation-settings: --theme(
|
||||
--font-mono--font-variation-settings,
|
||||
initial
|
||||
);
|
||||
}
|
||||
|
||||
/* Deprecated */
|
||||
@theme default inline reference {
|
||||
--blur: 8px;
|
||||
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
|
||||
--drop-shadow: 0 1px 2px rgb(0 0 0 / 0.1), 0 1px 1px rgb(0 0 0 / 0.06);
|
||||
--radius: 0.25rem;
|
||||
--max-width-prose: 65ch;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Remove default margins and padding
|
||||
3. Reset all borders.
|
||||
*/
|
||||
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
box-sizing: border-box; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
padding: 0; /* 2 */
|
||||
border: 0 solid; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
6. Use the user's configured `sans` font-variation-settings by default.
|
||||
7. Disable tap highlights on iOS.
|
||||
*/
|
||||
|
||||
html,
|
||||
:host {
|
||||
line-height: 1.5; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
tab-size: 4; /* 3 */
|
||||
font-family: --theme(
|
||||
--default-font-family,
|
||||
ui-sans-serif,
|
||||
system-ui,
|
||||
sans-serif,
|
||||
"Apple Color Emoji",
|
||||
"Segoe UI Emoji",
|
||||
"Segoe UI Symbol",
|
||||
"Noto Color Emoji"
|
||||
); /* 4 */
|
||||
font-feature-settings: --theme(
|
||||
--default-font-feature-settings,
|
||||
normal
|
||||
); /* 5 */
|
||||
font-variation-settings: --theme(
|
||||
--default-font-variation-settings,
|
||||
normal
|
||||
); /* 6 */
|
||||
-webkit-tap-highlight-color: transparent; /* 7 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Reset the default border style to a 1px solid border.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
border-top-width: 1px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
-webkit-text-decoration: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font-family by default.
|
||||
2. Use the user's configured `mono` font-feature-settings by default.
|
||||
3. Use the user's configured `mono` font-variation-settings by default.
|
||||
4. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: --theme(
|
||||
--default-mono-font-family,
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
"Liberation Mono",
|
||||
"Courier New",
|
||||
monospace
|
||||
); /* 1 */
|
||||
font-feature-settings: --theme(
|
||||
--default-mono-font-feature-settings,
|
||||
normal
|
||||
); /* 2 */
|
||||
font-variation-settings: --theme(
|
||||
--default-mono-font-variation-settings,
|
||||
normal
|
||||
); /* 3 */
|
||||
font-size: 1em; /* 4 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0; /* 1 */
|
||||
border-color: inherit; /* 2 */
|
||||
border-collapse: collapse; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Make lists unstyled by default.
|
||||
*/
|
||||
|
||||
ol,
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block; /* 1 */
|
||||
vertical-align: middle; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Inherit font styles in all browsers.
|
||||
2. Remove border radius in all browsers.
|
||||
3. Remove background color in all browsers.
|
||||
4. Ensure consistent opacity for disabled states in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
optgroup,
|
||||
textarea,
|
||||
::file-selector-button {
|
||||
font: inherit; /* 1 */
|
||||
font-feature-settings: inherit; /* 1 */
|
||||
font-variation-settings: inherit; /* 1 */
|
||||
letter-spacing: inherit; /* 1 */
|
||||
color: inherit; /* 1 */
|
||||
border-radius: 0; /* 2 */
|
||||
background-color: transparent; /* 3 */
|
||||
opacity: 1; /* 4 */
|
||||
}
|
||||
|
||||
/*
|
||||
Restore default font weight.
|
||||
*/
|
||||
|
||||
:where(select:is([multiple], [size])) optgroup {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
Restore indentation.
|
||||
*/
|
||||
|
||||
:where(select:is([multiple], [size])) optgroup option {
|
||||
padding-inline-start: 20px;
|
||||
}
|
||||
|
||||
/*
|
||||
Restore space after button.
|
||||
*/
|
||||
|
||||
::file-selector-button {
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
*/
|
||||
|
||||
::placeholder {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not
|
||||
crash when using `color-mix(…)` with `currentcolor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194)
|
||||
*/
|
||||
|
||||
@supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or
|
||||
(contain-intrinsic-size: 1px) /* Safari 17+ */ {
|
||||
::placeholder {
|
||||
color: color-mix(in oklab, currentcolor 50%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Ensure date/time inputs have the same height when empty in iOS Safari.
|
||||
2. Ensure text alignment can be changed on date/time inputs in iOS Safari.
|
||||
*/
|
||||
|
||||
::-webkit-date-and-time-value {
|
||||
min-height: 1lh; /* 1 */
|
||||
text-align: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent height from changing on date/time inputs in macOS Safari when the input is set to `display: block`.
|
||||
*/
|
||||
|
||||
::-webkit-datetime-edit {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove excess padding from pseudo-elements in date/time inputs to ensure consistent height across browsers.
|
||||
*/
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit,
|
||||
::-webkit-datetime-edit-year-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-minute-field,
|
||||
::-webkit-datetime-edit-second-field,
|
||||
::-webkit-datetime-edit-millisecond-field,
|
||||
::-webkit-datetime-edit-meridiem-field {
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Center dropdown marker shown on inputs with paired `<datalist>`s in Chrome. (https://github.com/tailwindlabs/tailwindcss/issues/18499)
|
||||
*/
|
||||
|
||||
::-webkit-calendar-picker-indicator {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the inability to style the border radius in iOS Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input:where([type="button"], [type="reset"], [type="submit"]),
|
||||
::file-selector-button {
|
||||
appearance: button;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Make elements with the HTML hidden attribute stay hidden by default.
|
||||
*/
|
||||
|
||||
[hidden]:where(:not([hidden="until-found"])) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@tailwind utilities;
|
||||
}
|
||||
89
templates/node_modules/tailwindcss/package.json
generated
vendored
Normal file
89
templates/node_modules/tailwindcss/package.json
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "tailwindcss",
|
||||
"version": "4.1.14",
|
||||
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tailwindlabs/tailwindcss.git",
|
||||
"directory": "packages/tailwindcss"
|
||||
},
|
||||
"bugs": "https://github.com/tailwindlabs/tailwindcss/issues",
|
||||
"homepage": "https://tailwindcss.com",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/lib.d.mts",
|
||||
"style": "./index.css",
|
||||
"require": "./dist/lib.js",
|
||||
"import": "./dist/lib.mjs"
|
||||
},
|
||||
"./plugin": {
|
||||
"require": "./dist/plugin.js",
|
||||
"import": "./dist/plugin.mjs"
|
||||
},
|
||||
"./plugin.js": {
|
||||
"require": "./dist/plugin.js",
|
||||
"import": "./dist/plugin.mjs"
|
||||
},
|
||||
"./defaultTheme": {
|
||||
"require": "./dist/default-theme.js",
|
||||
"import": "./dist/default-theme.mjs"
|
||||
},
|
||||
"./defaultTheme.js": {
|
||||
"require": "./dist/default-theme.js",
|
||||
"import": "./dist/default-theme.mjs"
|
||||
},
|
||||
"./colors": {
|
||||
"require": "./dist/colors.js",
|
||||
"import": "./dist/colors.mjs"
|
||||
},
|
||||
"./colors.js": {
|
||||
"require": "./dist/colors.js",
|
||||
"import": "./dist/colors.mjs"
|
||||
},
|
||||
"./lib/util/flattenColorPalette": {
|
||||
"require": "./dist/flatten-color-palette.js",
|
||||
"import": "./dist/flatten-color-palette.mjs"
|
||||
},
|
||||
"./lib/util/flattenColorPalette.js": {
|
||||
"require": "./dist/flatten-color-palette.js",
|
||||
"import": "./dist/flatten-color-palette.mjs"
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
"./index.css": "./index.css",
|
||||
"./index": "./index.css",
|
||||
"./preflight.css": "./preflight.css",
|
||||
"./preflight": "./preflight.css",
|
||||
"./theme.css": "./theme.css",
|
||||
"./theme": "./theme.css",
|
||||
"./utilities.css": "./utilities.css",
|
||||
"./utilities": "./utilities.css"
|
||||
},
|
||||
"publishConfig": {
|
||||
"provenance": true,
|
||||
"access": "public"
|
||||
},
|
||||
"style": "index.css",
|
||||
"files": [
|
||||
"dist",
|
||||
"index.css",
|
||||
"preflight.css",
|
||||
"theme.css",
|
||||
"utilities.css"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@jridgewell/remapping": "^2.3.4",
|
||||
"@types/node": "^20.19.0",
|
||||
"dedent": "1.7.0",
|
||||
"lightningcss": "1.30.1",
|
||||
"magic-string": "^0.30.19",
|
||||
"source-map-js": "^1.2.1",
|
||||
"@tailwindcss/oxide": "^4.1.14"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tsc --noEmit",
|
||||
"build": "tsup-node --env.NODE_ENV production",
|
||||
"dev": "tsup-node --env.NODE_ENV development --watch",
|
||||
"test:ui": "playwright test"
|
||||
}
|
||||
}
|
||||
393
templates/node_modules/tailwindcss/preflight.css
generated
vendored
Normal file
393
templates/node_modules/tailwindcss/preflight.css
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||
2. Remove default margins and padding
|
||||
3. Reset all borders.
|
||||
*/
|
||||
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
box-sizing: border-box; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
padding: 0; /* 2 */
|
||||
border: 0 solid; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use a consistent sensible line-height in all browsers.
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
6. Use the user's configured `sans` font-variation-settings by default.
|
||||
7. Disable tap highlights on iOS.
|
||||
*/
|
||||
|
||||
html,
|
||||
:host {
|
||||
line-height: 1.5; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
tab-size: 4; /* 3 */
|
||||
font-family: --theme(
|
||||
--default-font-family,
|
||||
ui-sans-serif,
|
||||
system-ui,
|
||||
sans-serif,
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji'
|
||||
); /* 4 */
|
||||
font-feature-settings: --theme(--default-font-feature-settings, normal); /* 5 */
|
||||
font-variation-settings: --theme(--default-font-variation-settings, normal); /* 6 */
|
||||
-webkit-tap-highlight-color: transparent; /* 7 */
|
||||
}
|
||||
|
||||
/*
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
3. Reset the default border style to a 1px solid border.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
border-top-width: 1px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr:where([title]) {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the default font size and weight for headings.
|
||||
*/
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset links to optimize for opt-in styling instead of opt-out.
|
||||
*/
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
-webkit-text-decoration: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font weight in Edge and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Use the user's configured `mono` font-family by default.
|
||||
2. Use the user's configured `mono` font-feature-settings by default.
|
||||
3. Use the user's configured `mono` font-variation-settings by default.
|
||||
4. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
pre {
|
||||
font-family: --theme(
|
||||
--default-mono-font-family,
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
Menlo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
'Liberation Mono',
|
||||
'Courier New',
|
||||
monospace
|
||||
); /* 1 */
|
||||
font-feature-settings: --theme(--default-mono-font-feature-settings, normal); /* 2 */
|
||||
font-variation-settings: --theme(--default-mono-font-variation-settings, normal); /* 3 */
|
||||
font-size: 1em; /* 4 */
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
3. Remove gaps between table borders by default.
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0; /* 1 */
|
||||
border-color: inherit; /* 2 */
|
||||
border-collapse: collapse; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct vertical alignment in Chrome and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/*
|
||||
Add the correct display in Chrome and Safari.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/*
|
||||
Make lists unstyled by default.
|
||||
*/
|
||||
|
||||
ol,
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||
*/
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas,
|
||||
audio,
|
||||
iframe,
|
||||
embed,
|
||||
object {
|
||||
display: block; /* 1 */
|
||||
vertical-align: middle; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||
*/
|
||||
|
||||
img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Inherit font styles in all browsers.
|
||||
2. Remove border radius in all browsers.
|
||||
3. Remove background color in all browsers.
|
||||
4. Ensure consistent opacity for disabled states in all browsers.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
optgroup,
|
||||
textarea,
|
||||
::file-selector-button {
|
||||
font: inherit; /* 1 */
|
||||
font-feature-settings: inherit; /* 1 */
|
||||
font-variation-settings: inherit; /* 1 */
|
||||
letter-spacing: inherit; /* 1 */
|
||||
color: inherit; /* 1 */
|
||||
border-radius: 0; /* 2 */
|
||||
background-color: transparent; /* 3 */
|
||||
opacity: 1; /* 4 */
|
||||
}
|
||||
|
||||
/*
|
||||
Restore default font weight.
|
||||
*/
|
||||
|
||||
:where(select:is([multiple], [size])) optgroup {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/*
|
||||
Restore indentation.
|
||||
*/
|
||||
|
||||
:where(select:is([multiple], [size])) optgroup option {
|
||||
padding-inline-start: 20px;
|
||||
}
|
||||
|
||||
/*
|
||||
Restore space after button.
|
||||
*/
|
||||
|
||||
::file-selector-button {
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
|
||||
/*
|
||||
Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||
*/
|
||||
|
||||
::placeholder {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not
|
||||
crash when using `color-mix(…)` with `currentcolor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194)
|
||||
*/
|
||||
|
||||
@supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or
|
||||
(contain-intrinsic-size: 1px) /* Safari 17+ */ {
|
||||
::placeholder {
|
||||
color: color-mix(in oklab, currentcolor 50%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent resizing textareas horizontally by default.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
1. Ensure date/time inputs have the same height when empty in iOS Safari.
|
||||
2. Ensure text alignment can be changed on date/time inputs in iOS Safari.
|
||||
*/
|
||||
|
||||
::-webkit-date-and-time-value {
|
||||
min-height: 1lh; /* 1 */
|
||||
text-align: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Prevent height from changing on date/time inputs in macOS Safari when the input is set to `display: block`.
|
||||
*/
|
||||
|
||||
::-webkit-datetime-edit {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove excess padding from pseudo-elements in date/time inputs to ensure consistent height across browsers.
|
||||
*/
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit,
|
||||
::-webkit-datetime-edit-year-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-minute-field,
|
||||
::-webkit-datetime-edit-second-field,
|
||||
::-webkit-datetime-edit-millisecond-field,
|
||||
::-webkit-datetime-edit-meridiem-field {
|
||||
padding-block: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Center dropdown marker shown on inputs with paired `<datalist>`s in Chrome. (https://github.com/tailwindlabs/tailwindcss/issues/18499)
|
||||
*/
|
||||
|
||||
::-webkit-calendar-picker-indicator {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the inability to style the border radius in iOS Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input:where([type='button'], [type='reset'], [type='submit']),
|
||||
::file-selector-button {
|
||||
appearance: button;
|
||||
}
|
||||
|
||||
/*
|
||||
Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
Make elements with the HTML hidden attribute stay hidden by default.
|
||||
*/
|
||||
|
||||
[hidden]:where(:not([hidden='until-found'])) {
|
||||
display: none !important;
|
||||
}
|
||||
462
templates/node_modules/tailwindcss/theme.css
generated
vendored
Normal file
462
templates/node_modules/tailwindcss/theme.css
generated
vendored
Normal file
@@ -0,0 +1,462 @@
|
||||
@theme default {
|
||||
--font-sans:
|
||||
ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
--font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
|
||||
--font-mono:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
||||
monospace;
|
||||
|
||||
--color-red-50: oklch(97.1% 0.013 17.38);
|
||||
--color-red-100: oklch(93.6% 0.032 17.717);
|
||||
--color-red-200: oklch(88.5% 0.062 18.334);
|
||||
--color-red-300: oklch(80.8% 0.114 19.571);
|
||||
--color-red-400: oklch(70.4% 0.191 22.216);
|
||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
||||
--color-red-600: oklch(57.7% 0.245 27.325);
|
||||
--color-red-700: oklch(50.5% 0.213 27.518);
|
||||
--color-red-800: oklch(44.4% 0.177 26.899);
|
||||
--color-red-900: oklch(39.6% 0.141 25.723);
|
||||
--color-red-950: oklch(25.8% 0.092 26.042);
|
||||
|
||||
--color-orange-50: oklch(98% 0.016 73.684);
|
||||
--color-orange-100: oklch(95.4% 0.038 75.164);
|
||||
--color-orange-200: oklch(90.1% 0.076 70.697);
|
||||
--color-orange-300: oklch(83.7% 0.128 66.29);
|
||||
--color-orange-400: oklch(75% 0.183 55.934);
|
||||
--color-orange-500: oklch(70.5% 0.213 47.604);
|
||||
--color-orange-600: oklch(64.6% 0.222 41.116);
|
||||
--color-orange-700: oklch(55.3% 0.195 38.402);
|
||||
--color-orange-800: oklch(47% 0.157 37.304);
|
||||
--color-orange-900: oklch(40.8% 0.123 38.172);
|
||||
--color-orange-950: oklch(26.6% 0.079 36.259);
|
||||
|
||||
--color-amber-50: oklch(98.7% 0.022 95.277);
|
||||
--color-amber-100: oklch(96.2% 0.059 95.617);
|
||||
--color-amber-200: oklch(92.4% 0.12 95.746);
|
||||
--color-amber-300: oklch(87.9% 0.169 91.605);
|
||||
--color-amber-400: oklch(82.8% 0.189 84.429);
|
||||
--color-amber-500: oklch(76.9% 0.188 70.08);
|
||||
--color-amber-600: oklch(66.6% 0.179 58.318);
|
||||
--color-amber-700: oklch(55.5% 0.163 48.998);
|
||||
--color-amber-800: oklch(47.3% 0.137 46.201);
|
||||
--color-amber-900: oklch(41.4% 0.112 45.904);
|
||||
--color-amber-950: oklch(27.9% 0.077 45.635);
|
||||
|
||||
--color-yellow-50: oklch(98.7% 0.026 102.212);
|
||||
--color-yellow-100: oklch(97.3% 0.071 103.193);
|
||||
--color-yellow-200: oklch(94.5% 0.129 101.54);
|
||||
--color-yellow-300: oklch(90.5% 0.182 98.111);
|
||||
--color-yellow-400: oklch(85.2% 0.199 91.936);
|
||||
--color-yellow-500: oklch(79.5% 0.184 86.047);
|
||||
--color-yellow-600: oklch(68.1% 0.162 75.834);
|
||||
--color-yellow-700: oklch(55.4% 0.135 66.442);
|
||||
--color-yellow-800: oklch(47.6% 0.114 61.907);
|
||||
--color-yellow-900: oklch(42.1% 0.095 57.708);
|
||||
--color-yellow-950: oklch(28.6% 0.066 53.813);
|
||||
|
||||
--color-lime-50: oklch(98.6% 0.031 120.757);
|
||||
--color-lime-100: oklch(96.7% 0.067 122.328);
|
||||
--color-lime-200: oklch(93.8% 0.127 124.321);
|
||||
--color-lime-300: oklch(89.7% 0.196 126.665);
|
||||
--color-lime-400: oklch(84.1% 0.238 128.85);
|
||||
--color-lime-500: oklch(76.8% 0.233 130.85);
|
||||
--color-lime-600: oklch(64.8% 0.2 131.684);
|
||||
--color-lime-700: oklch(53.2% 0.157 131.589);
|
||||
--color-lime-800: oklch(45.3% 0.124 130.933);
|
||||
--color-lime-900: oklch(40.5% 0.101 131.063);
|
||||
--color-lime-950: oklch(27.4% 0.072 132.109);
|
||||
|
||||
--color-green-50: oklch(98.2% 0.018 155.826);
|
||||
--color-green-100: oklch(96.2% 0.044 156.743);
|
||||
--color-green-200: oklch(92.5% 0.084 155.995);
|
||||
--color-green-300: oklch(87.1% 0.15 154.449);
|
||||
--color-green-400: oklch(79.2% 0.209 151.711);
|
||||
--color-green-500: oklch(72.3% 0.219 149.579);
|
||||
--color-green-600: oklch(62.7% 0.194 149.214);
|
||||
--color-green-700: oklch(52.7% 0.154 150.069);
|
||||
--color-green-800: oklch(44.8% 0.119 151.328);
|
||||
--color-green-900: oklch(39.3% 0.095 152.535);
|
||||
--color-green-950: oklch(26.6% 0.065 152.934);
|
||||
|
||||
--color-emerald-50: oklch(97.9% 0.021 166.113);
|
||||
--color-emerald-100: oklch(95% 0.052 163.051);
|
||||
--color-emerald-200: oklch(90.5% 0.093 164.15);
|
||||
--color-emerald-300: oklch(84.5% 0.143 164.978);
|
||||
--color-emerald-400: oklch(76.5% 0.177 163.223);
|
||||
--color-emerald-500: oklch(69.6% 0.17 162.48);
|
||||
--color-emerald-600: oklch(59.6% 0.145 163.225);
|
||||
--color-emerald-700: oklch(50.8% 0.118 165.612);
|
||||
--color-emerald-800: oklch(43.2% 0.095 166.913);
|
||||
--color-emerald-900: oklch(37.8% 0.077 168.94);
|
||||
--color-emerald-950: oklch(26.2% 0.051 172.552);
|
||||
|
||||
--color-teal-50: oklch(98.4% 0.014 180.72);
|
||||
--color-teal-100: oklch(95.3% 0.051 180.801);
|
||||
--color-teal-200: oklch(91% 0.096 180.426);
|
||||
--color-teal-300: oklch(85.5% 0.138 181.071);
|
||||
--color-teal-400: oklch(77.7% 0.152 181.912);
|
||||
--color-teal-500: oklch(70.4% 0.14 182.503);
|
||||
--color-teal-600: oklch(60% 0.118 184.704);
|
||||
--color-teal-700: oklch(51.1% 0.096 186.391);
|
||||
--color-teal-800: oklch(43.7% 0.078 188.216);
|
||||
--color-teal-900: oklch(38.6% 0.063 188.416);
|
||||
--color-teal-950: oklch(27.7% 0.046 192.524);
|
||||
|
||||
--color-cyan-50: oklch(98.4% 0.019 200.873);
|
||||
--color-cyan-100: oklch(95.6% 0.045 203.388);
|
||||
--color-cyan-200: oklch(91.7% 0.08 205.041);
|
||||
--color-cyan-300: oklch(86.5% 0.127 207.078);
|
||||
--color-cyan-400: oklch(78.9% 0.154 211.53);
|
||||
--color-cyan-500: oklch(71.5% 0.143 215.221);
|
||||
--color-cyan-600: oklch(60.9% 0.126 221.723);
|
||||
--color-cyan-700: oklch(52% 0.105 223.128);
|
||||
--color-cyan-800: oklch(45% 0.085 224.283);
|
||||
--color-cyan-900: oklch(39.8% 0.07 227.392);
|
||||
--color-cyan-950: oklch(30.2% 0.056 229.695);
|
||||
|
||||
--color-sky-50: oklch(97.7% 0.013 236.62);
|
||||
--color-sky-100: oklch(95.1% 0.026 236.824);
|
||||
--color-sky-200: oklch(90.1% 0.058 230.902);
|
||||
--color-sky-300: oklch(82.8% 0.111 230.318);
|
||||
--color-sky-400: oklch(74.6% 0.16 232.661);
|
||||
--color-sky-500: oklch(68.5% 0.169 237.323);
|
||||
--color-sky-600: oklch(58.8% 0.158 241.966);
|
||||
--color-sky-700: oklch(50% 0.134 242.749);
|
||||
--color-sky-800: oklch(44.3% 0.11 240.79);
|
||||
--color-sky-900: oklch(39.1% 0.09 240.876);
|
||||
--color-sky-950: oklch(29.3% 0.066 243.157);
|
||||
|
||||
--color-blue-50: oklch(97% 0.014 254.604);
|
||||
--color-blue-100: oklch(93.2% 0.032 255.585);
|
||||
--color-blue-200: oklch(88.2% 0.059 254.128);
|
||||
--color-blue-300: oklch(80.9% 0.105 251.813);
|
||||
--color-blue-400: oklch(70.7% 0.165 254.624);
|
||||
--color-blue-500: oklch(62.3% 0.214 259.815);
|
||||
--color-blue-600: oklch(54.6% 0.245 262.881);
|
||||
--color-blue-700: oklch(48.8% 0.243 264.376);
|
||||
--color-blue-800: oklch(42.4% 0.199 265.638);
|
||||
--color-blue-900: oklch(37.9% 0.146 265.522);
|
||||
--color-blue-950: oklch(28.2% 0.091 267.935);
|
||||
|
||||
--color-indigo-50: oklch(96.2% 0.018 272.314);
|
||||
--color-indigo-100: oklch(93% 0.034 272.788);
|
||||
--color-indigo-200: oklch(87% 0.065 274.039);
|
||||
--color-indigo-300: oklch(78.5% 0.115 274.713);
|
||||
--color-indigo-400: oklch(67.3% 0.182 276.935);
|
||||
--color-indigo-500: oklch(58.5% 0.233 277.117);
|
||||
--color-indigo-600: oklch(51.1% 0.262 276.966);
|
||||
--color-indigo-700: oklch(45.7% 0.24 277.023);
|
||||
--color-indigo-800: oklch(39.8% 0.195 277.366);
|
||||
--color-indigo-900: oklch(35.9% 0.144 278.697);
|
||||
--color-indigo-950: oklch(25.7% 0.09 281.288);
|
||||
|
||||
--color-violet-50: oklch(96.9% 0.016 293.756);
|
||||
--color-violet-100: oklch(94.3% 0.029 294.588);
|
||||
--color-violet-200: oklch(89.4% 0.057 293.283);
|
||||
--color-violet-300: oklch(81.1% 0.111 293.571);
|
||||
--color-violet-400: oklch(70.2% 0.183 293.541);
|
||||
--color-violet-500: oklch(60.6% 0.25 292.717);
|
||||
--color-violet-600: oklch(54.1% 0.281 293.009);
|
||||
--color-violet-700: oklch(49.1% 0.27 292.581);
|
||||
--color-violet-800: oklch(43.2% 0.232 292.759);
|
||||
--color-violet-900: oklch(38% 0.189 293.745);
|
||||
--color-violet-950: oklch(28.3% 0.141 291.089);
|
||||
|
||||
--color-purple-50: oklch(97.7% 0.014 308.299);
|
||||
--color-purple-100: oklch(94.6% 0.033 307.174);
|
||||
--color-purple-200: oklch(90.2% 0.063 306.703);
|
||||
--color-purple-300: oklch(82.7% 0.119 306.383);
|
||||
--color-purple-400: oklch(71.4% 0.203 305.504);
|
||||
--color-purple-500: oklch(62.7% 0.265 303.9);
|
||||
--color-purple-600: oklch(55.8% 0.288 302.321);
|
||||
--color-purple-700: oklch(49.6% 0.265 301.924);
|
||||
--color-purple-800: oklch(43.8% 0.218 303.724);
|
||||
--color-purple-900: oklch(38.1% 0.176 304.987);
|
||||
--color-purple-950: oklch(29.1% 0.149 302.717);
|
||||
|
||||
--color-fuchsia-50: oklch(97.7% 0.017 320.058);
|
||||
--color-fuchsia-100: oklch(95.2% 0.037 318.852);
|
||||
--color-fuchsia-200: oklch(90.3% 0.076 319.62);
|
||||
--color-fuchsia-300: oklch(83.3% 0.145 321.434);
|
||||
--color-fuchsia-400: oklch(74% 0.238 322.16);
|
||||
--color-fuchsia-500: oklch(66.7% 0.295 322.15);
|
||||
--color-fuchsia-600: oklch(59.1% 0.293 322.896);
|
||||
--color-fuchsia-700: oklch(51.8% 0.253 323.949);
|
||||
--color-fuchsia-800: oklch(45.2% 0.211 324.591);
|
||||
--color-fuchsia-900: oklch(40.1% 0.17 325.612);
|
||||
--color-fuchsia-950: oklch(29.3% 0.136 325.661);
|
||||
|
||||
--color-pink-50: oklch(97.1% 0.014 343.198);
|
||||
--color-pink-100: oklch(94.8% 0.028 342.258);
|
||||
--color-pink-200: oklch(89.9% 0.061 343.231);
|
||||
--color-pink-300: oklch(82.3% 0.12 346.018);
|
||||
--color-pink-400: oklch(71.8% 0.202 349.761);
|
||||
--color-pink-500: oklch(65.6% 0.241 354.308);
|
||||
--color-pink-600: oklch(59.2% 0.249 0.584);
|
||||
--color-pink-700: oklch(52.5% 0.223 3.958);
|
||||
--color-pink-800: oklch(45.9% 0.187 3.815);
|
||||
--color-pink-900: oklch(40.8% 0.153 2.432);
|
||||
--color-pink-950: oklch(28.4% 0.109 3.907);
|
||||
|
||||
--color-rose-50: oklch(96.9% 0.015 12.422);
|
||||
--color-rose-100: oklch(94.1% 0.03 12.58);
|
||||
--color-rose-200: oklch(89.2% 0.058 10.001);
|
||||
--color-rose-300: oklch(81% 0.117 11.638);
|
||||
--color-rose-400: oklch(71.2% 0.194 13.428);
|
||||
--color-rose-500: oklch(64.5% 0.246 16.439);
|
||||
--color-rose-600: oklch(58.6% 0.253 17.585);
|
||||
--color-rose-700: oklch(51.4% 0.222 16.935);
|
||||
--color-rose-800: oklch(45.5% 0.188 13.697);
|
||||
--color-rose-900: oklch(41% 0.159 10.272);
|
||||
--color-rose-950: oklch(27.1% 0.105 12.094);
|
||||
|
||||
--color-slate-50: oklch(98.4% 0.003 247.858);
|
||||
--color-slate-100: oklch(96.8% 0.007 247.896);
|
||||
--color-slate-200: oklch(92.9% 0.013 255.508);
|
||||
--color-slate-300: oklch(86.9% 0.022 252.894);
|
||||
--color-slate-400: oklch(70.4% 0.04 256.788);
|
||||
--color-slate-500: oklch(55.4% 0.046 257.417);
|
||||
--color-slate-600: oklch(44.6% 0.043 257.281);
|
||||
--color-slate-700: oklch(37.2% 0.044 257.287);
|
||||
--color-slate-800: oklch(27.9% 0.041 260.031);
|
||||
--color-slate-900: oklch(20.8% 0.042 265.755);
|
||||
--color-slate-950: oklch(12.9% 0.042 264.695);
|
||||
|
||||
--color-gray-50: oklch(98.5% 0.002 247.839);
|
||||
--color-gray-100: oklch(96.7% 0.003 264.542);
|
||||
--color-gray-200: oklch(92.8% 0.006 264.531);
|
||||
--color-gray-300: oklch(87.2% 0.01 258.338);
|
||||
--color-gray-400: oklch(70.7% 0.022 261.325);
|
||||
--color-gray-500: oklch(55.1% 0.027 264.364);
|
||||
--color-gray-600: oklch(44.6% 0.03 256.802);
|
||||
--color-gray-700: oklch(37.3% 0.034 259.733);
|
||||
--color-gray-800: oklch(27.8% 0.033 256.848);
|
||||
--color-gray-900: oklch(21% 0.034 264.665);
|
||||
--color-gray-950: oklch(13% 0.028 261.692);
|
||||
|
||||
--color-zinc-50: oklch(98.5% 0 0);
|
||||
--color-zinc-100: oklch(96.7% 0.001 286.375);
|
||||
--color-zinc-200: oklch(92% 0.004 286.32);
|
||||
--color-zinc-300: oklch(87.1% 0.006 286.286);
|
||||
--color-zinc-400: oklch(70.5% 0.015 286.067);
|
||||
--color-zinc-500: oklch(55.2% 0.016 285.938);
|
||||
--color-zinc-600: oklch(44.2% 0.017 285.786);
|
||||
--color-zinc-700: oklch(37% 0.013 285.805);
|
||||
--color-zinc-800: oklch(27.4% 0.006 286.033);
|
||||
--color-zinc-900: oklch(21% 0.006 285.885);
|
||||
--color-zinc-950: oklch(14.1% 0.005 285.823);
|
||||
|
||||
--color-neutral-50: oklch(98.5% 0 0);
|
||||
--color-neutral-100: oklch(97% 0 0);
|
||||
--color-neutral-200: oklch(92.2% 0 0);
|
||||
--color-neutral-300: oklch(87% 0 0);
|
||||
--color-neutral-400: oklch(70.8% 0 0);
|
||||
--color-neutral-500: oklch(55.6% 0 0);
|
||||
--color-neutral-600: oklch(43.9% 0 0);
|
||||
--color-neutral-700: oklch(37.1% 0 0);
|
||||
--color-neutral-800: oklch(26.9% 0 0);
|
||||
--color-neutral-900: oklch(20.5% 0 0);
|
||||
--color-neutral-950: oklch(14.5% 0 0);
|
||||
|
||||
--color-stone-50: oklch(98.5% 0.001 106.423);
|
||||
--color-stone-100: oklch(97% 0.001 106.424);
|
||||
--color-stone-200: oklch(92.3% 0.003 48.717);
|
||||
--color-stone-300: oklch(86.9% 0.005 56.366);
|
||||
--color-stone-400: oklch(70.9% 0.01 56.259);
|
||||
--color-stone-500: oklch(55.3% 0.013 58.071);
|
||||
--color-stone-600: oklch(44.4% 0.011 73.639);
|
||||
--color-stone-700: oklch(37.4% 0.01 67.558);
|
||||
--color-stone-800: oklch(26.8% 0.007 34.298);
|
||||
--color-stone-900: oklch(21.6% 0.006 56.043);
|
||||
--color-stone-950: oklch(14.7% 0.004 49.25);
|
||||
|
||||
--color-black: #000;
|
||||
--color-white: #fff;
|
||||
|
||||
--spacing: 0.25rem;
|
||||
|
||||
--breakpoint-sm: 40rem;
|
||||
--breakpoint-md: 48rem;
|
||||
--breakpoint-lg: 64rem;
|
||||
--breakpoint-xl: 80rem;
|
||||
--breakpoint-2xl: 96rem;
|
||||
|
||||
--container-3xs: 16rem;
|
||||
--container-2xs: 18rem;
|
||||
--container-xs: 20rem;
|
||||
--container-sm: 24rem;
|
||||
--container-md: 28rem;
|
||||
--container-lg: 32rem;
|
||||
--container-xl: 36rem;
|
||||
--container-2xl: 42rem;
|
||||
--container-3xl: 48rem;
|
||||
--container-4xl: 56rem;
|
||||
--container-5xl: 64rem;
|
||||
--container-6xl: 72rem;
|
||||
--container-7xl: 80rem;
|
||||
|
||||
--text-xs: 0.75rem;
|
||||
--text-xs--line-height: calc(1 / 0.75);
|
||||
--text-sm: 0.875rem;
|
||||
--text-sm--line-height: calc(1.25 / 0.875);
|
||||
--text-base: 1rem;
|
||||
--text-base--line-height: calc(1.5 / 1);
|
||||
--text-lg: 1.125rem;
|
||||
--text-lg--line-height: calc(1.75 / 1.125);
|
||||
--text-xl: 1.25rem;
|
||||
--text-xl--line-height: calc(1.75 / 1.25);
|
||||
--text-2xl: 1.5rem;
|
||||
--text-2xl--line-height: calc(2 / 1.5);
|
||||
--text-3xl: 1.875rem;
|
||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
||||
--text-4xl: 2.25rem;
|
||||
--text-4xl--line-height: calc(2.5 / 2.25);
|
||||
--text-5xl: 3rem;
|
||||
--text-5xl--line-height: 1;
|
||||
--text-6xl: 3.75rem;
|
||||
--text-6xl--line-height: 1;
|
||||
--text-7xl: 4.5rem;
|
||||
--text-7xl--line-height: 1;
|
||||
--text-8xl: 6rem;
|
||||
--text-8xl--line-height: 1;
|
||||
--text-9xl: 8rem;
|
||||
--text-9xl--line-height: 1;
|
||||
|
||||
--font-weight-thin: 100;
|
||||
--font-weight-extralight: 200;
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-extrabold: 800;
|
||||
--font-weight-black: 900;
|
||||
|
||||
--tracking-tighter: -0.05em;
|
||||
--tracking-tight: -0.025em;
|
||||
--tracking-normal: 0em;
|
||||
--tracking-wide: 0.025em;
|
||||
--tracking-wider: 0.05em;
|
||||
--tracking-widest: 0.1em;
|
||||
|
||||
--leading-tight: 1.25;
|
||||
--leading-snug: 1.375;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
--leading-loose: 2;
|
||||
|
||||
--radius-xs: 0.125rem;
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
--radius-2xl: 1rem;
|
||||
--radius-3xl: 1.5rem;
|
||||
--radius-4xl: 2rem;
|
||||
|
||||
--shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
|
||||
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
||||
|
||||
--inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05);
|
||||
--inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05);
|
||||
--inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05);
|
||||
|
||||
--drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05);
|
||||
--drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15);
|
||||
--drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12);
|
||||
--drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);
|
||||
--drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1);
|
||||
--drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15);
|
||||
|
||||
--text-shadow-2xs: 0px 1px 0px rgb(0 0 0 / 0.15);
|
||||
--text-shadow-xs: 0px 1px 1px rgb(0 0 0 / 0.2);
|
||||
--text-shadow-sm:
|
||||
0px 1px 0px rgb(0 0 0 / 0.075), 0px 1px 1px rgb(0 0 0 / 0.075), 0px 2px 2px rgb(0 0 0 / 0.075);
|
||||
--text-shadow-md:
|
||||
0px 1px 1px rgb(0 0 0 / 0.1), 0px 1px 2px rgb(0 0 0 / 0.1), 0px 2px 4px rgb(0 0 0 / 0.1);
|
||||
--text-shadow-lg:
|
||||
0px 1px 2px rgb(0 0 0 / 0.1), 0px 3px 2px rgb(0 0 0 / 0.1), 0px 4px 8px rgb(0 0 0 / 0.1);
|
||||
|
||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
--animate-spin: spin 1s linear infinite;
|
||||
--animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
--animate-bounce: bounce 1s infinite;
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ping {
|
||||
75%,
|
||||
100% {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(-25%);
|
||||
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: none;
|
||||
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
--blur-xs: 4px;
|
||||
--blur-sm: 8px;
|
||||
--blur-md: 12px;
|
||||
--blur-lg: 16px;
|
||||
--blur-xl: 24px;
|
||||
--blur-2xl: 40px;
|
||||
--blur-3xl: 64px;
|
||||
|
||||
--perspective-dramatic: 100px;
|
||||
--perspective-near: 300px;
|
||||
--perspective-normal: 500px;
|
||||
--perspective-midrange: 800px;
|
||||
--perspective-distant: 1200px;
|
||||
|
||||
--aspect-video: 16 / 9;
|
||||
|
||||
--default-transition-duration: 150ms;
|
||||
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--default-font-family: --theme(--font-sans, initial);
|
||||
--default-font-feature-settings: --theme(--font-sans--font-feature-settings, initial);
|
||||
--default-font-variation-settings: --theme(--font-sans--font-variation-settings, initial);
|
||||
--default-mono-font-family: --theme(--font-mono, initial);
|
||||
--default-mono-font-feature-settings: --theme(--font-mono--font-feature-settings, initial);
|
||||
--default-mono-font-variation-settings: --theme(--font-mono--font-variation-settings, initial);
|
||||
}
|
||||
|
||||
/* Deprecated */
|
||||
@theme default inline reference {
|
||||
--blur: 8px;
|
||||
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
|
||||
--drop-shadow: 0 1px 2px rgb(0 0 0 / 0.1), 0 1px 1px rgb(0 0 0 / 0.06);
|
||||
--radius: 0.25rem;
|
||||
--max-width-prose: 65ch;
|
||||
}
|
||||
1
templates/node_modules/tailwindcss/utilities.css
generated
vendored
Normal file
1
templates/node_modules/tailwindcss/utilities.css
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@tailwind utilities;
|
||||
16
templates/node_modules/util-deprecate/History.md
generated
vendored
Normal file
16
templates/node_modules/util-deprecate/History.md
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
1.0.2 / 2015-10-07
|
||||
==================
|
||||
|
||||
* use try/catch when checking `localStorage` (#3, @kumavis)
|
||||
|
||||
1.0.1 / 2014-11-25
|
||||
==================
|
||||
|
||||
* browser: use `console.warn()` for deprecation calls
|
||||
* browser: more jsdocs
|
||||
|
||||
1.0.0 / 2014-04-30
|
||||
==================
|
||||
|
||||
* initial commit
|
||||
24
templates/node_modules/util-deprecate/LICENSE
generated
vendored
Normal file
24
templates/node_modules/util-deprecate/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 Nathan Rajlich <nathan@tootallnate.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
53
templates/node_modules/util-deprecate/README.md
generated
vendored
Normal file
53
templates/node_modules/util-deprecate/README.md
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
util-deprecate
|
||||
==============
|
||||
### The Node.js `util.deprecate()` function with browser support
|
||||
|
||||
In Node.js, this module simply re-exports the `util.deprecate()` function.
|
||||
|
||||
In the web browser (i.e. via browserify), a browser-specific implementation
|
||||
of the `util.deprecate()` function is used.
|
||||
|
||||
|
||||
## API
|
||||
|
||||
A `deprecate()` function is the only thing exposed by this module.
|
||||
|
||||
``` javascript
|
||||
// setup:
|
||||
exports.foo = deprecate(foo, 'foo() is deprecated, use bar() instead');
|
||||
|
||||
|
||||
// users see:
|
||||
foo();
|
||||
// foo() is deprecated, use bar() instead
|
||||
foo();
|
||||
foo();
|
||||
```
|
||||
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2014 Nathan Rajlich <nathan@tootallnate.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
67
templates/node_modules/util-deprecate/browser.js
generated
vendored
Normal file
67
templates/node_modules/util-deprecate/browser.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = deprecate;
|
||||
|
||||
/**
|
||||
* Mark that a method should not be used.
|
||||
* Returns a modified function which warns once by default.
|
||||
*
|
||||
* If `localStorage.noDeprecation = true` is set, then it is a no-op.
|
||||
*
|
||||
* If `localStorage.throwDeprecation = true` is set, then deprecated functions
|
||||
* will throw an Error when invoked.
|
||||
*
|
||||
* If `localStorage.traceDeprecation = true` is set, then deprecated functions
|
||||
* will invoke `console.trace()` instead of `console.error()`.
|
||||
*
|
||||
* @param {Function} fn - the function to deprecate
|
||||
* @param {String} msg - the string to print to the console when `fn` is invoked
|
||||
* @returns {Function} a new "deprecated" version of `fn`
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function deprecate (fn, msg) {
|
||||
if (config('noDeprecation')) {
|
||||
return fn;
|
||||
}
|
||||
|
||||
var warned = false;
|
||||
function deprecated() {
|
||||
if (!warned) {
|
||||
if (config('throwDeprecation')) {
|
||||
throw new Error(msg);
|
||||
} else if (config('traceDeprecation')) {
|
||||
console.trace(msg);
|
||||
} else {
|
||||
console.warn(msg);
|
||||
}
|
||||
warned = true;
|
||||
}
|
||||
return fn.apply(this, arguments);
|
||||
}
|
||||
|
||||
return deprecated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks `localStorage` for boolean values for the given `name`.
|
||||
*
|
||||
* @param {String} name
|
||||
* @returns {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function config (name) {
|
||||
// accessing global.localStorage can trigger a DOMException in sandboxed iframes
|
||||
try {
|
||||
if (!global.localStorage) return false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
var val = global.localStorage[name];
|
||||
if (null == val) return false;
|
||||
return String(val).toLowerCase() === 'true';
|
||||
}
|
||||
6
templates/node_modules/util-deprecate/node.js
generated
vendored
Normal file
6
templates/node_modules/util-deprecate/node.js
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
/**
|
||||
* For Node.js, simply re-export the core `util.deprecate` function.
|
||||
*/
|
||||
|
||||
module.exports = require('util').deprecate;
|
||||
27
templates/node_modules/util-deprecate/package.json
generated
vendored
Normal file
27
templates/node_modules/util-deprecate/package.json
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "util-deprecate",
|
||||
"version": "1.0.2",
|
||||
"description": "The Node.js `util.deprecate()` function with browser support",
|
||||
"main": "node.js",
|
||||
"browser": "browser.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/TooTallNate/util-deprecate.git"
|
||||
},
|
||||
"keywords": [
|
||||
"util",
|
||||
"deprecate",
|
||||
"browserify",
|
||||
"browser",
|
||||
"node"
|
||||
],
|
||||
"author": "Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/TooTallNate/util-deprecate/issues"
|
||||
},
|
||||
"homepage": "https://github.com/TooTallNate/util-deprecate"
|
||||
}
|
||||
62
templates/package-lock.json
generated
Normal file
62
templates/package-lock.json
generated
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "templates",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
||||
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
5
templates/package.json
Normal file
5
templates/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19"
|
||||
}
|
||||
}
|
||||
35
templates/play.html
Normal file
35
templates/play.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white min-h-screen grid place-items-center">
|
||||
<div class="grid gap-4 text-center">
|
||||
<h2 class="text-2xl font-bold">Room {{ code }}</h2>
|
||||
<button id="tap" class="text-2xl py-16 px-24 rounded bg-indigo-600 active:scale-95">TAP</button>
|
||||
</div>
|
||||
<script>
|
||||
const code = "{{ code }}";
|
||||
const pid = "{{ pid }}";
|
||||
const socket = io("/game", { transports: ["websocket"] });
|
||||
|
||||
// join the room (server will verify if you're left/right)
|
||||
socket.emit("player_join_room", { code, pid });
|
||||
|
||||
const tapBtn = document.getElementById("tap");
|
||||
tapBtn.addEventListener("pointerdown", sendTap);
|
||||
function sendTap(e){ e && e.preventDefault(); socket.emit("tap", { code, pid }); }
|
||||
|
||||
// optional: hold-to-repeat
|
||||
tapBtn.addEventListener("pointerdown", () => {
|
||||
const t = setInterval(() => socket.emit("tap", { code, pid }), 100);
|
||||
const stop = () => { clearInterval(t); window.removeEventListener("pointerup", stop); tapBtn.removeEventListener("pointerleave", stop); };
|
||||
window.addEventListener("pointerup", stop, { once: true });
|
||||
tapBtn.addEventListener("pointerleave", stop, { once: true });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
196
templates/play_ai.html
Normal file
196
templates/play_ai.html
Normal file
@@ -0,0 +1,196 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Tapdown vs CPU — Cyber Sale Edition</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-slate-950 text-white min-h-screen flex flex-col">
|
||||
<!-- Top bar -->
|
||||
<header class="max-w-5xl mx-auto w-full px-4 py-4 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-slate-400">Player:</span>
|
||||
<span class="font-semibold">{{ name }}</span>
|
||||
<span class="mx-2 opacity-50">·</span>
|
||||
<span class="text-sm text-slate-400">Persona:</span>
|
||||
<span class="font-semibold">{{ persona }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm opacity-80">Difficulty</label>
|
||||
<select id="difficulty" class="text-black p-1 rounded">
|
||||
<option value="easy">Easy</option>
|
||||
<option value="normal" selected>Normal</option>
|
||||
<option value="hard">Hard</option>
|
||||
<option value="insane">Insane</option>
|
||||
</select>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Arena -->
|
||||
<main class="max-w-5xl mx-auto w-full px-4 flex-1">
|
||||
<div class="mb-3 text-center text-slate-300 text-sm">
|
||||
Beat the <b>Super Saver</b> bot — then browse Cyber Sale deals!
|
||||
</div>
|
||||
|
||||
<div class="relative h-44 rounded-2xl bg-slate-900/60 border border-slate-800 overflow-hidden">
|
||||
<div class="absolute inset-y-0 left-1/2 w-0.5 bg-slate-700"></div>
|
||||
<div class="absolute inset-y-0 left-0 w-1 bg-emerald-500"></div>
|
||||
<div class="absolute inset-y-0 right-0 w-1 bg-cyan-500"></div>
|
||||
<div id="puck" class="absolute top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-white shadow"></div>
|
||||
</div>
|
||||
|
||||
<p id="status" class="mt-3 text-center text-lg"></p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mt-6">
|
||||
<button id="tapLeft" class="text-2xl py-12 rounded-xl bg-emerald-600 active:scale-95 font-semibold">YOU TAP</button>
|
||||
<button id="tapRight" class="text-2xl py-12 rounded-xl bg-cyan-700 opacity-60 cursor-not-allowed font-semibold" disabled>CPU</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex items-center justify-center gap-3">
|
||||
<button id="startBtn" class="bg-emerald-500 hover:bg-emerald-600 px-5 py-2 rounded-lg font-semibold">Start</button>
|
||||
<button id="resetBtn" class="bg-slate-800 hover:bg-slate-700 px-5 py-2 rounded-lg font-semibold">Reset</button>
|
||||
<a id="shopBtn" href="/"
|
||||
class="bg-slate-900 border border-slate-700 px-5 py-2 rounded-lg font-semibold">Back</a>
|
||||
</div>
|
||||
|
||||
<!-- Cyber Sale CTA under the arena -->
|
||||
<div class="mt-8 text-center">
|
||||
<a href="{{ url_for('index') }}" class="text-slate-300 underline decoration-dotted">Cyber Sale details & QR on the promo page</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="max-w-5xl mx-auto w-full px-4 py-6 text-center text-slate-400 text-xs">
|
||||
Tip: Holding the button auto-taps. Bursts & fatigue make the bot feel human.
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
/* --- Pull persona from template to lightly tweak the feel --- */
|
||||
const PERSONA = "{{ persona }}";
|
||||
|
||||
/* --- Physics constants --- */
|
||||
const EDGE = 100.0;
|
||||
const BASE_IMPULSE = 7.0;
|
||||
let FRICTION = 0.92;
|
||||
const TICK_HZ = 60;
|
||||
|
||||
/* Persona perk (very light-touch) */
|
||||
let leftImpulseMult = 1.0;
|
||||
if (PERSONA === "Speed Demon") leftImpulseMult = 1.02;
|
||||
if (PERSONA === "Tank") FRICTION = 0.91;
|
||||
|
||||
/* --- Game state --- */
|
||||
let puck = 0.0, vel = 0.0, running = false;
|
||||
let lastTapLeft = 0, lastTapRight = 0;
|
||||
let MIN_TAP_MS = 85;
|
||||
|
||||
/* --- CPU model --- */
|
||||
const DIFF = {
|
||||
easy: { baseHz: 4.8, burstHz: 7.2, burstProb: 0.20, whiff: 0.08, friction: 0.92 },
|
||||
normal: { baseHz: 6.6, burstHz: 9.2, burstProb: 0.28, whiff: 0.05, friction: 0.92 },
|
||||
hard: { baseHz: 8.2, burstHz: 11.5, burstProb: 0.35, whiff: 0.035, friction: 0.91 },
|
||||
insane: { baseHz: 9.8, burstHz: 13.5, burstProb: 0.45, whiff: 0.02, friction: 0.905 },
|
||||
};
|
||||
let cpuCfg = DIFF.normal;
|
||||
let cpuBurstUntil = 0;
|
||||
let cpuNextTapAt = 0;
|
||||
|
||||
/* --- UI elements --- */
|
||||
const statusEl = document.getElementById("status");
|
||||
const puckEl = document.getElementById("puck");
|
||||
const leftBtn = document.getElementById("tapLeft");
|
||||
const startBtn = document.getElementById("startBtn");
|
||||
const resetBtn = document.getElementById("resetBtn");
|
||||
const diffSel = document.getElementById("difficulty");
|
||||
|
||||
/* --- Helpers --- */
|
||||
function flash(msg){ statusEl.textContent = msg; setTimeout(()=>statusEl.textContent="", 1200); }
|
||||
function setDifficulty(key){
|
||||
cpuCfg = DIFF[key] || DIFF.normal;
|
||||
FRICTION = cpuCfg.friction;
|
||||
}
|
||||
diffSel.onchange = () => { setDifficulty(diffSel.value); flash(`Difficulty: ${diffSel.value.toUpperCase()}`); };
|
||||
|
||||
/* --- Controls --- */
|
||||
leftBtn.addEventListener("pointerdown", (e) => {
|
||||
e.preventDefault(); tap("left");
|
||||
const t = setInterval(()=> tap("left"), 100);
|
||||
const stop = ()=>{ clearInterval(t); window.removeEventListener("pointerup", stop); leftBtn.removeEventListener("pointerleave", stop); };
|
||||
window.addEventListener("pointerup", stop, { once: true });
|
||||
leftBtn.addEventListener("pointerleave", stop, { once: true });
|
||||
});
|
||||
startBtn.onclick = startMatch;
|
||||
resetBtn.onclick = resetMatch;
|
||||
|
||||
/* --- Game functions --- */
|
||||
function startMatch(){
|
||||
resetMatch();
|
||||
running = true;
|
||||
scheduleCpuTap(performance.now());
|
||||
flash("Fight!");
|
||||
}
|
||||
function resetMatch(){
|
||||
running = false; vel = 0; puck = 0; updatePuck(puck);
|
||||
statusEl.textContent = "";
|
||||
}
|
||||
function tap(side){
|
||||
if(!running) return;
|
||||
const now = performance.now();
|
||||
if(side === "left"){
|
||||
if(now - lastTapLeft < MIN_TAP_MS) return;
|
||||
lastTapLeft = now;
|
||||
vel -= BASE_IMPULSE * leftImpulseMult;
|
||||
} else {
|
||||
if(now - lastTapRight < MIN_TAP_MS) return;
|
||||
lastTapRight = now;
|
||||
vel += BASE_IMPULSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- CPU scheduling --- */
|
||||
function scheduleCpuTap(now){
|
||||
if(now > cpuBurstUntil && Math.random() < cpuCfg.burstProb){
|
||||
cpuBurstUntil = now + (300 + Math.random()*400);
|
||||
}
|
||||
const inBurst = now < cpuBurstUntil;
|
||||
const hz = inBurst ? cpuCfg.burstHz : cpuCfg.baseHz;
|
||||
const intervalMs = 1000 * ( -Math.log(1 - Math.random()) / hz );
|
||||
cpuNextTapAt = now + intervalMs;
|
||||
}
|
||||
function maybeCpuTap(now){
|
||||
if(now < cpuNextTapAt) return;
|
||||
if(Math.random() > cpuCfg.whiff) tap("right");
|
||||
scheduleCpuTap(now);
|
||||
}
|
||||
|
||||
/* --- Loop --- */
|
||||
function tick(){
|
||||
const dt = 1.0 / TICK_HZ;
|
||||
if(running){
|
||||
vel *= FRICTION;
|
||||
puck += vel * dt * 6.0;
|
||||
const now = performance.now();
|
||||
maybeCpuTap(now);
|
||||
if(puck <= -EDGE || puck >= EDGE){
|
||||
running = false;
|
||||
statusEl.textContent = puck <= -EDGE ? "YOU WIN! 🥳" : "CPU WINS! 🤖";
|
||||
}
|
||||
}
|
||||
updatePuck(puck);
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
|
||||
function updatePuck(p){
|
||||
const track = document.querySelector(".relative.h-44");
|
||||
const w = track.clientWidth;
|
||||
const x = (p / 100) * (w/2 - 10);
|
||||
puckEl.style.left = `calc(50% + ${x}px)`;
|
||||
}
|
||||
|
||||
/* init */
|
||||
setDifficulty("normal");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,38 +1,74 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Projects{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
|
||||
<h1 class="text-5xl font-extrabold mb-4">My Projects</h1>
|
||||
<p class="text-xl mb-6">Here’s a showcase of the things I’ve built or contributed to.</p>
|
||||
</section>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="text-center py-20 bg-gradient-to-br from-orange-500/20 via-rose-500/10 to-fuchsia-500/10 text-white shadow-inner">
|
||||
<h1 class="heading-hero mb-4">My Projects</h1>
|
||||
<p class="text-lg opacity-90">A snapshot of things I’ve built or contributed to.</p>
|
||||
</section>
|
||||
|
||||
<!-- Projects Grid -->
|
||||
<section id="projects" class="py-16 bg-black text-white">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{% for projectpost in projectpost %}
|
||||
<div class="bg-white rounded-lg shadow p-6 hover:shadow-lg transition">
|
||||
<h2 class="text-2xl font-bold text-red-800 mb-2">
|
||||
<a href="{{ url_for('view_project', slug=projectpost.slug) }}" class="hover:underline">{{ projectpost.title }}</a>
|
||||
</h2>
|
||||
<p class="text-sm text-orange-500 font-medium mb-4">{{ projectpost.category }}</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for tag in projectpost.tags.split(',') %}
|
||||
<a href="{{ url_for('view_project_tag', tag=tag.strip()) }}"
|
||||
class="inline-block bg-orange-200 text-orange-900 text-sm px-3 py-1 rounded-full hover:bg-orange-300 transition">
|
||||
#{{ tag.strip() }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<section id="projects" class="py-16">
|
||||
<div class="container-page">
|
||||
{% if projectpost|length == 0 %}
|
||||
<div class="card text-center">
|
||||
<h2 class="text-2xl font-semibold mb-2">Nothing here yet</h2>
|
||||
<p class="subtle">Projects will appear as you publish them.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{% for p in projectpost %}
|
||||
<article class="card group overflow-hidden transition hover:shadow-glass">
|
||||
{% if p.cover_image %}
|
||||
<a href="{{ url_for('view_project', slug=p.slug) }}" class="block -m-6 mb-4 overflow-hidden rounded-2xl">
|
||||
<img src="{{ p.cover_image }}" alt="{{ p.title }}" class="w-full h-40 object-cover group-hover:scale-[1.02] transition" loading="lazy">
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="text-xl font-semibold">
|
||||
<a href="{{ url_for('view_project', slug=p.slug) }}" class="hover:underline">
|
||||
{{ p.title }}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
{% if p.category %}
|
||||
<span class="mt-2 inline-flex items-center gap-2 text-xs font-medium px-3 py-1 rounded-full
|
||||
border border-[rgb(var(--border))] bg-[rgb(var(--card))] text-[rgb(var(--muted))]">
|
||||
{{ p.category }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if p.summary %}
|
||||
<p class="mt-3 subtle line-clamp-3">{{ p.summary }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% set tags = (p.tags|default('', true)).split(',') if p.tags else [] %}
|
||||
{% if tags %}
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
{% for tag in tags %}
|
||||
{% set t = tag.strip() %}
|
||||
{% if t %}
|
||||
<a href="{{ url_for('view_project_tag', tag=t) }}"
|
||||
class="text-xs px-3 py-1 rounded-full border border-[rgb(var(--border))]
|
||||
bg-[rgb(var(--card))] hover:bg-[rgb(var(--border))] transition">
|
||||
#{{ t }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-6">
|
||||
<a href="{{ url_for('view_project', slug=p.slug) }}" class="btn btn-primary">View Project</a>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,35 +1,56 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tag Results{% endblock %}
|
||||
{% block title %}Tag: #{{ tag }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
|
||||
<h1 class="text-5xl font-extrabold mb-4">Posts Tagged: <span class="text-white">#{{ tag }}</span></h1>
|
||||
<p class="text-lg">Here are all Project Posts with this tag.</p>
|
||||
<!-- Hero -->
|
||||
<section class="relative text-center py-20 text-white bg-gradient-to-br from-orange-500/20 via-rose-500/10 to-fuchsia-500/10 backdrop-blur border-b border-white/10">
|
||||
<div class="max-w-3xl mx-auto px-6">
|
||||
<h1 class="text-5xl md:text-6xl font-archivo font-extrabold mb-3">
|
||||
Posts tagged <span class="text-orange-300">#{{ tag }}</span>
|
||||
</h1>
|
||||
<p class="text-white/80">Projects and posts matching this tag.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Tag Results Content -->
|
||||
<section class="py-16 bg-black text-white">
|
||||
<div class="max-w-5xl mx-auto px-4">
|
||||
{% if projectpost %}
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{% for projectpost in projectpost %}
|
||||
<div class="bg-white rounded-lg shadow p-6 hover:shadow-lg transition">
|
||||
<h2 class="text-2xl font-bold text-red-800 mb-2">
|
||||
<a href="{{ url_for('view_project', slug=projectpost.slug) }}" class="hover:underline">
|
||||
{{ projectpost.title }}
|
||||
<!-- Results -->
|
||||
<section class="py-16">
|
||||
<div class="container-page">
|
||||
{% if projectpost and projectpost|length %}
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{% for p in projectpost %}
|
||||
<article class="bg-white/5 backdrop-blur-md border border-white/10 rounded-2xl p-6 hover:shadow-xl hover:border-orange-400/30 transition-all duration-300">
|
||||
<h2 class="text-xl font-semibold font-archivo tracking-tight">
|
||||
<a href="{{ url_for('view_project', slug=p.slug) }}" class="hover:text-orange-400 transition">
|
||||
{{ p.title }}
|
||||
</a>
|
||||
</h2>
|
||||
<p class="text-sm text-orange-500 font-medium">{{ projectpost.category }}</p>
|
||||
</div>
|
||||
|
||||
{% if p.category %}
|
||||
<p class="mt-2 text-sm text-orange-400 font-medium">{{ p.category }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if p.summary %}
|
||||
<p class="mt-3 text-white/70 line-clamp-3">{{ p.summary }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-6">
|
||||
<a href="{{ url_for('view_project', slug=p.slug) }}" class="btn btn-primary">View</a>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-center text-lg text-gray-600">No posts found with this tag.</p>
|
||||
<div class="card max-w-2xl mx-auto text-center">
|
||||
<h3 class="text-2xl font-semibold mb-2">No results</h3>
|
||||
<p class="text-white/70">Nothing tagged <span class="text-orange-300">#{{ tag }}</span> yet.</p>
|
||||
<div class="mt-6">
|
||||
<a href="{{ url_for('projects') }}" class="btn btn-ghost">← Back to Projects</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
27
templates/queue.html
Normal file
27
templates/queue.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white min-h-screen grid place-items-center">
|
||||
<div class="text-center space-y-3">
|
||||
<h2 class="text-3xl font-bold">You’re in the Queue</h2>
|
||||
<p>ID: <b id="pid">{{ pid }}</b> — Persona: <b>{{ persona }}</b></p>
|
||||
<p>Hang tight—host will assign your match.</p>
|
||||
</div>
|
||||
<script>
|
||||
const pid = "{{ pid }}";
|
||||
const socket = io("/game", { transports: ["websocket"] });
|
||||
socket.emit("queue_subscribe", { pid });
|
||||
|
||||
// Host will notify your personal channel
|
||||
socket.on("assigned_room", ({ code, side }) => {
|
||||
// Optional: you can show side here; server still verifies it
|
||||
window.location.href = `/play/${code}`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,94 +1,162 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}About Me{% endblock %}
|
||||
{% block title %}Resume{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="relative text-center py-24 text-white bg-gradient-to-br from-orange-500/20 via-rose-500/10 to-fuchsia-500/10 backdrop-blur border-b border-white/10">
|
||||
<div class="max-w-3xl mx-auto px-6">
|
||||
<h1 class="text-5xl md:text-6xl font-archivo font-extrabold">Benjamin Mosley</h1>
|
||||
<p class="mt-3 text-white/80 text-lg">E-Commerce Manager - CIS Student</p>
|
||||
<div class="mt-6 flex flex-wrap items-center justify-center gap-3 text-sm">
|
||||
<span class="px-3 py-1 rounded-full border border-white/15 bg-white/5">Canyon, TX 79015</span>
|
||||
<a href="mailto:benjaymos@proton.me" class="px-3 py-1 rounded-full border border-white/15 bg-white/5 hover:bg-white/10 transition">benjaymos@proton.me</a>
|
||||
<a href="https://benjaminmosley.com" target="_blank" class="px-3 py-1 rounded-full border border-white/15 bg-white/5 hover:bg-white/10 transition">benjaminmosley.com</a>
|
||||
{# Optional: add a real static path if you place the PDF under /static/ #}
|
||||
{# <a href="{{ url_for('static', filename='Benjamin Mosley - Resume.pdf') }}" class="btn btn-ghost">Download PDF</a> #}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
|
||||
<h1 class="text-5xl font-extrabold mb-4">Benjamin Mosley</h1>
|
||||
<p class="text-xl">Aspiring IT Professional & Web Developer</p>
|
||||
<!-- About -->
|
||||
<section class="py-14">
|
||||
<div class="container-page">
|
||||
<article class="card max-w-4xl mx-auto">
|
||||
<h2 class="text-2xl font-semibold">About Me</h2>
|
||||
<p class="mt-3 text-white/80 leading-relaxed">
|
||||
I’ve been working with computers for as long as I can remember—what started as curiosity has grown into a passion.
|
||||
From backend scripting and networking to frontend design, I enjoy creating efficient and user-friendly solutions.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Skills & Education (balanced two-column layout) -->
|
||||
<section class="py-6">
|
||||
<div class="container-page grid md:grid-cols-2 gap-6 items-stretch">
|
||||
<!-- Skills -->
|
||||
<article class="card h-full">
|
||||
<h2 class="text-2xl font-semibold">Skills</h2>
|
||||
|
||||
<div class="mt-4 grid sm:grid-cols-2 gap-6 text-sm">
|
||||
<div>
|
||||
<p class="font-medium text-white/90">Programming</p>
|
||||
<ul class="mt-2 space-y-1 text-white/70 list-disc pl-5">
|
||||
<li>Python</li>
|
||||
<li>C#, ASP.NET Core</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-medium text-white/90">Web & Database</p>
|
||||
<ul class="mt-2 space-y-1 text-white/70 list-disc pl-5">
|
||||
<li>Flask, Tailwind CSS</li>
|
||||
<li>SQL (MySQL, SQLite), EF Core</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-medium text-white/90">Systems</p>
|
||||
<ul class="mt-2 space-y-1 text-white/70 list-disc pl-5">
|
||||
<li>Linux, Windows Server</li>
|
||||
<li>Networking & Security</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-medium text-white/90">Professional</p>
|
||||
<ul class="mt-2 space-y-1 text-white/70 list-disc pl-5">
|
||||
<li>Leadership & Team Ops</li>
|
||||
<li>Process Improvement</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Education + Accomplishments -->
|
||||
<article class="card h-full">
|
||||
<h2 class="text-2xl font-semibold">Education</h2>
|
||||
<div class="mt-4">
|
||||
<p class="font-medium">West Texas A&M University</p>
|
||||
<p class="text-white/70">BBA in Computer Information Systems</p>
|
||||
<p class="text-white/60 text-sm mt-1">Expected May 2026</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 border-t border-[rgb(var(--border))] pt-6">
|
||||
<h3 class="text-lg font-semibold">Accomplishments</h3>
|
||||
<ul class="mt-3 text-white/80 space-y-2 list-disc pl-5">
|
||||
<li>Eagle Scout (Dec 2021)</li>
|
||||
<li>National Youth Leadership Training (2017–2019)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Experience -->
|
||||
<section class="py-10">
|
||||
<div class="container-page">
|
||||
<article class="card">
|
||||
<h2 class="text-2xl font-semibold">Experience</h2>
|
||||
|
||||
<!-- Current role -->
|
||||
<div class="mt-6 relative pl-6">
|
||||
<div class="absolute left-0 top-2 h-2 w-2 rounded-full bg-[rgb(var(--accent))]"></div>
|
||||
<div class="flex flex-col sm:flex-row sm:items-baseline sm:justify-between gap-1">
|
||||
<p class="font-medium">E-Commerce Manager — United Supermarkets #532</p>
|
||||
<p class="text-white/60 text-sm">Aug 2025 – Present</p>
|
||||
</div>
|
||||
<p class="text-white/80">Canyon, TX</p>
|
||||
<ul class="mt-2 text-white/70 space-y-1 list-disc pl-5">
|
||||
<li>Own day-to-day online grocery operations (order flow, substitutions, delivery/pickup SLA).</li>
|
||||
<li>Lead and schedule the e-commerce team; coach on accuracy, speed, and CX.</li>
|
||||
<li>Monitor inventory/pricing anomalies and coordinate fixes with department leads.</li>
|
||||
<li>Track KPIs (fill rate, OTIF, cancels, customer feedback) and drive process improvements.</li>
|
||||
<li>Partner with IT/vendors to keep handhelds, printers, and store systems humming.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Internship (closed out) -->
|
||||
<div class="mt-8 relative pl-6">
|
||||
<div class="absolute left-0 top-2 h-2 w-2 rounded-full bg-white/40"></div>
|
||||
<div class="flex flex-col sm:flex-row sm:items-baseline sm:justify-between gap-1">
|
||||
<p class="font-medium">IT Intern / Junior System Administrator — Hutchinson County Court House</p>
|
||||
<p class="text-white/60 text-sm">May 2025 – Aug 2025</p>
|
||||
</div>
|
||||
<p class="text-white/80">Stinnett, TX</p>
|
||||
<ul class="mt-2 text-white/70 space-y-1 list-disc pl-5">
|
||||
<li>Assisted with Windows Server/Active Directory admin and routine maintenance.</li>
|
||||
<li>Resolved helpdesk tickets; imaged PCs and handled hardware/software issues.</li>
|
||||
<li>Helped configure switches/APs/firewall rules; supported backups and updates.</li>
|
||||
<li>Contributed to security policy rollouts and small infrastructure upgrades.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Earlier roles (optional condensed) -->
|
||||
<div class="mt-8 relative pl-6">
|
||||
<div class="absolute left-0 top-2 h-2 w-2 rounded-full bg-white/20"></div>
|
||||
<div class="flex flex-col sm:flex-row sm:items-baseline sm:justify-between gap-1">
|
||||
<p class="font-medium">United Supermarkets & The Pergola Shop — Various Roles</p>
|
||||
<p class="text-white/60 text-sm">2019 – 2025</p>
|
||||
</div>
|
||||
<ul class="mt-2 text-white/70 space-y-1 list-disc pl-5">
|
||||
<li>Bookkeeper, Lead Stocker, Wood Cutter — leadership, reliability, and strong customer service.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="py-16 bg-black text-white">
|
||||
<div class="max-w-4xl mx-auto px-6 space-y-12">
|
||||
|
||||
|
||||
<div class="text-center space-y-2">
|
||||
<p><strong>Email:</strong> <a href="mailto:ben@bennyshouse.net" class="text-blue-600 hover:underline">ben@bennyshouse.net</a></p>
|
||||
<p><strong>Portfolio:</strong> <a href="https://benjaminmosley.com" class="text-blue-600 hover:underline">benjaminmosley.com</a></p>
|
||||
<p><strong>Business Website:</strong> <a href="https://bennyshouse.net" class="text-blue-600 hover:underline">bennyshouse.net</a></p>
|
||||
<p><strong>LinkedIn:</strong> <a href="https://www.linkedin.com/in/benjamin-mosley-849643329/" class="text-blue-600 hover:underline">Benjamin Mosley</a></p>
|
||||
<p><strong>GitHub:</strong> <a href="https://github.com/Benny-ui-ux" class="text-blue-600 hover:underline">github.com/Benny-ui-ux</a></p>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-orange-600 mb-4">Education</h2>
|
||||
<ul class="space-y-3 text-lg">
|
||||
<li><strong>High School Graduation:</strong> Mother of Divine Grace – June 2022</li>
|
||||
<li><strong>College:</strong> West Texas A&M University<br>
|
||||
<span class="text-sm text-white italic">Currently pursuing B.S. in Computer Information Systems, graduating Spring 2026</span>
|
||||
</li>
|
||||
<section class="pb-16">
|
||||
<div class="container-page">
|
||||
<article class="card max-w-4xl">
|
||||
<h2 class="text-2xl font-semibold">Accomplishments</h2>
|
||||
<ul class="mt-3 text-white/80 space-y-2 list-disc pl-5">
|
||||
<li>Eagle Scout (Dec 2021)</li>
|
||||
<li>National Youth Leadership Training (2017–2019)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-orange-600 mb-4">Background</h2>
|
||||
<p class="text-lg leading-relaxed">
|
||||
I’ve been working with computers for as long as I can remember — what started as curiosity has grown into a passion. My technical journey continues to evolve through real-world experience and ongoing education.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-orange-600 mb-4">Skills</h2>
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-lg list-disc pl-6">
|
||||
<li>Python Programming</li>
|
||||
<li>Linux Operating Systems</li>
|
||||
<li>Web Design & Hosting</li>
|
||||
<li>SQL & Database Management</li>
|
||||
<li>Leadership & Teamwork</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold text-orange-600 mb-4">Work Experience</h2>
|
||||
<div class="space-y-6 text-lg">
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold">United Supermarkets – Borger, TX</h3>
|
||||
<p class="italic text-white">Grocery Team Lead | Nov 2019 – Aug 2022</p>
|
||||
<ul class="list-disc pl-5 mt-2">
|
||||
<li>Stocked shelves and maintained floor organization</li>
|
||||
<li>Assisted customers and team members</li>
|
||||
<li>Managed ordering and inventory processes</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold">The Pergola Shop – Canyon, TX</h3>
|
||||
<p class="italic text-white">Wood Stainer / Cutter | Aug 2022 – Aug 2023</p>
|
||||
<ul class="list-disc pl-5 mt-2">
|
||||
<li>Prepped and treated wood materials for construction</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold">United Supermarkets – Canyon, TX</h3>
|
||||
<p class="italic text-white">Service Center / Bookkeeper | Aug 2023 – Present</p>
|
||||
<ul class="list-disc pl-5 mt-2">
|
||||
<li>Nightly accounting duties and managment of my team</li>
|
||||
<li>Provided high-quality customer service</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
23
templates/services.html
Normal file
23
templates/services.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Services — {{ brand }}{% endblock %}
|
||||
{% block content %}
|
||||
<section class="max-w-7xl mx-auto px-6 py-16">
|
||||
<h1 class="text-3xl font-bold">Services</h1>
|
||||
<div class="mt-6 grid lg:grid-cols-3 gap-6">
|
||||
{% for s in [
|
||||
{"t":"Prototyping", "d":"Clickable by Friday; prove the bet fast."},
|
||||
{"t":"Web Platforms", "d":"Flask/FastAPI, Tailwind, SQL with CI/CD."},
|
||||
{"t":"Event Activations", "d":"Kiosks, QR onboarding, local-first modes."},
|
||||
{"t":"Data & Analytics", "d":"Capture, export, and visualize with ease."},
|
||||
{"t":"Integrations", "d":"Payments, auth, coupon APIs, catalog sync."},
|
||||
{"t":"Ops & Hardening", "d":"Nginx, Gunicorn, Docker, logging, SSO."},
|
||||
] %}
|
||||
<div class="rounded-2xl bg-bh.card/70 border border-bh.ring p-6">
|
||||
<h3 class="text-xl font-semibold">{{ s.t }}</h3>
|
||||
<p class="mt-2 text-white/75">{{ s.d }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
36
templates/survey.html
Normal file
36
templates/survey.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-white">
|
||||
<form method="post" class="max-w-xl mx-auto p-6 grid gap-4">
|
||||
<h1 class="text-2xl font-bold">30-Second Survey</h1>
|
||||
<label class="grid gap-2">
|
||||
Favorite tailgate drink?
|
||||
<select name="drink" class="text-black p-2 rounded">
|
||||
<option value="water">Water</option>
|
||||
<option value="soda">Soda</option>
|
||||
<option value="energy">Energy drink</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="grid gap-2">
|
||||
Food vibe?
|
||||
<select name="food" class="text-black p-2 rounded">
|
||||
<option value="bbq">BBQ</option>
|
||||
<option value="veggies">Veggies</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="grid gap-2">
|
||||
Your style?
|
||||
<select name="vibe" class="text-black p-2 rounded">
|
||||
<option value="precision">Precision</option>
|
||||
<option value="chaos">Chaos</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="bg-emerald-500 py-2 rounded text-lg">Submit</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
10
templates/thanks.html
Normal file
10
templates/thanks.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Thanks — {{ brand }}{% endblock %}
|
||||
{% block content %}
|
||||
<section class="max-w-7xl mx-auto px-6 py-24 text-center">
|
||||
<h1 class="text-3xl font-bold">Thanks! We’ll be in touch.</h1>
|
||||
<p class="mt-3 text-white/75">We usually reply within 1–2 business days.</p>
|
||||
<a href="{{ url_for('home') }}" class="mt-6 inline-flex items-center rounded-lg bg-bh-accent/90 hover:bg-bh-accent text-black px-5 py-2 font-semibold">Back to home</a>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,78 +1,63 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ blogpost.title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Hero Title Section -->
|
||||
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
|
||||
<h1 class="text-5xl font-extrabold mb-4">{{ blogpost.title }}</h1>
|
||||
<p class="text-lg italic">Category: {{ blogpost.category }}</p>
|
||||
<div class="mt-4 flex justify-center flex-wrap gap-2">
|
||||
{% for tag in blogpost.tags.split(',') %}
|
||||
<a href="{{ url_for('view_tag', tag=tag.strip()) }}"
|
||||
class="bg-white text-gray-800 text-sm px-3 py-1 rounded-full shadow hover:bg-gray-100 transition">
|
||||
#{{ tag.strip() }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
<!-- Hero -->
|
||||
<section class="py-20 text-center text-white bg-gradient-to-br from-orange-500/20 via-rose-500/10 to-fuchsia-500/10 backdrop-blur border-b border-white/10">
|
||||
<div class="max-w-3xl mx-auto px-6">
|
||||
<h1 class="text-5xl md:text-6xl font-archivo font-extrabold mb-3">{{ blogpost.title }}</h1>
|
||||
|
||||
{% if blogpost.category %}
|
||||
<p class="text-white/80 italic text-lg">{{ blogpost.category }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if blogpost.tags %}
|
||||
<div class="mt-4 flex justify-center flex-wrap gap-2">
|
||||
{% for tag in blogpost.tags.split(',') %}
|
||||
{% set t = tag.strip() %}
|
||||
{% if t %}
|
||||
<a href="{{ url_for('view_tag', tag=t) }}"
|
||||
class="text-xs px-3 py-1 rounded-full border border-white/15 bg-white/5 hover:bg-white/10 transition">
|
||||
#{{ t }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Content Section -->
|
||||
<section class="py-16 bg-black text-white">
|
||||
<div class="max-w-4xl mx-auto px-4 space-y-8">
|
||||
<!-- Body -->
|
||||
<section class="py-16">
|
||||
<div class="container-page space-y-10">
|
||||
|
||||
{% if blogpost.images %}
|
||||
<div class="mb-5">
|
||||
<h3 class="fs-3 fw-semibold text-orange mb-4">Gallery</h3>
|
||||
|
||||
<div id="blogImageCarousel" class="carousel slide shadow rounded overflow-hidden" data-bs-ride="carousel">
|
||||
<div class="carousel-inner">
|
||||
{% for image in blogpost.images|from_json %}
|
||||
<div class="carousel-item {% if loop.first %}active{% endif %}">
|
||||
<img src="{{ url_for('static', filename='uploads/' + image) }}" class="d-block w-100" alt="Post Image"
|
||||
style="max-height: 500px; object-fit: cover;">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#blogImageCarousel" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#blogImageCarousel" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
|
||||
<!-- Optional indicators -->
|
||||
<div class="carousel-indicators">
|
||||
{% for image in blogpost.images|from_json %}
|
||||
<button type="button" data-bs-target="#blogImageCarousel" data-bs-slide-to="{{ loop.index0 }}"
|
||||
{% if loop.first %}class="active" aria-current="true"{% endif %} aria-label="Slide {{ loop.index }}"></button>
|
||||
{% endfor %}
|
||||
<!-- Simple static gallery (no extra routes) -->
|
||||
<div class="card bg-[rgb(var(--card))]">
|
||||
<h3 class="text-xl font-semibold text-white mb-4">Gallery</h3>
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
{% for image in blogpost.images|from_json %}
|
||||
<div class="overflow-hidden rounded-xl border border-[rgb(var(--border))] bg-[rgb(var(--card))] hover:scale-[1.02] transition-transform duration-300 shadow-glass">
|
||||
<img src="{{ url_for('static', filename='uploads/' + image) }}"
|
||||
alt="Blog image {{ loop.index }}"
|
||||
class="w-full h-64 object-cover">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
<!-- Blog Content -->
|
||||
<div class="prose lg:prose-lg max-w-none">
|
||||
<!-- Content -->
|
||||
<article class="prose prose-invert max-w-none leading-relaxed">
|
||||
{{ blogpost.content | safe }}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="pt-10 text-center">
|
||||
<a href="{{ url_for('blog') }}"
|
||||
class="inline-block bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">
|
||||
← Back to Blog
|
||||
</a>
|
||||
<div class="pt-2">
|
||||
<a href="{{ url_for('blog') }}" class="btn btn-ghost">← Back to Blog</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,77 +1,61 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ projectpost.title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- Hero Title Section -->
|
||||
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
|
||||
<h1 class="text-5xl font-extrabold mb-4">{{ projectpost.title }}</h1>
|
||||
<p class="text-lg italic">Category: {{ projectpost.category }}</p>
|
||||
<div class="mt-4 flex justify-center flex-wrap gap-2">
|
||||
{% for tag in projectpost.tags.split(',') %}
|
||||
<a href="{{ url_for('view_tag', tag=tag.strip()) }}"
|
||||
class="bg-white text-gray-800 text-sm px-3 py-1 rounded-full shadow hover:bg-gray-100 transition">
|
||||
#{{ tag.strip() }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
<!-- Hero Section -->
|
||||
<section class="py-20 text-center text-white bg-gradient-to-br from-orange-500/20 via-rose-500/10 to-fuchsia-500/10 backdrop-blur border-b border-white/10">
|
||||
<div class="max-w-3xl mx-auto px-6">
|
||||
<h1 class="text-5xl md:text-6xl font-archivo font-extrabold mb-4">{{ projectpost.title }}</h1>
|
||||
{% if projectpost.category %}
|
||||
<p class="text-white/80 italic text-lg">{{ projectpost.category }}</p>
|
||||
{% endif %}
|
||||
{% if projectpost.tags %}
|
||||
<div class="mt-4 flex justify-center flex-wrap gap-2">
|
||||
{% for tag in projectpost.tags.split(',') %}
|
||||
{% set t = tag.strip() %}
|
||||
{% if t %}
|
||||
<a href="{{ url_for('view_tag', tag=t) }}"
|
||||
class="text-xs px-3 py-1 rounded-full border border-white/15 bg-white/5 hover:bg-white/10 transition">
|
||||
#{{ t }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Content Section -->
|
||||
<section class="py-16 bg-black text-white">
|
||||
<div class="max-w-4xl mx-auto px-4 space-y-8">
|
||||
<section class="py-16">
|
||||
<div class="container-page space-y-10">
|
||||
|
||||
{% if projectpost.images %}
|
||||
<div class="mb-5">
|
||||
<h3 class="fs-3 fw-semibold text-orange mb-4">Gallery</h3>
|
||||
|
||||
<div id="blogImageCarousel" class="carousel slide shadow rounded overflow-hidden" data-bs-ride="carousel">
|
||||
<div class="carousel-inner">
|
||||
{% for image in projectpost.images|from_json %}
|
||||
<div class="carousel-item {% if loop.first %}active{% endif %}">
|
||||
<img src="{{ url_for('static', filename='uploads/' + image) }}" class="d-block w-100" alt="Post Image"
|
||||
style="max-height: 500px; object-fit: cover;">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#blogImageCarousel" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#blogImageCarousel" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
|
||||
<!-- Optional indicators -->
|
||||
<div class="carousel-indicators">
|
||||
{% for image in projectpost.images|from_json %}
|
||||
<button type="button" data-bs-target="#blogImageCarousel" data-bs-slide-to="{{ loop.index0 }}"
|
||||
{% if loop.first %}class="active" aria-current="true"{% endif %} aria-label="Slide {{ loop.index }}"></button>
|
||||
{% endfor %}
|
||||
<!-- Simple static gallery (no extra routes) -->
|
||||
<div class="card bg-[rgb(var(--card))]">
|
||||
<h3 class="text-xl font-semibold text-white mb-4">Gallery</h3>
|
||||
<div class="grid gap-6 sm:grid-cols-2">
|
||||
{% for image in projectpost.images|from_json %}
|
||||
<div class="overflow-hidden rounded-xl border border-[rgb(var(--border))] bg-[rgb(var(--card))] hover:scale-[1.02] transition-transform duration-300 shadow-glass">
|
||||
<img src="{{ url_for('static', filename='uploads/' + image) }}"
|
||||
alt="Project image {{ loop.index }}"
|
||||
class="w-full h-64 object-cover">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<!-- Blog Content -->
|
||||
<div class="prose lg:prose-lg max-w-none">
|
||||
<!-- Content -->
|
||||
<article class="prose prose-invert max-w-none leading-relaxed">
|
||||
{{ projectpost.content | safe }}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="pt-10 text-center">
|
||||
<a href="{{ url_for('projects') }}"
|
||||
class="inline-block bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">
|
||||
← Back to Projects
|
||||
</a>
|
||||
<div class="pt-2">
|
||||
<a href="{{ url_for('projects') }}" class="btn btn-ghost">← Back to Projects</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
22
templates/work.html
Normal file
22
templates/work.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Work — {{ brand }}{% endblock %}
|
||||
{% block content %}
|
||||
<section class="max-w-7xl mx-auto px-6 py-16">
|
||||
<h1 class="text-3xl font-bold">Selected Work</h1>
|
||||
<div class="mt-8 grid lg:grid-cols-2 gap-8">
|
||||
{% for c in cases %}
|
||||
<article class="rounded-2xl bg-bh.card/70 border border-bh.ring overflow-hidden">
|
||||
<img src="{{ c.image }}" alt="{{ c.title }}" class="w-full aspect-video object-cover" onerror="this.style.display='none'">
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold">{{ c.title }}</h3>
|
||||
<p class="mt-2 text-white/80">{{ c.desc }}</p>
|
||||
<ul class="mt-3 space-y-1 text-white/70 text-sm">
|
||||
{% for b in c.bullets %}<li>• {{ b }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user