PageSourceSearch

https://www.shellen.com/2025/12/22/methaphone/

html shellen.com collected 2026-10-03 19:48:52 UTC 41,908 bytes, 767 lines download raw bytes

1<!doctype html>
2<html lang="en">
3    <head>
4        <meta charset="utf-8">
5        <meta name="viewport" content="width=device-width, initial-scale=1.0">
6        <title>methaphone</title>
7        <meta name="description" content="Videos about the strangely viral methaphone were viewed more than 200 million times, and now you can buy one. Such fortune! methaphone looks like a simple acrylic slab — and it is. But it’s also a stand-in, a totem, and an alibi. It’s a step on the road to freedom.">
8        <link rel="stylesheet" href="/style.css"> <!-- Local fonts and icons -->
9        <link rel="stylesheet" href="/css/fontawesome.css">
10        <link rel="stylesheet" href="/css/fonts.css">
11        <meta name="generator" content="Eleventy v3.1.2"> <!-- RSS Feed -->
12        <link rel="alternate" type="application/atom+xml" title="shellen dot com" href="https://www.shellen.com/feed.xml"> <!-- SEO and Open Graph meta tags -->
13        <meta charset="utf-8">
14        <meta http-equiv="X-UA-Compatible" content="IE=edge">
15        <meta name="viewport" content="width=device-width, initial-scale=1">
16        <title>methaphone</title>
17        <meta name="title" content="methaphone">
18        <meta name="author" content="Jason Shellen">
19        <meta name="description" content="Videos about the strangely viral methaphone were viewed more than 200 million times, and now you can buy one. Such fortune! methaphone looks like a simple acrylic slab — and it is. But it’s also a stand-in, a totem, and an alibi. It’s a step on the road to freedom.">
20        <meta property="og:type" content="website">
21        <meta property="og:url" content="https://www.shellen.com/2025/12/22/methaphone/">
22        <meta property="og:locale" content="en_US">
23        <meta property="og:title" content="methaphone">
24        <meta property="og:description" content="Videos about the strangely viral methaphone were viewed more than 200 million times, and now you can buy one. Such fortune! methaphone looks like a simple acrylic slab — and it is. But it’s also a stand-in, a totem, and an alibi. It’s a step on the road to freedom.">
25        
26        <meta property="og:image" content="https://methaphone.com/cdn/shop/files/metaphone_logo-4x-black-cropped.png?v=1762109343&amp;width=600">
27        
28        
29        <meta name="twitter:card" content="summary_large_image">
30        <meta name="twitter:url" content="https://www.shellen.com/2025/12/22/methaphone/">
31        <meta name="twitter:title" content="methaphone">
32        <meta name="twitter:description" content="Videos about the strangely viral methaphone were viewed more than 200 million times, and now you can buy one. Such fortune! methaphone looks like a simple acrylic slab — and it is. But it’s also a stand-in, a totem, and an alibi. It’s a step on the road to freedom.">
33        <meta name="twitter:image" content="https://methaphone.com/cdn/shop/files/metaphone_logo-4x-black-cropped.png?v=1762109343&amp;width=600">
34        <link rel="canonical" href="https://www.shellen.com/2025/12/22/methaphone/">
35        <meta property="og:site_name" content="shellen dot com"> <!-- IndieWeb and Micropub -->
36        <link rel="micropub" href="https://www.shellen.com/.netlify/functions/micropub">
37        <link rel="micropub-media" href="https://www.shellen.com/.netlify/functions/micropub-media">
38        <link rel="authorization_endpoint" href="https://www.shellen.com/.netlify/functions/indieauth-auth">
39        <link rel="token_endpoint" href="https://www.shellen.com/.netlify/functions/indieauth-token"> <!-- Webmention -->
40        <link rel="webmention" href="https://webmention.io/www.shellen.com/webmention">
41        <link rel="pingback" href="https://webmention.io/www.shellen.com/xmlrpc"> <!-- rel=me links for identity verification -->
42        <link rel="me" href="https://bsky.app/profile/shellen.com">
43        <link rel="me" href="https://github.com/shellen">
44        <link rel="me" href="https://linkedin.com/in/jasonshellen">
45        <!-- Social Interactions - Must load before Alpine -->
46        
46<script src="/js/social-interactions.js"></script>
46
47        <!-- Alpine.js -->
48        
48<script defer="" src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
48
49        <!-- Link Unfurling -->
50        
50<script src="/js/link-unfurl.js"></script>
50
51        <!-- Post Navigation (keyboard shortcuts) -->
52        
52<script src="/js/post-navigation.js"></script>
52
53        <!-- Simple Analytics -->
54        
54<script async="" defer="" src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
54
55        <noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt="" referrerpolicy="no-referrer-when-downgrade"></noscript>
56
57        <!-- Netlify Identity Widget for Decap CMS -->
58        
58<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
58
59
60        <style>
61            [x-cloak] {
62                display: none !important;
63            }
64            :root {
65                --post-max-width: 48rem; /* 3xl = 48rem, 2xl = 42rem */
66                --overlay-text: #ffffff;
67                --grid-line: rgba(43, 69, 95, 1);
68                --gradient-start: rgb(25, 25, 25);
69                --gradient-end: rgb(0, 0, 0);
70            }
71            .post-width {
72                max-width: var(--post-max-width);
73            }
74            /* Background: dotted grid + gradient overlay */
75            .dots-theme {
76                background-color: transparent;
77                background-image: radial-gradient(circle, var(--grid-line) 1px, transparent 1px), radial-gradient(circle, var(--grid-line) 1px, transparent 1px), linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
78                background-size: 20px 20px, 20px 20px, cover;
79                background-position: 0 0, 10px 10px, 0 0;
80                background-attachment: fixed;
81            }
82            /* Apply gray background on small screens and up */
83            @media(min-width: 640px) {
84                .dots-theme {
85                    background-color: rgb(243 244 246); /* bg-gray-100 */
86                }
87            }
88            /* Remove background on small screens */
89            @media(max-width: 639px) {
90                .dots-theme {
91                    background-color: white;
92                    background-image: none;
93                }
94            }
95        </style>
96        
97    </head>
98    <body class="overflow-x-hidden">
99        
100    <div class="mx-auto min-h-screen post-width bg-white" style="font-family: 'DM Sans', sans-serif;" x-data="{ ...shareModal(), ...subscribeModal() }" x-init="shareModal().init(); subscribeModal().init()">
101
102        <!-- Page Header Component -->
103<nav x-data="{
104        show: true,
105        lastScroll: 0,
106        init() {
107            window.addEventListener('scroll', () => {
108                const currentScroll = window.pageYOffset;
109                if (currentScroll <= 0) {
110                    this.show = true;
111                    return;
112                }
113                if (currentScroll > this.lastScroll && currentScroll > 100) {
114                    // Scrolling down
115                    this.show = false;
116                } else {
117                    // Scrolling up
118                    this.show = true;
119                }
120                this.lastScroll = currentScroll;
121            });
122        }
123    }" :class="show ? 'translate-y-0' : '-translate-y-full'" class="fixed top-0 left-0 right-0 z-50 bg-white transition-transform duration-300 ease-in-out">
124    <div class="mx-auto post-width px-6 pt-6 pb-3 flex items-center justify-between">
125        <div class="flex items-center gap-4">
126            <a href="/" class="flex items-center gap-2 font-mono text-sm text-gray-600 transition-colors hover:text-gray-900">
127                <i class="fas fa-arrow-left text-xs"></i>
128                <span>Home</span>
129            </a>
130            <span class="text-gray-300">|</span>
131            <a href="/" class="font-mono text-sm text-gray-900 transition-colors hover:text-sky-600">
132                SHELLEN DOT COM
133            </a>
134        </div>
135        <button @click="!hasSubscribed && openSubscribeModal()" :class="hasSubscribed ? 'text-sm text-sky-700 border-sky-600 rounded-lg px-3 py-1.5 cursor-default' : 'text-sm text-gray-600 hover:text-white hover:bg-sky-500 transition-all border border-gray-200 hover:border-sky-500 rounded-lg px-3 py-1.5'">
136            <span x-text="hasSubscribed ? 'Subscribed' : 'Subscribe'"></span>
137        </button>
138    </div>
139</nav>
140
141<!-- Spacer to prevent content jump -->
142<div class="h-[73px]"></div>
143
144
145        <!-- Bookmark Post (Social Media Style) -->
146        <article class="mx-auto post-width px-6 py-4" data-sb-object-id="src/content/bookmarks/2025-12-22-methaphone.md">
147
148            <!-- Author Info -->
149            <div class="flex items-center gap-3 mb-4">
150                <div class="h-12 w-12 overflow-hidden rounded-full ring-2 ring-gray-200">
151                    <img src="https://cdn.shellen.com/images/authors/shellen.png" alt="Jason Shellen avatar" class="h-full w-full object-cover">
152                </div>
153                <div>
154                    <p class="font-semibold text-gray-900">Jason Shellen</p>
155                    <p class="text-sm text-gray-500">
156                        <time datetime="2025-12-22">22 December 2025</time>
157                        
158                        
159                        <span class="mx-1">•</span>
160                        <span><i class="fas fa-bookmark mr-1 text-xs"></i>Bookmark</span>
161                    </p>
162                </div>
163            </div>
164
165            <!-- Bookmark Content -->
166            
167                <div class="prose prose-base max-w-none mb-4" data-sb-field-path="markdown_content">
168                    <p>Leave your phone without the cravings or withdrawal</p>
169
170                </div>
171            
172
173            <!-- Bookmark Link Card with Cached Thumbnail -->
174            
175                <a href="https://methaphone.com" target="_blank" rel="noopener noreferrer" class="block mb-4 no-underline">
176                    <div class="border rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-shadow duration-200">
177                        <div class="flex">
178                            <!-- Thumbnail Column -->
179                            <div class="w-40 h-40 flex-shrink-0 bg-slate-100 flex items-center justify-center relative overflow-hidden">
180                                
181                                
182                                    <img src="https://methaphone.com/cdn/shop/files/metaphone_logo-4x-black-cropped.png?v=1762109343&amp;width=600" alt="" class="absolute inset-0 w-full h-full object-cover" loading="lazy" onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
183                                    <i class="fas fa-link text-slate-400 text-2xl" style="display: none;"></i>
184                                
185                            </div>
186                            <!-- Content Column -->
187                            <div class="p-4 flex-1 min-w-0">
188                                <h3 class="text-base font-semibold text-slate-900 line-clamp-2 mb-1" id="methaphone">
189                                    
190                                        methaphone
191                                    
192                                </h3>
193                                <p class="text-sm text-slate-600 line-clamp-2 mb-2">
194                                    
195                                        Videos about the strangely viral methaphone were viewed more than 200 million times, and now you can buy one. Such fortune! methaphone looks like a simple acrylic slab — and it is. But it’s also a stand-in, a totem, and an alibi. It’s a step on the road to freedom.
196                                    
197                                </p>
198                                <div class="flex items-center gap-2 text-xs text-slate-400">
199                                    
200                                        <span class="truncate">methaphone.com</span>
201                                    
202                                    <i class="fas fa-external-link-alt"></i>
203                                </div>
204                            </div>
205                        </div>
206                    </div>
207                </a>
208            
209
210            <!-- Bookmark Stats/Actions -->
211            <div class="flex items-center justify-between pt-3">
212                <div class="flex items-center gap-6">
213                    
214                    
215
216
217<div class="social-likes" x-data="socialLikes('https://www.shellen.com/2025/12/22/methaphone/')" x-init="init()">
218
219  <!-- Like Button and Count -->
220  <div class="flex items-center gap-2">
221    <!-- Heart button - click to like -->
222    <button @click="likePost()" class="text-gray-500 hover:text-red-500 transition-colors group relative" :disabled="loading" :class="userHasLiked ? 'text-red-500' : ''" :aria-label="userHasLiked ? 'Unlike this post' : 'Like this post'" :aria-pressed="userHasLiked ? 'true' : 'false'">
223      <i class="fas fa-heart text-lg" :class="loading ? 'animate-pulse' : ''" aria-hidden="true"></i>
224    </button>
225
226    <!-- Count button - click to see who liked -->
227    <button @click="openModal()" class="text-sm font-medium text-gray-500 hover:text-gray-700 transition-colors" :disabled="loading" x-show="likeCount > 0" :aria-label="'View ' + likeCount + ' ' + (likeCount === 1 ? 'like' : 'likes')">
228      <span x-text="likeCount" aria-live="polite"></span>
229    </button>
230  </div>
231
232  <!-- Like Action Modal (Choose Platform) -->
233  <div x-show="likeModalOpen" x-cloak="" @click.away="closeLikeModal()" @keydown.escape.window="closeLikeModal()" role="dialog" aria-modal="true" aria-labelledby="like-modal-title" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50" style="display: none;">
234
235    <div class="bg-white rounded-lg shadow-xl max-w-sm w-full" @click.stop="">
236      <!-- Modal Header -->
237      <div class="flex items-center justify-between p-4 border-b border-gray-200">
238        <h3 id="like-modal-title" class="text-lg font-semibold text-gray-900">Like this post</h3>
239        <button @click="closeLikeModal()" class="text-gray-400 hover:text-gray-600 transition-colors" aria-label="Close modal">
240          <i class="fas fa-times text-xl" aria-hidden="true"></i>
241        </button>
242      </div>
243
244      <!-- Modal Body -->
245      <div class="p-6">
246        <p class="text-sm text-gray-600 mb-4">To like this post, share it on your social network. When others like your post, those likes will appear here:</p>
247        <div class="space-y-3">
248          <a :href="getBlueskyLikeUrl()" target="_blank" rel="noopener noreferrer" @click="closeLikeModal()" class="flex items-center justify-center gap-2 w-full px-4 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors font-medium">
249            <i class="fab fa-bluesky"></i>
250            <span>Share on Bluesky</span>
251          </a>
252          <button @click="showMastodonInput = true" x-show="!showMastodonInput" class="flex items-center justify-center gap-2 w-full px-4 py-3 bg-purple-500 text-white rounded-lg hover:bg-purple-600 transition-colors font-medium">
253            <i class="fab fa-mastodon"></i>
254            <span>Share on Mastodon</span>
255          </button>
256        </div>
257
258        <!-- Mastodon Input Section -->
259        <div x-show="showMastodonInput" x-transition="" class="mt-4" style="display: none;">
260          <div class="mb-3">
261            <label class="block text-sm font-medium text-gray-700 mb-2">
262              <i class="fab fa-mastodon"></i> Mastodon Instance
263            </label>
264            <input type="text" x-model="mastodonInstance" placeholder="e.g., mastodon.social, fosstodon.org" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:border-purple-400 focus:outline-none text-sm" @keydown.enter="openMastodonShare()">
265            <p class="mt-1 text-xs text-gray-500">Enter your Mastodon instance to share</p>
266          </div>
267          <div class="flex gap-2">
268            <button @click="openMastodonShare()" class="flex-1 px-4 py-2 bg-purple-600 text-white hover:bg-purple-700 rounded-lg transition-colors font-medium text-sm">
269              Share
270            </button>
271            <button @click="showMastodonInput = false" class="px-4 py-2 border border-gray-300 hover:border-gray-400 rounded-lg transition-colors text-gray-700 text-sm">
272              Cancel
273            </button>
274          </div>
275        </div>
276
277        <div class="mt-4 p-3 bg-blue-50 rounded-lg">
278          <p class="text-xs text-gray-700">
279            <strong>How it works:</strong> Post the link, and when people like/favorite your post on Bluesky or Mastodon, their likes will be federated back here via webmention.
280          </p>
281        </div>
282      </div>
283    </div>
284  </div>
285
286  <!-- Likes Modal (Who Liked) -->
287  <div x-show="modalOpen" x-cloak="" @click.away="closeModal()" @keydown.escape.window="closeModal()" role="dialog" aria-modal="true" aria-labelledby="likes-modal-title" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50" style="display: none;">
288
289    <div class="bg-white rounded-lg shadow-xl max-w-md w-full max-h-[80vh] overflow-hidden" @click.stop="">
290
291      <!-- Modal Header -->
292      <div class="flex items-center justify-between p-4 border-b border-gray-200">
293        <h3 id="likes-modal-title" class="text-lg font-semibold text-gray-900">
294          Liked by <span x-text="likeCount"></span> <span x-text="likeCount === 1 ? 'person' : 'people'"></span>
295        </h3>
296        <button @click="closeModal()" class="text-gray-400 hover:text-gray-600 transition-colors" aria-label="Close modal">
297          <i class="fas fa-times text-xl" aria-hidden="true"></i>
298        </button>
299      </div>
300
301      <!-- Modal Body - List of Likers -->
302      <div class="overflow-y-auto max-h-[60vh]">
303        <!-- Loading State -->
304        <div x-show="loadingDetails" class="text-center py-8">
305          <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
306          <p class="mt-2 text-sm text-gray-500">Loading...</p>
307        </div>
308
309        <!-- Likers List -->
310        <div x-show="!loadingDetails && likeCount > 0" class="divide-y divide-gray-100">
311          <template x-for="like in likes" :key="like.id">
312            <div class="flex items-center gap-3 p-4 hover:bg-gray-50 transition-colors">
313              <!-- Avatar -->
314              <img :src="like.avatar" :alt="like.author" class="w-12 h-12 rounded-full flex-shrink-0" onerror="this.src='https://www.gravatar.com/avatar/?d=mp&s=96'">
315
316              <!-- Author Info -->
317              <div class="flex-1 min-w-0">
318                <a :href="like.authorUrl" target="_blank" rel="noopener noreferrer" class="font-semibold text-gray-900 hover:text-blue-600 block truncate">
319                  <span x-text="like.author"></span>
320                </a>
321                <p class="text-sm text-gray-500 truncate" x-text="like.source"></p>
322              </div>
323
324              <!-- Platform Icon -->
325              <div>
326                <span class="inline-flex items-center justify-center w-8 h-8 rounded-full" :class="getPlatformBadgeClass(like.platform)">
327                  <i :class="getPlatformIcon(like.platform)" class="text-sm"></i>
328                </span>
329              </div>
330            </div>
331          </template>
332        </div>
333
334        <!-- Empty State -->
335        <div x-show="!loadingDetails && likeCount === 0" class="text-center py-8">
336          <i class="fas fa-heart text-gray-300 text-4xl mb-3"></i>
337          <p class="text-gray-500">Be the first to like this post!</p>
338        </div>
339      </div>
340    </div>
341  </div>
342</div>
343
344
345                    <button @click="openShareModal('/2025/12/22/methaphone/', 'methaphone')" class="flex items-center gap-2 text-gray-500 hover:text-green-500 transition-colors">
346                        <i class="fa-regular fa-paper-plane text-lg"></i>
347                    </button>
348                </div>
349                <div class="text-xs text-gray-400 font-mono">
350                    <a href="/2025/12/22/methaphone/" class="hover:text-gray-600">
351                        2025-12-22
352                    </a>
353                </div>
354            </div>
355        </article>
356                <!-- Next/Previous Navigation -->
357                <nav class="mx-auto mt-8 post-width px-6">
358                    <div class="grid grid-cols-1 gap-6 md:grid-cols-2">
359                            <div class="group">
360                                <a href="/2025/11/21/antic-bikes-future-motion/" data-nav="next" title="21 November 2025" class="block rounded-lg border border-gray-200 p-4 transition-all hover:border-blue-300 hover:shadow-md">
361                                    <div class="flex items-center justify-end gap-2 text-sm text-gray-500 mb-2">
362                                        <span class="text-xs opacity-0 group-hover:opacity-100 transition-opacity">21 November 2025</span>
363                                        <span class="font-mono uppercase tracking-wide text-xs">Next</span>
364                                        <i class="fas fa-arrow-right text-xs"></i>
365                                    </div>
366                                    
367                                        <h3 class="text-base font-semibold text-gray-900 group-hover:text-blue-600 transition-colors text-right" id="antic-bikes-future-motion">Antic Bikes // Future Motion</h3>
368                                    
369                                </a>
370                            </div>
371                        
372                    </div>
373                </nav>
374
375        <!-- Social Comments Section -->
376        <section class="mx-auto mt-8 post-width px-6 pb-12">
377            
378            
379            
380            
381
382
383
384
385
386<div id="comments" class="social-comments mt-8" x-data="socialComments('https://www.shellen.com/2025/12/22/methaphone/', 'https://www.shellen.com')" x-init="init()" x-show="!loading" style="display: none;">
387
388  <div class="border-t border-gray-200 pt-6">
389    <!-- Error State -->
390    <div x-show="error" class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
391      <p class="text-sm text-red-800" x-text="error"></p>
392    </div>
393
394    <!-- Comments List - shown when comments exist -->
395    <div x-show="comments.length > 0">
396      <h3 class="mb-4 text-lg font-semibold text-gray-900" id="comments-2">Comments</h3>
397      <div class="space-y-6">
398        <template x-for="comment in comments" :key="comment.id">
399          <article class="flex gap-4">
400            <!-- Avatar -->
401            <div class="flex-shrink-0">
402              <img :src="comment.avatar" :alt="comment.author" class="w-12 h-12 rounded-full" onerror="this.src='https://www.gravatar.com/avatar/?d=mp&s=96'">
403            </div>
404
405            <!-- Comment Content -->
406            <div class="flex-1 min-w-0">
407              <div class="flex items-baseline gap-2 mb-1">
408                <a :href="comment.authorUrl" target="_blank" rel="noopener noreferrer" class="font-semibold text-gray-900 hover:text-blue-600">
409                  <span x-text="comment.author"></span>
410                </a>
411                <span class="text-xs text-gray-500" x-text="comment.handle"></span>
412                <span class="text-xs text-gray-400">•</span>
413                <a :href="comment.url" target="_blank" rel="noopener noreferrer" class="text-xs text-gray-500 hover:text-gray-700">
414                  <time :datetime="comment.timestamp" x-text="formatDate(comment.timestamp)"></time>
415                </a>
416              </div>
417
418              <div class="prose prose-sm max-w-none text-gray-700" x-html="comment.content"></div>
419
420              <!-- Platform Badge -->
421              <div class="mt-2 flex items-center gap-2">
422                <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-gray-100 text-gray-700">
423                  <i :class="comment.platform === 'bluesky' ? 'fab fa-bluesky' : 'fab fa-mastodon'"></i>
424                  <span x-text="comment.platform === 'bluesky' ? 'Bluesky' : 'Mastodon'"></span>
425                </span>
426              </div>
427            </div>
428          </article>
429        </template>
430      </div>
431    </div>
432
433    <!-- Empty State - only show comment prompts if post is less than 6 months old -->
434    <div x-show="comments.length === 0 && !error">
435      
436      <h3 class="mb-2 text-lg font-semibold text-gray-900" id="comments-3">Comments</h3>
437      <p class="text-gray-600 text-sm mb-3">Share this post on Bluesky or Mastodon to start a conversation. Replies will appear here automatically.</p>
438      <!-- Test Button (only in local dev) -->
439      <button x-show="showTestButton" @click="addTestComment()" class="mb-3 px-3 py-1.5 bg-yellow-100 text-yellow-800 border border-yellow-300 rounded text-xs hover:bg-yellow-200">
440        🧪 Add Test Comments (Dev Only)
441      </button>
442      <div class="flex flex-wrap gap-3 text-sm">
443        <a :href="getBlueskyReplyUrl()" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1.5 text-gray-600 hover:text-gray-900">
444          <i class="fab fa-bluesky"></i>
445          <span>Post on Bluesky</span>
446        </a>
447        <button @click="showMastodonReplyInput = true" x-show="!showMastodonReplyInput" class="inline-flex items-center gap-1.5 text-gray-600 hover:text-gray-900">
448          <i class="fab fa-mastodon"></i>
449          <span>Post on Mastodon</span>
450        </button>
451      </div>
452      <!-- Mastodon Input -->
453      <div x-show="showMastodonReplyInput" x-transition="" class="mt-3 p-3 bg-gray-50 rounded-lg" style="display: none;">
454        <input type="text" x-model="mastodonReplyInstance" placeholder="e.g., mastodon.social, fosstodon.org" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:border-purple-400 focus:outline-none text-sm mb-2" @keydown.enter="openMastodonReply()">
455        <div class="flex gap-2">
456          <button @click="openMastodonReply()" class="px-3 py-1.5 bg-purple-600 text-white hover:bg-purple-700 rounded text-sm">
457            Post
458          </button>
459          <button @click="showMastodonReplyInput = false" class="px-3 py-1.5 text-gray-600 hover:text-gray-700 text-sm">
460            Cancel
461          </button>
462        </div>
463      </div>
464      
465    </div>
466  </div>
467</div>
468
469
470        </section>
471
472        <!-- Include Share Modal -->
473        <!-- Share Modal Component -->
474<div x-show="showShareModal" x-transition.opacity="" role="dialog" aria-modal="true" aria-labelledby="share-modal-title" class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50" style="display: none;">
475    <div class="bg-gray-900 rounded-lg p-6 max-w-md w-full mx-4" @click.away="closeShareModal()">
476        <div class="flex justify-between items-center mb-6">
477            <h2 id="share-modal-title" class="text-white text-xl font-bold uppercase tracking-tight">Share this link</h2>
478            <button @click="closeShareModal()" class="text-gray-400 hover:text-white" aria-label="Close share modal">
479                <i class="fas fa-times text-xl" aria-hidden="true"></i>
480            </button>
481        </div>
482
483        <!-- Two Column Layout -->
484        <div class="grid grid-cols-2 gap-3" role="group" aria-label="Share options">
485            <!-- Copy Link -->
486            <button @click="copyToClipboard()" aria-label="Copy link to clipboard" class="flex items-center space-x-3 p-4 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-left group">
487                <i class="fas fa-copy text-white group-hover:text-gray-300 text-lg transition-colors" aria-hidden="true"></i>
488                <span class="text-white font-medium">Copy Link</span>
489            </button>
490
491            <!-- LinkedIn -->
492            <button @click="shareToLinkedIn()" aria-label="Share on LinkedIn" class="flex items-center space-x-3 p-4 bg-gray-700 hover:bg-blue-700 rounded-lg transition-colors text-left group">
493                <i class="fa-brands fa-linkedin text-white group-hover:text-blue-300 text-lg transition-colors" aria-hidden="true"></i>
494                <span class="text-white font-medium">LinkedIn</span>
495            </button>
496
497            <!-- Bluesky -->
498            <button @click="shareToBluesky()" aria-label="Share on Bluesky" class="flex items-center space-x-3 p-4 bg-gray-700 hover:bg-blue-600 rounded-lg transition-colors text-left group">
499                <i class="fa-brands fa-bluesky text-white group-hover:text-blue-400 text-lg transition-colors" aria-hidden="true"></i>
500                <span class="text-white font-medium">Bluesky</span>
501            </button>
502
503            <!-- Mastodon - Initially looks like others, expands when clicked -->
504            <button @click="showMastodonInput = true" x-show="!showMastodonInput" aria-label="Share on Mastodon" class="flex items-center space-x-3 p-4 bg-gray-700 hover:bg-purple-600 rounded-lg transition-colors text-left group">
505                <i class="fa-brands fa-mastodon text-white group-hover:text-purple-400 text-lg transition-colors" aria-hidden="true"></i>
506                <span class="text-white font-medium">Mastodon</span>
507            </button>
508
509            <!-- Facebook -->
510            <button @click="shareToFacebook()" aria-label="Share on Facebook" class="flex items-center space-x-3 p-4 bg-gray-700 hover:bg-blue-600 rounded-lg transition-colors text-left group">
511                <i class="fa-brands fa-facebook text-white group-hover:text-blue-400 text-lg transition-colors" aria-hidden="true"></i>
512                <span class="text-white font-medium">Facebook</span>
513            </button>
514
515            <!-- Messages (iOS/macOS only) -->
516            <button x-show="isAppleDevice" @click="shareToMessages()" aria-label="Share via Messages" class="flex items-center space-x-3 p-4 bg-gray-700 hover:bg-green-600 rounded-lg transition-colors text-left group">
517                <i class="fa-regular fa-comment text-white group-hover:text-green-300 text-lg transition-colors" aria-hidden="true"></i>
518                <span class="text-white font-medium">Message</span>
519            </button>
520
521            <!-- Threads -->
522            <button @click="shareToThreads()" aria-label="Share on Threads" class="flex items-center space-x-3 p-4 bg-gray-700 hover:bg-black rounded-lg transition-colors text-left group">
523                <i class="fa-brands fa-threads text-white group-hover:text-white text-lg transition-colors" aria-hidden="true"></i>
524                <span class="text-white font-medium">Threads</span>
525            </button>
526
527            <!-- More Options (Web Share API) -->
528            <button x-show="hasNativeShare" @click="shareNatively()" aria-label="More sharing options" class="flex items-center space-x-3 p-4 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-left">
529                <i class="fa-regular fa-paper-plane text-gray-300 text-lg" aria-hidden="true"></i>
530                <span class="text-white font-medium">More Options</span>
531            </button>
532        </div>
533
534        <!-- Mastodon Input Section (Revealed when clicked) -->
535        <div x-show="showMastodonInput" x-transition="" class="mt-6 pt-4 border-t border-gray-600" style="display: none;">
536            <div class="mb-3">
537                <div class="flex items-center gap-2 mb-2">
538                    <i class="fa-brands fa-mastodon text-white text-lg"></i>
539                    <label class="text-sm font-medium text-white font-mono">MASTODON INSTANCE</label>
540                </div>
541                <input type="text" x-model="mastodonInstance" placeholder="e.g., xoxo.zone, mastodon.social" class="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none text-sm text-white placeholder-gray-400" x-ref="mastodonInput">
542                <p class="mt-1 text-xs text-gray-400">Enter your Mastodon instance URL to share this post</p>
543            </div>
544            <div class="flex gap-2">
545                <button @click="shareToMastodon()" class="flex-1 flex items-center justify-center gap-2 p-3 bg-purple-600 text-white hover:bg-purple-700 rounded-lg transition-colors">
546                    <span class="font-medium text-sm">Share</span>
547                </button>
548                <button @click="showMastodonInput = false" class="px-3 py-3 border border-gray-600 hover:border-gray-500 rounded-lg transition-colors text-gray-300">
549                    <span class="text-sm">Cancel</span>
550                </button>
551            </div>
552        </div>
553    </div>
554</div>
555
556<!-- Share Modal JavaScript -->
557<script>
558    document.addEventListener('alpine:init', () => {
559        Alpine.data('shareModal', () => ({
560            showShareModal: false,
561            shareUrl: '',
562            shareTitle: '',
563            showMastodonInput: false,
564            mastodonInstance: 'xoxo.zone',
565            isAppleDevice: false,
566            hasNativeShare: false,
567
568            init() {
569                // Detect device type
570                this.isAppleDevice = /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
571                this.hasNativeShare = 'share' in navigator;
572            },
573
574            openShareModal(url, title = '') {
575                this.shareUrl = url.startsWith('http') ? url : window.location.origin + url;
576                this.shareTitle = title;
577                this.showShareModal = true;
578            },
579
580            closeShareModal() {
581                this.showShareModal = false;
582                this.showMastodonInput = false;
583                this.shareUrl = '';
584                this.shareTitle = '';
585            },
586
587            async copyToClipboard() {
588                try {
589                    await navigator.clipboard.writeText(this.shareUrl);
590                    this.showShareSuccessMessage('Link copied to clipboard!');
591                } catch (err) {
592                    // Fallback for older browsers
593                    const textArea = document.createElement('textarea');
594                    textArea.value = this.shareUrl;
595                    document.body.appendChild(textArea);
596                    textArea.select();
597                    document.execCommand('copy');
598                    document.body.removeChild(textArea);
599                    this.showShareSuccessMessage('Link copied to clipboard!');
600                }
601            },
602
603            shareToLinkedIn() {
604                const url = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(this.shareUrl)}`;
605                window.open(url, '_blank', 'width=600,height=400');
606            },
607
608            shareToBluesky() {
609                const text = this.shareTitle ? `${this.shareTitle} ${this.shareUrl}` : this.shareUrl;
610                const url = `https://bsky.app/intent/compose?text=${encodeURIComponent(text)}`;
611                window.open(url, '_blank', 'width=600,height=400');
612            },
613
614            shareToMastodon() {
615                if (!this.mastodonInstance.trim()) {
616                    alert('Please enter a Mastodon instance (e.g., xoxo.zone)');
617                    return;
618                }
619                const text = this.shareTitle ? `${this.shareTitle} ${this.shareUrl}` : this.shareUrl;
620                const instance = this.mastodonInstance.trim().replace(/^https?:\/\//, '');
621                const url = `https://${instance}/share?text=${encodeURIComponent(text)}`;
622                window.open(url, '_blank', 'width=600,height=400');
623                this.closeShareModal();
624            },
625
626            shareToFacebook() {
627                const url = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(this.shareUrl)}`;
628                window.open(url, '_blank', 'width=600,height=400');
629            },
630
631            shareToMessages() {
632                if (this.isAppleDevice) {
633                    // iOS/macOS Messages app
634                    const body = this.shareTitle ? `${this.shareTitle} ${this.shareUrl}` : this.shareUrl;
635                    window.location.href = `sms:&body=${encodeURIComponent(body)}`;
636                }
637            },
638
639            shareToThreads() {
640                const text = this.shareTitle ? `${this.shareTitle} ${this.shareUrl}` : this.shareUrl;
641                const url = `https://www.threads.net/intent/post?text=${encodeURIComponent(text)}`;
642                window.open(url, '_blank', 'width=600,height=400');
643            },
644
645            shareNatively() {
646                if (navigator.share) {
647                    navigator.share({
648                        title: this.shareTitle || document.title,
649                        url: this.shareUrl
650                    }).catch(() => {
651                        // User canceled or error
652                    });
653                }
654            },
655
656            showShareSuccessMessage(message) {
657                // Create a temporary success message
658                const toast = document.createElement('div');
659                toast.className = 'fixed top-4 right-4 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg z-50';
660                toast.textContent = message;
661                document.body.appendChild(toast);
662
663                setTimeout(() => {
664                    toast.remove();
665                }, 2000);
666            }
667        }));
668    });
669</script>
669
670
671        <!-- Include Subscribe Modal -->
672        <!-- Subscribe Modal Component -->
673<div x-show="showSubscribeModal" x-transition.opacity="" class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50" style="display: none;">
674    <div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 p-6" @click.away="closeSubscribeModal()">
675        <div class="text-center">
676            <div class="mb-4">
677                <div class="mx-auto w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center mb-4">
678                    <i class="fas fa-bell text-white text-2xl"></i>
679                </div>
680                <h2 class="text-2xl font-bold text-gray-900 mb-2" id="stay-updated">Stay Updated</h2>
681                <p class="text-gray-600">Get notified when I publish new content about design, products, and technology.</p>
682            </div>
683            <form action="https://shellen.us7.list-manage.com/subscribe/post?u=1d5ae6da0d5950b955cb1a1b0&amp;id=43c78f8c62" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="space-y-4" target="_blank" novalidate="" @submit="handleSubscribe()">
684                <div>
685                    <input type="email" name="EMAIL" placeholder="Enter your email address" class="required email w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" id="mce-EMAIL" required="">
686                </div>
687
688                <!-- Mailchimp hidden fields -->
689                <div id="mce-responses" class="clear">
690                    <div class="response" id="mce-error-response" style="display:none"></div>
691                    <div class="response" id="mce-success-response" style="display:none"></div>
692                </div>
693                <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
694                <div style="position: absolute; left: -5000px;" aria-hidden="true">
695                    <input type="text" name="b_1d5ae6da0d5950b955cb1a1b0_43c78f8c62" tabindex="-1" value="">
696                </div>
697
698                <div class="flex gap-3">
699                    <button type="button" @click="closeSubscribeModal()" class="flex-1 px-4 py-3 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors">
700                        Cancel
701                    </button>
702                    <button type="submit" name="subscribe" id="mc-embedded-subscribe" class="flex-1 px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
703                        Subscribe
704                    </button>
705                </div>
706            </form>
707        </div>
708    </div>
709</div>
710
711<!-- Subscribe Modal JavaScript -->
712<script>
713    document.addEventListener('alpine:init', () => {
714        Alpine.data('subscribeModal', () => ({
715            showSubscribeModal: false,
716            hasSubscribed: false,
717
718            init() {
719                // Check if user has already subscribed (cookie check)
720                this.hasSubscribed = this.checkSubscribeCookie();
721            },
722
723            checkSubscribeCookie() {
724                return document.cookie.split('; ').find(row => row.startsWith('subscribed=')) !== undefined;
725            },
726
727            setSubscribeCookie() {
728                // Set cookie to expire in 1 year
729                const expiryDate = new Date();
730                expiryDate.setFullYear(expiryDate.getFullYear() + 1);
731                document.cookie = `subscribed=true; expires=${expiryDate.toUTCString()}; path=/; SameSite=Lax`;
732                this.hasSubscribed = true;
733            },
734
735            openSubscribeModal() {
736                this.showSubscribeModal = true;
737            },
738
739            closeSubscribeModal() {
740                this.showSubscribeModal = false;
741            },
742
743            handleSubscribe() {
744                // Set cookie when form is submitted
745                this.setSubscribeCookie();
746                setTimeout(() => this.closeSubscribeModal(), 100);
747            }
748        }));
749    });
750</script>
750
751    </div>
752
753
754        <!-- Netlify Identity redirect script -->
755        
755<script>
756          if (window.netlifyIdentity) {
757            window.netlifyIdentity.on("init", user => {
758              if (!user) {
759                window.netlifyIdentity.on("login", () => {
760                  document.location.href = "/admin/";
761                });
762              }
763            });
764          }
765        </script>
765
766    </body>
767</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.