#ScreenReaders
(imagine the 👏 emote between each word here i dont wanna fuck up screenreaders)
E621 STOP AUTO TAGGING MY CHARACTERS AS INTERSEX CHALLENGE !!!
November 4, 2026 at 2:13 AM
I'm gonna be writing some original and shared-universe fiction, does anyone know how I could poison it to AI scraping while still leaving it viable for screenreaders?

TIA
i mean the thing is that this guy ought to be sued into oblivion.

you should not be able to "my bad" your way out of copyright fraud on this scale. i'm sorry.

if it's going to continue being this easy to do, you have to make an example of people like this.
“In the time this site has been up, I’ve facilitated over 2,000 takedown requests. It was never my intention to hurt so many authors." Someone vibe coded AO3 by scraping and repackaging fanfiction’ works without permission. It has now been taken down.

Read now: www.404media.co/vibe-coded-a...
November 3, 2026 at 3:53 PM
I love your incisive, excoriating work.

Please include ALT in images you share so they are accessible to people who used screenreaders.

There is a handy prompt in settings so you always remember.

Everyone should be able to enjoy your art.
November 4, 2026 at 12:08 PM
*people who use screenreaders or otherwise need alt text for accessibility
October 29, 2026 at 5:25 PM
3. Complete shutting down of attempts to save the fic off-site: copy/paste shut down on the page, screenshotting disabled, no download button, while still allowing screenreaders and other accessibility features
4. Complete blocking of search engine indexing
5. Built-in tipping/comms features
November 1, 2026 at 4:06 AM
Surprised screenreaders haven't caught up, to be honest. The purpose of e.g. the 𝔣𝔯𝔞𝔨𝔱𝔲𝔯 block is ostensibly for math, but so many people use it decoratively that it should at least be a preference to read those like standard alphabetical letters. I'd be so easy to implement!
October 31, 2026 at 8:53 PM
Questions are prefaced Q0, Q1, Q2, etc. When you reply, preface posts with A0, A1, etc & include "#AutChat" so others see them!

Feel free to skip questions for any reason, or respond to them later.

Please capitalize the tag as #AutChat to make it more accessible to screenreaders.
November 1, 2026 at 9:00 PM
A status message users can see but screen readers can't hear isn't communication. WCAG 4.1.3 reminds us: make the dog bark.

tarnoff.info/2026/08...

#Accessibility #A11y #WCAG #ScreenReaders #ARIA #InclusiveDesign #WebAccessibility
A11y 101 – 4.1.3 Status Messages
In the Sherlock Holmes story “Silver Blaze,” the key clue is that a dog did nothing. The dog didn’t bark when an intruder entered. That silence was the evidence. Your website has …
tarnoff.info
November 4, 2026 at 7:41 PM
Please include ALT when you share images so they are accessible to people who use screenreaders.

There is a handy prompt in settings so you always remember.
October 29, 2026 at 2:24 PM
Please include ALT when you share images so they are accessible to people who use screenreaders.

There is a handy prompt in settings so you always remember.
October 29, 2026 at 2:23 PM
With alt text because you can't be anti-fascist and ableist at the same time [and because people who use screenreaders deserve accessibility, even if it's to access ridiculous bullshit like this] 🖕
October 29, 2026 at 5:21 PM
This is an important message. Please include ALT in the images you share so they are accessible to people who use screenreaders.

There is a handy prompt in settings so you always remember.
October 29, 2026 at 2:20 PM
I've almost surprised myself how far I've gotten in accessibility in my Euclid project even with my total current upset with how to do screenreaders lol. I have reduced motion, increased contrast, hover based focus, a built in magnifier with mouse or input lock, monochrome, 3 visual size adjustments
October 31, 2026 at 1:49 AM
Dynamic ARIA role changes often lag in screen readers due to async accessibility tree updates. Learn how focus and live regions trigger announcements for truly accessible apps. #Accessibility #ARIA #ScreenReaders #DynamicUI #WebDev https://blog.mspk.me/posts/how-screen-readers-interpret-and-expose-c
October 29, 2026 at 1:03 PM
#generativeai does not make anything accessible. Seriously, it does not. It makes webpages a nightmare for screenreaders, and the other options are often wonky, fail to work half the time, or riddled with bad code.

The world is hostile to disabled people, where we had to fight for accessibility […]
Original post on disabled.social
disabled.social
October 29, 2026 at 12:38 AM
Just added some new info to itvwrestling.co.uk/archive.html

(text version follows for screenreaders)

The 1969 show includes Thunder Sugiyama.
October 26, 2026 at 1:35 PM
Ich versuche es, aber es ist manchmal tatsächlich nicht einfach, manche Dinge zu erklären, zumal ich es kurz halten möchte. Ich persönlich hätte keinen Bock auf ewige Monologe eines Screenreaders. Und lustige Sachen sind nochmal schwerer...
Sind Emojis bzw Smileys aus Satzzeichen eigentlich nervig?
October 25, 2026 at 6:34 PM
Welkome toevoeging: je to-do's op volgorde plaatsen.

De lijst zou ook met screenreaders moeten werken, maar behalve met testen heb ik er geen ervaring mee. Dus feedback is heel erg welkom.

#todo #to-do #eu #beta #productivity #order #screenreader
0.6.0 - You can now change the order of to-dos in Next and Focus, by dragging (desktop and mobile), and keyboard (desktop).

Also tested for screenreader (feedback very welcome!)

Look at the changelog for more details kalondo.app/changelog

#todo #to-do #eu #beta #productivity #order #screenreader
October 25, 2026 at 4:57 PM
Questions are prefaced Q0, Q1, Q2, etc. When you reply, preface posts with A0, A1, etc & include "#AutChat" so others see them!

Feel free to skip questions for any reason, or respond to them later.

Please capitalize the tag as #AutChat to make it more accessible to screenreaders.
October 25, 2026 at 8:02 PM
Yes, I’m posting this from @sarahedo.bsky.social’s keynote.

She points out the positive consequences for accessibility - screenreaders can interact much more easily with websites via WebMCP than the DOM.
October 22, 2026 at 9:20 PM
Sucks that they don't have some kind of basic respect for accesibility or localization.

Maybe editing this line to add an SVG or <desc> tag would do it. I think screenreaders unpack SVGs to get at those.<br><br><a href="https://github.com/rails/rails/blob/2f2c00ba683ec7845d8fb195fb9d24ea00ec76a0/railties/lib/rails/generators/rails/app/templates/public/404.html#L126" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link" target="_blank" rel="noopener" data-link="bsky">github.com/rails/rails/...</a></div> <a href="https://github.com/rails/rails/blob/2f2c00ba683ec7845d8fb195fb9d24ea00ec76a0/railties/lib/rails/generators/rails/app/templates/public/404.html#L126" target="_blank" rel="noopener" class="block mt-2 border rounded overflow-hidden w-[calc(100%+3.5rem)] -ml-[3.5rem] no-card-link"> <div class="p-2"> <div class="text-sm font-semibold truncate">rails/railties/lib/rails/generators/rails/app/templates/public/404.html at 2f2c00ba683ec7845d8fb195fb9d24ea00ec76a0 · rails/rails</div> <div class="text-xs text-gray-500 truncate">Ruby on Rails. Contribute to rails/rails development by creating an account on GitHub.</div> </div> <div class="border-t px-2 py-2 text-sm text-gray-600 flex items-center gap-2"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 0 20"/><path d="M12 2a15.3 15.3 0 0 0 0 20"/></svg> <span class="truncate">github.com</span> </div> </a> <div class="mt-3 text-xs text-gray-500 flex items-center gap-2 whitespace-nowrap overflow-hidden w-[calc(100%+3.5rem)] -ml-[3.5rem]"> <span>October 23, 2026 at 5:00 PM</span> </div> <div class="border-t my-2 w-[calc(100%+3.5rem)] -ml-[3.5rem]"></div> <div class="mt-3 flex items-center justify-between text-gray-600 text-sm w-[calc(100%+3.5rem)] -ml-[3.5rem] no-card-link" data-controller="bluesky-interactions"> <div class="flex items-center justify-between w-[300px] pr-4 no-card-link"> <div class="hidden" data-post-text-uri="at://did:plc:xnx52zdoyuf777kntfughiwa/app.bsky.feed.post/3myknp4yc5s26"> Sucks that they don't have some kind of basic respect for accesibility or localization.<br><br>Maybe editing this line to add an SVG <title> or <desc> tag would do it. I think screenreaders unpack SVGs to get at those.<br><br><a href="https://github.com/rails/rails/blob/2f2c00ba683ec7845d8fb195fb9d24ea00ec76a0/railties/lib/rails/generators/rails/app/templates/public/404.html#L126" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link" target="_blank" rel="noopener" data-link="bsky">github.com/rails/rails/...</a> </div> <button type="button" class="flex items-center gap-1 hover:text-black no-card-link" data-action="click->bluesky-auth#requireConnection click->bluesky-interactions#reply" data-bluesky-interactions-reply-uri-param="at://did:plc:xnx52zdoyuf777kntfughiwa/app.bsky.feed.post/3myknp4yc5s26" data-bluesky-interactions-reply-cid-param="bafyreiaokhcrou4pdnideitgvuo33is2nz6y62bvozqiyevis73ytisu5e" data-bluesky-interactions-reply-root-uri-param="at://did:plc:u6raddo3hh67enk46yz4fsik/app.bsky.feed.post/3mykn3nkim22e" data-bluesky-interactions-reply-root-cid-param="bafyreieinqiehte4xng5maqbqiyo7ayzg7tk7ebi6h3n55ptchf5wqbxca" data-bluesky-interactions-reply-author-name-param="Mike Samuel 🟣" data-bluesky-interactions-reply-author-avatar-param="https://cdn.bsky.app/img/avatar/plain/did:plc:xnx52zdoyuf777kntfughiwa/bafkreifuzf4rbelpfdlfyvewwsln3uw3aeoplrmbwjdj3igu4cd4rxnpiq" data-bluesky-interactions-post-path-param="/bsky/mvsamuel.bsky.social/post/3myknp4yc5s26" title="Reply" style="pointer-events: auto; cursor: pointer; z-index: 10; position: relative;"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a4 4 0 0 1-4 4H7l-4 4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/></svg> <span data-bluesky-interactions-target="replyCount">1</span> </button> <div class="relative no-card-link" data-controller="bluesky-repost-menu" data-bluesky-repost-menu-uri-value="at://did:plc:xnx52zdoyuf777kntfughiwa/app.bsky.feed.post/3myknp4yc5s26" data-bluesky-repost-menu-cid-value="bafyreiaokhcrou4pdnideitgvuo33is2nz6y62bvozqiyevis73ytisu5e" data-bluesky-repost-menu-repost-uri-value="" data-bluesky-repost-menu-is-reposted-value="false" data-bluesky-repost-menu-author-handle-value="mvsamuel.bsky.social" data-bluesky-repost-menu-post-text-value="Sucks that they don't have some kind of basic respect for accesibility or localization. Maybe editing this line to add an SVG <title> or <desc> tag would do it. I think screenreaders unpack SVGs to get at those. github.com/rails/rails/..." style="pointer-events:auto; cursor:pointer;"> <button type="button" class="flex items-center gap-1 hover:text-black " data-action="click->bluesky-repost-menu#toggleMenu" data-bluesky-interactions-target="repostButton" title="Repost"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg> </button> <div class="hidden absolute left-0 bottom-full mb-3 w-48 rounded-2xl border border-white/10 bg-[#1C2333] text-white shadow-xl overflow-hidden z-50" data-bluesky-repost-menu-target="menu"> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left hidden" data-action="click->bluesky-auth#requireConnection click->bluesky-repost-menu#undoRepost"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg> <span>Undo repost</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left " data-action="click->bluesky-auth#requireConnection click->bluesky-repost-menu#repost"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg> <span>Repost</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-auth#requireConnection click->bluesky-repost-menu#quote"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> <span>Quote post</span> </button> </div> </div> <button type="button" class="flex items-center gap-1 hover:text-black no-card-link " data-action="click->bluesky-auth#requireConnection click->bluesky-interactions#toggleLike" data-bluesky-interactions-uri-param="at://did:plc:xnx52zdoyuf777kntfughiwa/app.bsky.feed.post/3myknp4yc5s26" data-bluesky-interactions-cid-param="bafyreiaokhcrou4pdnideitgvuo33is2nz6y62bvozqiyevis73ytisu5e" data-bluesky-interactions-like-uri-param="" data-bluesky-interactions-target="likeButton" title="Like"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 1 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z"/></svg> <span data-bluesky-interactions-target="likeCount">1</span> </button> </div> <div class="flex items-center gap-4 text-gray-500"> <button type="button" class="flex items-center hover:text-black no-card-link" data-controller="share" data-share-url-value="https://www.lightnews.app/bsky/mvsamuel.bsky.social/post/3myknp4yc5s26" data-share-title-value="Mike Samuel 🟣 on LightNews" data-share-text-value="Sucks that they don't have some kind of basic respect for accesibility or localization. Maybe editing this line to add an SVG <title> or <de…" data-action="click->share#shareSystem" title="Share" style="pointer-events:auto; cursor:pointer;"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12v7a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg> </button> <div class="relative no-card-link" data-controller="bluesky-options" data-bluesky-options-text-value="Sucks that they don't have some kind of basic respect for accesibility or localization. Maybe editing this line to add an SVG <title> or <desc> tag would do it. I think screenreaders unpack SVGs to get at those. github.com/rails/rails/..." data-bluesky-options-author-handle-value="mvsamuel.bsky.social" data-bluesky-options-author-did-value="did:plc:xnx52zdoyuf777kntfughiwa" data-bluesky-options-muted-value="false" data-bluesky-options-blocking-uri-value="" data-bluesky-options-own-value="false" data-bluesky-options-delete-url-value="" data-bluesky-options-post-element-selector-value='[data-card-type="bsky-post"]'> <button type="button" class="inline-flex items-center" data-action="click->bluesky-options#toggleMenu" aria-label="Post options"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg> </button> <div class="hidden absolute right-0 bottom-full mb-3 w-60 rounded-2xl border border-white/10 bg-[#1C2333] text-white shadow-xl overflow-hidden" data-bluesky-options-target="menu"> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-options#translate"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16v16H4z"/><path d="M9 8h6"/><path d="M9 12h6"/><path d="M9 16h6"/></svg> </span> <span>Translate</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-options#copyText"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2h-8a2 2 0 0 0-2 2v8"/></svg> </span> <span>Copy post text</span> </button> <div class="border-t border-white/10"></div> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-auth#requireConnection click->bluesky-options#muteAccount"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 9v6h3l5 5V4L7 9H4z"/><line x1="16" y1="9" x2="19" y2="12"/><line x1="19" y1="12" x2="16" y2="15"/></svg> </span> <span data-bluesky-options-target="muteLabel">Mute account</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-auth#requireConnection click->bluesky-options#blockAccount"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3h18v18H3z"/><path d="M3 3l18 18"/></svg> </span> <span data-bluesky-options-target="blockLabel">Block account</span> </button> </div> </div> </div> </div> </div> </div> </div> </div> <script> // Prevent navigation when clicking in avatar column area - must run in capture phase before global handler // Only initialize once to avoid duplicate listeners if (!window._avatarColumnNoNavInitialized) { window._avatarColumnNoNavInitialized = true; document.addEventListener('click', function(ev) { try { // Check if target itself has the class OR is inside an element with that class var avatarColumn = (ev.target.classList && ev.target.classList.contains('avatar-column-no-nav')) || (ev.target.closest && ev.target.closest('.avatar-column-no-nav')); if (avatarColumn) { // Only allow navigation if clicking directly on the avatar link itself var isAvatarLink = ev.target.tagName === 'A' && ev.target.href && ev.target.href.indexOf('/author') !== -1; if (!isAvatarLink) { ev.stopPropagation(); ev.stopImmediatePropagation(); ev.preventDefault(); return false; } } } catch(e) {} }, true); // Capture phase - runs BEFORE other handlers } </script> <div class="w-full border-y sm:border bg-white dark:bg-[#0C1220] bluesky-list-card digest-card card-clickable cursor-pointer group" data-href="/bsky/inautilo.bsky.social/post/3myeljgklfl2i" role="link" tabindex="0" data-card-type="bsky-post" onclick="if(!event.target.closest('.no-card-link')){ window.location = this.dataset.href }" onkeydown="if((event.key==='Enter' || event.key===' ') && !event.target.closest('.no-card-link')){ window.location = this.dataset.href }"> <div class="px-4 py-3"> <div class="flex items-start gap-4"> <div class="relative w-10 self-stretch flex justify-center no-card-link avatar-column-no-nav" style="min-height:40px; align-self:stretch" onclick="event.stopPropagation(); event.stopImmediatePropagation(); return false;" onkeydown="if(event.key==='Enter' || event.key===' '){ event.stopPropagation(); event.stopImmediatePropagation(); return false; }"> <a href="/author?bsky_handle=inautilo.bsky.social" class="no-card-link inline-block flex-shrink-0" aria-label="Inautilo" style="align-self: flex-start;"> <img src="https://cdn.bsky.app/img/avatar/plain/did:plc:2uccdpdhk75kies2czi4wmzy/bafkreihfpj6v4pookvu36qjthpxtzaczxzcdipu7nujvajtgkjd6jd6jjq" alt="inautilo.bsky.social" class="w-10 h-10 rounded-full relative z-10" loading="lazy"> </a> </div> <div class="flex-1 min-w-0 -mt-0.5"> <div> <div class="text-[1.1rem] sm:text-[1.15rem] font-semibold text-black flex items-center gap-1"> <a href="/author?bsky_handle=inautilo.bsky.social" class="truncate hover:underline text-black no-card-link">Inautilo</a> </div> <div class="text-gray-500 text-sm leading-4 -mt-0.5 truncate"> <a href="/author?bsky_handle=inautilo.bsky.social" class="hover:underline text-gray-500 no-card-link">@inautilo.bsky.social</a> </div> </div> <div class="mt-3 text-[1.2rem] leading-7 break-words text-gray-800 w-[calc(100%+3.5rem)] -ml-[3.5rem]"><a href="/hashtag/Development" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Development</a> <a href="/hashtag/Previews" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Previews</a><br>Accessibility Compatibility Data (ACD) · Bringing accessibility support data to developer tools <a href="https://ilo.im/16h6qx" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link" target="_blank" rel="noopener" data-link="bsky">ilo.im/16h6qx</a> by Lola Odelola<br><br>_____<br><a href="/hashtag/ACD" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#ACD</a> <a href="/hashtag/Interoperability" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Interoperability</a> <a href="/hashtag/Compatibility" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Compatibility</a> <a href="/hashtag/Browsers" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Browsers</a> <a href="/hashtag/Accessibility" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Accessibility</a> <a href="/hashtag/ScreenReaders" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#ScreenReaders</a> <a href="/hashtag/AssistiveTechnology" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#AssistiveTechnology</a> <a href="/hashtag/DevTools" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#DevTools</a> <a href="/hashtag/WebDev" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#WebDev</a> <a href="/hashtag/Frontend" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Frontend</a></div> <a href="https://ilo.im/16h6qx" target="_blank" rel="noopener" class="block mt-2 border rounded overflow-hidden w-[calc(100%+3.5rem)] -ml-[3.5rem] no-card-link"> <div class="p-2"> <div class="text-sm font-semibold truncate"></div> <div class="text-xs text-gray-500 truncate"></div> </div> <div class="border-t px-2 py-2 text-sm text-gray-600 flex items-center gap-2"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 0 20"/><path d="M12 2a15.3 15.3 0 0 0 0 20"/></svg> <span class="truncate">ilo.im</span> </div> </a> <div class="mt-3 text-xs text-gray-500 flex items-center gap-2 whitespace-nowrap overflow-hidden w-[calc(100%+3.5rem)] -ml-[3.5rem]"> <span>October 21, 2026 at 7:05 AM</span> </div> <div class="border-t my-2 w-[calc(100%+3.5rem)] -ml-[3.5rem]"></div> <div class="mt-3 flex items-center justify-between text-gray-600 text-sm w-[calc(100%+3.5rem)] -ml-[3.5rem] no-card-link" data-controller="bluesky-interactions"> <div class="flex items-center justify-between w-[300px] pr-4 no-card-link"> <div class="hidden" data-post-text-uri="at://did:plc:2uccdpdhk75kies2czi4wmzy/app.bsky.feed.post/3myeljgklfl2i"> <a href="/hashtag/Development" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Development</a> <a href="/hashtag/Previews" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Previews</a><br>Accessibility Compatibility Data (ACD) · Bringing accessibility support data to developer tools <a href="https://ilo.im/16h6qx" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link" target="_blank" rel="noopener" data-link="bsky">ilo.im/16h6qx</a> by Lola Odelola<br><br>_____<br><a href="/hashtag/ACD" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#ACD</a> <a href="/hashtag/Interoperability" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Interoperability</a> <a href="/hashtag/Compatibility" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Compatibility</a> <a href="/hashtag/Browsers" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Browsers</a> <a href="/hashtag/Accessibility" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Accessibility</a> <a href="/hashtag/ScreenReaders" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#ScreenReaders</a> <a href="/hashtag/AssistiveTechnology" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#AssistiveTechnology</a> <a href="/hashtag/DevTools" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#DevTools</a> <a href="/hashtag/WebDev" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#WebDev</a> <a href="/hashtag/Frontend" class="hover:underline text-blue-600 dark:text-sky-400 no-card-link">#Frontend</a> </div> <button type="button" class="flex items-center gap-1 hover:text-black no-card-link" data-action="click->bluesky-auth#requireConnection click->bluesky-interactions#reply" data-bluesky-interactions-reply-uri-param="at://did:plc:2uccdpdhk75kies2czi4wmzy/app.bsky.feed.post/3myeljgklfl2i" data-bluesky-interactions-reply-cid-param="bafyreied4tv3yheeuco7c6gpkeimdlpbrntmlasvw5fbtwitjjt3kj2y7q" data-bluesky-interactions-reply-root-uri-param="at://did:plc:2uccdpdhk75kies2czi4wmzy/app.bsky.feed.post/3myeljgklfl2i" data-bluesky-interactions-reply-root-cid-param="bafyreied4tv3yheeuco7c6gpkeimdlpbrntmlasvw5fbtwitjjt3kj2y7q" data-bluesky-interactions-reply-author-name-param="Inautilo" data-bluesky-interactions-reply-author-avatar-param="https://cdn.bsky.app/img/avatar/plain/did:plc:2uccdpdhk75kies2czi4wmzy/bafkreihfpj6v4pookvu36qjthpxtzaczxzcdipu7nujvajtgkjd6jd6jjq" data-bluesky-interactions-post-path-param="/bsky/inautilo.bsky.social/post/3myeljgklfl2i" title="Reply" style="pointer-events: auto; cursor: pointer; z-index: 10; position: relative;"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a4 4 0 0 1-4 4H7l-4 4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/></svg> </button> <div class="relative no-card-link" data-controller="bluesky-repost-menu" data-bluesky-repost-menu-uri-value="at://did:plc:2uccdpdhk75kies2czi4wmzy/app.bsky.feed.post/3myeljgklfl2i" data-bluesky-repost-menu-cid-value="bafyreied4tv3yheeuco7c6gpkeimdlpbrntmlasvw5fbtwitjjt3kj2y7q" data-bluesky-repost-menu-repost-uri-value="" data-bluesky-repost-menu-is-reposted-value="false" data-bluesky-repost-menu-author-handle-value="inautilo.bsky.social" data-bluesky-repost-menu-post-text-value="#Development #Previews Accessibility Compatibility Data (ACD) · Bringing accessibility support data to developer tools ilo.im/16h6qx by Lola Odelola _____ #ACD #Interoperability #Compatibility #Browsers #Accessibility #ScreenReaders #AssistiveTechnology #DevTools #WebDev #Frontend" style="pointer-events:auto; cursor:pointer;"> <button type="button" class="flex items-center gap-1 hover:text-black " data-action="click->bluesky-repost-menu#toggleMenu" data-bluesky-interactions-target="repostButton" title="Repost"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg> </button> <div class="hidden absolute left-0 bottom-full mb-3 w-48 rounded-2xl border border-white/10 bg-[#1C2333] text-white shadow-xl overflow-hidden z-50" data-bluesky-repost-menu-target="menu"> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left hidden" data-action="click->bluesky-auth#requireConnection click->bluesky-repost-menu#undoRepost"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg> <span>Undo repost</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left " data-action="click->bluesky-auth#requireConnection click->bluesky-repost-menu#repost"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg> <span>Repost</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-auth#requireConnection click->bluesky-repost-menu#quote"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> <span>Quote post</span> </button> </div> </div> <button type="button" class="flex items-center gap-1 hover:text-black no-card-link " data-action="click->bluesky-auth#requireConnection click->bluesky-interactions#toggleLike" data-bluesky-interactions-uri-param="at://did:plc:2uccdpdhk75kies2czi4wmzy/app.bsky.feed.post/3myeljgklfl2i" data-bluesky-interactions-cid-param="bafyreied4tv3yheeuco7c6gpkeimdlpbrntmlasvw5fbtwitjjt3kj2y7q" data-bluesky-interactions-like-uri-param="" data-bluesky-interactions-target="likeButton" title="Like"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 1 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z"/></svg> <span data-bluesky-interactions-target="likeCount">1</span> </button> </div> <div class="flex items-center gap-4 text-gray-500"> <button type="button" class="flex items-center hover:text-black no-card-link" data-controller="share" data-share-url-value="https://www.lightnews.app/bsky/inautilo.bsky.social/post/3myeljgklfl2i" data-share-title-value="Inautilo on LightNews" data-share-text-value="#Development #Previews Accessibility Compatibility Data (ACD) · Bringing accessibility support data to developer tools ilo.im/16h6qx by Lola…" data-action="click->share#shareSystem" title="Share" style="pointer-events:auto; cursor:pointer;"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12v7a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg> </button> <div class="relative no-card-link" data-controller="bluesky-options" data-bluesky-options-text-value="#Development #Previews Accessibility Compatibility Data (ACD) · Bringing accessibility support data to developer tools ilo.im/16h6qx by Lola Odelola _____ #ACD #Interoperability #Compatibility #Browsers #Accessibility #ScreenReaders #AssistiveTechnology #DevTools #WebDev #Frontend" data-bluesky-options-author-handle-value="inautilo.bsky.social" data-bluesky-options-author-did-value="did:plc:2uccdpdhk75kies2czi4wmzy" data-bluesky-options-muted-value="false" data-bluesky-options-blocking-uri-value="" data-bluesky-options-own-value="false" data-bluesky-options-delete-url-value="" data-bluesky-options-post-element-selector-value='[data-card-type="bsky-post"]'> <button type="button" class="inline-flex items-center" data-action="click->bluesky-options#toggleMenu" aria-label="Post options"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg> </button> <div class="hidden absolute right-0 bottom-full mb-3 w-60 rounded-2xl border border-white/10 bg-[#1C2333] text-white shadow-xl overflow-hidden" data-bluesky-options-target="menu"> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-options#translate"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16v16H4z"/><path d="M9 8h6"/><path d="M9 12h6"/><path d="M9 16h6"/></svg> </span> <span>Translate</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-options#copyText"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2h-8a2 2 0 0 0-2 2v8"/></svg> </span> <span>Copy post text</span> </button> <div class="border-t border-white/10"></div> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-auth#requireConnection click->bluesky-options#muteAccount"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 9v6h3l5 5V4L7 9H4z"/><line x1="16" y1="9" x2="19" y2="12"/><line x1="19" y1="12" x2="16" y2="15"/></svg> </span> <span data-bluesky-options-target="muteLabel">Mute account</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-auth#requireConnection click->bluesky-options#blockAccount"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3h18v18H3z"/><path d="M3 3l18 18"/></svg> </span> <span data-bluesky-options-target="blockLabel">Block account</span> </button> </div> </div> </div> </div> </div> </div> </div> </div> <script> // Prevent navigation when clicking in avatar column area - must run in capture phase before global handler // Only initialize once to avoid duplicate listeners if (!window._avatarColumnNoNavInitialized) { window._avatarColumnNoNavInitialized = true; document.addEventListener('click', function(ev) { try { // Check if target itself has the class OR is inside an element with that class var avatarColumn = (ev.target.classList && ev.target.classList.contains('avatar-column-no-nav')) || (ev.target.closest && ev.target.closest('.avatar-column-no-nav')); if (avatarColumn) { // Only allow navigation if clicking directly on the avatar link itself var isAvatarLink = ev.target.tagName === 'A' && ev.target.href && ev.target.href.indexOf('/author') !== -1; if (!isAvatarLink) { ev.stopPropagation(); ev.stopImmediatePropagation(); ev.preventDefault(); return false; } } } catch(e) {} }, true); // Capture phase - runs BEFORE other handlers } </script> <div class="w-full border-y sm:border bg-white dark:bg-[#0C1220] bluesky-list-card digest-card card-clickable cursor-pointer group" data-href="/bsky/jen-m-a-c.bsky.social/post/3myfq5uxj3c2u" role="link" tabindex="0" data-card-type="bsky-post" onclick="if(!event.target.closest('.no-card-link')){ window.location = this.dataset.href }" onkeydown="if((event.key==='Enter' || event.key===' ') && !event.target.closest('.no-card-link')){ window.location = this.dataset.href }"> <div class="px-4 py-3"> <div class="flex items-start gap-4"> <div class="relative w-10 self-stretch flex justify-center no-card-link avatar-column-no-nav" style="min-height:40px; align-self:stretch" onclick="event.stopPropagation(); event.stopImmediatePropagation(); return false;" onkeydown="if(event.key==='Enter' || event.key===' '){ event.stopPropagation(); event.stopImmediatePropagation(); return false; }"> <a href="/author?bsky_handle=jen-m-a-c.bsky.social" class="no-card-link inline-block flex-shrink-0" aria-label="Jen Mac" style="align-self: flex-start;"> <img src="https://cdn.bsky.app/img/avatar/plain/did:plc:vgxyu6g2jqsjmqjwgm3tghum/bafkreido4osrvjrtjbrluoupdu4gkz4qsn66gv2vjfnq4prih7vpna4bpm" alt="jen-m-a-c.bsky.social" class="w-10 h-10 rounded-full relative z-10" loading="lazy"> </a> </div> <div class="flex-1 min-w-0 -mt-0.5"> <div> <div class="text-[1.1rem] sm:text-[1.15rem] font-semibold text-black flex items-center gap-1"> <a href="/author?bsky_handle=jen-m-a-c.bsky.social" class="truncate hover:underline text-black no-card-link">Jen Mac</a> </div> <div class="text-gray-500 text-sm leading-4 -mt-0.5 truncate"> <a href="/author?bsky_handle=jen-m-a-c.bsky.social" class="hover:underline text-gray-500 no-card-link">@jen-m-a-c.bsky.social</a> </div> </div> <div class="mt-3 text-[1.2rem] leading-7 break-words text-gray-800 w-[calc(100%+3.5rem)] -ml-[3.5rem]">🐜Bugs to be squished before launching the new level include: <br><br>🧮Tallying up the collector cards correctly<br>📣Improving the chorus share function<br>🦮Testing 'spoiler free' alt tags for screenreaders<br>👂Checking and snagging sound clips and song descriptions<br><br>🐦‍⬛Fun! (almost)</div> <div class="mt-3 text-xs text-gray-500 flex items-center gap-2 whitespace-nowrap overflow-hidden w-[calc(100%+3.5rem)] -ml-[3.5rem]"> <span>October 21, 2026 at 6:01 PM</span> </div> <div class="border-t my-2 w-[calc(100%+3.5rem)] -ml-[3.5rem]"></div> <div class="mt-3 flex items-center justify-between text-gray-600 text-sm w-[calc(100%+3.5rem)] -ml-[3.5rem] no-card-link" data-controller="bluesky-interactions"> <div class="flex items-center justify-between w-[300px] pr-4 no-card-link"> <div class="hidden" data-post-text-uri="at://did:plc:vgxyu6g2jqsjmqjwgm3tghum/app.bsky.feed.post/3myfq5uxj3c2u"> 🐜Bugs to be squished before launching the new level include: <br><br>🧮Tallying up the collector cards correctly<br>📣Improving the chorus share function<br>🦮Testing 'spoiler free' alt tags for screenreaders<br>👂Checking and snagging sound clips and song descriptions<br><br>🐦‍⬛Fun! (almost) </div> <button type="button" class="flex items-center gap-1 hover:text-black no-card-link" data-action="click->bluesky-auth#requireConnection click->bluesky-interactions#reply" data-bluesky-interactions-reply-uri-param="at://did:plc:vgxyu6g2jqsjmqjwgm3tghum/app.bsky.feed.post/3myfq5uxj3c2u" data-bluesky-interactions-reply-cid-param="bafyreias2pc2hcsw5g2l2nj4dmjzwx3nc7tu3dajtqlzn7x5gefzmfhw6u" data-bluesky-interactions-reply-root-uri-param="at://did:plc:vgxyu6g2jqsjmqjwgm3tghum/app.bsky.feed.post/3myfk56g24c2u" data-bluesky-interactions-reply-root-cid-param="bafyreictajghos4get2gvqtz24n3jlo7qtw6jq664mfnq3rdw4r4aw7e7a" data-bluesky-interactions-reply-author-name-param="Jen Mac" data-bluesky-interactions-reply-author-avatar-param="https://cdn.bsky.app/img/avatar/plain/did:plc:vgxyu6g2jqsjmqjwgm3tghum/bafkreido4osrvjrtjbrluoupdu4gkz4qsn66gv2vjfnq4prih7vpna4bpm" data-bluesky-interactions-post-path-param="/bsky/jen-m-a-c.bsky.social/post/3myfq5uxj3c2u" title="Reply" style="pointer-events: auto; cursor: pointer; z-index: 10; position: relative;"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a4 4 0 0 1-4 4H7l-4 4V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4z"/></svg> </button> <div class="relative no-card-link" data-controller="bluesky-repost-menu" data-bluesky-repost-menu-uri-value="at://did:plc:vgxyu6g2jqsjmqjwgm3tghum/app.bsky.feed.post/3myfq5uxj3c2u" data-bluesky-repost-menu-cid-value="bafyreias2pc2hcsw5g2l2nj4dmjzwx3nc7tu3dajtqlzn7x5gefzmfhw6u" data-bluesky-repost-menu-repost-uri-value="" data-bluesky-repost-menu-is-reposted-value="false" data-bluesky-repost-menu-author-handle-value="jen-m-a-c.bsky.social" data-bluesky-repost-menu-post-text-value="🐜Bugs to be squished before launching the new level include: 🧮Tallying up the collector cards correctly 📣Improving the chorus share function 🦮Testing 'spoiler free' alt tags for screenreaders 👂Checking and snagging sound clips and song descriptions 🐦‍⬛Fun! (almost)" style="pointer-events:auto; cursor:pointer;"> <button type="button" class="flex items-center gap-1 hover:text-black " data-action="click->bluesky-repost-menu#toggleMenu" data-bluesky-interactions-target="repostButton" title="Repost"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg> </button> <div class="hidden absolute left-0 bottom-full mb-3 w-48 rounded-2xl border border-white/10 bg-[#1C2333] text-white shadow-xl overflow-hidden z-50" data-bluesky-repost-menu-target="menu"> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left hidden" data-action="click->bluesky-auth#requireConnection click->bluesky-repost-menu#undoRepost"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg> <span>Undo repost</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left " data-action="click->bluesky-auth#requireConnection click->bluesky-repost-menu#repost"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg> <span>Repost</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-auth#requireConnection click->bluesky-repost-menu#quote"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> <span>Quote post</span> </button> </div> </div> <button type="button" class="flex items-center gap-1 hover:text-black no-card-link " data-action="click->bluesky-auth#requireConnection click->bluesky-interactions#toggleLike" data-bluesky-interactions-uri-param="at://did:plc:vgxyu6g2jqsjmqjwgm3tghum/app.bsky.feed.post/3myfq5uxj3c2u" data-bluesky-interactions-cid-param="bafyreias2pc2hcsw5g2l2nj4dmjzwx3nc7tu3dajtqlzn7x5gefzmfhw6u" data-bluesky-interactions-like-uri-param="" data-bluesky-interactions-target="likeButton" title="Like"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 1 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z"/></svg> </button> </div> <div class="flex items-center gap-4 text-gray-500"> <button type="button" class="flex items-center hover:text-black no-card-link" data-controller="share" data-share-url-value="https://www.lightnews.app/bsky/jen-m-a-c.bsky.social/post/3myfq5uxj3c2u" data-share-title-value="Jen Mac on LightNews" data-share-text-value="🐜Bugs to be squished before launching the new level include: 🧮Tallying up the collector cards correctly 📣Improving the chorus share function…" data-action="click->share#shareSystem" title="Share" style="pointer-events:auto; cursor:pointer;"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 12v7a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg> </button> <div class="relative no-card-link" data-controller="bluesky-options" data-bluesky-options-text-value="🐜Bugs to be squished before launching the new level include: 🧮Tallying up the collector cards correctly 📣Improving the chorus share function 🦮Testing 'spoiler free' alt tags for screenreaders 👂Checking and snagging sound clips and song descriptions 🐦‍⬛Fun! (almost)" data-bluesky-options-author-handle-value="jen-m-a-c.bsky.social" data-bluesky-options-author-did-value="did:plc:vgxyu6g2jqsjmqjwgm3tghum" data-bluesky-options-muted-value="false" data-bluesky-options-blocking-uri-value="" data-bluesky-options-own-value="false" data-bluesky-options-delete-url-value="" data-bluesky-options-post-element-selector-value='[data-card-type="bsky-post"]'> <button type="button" class="inline-flex items-center" data-action="click->bluesky-options#toggleMenu" aria-label="Post options"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg> </button> <div class="hidden absolute right-0 bottom-full mb-3 w-60 rounded-2xl border border-white/10 bg-[#1C2333] text-white shadow-xl overflow-hidden" data-bluesky-options-target="menu"> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-options#translate"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16v16H4z"/><path d="M9 8h6"/><path d="M9 12h6"/><path d="M9 16h6"/></svg> </span> <span>Translate</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-options#copyText"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2h-8a2 2 0 0 0-2 2v8"/></svg> </span> <span>Copy post text</span> </button> <div class="border-t border-white/10"></div> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-auth#requireConnection click->bluesky-options#muteAccount"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 9v6h3l5 5V4L7 9H4z"/><line x1="16" y1="9" x2="19" y2="12"/><line x1="19" y1="12" x2="16" y2="15"/></svg> </span> <span data-bluesky-options-target="muteLabel">Mute account</span> </button> <button type="button" class="w-full px-4 py-3 flex items-center gap-3 text-sm hover:bg-[#273045] text-left" data-action="click->bluesky-auth#requireConnection click->bluesky-options#blockAccount"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-white/10"> <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3h18v18H3z"/><path d="M3 3l18 18"/></svg> </span> <span data-bluesky-options-target="blockLabel">Block account</span> </button> </div> </div> </div> </div> </div> </div> </div> </div> <script> // Prevent navigation when clicking in avatar column area - must run in capture phase before global handler // Only initialize once to avoid duplicate listeners if (!window._avatarColumnNoNavInitialized) { window._avatarColumnNoNavInitialized = true; document.addEventListener('click', function(ev) { try { // Check if target itself has the class OR is inside an element with that class var avatarColumn = (ev.target.classList && ev.target.classList.contains('avatar-column-no-nav')) || (ev.target.closest && ev.target.closest('.avatar-column-no-nav')); if (avatarColumn) { // Only allow navigation if clicking directly on the avatar link itself var isAvatarLink = ev.target.tagName === 'A' && ev.target.href && ev.target.href.indexOf('/author') !== -1; if (!isAvatarLink) { ev.stopPropagation(); ev.stopImmediatePropagation(); ev.preventDefault(); return false; } } } catch(e) {} }, true); // Capture phase - runs BEFORE other handlers } </script> </div> <div id="infinite-scroll-sentinel" class="mt-4" data-infinite-scroll-target="sentinel"> <a class="text-blue-600" data-action="click->infinite-scroll#onClickLoadMore" data-infinite-scroll-target="link" href="/hashtag/ScreenReaders?cursor=eyJzIjpbMC4zMDY5OTU5MywiZGlkOnBsYzp2Z3h5dTZnMmpxc2ptcWp3Z20zdGdodW0iLCIzbXlmcTV1eGozYzJ1Il0sIm8iOiIyMDI2LTExLTA0VDIwOjQ5OjEyWiJ9">Load more</a> </div> </div> </div> </div> </main> <!-- mobile footer CTA will be rendered after the grid to avoid stacking issues --> </div> <div class="hidden md:block"></div> </div> <!-- Mobile footer: CTA when logged out; mini-nav when logged in --> <div id="cta-footer" class="md:hidden fixed bottom-0 left-0 right-0 z-40 bg-white dark:bg-black border-t border-gray-200 dark:border-gray-700"> <div class="px-2 pt-3 pb-5"> <div class="flex items-center justify-between gap-3"> <!-- Left: Logo + Text --> <div class="flex items-center gap-2 flex-1 min-w-0"> <a class="inline-flex items-center gap-2" aria-label="LightNews" data-apply-edition="1" href="/"> <img alt="LightNews" class="logo-light h-12 w-12 flex-shrink-0" src="/assets/brands/lightnews/logo-light-333069a8.png" /> <img alt="LightNews" class="logo-dark h-12 w-12 flex-shrink-0" src="/assets/brands/lightnews/logo-dark-ef2a5b66.png" /> <span class="text-base font-semibold text-gray-900 dark:text-white truncate brand-text">LightNews</span> </a> </div> <!-- Right: Buttons --> <div class="flex items-center gap-2 flex-shrink-0"> <button type="button" data-action="click->auth-modal#openSignup" class="btn btn-primary btn-sm whitespace-nowrap">Create account</button> <button type="button" data-action="click->auth-modal#openSignin" class="btn btn-light btn-sm whitespace-nowrap">Sign in</button> </div> </div> </div> </div> <!-- Add bottom padding to body on mobile to prevent content from being hidden behind sticky footer --> <style> @media (max-width: 767px) { body { padding-bottom: 6.5rem; } } /* Hard guarantee: footer is visible on mobile widths */ @media (max-width: 1023px) { #cta-footer { display: block !important; } } </style> <!-- Sign In Modal --> <div id="signin-modal" class="hidden fixed inset-0 z-50 overflow-y-auto" data-auth-modal-target="signinModal"> <div class="flex min-h-screen items-center justify-center p-4"> <!-- Backdrop --> <div class="fixed inset-0 bg-black/80 transition-opacity" data-action="click->auth-modal#close"></div> <!-- Modal Content --> <div class="relative bg-white dark:bg-black rounded-2xl w-full max-w-md p-8 shadow-2xl"> <!-- Close Button --> <button data-action="click->auth-modal#close" class="absolute top-4 right-4 text-gray-400 hover:text-white transition-colors"> <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> <!-- Logo --> <div class="flex justify-center mb-6"> <img alt="LightNews" class="logo-light h-12 w-12" src="/assets/brands/lightnews/logo-light-333069a8.png" /> <img alt="LightNews" class="logo-dark h-12 w-12" src="/assets/brands/lightnews/logo-dark-ef2a5b66.png" /> </div> <!-- Title --> <h2 class="text-black dark:text-white text-3xl font-bold text-center mb-6">Sign in to LightNews</h2> <!-- OAuth Buttons --> <div class="space-y-3 mb-6"> <!-- Google --> <a class="w-full flex items-center justify-center gap-3 px-6 py-3 bg-transparent hover:bg-black/5 dark:hover:bg-white/10 text-black dark:text-white font-semibold rounded transition-colors border border-gray-300 dark:border-gray-600" href="/auth/google/start"> <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"> <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/> <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/> <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/> <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/> </svg> Continue with Google </a> <!-- Apple --> <a class="w-full flex items-center justify-center gap-3 px-6 py-3 bg-transparent hover:bg-black/5 dark:hover:bg-white/10 text-black dark:text-white font-semibold rounded transition-colors border border-gray-300 dark:border-gray-600" href="/auth/apple/start"> <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"> <path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/> </svg> Continue with Apple </a> <!-- Bluesky --> <button type="button" class="w-full flex items-center justify-center gap-3 px-6 py-3 bg-transparent hover:bg-black/5 dark:hover:bg-white/10 text-black dark:text-white font-semibold rounded transition-colors border border-gray-300 dark:border-gray-600" data-action="click->auth-modal#close click->bluesky-auth#handleSignIn"> <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"> <path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.038.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 01-.415-.056c.14.018.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8z" fill="#1185fe"/> </svg> Continue with Bluesky </button> </div> <!-- Divider --> <div class="relative my-6"> <div class="absolute inset-0 flex items-center"> <div class="w-full border-t border-gray-300 dark:border-gray-700"></div> </div> <div class="relative flex justify-center"> <span class="bg-white dark:bg-black px-3 text-sm text-gray-600 dark:text-gray-300">or</span> </div> </div> <!-- Email Sign-In Form --> <form class="space-y-4" action="/auth/email/signin" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="W_xuSoi6maLqZPtBN7XAXZK-HNXnmToEmUaR0ILVIM7yitbcU49zDqa65omJZRSht-jMrRyyyf5uNmJeRkt_QQ" autocomplete="off" /> <div> <label for="signin-email" class="block text-black dark:text-white font-semibold text-sm mb-2">Email address</label> <input type="email" name="email" id="signin-email" placeholder="name@domain.com" required class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded text-black dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent" /> </div> <div> <label for="signin-password" class="block text-black dark:text-white font-semibold text-sm mb-2">Password</label> <input type="password" name="password" id="signin-password" placeholder="Your password" required class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded text-black dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent" /> </div> <button type="submit" class="w-full btn btn-primary py-3 font-semibold"> Sign in </button> </form> <div class="mt-3 text-center"> <a class="text-sm text-blue-600 dark:text-blue-400 hover:underline" href="/auth/email/forgot_password">Forgot password?</a> </div> <!-- Footer --> <div class="mt-8 text-center"> <p class="text-gray-600 dark:text-gray-400 mb-2">Don't have an account?</p> <button data-action="click->auth-modal#switchToSignup" class="text-blue-600 dark:text-white font-semibold underline hover:text-blue-500 transition-colors"> Sign up for LightNews </button> </div> </div> </div> </div> <!-- Sign Up Modal --> <div id="signup-modal" class="hidden fixed inset-0 z-50 overflow-y-auto" data-auth-modal-target="signupModal"> <div class="flex min-h-screen items-center justify-center p-4"> <!-- Backdrop --> <div class="fixed inset-0 bg-black/80 transition-opacity" data-action="click->auth-modal#close"></div> <!-- Modal Content --> <div class="relative bg-white dark:bg-black rounded-2xl w-full max-w-md p-8 shadow-2xl"> <!-- Close Button --> <button data-action="click->auth-modal#close" class="absolute top-4 right-4 text-gray-400 hover:text-white transition-colors"> <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> <!-- Logo --> <div class="flex justify-center mb-6"> <img alt="LightNews" class="logo-light h-12 w-12" src="/assets/brands/lightnews/logo-light-333069a8.png" /> <img alt="LightNews" class="logo-dark h-12 w-12" src="/assets/brands/lightnews/logo-dark-ef2a5b66.png" /> </div> <!-- Title --> <h2 class="text-black dark:text-white text-3xl font-bold text-center mb-6">Sign up to start reading</h2> <!-- OAuth Buttons --> <div class="space-y-3 mb-6"> <!-- Google --> <a class="w-full flex items-center justify-center gap-3 px-6 py-3 bg-transparent hover:bg-black/5 dark:hover:bg-white/10 text-black dark:text-white font-semibold rounded transition-colors border border-gray-300 dark:border-gray-600" href="/auth/google/start"> <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"> <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/> <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/> <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/> <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/> </svg> Sign up with Google </a> <!-- Apple --> <a class="w-full flex items-center justify-center gap-3 px-6 py-3 bg-transparent hover:bg-black/5 dark:hover:bg-white/10 text-black dark:text-white font-semibold rounded transition-colors border border-gray-300 dark:border-gray-600" href="/auth/apple/start"> <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"> <path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/> </svg> Sign up with Apple </a> <!-- Bluesky --> <button type="button" class="w-full flex items-center justify-center gap-3 px-6 py-3 bg-transparent hover:bg-black/5 dark:hover:bg-white/10 text-black dark:text-white font-semibold rounded transition-colors border border-gray-300 dark:border-gray-600" data-action="click->auth-modal#close click->bluesky-auth#handleCreateAccount"> <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"> <path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.038.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 01-.415-.056c.14.018.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8z" fill="#1185fe"/> </svg> Sign up with Bluesky </button> </div> <!-- Divider --> <div class="relative my-6"> <div class="absolute inset-0 flex items-center"> <div class="w-full border-t border-gray-300 dark:border-gray-700"></div> </div> <div class="relative flex justify-center"> <span class="bg-white dark:bg-black px-3 text-sm text-gray-600 dark:text-gray-300">or</span> </div> </div> <!-- Email Sign-Up Form --> <form class="space-y-4" action="/auth/email/signup" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="Pl9nbBOoxptO0wjF4T8qlpr4uVPpJNI4-SseymYzaiLyxDmduE8zRiYYRrxtCmURpHjI6QRc0fQRxm459alguw" autocomplete="off" /> <div> <label for="signup-name" class="block text-black dark:text-white font-semibold text-sm mb-2">Name</label> <input type="text" name="display_name" id="signup-name" placeholder="Your name" required class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded text-black dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent" /> </div> <div> <label for="signup-email" class="block text-black dark:text-white font-semibold text-sm mb-2">Email address</label> <input type="email" name="email" id="signup-email" placeholder="name@domain.com" required class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded text-black dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent" /> </div> <div> <label for="signup-password" class="block text-black dark:text-white font-semibold text-sm mb-2">Password</label> <input type="password" name="password" id="signup-password" placeholder="At least 8 characters" minlength="8" required class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded text-black dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent" /> </div> <div> <label for="signup-password-confirmation" class="block text-black dark:text-white font-semibold text-sm mb-2">Confirm password</label> <input type="password" name="password_confirmation" id="signup-password-confirmation" placeholder="Repeat your password" minlength="8" required class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded text-black dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent" /> </div> <button type="submit" class="w-full btn btn-primary py-3 font-semibold"> Create account </button> </form> <!-- Footer --> <div class="mt-8 text-center"> <p class="text-gray-600 dark:text-gray-400 mb-2">Already have an account?</p> <button data-action="click->auth-modal#switchToSignin" class="text-blue-600 dark:text-white font-semibold underline hover:text-blue-500 transition-colors"> Sign in </button> </div> </div> </div> </div> <div data-controller="modal" data-reply-modal="true" id="global-reply-modal" data-viewer-avatar-url="" data-viewer-display-name=""> <div class="hidden fixed inset-0 z-50 overflow-y-auto" data-modal-target="panel" data-viewer-avatar-url="" data-viewer-display-name=""> <div class="flex min-h-screen items-start justify-center pt-4 md:pt-20"> <!-- Backdrop --> <div class="fixed inset-0 bg-black/80 transition-opacity" data-action="click->modal#close"></div> <!-- Modal Content --> <div class="relative bg-[#1e3a5f] rounded-2xl w-full max-w-xl mx-4 shadow-2xl" data-action="click->modal#stop"> <!-- Top bar: Cancel + Reply button --> <div class="flex items-center justify-between px-4 py-3 border-b border-gray-600"> <button type="button" class="text-blue-400 text-base font-medium" data-action="click->modal#close">Cancel</button> <button type="button" id="modal-reply-submit" class="px-5 py-1.5 bg-blue-600 text-white rounded-full font-semibold hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"> Reply </button> </div> <!-- Parent post preview (for replies) --> <div class="px-4 py-3 border-b border-gray-600 modal-reply-preview"> <div class="flex items-start gap-3"> <img src="" alt="" class="modal-reply-author-avatar w-10 h-10 rounded-full flex-shrink-0 hidden"> <div class="modal-reply-author-avatar-placeholder w-10 h-10 rounded-full bg-gray-300 flex-shrink-0"></div> <div class="flex-1 min-w-0"> <div class="font-semibold text-white text-base modal-reply-author-name"> </div> <div class="text-gray-300 text-sm line-clamp-3 modal-reply-post-text"> </div> </div> </div> </div> <!-- Reply/Post textarea --> <div class="px-4 py-3"> <div class="flex items-start gap-3"> <img src="" alt="You" class="modal-viewer-avatar w-10 h-10 rounded-full flex-shrink-0 hidden"> <div class="modal-viewer-avatar-placeholder w-10 h-10 rounded-full bg-gray-300 flex-shrink-0 "></div> <div class="flex-1 min-w-0"> <textarea id="modal-reply-text" placeholder="Write your reply" rows="8" maxlength="300" class="w-full px-0 py-2 bg-transparent text-white text-base placeholder-gray-400 resize-none focus:outline-none border-0" data-reply-uri="" data-reply-cid="" data-root-uri="" data-root-cid="" data-mode="reply" data-embed="" ></textarea> </div> </div> <!-- Quote preview (for quote posts, shown below textarea) --> <div class="modal-quote-preview hidden mt-3 p-3 border border-gray-600 rounded-xl bg-[#152642]"> <div class="flex items-start gap-3"> <img src="" alt="" class="modal-quote-author-avatar w-8 h-8 rounded-full flex-shrink-0 hidden"> <div class="modal-quote-author-avatar-placeholder w-8 h-8 rounded-full bg-gray-400 flex-shrink-0"></div> <div class="flex-1 min-w-0"> <div class="font-semibold text-white text-sm modal-quote-author-name"></div> <div class="text-gray-300 text-sm mt-1 modal-quote-post-text"></div> </div> </div> </div> <input type="file" id="modal-attachment-input" accept="image/*" class="hidden" multiple> <div id="modal-attachment-preview" class="mt-3 hidden"> <div class="flex flex-wrap gap-3" id="modal-attachment-preview-list"></div> </div> </div> <!-- Bottom toolbar --> <div class="px-4 py-3 border-t border-gray-600 flex items-center justify-between"> <div class="flex items-center gap-3"> <button type="button" class="text-blue-500 hover:text-blue-400" id="modal-attachment-button"> <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg> </button> <button type="button" class="text-blue-500 hover:text-blue-400" id="modal-gif-button"> <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><text x="3" y="18" font-family="Arial" font-size="14" font-weight="bold">GIF</text></svg> </button> </div> <div class="flex items-center gap-3 text-sm"> <span class="text-blue-400">English</span> <span class="text-gray-400" id="modal-reply-count">300</span> </div> </div> </div> </div> </div> </div> <script> (function(){ function setupReplyModal(){ var textarea = document.getElementById('modal-reply-text'); var submitBtn = document.getElementById('modal-reply-submit'); var countEl = document.getElementById('modal-reply-count'); var attachmentButton = document.getElementById('modal-attachment-button'); var gifButton = document.getElementById('modal-gif-button'); var attachmentInput = document.getElementById('modal-attachment-input'); var attachmentPreview = document.getElementById('modal-attachment-preview'); var attachmentList = document.getElementById('modal-attachment-preview-list'); if (!textarea || !submitBtn || !countEl) { return; } if (textarea.dataset.replyModalBound === '1') { return; } textarea.dataset.replyModalBound = '1'; var attachments = []; var currentEmbed = null; var isUploadingAttachment = false; var MAX_ATTACHMENTS = 4; function updateCount(){ var length = textarea.value.length; var remaining = 300 - length; countEl.textContent = remaining.toString(); countEl.classList.toggle('text-red-400', remaining < 0); countEl.classList.toggle('text-gray-400', remaining >= 0); submitBtn.disabled = ((length === 0) && attachments.length === 0) || length > 300; } function updateEmbedPayload() { if (!attachments.length) { currentEmbed = null; textarea.removeAttribute('data-embed'); return; } currentEmbed = { "$type": "app.bsky.embed.images", "images": attachments.map(function(item){ return { "alt": item.alt || 'Image', "image": item.blob }; }) }; textarea.dataset.embed = JSON.stringify(currentEmbed); } function refreshAttachmentPreview() { if (!attachmentList || !attachmentPreview) { return; } attachmentList.innerHTML = ''; attachments.forEach(function(item, index){ var wrapper = document.createElement('div'); wrapper.className = 'relative w-20 h-20 rounded-xl overflow-hidden'; var img = document.createElement('img'); img.src = item.previewUrl; img.alt = item.alt || 'Attachment'; img.className = 'object-cover w-full h-full'; wrapper.appendChild(img); var removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.className = 'absolute top-1 right-1 bg-black/70 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs'; removeBtn.innerHTML = '×'; removeBtn.addEventListener('click', function(){ removeAttachment(index); }); wrapper.appendChild(removeBtn); attachmentList.appendChild(wrapper); }); if (attachments.length) { attachmentPreview.classList.remove('hidden'); } else { attachmentPreview.classList.add('hidden'); } if (attachmentButton) { attachmentButton.disabled = attachments.length >= MAX_ATTACHMENTS || isUploadingAttachment; attachmentButton.classList.toggle('opacity-50', attachmentButton.disabled); attachmentButton.classList.toggle('pointer-events-none', attachmentButton.disabled); } if (gifButton) { gifButton.disabled = attachments.length >= MAX_ATTACHMENTS || isUploadingAttachment; gifButton.classList.toggle('opacity-50', gifButton.disabled); gifButton.classList.toggle('pointer-events-none', gifButton.disabled); } updateCount(); } function removeAttachment(index) { if (index < 0 || index >= attachments.length) { return; } var removed = attachments.splice(index, 1)[0]; if (removed && removed.previewUrl) { try { URL.revokeObjectURL(removed.previewUrl); } catch (_) {} } updateEmbedPayload(); refreshAttachmentPreview(); } function setAttachmentUploading(state) { isUploadingAttachment = state; refreshAttachmentPreview(); } async function uploadFile(file) { if (!file || !attachmentInput) { return; } if (!file.type.match(/^image\//)) { alert('Only image uploads are supported right now.'); return; } if (attachments.length >= MAX_ATTACHMENTS) { alert('You can attach up to ' + MAX_ATTACHMENTS + ' images.'); return; } if (file.size > 976 * 1024) { alert('Images must be 976KB or smaller.'); return; } setAttachmentUploading(true); var formData = new FormData(); formData.append('file', file); formData.append('alt', file.name || 'Image'); try { var response = await fetch('/api/bluesky/uploads', { method: 'POST', headers: { 'X-CSRF-Token': (document.querySelector('meta[name="csrf-token"]') || {}).content || '', 'Accept': 'application/json' }, credentials: 'same-origin', body: formData }); if (!response.ok) { var errorText = await response.text(); var message = 'Upload failed'; try { var parsed = JSON.parse(errorText); message = parsed.error || message; } catch (_) { if (errorText && errorText.length < 200) { message = errorText; } } throw new Error(message); } var data = await response.json(); if (!data || !data.success || !data.blob) { throw new Error((data && data.error) || 'Upload failed'); } var previewUrl = URL.createObjectURL(file); attachments.push({ blob: data.blob, alt: data.alt || file.name || 'Image', previewUrl: previewUrl }); updateEmbedPayload(); refreshAttachmentPreview(); } catch (error) { console.error('Attachment upload error:', error); alert(error && error.message ? error.message : 'Unable to upload image right now.'); } finally { setAttachmentUploading(false); if (attachmentInput) { attachmentInput.value = ''; } } } function handleFileSelection(files) { if (!files || !files.length) { return; } var remaining = MAX_ATTACHMENTS - attachments.length; Array.from(files).slice(0, remaining).forEach(function(file){ uploadFile(file); }); } if (attachmentButton && attachmentInput) { attachmentButton.addEventListener('click', function(e){ e.preventDefault(); if (isUploadingAttachment || attachments.length >= MAX_ATTACHMENTS) { return; } attachmentInput.setAttribute('accept', 'image/*'); attachmentInput.click(); }); } if (gifButton && attachmentInput) { gifButton.addEventListener('click', function(e){ e.preventDefault(); if (isUploadingAttachment || attachments.length >= MAX_ATTACHMENTS) { return; } attachmentInput.setAttribute('accept', 'image/gif'); attachmentInput.click(); }); } if (attachmentInput) { attachmentInput.addEventListener('change', function(e){ handleFileSelection(e.target.files); }); } refreshAttachmentPreview(); textarea.addEventListener('input', updateCount); updateCount(); submitBtn.addEventListener('click', async function(e){ e.preventDefault(); var text = textarea.value.trim(); if ((text.length === 0 && attachments.length === 0) || text.length > 300) { return; } var replyUri = textarea.dataset.replyUri; var replyCid = textarea.dataset.replyCid; var rootUri = textarea.dataset.rootUri; var rootCid = textarea.dataset.rootCid; var mode = textarea.dataset.mode || 'reply'; if (mode === 'reply' && (!replyUri || !replyCid)) { return; } submitBtn.disabled = true; var originalText = submitBtn.textContent; submitBtn.textContent = mode === 'post' ? 'Posting...' : 'Replying...'; try { var endpoint, payload; if (mode === 'post') { endpoint = '/api/bluesky/posts'; payload = { text: text }; } else { endpoint = '/api/bluesky/replies'; payload = { text: text, reply_to_uri: replyUri, reply_to_cid: replyCid, root_uri: rootUri, root_cid: rootCid }; } // Attach quote embed if present (for quote posts) - check first var quoteEmbed = textarea.dataset.quoteEmbed; if (quoteEmbed && mode === 'post' && !currentEmbed) { try { payload.embed = JSON.parse(quoteEmbed); console.log('Added quote embed:', payload.embed); } catch (e) { console.error('Failed to parse quote embed', e); } } // Attach image embed if present (overrides quote for now) if (currentEmbed) { payload.embed = currentEmbed; console.log('Added image embed:', payload.embed); } console.log('Submitting payload:', JSON.stringify(payload).substring(0, 300)); var response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': (document.querySelector('meta[name="csrf-token"]') || {}).content || '' }, credentials: 'same-origin', body: JSON.stringify(payload) }); console.log('Reply response status:', response.status); if (!response.ok) { var errorText = await response.text(); var errorMessage = 'Network error. Please try again.'; var settingsUrl = null; try { var errorData = JSON.parse(errorText); errorMessage = errorData.error || errorMessage; settingsUrl = errorData.settings_url || null; } catch (e2) { if (errorText && errorText.length < 200) { errorMessage = errorText; } else { errorMessage = 'HTTP ' + response.status + ': ' + response.statusText; } } console.error('Error response:', response.status, errorMessage); if (response.status === 401 && settingsUrl) { if (confirm(errorMessage + '\n\nWould you like to go to Settings now?')) { window.location.href = settingsUrl; return; } } else { alert(errorMessage); } submitBtn.disabled = false; submitBtn.textContent = originalText; return; } var data = await response.json(); console.log('Reply response data:', data); if (data.success) { window.location.reload(); } else { alert(data.error || (mode === 'post' ? 'Failed to create post' : 'Failed to post reply')); submitBtn.disabled = false; submitBtn.textContent = originalText; } } catch (error) { console.error('Reply error:', error); alert(error && error.message ? error.message : 'Network error. Please try again.'); submitBtn.disabled = false; submitBtn.textContent = originalText; } }); } function openReplyIfRequested(){ try{ var qs = new URLSearchParams(window.location.search); if(qs.get('reply') === '1'){ var panel = document.querySelector('[data-modal-target="panel"]'); if(panel){ panel.classList.remove('hidden'); document.documentElement.classList.add('overflow-hidden'); document.body.classList.add('overflow-hidden'); var ta = document.getElementById('modal-reply-text'); if(ta){ ta.focus(); } } } }catch(e){} } function initReplyModal(){ setupReplyModal(); openReplyIfRequested(); } document.addEventListener('turbo:load', function(){ setTimeout(initReplyModal, 0); }); if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(initReplyModal, 0); } else { document.addEventListener('DOMContentLoaded', initReplyModal); } })(); </script> <script> // Sanitize any legacy nav=1 param from links and the current URL document.addEventListener('click', function(ev){ try{ var a = ev.target.closest && ev.target.closest('a'); if(!a) return; var url = new URL(a.href, location.origin); if(url.searchParams.has('nav')){ url.searchParams.delete('nav'); a.setAttribute('href', url.pathname + (url.searchParams.toString() ? ('?' + url.searchParams.toString()) : '') + url.hash); } }catch(e){} }, true); document.addEventListener('turbo:load', function(){ try{ var url = new URL(location.href); if(url.searchParams.has('nav')){ url.searchParams.delete('nav'); var cleaned = url.pathname + (url.searchParams.toString() ? ('?' + url.searchParams.toString()) : '') + url.hash; history.replaceState(null, '', cleaned); } }catch(e){} }); </script> <script> function loadTwitterEmbeds() { try { if (window.twttr && window.twttr.widgets) { window.twttr.widgets.load(); return true; } } catch(e) {} return false; } document.addEventListener("turbo:load", function() { if (!loadTwitterEmbeds()) { var tries = 0; var max = 10; (function retry(){ if (loadTwitterEmbeds() || ++tries > max) return; setTimeout(retry, 400); })(); } // Re-hydrate Bluesky embeds after Turbo navigation by reinjecting the script try { var s = document.createElement("script"); s.src = "https://embed.bsky.app/static/embed.js?ts=" + Date.now(); s.async = true; s.onload = function(){ try { s.remove(); } catch(_){} }; document.body.appendChild(s); } catch (e) {} }); </script> <script> // Decorate links to lists (home/sections) with stored edition at click time (web only) document.addEventListener('click', function(ev){ try{ var a = ev.target.closest && ev.target.closest('a[data-apply-edition="1"]'); if(!a) return; var pref = localStorage.getItem('preferredEdition'); if(pref && pref !== 'global'){ var url = new URL(a.href, location.origin); url.searchParams.set('edition', pref); if(pref === 'eu'){ url.searchParams.set('signal_scope', 'expanded'); } else { url.searchParams.delete('signal_scope'); } if (url.toString() !== a.href) { // Update href and let the browser/Turbo handle the navigation once a.setAttribute('href', url.pathname + '?' + url.searchParams.toString() + url.hash); } } else { var url2 = new URL(a.href, location.origin); if(url2.searchParams.has('edition') || url2.searchParams.has('signal_scope')){ url2.searchParams.delete('edition'); url2.searchParams.delete('signal_scope'); var newHref = url2.pathname + (url2.searchParams.toString() ? ('?' + url2.searchParams.toString()) : '') + url2.hash; a.setAttribute('href', newHref); } } }catch(e){} }, true); </script> <script> // Track last list path (home or sections) for back navigation on digest pages document.addEventListener('turbo:load', function(){ try{ var path = location.pathname; if(path === '/' || path.indexOf('/sections/') === 0){ var url = new URL(location.href); var stored = path + (url.searchParams.toString() ? ('?' + url.searchParams.toString()) : ''); sessionStorage.setItem('lastListPath', stored); sessionStorage.setItem('lastNonPost', stored); } else if(path === '/trending'){ sessionStorage.setItem('lastTrendingPath', '/trending'); sessionStorage.setItem('lastNonPost', '/trending'); } else if(path.indexOf('/digests/') === 0){ // Keep lastDigestPath fresh when on digest pages to avoid stale fallbacks sessionStorage.setItem('lastDigestPath', path + location.search); sessionStorage.setItem('lastNonPost', path + location.search); } }catch(e){} }); </script> <script> // Remove any l=... language parameter from digest links to unify cache keys document.addEventListener('click', function(ev){ try{ var a = ev.target.closest && ev.target.closest('a[href^="/digests/"]'); if(!a) return; var url = new URL(a.href, location.origin); if(url.searchParams.has('l')){ url.searchParams.delete('l'); var newHref = url.pathname + (url.searchParams.toString() ? ('?' + url.searchParams.toString()) : '') + url.hash; a.setAttribute('href', newHref); } }catch(e){} }, true); </script> <script> // Remember previous page before navigating to a local Bluesky post via anchor document.addEventListener('click', function(ev){ try{ var a = ev.target.closest && ev.target.closest('a[href^="/bsky/"]'); if(!a) return; var prev = location.pathname + location.search; sessionStorage.setItem('prevPage', prev); sessionStorage.setItem('lastPostPath', location.pathname); }catch(e){} }, true); </script> <script> // Remember previous page before navigating to an author profile document.addEventListener('click', function(ev){ try{ // Match both /author and /author/... and /author?... var a = ev.target.closest && (ev.target.closest('a[href^="/author/"]') || ev.target.closest('a[href^="/author"]')); if(!a) return; var prev = location.pathname + location.search; sessionStorage.setItem('prevPage', prev); if(location.pathname.indexOf('/author/') !== 0){ sessionStorage.setItem('lastNonAuthor', prev); } sessionStorage.setItem('entrySource','author'); }catch(e){} }, true); </script> <script> // Also sanitize card-clickable data-href: strip l=... if present document.addEventListener('click', function(ev){ try{ var card = ev.target.closest && ev.target.closest('.card-clickable'); if(!card) return; var href = (card.getAttribute('data-href') || '').trim(); if(!href) return; // Strip accidental wrapping quotes if((href[0]==='"' && href[href.length-1]==='"') || (href[0]==="'" && href[href.length-1]==="'")){ href = href.slice(1,-1); } var url = new URL(href, location.origin); if(url.searchParams.has('l')){ url.searchParams.delete('l'); var newHref = url.pathname + (url.searchParams.toString() ? ('?' + url.searchParams.toString()) : '') + url.hash; card.setAttribute('data-href', newHref); } // Navigate on card click unless clicking an inner actionable link var isInlineVideo = ev.target.closest && ev.target.closest('.inline-video'); // Tag nested-card navigations so back button can return to previous post instead of thread parent var inNested = ev.target.closest && ev.target.closest('.bsky-nested, .nested-bsky, .bluesky-nested'); // Don't navigate if clicking in avatar column area (left side of post with avatar and thread lines) // Check if target itself has the class OR is inside an element with that class var isAvatarColumn = (ev.target.classList && ev.target.classList.contains('avatar-column-no-nav')) || (ev.target.closest && ev.target.closest('.avatar-column-no-nav')); if(!ev.target.closest('.no-card-link') && !isInlineVideo && !isAvatarColumn){ ev.preventDefault(); var targetHref = (card.getAttribute('data-href') || href).trim(); if((targetHref[0]==='"' && targetHref[targetHref.length-1]==='"') || (targetHref[0]==="'" && targetHref[targetHref.length-1]==="'")){ targetHref = targetHref.slice(1,-1); } try{ // If navigating from a digest page to a post, remember this digest for back navigation if(location.pathname.indexOf('/digests/') === 0){ sessionStorage.setItem('lastDigestPath', location.pathname + location.search); sessionStorage.setItem('entrySource','digest'); } else if(location.pathname.indexOf('/bsky/') === 0){ // Navigating from a post to another post (e.g., reply): mark source as post sessionStorage.setItem('entrySource', inNested ? 'nested' : 'post'); } // Always remember the immediate previous page when going to a post if(targetHref.indexOf('/bsky/') === 0){ var prevp = location.pathname + location.search; sessionStorage.setItem('prevPage', prevp); sessionStorage.setItem('lastPostPath', location.pathname); } }catch(e){} if(window.Turbo && Turbo.visit){ Turbo.visit(targetHref); } else { window.location.href = targetHref; } } }catch(e){} }, true); </script> <script> // Keyboard activation for card-clickable elements document.addEventListener('keydown', function(ev){ try{ if(ev.key !== 'Enter' && ev.key !== ' ') return; var card = ev.target.closest && ev.target.closest('.card-clickable'); if(!card) return; var isInlineVideo = ev.target.closest && ev.target.closest('.inline-video'); if(isInlineVideo) return; var href = (card.getAttribute('data-href') || '').trim(); if(!href) return; if((href[0]==='"' && href[href.length-1]==='"') || (href[0]==="'" && href[href.length-1]==="'")){ href = href.slice(1,-1); } ev.preventDefault(); if(window.Turbo && Turbo.visit){ Turbo.visit(href); } else { window.location.href = href; } }catch(e){} }, true); </script> <script> // Sync stored edition from current URL on list pages (no navigation) // Only update when the URL explicitly includes an edition param; otherwise keep prior preference. document.addEventListener("turbo:load", function(){ try{ var onDigest = location.pathname.indexOf('/digests/') === 0; if(onDigest) return; var url = new URL(location.href); if(url.searchParams.has('edition')){ var ed = url.searchParams.get('edition'); localStorage.setItem('preferredEdition', (ed && ed !== 'global') ? ed : 'global'); } else if(url.searchParams.has('signal_scope')){ localStorage.setItem('preferredEdition', 'global'); } }catch(e){} }); </script> <div data-controller="modal" data-bluesky-auth-target="modal" id="bluesky-pds-modal"> <div class="hidden fixed inset-0 z-50 overflow-y-auto" data-modal-target="panel"> <div class="flex min-h-screen items-center justify-center p-4"> <div class="fixed inset-0 bg-black/80 transition-opacity" data-action="click->modal#close"></div> <div class="relative bg-white dark:bg-black rounded-2xl w-full max-w-md p-8 shadow-2xl" data-action="click->modal#stop"> <button type="button" class="absolute top-4 right-4 text-gray-400 hover:text-black dark:hover:text-white transition" data-action="click->modal#close"> <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> <div class="flex justify-center mb-6"> <img alt="LightNews" class="logo-light h-12 w-12" src="/assets/brands/lightnews/logo-light-333069a8.png" /> <img alt="LightNews" class="logo-dark h-12 w-12" src="/assets/brands/lightnews/logo-dark-ef2a5b66.png" /> </div> <h2 class="text-2xl font-bold text-center text-black dark:text-white mb-3">Connect Bluesky</h2> <p class="text-center text-sm text-gray-600 dark:text-gray-300 mb-6"> Enter your Bluesky handle and app password to unlock posting, likes, and your Following feed. </p> <div class="mb-4 hidden text-sm text-red-500" data-bluesky-auth-target="error"></div> <form data-bluesky-auth-target="form" class="space-y-4" action="/auth/bluesky/pds_session" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="J8d0EkSCtgjF28jK5Dtz4YOXOx_zF5WM0Q76L6SPt0OapGz5kRqZyIapzjBA9E-wKEIwX1MPuSl8V6q634yoxQ" autocomplete="off" /> <div> <label class="block text-sm font-semibold text-black dark:text-white mb-2" for="identifier">Handle or email</label> <input placeholder="@you.bsky.social" autocomplete="username" required="required" class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-lg text-black dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent" data-bluesky-auth-target="identifierInput" type="text" name="identifier" id="identifier" /> </div> <div> <label class="block text-sm font-semibold text-black dark:text-white mb-2" for="password">App password</label> <input placeholder="xxxx-xxxx-xxxx-xxxx" autocomplete="current-password" required="required" class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-lg text-black dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent" data-bluesky-auth-target="passwordInput" type="password" name="password" id="password" /> </div> <div class="hidden" data-bluesky-auth-target="tfaField"> <label class="block text-sm font-semibold text-black dark:text-white mb-2" for="auth_factor_token">2FA Code</label> <input placeholder="123456" autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" class="w-full px-4 py-3 bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700 rounded-lg text-black dark:text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent" data-bluesky-auth-target="tfaInput" type="text" name="auth_factor_token" id="auth_factor_token" /> </div> <button type="submit" class="w-full btn btn-primary py-3 font-semibold" data-bluesky-auth-target="submitButton"> Connect Bluesky </button> </form> <p class="mt-5 text-xs text-center text-gray-500 dark:text-gray-400"> Need an app password? Open Bluesky, go to Settings > App passwords, and create a new one. </p> </div> </div> </div> </div> <!-- Bluesky OAuth Modal - Shows when logged-out user tries Bluesky action --> <div id="bluesky-oauth-modal" class="hidden fixed inset-0 z-50 overflow-y-auto" data-bluesky-auth-target="oauthModal"> <div class="flex min-h-screen items-center justify-center p-4"> <!-- Backdrop --> <div class="fixed inset-0 bg-black/80 transition-opacity" data-action="click->bluesky-auth#closeOAuthModal"></div> <!-- Modal Content --> <div class="relative bg-white dark:bg-black rounded-2xl w-full max-w-md p-8 shadow-2xl" data-action="click->bluesky-auth#stopPropagation"> <!-- Close Button --> <button data-action="click->bluesky-auth#closeOAuthModal" class="absolute top-4 right-4 text-gray-400 hover:text-white transition-colors"> <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> <!-- Logo --> <div class="flex justify-center mb-6"> <img alt="LightNews" class="logo-light h-12 w-12" src="/assets/brands/lightnews/logo-light-333069a8.png" /> <img alt="LightNews" class="logo-dark h-12 w-12" src="/assets/brands/lightnews/logo-dark-ef2a5b66.png" /> </div> <!-- Title --> <h2 class="text-black dark:text-white text-3xl font-bold text-center mb-3">Connect with Bluesky</h2> <!-- Description --> <p class="text-center text-base text-gray-600 dark:text-gray-300 mb-6"> Sign in with your Bluesky account to unlock posting, likes, and your Following feed. </p> <!-- Buttons --> <div class="space-y-3"> <!-- Sign In Button - Grey --> <button type="button" data-action="click->bluesky-auth#handleSignIn" data-bluesky-auth-target="signinButton" class="bsky-signin-btn w-full flex items-center justify-center gap-3 px-6 py-3 bg-gray-600 hover:bg-gray-700 font-semibold rounded transition-colors border border-gray-600 text-white"> <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" style="color: #1185fe !important;"> <path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.038.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 01-.415-.056c.14.018.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8z" fill="#1185fe"/> </svg> <span>Sign in</span> </button> <!-- Create Account Button - Blue --> <button type="button" data-action="click->bluesky-auth#handleCreateAccount" data-bluesky-auth-target="signupButton" class="bsky-signup-btn w-full flex items-center justify-center gap-3 px-6 py-3 bg-blue-600 hover:bg-blue-700 font-semibold rounded transition-colors border border-blue-600" style="color: #ffffff !important;"> <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" style="color: #ffffff !important;"> <path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.038.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 01-.415-.056c.14.018.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8z" fill="currentColor"/> </svg> <span style="color: #ffffff !important;">Create an account</span> </button> </div> </div> </div> </div> <div data-controller="cookie-consent" data-turbo-permanent id="cookie-consent-banner"> <div data-cookie-consent-target="banner" class="hidden fixed bottom-0 inset-x-0 z-[9999] p-4 sm:p-6"> <div class="mx-auto max-w-3xl rounded-xl bg-white shadow-2xl ring-1 ring-gray-200 p-4 sm:p-6 flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-4"> <div class="flex-1 text-sm text-gray-700 leading-relaxed"> We use cookies to analyze site traffic and improve your experience. You can accept or reject non-essential cookies. </div> <div class="flex gap-2 shrink-0 w-full sm:w-auto"> <button data-action="cookie-consent#reject" class="flex-1 sm:flex-none px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition"> Reject </button> <button data-action="cookie-consent#accept" class="flex-1 sm:flex-none px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition"> Accept </button> </div> </div> </div> </div> </body> </html>