Compare commits
11 Commits
fix/deploy
...
fix/csp-go
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd0c1dd4c8
|
||
|
|
78c4313874
|
||
| ec89a5fe35 | |||
|
|
71fe06edd1
|
||
|
|
bff59eec06
|
||
|
|
8b83712cbf
|
||
|
|
73ef38a144
|
||
|
|
1c7b96f723
|
||
| a880643d65 | |||
| d7bfb44ced | |||
| 44ffae7f99 |
@@ -25,9 +25,9 @@ class SecurityHeadersMiddleware:
|
||||
response["Content-Security-Policy"] = (
|
||||
f"default-src 'self'; "
|
||||
f"script-src 'self' 'nonce-{nonce}'; "
|
||||
"style-src 'self'; "
|
||||
"style-src 'self' https://fonts.googleapis.com; "
|
||||
"img-src 'self' data: blob:; "
|
||||
"font-src 'self'; "
|
||||
"font-src 'self' https://fonts.gstatic.com; "
|
||||
"connect-src 'self'; "
|
||||
"object-src 'none'; "
|
||||
"base-uri 'self'; "
|
||||
|
||||
@@ -142,6 +142,13 @@ X_CONTENT_TYPE_OPTIONS = "nosniff"
|
||||
CSRF_TRUSTED_ORIGINS = [u for u in os.getenv("CSRF_TRUSTED_ORIGINS", "http://localhost:8035").split(",") if u]
|
||||
TRUSTED_PROXY_IPS = [ip.strip() for ip in os.getenv("TRUSTED_PROXY_IPS", "").split(",") if ip.strip()]
|
||||
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
TAILWIND_APP_NAME = "theme"
|
||||
|
||||
@@ -9,7 +9,15 @@ INTERNAL_IPS = ["127.0.0.1"]
|
||||
# media files natively when DEBUG=True (via django.contrib.staticfiles + the
|
||||
# media URL pattern in urls.py).
|
||||
MIDDLEWARE = [m for m in MIDDLEWARE if m != "whitenoise.middleware.WhiteNoiseMiddleware"]
|
||||
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
import debug_toolbar # noqa: F401
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{% block title %}No Hype AI{% endblock %}</title>
|
||||
{% block head_meta %}{% endblock %}
|
||||
<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=Fira+Code:wght@400;500&family=Inter:wght@400;500;600&family=Space+Grotesk:wght@500;700;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{% static 'css/styles.css' %}" />
|
||||
<script nonce="{{ request.csp_nonce|default:'' }}">
|
||||
(function(){try{if(localStorage.getItem('theme')==='light'){document.documentElement.classList.remove('dark');}}catch(e){}})();
|
||||
@@ -15,17 +18,18 @@
|
||||
<script src="{% static 'js/prism.js' %}" defer></script>
|
||||
<script src="{% static 'js/newsletter.js' %}" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="bg-brand-light dark:bg-brand-dark text-brand-dark dark:text-brand-light antialiased min-h-screen flex flex-col relative">
|
||||
<div class="fixed inset-0 bg-grid-pattern pointer-events-none z-[-1]"></div>
|
||||
{% include 'components/nav.html' %}
|
||||
{% include 'components/cookie_banner.html' %}
|
||||
{% if messages %}
|
||||
<section aria-label="Messages">
|
||||
<section aria-label="Messages" class="max-w-7xl mx-auto px-6 py-2">
|
||||
{% for message in messages %}
|
||||
<p>{{ message }}</p>
|
||||
<p class="font-mono text-sm py-2 px-4 bg-brand-cyan/10 text-brand-cyan border border-brand-cyan/20 mb-2">{{ message }}</p>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
<main>{% block content %}{% endblock %}</main>
|
||||
<main class="flex-grow w-full max-w-7xl mx-auto px-6 py-8">{% block content %}{% endblock %}</main>
|
||||
{% include 'components/footer.html' %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,14 +1,40 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load wagtailimages_tags wagtailcore_tags %}
|
||||
{% block title %}{{ page.title }} | No Hype AI{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
<p>{{ page.mission_statement }}</p>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="py-8 md:py-12 border-b border-zinc-200 dark:border-zinc-800 mb-12">
|
||||
<h1 class="font-display font-black text-4xl md:text-6xl mb-4">{{ page.title }}</h1>
|
||||
{% if page.mission_statement %}
|
||||
<p class="text-xl md:text-2xl text-zinc-600 dark:text-zinc-400 font-medium max-w-2xl">{{ page.mission_statement }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12">
|
||||
<div class="lg:col-span-8 prose prose-lg dark:prose-invert max-w-none
|
||||
prose-headings:font-display prose-headings:font-bold
|
||||
prose-a:text-brand-cyan hover:prose-a:text-brand-pink prose-a:transition-colors prose-a:no-underline hover:prose-a:underline">
|
||||
{{ page.body|richtext }}
|
||||
</div>
|
||||
|
||||
{% if page.featured_author %}
|
||||
<h2>{{ page.featured_author.name }}</h2>
|
||||
<p>{{ page.featured_author.bio }}</p>
|
||||
<aside class="lg:col-span-4">
|
||||
<div class="bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 p-6 relative overflow-hidden">
|
||||
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-brand-cyan to-brand-pink"></div>
|
||||
{% if page.featured_author.avatar %}
|
||||
{% image page.featured_author.avatar fill-200x200 %}
|
||||
<div class="w-20 h-20 mb-4 overflow-hidden border border-zinc-200 dark:border-zinc-800">
|
||||
{% image page.featured_author.avatar fill-80x80 class="w-full h-full object-cover" %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="w-20 h-20 mb-4 bg-gradient-to-tr from-brand-cyan to-brand-pink"></div>
|
||||
{% endif %}
|
||||
<h2 class="font-display font-bold text-xl mb-1">{{ page.featured_author.name }}</h2>
|
||||
{% if page.featured_author.role %}<p class="font-mono text-xs text-zinc-500 mb-3">{{ page.featured_author.role }}</p>{% endif %}
|
||||
{% if page.featured_author.bio %}<p class="text-sm text-zinc-600 dark:text-zinc-400">{{ page.featured_author.bio }}</p>{% endif %}
|
||||
</div>
|
||||
</aside>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -10,26 +10,39 @@
|
||||
<meta property="og:url" content="{{ canonical }}" />
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
<section>
|
||||
<h2>Filter by tag</h2>
|
||||
<a href="/articles/" {% if not active_tag %}aria-current="page"{% endif %}>All</a>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="py-8 md:py-12 border-b border-zinc-200 dark:border-zinc-800 mb-12">
|
||||
<h1 class="font-display font-black text-4xl md:text-6xl mb-6">{{ page.title }}</h1>
|
||||
|
||||
<!-- Tag Filters -->
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="/articles/" class="px-4 py-2 font-mono text-sm font-bold border transition-colors {% if not active_tag %}bg-brand-dark text-brand-light dark:bg-brand-light dark:text-brand-dark border-transparent{% else %}bg-transparent text-zinc-600 dark:text-zinc-400 border-zinc-300 dark:border-zinc-700 hover:text-brand-dark dark:hover:text-brand-light hover:border-brand-dark dark:hover:border-brand-light{% endif %}" {% if not active_tag %}aria-current="page"{% endif %}>All</a>
|
||||
{% for tag in available_tags %}
|
||||
<a href="/articles/?tag={{ tag.slug }}" {% if active_tag == tag.slug %}aria-current="page"{% endif %}>{{ tag.name }}</a>
|
||||
<a href="/articles/?tag={{ tag.slug }}" class="px-4 py-2 font-mono text-sm font-bold border transition-colors {% if active_tag == tag.slug %}bg-brand-dark text-brand-light dark:bg-brand-light dark:text-brand-dark border-transparent{% else %}bg-transparent text-zinc-600 dark:text-zinc-400 border-zinc-300 dark:border-zinc-700 hover:text-brand-dark dark:hover:text-brand-light hover:border-brand-dark dark:hover:border-brand-light{% endif %}" {% if active_tag == tag.slug %}aria-current="page"{% endif %}>{{ tag.name }}</a>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Article List -->
|
||||
<div class="space-y-8">
|
||||
{% for article in articles %}
|
||||
{% include 'components/article_card.html' with article=article %}
|
||||
{% empty %}
|
||||
<p>No articles found.</p>
|
||||
<p class="font-mono text-zinc-500 py-12 text-center">No articles found.</p>
|
||||
{% endfor %}
|
||||
<nav aria-label="Pagination">
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if articles.has_previous or articles.has_next %}
|
||||
<nav aria-label="Pagination" class="mt-12 flex justify-center items-center gap-4 font-mono text-sm">
|
||||
{% if articles.has_previous %}
|
||||
<a href="?page={{ articles.previous_page_number }}{% if active_tag %}&tag={{ active_tag }}{% endif %}">Previous</a>
|
||||
<a href="?page={{ articles.previous_page_number }}{% if active_tag %}&tag={{ active_tag }}{% endif %}" class="px-6 py-3 border border-zinc-300 dark:border-zinc-700 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors">← Previous</a>
|
||||
{% endif %}
|
||||
<span>Page {{ articles.number }} of {{ paginator.num_pages }}</span>
|
||||
<span class="text-zinc-500">Page {{ articles.number }} of {{ paginator.num_pages }}</span>
|
||||
{% if articles.has_next %}
|
||||
<a href="?page={{ articles.next_page_number }}{% if active_tag %}&tag={{ active_tag }}{% endif %}">Next</a>
|
||||
<a href="?page={{ articles.next_page_number }}{% if active_tag %}&tag={{ active_tag }}{% endif %}" class="px-6 py-3 border border-zinc-300 dark:border-zinc-700 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors">Next →</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load wagtailcore_tags wagtailimages_tags seo_tags %}
|
||||
{% load wagtailcore_tags wagtailimages_tags seo_tags core_tags %}
|
||||
{% block title %}{{ page.title }} | No Hype AI{% endblock %}
|
||||
{% block head_meta %}
|
||||
{% canonical_url page as canonical %}
|
||||
@@ -17,75 +17,210 @@
|
||||
{% if og_image %}<meta name="twitter:image" content="{{ og_image }}" />{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<article>
|
||||
<h1>{{ page.title }}</h1>
|
||||
<p>{{ page.read_time_mins }} min read</p>
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<div class="mb-8 font-mono text-sm text-zinc-500">
|
||||
<a href="/" class="hover:text-brand-cyan transition-colors">Home</a> /
|
||||
<a href="/articles/" class="hover:text-brand-cyan transition-colors">Articles</a> /
|
||||
<span class="text-brand-dark dark:text-brand-light">{{ page.title|truncatechars:40 }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Article Header -->
|
||||
<header class="mb-12 border-b border-zinc-200 dark:border-zinc-800 pb-12">
|
||||
<div class="flex gap-3 mb-6 items-center flex-wrap">
|
||||
{% for tag in page.tags.all %}
|
||||
<span class="text-xs font-mono font-bold px-2 py-1 {{ tag|get_tag_css }} border border-current/20">{{ tag.name }}</span>
|
||||
{% endfor %}
|
||||
<span class="text-sm font-mono text-zinc-500">{{ page.first_published_at|date:"M j, Y" }}</span>
|
||||
<span class="text-sm font-mono text-zinc-500">{{ page.read_time_mins }} min read</span>
|
||||
</div>
|
||||
<h1 class="font-display font-black text-4xl md:text-6xl lg:text-7xl leading-tight mb-8">{{ page.title }}</h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-12 h-12 bg-gradient-to-tr from-brand-cyan to-brand-pink shrink-0"></div>
|
||||
<div>
|
||||
<div class="font-bold font-display text-lg">{{ page.author.name }}</div>
|
||||
{% if page.author.role %}<div class="font-mono text-xs text-zinc-500">{{ page.author.role }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if page.hero_image %}
|
||||
{% image page.hero_image fill-1200x630 %}
|
||||
<div class="mb-12 border border-zinc-200 dark:border-zinc-800 overflow-hidden">
|
||||
{% image page.hero_image width-1200 class="w-full h-auto" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Main Content Layout -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12">
|
||||
|
||||
<!-- Article Body -->
|
||||
<article class="lg:col-span-8 prose prose-lg dark:prose-invert max-w-none
|
||||
prose-headings:font-display prose-headings:font-bold
|
||||
prose-a:text-brand-cyan hover:prose-a:text-brand-pink prose-a:transition-colors prose-a:no-underline hover:prose-a:underline
|
||||
prose-img:border prose-img:border-zinc-200 dark:prose-img:border-zinc-800
|
||||
prose-blockquote:border-l-brand-pink prose-blockquote:bg-brand-pink/5 prose-blockquote:py-2 prose-blockquote:not-italic
|
||||
prose-code:font-mono prose-code:text-brand-cyan prose-code:bg-zinc-100 dark:prose-code:bg-zinc-900 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:before:content-none prose-code:after:content-none">
|
||||
{{ page.body }}
|
||||
{% article_json_ld page %}
|
||||
</article>
|
||||
<section aria-label="Share this article">
|
||||
<h2>Share</h2>
|
||||
<a href="https://x.com/intent/post?url={{ request.build_absolute_uri|urlencode }}&text={{ page.title|urlencode }}" target="_blank" rel="noopener noreferrer">Share on X</a>
|
||||
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ request.build_absolute_uri|urlencode }}" target="_blank" rel="noopener noreferrer">Share on LinkedIn</a>
|
||||
<button type="button" data-copy-link data-copy-url="{{ request.build_absolute_uri }}">Copy link</button>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="lg:col-span-4 space-y-8">
|
||||
<div class="sticky top-28">
|
||||
|
||||
<!-- Share -->
|
||||
<section aria-label="Share this article" class="mb-8">
|
||||
<h3 class="font-display font-bold text-lg mb-4 uppercase tracking-widest text-zinc-500 text-sm">Share Article</h3>
|
||||
<div class="flex gap-2">
|
||||
<a href="https://x.com/intent/post?url={{ request.build_absolute_uri|urlencode }}&text={{ page.title|urlencode }}" target="_blank" rel="noopener noreferrer"
|
||||
class="w-10 h-10 flex items-center justify-center bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 hover:border-brand-cyan transition-colors hover:text-brand-cyan" aria-label="Share on X">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.748l7.73-8.835L1.254 2.25H8.08l4.259 5.63L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77z"/></svg>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ request.build_absolute_uri|urlencode }}" target="_blank" rel="noopener noreferrer"
|
||||
class="w-10 h-10 flex items-center justify-center bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 hover:border-blue-600 transition-colors hover:text-blue-600" aria-label="Share on LinkedIn">
|
||||
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
|
||||
</a>
|
||||
<button type="button" data-copy-link data-copy-url="{{ request.build_absolute_uri }}"
|
||||
class="w-10 h-10 flex items-center justify-center bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 hover:border-brand-pink transition-colors hover:text-brand-pink" aria-label="Copy link">
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Related</h2>
|
||||
{% for article in related_articles %}
|
||||
<a href="{{ article.url }}">{{ article.title }}</a>
|
||||
{% endfor %}
|
||||
</section>
|
||||
<aside>
|
||||
<h2>Newsletter</h2>
|
||||
{% include 'components/newsletter_form.html' with source='article' label='Never miss a post' %}
|
||||
|
||||
<!-- Newsletter -->
|
||||
<div class="bg-brand-dark text-brand-light dark:bg-brand-light dark:text-brand-dark p-6 border border-transparent dark:border-zinc-700 shadow-solid-dark dark:shadow-solid-light">
|
||||
<h3 class="font-display font-bold text-xl mb-2">Subscribe for Updates</h3>
|
||||
<p class="text-sm opacity-80 mb-4">Get our latest articles and coding benchmarks delivered to your inbox every week.</p>
|
||||
{% include 'components/newsletter_form.html' with source='article' label='Subscribe' %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</aside>
|
||||
{% if page.comments_enabled %}
|
||||
<section>
|
||||
<h2>Comments</h2>
|
||||
{% for comment in approved_comments %}
|
||||
<article id="comment-{{ comment.id }}">
|
||||
<p><strong>{{ comment.author_name }}</strong></p>
|
||||
<p>{{ comment.body }}</p>
|
||||
{% for reply in comment.replies.all %}
|
||||
<article id="comment-{{ reply.id }}">
|
||||
<p><strong>{{ reply.author_name }}</strong></p>
|
||||
<p>{{ reply.body }}</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Related Articles -->
|
||||
{% if related_articles %}
|
||||
<section class="mt-16 md:mt-24 pt-12 border-t border-zinc-200 dark:border-zinc-800">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<h3 class="font-display font-bold text-3xl">Related Articles</h3>
|
||||
<a href="/articles/" class="font-mono text-sm text-brand-cyan hover:underline">View All</a>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{% for article in related_articles %}
|
||||
<article class="group flex flex-col h-full bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 hover:-translate-y-2 hover:shadow-solid-dark dark:hover:shadow-solid-light transition-all duration-300">
|
||||
<a href="{{ article.url }}" class="h-48 overflow-hidden relative bg-zinc-900 flex items-center justify-center border-b border-zinc-200 dark:border-zinc-800 shrink-0 block">
|
||||
{% if article.hero_image %}
|
||||
{% image article.hero_image fill-400x300 class="w-full h-full object-cover grayscale group-hover:grayscale-0 transition-all duration-500" %}
|
||||
{% else %}
|
||||
<svg class="w-16 h-16 text-brand-cyan opacity-40 group-hover:opacity-80 transition-all duration-500 group-hover:scale-110" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 1-6.23-.693L5 14.5m14.8.8 1.402 1.402c1 1 .03 2.798-1.442 2.798H4.24c-1.47 0-2.44-1.798-1.442-2.798L4.2 15.3" /></svg>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="p-6 flex flex-col flex-grow">
|
||||
<div class="flex gap-2 mb-3 flex-wrap">
|
||||
{% for tag in article.tags.all %}
|
||||
<span class="text-xs font-mono font-bold px-2 py-1 {{ tag|get_tag_css }}">{{ tag.name }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a href="{{ article.url }}">
|
||||
<h4 class="font-display font-bold text-xl mb-2 group-hover:text-brand-cyan transition-colors">{{ article.title }}</h4>
|
||||
</a>
|
||||
<p class="text-zinc-600 dark:text-zinc-400 text-sm mb-6 line-clamp-2">{{ article.summary }}</p>
|
||||
<div class="mt-auto pt-4 border-t border-zinc-100 dark:border-zinc-800 flex items-center gap-2 text-sm font-bold font-mono group-hover:text-brand-cyan transition-colors">
|
||||
Read Article
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
<form method="post" action="{% url 'comment_post' %}">
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<!-- Comments -->
|
||||
{% if page.comments_enabled %}
|
||||
<section class="mt-16 pt-12 border-t border-zinc-200 dark:border-zinc-800">
|
||||
<h2 class="font-display font-bold text-3xl mb-8">Comments</h2>
|
||||
|
||||
{% if approved_comments %}
|
||||
<div class="space-y-8 mb-12">
|
||||
{% for comment in approved_comments %}
|
||||
<article id="comment-{{ comment.id }}" class="bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 p-6">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="w-8 h-8 bg-gradient-to-tr from-brand-cyan to-brand-pink shrink-0"></div>
|
||||
<div>
|
||||
<div class="font-display font-bold text-sm">{{ comment.author_name }}</div>
|
||||
<div class="font-mono text-xs text-zinc-500">{{ comment.created_at|date:"M j, Y" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-zinc-700 dark:text-zinc-300 text-sm leading-relaxed">{{ comment.body }}</p>
|
||||
{% for reply in comment.replies.all %}
|
||||
<article id="comment-{{ reply.id }}" class="mt-6 ml-8 bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 p-4">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<div class="w-6 h-6 bg-gradient-to-tr from-brand-pink to-brand-cyan shrink-0"></div>
|
||||
<div>
|
||||
<div class="font-display font-bold text-sm">{{ reply.author_name }}</div>
|
||||
<div class="font-mono text-xs text-zinc-500">{{ reply.created_at|date:"M j, Y" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-zinc-700 dark:text-zinc-300 text-sm leading-relaxed">{{ reply.body }}</p>
|
||||
</article>
|
||||
{% endfor %}
|
||||
<form method="post" action="{% url 'comment_post' %}" class="mt-4 pt-4 border-t border-zinc-100 dark:border-zinc-800">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="article_id" value="{{ page.id }}" />
|
||||
<input type="hidden" name="parent_id" value="{{ comment.id }}" />
|
||||
<input type="text" name="author_name" required />
|
||||
<input type="email" name="author_email" required />
|
||||
<textarea name="body" required></textarea>
|
||||
<div class="flex gap-3 mb-3">
|
||||
<input type="text" name="author_name" required placeholder="Your name"
|
||||
class="flex-1 bg-transparent border border-zinc-300 dark:border-zinc-700 px-3 py-2 font-mono text-sm focus:outline-none focus:border-brand-pink transition-colors" />
|
||||
<input type="email" name="author_email" required placeholder="your@email.com"
|
||||
class="flex-1 bg-transparent border border-zinc-300 dark:border-zinc-700 px-3 py-2 font-mono text-sm focus:outline-none focus:border-brand-pink transition-colors" />
|
||||
</div>
|
||||
<textarea name="body" required placeholder="Write a reply..." rows="2"
|
||||
class="w-full bg-transparent border border-zinc-300 dark:border-zinc-700 px-3 py-2 font-mono text-sm focus:outline-none focus:border-brand-pink transition-colors mb-3 resize-none"></textarea>
|
||||
<input type="text" name="honeypot" style="display:none" />
|
||||
<button type="submit">Reply</button>
|
||||
<button type="submit" class="px-4 py-2 bg-zinc-200 dark:bg-zinc-800 font-display font-bold text-sm hover:bg-brand-pink hover:text-white transition-colors">Reply</button>
|
||||
</form>
|
||||
</article>
|
||||
{% empty %}
|
||||
<p>No comments yet.</p>
|
||||
{% endfor %}
|
||||
{% if comment_form and comment_form.errors %}
|
||||
<div aria-label="Comment form errors">
|
||||
{{ comment_form.non_field_errors }}
|
||||
{% for field in comment_form %}
|
||||
{{ field.errors }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="font-mono text-sm text-zinc-500 mb-12">No comments yet. Be the first to comment.</p>
|
||||
{% endif %}
|
||||
<form method="post" action="{% url 'comment_post' %}">
|
||||
|
||||
{% if comment_form and comment_form.errors %}
|
||||
<div aria-label="Comment form errors" class="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 font-mono text-sm text-red-600 dark:text-red-400">
|
||||
{{ comment_form.non_field_errors }}
|
||||
{% for field in comment_form %}{{ field.errors }}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 p-6">
|
||||
<h3 class="font-display font-bold text-xl mb-6">Post a Comment</h3>
|
||||
<form method="post" action="{% url 'comment_post' %}" class="space-y-4">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="article_id" value="{{ page.id }}" />
|
||||
<input type="text" name="author_name" value="{% if comment_form %}{{ comment_form.author_name.value|default:'' }}{% endif %}" required />
|
||||
<input type="email" name="author_email" value="{% if comment_form %}{{ comment_form.author_email.value|default:'' }}{% endif %}" required />
|
||||
<textarea name="body" required>{% if comment_form %}{{ comment_form.body.value|default:'' }}{% endif %}</textarea>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block font-mono text-xs text-zinc-500 mb-1 uppercase tracking-wider">Name *</label>
|
||||
<input type="text" name="author_name" value="{% if comment_form %}{{ comment_form.author_name.value|default:'' }}{% endif %}" required
|
||||
class="w-full bg-transparent border border-zinc-300 dark:border-zinc-700 px-4 py-2 font-mono text-sm focus:outline-none focus:border-brand-pink transition-colors" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block font-mono text-xs text-zinc-500 mb-1 uppercase tracking-wider">Email *</label>
|
||||
<input type="email" name="author_email" value="{% if comment_form %}{{ comment_form.author_email.value|default:'' }}{% endif %}" required
|
||||
class="w-full bg-transparent border border-zinc-300 dark:border-zinc-700 px-4 py-2 font-mono text-sm focus:outline-none focus:border-brand-pink transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block font-mono text-xs text-zinc-500 mb-1 uppercase tracking-wider">Comment *</label>
|
||||
<textarea name="body" required rows="5"
|
||||
class="w-full bg-transparent border border-zinc-300 dark:border-zinc-700 px-4 py-2 font-mono text-sm focus:outline-none focus:border-brand-pink transition-colors resize-none">{% if comment_form %}{{ comment_form.body.value|default:'' }}{% endif %}</textarea>
|
||||
</div>
|
||||
<input type="text" name="honeypot" style="display:none" />
|
||||
<button type="submit">Post comment</button>
|
||||
<button type="submit" class="px-6 py-3 bg-brand-dark text-brand-light dark:bg-brand-light dark:text-brand-dark font-display font-bold hover:-translate-y-1 hover:shadow-solid-dark dark:hover:shadow-solid-light transition-all">Post comment</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
<div class="callout icon-{{ value.icon }}">
|
||||
<h3>{{ value.heading }}</h3>
|
||||
{{ value.body }}
|
||||
<div class="bg-zinc-100 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 p-6 my-8 flex items-start gap-4
|
||||
{% if value.icon == 'warning' %}border-l-4 border-l-yellow-400{% elif value.icon == 'trophy' %}border-l-4 border-l-brand-pink{% elif value.icon == 'tip' %}border-l-4 border-l-green-500{% else %}border-l-4 border-l-brand-cyan{% endif %}">
|
||||
<div class="shrink-0 mt-0.5">
|
||||
{% if value.icon == 'warning' %}
|
||||
<svg class="w-6 h-6 text-yellow-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" /></svg>
|
||||
{% elif value.icon == 'trophy' %}
|
||||
<svg class="w-6 h-6 text-brand-pink" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 18.75h-9m9 0a3 3 0 0 1 3 3h-15a3 3 0 0 1 3-3m9 0v-3.375c0-.621-.503-1.125-1.125-1.125h-.871M7.5 18.75v-3.375c0-.621.504-1.125 1.125-1.125h.872m5.007 0H9.497m5.007 0a7.454 7.454 0 0 1-.982-3.172M9.497 14.25a7.454 7.454 0 0 0 .981-3.172M5.25 4.236c-.982.143-1.954.317-2.916.52A6.003 6.003 0 0 0 7.73 9.728M5.25 4.236V4.5c0 2.108.966 3.99 2.48 5.228M5.25 4.236V2.721C7.456 2.41 9.71 2.25 12 2.25c2.291 0 4.545.16 6.75.47v1.516M7.73 9.728a6.726 6.726 0 0 0 2.748 1.35m8.272-6.842V4.5c0 2.108-.966 3.99-2.48 5.228m2.48-5.492a46.32 46.32 0 0 1 2.916.52 6.003 6.003 0 0 1-5.395 4.972m0 0a6.726 6.726 0 0 1-2.749 1.35m0 0a6.772 6.772 0 0 1-3.044 0" /></svg>
|
||||
{% elif value.icon == 'tip' %}
|
||||
<svg class="w-6 h-6 text-green-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" /></svg>
|
||||
{% else %}
|
||||
<svg class="w-6 h-6 text-brand-cyan" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" /></svg>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
{% if value.heading %}
|
||||
<h4 class="font-display font-bold text-lg mb-2">{{ value.heading }}</h4>
|
||||
{% endif %}
|
||||
<div class="text-zinc-700 dark:text-zinc-300 text-sm leading-relaxed">{{ value.body }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
{% load wagtailcore_tags %}
|
||||
<div class="code-block">
|
||||
{% if value.filename %}<div>{{ value.filename }}</div>{% endif %}
|
||||
<pre data-lang="{{ value.language }}"><code class="language-{{ value.language }}">{{ value.raw_code }}</code></pre>
|
||||
<div class="my-8 overflow-hidden bg-[#0d1117] border border-zinc-800 shadow-xl">
|
||||
<div class="flex items-center justify-between px-4 py-2 bg-[#161b22] border-b border-zinc-800">
|
||||
<div class="flex gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
{% if value.filename %}
|
||||
<div class="font-mono text-xs text-zinc-500">{{ value.filename }}</div>
|
||||
{% else %}
|
||||
<div class="font-mono text-xs text-zinc-500">{{ value.language }}</div>
|
||||
{% endif %}
|
||||
<div class="w-8"></div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<pre data-lang="{{ value.language }}" class="p-6 text-sm"><code class="language-{{ value.language }} font-mono text-zinc-300">{{ value.raw_code }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load seo_tags %}
|
||||
{% load wagtailimages_tags seo_tags core_tags %}
|
||||
{% block title %}No Hype AI{% endblock %}
|
||||
{% block head_meta %}
|
||||
{% canonical_url page as canonical %}
|
||||
@@ -11,21 +11,146 @@
|
||||
<meta property="og:url" content="{{ canonical }}" />
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section>
|
||||
|
||||
<!-- Featured Article -->
|
||||
{% if featured_article %}
|
||||
<h2>{{ featured_article.title }}</h2>
|
||||
<p>{{ featured_article.author.name }}</p>
|
||||
<p>{{ featured_article.read_time_mins }} min read</p>
|
||||
<section class="mb-12 md:mb-16">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<span class="w-2 h-2 rounded-full bg-brand-pink animate-pulse"></span>
|
||||
<span class="font-mono text-sm font-bold uppercase tracking-widest text-zinc-500">Featured Article</span>
|
||||
</div>
|
||||
<article class="group cursor-pointer grid grid-cols-1 lg:grid-cols-2 gap-8 items-center bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 p-4 md:p-6 hover:border-brand-cyan dark:hover:border-brand-cyan hover:shadow-solid-dark dark:hover:shadow-solid-light transition-all duration-300">
|
||||
<a href="{{ featured_article.url }}" class="w-full h-64 md:h-[400px] overflow-hidden relative bg-zinc-100 dark:bg-zinc-900 order-2 lg:order-1 border border-zinc-200 dark:border-zinc-800 block">
|
||||
{% if featured_article.hero_image %}
|
||||
{% image featured_article.hero_image fill-800x600 class="w-full h-full object-cover grayscale group-hover:grayscale-0 transition-all duration-700 group-hover:scale-105" %}
|
||||
{% else %}
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<svg class="w-20 h-20 text-brand-cyan opacity-30" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 1-6.23-.693L5 14.5m14.8.8 1.402 1.402c1 1 .03 2.798-1.442 2.798H4.24c-1.47 0-2.44-1.798-1.442-2.798L4.2 15.3" /></svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
<section>
|
||||
{% for article in latest_articles %}
|
||||
{% include 'components/article_card.html' with article=article %}
|
||||
</a>
|
||||
<div class="flex flex-col py-2 order-1 lg:order-2">
|
||||
<div class="flex gap-3 mb-4 items-center flex-wrap">
|
||||
{% for tag in featured_article.tags.all %}
|
||||
<span class="text-xs font-mono font-bold px-2 py-1 {{ tag|get_tag_css }}">{{ tag.name }}</span>
|
||||
{% endfor %}
|
||||
<span class="text-sm font-mono text-zinc-500">{{ featured_article.read_time_mins }} min read</span>
|
||||
</div>
|
||||
<a href="{{ featured_article.url }}">
|
||||
<h2 class="font-display font-black text-3xl md:text-5xl mb-4 group-hover:text-brand-cyan transition-colors leading-[1.1]">{{ featured_article.title }}</h2>
|
||||
</a>
|
||||
<p class="text-zinc-600 dark:text-zinc-400 mb-8 text-lg md:text-xl line-clamp-3">{{ featured_article.summary }}</p>
|
||||
<div class="mt-auto flex items-center justify-between pt-4 border-t border-zinc-200 dark:border-zinc-800">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-gradient-to-tr from-brand-cyan to-brand-pink"></div>
|
||||
<div>
|
||||
<div class="text-sm font-bold font-display">{{ featured_article.author.name }}</div>
|
||||
<div class="text-xs font-mono text-zinc-500">{{ featured_article.first_published_at|date:"M j, Y" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ featured_article.url }}" class="text-sm font-bold font-mono group-hover:text-brand-cyan transition-colors flex items-center gap-1">
|
||||
Read
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<section>
|
||||
{% endif %}
|
||||
|
||||
<!-- 2-Column Editorial Layout -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12">
|
||||
|
||||
<!-- Main Feed -->
|
||||
<div class="lg:col-span-8">
|
||||
<div class="flex items-center justify-between mb-8 pb-4 border-b border-zinc-200 dark:border-zinc-800">
|
||||
<h3 class="font-display font-bold text-3xl">Latest Articles</h3>
|
||||
<a href="/articles/" class="font-mono text-sm text-brand-cyan hover:underline flex items-center gap-1">View All</a>
|
||||
</div>
|
||||
<div class="space-y-8">
|
||||
{% for article in latest_articles %}
|
||||
<article class="group flex flex-col md:flex-row gap-6 items-start pb-8 border-b border-zinc-200 dark:border-zinc-800 last:border-0">
|
||||
<a href="{{ article.url }}" class="w-full md:w-48 h-48 md:h-32 shrink-0 overflow-hidden relative bg-zinc-100 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 flex items-center justify-center block">
|
||||
{% if article.hero_image %}
|
||||
{% image article.hero_image fill-200x130 class="w-full h-full object-cover grayscale group-hover:grayscale-0 transition-all duration-500" %}
|
||||
{% else %}
|
||||
<svg class="w-12 h-12 text-brand-cyan opacity-40 group-hover:opacity-80 transition-all duration-500 group-hover:scale-110" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 1-6.23-.693L5 14.5m14.8.8 1.402 1.402c1 1 .03 2.798-1.442 2.798H4.24c-1.47 0-2.44-1.798-1.442-2.798L4.2 15.3" /></svg>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex gap-3 mb-2 items-center flex-wrap">
|
||||
{% for tag in article.tags.all %}
|
||||
<span class="text-xs font-mono font-bold px-2 py-1 {{ tag|get_tag_css }}">{{ tag.name }}</span>
|
||||
{% endfor %}
|
||||
<span class="text-sm font-mono text-zinc-500">{{ article.first_published_at|date:"M j" }}</span>
|
||||
</div>
|
||||
<a href="{{ article.url }}">
|
||||
<h4 class="font-display font-bold text-2xl mb-2 group-hover:text-brand-cyan transition-colors">{{ article.title }}</h4>
|
||||
</a>
|
||||
<p class="text-zinc-600 dark:text-zinc-400 text-sm line-clamp-2 mb-3">{{ article.summary }}</p>
|
||||
<a href="{{ article.url }}" class="text-xs font-mono font-bold group-hover:text-brand-cyan transition-colors mt-auto">Read article →</a>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if more_articles %}
|
||||
<div class="mt-10">
|
||||
<div class="flex items-center justify-between mb-6 pb-4 border-b border-zinc-200 dark:border-zinc-800">
|
||||
<h3 class="font-display font-bold text-2xl">More Articles</h3>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
{% for article in more_articles %}
|
||||
{% include 'components/article_card.html' with article=article %}
|
||||
{% endfor %}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="lg:col-span-4 space-y-8">
|
||||
|
||||
<!-- Newsletter Widget -->
|
||||
<div class="bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 p-6 relative overflow-hidden">
|
||||
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-brand-cyan to-brand-pink"></div>
|
||||
<h4 class="font-display font-bold text-xl mb-2">Weekly Newsletter</h4>
|
||||
<p class="text-zinc-600 dark:text-zinc-400 text-sm mb-4">Get our latest articles and coding benchmarks delivered to your inbox every week.</p>
|
||||
{% include 'components/newsletter_form.html' with source='sidebar' label='Subscribe' %}
|
||||
</div>
|
||||
|
||||
<!-- Popular Articles -->
|
||||
{% if latest_articles %}
|
||||
<div>
|
||||
<h4 class="font-display font-bold mb-4 uppercase tracking-widest text-zinc-500 text-sm">Popular Articles</h4>
|
||||
<ul class="space-y-4">
|
||||
{% for article in latest_articles %}
|
||||
<li class="group">
|
||||
<a href="{{ article.url }}" class="flex gap-4 items-start">
|
||||
<span class="font-display font-black text-2xl text-zinc-300 dark:text-zinc-800 group-hover:text-brand-{% cycle 'cyan' 'pink' 'cyan' 'pink' 'cyan' %} transition-colors">0{{ forloop.counter }}</span>
|
||||
<div>
|
||||
<h5 class="font-display font-bold text-sm leading-tight group-hover:text-brand-cyan transition-colors">{{ article.title }}</h5>
|
||||
<div class="text-xs font-mono text-zinc-500 mt-1">{{ article.read_time_mins }} min read</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if available_tags %}
|
||||
<div>
|
||||
<h4 class="font-display font-bold mb-4 uppercase tracking-widest text-zinc-500 text-sm">Explore Topics</h4>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for tag in available_tags %}
|
||||
<a href="/articles/?tag={{ tag.slug }}" class="px-3 py-1.5 border border-zinc-200 dark:border-zinc-800 text-sm font-mono hover:border-brand-cyan hover:text-brand-cyan transition-colors">#{{ tag.name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,8 +1,31 @@
|
||||
{% load core_tags %}
|
||||
<article>
|
||||
<a href="{{ article.url }}">{{ article.title }}</a>
|
||||
<p>{{ article.summary|truncatewords:20 }}</p>
|
||||
{% load core_tags wagtailimages_tags %}
|
||||
<article class="group flex flex-col md:flex-row gap-8 items-center bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 p-4 hover:border-brand-cyan dark:hover:border-brand-cyan transition-colors">
|
||||
<a href="{{ article.url }}" class="w-full md:w-1/3 h-48 md:h-full min-h-[200px] overflow-hidden relative bg-zinc-100 dark:bg-zinc-900 shrink-0 block">
|
||||
{% if article.hero_image %}
|
||||
{% image article.hero_image fill-600x400 class="w-full h-full object-cover grayscale group-hover:grayscale-0 transition-all duration-500" %}
|
||||
{% else %}
|
||||
<div class="w-full h-full min-h-[200px] flex items-center justify-center bg-zinc-900">
|
||||
<svg class="w-16 h-16 text-brand-cyan opacity-40 group-hover:opacity-80 transition-opacity" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 0 1-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 0 1 4.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0 1 12 15a9.065 9.065 0 0 1-6.23-.693L5 14.5m14.8.8 1.402 1.402c1 1 .03 2.798-1.442 2.798H4.24c-1.47 0-2.44-1.798-1.442-2.798L4.2 15.3" /></svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="flex flex-col py-2 w-full">
|
||||
<div class="flex gap-3 mb-4 items-center flex-wrap">
|
||||
{% for tag in article.tags.all %}
|
||||
<span class="{{ tag|get_tag_css }}">{{ tag.name }}</span>
|
||||
<span class="text-xs font-mono font-bold px-2 py-1 {{ tag|get_tag_css }}">{{ tag.name }}</span>
|
||||
{% endfor %}
|
||||
<span class="text-sm font-mono text-zinc-500">{{ article.first_published_at|date:"M j, Y" }}</span>
|
||||
</div>
|
||||
<a href="{{ article.url }}">
|
||||
<h2 class="font-display font-bold text-2xl md:text-3xl mb-3 group-hover:text-brand-cyan transition-colors">{{ article.title }}</h2>
|
||||
</a>
|
||||
<p class="text-zinc-600 dark:text-zinc-400 mb-6 max-w-2xl line-clamp-2">{{ article.summary }}</p>
|
||||
<div class="flex items-center justify-between mt-auto">
|
||||
<span class="text-sm font-mono text-zinc-500">{{ article.read_time_mins }} min read</span>
|
||||
<a href="{{ article.url }}" class="flex items-center gap-2 text-sm font-bold font-mono group-hover:text-brand-cyan transition-colors">
|
||||
Read Article
|
||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 19.5 15-15m0 0H8.25m11.25 0v11.25" /></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -1,27 +1,43 @@
|
||||
{% if request.consent.requires_prompt %}
|
||||
<div id="cookie-banner">
|
||||
<form method="post" action="{% url 'consent' %}">
|
||||
<div id="cookie-banner" class="fixed bottom-0 left-0 right-0 z-50 bg-brand-surfaceLight dark:bg-brand-surfaceDark border-t border-zinc-200 dark:border-zinc-800 shadow-lg">
|
||||
<div class="max-w-7xl mx-auto px-6 py-4 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
|
||||
<div class="flex-1">
|
||||
<p class="font-mono text-sm text-zinc-600 dark:text-zinc-400">
|
||||
We use cookies to improve your experience.
|
||||
{% if site_settings and site_settings.privacy_policy_page %}
|
||||
<a href="{{ site_settings.privacy_policy_page.url }}" class="text-brand-cyan hover:underline">Privacy Policy</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 shrink-0">
|
||||
<form method="post" action="{% url 'consent' %}" class="inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" name="accept_all" value="1">Accept all</button>
|
||||
<button type="submit" name="reject_all" value="1">Reject all</button>
|
||||
<button type="submit" name="reject_all" value="1"
|
||||
class="px-4 py-2 border border-zinc-300 dark:border-zinc-700 font-mono text-sm hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors">Reject all</button>
|
||||
</form>
|
||||
<details>
|
||||
<summary>Manage preferences</summary>
|
||||
<form method="post" action="{% url 'consent' %}">
|
||||
<form method="post" action="{% url 'consent' %}" class="inline">
|
||||
{% csrf_token %}
|
||||
<label>
|
||||
<input type="checkbox" name="analytics" value="1" />
|
||||
<button type="submit" name="accept_all" value="1"
|
||||
class="px-4 py-2 bg-brand-dark text-brand-light dark:bg-brand-light dark:text-brand-dark font-display font-bold text-sm hover:bg-brand-cyan transition-colors">Accept all</button>
|
||||
</form>
|
||||
<details class="relative">
|
||||
<summary class="px-4 py-2 border border-zinc-300 dark:border-zinc-700 font-mono text-sm hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors cursor-pointer list-none">Manage</summary>
|
||||
<div class="absolute bottom-full right-0 mb-2 w-72 bg-brand-surfaceLight dark:bg-brand-surfaceDark border border-zinc-200 dark:border-zinc-800 p-4 shadow-lg">
|
||||
<form method="post" action="{% url 'consent' %}" class="space-y-3">
|
||||
{% csrf_token %}
|
||||
<label class="flex items-center gap-3 font-mono text-sm cursor-pointer">
|
||||
<input type="checkbox" name="analytics" value="1" class="accent-brand-cyan" />
|
||||
Analytics cookies
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" name="advertising" value="1" />
|
||||
<label class="flex items-center gap-3 font-mono text-sm cursor-pointer">
|
||||
<input type="checkbox" name="advertising" value="1" class="accent-brand-pink" />
|
||||
Advertising cookies
|
||||
</label>
|
||||
<button type="submit">Save preferences</button>
|
||||
<button type="submit" class="w-full px-4 py-2 bg-brand-dark text-brand-light dark:bg-brand-light dark:text-brand-dark font-display font-bold text-sm hover:bg-brand-cyan transition-colors">Save preferences</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
{% if site_settings and site_settings.privacy_policy_page %}
|
||||
<a href="{{ site_settings.privacy_policy_page.url }}">Privacy Policy</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
{% load core_tags %}
|
||||
<footer>
|
||||
<footer class="border-t border-zinc-200 dark:border-zinc-800 bg-brand-light dark:bg-brand-dark mt-12 py-12 text-center md:text-left">
|
||||
<div class="max-w-7xl mx-auto px-6 grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
<div class="md:col-span-2">
|
||||
<a href="/" class="font-display font-bold text-2xl tracking-tight mb-4 inline-block">NO HYPE AI</a>
|
||||
<p class="text-zinc-500 font-mono text-sm max-w-sm mx-auto md:mx-0">
|
||||
In-depth reviews and benchmarks of the latest AI coding tools.<br>
|
||||
Honest analysis for developers.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-display font-bold mb-4 uppercase text-sm tracking-widest text-zinc-400">Navigation</h4>
|
||||
<ul class="space-y-2 font-mono text-sm text-zinc-500">
|
||||
<li><a href="/" class="hover:text-brand-cyan transition-colors">Home</a></li>
|
||||
<li><a href="/articles/" class="hover:text-brand-cyan transition-colors">Articles</a></li>
|
||||
<li><a href="/about/" class="hover:text-brand-pink transition-colors">About</a></li>
|
||||
{% get_legal_pages as legal_pages %}
|
||||
{% include 'components/newsletter_form.html' with source='footer' label='Newsletter' %}
|
||||
{% for page in legal_pages %}
|
||||
<a href="{{ page.url }}">{{ page.title }}</a>
|
||||
<li><a href="{{ page.url }}" class="hover:text-brand-pink transition-colors">{{ page.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-display font-bold mb-4 uppercase text-sm tracking-widest text-zinc-400">Newsletter</h4>
|
||||
<p class="text-zinc-500 font-mono text-sm mb-4">Get weekly AI tool reviews.</p>
|
||||
{% include 'components/newsletter_form.html' with source='footer' label='Newsletter' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-w-7xl mx-auto px-6 mt-12 pt-8 border-t border-zinc-200 dark:border-zinc-800 text-center font-mono text-xs text-zinc-500 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<p>© {% now "Y" %} No Hype AI. All rights reserved.</p>
|
||||
<p>Honest AI tool reviews for developers.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,7 +1,71 @@
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/articles/">Articles</a>
|
||||
<a href="/about/">About</a>
|
||||
<button type="button" data-theme-toggle>Toggle theme</button>
|
||||
{% include 'components/newsletter_form.html' with source='nav' label='Get updates' %}
|
||||
{% load static %}
|
||||
<nav class="sticky top-0 z-50 backdrop-blur-md bg-brand-light/80 dark:bg-brand-dark/80 border-b border-zinc-200 dark:border-zinc-800 transition-colors">
|
||||
<div class="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="group flex items-center gap-2">
|
||||
<div class="w-8 h-8 bg-brand-dark dark:bg-brand-light text-brand-light dark:text-brand-dark flex items-center justify-center font-display font-bold text-xl group-hover:rotate-12 transition-transform">
|
||||
/
|
||||
</div>
|
||||
<span class="font-display font-bold text-2xl tracking-tight">NO HYPE AI</span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop Links -->
|
||||
<div class="hidden md:flex items-center gap-8 font-medium">
|
||||
<a href="/" class="hover:text-brand-cyan transition-colors">Home</a>
|
||||
<a href="/articles/" class="hover:text-brand-cyan transition-colors">Articles</a>
|
||||
<a href="/about/" class="hover:text-brand-pink transition-colors">About</a>
|
||||
<form method="post" action="/newsletter/subscribe/" data-newsletter-form class="flex items-center gap-2" id="nav-newsletter">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="source" value="nav" />
|
||||
<input type="email" name="email" required placeholder="dev@example.com"
|
||||
class="bg-transparent border border-zinc-300 dark:border-zinc-700 px-3 py-2 text-brand-dark dark:text-brand-light font-mono text-sm focus:outline-none focus:border-brand-pink transition-colors w-44" />
|
||||
<input type="text" name="honeypot" style="display:none" />
|
||||
<button type="submit" class="px-5 py-2.5 bg-brand-dark dark:bg-brand-light text-brand-light dark:text-brand-dark font-display font-bold hover:-translate-y-1 hover:shadow-solid-dark dark:hover:shadow-solid-light transition-all border border-transparent dark:border-zinc-700 whitespace-nowrap">Subscribe</button>
|
||||
<p data-newsletter-message aria-live="polite" class="font-mono text-xs text-brand-cyan min-h-[1rem]"></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Theme Toggle + Hamburger -->
|
||||
<div class="flex items-center gap-4">
|
||||
<button type="button" data-theme-toggle class="p-2 rounded-full hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors" aria-label="Toggle theme">
|
||||
<svg class="w-5 h-5 hidden dark:block text-yellow-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" /></svg>
|
||||
<svg class="w-5 h-5 block dark:hidden text-zinc-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" /></svg>
|
||||
</button>
|
||||
<button type="button" data-mobile-menu-toggle class="md:hidden p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded transition-colors" aria-label="Open menu" aria-expanded="false" aria-controls="mobile-menu">
|
||||
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile Menu (outside <nav> to avoid duplicate form[data-newsletter-form] in nav scope) -->
|
||||
<div id="mobile-menu" class="md:hidden hidden sticky top-20 z-40 border-b border-zinc-200 dark:border-zinc-800 bg-brand-light/95 dark:bg-brand-dark/95 backdrop-blur-md">
|
||||
<div class="max-w-7xl mx-auto px-6 py-4 flex flex-col gap-4">
|
||||
<a href="/" class="font-medium py-2 hover:text-brand-cyan transition-colors">Home</a>
|
||||
<a href="/articles/" class="font-medium py-2 hover:text-brand-cyan transition-colors">Articles</a>
|
||||
<a href="/about/" class="font-medium py-2 hover:text-brand-pink transition-colors">About</a>
|
||||
<form method="post" action="/newsletter/subscribe/" data-newsletter-form class="space-y-2 pt-2 border-t border-zinc-200 dark:border-zinc-800" id="mobile-newsletter">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="source" value="nav-mobile" />
|
||||
<input type="email" name="email" required placeholder="dev@example.com"
|
||||
class="w-full bg-transparent border border-zinc-300 dark:border-zinc-700 px-3 py-2 text-brand-dark dark:text-brand-light font-mono text-sm focus:outline-none focus:border-brand-pink transition-colors" />
|
||||
<input type="text" name="honeypot" style="display:none" />
|
||||
<button type="submit" class="w-full px-4 py-2.5 bg-brand-dark dark:bg-brand-light text-brand-light dark:text-brand-dark font-display font-bold transition-colors">Subscribe</button>
|
||||
<p data-newsletter-message aria-live="polite" class="font-mono text-xs text-brand-cyan min-h-[1rem]"></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="{{ request.csp_nonce|default:'' }}">
|
||||
(function(){
|
||||
var btn = document.querySelector('[data-mobile-menu-toggle]');
|
||||
var menu = document.getElementById('mobile-menu');
|
||||
if (btn && menu) {
|
||||
btn.addEventListener('click', function(){
|
||||
var isOpen = !menu.classList.contains('hidden');
|
||||
menu.classList.toggle('hidden');
|
||||
btn.setAttribute('aria-expanded', String(!isOpen));
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<form method="post" action="/newsletter/subscribe/" data-newsletter-form>
|
||||
<form method="post" action="/newsletter/subscribe/" data-newsletter-form class="space-y-3" id="newsletter">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="source" value="{{ source|default:'unknown' }}" />
|
||||
<label>
|
||||
<span>{{ label|default:"Newsletter" }}</span>
|
||||
<input type="email" name="email" required />
|
||||
</label>
|
||||
<input type="email" name="email" required placeholder="dev@example.com"
|
||||
class="w-full bg-transparent border border-zinc-300 dark:border-zinc-700 px-4 py-2 text-brand-dark dark:text-brand-light font-mono text-sm focus:outline-none focus:border-brand-pink transition-colors" />
|
||||
<input type="text" name="honeypot" style="display:none" />
|
||||
<button type="submit">Subscribe</button>
|
||||
<p data-newsletter-message aria-live="polite"></p>
|
||||
<button type="submit"
|
||||
class="w-full bg-brand-dark text-brand-light dark:bg-brand-light dark:text-brand-dark font-display font-bold py-2 hover:bg-brand-pink dark:hover:bg-brand-pink hover:text-white transition-colors">
|
||||
{{ label|default:"Subscribe" }}
|
||||
</button>
|
||||
<p data-newsletter-message aria-live="polite" class="font-mono text-xs text-brand-cyan min-h-[1rem]"></p>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load wagtailcore_tags %}
|
||||
{% block title %}{{ page.title }} | No Hype AI{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{{ page.title }}</h1>
|
||||
<p>Last updated: {{ page.last_updated|date:'F Y' }}</p>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="py-8 md:py-12 border-b border-zinc-200 dark:border-zinc-800 mb-12">
|
||||
<h1 class="font-display font-black text-4xl md:text-5xl mb-3">{{ page.title }}</h1>
|
||||
<p class="font-mono text-sm text-zinc-500">Last updated: {{ page.last_updated|date:'F Y' }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="max-w-3xl prose prose-lg dark:prose-invert
|
||||
prose-headings:font-display prose-headings:font-bold
|
||||
prose-a:text-brand-cyan hover:prose-a:text-brand-pink prose-a:transition-colors prose-a:no-underline hover:prose-a:underline">
|
||||
{{ page.body|richtext }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
File diff suppressed because one or more lines are too long
28
theme/static_src/package-lock.json
generated
28
theme/static_src/package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "nohype-theme",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"tailwindcss": "^3.4.17"
|
||||
}
|
||||
},
|
||||
@@ -93,6 +94,33 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"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,
|
||||
"license": "MIT",
|
||||
"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/@tailwindcss/typography/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,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/any-promise": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"dev": "tailwindcss -i ./src/input.css -o ../static/css/styles.css --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"tailwindcss": "^3.4.17"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,40 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.bg-grid-pattern {
|
||||
background-image: radial-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px);
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
.dark .bg-grid-pattern {
|
||||
background-image: radial-gradient(rgba(255, 255, 255, 0.07) 1px, transparent 1px);
|
||||
}
|
||||
.text-gradient {
|
||||
background: linear-gradient(135deg, #06b6d4, #ec4899);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: #ec4899;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
pre {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
|
||||
}
|
||||
pre::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
pre::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(156, 163, 175, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
body {
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,34 @@
|
||||
module.exports = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
"../../templates/**/*.html",
|
||||
"../../apps/**/templates/**/*.html"
|
||||
"../../apps/**/templates/**/*.html",
|
||||
],
|
||||
theme: {
|
||||
extend: {}
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
display: ['Space Grotesk', 'sans-serif'],
|
||||
mono: ['Fira Code', 'monospace'],
|
||||
},
|
||||
plugins: []
|
||||
colors: {
|
||||
brand: {
|
||||
cyan: '#06b6d4',
|
||||
cyanGlow: 'rgba(6,182,212,0.4)',
|
||||
pink: '#ec4899',
|
||||
dark: '#09090b',
|
||||
light: '#fafafa',
|
||||
surfaceDark: '#18181b',
|
||||
surfaceLight: '#ffffff',
|
||||
},
|
||||
},
|
||||
boxShadow: {
|
||||
'neon-cyan': '0 0 20px rgba(6, 182, 212, 0.3)',
|
||||
'neon-pink': '0 0 20px rgba(236, 72, 153, 0.3)',
|
||||
'solid-dark': '6px 6px 0px 0px #09090b',
|
||||
'solid-light': '6px 6px 0px 0px #e4e4e7',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user