1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
{{- $url := .Get 0 -}}
{{- $parsed := urls.Parse $url -}}
{{- $parts := split $parsed.Path "/" -}}
{{- $id := index $parts (sub (len $parts) 1) -}}
{{- $instance := printf "%s://%s" $parsed.Scheme $parsed.Host -}}
{{- $apiUrl := printf "%s/api/v1/statuses/%s" $instance $id -}}
{{- $data := resources.GetRemote $apiUrl -}}
{{- $json := $data | transform.Unmarshal -}}
{{- $author := $json.account -}}
{{- $publishedAt := time $json.created_at -}}
{{- $avatarSrc := $author.avatar -}}
{{- with resources.GetRemote $author.avatar -}}
{{- with .Resize "88x88 q85 webp" -}}
{{- $avatarSrc = .RelPermalink -}}
{{- end -}}
{{- end -}}
{{- /* Count image attachments */ -}}
{{- $imgCount := 0 -}}
{{- range $json.media_attachments -}}{{- if eq .type "image" -}}{{- $imgCount = add $imgCount 1 -}}{{- end -}}{{- end -}}
{{- $cId := printf "sc-%s" (md5 $url) -}}
{{- $dId := printf "sd-%s" (md5 $url) -}}
<div class="social-card-final cnt-undo rd bg-[var(--block-background-color)] p-4 md:px-8 md:py-6 relative overflow-hidden" style="background-color: oklch(from var(--block-background-color) l c h / calc(alpha - 0.5))">
<div style="display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 8px;">
<div style="display: flex; align-items: center; gap: 12px;">
<img src="{{ $avatarSrc }}" style="width: 44px; height: 44px; border-radius: 50%; flex-shrink: 0; object-fit: cover;" />
<div style="display: flex; flex-direction: column; line-height: 1.2;">
<span style="font-weight: 700; color: var(--color-neso-fg1); font-size: 1rem;">{{ $author.display_name }}</span>
<span style="color: var(--color-neso-fg3); font-size: 0.9rem;">@{{ $author.acct }}</span>
</div>
</div>
<a href="{{ $url }}" target="_blank" style="color: #6364ff; align-self: flex-start; margin-top: 2px; transition: transform 0.2s ease-in-out; display: flex; flex-shrink: 0;" onmouseover="this.style.transform='scale(1.2)'" onmouseout="this.style.transform='scale(1)'">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 196.52 196.52">
<path fill="#a730b8" d="M47.9242 72.7966a18.2278 18.2278 0 0 1-7.7959 7.7597l42.7984 42.9653 10.3182-5.2291zm56.4524 56.6704-10.3182 5.2291 21.686 21.7708a18.2278 18.2278 0 0 1 7.7975-7.7608z"/>
<path fill="#5496be" d="M129.6645 102.0765l1.7865 11.4272 27.4149-13.8942a18.2278 18.2278 0 0 1-4.9719-9.8124zm-14.0658 7.1282-57.2891 29.0339a18.2278 18.2278 0 0 1 4.9728 9.8133l54.1027-27.4194z"/>
<path fill="#ce3d1a" d="M69.5312 91.6539l8.1618 8.1933 29.269-57.1387a18.2278 18.2278 0 0 1-9.787-5.0219zm-7.1897 14.0363-14.0022 27.3353a18.2278 18.2278 0 0 1 9.786 5.0214l12.3775-24.1639z"/>
<path fill="#d0188f" d="M39.8906 80.6763a18.2278 18.2278 0 0 1-10.8655 1.7198l8.1762 52.2981a18.2278 18.2278 0 0 1 10.8645-1.7198z"/>
<path fill="#5b36e9" d="M63.3259 148.3109a18.2278 18.2278 0 0 1-1.7322 10.8629l52.2893 8.3907a18.2278 18.2278 0 0 1 1.7322-10.8629z"/>
<path fill="#30b873" d="M134.9148 146.9182a18.2278 18.2278 0 0 1 9.788 5.0224l24.1345-47.117a18.2278 18.2278 0 0 1-9.7875-5.0229z"/>
<path fill="#ebe305" d="M126.1329 33.1603a18.2278 18.2278 0 0 1-7.7975 7.7608l37.3765 37.5207a18.2278 18.2278 0 0 1 7.7969-7.7608z"/>
<path fill="#f47601" d="M44.7704 51.6279a18.2278 18.2278 0 0 1 4.9723 9.8123l47.2478-23.9453a18.2278 18.2278 0 0 1-4.9718-9.8113z"/>
<path fill="#57c115" d="M118.2491 40.9645a18.2278 18.2278 0 0 1-10.8511 1.8123l4.1853 26.8 11.42 1.8324zm-4.2333 44.1927 9.8955 63.3631a18.2278 18.2278 0 0 1 10.88-1.6278l-9.355-59.9035z"/>
<path fill="#dbb210" d="M49.7763 61.6412a18.2278 18.2278 0 0 1-1.694 10.8686l26.8206 4.3077 5.2715-10.2945zm45.9677 7.382-5.272 10.2955 63.3713 10.1777a18.2278 18.2278 0 0 1 1.7606-10.8593z"/>
<path fill="#ffca00" d="M93.4385 23.8419a1 1 0 1 0 33.0924 1.8025 1 1 0 1 0-33.0924-1.8025"/>
<path fill="#64ff00" d="M155.314 85.957a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/>
<path fill="#00a3ff" d="M115.3466 163.9824a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/>
<path fill="#9500ff" d="M28.7698 150.0898a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/>
<path fill="#ff0000" d="M15.2298 63.4781a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/>
</svg>
</a>
</div>
{{- $bodyText := $json.content -}}
{{- $bodyText = $bodyText | replaceRE `(<p>[\s]*(<a [^>]*class="mention hashtag"[^>]*>#<span>[^<]+</span></a>[\s]*)+</p>[\s]*)+$` "" -}}
{{- $bodyText = $bodyText | replaceRE `<span class="invisible">[^<]*</span>` "" -}}
{{- $bodyText = $bodyText | replaceRE `<a ` `<a style="text-decoration:none;" ` -}}
{{- $bodyText = $bodyText | replaceRE `<p>` `<p style="margin:0 0 0.9em 0;">` -}}
{{- $bodyText = $bodyText | replaceRE `<p [^>]+>[\s]*</p>` "" -}}
<div style="font-size: 1.05rem; line-height: 1.6; margin-bottom: 12px; color: var(--color-neso-fg1);">
{{ $bodyText | safeHTML }}
</div>
{{- if gt $imgCount 0 -}}
{{- if le $imgCount 2 -}}
{{- /* 1–2 images: flex row (2 images split 50/50, cropped) */ -}}
<div style="display: flex; gap: 8px; margin-top: 8px; margin-bottom: 12px; overflow: hidden;">
{{- range $json.media_attachments -}}
{{- if eq .type "image" -}}
{{- $imgSrc := .preview_url -}}
{{- with resources.GetRemote .preview_url -}}
{{- with .Process "q85 webp" -}}
{{- $imgSrc = .RelPermalink -}}
{{- end -}}
{{- end -}}
<img src="{{ $imgSrc }}" alt="{{ .description }}" onclick="window.__scLb&&window.__scLb(this.src)" style="flex: 1 1 0%; min-width: 0; border-radius: 8px; display: block; margin: 0 !important; cursor: pointer;{{ if gt $imgCount 1 }} height: 250px; object-fit: cover;{{ else }} height: auto; width: 100%;{{ end }}" loading="lazy">
{{- end -}}
{{- end -}}
</div>
{{- else -}}
{{- /* 3+ images: scroll-snap carousel */ -}}
<style>#{{ $cId }}::-webkit-scrollbar{display:none}</style>
<div style="margin: 8px 0 4px; overflow: hidden;">
<div id="{{ $cId }}" style="display: flex; overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; scrollbar-width: none; border-radius: 8px;">
{{- range $json.media_attachments -}}
{{- if eq .type "image" -}}
{{- $imgSrc := .preview_url -}}
{{- with resources.GetRemote .preview_url -}}
{{- with .Process "q85 webp" -}}
{{- $imgSrc = .RelPermalink -}}
{{- end -}}
{{- end -}}
<img src="{{ $imgSrc }}" alt="{{ .description }}" onclick="window.__scLb&&window.__scLb(this.src)" style="scroll-snap-align: start; flex-shrink: 0; width: 100%; height: 280px; object-fit: cover; border-radius: 8px; cursor: pointer; display: block; margin: 0 !important;" loading="lazy">
{{- end -}}
{{- end -}}
</div>
{{- $imgAttachments := slice -}}
{{- range $json.media_attachments -}}{{- if eq .type "image" -}}{{- $imgAttachments = $imgAttachments | append . -}}{{- end -}}{{- end -}}
<div id="{{ $dId }}" style="display: flex; justify-content: center; gap: 6px; margin-top: 8px; margin-bottom: 12px;">
{{- range $i, $_ := $imgAttachments -}}
<button onclick="(function(){var c=document.getElementById('{{ $cId }}');c.scrollTo({left:c.children[{{ $i }}].offsetLeft,behavior:'smooth'});})()" aria-label="Image {{ add $i 1 }}" style="width: 8px; height: 8px; border-radius: 50%; border: none; background: {{ if eq $i 0 }}var(--color-neso-fg2){{ else }}var(--color-neso-deco2){{ end }}; cursor: pointer; padding: 0; transition: background 0.2s;"></button>
{{- end -}}
</div>
</div>
<script>(function(){var c=document.getElementById('{{ $cId }}'),ds=document.getElementById('{{ $dId }}').querySelectorAll('button');c.addEventListener('scroll',function(){var i=Math.round(c.scrollLeft/c.offsetWidth);ds.forEach(function(d,j){d.style.background=j===i?'var(--color-neso-fg2)':'var(--color-neso-deco2)';});},{passive:true});})()</script>
{{- end -}}
{{- end -}}
{{- /* Link preview card — use original image URL, skip processing (CDN images can return 403) */ -}}
{{- with $json.card -}}
{{- if or (eq .type "link") (eq .type "photo") (eq .type "video") -}}
<a href="{{ .url }}" target="_blank" rel="noopener noreferrer" style="display: block; margin-top: 8px; margin-bottom: 16px; border: 1px solid var(--color-neso-deco2); border-radius: 10px; overflow: hidden; text-decoration: none; color: inherit;">
{{- if .image -}}
<img src="{{ .image }}" alt="{{ .title }}" style="width: 100%; height: 180px; object-fit: cover; display: block; margin: 0 !important;">
{{- end -}}
<div style="padding: 10px 14px;">
<div style="font-weight: 600; font-size: 0.95rem; color: var(--color-neso-fg1); margin-bottom: 3px;">{{ .title }}</div>
{{- if .description -}}<div style="font-size: 0.85rem; color: var(--color-neso-fg3); margin-bottom: 4px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;">{{ .description }}</div>{{- end -}}
<div style="font-size: 0.8rem; color: var(--color-neso-fg3);">{{ (urls.Parse .url).Host }}</div>
</div>
</a>
{{- end -}}
{{- end -}}
<div style="border-top: 1px solid var(--color-neso-deco3); padding-top: 12px; margin-top: 16px; display: flex; justify-content: space-between; align-items: center;">
<time class="social-card-time" datetime="{{ $publishedAt.Format "2006-01-02T15:04:05Z" }}" style="color: var(--color-neso-fg3); font-size: 0.9rem;">{{ $publishedAt.Format "Jan 2, 2006 at 3:04 PM UTC" }}</time>
<a href="{{ $url }}" target="_blank" style="color: #6364ff; font-weight: 600; text-decoration: none; font-size: 0.9rem; transition: color 0.2s ease-in-out;" onmouseover="this.style.color='#8A8BFF'" onmouseout="this.style.color='#6364ff'">{{ T "ViewOnFediverse" . }}</a>
</div>
</div>
{{- /* Shared lightbox — injected once per page via DOM API (no innerHTML) */ -}}
<script>
if (!window.__scLb) {
var _lbImg = document.createElement('img');
_lbImg.style.cssText = 'max-width:92vw;max-height:92vh;object-fit:contain;border-radius:8px;cursor:default;';
_lbImg.addEventListener('click', function(e) { e.stopPropagation(); });
var _lbOv = document.createElement('div');
_lbOv.style.cssText = 'display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.88);cursor:zoom-out;align-items:center;justify-content:center;';
_lbOv.appendChild(_lbImg);
_lbOv.addEventListener('click', function() { _lbOv.style.display = 'none'; document.body.style.overflow = ''; });
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { _lbOv.style.display = 'none'; document.body.style.overflow = ''; } });
document.body.appendChild(_lbOv);
window.__scLb = function(src) { _lbImg.src = src; _lbOv.style.display = 'flex'; document.body.style.overflow = 'hidden'; };
}
</script>
|