This commit is contained in:
2026-07-30 13:25:36 +10:00
parent d857eb58c5
commit 6b967e501f
2 changed files with 319 additions and 85 deletions
+249 -32
View File
@@ -1,57 +1,274 @@
{% extends 'base.html' %}
{% load i18n %}
{% load bookmark_extras %}
{% block content %}
<div class="max-w-2xl mx-auto px-4 py-6 mt-16">
<a href="{% url 'bookmark-list' %}" class="text-sm text-blue-600 hover:underline">← {% trans "Back to Bookmarks" %}</a>
<div id="post-toc-layout">
<div class="main-content" id="post-main-content">
<a href="{% url 'bookmark-list' %}" class="back-link">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
{% trans "Back to Bookmarks" %}
</a>
<article class="bg-white shadow rounded-lg p-5 mt-3">
<div class="flex items-start gap-3">
{% if bookmark.author_profile_image_url %}
<img src="{{ bookmark.author_profile_image_url }}" alt="" class="w-12 h-12 rounded-full border border-gray-200" loading="lazy">
{% endif %}
<div>
<a href="https://x.com/{{ bookmark.author_screen_name }}" target="_blank" rel="noopener" class="font-semibold text-gray-900 hover:underline">{{ bookmark.author_name|default:bookmark.author_screen_name }}</a>
<div class="text-sm text-gray-500">@{{ bookmark.author_screen_name }}</div>
{% if bookmark.tweet_created_at %}<div class="text-sm text-gray-500">{{ bookmark.tweet_created_at|date:"M j, Y H:i" }}</div>{% endif %}
<!-- Header -->
<div class="post-header-row">
<div class="bookmark-author">
{% if bookmark.author_profile_image_url %}
<a href="https://x.com/{{ bookmark.author_screen_name }}" target="_blank" rel="noopener">
<img src="{{ bookmark.author_profile_image_url }}" alt="" class="bookmark-avatar" loading="lazy">
</a>
{% endif %}
<div>
<a href="https://x.com/{{ bookmark.author_screen_name }}" target="_blank" rel="noopener" class="bookmark-name">{{ bookmark.author_name|default:bookmark.author_screen_name }}</a>
<div class="bookmark-handle">@{{ bookmark.author_screen_name }}</div>
</div>
</div>
<div class="post-actions">
<a href="{{ bookmark.url|default:'#' }}" target="_blank" rel="noopener" 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="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
{% trans "Open on X" %}
</a>
</div>
<a href="{{ bookmark.url|default:'#' }}" target="_blank" rel="noopener" class="ml-auto text-blue-600 text-sm hover:underline">{% trans "Open on X" %} ↗</a>
</div>
{% if bookmark.text %}
<div class="mt-4 text-gray-800 whitespace-pre-wrap break-words">{{ bookmark.text }}</div>
<div class="bookmark-detail-text">{{ bookmark.text|urlizetrunc:60|linebreaksbr }}</div>
{% endif %}
{% if bookmark.summary %}
<div class="mt-3 text-sm text-emerald-700 bg-emerald-50 border border-emerald-100 rounded px-3 py-2">{{ bookmark.summary }}</div>
<div class="bookmark-detail-summary">{{ bookmark.summary }}</div>
{% endif %}
{% if bookmark.media %}
<div class="grid gap-2 mt-4">
<div class="bm-media {{ bookmark.media|length|media_grid_class }}">
{% for m in bookmark.media %}
{% if m.type == 'photo' %}
<a href="{{ m.media_large|default:m.media_url }}" target="_blank" rel="noopener">
<img src="{{ m.media_large|default:m.media_url }}" alt="" class="w-full rounded-lg" loading="lazy">
</a>
<a class="bm-cell" href="{{ m.media_large|default:m.media_url }}" target="_blank" rel="noopener">
<img src="{{ m.media_large|default:m.media_url }}" alt="{{ m.alt_text|default:'' }}" loading="lazy">
</a>
{% elif m.type == 'video' or m.type == 'animated_gif' %}
{% with v=m.video %}
<a href="{{ bookmark.url|default:'#' }}" target="_blank" rel="noopener" class="relative block rounded-lg overflow-hidden">
<img src="{{ v.poster|default:m.media_thumb }}" alt="" class="w-full" loading="lazy">
<span class="absolute left-3 top-3 bg-black/60 text-white text-xs px-2 py-0.5 rounded">{% if m.type == 'animated_gif' %}GIF — open to play{% else %}VIDEO — open to play{% endif %}</span>
<span class="absolute right-3 top-3 bg-blue-500 text-white text-xs px-2 py-0.5 rounded font-bold">X</span>
</a>
<a class="bm-cell" href="{{ bookmark.url|default:'#' }}" target="_blank" rel="noopener" title="{% trans 'Open on X to play' %}">
<span class="bm-vbadge">{% if m.type == 'animated_gif' %}GIF{% else %}VIDEO{% endif %}</span>
<img src="{{ v.poster|default:m.media_thumb|default:m.media_large|default:m.media_url }}" alt="" loading="lazy">
<span class="bm-play"><span class="ring"><svg width="22" height="22" fill="#fff" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg></span></span>
{% if v.duration_ms %}<span class="bm-dur">{{ v.duration_ms|ms_to_duration }}</span>{% endif %}
<span class="bm-xbadge">X</span>
</a>
{% endwith %}
{% endif %}
{% endfor %}
</div>
{% endif %}
<dl class="mt-4 text-sm text-gray-600 grid grid-cols-2 sm:grid-cols-5 gap-2">
{% if bookmark.reply_count %}<div><dt class="text-gray-400">Replies</dt><dd>{{ bookmark.reply_count }}</dd></div>{% endif %}
{% if bookmark.retweet_count %}<div><dt class="text-gray-400">Retweets</dt><dd>{{ bookmark.retweet_count }}</dd></div>{% endif %}
{% if bookmark.favorite_count %}<div><dt class="text-gray-400">Likes</dt><dd>{{ bookmark.favorite_count }}</dd></div>{% endif %}
{% if bookmark.bookmark_count %}<div><dt class="text-gray-400">Bookmarks</dt><dd>{{ bookmark.bookmark_count }}</dd></div>{% endif %}
{% if bookmark.view_count %}<div><dt class="text-gray-400">Views</dt><dd>{{ bookmark.view_count }}</dd></div>{% endif %}
</dl>
</article>
{% if bookmark.has_card and bookmark.card_data %}
{% with c=bookmark.card_data %}
{% if c.title or c.description or c.image %}
<a class="bm-cardlink" href="{{ c.url|default:'#' }}" target="_blank" rel="noopener">
{% if c.image %}<img src="{{ c.image }}" loading="lazy" alt="">{% endif %}
<div class="ci">
<div class="csite">{{ c.site|default:'' }}</div>
{% if c.title %}<div class="ct">{{ c.title }}</div>{% endif %}
{% if c.description %}<div class="cd">{{ c.description }}</div>{% endif %}
</div>
</a>
{% endif %}
{% endwith %}
{% endif %}
{% if bookmark.is_quote and bookmark.quoted_data %}
{% with q=bookmark.quoted_data %}
{% if q.tombstone or q.unavailable %}
<div class="bm-quote">{% trans "This post is unavailable." %}</div>
{% else %}
{% with qa=q.author %}
<a class="bm-quote" href="{{ q.url|default:'https://x.com/i/status/'|add:q.id }}" target="_blank" rel="noopener">
<div class="qhead">
{% if qa.profile_image_url %}<img src="{{ qa.profile_image_url }}" loading="lazy" alt="">{% endif %}
<span class="name">{{ qa.name|default:'' }}</span>
<span class="handle">@{{ qa.screen_name|default:'' }}</span>
</div>
<div class="qtext">{{ q.text|default:''|linebreaksbr }}</div>
</a>
{% endwith %}
{% endif %}
{% endwith %}
{% endif %}
<!-- Metrics -->
<div class="bookmark-detail-metrics">
{% if bookmark.reply_count %}<span class="bookmark-metric">💬 {{ bookmark.reply_count }} <span class="metric-label">{% trans "Replies" %}</span></span>{% endif %}
{% if bookmark.retweet_count %}<span class="bookmark-metric">🔁 {{ bookmark.retweet_count }} <span class="metric-label">{% trans "Retweets" %}</span></span>{% endif %}
{% if bookmark.favorite_count %}<span class="bookmark-metric">❤ {{ bookmark.favorite_count }} <span class="metric-label">{% trans "Likes" %}</span></span>{% endif %}
{% if bookmark.bookmark_count %}<span class="bookmark-metric">🔖 {{ bookmark.bookmark_count }} <span class="metric-label">{% trans "Bookmarks" %}</span></span>{% endif %}
{% if bookmark.view_count %}<span class="bookmark-metric">📊 {{ bookmark.view_count }} <span class="metric-label">{% trans "Views" %}</span></span>{% endif %}
</div>
<!-- Timestamps -->
<div class="post-timestamps">
{% if bookmark.tweet_created_at %}
<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="timestamp-label">{% trans "Posted" %}:</span>
<time datetime="{{ bookmark.tweet_created_at|date:'Y-m-d H:i' }}">{{ bookmark.tweet_created_at|date:"Y-m-d H:i" }}</time>
</div>
{% endif %}
<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="M5 8h14M5 8a2 2 0 01-2-2V5a2 2 0 012-2h14a2 2 0 012 2v1a2 2 0 01-2 2M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8"/>
</svg>
<span class="timestamp-label">{% trans "Bookmarked" %}:</span>
<time datetime="{{ bookmark.created_at|date:'Y-m-d H:i' }}">{{ bookmark.created_at|date:"Y-m-d H:i" }}</time>
</div>
</div>
</div>
</div>
{% endblock %}
{% endblock %}
{% block extra_css %}
<style>
/* ============================================================
Apple-inspired design system — Bookmark detail page
Mirrors the post detail page: #f5f5f7 canvas, SF typography,
soft shadows, pill buttons, generous rounded cards, 80rem page.
============================================================ */
:root {
--apple-bg: #f5f5f7;
--apple-text: #1d1d1f;
--apple-gray: #6e6e73;
--apple-blue: #0071e3;
--apple-blue-hover: #0077ed;
--apple-red: #ff3b30;
--apple-green: #1e7b34;
--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);
}
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;
}
/* page shell — same max-width as the post-detail layout (#post-toc-layout: 80rem) */
#post-toc-layout { max-width: 80rem; margin: 0.5rem auto; padding: 0.5rem; }
.main-content {
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.5rem);
max-width: 44rem;
margin: 0 auto;
}
.back-link {
display: inline-flex; align-items: center; gap: 0.4rem;
font-size: 0.85rem; font-weight: 600; color: var(--apple-gray);
text-decoration: none; margin-bottom: 1.25rem;
}
.back-link svg { width: 1rem; height: 1rem; }
.back-link:hover { color: var(--apple-text); }
.post-header-row { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 0.85rem; margin-bottom: 1.1rem; }
.post-actions { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; flex-wrap: wrap; }
.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-blue { background: rgba(0, 113, 227, 0.10); color: var(--apple-blue); }
.action-btn-blue:hover { background: rgba(0, 113, 227, 0.18); }
.bookmark-author { display: flex; align-items: center; gap: 0.7rem; min-width: 0; }
.bookmark-avatar { width: 3rem; height: 3rem; border-radius: 50%; object-fit: cover; border: 1px solid var(--apple-separator); }
.bookmark-name { font-weight: 700; color: var(--apple-text); text-decoration: none; }
.bookmark-name:hover { text-decoration: underline; }
.bookmark-handle { font-size: 0.85rem; color: var(--apple-gray); }
.bookmark-detail-text {
font-size: 1.05rem; line-height: 1.55; color: var(--apple-text);
white-space: pre-wrap; word-wrap: break-word; unicode-bidi: plaintext;
margin-top: 0.75rem;
}
.bookmark-detail-text a { color: var(--apple-blue); text-decoration: none; }
.bookmark-detail-text a:hover { text-decoration: underline; }
.bookmark-detail-summary {
margin-top: 0.85rem; font-size: 0.9rem; color: var(--apple-green);
background: rgba(48, 209, 88, 0.10); border: 1px solid rgba(48, 209, 88, 0.25);
border-radius: var(--apple-radius-sm); padding: 0.6rem 0.85rem;
}
/* media grid (photos/video) */
.bm-media { margin-top: 1.1rem; display: grid; gap: 4px; }
.bm-media.one { grid-template-columns: 1fr; }
.bm-media.two { grid-template-columns: 1fr 1fr; }
.bm-media.three { grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr; }
.bm-media.three .bm-cell:nth-child(1) { grid-row: 1 / span 2; }
.bm-media.four { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.bm-cell { position: relative; overflow: hidden; border-radius: var(--apple-radius-sm); cursor: pointer;
background: #0d1b27; min-height: 160px; display: block; text-decoration: none; }
.bm-cell img { width: 100%; height: 100%; object-fit: cover; display: block; transition: filter 0.2s; }
.bm-cell:hover img { filter: brightness(0.92); }
.bm-vbadge { position: absolute; left: 10px; top: 10px; background: rgba(0, 0, 0, 0.65); color: #fff;
font-size: 10.5px; padding: 2px 8px; border-radius: 6px; pointer-events: none; font-weight: 600; }
.bm-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
.bm-play .ring { width: 54px; height: 54px; border-radius: 50%; background: rgba(0, 0, 0, 0.55);
display: flex; align-items: center; justify-content: center; color: #fff; backdrop-filter: blur(2px); }
.bm-dur { position: absolute; right: 8px; bottom: 8px; background: rgba(0, 0, 0, 0.7); color: #fff;
font-size: 11px; padding: 1px 6px; border-radius: 4px; pointer-events: none; font-weight: 600; }
.bm-xbadge { position: absolute; right: 8px; top: 8px; background: rgba(0, 113, 227, 0.92); color: #fff;
font-size: 10px; padding: 1px 7px; border-radius: 5px; font-weight: 700; pointer-events: none; }
/* link preview card */
.bm-cardlink { border: 1px solid var(--apple-separator); border-radius: var(--apple-radius-sm); overflow: hidden; display: flex;
text-decoration: none; color: var(--apple-text); margin-top: 1.1rem; transition: border-color 0.15s, background 0.15s; }
.bm-cardlink:hover { border-color: rgba(0, 0, 0, 0.10); background: var(--apple-bg); }
.bm-cardlink .ci { flex: 1; padding: 0.7rem 0.85rem; min-width: 0; }
.bm-cardlink .csite { color: var(--apple-gray); font-size: 0.75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bm-cardlink .ct { font-weight: 700; margin: 3px 0 2px; font-size: 0.95rem; line-height: 1.25; }
.bm-cardlink .cd { color: var(--apple-gray); font-size: 0.82rem; line-height: 1.35;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.bm-cardlink img { width: 140px; max-height: 200px; object-fit: cover; flex: 0 0 140px; }
/* quoted post */
.bm-quote { margin-top: 1.1rem; border: 1px solid var(--apple-separator); border-radius: var(--apple-radius-sm); padding: 0.85rem 0.9rem;
cursor: pointer; background: var(--apple-bg); text-decoration: none; display: block; transition: border-color 0.15s, background 0.15s; }
.bm-quote:hover { border-color: rgba(0, 0, 0, 0.10); background: #ececef; }
.bm-quote .qhead { display: flex; gap: 8px; align-items: center; font-size: 0.88rem; color: var(--apple-text); }
.bm-quote .qhead img { width: 22px; height: 22px; border-radius: 50%; border: 1px solid var(--apple-separator); }
.bm-quote .qhead .name { font-weight: 700; }
.bm-quote .qhead .handle { color: var(--apple-gray); font-size: 0.82rem; }
.bm-quote .qtext { font-size: 0.92rem; margin-top: 0.55rem; white-space: pre-wrap; unicode-bidi: plaintext;
line-height: 1.45; color: var(--apple-text); }
/* metrics */
.bookmark-detail-metrics { margin-top: 1.25rem; display: flex; flex-wrap: wrap; gap: 0.5rem; padding-top: 1rem; border-top: 1px solid var(--apple-separator); }
.bookmark-metric { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.85rem; color: var(--apple-text);
background: var(--apple-bg); border-radius: 980px; padding: 0.4rem 0.85rem; font-weight: 600; }
.bookmark-metric .metric-label { font-weight: 500; color: var(--apple-gray); }
/* timestamps */
.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; margin-top: 1.25rem; }
.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; }
</style>
{% endblock %}
+70 -53
View File
@@ -6,52 +6,63 @@
{% block content %}
<style>
:root{
--bm-bg:#f6f8fa; --bm-card:#ffffff; --bm-bd:#e3e8ed; --bm-bd2:#d7dde3;
--bm-txt:#0f1419; --bm-txt2:#3d434c; --bm-muted:#697384; --bm-muted2:#8a94a3;
--bm-accent:#1d9bf0; --bm-accent2:#0a84dc; --bm-soft:#e8f3fd;
--bm-radius:18px; --bm-shadow:0 1px 3px rgba(15,20,25,.06),0 1px 2px rgba(15,20,25,.04);
--bm-shadow-hover:0 4px 14px rgba(15,20,25,.08),0 2px 6px rgba(15,20,25,.05);
--bm-bg:#f5f5f7; --bm-card:#ffffff; --bm-bd:rgba(0,0,0,.06); --bm-bd2:rgba(0,0,0,.10);
--bm-txt:#1d1d1f; --bm-txt2:#3d434c; --bm-muted:#6e6e73; --bm-muted2:#8a8a8e;
--bm-accent:#0071e3; --bm-accent2:#0077ed; --bm-soft:rgba(0,113,227,.10);
--bm-radius:22px; --bm-shadow:0 4px 24px rgba(0,0,0,.05);
--bm-shadow-hover:0 18px 44px rgba(0,0,0,.10);
}
#bm-page{max-width:680px;margin:0 auto;padding:14px 14px 100px}
/* sub-header scrolls with content (no longer sticky) */
body{
background-color: var(--bm-bg) !important;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
"Helvetica Neue", "Segoe UI", Roboto, Arial, sans-serif;
color: var(--bm-txt);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
/* shell max-width matches the post-detail layout (#post-toc-layout: 80rem) */
#bm-shell{max-width:80rem;margin:0.5rem auto;padding:0.5rem 0.75rem 100px}
#bm-page{padding:0}
#bm-header{
position:relative;
background:rgba(255,255,255,.88); backdrop-filter:saturate(180%) blur(12px);
-webkit-backdrop-filter:saturate(180%) blur(12px);
border:1px solid var(--bm-bd); border-radius:var(--bm-radius);
padding:13px 16px 11px; margin:0 14px 14px;
padding:16px 22px 14px; margin:0 0 14px;
box-shadow:var(--bm-shadow);
}
#bm-header .htop{display:flex;flex-direction:column;gap:10px}
#bm-header .brand-row{display:flex;align-items:center;gap:9px}
#bm-header h1{margin:0;font-size:18px;font-weight:800;letter-spacing:-.2px;color:var(--bm-txt)}
#bm-header h1{margin:0;font-size:1.3rem;font-weight:700;letter-spacing:-.02em;color:var(--bm-txt)}
#bm-header .logo{
width:30px;height:30px;border-radius:50%;background:#0f1419;color:#fff;
display:flex;align-items:center;justify-content:center;font-weight:800;
width:30px;height:30px;border-radius:50%;background:var(--bm-txt);color:#fff;
display:flex;align-items:center;justify-content:center;font-weight:700;
font-size:15px;font-family:Georgia,serif;
}
#bm-header .count-sub{margin-left:auto;font-size:12.5px;color:var(--bm-muted);font-weight:500}
.bm-search-wrap{position:relative;flex:1}
.bm-search-wrap{position:relative;flex:1;max-width:32rem}
.bm-search-wrap svg{position:absolute;left:13px;top:50%;transform:translateY(-50%);color:var(--bm-muted2)}
#bm-q{width:100%;background:var(--bm-bg);border:1px solid transparent;color:var(--bm-txt);
border-radius:999px;padding:9px 16px 9px 38px;font-size:14.5px;outline:none;transition:.18s}
border-radius:980px;padding:9px 16px 9px 38px;font-size:14.5px;outline:none;transition:.18s}
#bm-q:focus{background:#fff;border-color:var(--bm-accent);box-shadow:0 0 0 3px var(--bm-soft)}
.bm-filters{display:flex;gap:6px;flex-wrap:wrap}
.bm-chip{
display:inline-flex;align-items:center;gap:6px;background:#fff;border:1px solid var(--bm-bd);
color:var(--bm-muted);border-radius:999px;padding:6px 12px;font-size:13px;font-weight:600;
color:var(--bm-muted);border-radius:980px;padding:6px 12px;font-size:13px;font-weight:600;
cursor:pointer;user-select:none;transition:.15s;white-space:nowrap;
}
.bm-chip:hover{background:var(--bm-bg);color:var(--bm-txt2);border-color:var(--bm-bd2)}
.bm-chip.active{background:var(--bm-accent);border-color:var(--bm-accent);color:#fff;box-shadow:var(--bm-shadow)}
.bm-chip.active{background:var(--bm-accent);border-color:var(--bm-accent);color:#fff;box-shadow:0 6px 18px rgba(0,113,227,.28)}
.bm-chip .n{opacity:.65;font-weight:600;margin-left:2px}
.bm-chip.active .n{opacity:.9}
#bm-meta{font-size:12px;color:var(--bm-muted);padding:6px 2px 0}
#bm-meta{font-size:12px;color:var(--bm-muted);padding:10px 4px 0}
/* feed column keeps a readable width, centered within the wider shell */
#bm-list{max-width:44rem;margin:0 auto}
/* cards */
.bm-tweet{
background:var(--bm-card);border:1px solid var(--bm-bd);border-radius:var(--bm-radius);
padding:16px 18px;margin-bottom:14px;box-shadow:var(--bm-shadow);
content-visibility:auto;contain-intrinsic-size:0 420px;transition:box-shadow .18s,border-color .18s;
padding:18px 20px;margin-bottom:14px;box-shadow:var(--bm-shadow);
content-visibility:auto;contain-intrinsic-size:0 420px;transition:box-shadow .2s ease,border-color .2s ease;
}
.bm-tweet:hover{box-shadow:var(--bm-shadow-hover);border-color:var(--bm-bd2)}
.bm-tweet a{color:var(--bm-accent);text-decoration:none}
@@ -67,10 +78,10 @@
.bm-ext:hover{color:var(--bm-accent)}
.bm-text{font-size:16px;line-height:1.5;margin:11px 0 0;white-space:pre-wrap;
word-wrap:break-word;unicode-bidi:plaintext;color:var(--bm-txt)}
.bm-summary{margin-top:9px;font-size:13.5px;color:#067647;background:#ecfdf5;
border:1px solid #d1fae5;border-radius:8px;padding:6px 10px}
.bm-summary{margin-top:9px;font-size:13.5px;color:#1e7b34;background:rgba(48,209,88,.10);
border:1px solid rgba(48,209,88,.25);border-radius:10px;padding:6px 10px}
/* media */
.bm-media{margin-top:13px;display:grid;gap:3px}
.bm-media{margin-top:13px;display:grid;gap:4px}
.bm-media.one{grid-template-columns:1fr}
.bm-media.two{grid-template-columns:1fr 1fr}
.bm-media.three{grid-template-columns:2fr 1fr;grid-template-rows:1fr 1fr}
@@ -87,7 +98,7 @@
display:flex;align-items:center;justify-content:center;color:#fff;backdrop-filter:blur(2px)}
.bm-dur{position:absolute;right:8px;bottom:8px;background:rgba(0,0,0,.7);color:#fff;
font-size:11px;padding:1px 6px;border-radius:4px;pointer-events:none;font-weight:600}
.bm-xbadge{position:absolute;right:8px;top:8px;background:rgba(29,155,240,.92);color:#fff;
.bm-xbadge{position:absolute;right:8px;top:8px;background:rgba(0,113,227,.92);color:#fff;
font-size:10px;padding:1px 7px;border-radius:5px;font-weight:700;pointer-events:none}
/* card link */
.bm-cardlink{border:1px solid var(--bm-bd);border-radius:14px;overflow:hidden;display:flex;
@@ -101,8 +112,8 @@
.bm-cardlink img{width:140px;max-height:200px;object-fit:cover;flex:0 0 140px}
/* quote */
.bm-quote{margin-top:13px;border:1px solid var(--bm-bd);border-radius:14px;padding:13px 14px;
cursor:pointer;background:#f7f9fb;transition:border-color .15s,background .15s}
.bm-quote:hover{border-color:var(--bm-bd2);background:#eef2f6}
cursor:pointer;background:var(--bm-bg);transition:border-color .15s,background .15s}
.bm-quote:hover{border-color:var(--bm-bd2);background:#ececef}
.bm-quote .qhead{display:flex;gap:8px;align-items:center;font-size:14px}
.bm-quote .qhead img{width:22px;height:22px;border-radius:50%;border:1px solid var(--bm-bd)}
.bm-quote .qhead .name{font-weight:700}
@@ -121,44 +132,50 @@
/* to-top */
#bm-totop{position:fixed;right:22px;bottom:22px;width:44px;height:44px;border-radius:50%;
background:var(--bm-accent);color:#fff;border:none;cursor:pointer;z-index:60;
box-shadow:0 4px 14px rgba(29,155,240,.4);display:none;align-items:center;justify-content:center;
transition:transform .15s}
box-shadow:0 6px 18px rgba(0,113,227,.32);display:none;align-items:center;justify-content:center;
transition:transform .15s,background .15s}
#bm-totop:hover{transform:translateY(-2px);background:var(--bm-accent2)}
#bm-totop.show{display:flex}
/* lightbox */
#bm-lightbox{position:fixed;inset:0;background:rgba(13,27,39,.94);display:none;
align-items:center;justify-content:center;z-index:100;cursor:zoom-out;padding:30px}
#bm-lightbox img{max-width:96vw;max-height:94vh;border-radius:10px;
box-shadow:0 10px 40px rgba(0,0,0,.5)}
#bm-lightbox img{max-width:96vw;max-height:94vh;border-radius:14px;
box-shadow:0 18px 44px rgba(0,0,0,.5)}
@media (max-width:640px){
#bm-header{padding:14px 16px 12px}
.bm-search-wrap{max-width:none}
}
</style>
<!-- sticky sub-header -->
<div id="bm-header">
<div class="htop">
<div class="brand-row">
<div class="logo">X</div>
<h1>{% trans "Bookmarks Archive" %}</h1>
<span class="count-sub" id="bm-count">{{ stats.total }} {% trans "bookmarks" %}</span>
</div>
<div class="bm-search-wrap">
<svg width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<input id="bm-q" type="search" placeholder="{% trans 'Search text, author, handle…' %}" autocomplete="off">
</div>
<div class="bm-filters" id="bm-filters">
<button class="bm-chip active" data-f="all">{% trans "All" %}<span class="n">{{ stats.total }}</span></button>
<button class="bm-chip" data-f="photo">{% trans "Photos" %}<span class="n">{{ stats.has_photo }}</span></button>
<button class="bm-chip" data-f="video">{% trans "Videos" %}<span class="n">{{ stats.has_video }}</span></button>
<button class="bm-chip" data-f="text">{% trans "Text" %}<span class="n"></span></button>
<button class="bm-chip" data-f="quote">{% trans "Quotes" %}<span class="n">{{ stats.is_quote }}</span></button>
<button class="bm-chip" data-f="card">{% trans "Links" %}<span class="n">{{ stats.has_card }}</span></button>
<!-- shell max-width mirrors post-detail's #post-toc-layout for a consistent app-wide page width -->
<div id="bm-shell">
<div id="bm-header">
<div class="htop">
<div class="brand-row">
<div class="logo">X</div>
<h1>{% trans "Bookmarks Archive" %}</h1>
<span class="count-sub" id="bm-count">{{ stats.total }} {% trans "bookmarks" %}</span>
</div>
<div class="bm-search-wrap">
<svg width="17" height="17" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
<input id="bm-q" type="search" placeholder="{% trans 'Search text, author, handle…' %}" autocomplete="off">
</div>
<div class="bm-filters" id="bm-filters">
<button class="bm-chip active" data-f="all">{% trans "All" %}<span class="n">{{ stats.total }}</span></button>
<button class="bm-chip" data-f="photo">{% trans "Photos" %}<span class="n">{{ stats.has_photo }}</span></button>
<button class="bm-chip" data-f="video">{% trans "Videos" %}<span class="n">{{ stats.has_video }}</span></button>
<button class="bm-chip" data-f="text">{% trans "Text" %}<span class="n"></span></button>
<button class="bm-chip" data-f="quote">{% trans "Quotes" %}<span class="n">{{ stats.is_quote }}</span></button>
<button class="bm-chip" data-f="card">{% trans "Links" %}<span class="n">{{ stats.has_card }}</span></button>
</div>
</div>
</div>
</div>
<div id="bm-page">
<div id="bm-meta"></div>
<div id="bm-list">
{% include "links/includes/bookmark_list_items.html" %}
<div id="bm-page">
<div id="bm-meta"></div>
<div id="bm-list">
{% include "links/includes/bookmark_list_items.html" %}
</div>
</div>
</div>
@@ -235,4 +252,4 @@ window.bmLightbox = function(el){
b.onclick = ()=>window.scrollTo({top:0,behavior:'smooth'});
})();
</script>
{% endblock %}
{% endblock %}