Skip to content

Instantly share code, notes, and snippets.

@rebane2001
Last active November 14, 2025 02:10
Show Gist options
  • Select an option

  • Save rebane2001/138e8e64980036a8b7b63a528222b404 to your computer and use it in GitHub Desktop.

Select an option

Save rebane2001/138e8e64980036a8b7b63a528222b404 to your computer and use it in GitHub Desktop.
Twitter Card Mod (paste into console) // Note: Moved to https://github.com/rebane2001/TweetsAgainstHumanity/ (get the extension/userscript there)
twitterCardCount = 5;
function getNextTweets(count) {
const nextTweets = [...document.querySelectorAll("[data-testid='cellInnerDiv']:not(:has(>.HiddenTweet)) [data-testid='tweet']:not([data-twc-used])")].slice(0, count);
nextTweets.forEach(e => e.dataset.twcUsed = true);
return nextTweets;
}
function setStyle(styleText) {
let styleEl = document.querySelector(".twc-style");
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.classList.add("twc-style");
document.head.appendChild(styleEl);
}
styleEl.textContent = styleText;
}
function cardNextTweets(count) {
const nextTweets = getNextTweets(count);
nextTweets.forEach((e,i) => {
e.dataset.twcCard = true;
e.setAttribute("style", (e.getAttribute("style") ?? "") + `; --card-offset: ${i/(count-1)}`);
e.addEventListener("click", (ev)=>{ev.preventDefault();pickCard(e)}, {capture:true,once:true});
});
}
function pickCard(cardEl) {
if (window.cardSnd) {
cardSnd.currentTime = 0;
cardSnd.play();
}
const otherCards = [...document.querySelectorAll("[data-twc-card]:not([data-twc-gone])")].filter(e=>e!==cardEl);
otherCards.forEach(e=>e.dataset.twcGone = true);
cardEl.dataset.twcPick = true;
cardNextTweets(twitterCardCount);
}
setStyle(`
html {
scrollbar-width: none;
}
[data-testid='cellInnerDiv']:not(:has([data-twc-used])) {
opacity: 0;
pointer-events: none;
}
[data-testid='cellInnerDiv']:has([data-twc-used]) {
position: fixed !important;
top: 0;
left: 0;
transform: none!important;
transition: display 1s allow-discrete;
&:has([data-twc-gone]) {
display: none;
}
&>*{border-bottom-color:#0000}
}
[data-twc-card] {
position: absolute;
background: #234;
top: 100dvh;
left: 50dvw;
width: 360px;
width: 480px;
width: 400px;
translate: calc(-50% + (var(--card-offset) - 0.5) * 2 * 512px) -80px;
rotate: calc((var(--card-offset) - 0.5) * 2 * 5deg);
border: 2px solid #123;
border-radius: 42px;
corner-shape: superellipse(1.5);
box-shadow: 2px 2px 8px #0004;
padding-right: 12px;
padding-left: 12px;
transition: translate 0.4s, rotate 0.4s, background 0.4s;
&:hover, &:has(:hover) {
background: #345;
translate: calc(-50% + (var(--card-offset) - 0.5) * 2 * 512px) max(calc(-100% - 32px), -256px);
rotate: calc((var(--card-offset) - 0.5) * 2 * 2deg);
}
&[data-twc-pick] {
rotate: 0deg;
/*width: 480px;*/
translate: -50% calc(-50dvh - 50% - 40px);
}
&[data-twc-gone] {
translate: calc(-50% + (var(--card-offset) - 0.5) * 2 * 512px) 8px;
}
[data-testid="tweetText"], [data-testid="tweetPhoto"] {
transition: filter 0.4s;
filter: none;
}
[role="link"]:has([data-testid="Tweet-User-Avatar"]) {
box-shadow: 4px 4px 16px inset #0128;
outline: none;
}
&:not([data-twc-pick]) {
[data-testid="tweetText"], [data-testid="tweetPhoto"] {
transition: none;
filter: blur(12px);
}
/*
[role="link"]:has([data-testid="Tweet-User-Avatar"]), [data-testid="tweetPhoto"] {
max-width: 280px;
overflow: clip;
}
*/
}
}
[data-testid=primaryColumn] {
border: none;
}
[aria-label="Home timeline"]>*:not(:has([data-testid=tweet])), header[role=banner], [data-testid=sidebarColumn], [data-testid=DMDrawer] {
opacity: 0;
pointer-events: none;
}
[aria-label="Home timeline"]>:first-child {
pointer-events: all;
opacity: 1;
position: fixed;
top: 0;
left: 50vw;
width: 50vw;
translate: -50% 0;
nav {
border-bottom-color: #0000;
}
[aria-selected="true"]>*>*>:not(:first-child) {
opacity: 0;
}
}
/*
[data-twc-card] > * {
width: 452px;
flex-grow: 0;
flex-shrink: 0;
}
*/
`);
cardNextTweets(twitterCardCount);
@rebane2001
Copy link
Author

if i release the extension i should call it "tweets against humanity" as per this

@rebane2001
Copy link
Author

todo:

  • improve hiding other elements so that eg overlay still shows comments
  • make the cards more circular
  • make a version that's userscript-compatible

@rebane2001
Copy link
Author

quick guide:

if you copy the code, you can press F12, select the Console tab, and paste the code in the console

note that this is generally considered bad practice unless you can read the code yourself and make sure it is safe

i just haven't had time to make it into an extension yet :c

@sagarchaulagai
Copy link

Here is the userscript.

// ==UserScript==
// @name         Twitter Card Swiper
// @namespace    https://twitter.com/
// @version      1.0
// @description  Turns tweets into swipeable cards with visual effects
// @author       rebane2001
// @match        https://twitter.com/*
// @match        https://x.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const twitterCardCount = 5;

    function getNextTweets(count) {
        const nextTweets = [...document.querySelectorAll("[data-testid='cellInnerDiv']:not(:has(>.HiddenTweet)) [data-testid='tweet']:not([data-twc-used])")].slice(0, count);
        nextTweets.forEach(e => e.dataset.twcUsed = true);
        return nextTweets;
    }

    function setStyle(styleText) {
        let styleEl = document.querySelector(".twc-style");
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.classList.add("twc-style");
            document.head.appendChild(styleEl);
        }
        styleEl.textContent = styleText;
    }

    function cardNextTweets(count) {
        const nextTweets = getNextTweets(count);
        nextTweets.forEach((e,i) => {
            e.dataset.twcCard = true;
            e.setAttribute("style", (e.getAttribute("style") ?? "") + `; --card-offset: ${i/(count-1)}`);
            e.addEventListener("click", (ev)=>{ev.preventDefault();pickCard(e)}, {capture:true,once:true});
        });
    }

    function pickCard(cardEl) {
        if (window.cardSnd) {
            cardSnd.currentTime = 0;
            cardSnd.play();
        }
        const otherCards = [...document.querySelectorAll("[data-twc-card]:not([data-twc-gone])")].filter(e=>e!==cardEl);
        otherCards.forEach(e=>e.dataset.twcGone = true);
        cardEl.dataset.twcPick = true;
        cardNextTweets(twitterCardCount);
    }

    setStyle(`
html {
    scrollbar-width: none;
}

[data-testid='cellInnerDiv']:not(:has([data-twc-used])) {
    opacity: 0;
    pointer-events: none;
}

[data-testid='cellInnerDiv']:has([data-twc-used]) {
    position: fixed !important;
    top: 0;
    left: 0;
    transform: none!important;
    transition: display 1s allow-discrete;
}
[data-testid='cellInnerDiv']:has([data-twc-gone]) {
    display: none;
}
[data-testid='cellInnerDiv']:has([data-twc-used])>*{border-bottom-color:#0000}

[data-twc-card] {
    position: absolute;
    background: #234;
    top: 100dvh;
    left: 50dvw;
    width: 400px;
    translate: calc(-50% + (var(--card-offset) - 0.5) * 2 * 512px) -80px;
    rotate: calc((var(--card-offset) - 0.5) * 2 * 5deg);
    border: 2px solid #123;
    border-radius: 42px;
    box-shadow: 2px 2px 8px #0004;
    padding: 0 12px;
    transition: translate 0.4s, rotate 0.4s, background 0.4s;
}

[data-twc-card]:hover, [data-twc-card]:has(:hover) {
    background: #345;
    translate: calc(-50% + (var(--card-offset) - 0.5) * 2 * 512px) max(calc(-100% - 32px), -256px);
    rotate: calc((var(--card-offset) - 0.5) * 2 * 2deg);
}

[data-twc-card][data-twc-pick] {
    rotate: 0deg;
    translate: -50% calc(-50dvh - 50% - 40px);
}

[data-twc-card][data-twc-gone] {
    translate: calc(-50% + (var(--card-offset) - 0.5) * 2 * 512px) 8px;
}

[data-twc-card] [data-testid="tweetText"],
[data-twc-card] [data-testid="tweetPhoto"] {
    transition: filter 0.4s;
    filter: none;
}

[data-twc-card] [role="link"]:has([data-testid="Tweet-User-Avatar"]) {
    box-shadow: 4px 4px 16px inset #0128;
    outline: none;
}

[data-twc-card]:not([data-twc-pick]) [data-testid="tweetText"],
[data-twc-card]:not([data-twc-pick]) [data-testid="tweetPhoto"] {
    transition: none;
    filter: blur(12px);
}

[data-testid=primaryColumn] {
    border: none;
}

[aria-label="Home timeline"]>*:not(:has([data-testid=tweet])),
header[role=banner],
[data-testid=sidebarColumn],
[data-testid=DMDrawer] {
    opacity: 0;
    pointer-events: none;
}

[aria-label="Home timeline"]>:first-child {
    pointer-events: all;
    opacity: 1;
    position: fixed;
    top: 0;
    left: 50vw;
    width: 50vw;
    translate: -50% 0;
}

[aria-label="Home timeline"]>:first-child nav {
    border-bottom-color: #0000;
}

[aria-label="Home timeline"]>:first-child [aria-selected="true"]>*>*>:not(:first-child) {
    opacity: 0;
}
`);

    // Run after DOM loads
    const observer = new MutationObserver(() => {
        if (document.querySelector("[data-testid='tweet']")) {
            cardNextTweets(twitterCardCount);
            observer.disconnect();
        }
    });
    observer.observe(document.body, {childList: true, subtree: true});

})();

@cat8587
Copy link

cat8587 commented Nov 12, 2025

Support for light and lights out themes

// ==UserScript==
// @name         Twitter Card Swiper
// @namespace    https://twitter.com/
// @version      1.0.1
// @description  Turns tweets into swipeable cards with visual effects
// @author       rebane2001
// @match        https://twitter.com/*
// @match        https://x.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const twitterCardCount = 5;

    function getNextTweets(count) {
        const nextTweets = [...document.querySelectorAll("[data-testid='cellInnerDiv']:not(:has(>.HiddenTweet)) [data-testid='tweet']:not([data-twc-used])")].slice(0, count);
        nextTweets.forEach(e => e.dataset.twcUsed = true);
        return nextTweets;
    }

    function setStyle(styleText) {
        let styleEl = document.querySelector(".twc-style");
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.classList.add("twc-style");
            document.head.appendChild(styleEl);
        }
        styleEl.textContent = styleText;
    }

    function cardNextTweets(count) {
        const nextTweets = getNextTweets(count);
        nextTweets.forEach((e,i) => {
            e.dataset.twcCard = true;
            e.setAttribute("style", (e.getAttribute("style") ?? "") + `; --card-offset: ${i/(count-1)}`);
            e.addEventListener("click", (ev)=>{ev.preventDefault();pickCard(e)}, {capture:true,once:true});
        });
    }

    function pickCard(cardEl) {
        if (window.cardSnd) {
            cardSnd.currentTime = 0;
            cardSnd.play();
        }
        const otherCards = [...document.querySelectorAll("[data-twc-card]:not([data-twc-gone])")].filter(e=>e!==cardEl);
        otherCards.forEach(e=>e.dataset.twcGone = true);
        cardEl.dataset.twcPick = true;
        cardNextTweets(twitterCardCount);
    }

    setStyle(`
html {
    scrollbar-width: none;
}

[data-testid='cellInnerDiv']:not(:has([data-twc-used])) {
    opacity: 0;
    pointer-events: none;
}

[data-testid='cellInnerDiv']:has([data-twc-used]) {
    position: fixed !important;
    top: 0;
    left: 0;
    transform: none!important;
    transition: display 1s allow-discrete;
}
[data-testid='cellInnerDiv']:has([data-twc-gone]) {
    display: none;
}
[data-testid='cellInnerDiv']:has([data-twc-used])>*{border-bottom-color:#0000}

html.Default [data-twc-card] {
    --bg-color-1: #EEE;
    --bg-color-2: #FFF;
    --border-color: #888;
}

html.Dim [data-twc-card] {
    --bg-color-1: #234;
    --bg-color-2: #345;
    --border-color: #123;
}

html.LightsOut [data-twc-card] {
    --bg-color-1: #122;
    --bg-color-2: #233;
    --border-color: #234;
}

[data-twc-card] {
    position: absolute;
    background: var(--bg-color-1);
    top: 100dvh;
    left: 50dvw;
    width: 400px;
    translate: calc(-50% + (var(--card-offset) - 0.5) * 2 * 512px) -80px;
    rotate: calc((var(--card-offset) - 0.5) * 2 * 5deg);
    border: 2px solid var(--border-color);
    border-radius: 42px;
    box-shadow: 2px 2px 8px #0004;
    padding: 0 12px;
    transition: translate 0.4s, rotate 0.4s, background 0.4s;
}

[data-twc-card]:hover, [data-twc-card]:has(:hover) {
    background: var(--bg-color-2);
    translate: calc(-50% + (var(--card-offset) - 0.5) * 2 * 512px) max(calc(-100% - 32px), -256px);
    rotate: calc((var(--card-offset) - 0.5) * 2 * 2deg);
}

[data-twc-card][data-twc-pick] {
    rotate: 0deg;
    translate: -50% calc(-50dvh - 50% - 40px);
}

[data-twc-card][data-twc-gone] {
    translate: calc(-50% + (var(--card-offset) - 0.5) * 2 * 512px) 8px;
}

[data-twc-card] [data-testid="tweetText"],
[data-twc-card] [data-testid="tweetPhoto"] {
    transition: filter 0.4s;
    filter: none;
}

[data-twc-card] [role="link"]:has([data-testid="Tweet-User-Avatar"]) {
    box-shadow: 4px 4px 16px inset #0128;
    outline: none;
}

[data-twc-card]:not([data-twc-pick]) [data-testid="tweetText"],
[data-twc-card]:not([data-twc-pick]) [data-testid="tweetPhoto"] {
    transition: none;
    filter: blur(12px);
}

[data-testid=primaryColumn] {
    border: none;
}

[aria-label="Home timeline"]>*:not(:has([data-testid=tweet])),
header[role=banner],
[data-testid=sidebarColumn],
[data-testid=DMDrawer] {
    opacity: 0;
    pointer-events: none;
}

[aria-label="Home timeline"]>:first-child {
    pointer-events: all;
    opacity: 1;
    position: fixed;
    top: 0;
    left: 50vw;
    width: 50vw;
    translate: -50% 0;
}

[aria-label="Home timeline"]>:first-child nav {
    border-bottom-color: #0000;
}

[aria-label="Home timeline"]>:first-child [aria-selected="true"]>*>*>:not(:first-child) {
    opacity: 0;
}
`);

    // Run after DOM loads
    const observer = new MutationObserver(() => {
        if (document.querySelector("[data-testid='tweet']")) {
            cardNextTweets(twitterCardCount);
            observer.disconnect();
        }
    });
    observer.observe(document.body, {childList: true, subtree: true});

})();

@rebane2001
Copy link
Author

Moved to https://github.com/rebane2001/TweetsAgainstHumanity/ (get the extension/userscript there)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment