NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name CleanerReads - A Goodreads Theme
// @description Muting and moving some UI elements in favor of text. Nothing changed, just rearranged.
// @version 1.4.3
// @author icetbr
// @icon https://www.google.com/s2/favicons?sz=64&domain=goodreads.com
// @license MIT
// @namespace https://github.com/icetbr/userscripts
// @updateURL https://openuserjs.org/meta/icetbr/CleanerReads.meta.js
// @downloadURL https://openuserjs.org/src/scripts/icetbr/CleanerReads.user.js
// @grant none
// ==/UserScript==
var style = /*css*/`
/*****************/
/* SIDEBAR */
/*****************/
/* LEFT */
.BookPage__leftColumn .Sticky { position: relative !important; top: 15px !important; z-index: unset } /* z-index needed to prevent being in front of profile menu */
.BookPage__rightColumn { overflow: unset } /* allows moving content to it */
.BookActions > .BookRatingStars { display: none } /* hide Community Rating (duplicated) */
.RatingStatistics { flex-flow: column wrap; align-items: center; }
/*[Want to read], [Buy on Amazon]*/
.BookActions { position: absolute; top: 407px; left: 72px; color: var(--color-text-subdued); }
.BookActions__editActivityButton { display: none; }
/* pencil icon */
.Button.Button--secondary.Button--medium.Button--block { margin-left: -4px; }
.PencilIcon .Button.Button--secondary.Button--medium.Button--block span:first-child { display: none;}
/* top sticky header as a sidebar */
.SiteHeaderBanner__topFullImageContainer,
.siteHeader__topFullImageContainer /* selector for old goodreads design */
{ position: absolute; transform-origin: top right; right: 0; top: 1140px; transform: rotate(90deg); }
.ResponsiveImage { max-height: 330px; } /* resizes large covers to prevent overlap */
/*****************/
/* TOPBAR */
/*****************/
.NotificationsTray { z-index: 1} /* prevent being behing content */
/* nearly invisible top navbar */
.Header { position: unset; box-shadow: unset; background: unset; color: #00000021; }
input::placeholder { color: #0000005c; }
.Header__siteHeaderBanner { height: unset; }
.PageFrame { padding-top: unset; }
.HeaderSearch__input { border: 0.1rem solid #0000; }
.Icon.ShareIcon svg { fill: #00000021; } /* share icon */
.HeaderSearch__button { opacity: 0.3; }
.Text__title3 { font-size: 1.5rem; } /* Book Series title */
.HeaderSecondaryNav { right: 43px; } /* align second nav icons with cover */
.HeaderNavDropdown { z-index: 9999 } /* .Header position uset made this go to the back */
/*****************/
/* MAIN */
/*****************/
/* Book Details */
.ChoiceAwardBadge { margin-top: 10px; }
.BookPageMetadataSection__contributor { display: none; } /* hides Author (duplicated) */
.BookPage__mainContent .TruncatedContent__text--large { max-height: unset; } /* book description always expanded */
.BookPageMetadataSection__genrePlainText { display: none; } /* hides "genre" word */
.BookPageMetadataSection__genres { margin-bottom: -4px; }
.WorkDetails { margin-top: 3.2rem; } /* Literary awards */
/* .BookDetails .Button__container { top: -4px; left: 376px; } messes up the More Editions arrows*/
[aria-label="Book details and editions"] { top: -4px; left: 386px; position: relative; }
.BookDetails .FeaturedDetails { margin-bottom: -3rem; overflow: clip; display: flex; } /* flex brings pages count and publish date close together */
.BookDetails + hr.Divider { display: none }
.FeaturedDetails p + p::before { content: ',\\00a0'; } /* spacing in 352 pages, January 19, 2023 */
.BookDetails__list { margin-top: 35px; } /* "This Edition" overlaping when fewer details displayed. Ex: https://www.goodreads.com/book/show/60741407-a-christmas-memory */
/* 1,004 reading · 11.4k want to read */
.AvatarGroup { display: none; }
.SocialSignalsSection { color: var(--color-text-body-light); }
.SocialSignalCard__caption { margin-left: unset; }
.SocialSignalCard { min-height: unset; }
.SocialSignalsSection__container { display: unset; }
.SocialSignalsSection__signal { margin-right: unset; display: inline-block; }
/* About Author */
.PageSection__title { display: none; }
.PageSection { margin-top: 20px; width: 550px;}
.PageSection .DetailsLayoutRightParagraph__widthConstrained { grid-column: span 10; }
.PageSection .Divider,
.lazyload-wrapper .Divider { width: 693px; }
/*****************/
/* REVIEWS */
/*****************/
/* My Review */
.ReviewsSection__header { display: none; }
.WriteReviewCTA .Avatar { display: none; }
.WriteReviewCTA__hero { display: none; } /* What do you Think title */
#SocialReviews .Text__title3 { display: none; }
.MyReviewCard { left: -193px; width: 730px;} /* center align review */
.MyReviewCardCarousel .DynamicCarousel__itemsArea { overflow: unset; }
.Alert--informational { background-color: unset; }
.Alert { padding: unset; color: #00000021; text-align: center; }
.WriteReviewCTA { padding: unset; width: 600px;}
.ReviewsList__listHeader { margin: unset; }
/* Reviews */
.BookPage__reviewsSection { width: 550px; }
.ReviewsSectionStatistics__histogram { width: unset; }
.ReviewFilters__filterControl { width: unset; }
#CommunityReviews { display: none } /* hide title */
.ReviewsSectionStatistics .ReviewsSectionStatistics__ratingStatistics { display: none; } /* hide starts count, already shown on sidebar */
/* user reviews gravatar to the left, content centered */
#ReviewsSection .ReviewCard { left: -190px; width: 720px;}
#ReviewsSection > .Divider { width: 693px;}
.FeaturedPerson__info { width: 515px; }
.TruncatedContent__text--large { max-height: 48rem; }
.TruncatedContent__text--expanded { max-height: none; overflow-y: visible; }
/* user's reviews */
.SocialFooter { display: flex; flex-wrap: wrap; }
.SocialFooter .Button { font-weight: 400; }
.ReviewCard .Text__title4 { font-weight: 400; }
div[data-testid="actions"] { margin-left: 15px; margin-top: -1px; }
/*****************/
/* COMMON */
/*****************/
.Formatted {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
hyphens: auto;
}
/* hr.Divider { width: 693px } */
/*****************/
/* BUTTONS */
/*****************/
.BookActions .Button { border-radius: unset; font-weight: unset; }
.BookActions__button { margin: unset; width: 100%;}
.Button--medium { padding: 5px; padding-right: 12px; height: 2.5rem; }
.Button--wtr { background-color: unset; color: unset; }
.Button--wtr:hover { background-color: unset; box-shadow: inset 0 -3px 0 0 var(--color-background-wtr-hover); }
.Button { transition: unset; border: 0; } /* border-radius: unset; font-weight: unset; */
.Button--buy { color: unset; }
.Button--buy:hover { background-color: unset; box-shadow: inset 0 -3px 0 0 var(--color-text-button-transparent-base); }
.Button--medium.Button--rounded { width: 3em; }
.Button--rounded { border-radius: unset; }
.Button--small { height: unset; padding: unset; }
.Button--large { height: unset; padding: unset; }
.Button--block { width: unset; }
.Button--primary:hover {
box-shadow: inset 0 -2px 0 0 var(--color-background-primary-base);
background-color: unset;
}
.Button--primary {
background-color: unset;
color: var(--color-background-primary-base);
border-radius: unset;
}
.Button--buy:active, .Button--buy:focus, .Button--inline.Button--active:active, .Button--inline.Button--active:focus, .Button--inline:active, .Button--inline:focus, .Button--primary:active, .Button--primary:focus, .Button--secondary.Button--active:active, .Button--secondary.Button--active:focus, .Button--secondary:active, .Button--secondary:focus, .Button--signinwithamazon:active, .Button--signinwithamazon:focus, .Button--signinwithapple:active, .Button--signinwithapple:focus, .Button--signinwithfacebook:active, .Button--signinwithfacebook:focus, .Button--tag-inline:active, .Button--tag-inline:focus, .Button--tag-selector:active, .Button--tag-selector:focus, .Button--transparent:active, .Button--transparent:focus, .Button--wtr:active, .Button--wtr:focus {
box-shadow: unset;
}
.ButtonGroup .Button__container:not(:first-child) .Button:active, .ButtonGroup .Button__container:not(:first-child) .Button:focus {
box-shadow: inset 0 -3px 0 0 var(--color-background-wtr-hover);
}
.ButtonGroup .Button__container:not(:first-child) .Button:active, .ButtonGroup .Button__container:not(:first-child) .Button--wtr:focus {
box-shadow: inset 0 -3px 0 0 var(--color-background-wtr-hover);
}
.ButtonGroup .Button__container:not(:first-child) .Button:active, .ButtonGroup .Button__container:not(:first-child) .Button--buy:focus {
box-shadow: inset 0 -3px 0 0 var(--color-text-button-transparent-base);
}
.BookPage__bookCover { margin-bottom: 200px !important } /* needed for content moved to the sidebar */
/* .BookPageMetadataSection__genreButton { padding: 0 4px 0px 0; } */
.BookPageMetadataSection__genreButton a { height: 30px; }
.Button--tag:after, .Button--tag:before { bottom: 3px }
/*****************/
/* Conditionals */
/*****************/
/* coverOnLeft */
body.coverOnLeft .BookPageMetadataSection__ratingStats { position: absolute; left: -334px; top: 344px; } /* move to the sidebar */
body.coverOnLeft .BookPage__rightColumn { grid-column-start: 4; grid-column-end: 11; grid-row-start: 1; } /* make non sticky */
body.coverOnLeft .BookPage__leftColumn { margin-left: -44px; }
body.coverOnLeft .DetailsLayoutRightParagraph__widthConstrained { grid-column: span 8; }
/* coverOnRight */
body.coverOnRight .BookPageMetadataSection__ratingStats { position: absolute; right: -314px; top: 344px; } /* move to the sidebar */
body.coverOnRight .BookPage__leftColumn { grid-column-start: 10; grid-column-end: 13; } /* move to the right */
body.coverOnRight .BookPage__rightColumn { grid-column-start: 3; grid-column-end: 10; grid-row-start: 1; } /* make non sticky */
/* authorToTheSidebar */
/* when loading, the author section is called PageSection, after loading it is .lazyload-wrapper */
body.authorToTheSidebar .BookPage__leftColumn .PageSection,
body.authorToTheSidebar .BookPage__leftColumn .lazyload-wrapper { font-size: 14px; width: 320px; }
body.authorToTheSidebar .BookPage__leftColumn .PageSection .Divider,
body.authorToTheSidebar .BookPage__leftColumn .lazyload-wrapper .Divider { width: 305px; }
body.authorToTheSidebar .AuthorPreview .FeaturedPerson__infoPrimary { width: 250px; }
body.authorToTheSidebar .ContributorLinksList { font-size: 1.2rem; }
body.authorToTheSidebar .ContributorLink__role { font-size: 1.0rem; }
/* body.authorToTheSidebar .FeaturedPerson__profile { overflow: scroll } */
body.authorToTheSidebar .DetailsLayoutRightParagraph__widthConstrained { grid-column: span 10; }
/* muteTopbar */
/* OLD TOPBAR */
body.muteTopbar .siteHeader__topLine { position: unset; box-shadow: unset; background: unset }
body.muteTopbar .siteHeader .siteHeader__topLevelLink, .siteHeader .primaryNavMenu__trigger { color: #00000021; }
body.muteTopbar .siteHeader input { border: 0.1rem solid #0000; }
body.muteTopbar .siteHeader .searchBox__icon--magnifyingGlass { opacity: 0.3; }
body.muteTopbar .siteHeader input::placeholder { color: #0000005c !important; }
body.muteTopbar .siteHeader { position: unset; }
body.muteTopbar .siteHeaderBottomSpacer { display: none }
/* widerText */
body.widerText .BookPageMetadataSection__description .DetailsLayoutRightParagraph__widthConstrained { width: 690px; } /* book description */
body.widerText .BookPage__mainContent .TruncatedContent:nth-child(3) { width: 700px; } /* author description */
body.widerText .MyReviewCard__content { width: 700px; }
body.widerText .ReviewCard__content { width: 700px; }
body.widerText .BookPageMetadataSection__description .TruncatedContent__text { overflow: visible !important; }
/* readersEnjoyedToTheSidebar */
body.readersEnjoyedToTheSidebar .BookPage__relatedTopContent .DynamicCarousel__item { margin-right: 0px; }
body.readersEnjoyedToTheSidebar .BookPage__relatedTopContent .BookCard__content { display: none }
body.readersEnjoyedToTheSidebar .BookPage__relatedTopContent .Divider { display: none }
body.coverOnLeft.authorToTheSidebar.readersEnjoyedToTheSidebar .MyReviewCard__profile > .ReviewerProfile { margin-left: 950px; position: absolute; }
/* sharpCorners */
body.sharpCorners .BookCover__image { border-radius: unset }
body.hide-CurrentlyReading section:has([data-react-class="ReactComponents.CurrentlyReading"]),
body.hide-ReadingChallenge section:has([data-react-class="ReactComponents.ReadingChallenge"]),
body.hide-ShelfDisplay section:has([data-react-class="ReactComponents.ShelfDisplay"]),
body.hide-UserShelvesBookCounts section:has([data-react-class="ReactComponents.UserShelvesBookCounts"]),
body.hide-EditorialBlogThumbnail section:has([data-react-class="ReactComponents.EditorialBlogThumbnail"]),
body.hide-RecommendationsWidget section:has([data-react-class="ReactComponents.RecommendationsWidget"]),
body.hide-ChoiceWidget section:has([data-react-class="ReactComponents.ChoiceWidget"]),
body.hide-Footer [data-react-class="ReactComponents.Footer"],
body.hide-SiteAnnouncement [data-react-class="ReactComponents.SiteAnnouncement"],
body.hide-GoogleBannerAd .Ad,
body.hide-ColoredTopBar .SiteHeaderBanner__topFullImageContainer,
body.hide-ColoredTopBar .siteHeader__topFullImageContainer
{
display: none !important;
}
`;
const assign = Object.assign,
isPlainObject = (o) => o?.constructor === Object;
const $ = (s, p = document) => p.querySelector(s),
h = new Proxy({} , {
get: (_, tag) => (propsOrChild, ...children) => {
const isProps = isPlainObject(propsOrChild);
const el = assign(document.createElement(tag), isProps ? propsOrChild : {});
el.append(...(isProps ? children : [propsOrChild, ...children]));
if (isProps && propsOrChild.popovertarget) el.setAttribute('popovertarget', propsOrChild.popovertarget);
return el
}
}),
waitForEl = (selector) => new Promise(resolve => {
if ($(selector)) return resolve($(selector));
const observer = new MutationObserver(() => {
if (!$(selector)) return;
resolve($(selector));
observer.disconnect();
});
observer.observe(document.documentElement, { childList: true, subtree: true });
return observer;
}),
switchStyle = (nameA, nameB, isAOn) => {
document.body.classList.toggle(nameA, isAOn);
document.body.classList.toggle(nameB, !isAOn);
},
toggleStyle = (name, isOn) => document.body.classList.toggle(name, isOn),
addStyle = (css) => document.body.appendChild(document.createElement("style")).append(css); // needs to be body to ovewrite page's style; don't want to use h helper because I usually use this alone
const
addOptionsMenu = (options, savedOptions) => {
const { label, input, p, b, div, button, style } = h;
const save = (id) => (e) => {
options[id][1](e.target.checked);
savedOptions[id] = e.target.checked;
localStorage.setItem('options', JSON.stringify(savedOptions));
};
const $options = div({id: 'optionsMenu', popover: ''});
for(const id in options) {
if (id === 'CurrentlyReading') $options.append(p(b("Hide from Home page")));
$options.append(
label(input({ type: 'checkbox', id, checked: !!savedOptions[id], onchange: save(id) }), options[id][0])
);
}
const $optionsBtn = button({id: 'optionsBtn', popovertarget: 'optionsMenu'}, '⚙');
const $style = style(`
#optionsMenu { border: 1px solid #d7d7db; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,.2); margin-right: 5px; margin-top: 40px; }
#optionsMenu > * { display: block; }
#optionsBtn {
background: none; border: none; padding: 5px; cursor: pointer; /* Reset button styles*/
position: fixed; top: 5px; right: 5px; z-index: 9999;
font-size: 26px; opacity: 0.4; transition: opacity 0.2s, transform 0.2s;
}
#optionsBtn:hover { opacity: 1; transform: scale(1.1); }
`);
const $host = div({id: 'options'});
const shadow = $host.attachShadow({ mode: 'open' });
shadow.append($style, $optionsBtn, $options);
document.body.append($host);
},
styleBookPages = () => {
toggleStyle('coverOnRight', true);
// 352 pages, Kindle Edition
const $pagesFormat = $('p[data-testid="pagesFormat"]');
if ($pagesFormat) $pagesFormat.innerText = $pagesFormat.innerText.replace(/(.*pages).*/, '$1');
// Expected publication January 19, 2023
const dateRegex = /.*((January|February|March|April|May|June|July|August|September|October|November|December).*)/;
const $publicationInfo = $('p[data-testid="publicationInfo"]');
if ($publicationInfo) $publicationInfo.innerText = $publicationInfo.innerText.replace(dateRegex, '$1');
// 263 people are currently reading
waitForEl('div[data-testid="currentlyReadingSignal"]').then(el => el.innerText = el.innerText.replace(' people are currently reading', ' reading ·'));
// 11.4k people want to read
waitForEl('div[data-testid="toReadSignal"]').then(el => el.innerText = el.innerText.replace(' people', ''));
// moves authors to the bio section
waitForEl('.AuthorPreview .ContributorLink').then(el => el.replaceWith($('.ContributorLinksList')));
},
styleOptionals = async () => {
const
/* when loading, the author section is called PageSection, after loading it is .lazyload-wrapper */
toggleAboutTheAuthorToTheSidebar = isOn => {
toggleStyle('authorToTheSidebar', isOn);
waitForEl('.PageSection').then(el =>
waitForEl(isOn ? '.BookPage__leftColumn .Sticky .BookActions' : '.SocialSignalsSection ~.Divider').then(p => p.insertAdjacentElement('afterend', el))
);
},
toggleReadersEnjoyedSidebar = isOn => {
toggleStyle('readersEnjoyedToTheSidebar', isOn);
waitForEl('.BookPage__relatedTopContent').then(el =>
isOn
? $('.BookPage__leftColumn .Sticky').append(el)
: $('.BookPage__mainContent').insertAdjacentElement('afterend', el));
},
options = {
moveAboutTheAuthorToTheSidebar: ["Move About the Author to the sidebar", isOn => toggleAboutTheAuthorToTheSidebar(isOn)],
moveReadersAlsoEnjoyedToTheSidebar: ["Move Readers Also Enjoyed to the sidebar", isOn => toggleReadersEnjoyedSidebar(isOn)],
showCoverOnTheLeft: ["Show cover on the left", isOn => switchStyle('coverOnLeft', 'coverOnRight', isOn)],
removeBookCoverRoundedCorners: ["Remove book cover rounded corners", isOn => toggleStyle('sharpCorners', isOn)],
makeTextWider: ["Make text wider", isOn => toggleStyle('widerText', isOn)],
GoogleBannerAd: ["Hide Google Ads (AdBlock is better)", isOn => toggleStyle('hide-GoogleBannerAd', isOn)],
ColoredTopBar: ["Hide Colored side banner", isOn => toggleStyle('hide-ColoredTopBar', isOn)],
CurrentlyReading: ["Currently Reading", isOn => toggleStyle('hide-CurrentlyReading', isOn)],
ReadingChallenge: ["Reading Challenge", isOn => toggleStyle('hide-ReadingChallenge', isOn)],
ShelfDisplay: ["Want to Read", isOn => toggleStyle('hide-ShelfDisplay', isOn)],
UserShelvesBookCounts: ["Bookshelves", isOn => toggleStyle('hide-UserShelvesBookCounts', isOn)],
EditorialBlogThumbnail: ["News & Interviews", isOn => toggleStyle('hide-EditorialBlogThumbnail', isOn)],
RecommendationsWidget: ["Recommendations", isOn => toggleStyle('hide-RecommendationsWidget', isOn)],
ChoiceWidget: ["Goodreads Choice Awards", isOn => toggleStyle('hide-ChoiceWidget', isOn)],
Footer: ["Company / Work With Us / Connect", isOn => toggleStyle('hide-Footer', isOn)],
SiteAnnouncement: ["Site Announcements (top of middle column)", isOn => toggleStyle('hide-SiteAnnouncement', isOn)],
},
savedOptions = JSON.parse(localStorage.getItem('options') || '{}');
await waitForEl(isHomePage ? '[data-react-class="ReactComponents.UserShelvesBookCounts"]' : '.BookCover__image');
addOptionsMenu(options, savedOptions);
for (const i in savedOptions) options[i][1](savedOptions[i]);
},
isHomePage = location.href === 'https://www.goodreads.com/' || location.href.startsWith('https://www.goodreads.com/?'),
isBookPage = location.href.includes('/book/show/'),
init = async () => {
console.log('[cleanerreads]', 'adding styles');
addStyle(style);
if (!isHomePage) toggleStyle('muteTopbar', true);
if (isBookPage) styleBookPages();
await styleOptionals();
};
waitForEl('body').then(init);