Update style for post and link details

This commit is contained in:
2026-07-19 22:17:56 +10:00
parent 960f0c321e
commit 48050291d9
2 changed files with 659 additions and 447 deletions
+370 -275
View File
@@ -3,220 +3,211 @@
{% load static %}
{% block content %}
<div class="container mx-auto px-2 py-2">
<div class="bg-white shadow-md rounded-lg overflow-hidden mb-8">
<div class="px-2 py-5 sm:px-6 bg-gray-50 border-b border-gray-200 flex items-center justify-between">
<h1 class="text-2xl font-bold text-gray-900 flex items-center">
{% trans "Link Details" %}
<div class="apple-wrap link-detail-wrap">
<!-- Header + metadata card -->
<div class="apple-card">
<div class="link-header-row">
<div class="link-type-badge-row">
<h1 class="link-detail-title">{% trans "Link Details" %}</h1>
{% if '{' in link.original_url and '}' in link.original_url %}
<span class="ml-2 bg-purple-100 text-purple-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded-full flex items-center">
<i class="fas fa-magic mr-1"></i>{% trans "Template" %}
</span>
<span class="badge badge-template"><i class="fas fa-magic" aria-hidden="true"></i>{% trans "Template" %}</span>
{% elif link.link_type == 'LINK' %}
<span class="ml-2 bg-blue-100 text-blue-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded-full flex items-center">
<i class="fas fa-link mr-1"></i>{% trans "Link" %}
</span>
<span class="badge badge-link"><i class="fas fa-link" aria-hidden="true"></i>{% trans "Link" %}</span>
{% else %}
<span class="ml-2 bg-green-100 text-green-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded-full flex items-center">
<i class="fas fa-code mr-1"></i>{% trans "Custom" %}
</span>
<span class="badge badge-custom"><i class="fas fa-code" aria-hidden="true"></i>{% trans "Custom" %}</span>
{% endif %}
</h1>
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2">
<a href="{% url 'redirect_to_original' link.alias %}" target="_blank" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-2 rounded text-sm sm:text-base inline-flex items-center justify-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
</div>
<div class="link-header-actions">
<a href="{% url 'redirect_to_original' link.alias %}" target="_blank" class="action-btn action-btn-green">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
</svg>
{% trans "Go to link" %}
</a>
<a href="{% url 'link_update' link.pk %}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-2 rounded text-sm sm:text-base inline-flex items-center justify-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<a href="{% url 'link_update' link.pk %}" class="action-btn action-btn-blue">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
{% trans "Edit" %}
</a>
</div>
</div>
<div class="px-2 py-5 sm:p-6">
<dl class="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2">
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">{% trans "Alias" %}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ link.alias }}</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">{% trans "Click Count" %}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ link.click_count }}</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">{% trans "Created At" %}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ link.created_at|date:"Y-m-d H:i" }}</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">{% trans "Updated At" %}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ link.updated_at|date:"Y-m-d H:i" }}</dd>
</div>
{% if link.link_type == 'LINK' %}
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">{% trans "Original URL" %}</dt>
<dd class="mt-1 text-sm text-gray-900">
<a href="{{ link.original_url }}" target="_blank" class="text-blue-600 hover:underline">{{ link.original_url }}</a>
</dd>
</div>
{% endif %}
{% if link.description %}
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">{% trans "Description" %}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ link.description }}</dd>
</div>
{% endif %}
{% if link.tags.exists %}
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">{% trans "Tags" %}</dt>
<dd class="mt-1">
<div class="flex flex-wrap gap-2 mt-2">
{% for tag in link.tags.all %}
{% with number=forloop.counter %}
<a href="{% url 'tag-detail' tag.slug %}"
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium transition duration-150 {% if number|divisibleby:10 %}tag-10{% elif number|divisibleby:9 %}tag-9{% elif number|divisibleby:8 %}tag-8{% elif number|divisibleby:7 %}tag-7{% elif number|divisibleby:6 %}tag-6{% elif number|divisibleby:5 %}tag-5{% elif number|divisibleby:4 %}tag-4{% elif number|divisibleby:3 %}tag-3{% elif number|divisibleby:2 %}tag-2{% else %}tag-1{% endif %}">
{{ tag.name }}
</a>
{% endwith %}
{% endfor %}
</div>
<dl class="link-meta-grid">
<div class="link-meta-item">
<dt>{% trans "Alias" %}</dt>
<dd>{{ link.alias }}</dd>
</div>
<div class="link-meta-item">
<dt>{% trans "Click Count" %}</dt>
<dd>{{ link.click_count }}</dd>
</div>
<div class="link-meta-item">
<dt>{% trans "Created At" %}</dt>
<dd>{{ link.created_at|date:"Y-m-d H:i" }}</dd>
</div>
<div class="link-meta-item">
<dt>{% trans "Updated At" %}</dt>
<dd>{{ link.updated_at|date:"Y-m-d H:i" }}</dd>
</div>
{% if link.link_type == 'LINK' %}
<div class="link-meta-item span-2">
<dt>{% trans "Original URL" %}</dt>
<dd>
<a href="{{ link.original_url }}" target="_blank">{{ link.original_url }}</a>
</dd>
</div>
{% endif %}
</dl>
</div>
{% endif %}
{% if link.description %}
<div class="link-meta-item span-2">
<dt>{% trans "Description" %}</dt>
<dd>{{ link.description }}</dd>
</div>
{% endif %}
{% if link.tags.exists %}
<div class="link-meta-item span-2">
<dt>{% trans "Tags" %}</dt>
<dd>
<div class="link-meta-tags">
{% for tag in link.tags.all %}
{% with number=forloop.counter %}
<a href="{% url 'tag-detail' tag.slug %}"
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium transition duration-150 {% if number|divisibleby:10 %}tag-10{% elif number|divisibleby:9 %}tag-9{% elif number|divisibleby:8 %}tag-8{% elif number|divisibleby:7 %}tag-7{% elif number|divisibleby:6 %}tag-6{% elif number|divisibleby:5 %}tag-5{% elif number|divisibleby:4 %}tag-4{% elif number|divisibleby:3 %}tag-3{% elif number|divisibleby:2 %}tag-2{% else %}tag-1{% endif %}">
{{ tag.name }}
</a>
{% endwith %}
{% endfor %}
</div>
</dd>
</div>
{% endif %}
</dl>
</div>
{% if link.link_type == 'CUSTOM' %}
<div class="bg-white shadow-md rounded-lg overflow-hidden mb-8">
<div class="px-2 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h2 class="text-xl font-bold text-gray-900">{% trans "Custom Content" %}</h2>
</div>
<div class="px-2 py-5 sm:p-6">
<div class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl max-w-none">
{{ rendered_text|safe }}
</div>
<div class="apple-card section-spacing">
<h2 class="section-title-sm mb-4">{% trans "Custom Content" %}</h2>
<div class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl max-w-none">
{{ rendered_text|safe }}
</div>
</div>
{% endif %}
<div class="bg-white shadow-md rounded-lg overflow-hidden">
<div class="px-2 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center space-y-4 sm:space-y-0">
<h2 class="text-xl font-bold text-gray-900">{% trans "Click Statistics" %} - {{ period_name }}</h2>
<div class="flex flex-wrap gap-2">
<button onclick="changePeriod('3m')" class="period-btn {% if current_period == '3m' %}active{% endif %}" data-period="3m">
{% trans "3 Months" %}
</button>
<button onclick="changePeriod('6m')" class="period-btn {% if current_period == '6m' %}active{% endif %}" data-period="6m">
{% trans "6 Months" %}
</button>
<button onclick="changePeriod('1y')" class="period-btn {% if current_period == '1y' %}active{% endif %}" data-period="1y">
{% trans "1 Year" %}
</button>
<button onclick="changePeriod('all')" class="period-btn {% if current_period == 'all' %}active{% endif %}" data-period="all">
{% trans "All Time" %}
</button>
</div>
<div class="apple-card section-spacing">
<div class="stats-header">
<h2 class="section-title-sm">{% trans "Click Statistics" %} - {{ period_name }}</h2>
<div class="period-toggle-group">
<button onclick="changePeriod('3m')" class="period-btn {% if current_period == '3m' %}active{% endif %}" data-period="3m">
{% trans "3 Months" %}
</button>
<button onclick="changePeriod('6m')" class="period-btn {% if current_period == '6m' %}active{% endif %}" data-period="6m">
{% trans "6 Months" %}
</button>
<button onclick="changePeriod('1y')" class="period-btn {% if current_period == '1y' %}active{% endif %}" data-period="1y">
{% trans "1 Year" %}
</button>
<button onclick="changePeriod('all')" class="period-btn {% if current_period == 'all' %}active{% endif %}" data-period="all">
{% trans "All Time" %}
</button>
</div>
</div>
<div class="px-2 py-5 sm:p-6">
<div style="height: 400px; position: relative;">
<canvas id="clickChart"></canvas>
</div>
<div style="height: 400px; position: relative;">
<canvas id="clickChart"></canvas>
</div>
</div>
<div class="bg-white shadow-md rounded-lg overflow-hidden mt-8">
<div class="px-2 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h2 class="text-xl font-bold text-gray-900">{% trans "Change History" %}</h2>
</div>
<div class="px-2 py-5 sm:p-6">
{% if change_logs %}
<ul class="divide-y divide-gray-200">
{% for log in change_logs %}
<li class="py-3">
{% if log.change_type == 'url_change' %}
<div class="flex items-center mb-2">
<svg class="w-5 h-5 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
<p class="text-sm font-medium text-gray-900">
{% trans "URL Changed" %}
</p>
<span class="ml-2 text-xs text-gray-500">{{ log.changed_at|date:"Y-m-d H:i" }}</span>
<div class="apple-card section-spacing">
<h2 class="section-title-sm mb-4">{% trans "Change History" %}</h2>
{% if change_logs %}
<ul class="change-log-list">
{% for log in change_logs %}
<li class="change-log-item">
{% if log.change_type == 'url_change' %}
<div class="change-icon change-icon-blue">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
</svg>
</div>
<div class="change-log-body">
<div class="change-log-title-row">
<p class="change-log-title">{% trans "URL Changed" %}</p>
<span class="change-log-time">{{ log.changed_at|date:"Y-m-d H:i" }}</span>
</div>
{% if log.old_url %}
<p class="mt-1 text-sm text-red-600 line-through ml-7">{{ log.old_url }}</p>
<p class="change-log-detail change-url-old">{{ log.old_url }}</p>
{% endif %}
{% if log.new_url %}
<p class="mt-1 text-sm text-green-600 ml-7">{{ log.new_url }}</p>
<p class="change-log-detail change-url-new">{{ log.new_url }}</p>
{% endif %}
{% elif log.change_type == 'task_toggle' %}
<div class="flex items-center mb-2">
<svg class="w-5 h-5 text-purple-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
<p class="text-sm font-medium text-gray-900">
{% trans "Task Toggled" %}
</p>
<span class="ml-2 text-xs text-gray-500">{{ log.changed_at|date:"Y-m-d H:i" }}</span>
</div>
{% elif log.change_type == 'task_toggle' %}
<div class="change-icon change-icon-purple">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</div>
<div class="change-log-body">
<div class="change-log-title-row">
<p class="change-log-title">{% trans "Task Toggled" %}</p>
<span class="change-log-time">{{ log.changed_at|date:"Y-m-d H:i" }}</span>
</div>
{% if log.metadata %}
<div class="ml-7 text-sm">
<div class="change-log-detail">
{% if log.metadata.task_text %}
<p class="text-gray-700 font-medium mb-1">
<p style="font-weight: 500; color: var(--apple-text); margin-bottom: 0.25rem;">
"{{ log.metadata.task_text }}"
</p>
{% endif %}
<p class="text-gray-600">
<span class="{% if log.metadata.old_state %}text-green-600{% else %}text-gray-400{% endif %}">
<p>
<span class="{% if log.metadata.old_state %}change-url-new{% else %}change-log-time{% endif %}">
{% if log.metadata.old_state %}☑{% else %}☐{% endif %}
</span>
<span class="{% if log.metadata.new_state %}text-green-600{% else %}text-gray-400{% endif %}">
<span class="{% if log.metadata.new_state %}change-url-new{% else %}change-log-time{% endif %}">
{% if log.metadata.new_state %}☑{% else %}☐{% endif %}
</span>
<span class="ml-1">{% if log.metadata.new_state %}{% trans "checked" %}{% else %}{% trans "unchecked" %}{% endif %}</span>
</p>
</div>
{% endif %}
{% elif log.change_type == 'content_edit' %}
<div class="flex items-center mb-2">
<svg class="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
<p class="text-sm font-medium text-gray-900">
{% trans "Content Edited" %}
</p>
<span class="ml-2 text-xs text-gray-500">{{ log.changed_at|date:"Y-m-d H:i" }}</span>
</div>
{% elif log.change_type == 'content_edit' %}
<div class="change-icon change-icon-green">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
</div>
<div class="change-log-body">
<div class="change-log-title-row">
<p class="change-log-title">{% trans "Content Edited" %}</p>
<span class="change-log-time">{{ log.changed_at|date:"Y-m-d H:i" }}</span>
</div>
{% if log.metadata %}
<div class="ml-7 text-sm text-gray-600">
<div class="change-log-detail">
{{ log.metadata|safe }}
</div>
{% endif %}
{% else %}
<div class="flex items-center mb-2">
<svg class="w-5 h-5 text-gray-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<p class="text-sm font-medium text-gray-900">
{% trans "Change" %}
</p>
<span class="ml-2 text-xs text-gray-500">{{ log.changed_at|date:"Y-m-d H:i" }}</span>
</div>
{% else %}
<div class="change-icon change-icon-gray">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="change-log-body">
<div class="change-log-title-row">
<p class="change-log-title">{% trans "Change" %}</p>
<span class="change-log-time">{{ log.changed_at|date:"Y-m-d H:i" }}</span>
</div>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-sm text-gray-600">{% trans "No changes recorded." %}</p>
{% endif %}
</div>
</div>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="empty-history">{% trans "No changes recorded." %}</p>
{% endif %}
</div>
</div>
@@ -255,17 +246,17 @@
datasets: [{
label: '{% trans "Clicks" %}',
data: clickData.map(item => item.count),
backgroundColor: 'rgba(99, 102, 241, 0.1)',
borderColor: 'rgba(99, 102, 241, 1)',
backgroundColor: 'rgba(0, 113, 227, 0.08)',
borderColor: 'rgba(0, 113, 227, 1)',
borderWidth: 3,
fill: true,
tension: 0.4,
pointBackgroundColor: 'rgba(99, 102, 241, 1)',
pointBackgroundColor: 'rgba(0, 113, 227, 1)',
pointBorderColor: '#ffffff',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6,
pointHoverBackgroundColor: 'rgba(99, 102, 241, 1)',
pointHoverBackgroundColor: 'rgba(0, 113, 227, 1)',
pointHoverBorderColor: '#ffffff',
pointHoverBorderWidth: 2
}]
@@ -376,124 +367,228 @@
{% block extra_css %}
<style>
.prose {
color: #374151;
max-width: 65ch;
}
.prose p {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
.prose a {
color: #2563eb;
text-decoration: underline;
}
.prose strong {
font-weight: 600;
}
.prose ul {
margin-top: 1.25em;
margin-bottom: 1.25em;
list-style-type: disc;
padding-left: 1.625em;
}
.prose ol {
margin-top: 1.25em;
margin-bottom: 1.25em;
list-style-type: decimal;
padding-left: 1.625em;
}
.prose h1 {
font-size: 2.25em;
margin-top: 0;
margin-bottom: 0.8888889em;
line-height: 1.1111111;
}
.prose h2 {
font-size: 1.5em;
margin-top: 2em;
margin-bottom: 1em;
line-height: 1.3333333;
}
.prose h3 {
font-size: 1.25em;
margin-top: 1.6em;
margin-bottom: 0.6em;
line-height: 1.6;
}
.prose img {
margin-top: 2em;
margin-bottom: 2em;
}
.prose code {
color: #111827;
font-weight: 600;
}
.prose pre {
color: #e5e7eb;
background-color: #1f2937;
overflow-x: auto;
font-size: 0.875em;
line-height: 1.7142857;
margin-top: 1.7142857em;
margin-bottom: 1.7142857em;
border-radius: 0.375rem;
padding-top: 0.8571429em;
padding-right: 1.1428571em;
padding-bottom: 0.8571429em;
padding-left: 1.1428571em;
}
/* ============================================================
Apple-inspired design system — Link detail page
Mirrors the home page: #f5f5f7 canvas, SF typography, soft
shadows, pill buttons, generous rounded cards.
============================================================ */
:root {
--apple-bg: #f5f5f7;
--apple-text: #1d1d1f;
--apple-gray: #6e6e73;
--apple-blue: #0071e3;
--apple-blue-hover: #0077ed;
--apple-red: #ff3b30;
--apple-green: #1e7b34;
--apple-purple: #8944ab;
--apple-separator: rgba(0, 0, 0, 0.06);
--apple-ease: cubic-bezier(0.28, 0.11, 0.32, 1);
--apple-radius-lg: 28px;
--apple-radius-md: 22px;
--apple-radius-sm: 14px;
--apple-shadow-card: 0 4px 24px rgba(0, 0, 0, 0.05);
--apple-shadow-lift: 0 18px 44px rgba(0, 0, 0, 0.10);
}
/* Chart container styling */
#clickChart {
border-radius: 8px;
}
body {
background-color: var(--apple-bg) !important;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
"Helvetica Neue", "Segoe UI", Roboto, Arial, sans-serif;
color: var(--apple-text);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
/* Period selection buttons */
.period-btn {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: 0.375rem;
border: 1px solid #d1d5db;
background-color: #ffffff;
color: #374151;
cursor: pointer;
transition: all 0.2s ease-in-out;
}
.apple-wrap { max-width: 72rem; margin: 0 auto; }
.link-detail-wrap { padding: 0 0.25rem; }
.period-btn:hover {
background-color: #f9fafb;
border-color: #9ca3af;
}
.apple-card {
background: #fff;
border-radius: var(--apple-radius-lg);
border: 1px solid rgba(0, 0, 0, 0.04);
box-shadow: var(--apple-shadow-card);
padding: clamp(1.25rem, 4vw, 2.25rem);
}
.section-spacing { margin-top: 1.75rem; }
.section-title-sm { font-size: 1.2rem; font-weight: 700; letter-spacing: -0.01em; color: var(--apple-text); }
.mb-4 { margin-bottom: 1rem; }
.period-btn.active {
background-color: #6366f1;
border-color: #6366f1;
color: #ffffff;
}
/* ---------- Header ---------- */
.link-header-row { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 1.5rem; }
.link-type-badge-row { display: flex; align-items: center; gap: 0.65rem; flex-wrap: wrap; }
.link-detail-title { font-size: clamp(1.4rem, 3vw, 1.85rem); font-weight: 700; letter-spacing: -0.02em; color: var(--apple-text); }
.link-header-actions { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.period-btn.active:hover {
background-color: #5b21b6;
border-color: #5b21b6;
}
.badge {
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.28rem 0.72rem; border-radius: 980px;
font-size: 0.78rem; font-weight: 600;
}
.badge-template { background: rgba(191, 90, 242, 0.12); color: #8944ab; }
.badge-link { background: rgba(0, 113, 227, 0.10); color: var(--apple-blue); }
.badge-custom { background: rgba(48, 209, 88, 0.15); color: #1e7b34; }
/* Task List Styles */
.task-item {
list-style: none;
padding-left: 0;
}
.action-btn {
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.55rem 1.1rem; border-radius: 980px;
font-size: 0.88rem; font-weight: 600; border: none; cursor: pointer;
text-decoration: none; white-space: nowrap;
transition: background 0.25s var(--apple-ease), color 0.25s var(--apple-ease), transform 0.2s var(--apple-ease);
}
.action-btn:active { transform: scale(0.96); }
.action-btn svg { width: 1rem; height: 1rem; }
.action-btn-green { background: rgba(48, 209, 88, 0.14); color: var(--apple-green); }
.action-btn-green:hover { background: rgba(48, 209, 88, 0.22); color: var(--apple-green); }
.action-btn-blue { background: rgba(0, 113, 227, 0.10); color: var(--apple-blue); }
.action-btn-blue:hover { background: rgba(0, 113, 227, 0.18); color: var(--apple-blue); }
.task-checkbox {
margin-right: 0.5rem;
cursor: pointer;
width: 1.1em;
height: 1.1em;
vertical-align: middle;
}
/* ---------- Meta grid ---------- */
.link-meta-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem 2rem; }
@media (max-width: 640px) { .link-meta-grid { grid-template-columns: 1fr; } }
.link-meta-item.span-2 { grid-column: 1 / -1; }
.link-meta-item dt { font-size: 0.76rem; font-weight: 600; color: var(--apple-gray); text-transform: uppercase; letter-spacing: 0.07em; margin-bottom: 0.3rem; }
.link-meta-item dd { font-size: 0.95rem; color: var(--apple-text); margin: 0; word-break: break-all; }
.link-meta-item dd a { color: var(--apple-blue); text-decoration: none; }
.link-meta-item dd a:hover { text-decoration: underline; }
.link-meta-tags { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.task-checkbox:hover {
opacity: 0.7;
}
/* ---------- Prose (custom content) ---------- */
.prose {
color: var(--apple-text);
max-width: 65ch;
}
.prose p {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
.prose a {
color: var(--apple-blue);
text-decoration: underline;
}
.prose strong {
font-weight: 600;
}
.prose ul {
margin-top: 1.25em;
margin-bottom: 1.25em;
list-style-type: disc;
padding-left: 1.625em;
}
.prose ol {
margin-top: 1.25em;
margin-bottom: 1.25em;
list-style-type: decimal;
padding-left: 1.625em;
}
.prose h1 {
font-size: 2.25em;
margin-top: 0;
margin-bottom: 0.8888889em;
line-height: 1.1111111;
}
.prose h2 {
font-size: 1.5em;
margin-top: 2em;
margin-bottom: 1em;
line-height: 1.3333333;
}
.prose h3 {
font-size: 1.25em;
margin-top: 1.6em;
margin-bottom: 0.6em;
line-height: 1.6;
}
.prose img {
margin-top: 2em;
margin-bottom: 2em;
border-radius: var(--apple-radius-sm);
}
.prose code {
color: var(--apple-text);
font-weight: 600;
}
.prose pre {
color: #e5e7eb;
background-color: #1f2937;
overflow-x: auto;
font-size: 0.875em;
line-height: 1.7142857;
margin-top: 1.7142857em;
margin-bottom: 1.7142857em;
border-radius: var(--apple-radius-sm);
padding-top: 0.8571429em;
padding-right: 1.1428571em;
padding-bottom: 0.8571429em;
padding-left: 1.1428571em;
}
/* ---------- Click statistics ---------- */
.stats-header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1.5rem; }
.period-toggle-group { display: inline-flex; gap: 0.35rem; background: var(--apple-bg); padding: 0.3rem; border-radius: 980px; }
.period-btn {
padding: 0.45rem 0.95rem;
font-size: 0.83rem;
font-weight: 600;
border-radius: 980px;
border: none;
background: transparent;
color: var(--apple-gray);
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease;
}
.period-btn:hover {
color: var(--apple-text);
}
.period-btn.active {
background: #fff;
color: var(--apple-blue);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* Chart container styling */
#clickChart {
border-radius: var(--apple-radius-sm);
}
/* ---------- Change history timeline ---------- */
.change-log-list { list-style: none; margin: 0; padding: 0; }
.change-log-item { display: flex; gap: 0.9rem; padding: 1rem 0; border-bottom: 1px solid var(--apple-separator); }
.change-log-item:last-child { border-bottom: none; padding-bottom: 0; }
.change-log-item:first-child { padding-top: 0; }
.change-icon {
flex-shrink: 0; width: 2.2rem; height: 2.2rem; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
}
.change-icon svg { width: 1.1rem; height: 1.1rem; }
.change-icon-blue { background: rgba(0, 113, 227, 0.10); color: var(--apple-blue); }
.change-icon-purple { background: rgba(191, 90, 242, 0.12); color: var(--apple-purple); }
.change-icon-green { background: rgba(48, 209, 88, 0.14); color: var(--apple-green); }
.change-icon-gray { background: var(--apple-bg); color: var(--apple-gray); }
.change-log-body { flex: 1; min-width: 0; }
.change-log-title-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.change-log-title { font-size: 0.92rem; font-weight: 600; color: var(--apple-text); margin: 0; }
.change-log-time { font-size: 0.78rem; color: var(--apple-gray); }
.change-log-detail { margin-top: 0.35rem; font-size: 0.86rem; color: var(--apple-gray); }
.change-url-old { color: var(--apple-red); text-decoration: line-through; }
.change-url-new { color: var(--apple-green); }
.empty-history { font-size: 0.9rem; color: var(--apple-gray); }
/* ---------- Task List Styles ---------- */
.task-item {
list-style: none;
padding-left: 0;
}
.task-checkbox {
margin-right: 0.5rem;
cursor: pointer;
width: 1.1em;
height: 1.1em;
vertical-align: middle;
}
.task-checkbox:hover {
opacity: 0.7;
}
</style>
{% endblock %}
+289 -172
View File
@@ -6,103 +6,188 @@
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/markdown.css' %}">
<style>
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
z-index: 50;
align-items: center;
justify-content: center;
backdrop-filter: blur(4px);
}
.modal.show {
display: flex;
}
.modal-content {
background: white;
border-radius: 0.75rem;
width: 100%;
max-width: 28rem;
margin: 1rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
/* ============================================================
Apple-inspired design system — Post detail page
Mirrors the home page: #f5f5f7 canvas, SF typography, soft
shadows, pill buttons, generous rounded cards.
============================================================ */
:root {
--apple-bg: #f5f5f7;
--apple-text: #1d1d1f;
--apple-gray: #6e6e73;
--apple-blue: #0071e3;
--apple-blue-hover: #0077ed;
--apple-red: #ff3b30;
--apple-green: #1e7b34;
--apple-purple: #8944ab;
--apple-separator: rgba(0, 0, 0, 0.06);
--apple-ease: cubic-bezier(0.28, 0.11, 0.32, 1);
--apple-radius-lg: 28px;
--apple-radius-md: 22px;
--apple-radius-sm: 14px;
--apple-shadow-card: 0 4px 24px rgba(0, 0, 0, 0.05);
--apple-shadow-lift: 0 18px 44px rgba(0, 0, 0, 0.10);
}
/* Think Panel Styles */
.think-panel {
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
margin: 1rem 0;
overflow: hidden;
background-color: #fff;
}
body {
background-color: var(--apple-bg) !important;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
"Helvetica Neue", "Segoe UI", Roboto, Arial, sans-serif;
color: var(--apple-text);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
.think-header {
background-color: #f9fafb;
padding: 1rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 1rem;
color: #374151;
}
.apple-card {
background: #fff;
border-radius: var(--apple-radius-lg);
border: 1px solid rgba(0, 0, 0, 0.04);
box-shadow: var(--apple-shadow-card);
}
.think-header:hover {
background-color: #f3f4f6;
}
.apple-btn {
display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
min-height: 44px; padding: 0.6rem 1.4rem; border-radius: 980px;
font-weight: 600; font-size: 0.95rem; border: none; cursor: pointer; text-decoration: none;
transition: background 0.3s var(--apple-ease), transform 0.3s var(--apple-ease), box-shadow 0.3s var(--apple-ease);
}
.apple-btn:active { transform: scale(0.97); }
.apple-btn-primary { background: var(--apple-blue); color: #fff; box-shadow: 0 6px 18px rgba(0, 113, 227, 0.28); }
.apple-btn-primary:hover { background: var(--apple-blue-hover); color: #fff; }
.apple-btn-secondary { background: var(--apple-bg); color: var(--apple-text); border: 1px solid rgba(0, 0, 0, 0.10); }
.apple-btn-secondary:hover { background: #ececef; }
.apple-btn-danger { background: transparent; color: var(--apple-red); border: 1px solid rgba(255, 59, 48, 0.4); }
.apple-btn-danger:hover { background: rgba(255, 59, 48, 0.08); }
.think-content {
padding: 0;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out, padding 0.3s ease-out;
}
/* ---------- Soft pill action buttons (Listen / Edit / Share / Delete) ---------- */
.action-btn {
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.5rem 1rem; border-radius: 980px;
font-size: 0.85rem; font-weight: 600; border: none; cursor: pointer;
text-decoration: none; white-space: nowrap;
transition: background 0.25s var(--apple-ease), color 0.25s var(--apple-ease), transform 0.2s var(--apple-ease);
}
.action-btn:active { transform: scale(0.96); }
.action-btn svg { width: 1rem; height: 1rem; }
.action-btn-purple { background: rgba(191, 90, 242, 0.12); color: var(--apple-purple); }
.action-btn-purple:hover { background: rgba(191, 90, 242, 0.20); }
.action-btn-blue { background: rgba(0, 113, 227, 0.10); color: var(--apple-blue); }
.action-btn-blue:hover { background: rgba(0, 113, 227, 0.18); }
.action-btn-green { background: rgba(48, 209, 88, 0.14); color: var(--apple-green); }
.action-btn-green:hover { background: rgba(48, 209, 88, 0.22); }
.action-btn-red { background: rgba(255, 59, 48, 0.10); color: var(--apple-red); }
.action-btn-red:hover { background: rgba(255, 59, 48, 0.18); }
.action-btn:disabled { opacity: 0.55; cursor: default; }
.action-icon-btn {
display: inline-flex; align-items: center; justify-content: center;
width: 34px; height: 34px; border-radius: 50%; border: none; background: transparent;
color: var(--apple-gray); cursor: pointer; transition: background 0.25s ease, color 0.25s ease;
}
.action-icon-btn:hover { background: rgba(255, 59, 48, 0.10); color: var(--apple-red); }
.action-icon-btn:disabled { opacity: 0.5; cursor: default; }
.action-icon-btn svg { width: 1rem; height: 1rem; }
.think-content.expanded {
padding: 1rem;
max-height: 2000px; /* Adjust based on your needs */
transition: max-height 0.5s ease-in, padding 0.3s ease-in;
}
/* ---------- Modal ---------- */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.45);
z-index: 50;
align-items: center;
justify-content: center;
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
}
.modal.show {
display: flex;
}
.modal-content {
background: #fff;
border-radius: var(--apple-radius-md);
width: 100%;
max-width: 26rem;
margin: 1rem;
box-shadow: var(--apple-shadow-lift);
border: 1px solid rgba(0, 0, 0, 0.04);
}
.think-icon {
transition: transform 0.3s ease;
}
/* Think Panel Styles */
.think-panel {
border: 1px solid var(--apple-separator);
border-radius: var(--apple-radius-sm);
margin: 1rem 0;
overflow: hidden;
background-color: #fff;
}
.think-icon.expanded {
transform: rotate(180deg);
}
.think-header {
background-color: var(--apple-bg);
padding: 1rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 1rem;
color: var(--apple-text);
}
/* Task List Styles */
.task-item {
list-style: none;
padding-left: 0;
}
.think-header:hover {
background-color: #ececef;
}
.task-checkbox {
margin-right: 0.5rem;
cursor: pointer;
width: 1.1em;
height: 1.1em;
vertical-align: middle;
}
.think-content {
padding: 0;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out, padding 0.3s ease-out;
}
.task-checkbox:hover {
opacity: 0.7;
}
.think-content.expanded {
padding: 1rem;
max-height: 2000px; /* Adjust based on your needs */
transition: max-height 0.5s ease-in, padding 0.3s ease-in;
}
/* Enhanced Audio Player Styles */
.audio-player {
background: linear-gradient(145deg, #f8fafc 0%, #e2e8f0 100%);
border: 1px solid #cbd5e1;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.think-icon {
transition: transform 0.3s ease;
}
.think-icon.expanded {
transform: rotate(180deg);
}
/* Task List Styles */
.task-item {
list-style: none;
padding-left: 0;
}
.task-checkbox {
margin-right: 0.5rem;
cursor: pointer;
width: 1.1em;
height: 1.1em;
vertical-align: middle;
}
.task-checkbox:hover {
opacity: 0.7;
}
/* Enhanced Audio Player Styles */
.audio-player {
background: linear-gradient(145deg, #fafafa 0%, #eef0f3 100%);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: var(--apple-radius-md);
box-shadow: var(--apple-shadow-card);
padding: 1.25rem;
}
.audio-controls {
display: flex;
@@ -307,21 +392,23 @@
gap: 0.75rem;
margin-bottom: 1rem;
padding: 1rem;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
background: rgba(255, 255, 255, 0.6);
border-radius: var(--apple-radius-sm);
border: 1px solid rgba(0, 0, 0, 0.06);
}
.voice-label {
font-size: 12px;
font-weight: 600;
color: #374151;
color: var(--apple-text);
white-space: nowrap;
}
.main-content {
background: #fff;
border-radius: 0.5rem;
padding: 1rem;
border-radius: var(--apple-radius-lg);
border: 1px solid rgba(0, 0, 0, 0.04);
box-shadow: var(--apple-shadow-card);
padding: clamp(1.25rem, 4vw, 2.5rem);
}
/* ===== Table of Contents Layout ===== */
@@ -330,6 +417,34 @@
margin: 0.5rem auto;
padding: 0.5rem;
}
.post-header-row { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 0.85rem; margin-bottom: 1.1rem; }
.post-title { font-size: clamp(1.5rem, 3.4vw, 2.1rem); font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; flex: 1 1 16rem; min-width: 0; color: var(--apple-text); }
.post-actions { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; flex-wrap: wrap; }
.post-meta { display: flex; flex-direction: column; gap: 0.85rem; margin-bottom: 1.25rem; }
.post-summary { position: relative; padding-left: 1rem; border-left: 3px solid rgba(0, 113, 227, 0.35); }
.post-summary p { font-size: 1.08rem; color: var(--apple-gray); font-style: italic; margin: 0; }
.post-tags { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.post-timestamps { display: flex; flex-wrap: wrap; align-items: center; gap: 1.5rem; font-size: 0.85rem; color: var(--apple-gray); background: var(--apple-bg); border-radius: 980px; padding: 0.6rem 1.1rem; }
.post-timestamps .timestamp-item { display: flex; align-items: center; }
.post-timestamps svg { width: 1rem; height: 1rem; margin-right: 0.4rem; color: #a1a1a6; }
.post-timestamps .timestamp-label { font-weight: 600; color: var(--apple-text); margin-right: 0.25rem; }
.post-article { margin-top: 1.5rem; }
.append-section { margin-top: 2.25rem; border-top: 1px solid var(--apple-separator); padding-top: 1.5rem; }
.append-title { font-size: 0.85rem; font-weight: 600; color: var(--apple-text); margin-bottom: 0.75rem; }
.append-textarea {
width: 100%; min-height: 200px; padding: 0.85rem 1rem; font-size: 0.9rem;
font-family: 'SF Mono', 'Monaco', 'Menlo', monospace; color: var(--apple-text);
background: var(--apple-bg); border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: var(--apple-radius-sm); resize: vertical;
transition: border-color 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
}
.append-textarea:focus {
outline: none; background: #fff;
border-color: rgba(0, 113, 227, 0.5);
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.14);
}
.append-footer { display: flex; align-items: center; justify-content: space-between; margin-top: 0.75rem; }
.append-status { font-size: 0.78rem; color: var(--apple-gray); }
#toc-sidebar {
display: none; /* hidden by default (mobile) */
}
@@ -360,16 +475,21 @@
}
#toc-sidebar-inner > div {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
padding: 1rem;
border: 1px solid rgba(0, 0, 0, 0.04);
border-radius: var(--apple-radius-md);
box-shadow: var(--apple-shadow-card);
padding: 1.25rem;
}
.toc-card-label {
font-size: 0.7rem; font-weight: 600; color: var(--apple-gray);
text-transform: uppercase; letter-spacing: 0.09em; margin-bottom: 0.75rem; user-select: none;
}
#toc-sidebar .toc-link {
display: block;
font-size: 0.8rem;
line-height: 1.6;
color: #6b7280;
color: var(--apple-gray);
text-decoration: none;
padding: 0.2rem 0.5rem;
border-left: 2px solid transparent;
@@ -378,16 +498,16 @@
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
border-radius: 0 4px 4px 0;
border-radius: 0 8px 8px 0;
}
#toc-sidebar .toc-link:hover { color: #1d4ed8; background-color: #eff6ff; }
#toc-sidebar .toc-link:hover { color: var(--apple-blue); background-color: rgba(0, 113, 227, 0.08); }
#toc-sidebar .toc-link.toc-active {
color: #1d4ed8;
border-left-color: #1d4ed8;
color: var(--apple-blue);
border-left-color: var(--apple-blue);
font-weight: 500;
background-color: #eff6ff;
background-color: rgba(0, 113, 227, 0.08);
}
#toc-sidebar .toc-h2 { font-weight: 500; color: #374151; }
#toc-sidebar .toc-h2 { font-weight: 500; color: var(--apple-text); }
#toc-sidebar .toc-h3,
#toc-sidebar .toc-h4 { font-size: 0.775rem; }
@@ -397,27 +517,29 @@
bottom: 1.5rem;
right: 1.5rem;
z-index: 40;
background: #2563eb;
background: var(--apple-blue);
color: #fff;
border: none;
border-radius: 9999px;
width: 3rem;
height: 3rem;
box-shadow: 0 10px 15px -3px rgba(0,0,0,.1), 0 4px 6px -4px rgba(0,0,0,.1);
box-shadow: 0 12px 28px rgba(0, 113, 227, 0.32);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.15s;
transition: background-color 0.2s ease, transform 0.2s var(--apple-ease);
}
#toc-mobile-btn:hover { background: #1d4ed8; }
#toc-mobile-btn:hover { background: var(--apple-blue-hover); transform: translateY(-1px); }
/* Mobile drawer */
#toc-mobile-backdrop {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
background: rgba(0, 0, 0, 0.45);
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
z-index: 40;
}
#toc-mobile-drawer {
@@ -429,24 +551,25 @@
max-width: 80vw;
background: #fff;
z-index: 50;
box-shadow: 0 25px 50px -12px rgba(0,0,0,.25);
box-shadow: var(--apple-shadow-lift);
border-radius: 0 var(--apple-radius-md) var(--apple-radius-md) 0;
overflow-y: auto;
transform: translateX(-100%);
transition: transform 0.3s ease;
transition: transform 0.35s var(--apple-ease);
}
#toc-nav-mobile .toc-link {
display: block;
font-size: 0.875rem;
line-height: 1.7;
color: #374151;
color: var(--apple-text);
text-decoration: none;
padding: 0.3rem 0.5rem;
border-left: 2px solid transparent;
transition: color 0.15s, border-color 0.15s;
border-radius: 0 4px 4px 0;
border-radius: 0 8px 8px 0;
}
#toc-nav-mobile .toc-link:hover { color: #1d4ed8; background-color: #eff6ff; }
#toc-nav-mobile .toc-link.toc-active { color: #1d4ed8; border-left-color: #1d4ed8; font-weight: 500; }
#toc-nav-mobile .toc-link:hover { color: var(--apple-blue); background-color: rgba(0, 113, 227, 0.08); }
#toc-nav-mobile .toc-link.toc-active { color: var(--apple-blue); border-left-color: var(--apple-blue); font-weight: 500; }
</style>
{% endblock %}
@@ -455,23 +578,22 @@
<div id="deleteModal" class="modal" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="modal-content">
<div class="p-6">
<h3 class="text-lg font-medium text-gray-900" id="modal-title">
<h3 class="text-lg font-semibold" style="color: var(--apple-text);" id="modal-title">
{% trans "Delete Post" %}
</h3>
<p class="mt-2 text-sm text-gray-500">
<p class="mt-2 text-sm" style="color: var(--apple-gray);">
{% trans "Are you sure you want to delete this post? This action cannot be undone." %}
</p>
<div class="mt-6 flex space-x-3">
<div class="mt-6 flex gap-3">
<form method="post" action="{% url 'post-delete' post.pk %}">
{% csrf_token %}
<button type="submit"
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-lg hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition duration-150">
<button type="submit" class="apple-btn" style="background: var(--apple-red); color: #fff; box-shadow: 0 6px 18px rgba(255, 59, 48, 0.28);">
{% trans "Delete" %}
</button>
</form>
<button type="button"
onclick="closeDeleteModal()"
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition duration-150">
class="apple-btn apple-btn-secondary">
{% trans "Cancel" %}
</button>
</div>
@@ -488,7 +610,7 @@
<aside id="toc-sidebar">
<div id="toc-sidebar-inner">
<div>
<p style="font-size:0.7rem;font-weight:600;color:#9ca3af;text-transform:uppercase;letter-spacing:0.05em;margin-bottom:0.75rem;user-select:none;">Contents</p>
<p class="toc-card-label">{% trans "Contents" %}</p>
<nav id="toc-nav"></nav>
</div>
</div>
@@ -496,23 +618,21 @@
<!-- Main Content -->
<div class="main-content" id="post-main-content">
<!-- Title and Actions - Responsive Layout -->
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 mb-4">
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 sm:flex-1">{{ post.title }}</h1>
<!-- Title and Actions -->
<div class="post-header-row">
<h1 class="post-title">{{ post.title }}</h1>
<!-- Action Buttons - Responsive positioning -->
<div class="flex items-center space-x-2 sm:flex-shrink-0">
<button onclick="toggleTTS()"
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-purple-700 hover:text-purple-800 bg-purple-50 hover:bg-purple-100 rounded-md transition duration-150">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<!-- Action Buttons -->
<div class="post-actions">
<button onclick="toggleTTS()" class="action-btn action-btn-purple">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 14.142M9 9v6l4-3-4-3z"/>
</svg>
<span id="tts-button-text">{% trans "Listen" %}</span>
</button>
<a href="{% url 'post-update' post.pk %}"
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-blue-700 hover:text-blue-800 bg-blue-50 hover:bg-blue-100 rounded-md transition duration-150">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<a href="{% url 'post-update' post.pk %}" class="action-btn action-btn-blue">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
@@ -523,8 +643,8 @@
<!-- Not yet shared -->
<button x-show="!isPublic" @click="enableShare()"
:disabled="busy"
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-green-700 hover:text-green-800 bg-green-50 hover:bg-green-100 rounded-md transition duration-150 disabled:opacity-50">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
class="action-btn action-btn-green">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"/>
</svg>
@@ -532,9 +652,8 @@
</button>
<!-- Already shared: copy + revoke -->
<div x-show="isPublic" class="flex items-center gap-1">
<button @click="copyUrl()"
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-green-700 bg-green-50 hover:bg-green-100 border border-green-200 rounded-md transition duration-150">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<button @click="copyUrl()" class="action-btn action-btn-green">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
</svg>
@@ -542,16 +661,15 @@
</button>
<button @click="revokeShare()" :disabled="busy"
title="{% trans 'Revoke public access' %}"
class="p-1.5 text-gray-400 hover:text-red-500 rounded transition disabled:opacity-50">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
class="action-icon-btn">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
</div>
<button onclick="openDeleteModal()"
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-red-700 hover:text-red-800 bg-red-50 hover:bg-red-100 rounded-md transition duration-150">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<button onclick="openDeleteModal()" class="action-btn action-btn-red">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
@@ -561,17 +679,17 @@
</div>
<!-- Metadata -->
<div class="space-y-3 mb-4">
<div class="post-meta">
{% if post.summary %}
<div class="relative pl-3 border-l-4 border-gray-200">
<p class="text-lg text-gray-600 italic">{{ post.summary }}</p>
<div class="post-summary">
<p>{{ post.summary }}</p>
</div>
{% endif %}
<!-- Tags -->
{% if post.tags.exists %}
<div class="flex flex-wrap gap-2">
<div class="post-tags">
{% for tag in post.tags.all %}
{% with number=forloop.counter %}
<a href="{% url 'tag-detail' tag.slug %}"
@@ -583,23 +701,23 @@
</div>
{% endif %}
<!-- Timestamps - Compact single row design -->
<div class="flex items-center gap-6 text-sm text-gray-500 bg-gray-50 rounded-md px-3 py-2">
<div class="flex items-center">
<svg class="w-4 h-4 mr-1.5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<!-- Timestamps -->
<div class="post-timestamps">
<div class="timestamp-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
<span class="font-medium text-gray-600">{% trans "Created" %}:</span>
<time datetime="{{ post.created_at|date:'Y-m-d H:i' }}" class="ml-1">{{ post.created_at|date:"Y-m-d H:i" }}</time>
<span class="timestamp-label">{% trans "Created" %}:</span>
<time datetime="{{ post.created_at|date:'Y-m-d H:i' }}">{{ post.created_at|date:"Y-m-d H:i" }}</time>
</div>
<div class="flex items-center">
<svg class="w-4 h-4 mr-1.5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="timestamp-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<span class="font-medium text-gray-600">{% trans "Updated" %}:</span>
<time datetime="{{ post.updated_at|date:'Y-m-d H:i' }}" class="ml-1">{{ post.updated_at|date:"Y-m-d H:i" }}</time>
<span class="timestamp-label">{% trans "Updated" %}:</span>
<time datetime="{{ post.updated_at|date:'Y-m-d H:i' }}">{{ post.updated_at|date:"Y-m-d H:i" }}</time>
</div>
</div>
</div>
@@ -607,7 +725,7 @@
<!-- TTS Audio Player - Initially Hidden -->
<div id="tts-player" class="mt-4 audio-player hidden">
<div class="flex items-center justify-between mb-3">
<h4 class="text-sm font-medium text-gray-800">{% trans "Audio Player" %}</h4>
<h4 class="text-sm font-medium" style="color: var(--apple-text);">{% trans "Audio Player" %}</h4>
<div id="tts-status" class="audio-status status-ready">{% trans "Ready" %}</div>
</div>
@@ -633,7 +751,7 @@
<option value="custom">{% trans "Custom" %}</option>
</select>
<button onclick="regenerateWithVoice()" id="regenerate-btn"
class="px-3 py-1 text-xs font-medium text-purple-700 bg-purple-100 border border-purple-300 rounded-md hover:bg-purple-200 transition-colors duration-200"
class="action-btn action-btn-purple"
disabled>
{% trans "Apply Voice" %}
</button>
@@ -647,7 +765,7 @@
class="custom-voice-input"
placeholder="{% trans 'e.g., zh-CN-XiaoxiaoNeural' %}"
maxlength="100">
<div class="text-xs text-gray-500 mt-1">
<div class="text-xs mt-1" style="color: var(--apple-gray);">
{% trans "Enter the voice code you want to use" %}
</div>
</div>
@@ -696,21 +814,20 @@
</div>
</div>
<!-- Content Section - Full width, no containers -->
<article class="prose max-w-none prose-lg">
<!-- Content Section -->
<article class="prose max-w-none prose-lg post-article">
{% markdown_with_tasks post.content post.task_states %}
</article>
<!-- Append Content Section -->
<div class="mt-8 border-t border-gray-200 pt-6">
<h3 class="text-sm font-semibold text-gray-700 mb-3">{% trans "Append Content" %}</h3>
<div class="append-section">
<h3 class="append-title">{% trans "Append Content" %}</h3>
<textarea id="append-content-input"
class="w-full min-h-[200px] px-3 py-2 text-sm font-mono border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-y"
class="append-textarea"
placeholder="{% trans 'Write markdown to append...' %}"></textarea>
<div class="flex items-center justify-between mt-2">
<span id="append-status" class="text-xs text-gray-500"></span>
<button id="append-btn" onclick="appendContent()"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
<div class="append-footer">
<span id="append-status" class="append-status"></span>
<button id="append-btn" onclick="appendContent()" class="apple-btn apple-btn-primary">
{% trans "Append" %}
</button>
</div>
@@ -729,11 +846,11 @@
<!-- Mobile TOC drawer -->
<div id="toc-mobile-drawer">
<div style="padding:1rem;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;padding-bottom:0.75rem;border-bottom:1px solid #f3f4f6;">
<p style="font-weight:600;color:#374151;margin:0;">{% trans "Contents" %}</p>
<button id="toc-mobile-close" style="background:none;border:none;cursor:pointer;padding:0.25rem;color:#9ca3af;border-radius:0.25rem;" aria-label="Close">
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div style="padding:1.25rem;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;padding-bottom:0.75rem;border-bottom:1px solid var(--apple-separator);">
<p style="font-weight:600;color:var(--apple-text);margin:0;">{% trans "Contents" %}</p>
<button id="toc-mobile-close" class="action-icon-btn" style="color: var(--apple-gray);" aria-label="Close">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>