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&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&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&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&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.