Last active
November 14, 2025 02:10
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); |
Author
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
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
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});
})();
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});
})();
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
if i release the extension i should call it "tweets against humanity" as per this