' +
75 | // If video description is found, use it, otherwise fallback to generic description element.
76 | (feedItem.querySelector('.enclosure-description')?.innerHTML.trim() ||
77 | feedItem.querySelector('article div.text')?.innerHTML.trim() || '') +
78 | '
',
79 | video_youtube_url: youubeUrl,
80 | video_invidious_redirect_url: `${youtubeId ? invidiousRedirectPrefixUrl + youtubeId : ''}`
81 | };
82 | }
83 |
84 | function createModalWithData(data) {
85 | // Create custom modal
86 | let modal = document.getElementById('youlagTheaterModal');
87 |
88 | if (!modal) {
89 | modal = document.createElement('div');
90 | modal.id = 'youlagTheaterModal';
91 | modal.innerHTML = `
99 |
100 |
103 |
104 |
105 |
106 |

107 |
108 |
109 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
162 |
163 |
164 |
165 |
166 | ${data.video_description}
167 |
168 |
169 |
170 |
171 |
172 | `;
173 |
174 |
175 | if (!youtubeId) {
176 | // Not a video feed item
177 | modal.classList.add('youlag-modal-feed-item--text');
178 | let iframeContainer = document.querySelector('.youlag-iframe-container');
179 | if (iframeContainer) {
180 | document.querySelector('.youlag-iframe-container').remove();
181 | }
182 | }
183 |
184 |
185 | container.querySelector(`#${modalCloseIdName}`)?.addEventListener('click', closeModal);
186 | container.querySelector(`#${modalToggleFavoriteIdName}`)?.addEventListener('click', (e) => {
187 | // Toggle favorites state in background
188 | e.preventDefault();
189 | toggleFavorite(data.favorite_toggle_url, container, data.feedItemEl);
190 | });
191 |
192 | // Push a new state to the history, to allow modal close when routing back.
193 | history.pushState({ modalOpen: true }, '', '');
194 |
195 | // Close theater modal on Esc key
196 | document.addEventListener('keydown', (event) => {
197 | if (event.key === 'Escape') {
198 | closeModal();
199 | }
200 | });
201 |
202 | window.addEventListener('popstate', closeModal);
203 | }
204 |
205 | function toggleFavorite(url, container, feedItemEl) {
206 | const favoriteButton = container.querySelector(`#${modalToggleFavoriteIdName}`);
207 | if (!favoriteButton) return;
208 |
209 | fetch(url, { method: 'GET' })
210 | .then(response => {
211 | if (response.ok) {
212 | // Toggle favorite classes and icons
213 | const currentlyTrue = favoriteButton.classList.contains(`${modalFavoriteClassName}--true`);
214 | const bookmarkIcon = feedItemEl.querySelector('.item-element.bookmark img.icon');
215 | favoriteButton.classList.remove(`${modalFavoriteClassName}--${currentlyTrue}`);
216 | favoriteButton.classList.add(`${modalFavoriteClassName}--${!currentlyTrue}`);
217 |
218 | if (currentlyTrue) {
219 | feedItemEl.classList.remove('favorite');
220 | if (bookmarkIcon) {
221 | bookmarkIcon.src = '../themes/Mapco/icons/non-starred.svg';
222 | }
223 | } else {
224 | feedItemEl.classList.add('favorite');
225 | if (bookmarkIcon) {
226 | bookmarkIcon.src = '../themes/Mapco/icons/starred.svg';
227 | }
228 | }
229 | } else {
230 | console.error('Failed to toggle favorite status');
231 | }
232 | })
233 | .catch(error => console.error('Error:', error));
234 | }
235 |
236 | function closeModal() {
237 | disableBodyScroll(false);
238 | const modal = document.getElementById('youlagTheaterModal');
239 | if (modal) modal.remove();
240 | if (history.state && history.state.modalOpen) {
241 | history.back();
242 | }
243 | }
244 |
245 | function setupClickListener() {
246 | const streamContainer = document.querySelector('#stream');
247 |
248 | if (streamContainer) {
249 | streamContainer.addEventListener('click', (event) => {
250 | // Prevent activation if clicked element is inside .flux_header li.
251 | // These are the feed item actions buttons.
252 | if (event.target.closest('div[data-feed] .flux_header li.manage')) return;
253 | if (event.target.closest('div[data-feed] .flux_header li.labels')) return;
254 | if (event.target.closest('div[data-feed] .flux_header li.share')) return;
255 | if (event.target.closest('div[data-feed] .flux_header li.link')) return;
256 | if (event.target.closest('div[data-feed] .flux_header .website a[href^="./?get=f_"]')) return;
257 | const target = event.target.closest('div[data-feed]');
258 |
259 | if (target) {
260 | handleActiveRssItem(event);
261 | collapseBackgroundFeedItem(target);
262 | }
263 | });
264 | }
265 | }
266 |
267 | function collapseBackgroundFeedItem(target) {
268 | // Workaround: If user has YouTube Video Feed extension installed, prevent it from showing the default embedded
269 | // in favor of Youlag theater view modal. This collapses down the original feed item that activates by FreshRSS clickevent.
270 |
271 | const feedItem = target;
272 | let isActive = feedItem.classList.contains('active') && feedItem.classList.contains('current');
273 | const iframes = feedItem.querySelectorAll('iframe');
274 |
275 | if (iframes || youtubeExtensionInstalled) {
276 | iframes.forEach(iframe => {
277 | // Disable iframes to prevent autoplay
278 | const src = iframe.getAttribute('src');
279 | if (src) {
280 | iframe.setAttribute('data-original', src);
281 | iframe.setAttribute('src', '');
282 | }
283 | });
284 | }
285 |
286 | if (isActive) {
287 | // Collapse the feed item
288 | feedItem.classList.remove('active');
289 | feedItem.classList.remove('current');
290 | }
291 | }
292 |
293 | function disableBodyScroll(scroll) {
294 | document.body.style.overflow = scroll ? 'hidden' : 'auto';
295 | }
296 |
297 | function init() {
298 | setupClickListener();
299 | removeYoulagLoadingState();
300 | youlagScriptLoaded = true;
301 | }
302 |
303 | function removeYoulagLoadingState() {
304 | // By default, the youlag CSS is set to a loading state.
305 | // This will remove the loading state when the script is ready.
306 | document.body.classList.add('youlag-loaded');
307 | }
308 |
309 | function initFallback() {
310 | if (document.readyState === 'complete' || document.readyState === 'interactive' || youlagScriptLoaded === true) {
311 | init();
312 | } else {
313 | document.addEventListener('DOMContentLoaded', init);
314 | window.addEventListener('load', init);
315 | }
316 | }
317 |
318 | // Fallback interval check
319 | const checkInitInterval = setInterval(() => {
320 | if (document.readyState === 'complete' || youlagScriptLoaded === true) {
321 | init();
322 | clearInterval(checkInitInterval);
323 | }
324 | }, 1000);
325 |
326 | // Ensure init runs
327 | initFallback();
--------------------------------------------------------------------------------
/src/capture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/civilblur/youlag/67940135294bae938804705d80363db9654c7f63/src/capture.png
--------------------------------------------------------------------------------
/src/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/civilblur/youlag/67940135294bae938804705d80363db9654c7f63/src/icon.png
--------------------------------------------------------------------------------
/src/theme.scss:
--------------------------------------------------------------------------------
1 | /*****************************************
2 | *
3 | * Youlag Theme for FreshRSS
4 | *
5 | * Version: 3.0.5
6 | * License: GNU General Public License v3.0
7 | * Author: civilblur @ github
8 | *
9 | ****************************************/
10 |
11 |
12 | /*****************************************
13 | *
14 | * INDEX
15 | * - Variables and keyframes
16 | * - Feed grid layout
17 | * - Common layout elements
18 | * - Card
19 | * - Mapco theme dark mode (1)
20 | * - Dark mode: Global styles
21 | * - Dark mode: Dropdown, dialog, box
22 | * - Dark mode: Feed side navigation
23 | * - Dark mode: Settings side navigation
24 | * - Dark mode: Card
25 | * - Card expanded - Inline view mode (2)
26 | * - Favorites page
27 | * - Reader view page
28 | * - Animation keyframes
29 | * - Youlag theater view modal
30 | *
31 | *
32 | * NOTE
33 | * (1)
34 | * To revert Mapco theme to light mode,
35 | * comment out the CSS in the
36 | * "Mapco theme dark mode" section.
37 | *
38 | * (2)
39 | * This is a fallback view of displaying
40 | * feed items, if the Youlag script is not
41 | * utilized or fails to load.
42 | *
43 | *
44 | * TODO
45 | * - Refactor values to CSS variable, such as
46 | * colors, border-radius, margin, padding,
47 | * spacing, etc.
48 | *
49 | ****************************************/
50 |
51 |
52 |
53 | /*****************************************
54 | * BEGIN "VARIABLES AND KEYFRAMES"
55 | ****************************************/
56 |
57 |
58 |
59 |
60 | :root {
61 | --yl-space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
62 | --yl-space-2xs: clamp(0.5625rem, 0.5408rem + 0.1087vw, 0.625rem);
63 | --yl-space-xs: clamp(0.875rem, 0.8533rem + 0.1087vw, 0.9375rem);
64 | --yl-space-sm: clamp(1.125rem, 1.0815rem + 0.2174vw, 1.25rem);
65 | --yl-space-md: clamp(1.6875rem, 1.6223rem + 0.3261vw, 1.875rem);
66 | --yl-space-lg: clamp(2.25rem, 2.163rem + 0.4348vw, 2.5rem);
67 | --yl-space-xl: clamp(3.375rem, 3.2446rem + 0.6522vw, 3.75rem);
68 | --yl-space-2xl: clamp(4.5rem, 4.3261rem + 0.8696vw, 5rem);
69 | --yl-space-3xl: clamp(6.75rem, 6.4891rem + 1.3043vw, 7.5rem);
70 |
71 | --yl-p-3xs: var(--yl-space-3xs);
72 | --yl-p-2xs: var(--yl-space-2xs);
73 | --yl-p-xs: var(--yl-space-xs);
74 | --yl-p-sm: var(--yl-space-sm);
75 | --yl-p-md: var(--yl-space-md);
76 | --yl-p-lg: var(--yl-space-lg);
77 | --yl-p-xl: var(--yl-space-xl);
78 | --yl-p-2xl: var(--yl-space-2xl);
79 | --yl-p-3xl: var(--yl-space-3xl);
80 |
81 | --yl-m-3xs: var(--yl-space-3xs);
82 | --yl-m-2xs: var(--yl-space-2xs);
83 | --yl-m-xs: var(--yl-space-xs);
84 | --yl-m-sm: var(--yl-space-sm);
85 | --yl-m-md: var(--yl-space-md);
86 | --yl-m-lg: var(--yl-space-lg);
87 | --yl-m-xl: var(--yl-space-xl);
88 | --yl-m-2xl: var(--yl-space-2xl);
89 | --yl-m-3xl: var(--yl-space-3xl);
90 |
91 | --yl-text-2xs: clamp(0.7813rem, 0.7747rem + 0.0326vw, 0.8rem);
92 | --yl-text-xs: clamp(0.9375rem, 0.9158rem + 0.1087vw, 1rem);
93 | --yl-text-sm: clamp(1.125rem, 1.0815rem + 0.2174vw, 1.25rem);
94 | --yl-text-md: clamp(1.35rem, 1.2761rem + 0.3696vw, 1.5625rem);
95 | --yl-text-lg: clamp(1.62rem, 1.5041rem + 0.5793vw, 1.9531rem);
96 | --yl-text-xl: clamp(1.944rem, 1.771rem + 0.8651vw, 2.4414rem);
97 | --yl-text-2xl: clamp(2.3328rem, 2.0827rem + 1.2504vw, 3.0518rem);
98 | --yl-text-3xl: clamp(2.7994rem, 2.4462rem + 1.7658vw, 3.8147rem);
99 |
100 | --favicon-avatar-size: 34px;
101 | --favicon-avatar-size-margin: 6px;
102 | --feed-day-title-font-size: 2.3rem;
103 | }
104 |
105 | @media (max-width: 840px) {
106 | :root {
107 | --favicon-avatar-size: 42px;
108 | }
109 | }
110 |
111 | @keyframes pulse {
112 | 0%, 100% {
113 | filter: brightness(1);
114 | }
115 | 50% {
116 | filter: brightness(0.6);
117 | }
118 | }
119 |
120 |
121 | // TODO 2025-02-22: Create an utility classes section and move these classes to it.
122 | .yl-flex {
123 | display: flex;
124 | }
125 |
126 | .yl-flex-col {
127 | flex-direction: column;
128 | }
129 |
130 | .yt-items-center {
131 | align-items: center;
132 | }
133 |
134 | .yt-flex-1 {
135 | flex: 1;
136 | }
137 |
138 | .yl-gap-3xs {
139 | gap: var(--yl-space-3xs);
140 | }
141 |
142 | .yl-mb-md {
143 | margin-bottom: var(--yl-m-md);
144 | }
145 |
146 | .w-100 {
147 | width: 100%;
148 | }
149 |
150 |
151 |
152 | /*****************************************
153 | * END "VARIABLES AND KEYFRAMES"
154 | ****************************************/
155 |
156 |
157 |
158 |
159 |
160 |
161 | /*****************************************
162 | *
163 | * BEGIN "FEED GRID LAYOUT"
164 | *
165 | * Turn the feed table view to grid view.
166 | *
167 | ****************************************/
168 |
169 | body:not(.reader) main#stream {
170 | display: grid;
171 | grid-template-columns: repeat(3, minmax(150px, 1fr));
172 | gap: 4px;
173 | padding: 1.5rem;
174 |
175 | @media (max-width: 840px) {
176 | grid-template-columns: minmax(200px, 1fr);
177 | }
178 |
179 | @media (min-width: 600px) {
180 | grid-template-columns: repeat(2, minmax(200px, 1fr));
181 | }
182 |
183 | @media (max-width: 600px) {
184 | grid-row-gap: 2rem;
185 | }
186 |
187 | @media (min-width: 1144px) {
188 | grid-template-columns: repeat(2, minmax(200px, 1fr));
189 | }
190 |
191 | @media (min-width: 1360px) {
192 | grid-template-columns: repeat(3, minmax(240px, 1fr));
193 | }
194 |
195 | @media (min-width: 1800px) {
196 | grid-template-columns: repeat(4, minmax(200px, 1fr));
197 | }
198 | }
199 |
200 |
201 | /*****************************************
202 | * END "FEED GRID LAYOUT"
203 | ****************************************/
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 | /*****************************************
213 | *
214 | * BEGIN "COMMON LAYOUT ELEMENTS"
215 | *
216 | * For misc. elements such as navigation,
217 | * structural titles, etc.
218 | *
219 | ****************************************/
220 |
221 | @media (max-width: 840px) {
222 | body {
223 | padding-top: 61px; // Encompass mobile fixed header
224 | }
225 | }
226 |
227 |
228 | nav.aside.aside_feed {
229 | /* Position and adjust behavior of "Subscriptions management" button */
230 | text-align: unset;
231 |
232 | /* Manage feeds ("Subscriptions management") button */
233 | .stick.configure-feeds {
234 | position: relative;
235 |
236 | &:before {
237 | content: "Manage feeds";
238 | position: absolute;
239 | top: 50%;
240 | left: 69px;
241 | transform: translateY(-50%);
242 | z-index: 1;
243 | font-weight: normal;
244 | pointer-events: none;
245 | }
246 |
247 | &:after {
248 | content: "";
249 | position: absolute;
250 | top: 50%;
251 | left: 21px;
252 | transform: translateY(-50%);
253 | z-index: 1;
254 | filter: invert(1);
255 | width: 20px;
256 | height: 20px;
257 | pointer-events: none;
258 | background-repeat: no-repeat;
259 | /* RSS icon */
260 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCA0OTAgNDkwIj48cGF0aCBkPSJNNDY4LjIgNDg5LjVIMjAuOEM5LjQgNDg5LjUgMCA0ODAuMSAwIDQ2OC43VjIxLjNDMCA5LjkgOS40LjUgMjAuOC41aDQ0OC40QzQ4MC42LjUgNDkwIDkuOSA0OTAgMjEuM3Y0NDguNGMtMSAxMC40LTEwLjQgMTkuOC0yMS44IDE5Ljh6TTQwLjYgNDQ4LjloNDA3LjhWNDEuMUg0MC42djQwNy44eiIvPjxwYXRoIGQ9Ik0yNjAuMSA0MTkuOGMtMTEuNCAwLTIwLjgtOS40LTIwLjgtMjAuOCAwLTc3LTYyLjQtMTM5LjQtMTM5LjQtMTM5LjQtMTEuNCAwLTIwLjgtOS40LTIwLjgtMjAuOFM4OC41IDIxOCA5OS45IDIxOGM5OS45IDAgMTgxIDgxLjEgMTgxIDE4MSAwIDExLjQtOS40IDIwLjgtMjAuOCAyMC44eiIvPjxwYXRoIGQ9Ik0zNDcuNSA0MTkuOGMtMTEuNCAwLTIwLjgtOS40LTIwLjgtMjAuOCAwLTEyNC44LTEwMi0yMjcuOC0yMjcuOC0yMjcuOC0xMS40IDAtMjAuOC05LjQtMjAuOC0yMC44czkuNC0yMC44IDIwLjgtMjAuOGMxNDcuNyAwIDI2OC40IDEyMC43IDI2OC40IDI2OC40IDEgMTIuNC04LjQgMjEuOC0xOS44IDIxLjh6TTE3My43IDQxOS44Yy0xMS40IDAtMjAuOC05LjQtMjAuOC0yMC44IDAtMjkuMS0yMy45LTUzLjEtNTMuMS01My4xLTExLjQgMC0yMC44LTkuNC0yMC44LTIwLjhzOS40LTIwLjggMjAuOC0yMC44YzUyIDAgOTQuNyA0Mi43IDk0LjcgOTQuNyAwIDExLjQtOS4zIDIwLjgtMjAuOCAyMC44eiIvPjwvc3ZnPg==);
261 | }
262 |
263 | #btn-subscription,
264 | #btn-add {
265 | transition: 0s;
266 | }
267 |
268 | #btn-subscription:hover,
269 | #btn-subscription:hover ~ #btn-add,
270 | #btn-add:hover {
271 | background-color: #303136;
272 | }
273 |
274 | #btn-add {
275 | border-top-right-radius: 8px;
276 | border-bottom-right-radius: 8px;
277 |
278 | &:before {
279 | /* Hack: When hovering #btn-add, also highlight #btn-subscription */
280 | content: "";
281 | width: calc(100% - 68px);
282 | height: 100%;
283 | background: #303136;
284 | left: 8px;
285 | position: absolute;
286 | border-top-left-radius: 8px;
287 | border-bottom-left-radius: 8px;
288 | pointer-events: none;
289 | opacity: 0;
290 | transition: 0s;
291 | }
292 |
293 | &:hover {
294 | background-color: #424348;
295 |
296 | &:before {
297 | opacity: 1;
298 | }
299 | }
300 | }
301 |
302 | #btn-subscription {
303 | /* Hide text, later replace with psuedo element :before text */
304 | font-size: 0;
305 | flex: 1;
306 | height: 52px;
307 | margin-left: 8px;
308 | border-top-left-radius: 8px;
309 | border-bottom-left-radius: 8px;
310 | }
311 |
312 | #btn-subscription,
313 | #btn-add {
314 | text-transform: capitalize;
315 | font-weight: normal;
316 | display: flex;
317 | align-items: center;
318 | background: none;
319 | box-sizing: border-box;
320 | }
321 |
322 | #btn-add {
323 | font-size: 1rem;
324 | border-top-right-radius: 8px;
325 | border-bottom-right-radius: 8px;
326 | background: none;
327 | border: none;
328 | width: 60px;
329 | display: flex;
330 | justify-content: center;
331 | align-items: center;
332 |
333 | .icon {
334 | filter: brightness(3);
335 | height: 1.5rem;
336 | }
337 |
338 | &:hover {
339 | background-color: #424348;
340 | }
341 | }
342 | }
343 |
344 | #mark-read-aside ul#sidebar {
345 |
346 | .tree-folder.category {
347 | .tree-folder-title .title {
348 | font-weight: normal;
349 | }
350 | }
351 |
352 | & > li.tree-folder ul.tree-folder-items li.item.feed > .dropdown {
353 | margin-right: 0;
354 | }
355 |
356 | /* Subscriptions ("Main stream") button */
357 | .tree-folder.category.all {
358 | .tree-folder-title .title {
359 | font-size: 0;
360 |
361 | &:before {
362 | content: "Subscriptions";
363 | }
364 | }
365 |
366 | .icon {
367 | opacity: 0;
368 | pointer-events: none;
369 | }
370 |
371 | .tree-folder-title:before {
372 | /* Video icon */
373 | content:"";
374 | width:20px;
375 | height:20px;
376 | position: absolute;
377 | filter: invert(1);
378 | pointer-events: none;
379 | background-repeat: no-repeat;
380 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4MDAgODAwIj48cGF0aCBkPSJNNzY2LjE5Ni43MzVIMzMuOTY2QzE1LjM1LjczNSAwIDE2LjA4NSAwIDM0Ljd2NjAxLjQyN2MwIDE4LjYxNiAxNS4zNSAzMy45NjcgMzMuOTY2IDMzLjk2N2g3MzAuNDMzYzE4LjYxNiAwIDMzLjk2Ni0xNS4zNTEgMzUuNTk5LTMyLjMzNFYzNC43MDFDODAwLjE2MiAxNi4wODUgNzg0LjgxMi43MzUgNzY2LjE5Ni43MzVabS0zMy45NjYgNjAzLjA2SDY2LjNWNjcuMDM0aDY2NS45M3Y1MzYuNzYxWk03NjYuMTk2IDczMS4zMzJIMzMuOTY2QzE1LjM1IDczMS4zMzIgMCA3NDYuNjgyIDAgNzY1LjI5OHMxNS4zNSAzMy45NjYgMzMuOTY2IDMzLjk2Nmg3MzAuNDMzYzE4LjYxNiAwIDM1LjU5OS0xNS4zNSAzNS41OTktMzMuOTY2LjE2NC0xOC43OC0xNS4xODYtMzMuOTY2LTMzLjgwMi0zMy45NjZaIi8+PHBhdGggZD0iTTUxNi40MDggMzA4LjIwOCAzNDYuNjEyIDE5NC40MTJjLTIyLjIwNC0xNC41My01MC45MzkgMi4xMjMtNTAuOTM5IDI3LjEwMnYyMjcuNTkyYzAgMjYuMjg2IDI4LjczNSA0MS40NjkgNTAuOTM5IDI3LjEwMmwxNjkuNzk2LTExMy43OTZjMTUuMTg0LTguMTYzIDI0LjY1My0zNi4yNDUgMC01NC4yMDRabS0xNTQuNjEyIDc4LjA0MVYyODQuMzcxbDc2LjQwOCA1MC45MzktNzYuNDA4IDUwLjkzOVoiLz48L3N2Zz4=);
381 | }
382 | }
383 |
384 |
385 | /* Important feeds button */
386 | .tree-folder.category.important {
387 | .tree-folder-title .title {
388 | font-size: 0;
389 |
390 | &:before {
391 | content: "Important";
392 | }
393 | }
394 |
395 | .icon {
396 | opacity: 0;
397 | pointer-events: none;
398 | }
399 |
400 | .tree-folder-title:before {
401 | /* Bell icon */
402 | content:"";
403 | width:20px;
404 | height:20px;
405 | position: absolute;
406 | filter: invert(1);
407 | background-repeat: no-repeat;
408 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCA0ODcuNzcgNDg3Ljc3Ij48cGF0aCBkPSJNNDg2LjIgMjEwLjI2Yy02LjMtMTguOC0yNi4xLTIxLjktNDQuOC0yNS0xOS44LTMuMS00Mi43LTYuMy01OS40LTI0LTc2LjEtODMuNC0xNDEuOC0xMDEuMS0xODMuNS0xMDIuMS0xNy43LS41LTM0LjYgMi4yLTUwLjMgNy44LTMuOC0zNy40LTM1LjUtNjYuOC03My45LTY2LjhDMzMuMy4xNiAwIDMzLjQ2IDAgNzQuNDZjMCAzOC44IDI5LjggNzAuNyA2Ny43IDc0LTUuNiAxNS44LTguMyAzMi42LTcuOCA1MC40IDEgNDEuNyAxOS44IDEwNi4zIDEwMi4yIDE4My41IDE3LjcgMTYuNyAyMC45IDM5LjYgMjQgNTkuNCAzLjEgMTguOCA2IDM5LjUgMjUgNDQuOCAyMC4xIDUuNiA0Ny0xMSA5NC01NC41IDE3LjEgMTYuMyA0MC4yIDI1LjkgNjQuMyAyNS45IDUxLjIgMCA5Mi45LTQxLjcgOTIuOS05Mi45IDAtMjQuNi05LjgtNDgtMjYuOC02NS4zIDQ5LjUtNTQgNTYuMS03MS43IDUwLjctODkuNXpNNDAuOSA3NC40NmMwLTE4LjQgMTQuOS0zMy4zIDMzLjMtMzMuM3MzMy4zIDE0LjkgMzMuMyAzMy4zLTE0LjkgMzMuMy0zMy4zIDMzLjMtMzMuMy0xNC45LTMzLjMtMzMuM3ptMzI4LjQgMzQyLjVjLTEzIDAtMjUuMi00LjgtMzQuNi0xMy4xIDExLjEtMTEuMSA2My02NS4xIDcyLjQtNzQuMyA4LjkgOS41IDE0LjEgMjIuMiAxNC4xIDM1LjUgMCAyOC42LTIzLjMgNTEuOS01MS45IDUxLjl6bS0xNDIuNyAyNy45YzAtMy4xLTkuNC02NS43LTM3LjUtOTEuNy01Ny4zLTUzLjItODcuNi0xMDYuMy04OC42LTE1NC4zIDAtMjguMSAxOC42LTEwMC4yIDk4LTk5IDQ4IC43IDEwMS4xIDMxLjMgMTU0LjMgODguNiAyNi4xIDI4LjEgODguNiAzNy41IDkxLjcgMzcuNS0xNC43IDI2LjgtMTkyIDIwNC42LTIxNy45IDIxOC45eiIvPjwvc3ZnPg==);
409 | }
410 | }
411 |
412 |
413 | /* Favorites button */
414 | .tree-folder.category.favorites {
415 | .icon {
416 | opacity: 0;
417 | pointer-events: none;
418 | }
419 |
420 | .tree-folder-title:before {
421 | /* Star icon */
422 | content:"";
423 | width:20px;
424 | height:20px;
425 | position: absolute;
426 | filter: invert(1);
427 | pointer-events: none;
428 | background-repeat: no-repeat;
429 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNMTEuMDkxIDIuNjM1Yy4yODgtLjY0Ny40MzItLjk3LjYzMy0xLjA3YS42MjEuNjIxIDAgMCAxIC41NTIgMGMuMjAxLjEuMzQ1LjQyMy42MzMgMS4wN2wyLjI5NCA1LjE1Yy4wODUuMTkyLjEyOC4yODcuMTk0LjM2LjA1OC4wNjUuMTMuMTE3LjIwOS4xNTMuMDkuMDQuMTk0LjA1LjQwMi4wNzNsNS42MDMuNTkyYy43MDMuMDc0IDEuMDU1LjExMSAxLjIxMi4yNzEuMTM2LjE0LjE5OS4zMzQuMTcuNTI3LS4wMzIuMjIxLS4yOTUuNDU4LS44Mi45MzJsLTQuMTg2IDMuNzc2Yy0uMTU1LjE0LS4yMzMuMjEtLjI4Mi4yOTVhLjYyNC42MjQgMCAwIDAtLjA4LjI0NmMtLjAxLjA5OC4wMTEuMi4wNTUuNDA1bDEuMTY5IDUuNTE3Yy4xNDcuNjkzLjIyIDEuMDQuMTE2IDEuMjM4YS42MjIuNjIyIDAgMCAxLS40NDcuMzI1Yy0uMjIuMDM3LS41MjctLjE0LTEuMTQtLjQ5M2wtNC44OC0yLjgxOGMtLjE4Mi0uMTA1LS4yNzItLjE1Ny0uMzY5LS4xNzdhLjYyMi42MjIgMCAwIDAtLjI1OCAwYy0uMDk3LjAyLS4xODcuMDcyLS4zNjguMTc3bC00Ljg4MSAyLjgxOGMtLjYxMy4zNTQtLjkyLjUzLTEuMTQuNDkzYS42MjIuNjIyIDAgMCAxLS40NDctLjMyNWMtLjEwNC0uMTk5LS4wMy0uNTQ1LjExNi0xLjIzOGwxLjE3LTUuNTE3Yy4wNDMtLjIwNC4wNjQtLjMwNy4wNTQtLjQwNWEuNjI0LjYyNCAwIDAgMC0uMDgtLjI0NmMtLjA0OS0uMDg1LS4xMjctLjE1NS0uMjgyLS4yOTVsLTQuMTg1LTMuNzc2Yy0uNTI2LS40NzQtLjc4OS0uNzEtLjgyMS0uOTMyYS42MjMuNjIzIDAgMCAxIC4xNy0uNTI3Yy4xNTctLjE2LjUwOS0uMTk3IDEuMjEyLS4yNzFsNS42MDQtLjU5MmMuMjA4LS4wMjIuMzEyLS4wMzMuNDAxLS4wNzNhLjYyMi42MjIgMCAwIDAgLjIxLS4xNTJjLjA2NS0uMDc0LjEwOC0uMTcuMTkzLS4zNmwyLjI5NC01LjE1MVoiLz48L2c+PGRlZnM+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBkPSJNMCAwaDI0djI0SDB6Ii8+PC9jbGlwUGF0aD48L2RlZnM+PC9zdmc+);
430 | }
431 | }
432 |
433 | /* Playlists ("My labels") button */
434 | #tags {
435 | /* Always display Playlist ("My Labels") button */
436 | display: list-item !important;
437 |
438 | .tree-folder-title .title {
439 | font-size: 0;
440 |
441 | &:before {
442 | content: "Playlists";
443 | }
444 | }
445 |
446 | button.dropdown-toggle .icon {
447 | /* Hide playlist ("My Labels") dropdown toggle icon while keeping its functionality */
448 | opacity: 0;
449 | }
450 |
451 | & > .tree-folder-items .item.feed .item-title .icon {
452 | /* Remove tag item icons */
453 | display: none;
454 | }
455 |
456 | .tree-folder-title:before {
457 | /* Playlist icon */
458 | content:"";
459 | width:20px;
460 | height:20px;
461 | position: absolute;
462 | filter: invert(1);
463 | background-repeat: no-repeat;
464 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA4MDAgODAwIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMCA5MC41QTM3LjUgMzcuNSAwIDAgMSAzNy41IDUzaDcyNUEzNy40OTcgMzcuNDk3IDAgMCAxIDgwMCA5MC41YTM3LjUgMzcuNSAwIDAgMS0zNy41IDM3LjVoLTcyNUEzNy40OTggMzcuNDk4IDAgMCAxIDAgOTAuNVpNMzcuNSAzMjhoNzI1YTM3LjQ5NiAzNy40OTYgMCAwIDAgMjYuNTE2LTEwLjk4NCAzNy40OTYgMzcuNDk2IDAgMCAwIDAtNTMuMDMzQTM3LjUgMzcuNSAwIDAgMCA3NjIuNSAyNTNoLTcyNUEzNy41MDIgMzcuNTAyIDAgMCAwIDAgMjkwLjUgMzcuNDk3IDM3LjQ5NyAwIDAgMCAzNy41IDMyOFptMjQ0IDEyNWgtMjQ0QTM3LjUwMiAzNy41MDIgMCAwIDAgMCA0OTAuNSAzNy40OTcgMzcuNDk3IDAgMCAwIDM3LjUgNTI4aDI0NGEzNy41IDM3LjUgMCAwIDAgMC03NVpNMzcuNSA2NTNoMjQ0YTM3LjUgMzcuNSAwIDAgMSAwIDc1aC0yNDRhMzcuNDk5IDM3LjQ5OSAwIDAgMSAwLTc1WiIvPjxwYXRoIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSI2NSIgZD0iTTYzNi44MzMgNDY5Ljg4YzgwLjExOCA0Ni4zNTggMTIwLjE4MSA2OS41MzQgMTI2LjA2MiAxMDIuOTc0YTcyLjkzNyA3Mi45MzcgMCAwIDEgMCAyNS4yOTNjLTUuODgxIDMzLjQzOS00NS45NDQgNTYuNjE1LTEyNi4wNjIgMTAyLjk3My04MC4xMTcgNDYuMzUzLTEyMC4xODEgNjkuNTI5LTE1Mi4wMTggNTcuOTE2YTcyLjU2NiA3Mi41NjYgMCAwIDEtMjEuODU4LTEyLjY0NkM0MzcgNzI0LjU2MyA0MzcgNjc4LjIxIDQzNyA1ODUuNWMwLTkyLjcxIDAtMTM5LjA2MyAyNS45NTctMTYwLjg5YTcyLjY3IDcyLjY3IDAgMCAxIDIxLjg1OC0xMi42NDZjMzEuODM3LTExLjYxMyA3MS45MDEgMTEuNTY0IDE1Mi4wMTggNTcuOTE2WiIvPjwvc3ZnPg==);
465 | }
466 | }
467 |
468 | }
469 |
470 | /**
471 | * Side navigation button label font adjustment:
472 | * - Manage feed ("Subscriptions management")
473 | * - Subscriptions ("Main stream")
474 | * - Important
475 | * - Favorites
476 | * - Playlists ("My labels")
477 | * - User's category
478 | */
479 | .stick.configure-feeds:before,
480 | .tree-folder.category.all .tree-folder-title .title:before,
481 | .tree-folder.category.important .tree-folder-title .title:before,
482 | .tree-folder.category.favorites .tree-folder-title .title:before,
483 | #tags .tree-folder-title .title:before,
484 | .tree-folder.category .tree-folder-title .title {
485 | font-size: 1.2rem;
486 | letter-spacing: 1px;
487 | font-weight: normal;
488 | }
489 |
490 |
491 | /* Keep "Subscriptions management" button sticky on scroll */
492 | .stick.configure-feeds {
493 | position: sticky;
494 | top: 20px;
495 | z-index: 10;
496 | width: 292px;
497 | }
498 |
499 | @media (max-width: 840px) {
500 | .stick.configure-feeds {
501 | width: calc(100% - 8px);
502 | }
503 | }
504 |
505 | #mark-read-aside {
506 | top: calc(20px + 52px); /* Padding-top of container + height of "Subscriptions management" button */
507 | }
508 | }
509 |
510 |
511 | /* Settings page buttons */
512 | #aside_feed .item.nav-section a[href="./?c=tag"] {
513 | font-size: 0;
514 |
515 | &:before {
516 | content: "Playlist management";
517 | font-size: 1.2rem;
518 | }
519 | }
520 |
521 | /* Change text in settings page containing "Label" to "Playlist" */
522 | main.post:has(#add_tag) {
523 | > h1:first-of-type {
524 | font-size: 0;
525 |
526 | &:before {
527 | content: "Playlist management";
528 | font-size: 2rem;
529 | }
530 | }
531 |
532 | > h2:first-of-type {
533 | font-size: 0;
534 |
535 | &:before {
536 | content: "Create a playlist";
537 | font-size: 1.5rem;
538 | }
539 | }
540 | }
541 |
542 | @media (max-width: 840px) {
543 | .header {
544 | // Header on mobile
545 | position: fixed;
546 | top: 0;
547 | left: 0;
548 | z-index: 50;
549 |
550 | &:has(.configure .dropdown-target:target) {
551 | // On mobile, prevent the fixed hamburger menu `nav_menu .toggle_aside`
552 | // to sit on top of the settings menu. Applies for feed and non-feed pages.
553 | z-index: 110;
554 | }
555 |
556 | .configure .dropdown .dropdown-menu {
557 | // Remove gap on left side.
558 | left: 0 !important;
559 | }
560 | }
561 |
562 | #slider {
563 |
564 | .btn.toggle_aside {
565 | // Reset fixed navigation for non-feed pages.
566 | max-width: 49px;
567 | margin: 0;
568 | padding: 0.85rem 1.25rem;
569 | background: #303136;
570 | border: none;
571 |
572 | &:hover {
573 | background-color: #424348;
574 | }
575 | }
576 | }
577 |
578 | .aside {
579 | &:target ~ aside#slider a.toggle_aside {
580 | // Hide the fixed hamburger menu on non-feed page when its `aside_feed` menu is opened,
581 | // to prevent overlapping.
582 | opacity: 0;
583 | pointer-events: none;
584 | transition: opacity 0s;
585 | }
586 |
587 | & ~ aside#slider a.toggle_aside {
588 | // Fade in the hamburger menu on non-feed page,
589 | // to prevent abrupt display of the button while `aside_feed` is being collapse animated.
590 | transition: opacity 0.3s;
591 | }
592 |
593 | }
594 |
595 |
596 |
597 | body:not(.normal) #aside_feed.nav.nav-list.aside {
598 | padding-top: 0;
599 | }
600 |
601 | }
602 |
603 | .header {
604 | min-height: 61px;
605 | box-sizing: border-box;
606 |
607 |
608 | /* Page logo */
609 | .item.title {
610 | position: relative;
611 | text-align: unset;
612 | padding-left: 5px;
613 | min-height: 61px;
614 | box-sizing: border-box;
615 |
616 | & > a {
617 | align-content: center;
618 | }
619 |
620 |
621 | @media (max-width: 840px) {
622 | /* Center logo as sidenav toggle button is on the left side for mobile view */
623 | display: flex;
624 | justify-content: center;
625 | position: absolute;
626 | width: 100%;
627 | }
628 | }
629 |
630 | /* Header search bar */
631 | .item.search {
632 | input {
633 | width: 600px;
634 | padding: 0.8rem 1.6rem;
635 | border-top-left-radius: 99px;
636 | border-bottom-left-radius: 99px;
637 |
638 | &:focus,
639 | &:hover {
640 | background-color: #26272a;
641 | color: white;
642 |
643 | & ~ .btn {
644 | background-color: #57575a;
645 | }
646 | }
647 | }
648 |
649 | .btn {
650 | border-top-right-radius: 99px;
651 | border-bottom-right-radius: 99px;
652 | width: 75px;
653 |
654 | &:hover {
655 | background-color: #57575a;
656 | }
657 | }
658 | }
659 |
660 | /* Header settings button */
661 | .item.configure .btn {
662 | width: 46px;
663 | height: 46px;
664 | display: flex;
665 | align-items: center;
666 | justify-content: center;
667 | background: #26272a;
668 | border-radius: 50%;
669 | padding: 0;
670 | box-sizing: border-box;
671 |
672 | &:hover {
673 | background-color: #57575a;
674 | }
675 |
676 | > img {
677 | /* Sizing for default settings button */
678 | width: 18px;
679 | height: 18px;
680 | }
681 |
682 | /*
683 | > img {
684 | // Hide default settings icon
685 | opacity: 0;
686 | pointer-events: none;
687 | }
688 |
689 | // Issue with loading base64 image in settings page due to "Content Security Policy directive: "default-src 'self'".
690 | &:before {
691 | // User icon
692 | content:"";
693 | width: 20px;
694 | height: 20px;
695 | position: absolute;
696 | filter: invert(1);
697 | background-repeat: no-repeat;
698 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTIgMy4yYy0xLjg2NCAwLTMuMzc1IDEuNDc3LTMuMzc1IDMuMyAwIDEuODIzIDEuNTExIDMuMyAzLjM3NSAzLjNzMy4zNzUtMS40NzcgMy4zNzUtMy4zYzAtMS44MjMtMS41MTEtMy4zLTMuMzc1LTMuM1pNNi4zNzUgNi41QzYuMzc1IDMuNDYyIDguODkzIDEgMTIgMXM1LjYyNSAyLjQ2MiA1LjYyNSA1LjVTMTUuMTA3IDEyIDEyIDEyIDYuMzc1IDkuNTM4IDYuMzc1IDYuNVpNMTIgMTUuM2MtMy43MjggMC02Ljc1IDIuOTU1LTYuNzUgNi42IDAgLjYwOC0uNTA0IDEuMS0xLjEyNSAxLjFBMS4xMTMgMS4xMTMgMCAwIDEgMyAyMS45YzAtNC44NiA0LjAzLTguOCA5LTguOHM5IDMuOTQgOSA4LjhjMCAuNjA4LS41MDQgMS4xLTEuMTI1IDEuMWExLjExMyAxLjExMyAwIDAgMS0xLjEyNS0xLjFjMC0zLjY0NS0zLjAyMi02LjYtNi43NS02LjZaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=);
699 | }
700 | */
701 | }
702 | }
703 |
704 | @media (max-width: 840px) {
705 |
706 | header.header {
707 | .item.configure {
708 | display: flex;
709 | align-items: center;
710 | height: 50px;
711 | justify-content: flex-end;
712 | padding-right: 14px;
713 |
714 | .btn {
715 | width: 40px;
716 | height: 40px;
717 | }
718 | }
719 | }
720 |
721 | #global .nav_menu {
722 |
723 | a.btn[href="#aside_feed"] {
724 | /* Place mobile sidenav expand button toggle on header */
725 | position: fixed;
726 | top: 0;
727 | left: 0;
728 | min-height: 61px;
729 | box-sizing: border-box;
730 | border-radius: 0;
731 | z-index: 60;
732 |
733 | .icon {
734 | /* Hide existing sidenav toggle icon */
735 | display: none;
736 | }
737 |
738 | &:after {
739 | /* Add new sidenav toggle icon */
740 | content: "";
741 | display: block;
742 | width: 14px;
743 | height: 36px; // For centering the icon vertically, actual icon size is determined by the width.
744 | background-image: url('../themes/Mapco/icons/view-normal.svg'); // Existing image from FreshRSS theme "Mapco"
745 | background-size: contain;
746 | background-position: center;
747 | background-repeat: no-repeat;
748 | filter: brightness(3);
749 | }
750 | }
751 |
752 | .item.configure {
753 |
754 | .btn {
755 | width: 40px;
756 | height: 40px;
757 | }
758 | }
759 |
760 |
761 | }
762 |
763 |
764 |
765 | }
766 |
767 |
768 |
769 |
770 | /* End of feed footer */
771 | main#stream #stream-footer,
772 | #overlay #panel #stream-footer {
773 | grid-column: 1 / -1;
774 | border-top: none;
775 | margin-top: 3rem;
776 |
777 | .stream-footer-inner {
778 | /* Hide text, then, apply new text using psuedo element :before/:after */
779 | font-size: 0;
780 | display: flex;
781 | flex-direction: column;
782 |
783 | &:before {
784 | content: "(╯°□°)╯︵ ┻━┻";
785 | font-size: 2.4rem;
786 | margin-bottom: 2rem;
787 | opacity: 0.75;
788 | }
789 |
790 | &:after {
791 | content: "You've reached the end";
792 | font-size: 1.4rem;
793 | order: -1;
794 | margin-bottom: 0.5rem;
795 | opacity: 0.75;
796 | }
797 | }
798 |
799 | button#bigMarkAsRead {
800 | /* Mark as read button */
801 | border-radius: 12px;
802 | margin-top: 4rem;
803 | padding-left: 8px;
804 | padding-right: 8px;
805 | padding-top: 1rem;
806 | padding-bottom: 2rem;
807 | margin-bottom: 0;
808 | max-width: 600px;
809 | margin-left: auto;
810 | margin-right: auto;
811 | }
812 | }
813 |
814 | #load_more.loading,
815 | #load_more.loading:hover {
816 | /* Infinite scroll loading spinner */
817 | border-radius: 8px;
818 | }
819 |
820 | /* Feed menu */
821 | .nav_menu {
822 | padding: 2rem 1rem 0 1rem;
823 | box-sizing: border-box;
824 |
825 | #nav_menu_actions {
826 | flex-wrap: wrap;
827 | }
828 |
829 | #mark-read-menu {
830 | align-items: center;
831 |
832 | .read_all.btn {
833 | width: 100%;
834 | height: 100%;
835 | }
836 | }
837 |
838 | }
839 |
840 | /* Card day section title */
841 | main#stream .day {
842 | grid-column: 1 / -1;
843 | font-size: var(--feed-day-title-font-size);
844 | opacity: 0.8;
845 | padding: 0 8px;
846 | line-height: 1.2;
847 | margin-top: 8rem;
848 | margin-bottom: 2rem;
849 |
850 |
851 | .date,
852 | .name {
853 | display: none;
854 | }
855 |
856 | day_before_yesterday {
857 | /* Hide text, then, apply new text using psuedo element :before */
858 | font-size: 0 !important;
859 |
860 | &:before {
861 | content: "Past";
862 | font-size: var(--feed-day-title-font-size);
863 | }
864 | }
865 | }
866 |
867 | main#stream {
868 | /* Reduce margin-top on the first day that appears at the top of the feed */
869 | #new-article + .day {
870 | margin-top: 2rem;
871 | }
872 |
873 | /* Hide title "Past" if it is the only day-title on the feed */
874 | #new-article + #day_before_yesterday {
875 | visibility: hidden;
876 | margin-bottom: 0;
877 | pointer-events: none;
878 | user-select: none;
879 | }
880 |
881 | .flux:has(li.item.labels .item-element.dropdown .dropdown-target:target) {
882 | /* Prevent card item favicon avatar to be placed above Playlist (labels) dropdown */
883 | z-index: 20;
884 | }
885 |
886 | }
887 |
888 |
889 |
890 |
891 |
892 | a.signout img.icon {
893 | display: none;
894 | }
895 |
896 |
897 | /*****************************************
898 | *
899 | * BEGIN "CARD"
900 | *
901 | * Feed items are referred to as cards,
902 | * as they're styled as grid cells opposed
903 | * to FreshRSS' table view.
904 | *
905 | ****************************************/
906 |
907 | /* Cards default state in feed */
908 | body:not(.reader) main#stream div.flux {
909 | display: flex;
910 | flex-direction: column;
911 | align-items: center;
912 | border-radius: 12px;
913 | margin-bottom: auto; /* Prevents card from becoming incredibly tall affected by other grid cells, e.g. #bigMarkAsRead. */
914 |
915 | /* Card hover effect */
916 | &:not(.active):hover {
917 | background-color: hsl(0deg 0% 0% / 6%);
918 | }
919 |
920 | &:not(.not_read),
921 | &.favorite:not(.current),
922 | &:not(.current):hover .item .title {
923 | background: unset;
924 | background-color: unset;
925 | }
926 |
927 | /* Card container */
928 | .flux_header {
929 | /* In card default state, the header is the card container itself, unlike in card expended view where it is used as a header/toolbar. */
930 | border-radius: 12px;
931 | display: flex;
932 | flex-direction: row;
933 | flex-wrap: wrap;
934 | height: 100%;
935 | max-width: 100%;
936 | padding: 8px;
937 | box-sizing: border-box;
938 | border: none;
939 | background: unset;
940 |
941 | &:hover,
942 | &:hover:not(.current):hover .item .title {
943 | /* Prevent background change on hover. */
944 | background-color: unset;
945 | background: unset;
946 | }
947 |
948 |
949 | &:before {
950 | // Decorative attr(data-article-authors) text on thumbnail placeholder, visible for feeds without images
951 | // Alternative: content: attr(data-website-name);
952 | content: attr(data-article-authors);
953 | position: absolute;
954 | top: 0;
955 | left: 0;
956 | z-index: 0;
957 | width: calc(100% - 24px);
958 | height: 0;
959 | padding-top: 16%;
960 | padding-bottom: calc(40.2% - 12px);
961 | padding-left: 20px;
962 | font-size: min(1.7rem,5vw);
963 | color: #fff;
964 | box-sizing: border-box;
965 | overflow: hidden;
966 | background: linear-gradient(to right, #5c8dff, #4cffdd);
967 | background-clip: border-box;
968 | background-clip: border-box;
969 | -webkit-background-clip: text;
970 | -webkit-text-fill-color: rgba(0,0,0,0);
971 | }
972 |
973 | .item.titleAuthorSummaryDate {
974 | display: flex;
975 | flex-direction: column;
976 | width: 100%;
977 | margin: 4px 0;
978 |
979 |
980 | @media (min-width: 600px) {
981 | & {
982 | /* Hack: On desktop, keep position consistent while allowing 100% vertical stretch of .flux and .flux-header */
983 | height: 105px;
984 | }
985 | }
986 |
987 |
988 | a.item-element.title.multiline {
989 | display: flex;
990 | flex-direction: column;
991 | padding: 0;
992 | position: static;
993 | padding-left: 2px;
994 | padding-right: 2px;
995 | min-width: unset;
996 | font-size: 1.2rem;
997 | white-space: normal;
998 | display: -webkit-box;
999 | -webkit-box-orient: vertical;
1000 | -webkit-line-clamp: 4;
1001 | line-clamp: 4;
1002 | overflow: hidden;
1003 |
1004 | .author {
1005 | padding: 0;
1006 | display: block;
1007 | margin-top: 4px;
1008 | font-size: 1rem;
1009 | line-height: 1.2;
1010 | color: hsla(0, 0%, 100%, 0.59);
1011 | }
1012 | }
1013 |
1014 | span.item-element.date {
1015 | padding: 0;
1016 | position: static;
1017 | text-align: left;
1018 | margin-top: 4px;
1019 | padding-left: 2px;
1020 | padding-right: 2px;
1021 | display: flex;
1022 | width: 100%;
1023 | font-size: 1rem;
1024 | }
1025 | }
1026 |
1027 | .item.thumbnail {
1028 | /* Card thumbnail */
1029 | position: relative;
1030 |
1031 | order: -1; /* Position thumbnail first within card, at the top */
1032 | width: 100%;
1033 | height: auto;
1034 | aspect-ratio: 16 / 9;
1035 | flex: 0 0 100%;
1036 | box-sizing: border-box;
1037 | margin-bottom: 4px;
1038 | padding: 0;
1039 | overflow: hidden;
1040 |
1041 | img {
1042 | position: relative;
1043 | width: 100%;
1044 | height: auto;
1045 | border-radius: 12px;
1046 | background: #303136;
1047 | aspect-ratio: 16 / 9;
1048 | object-fit: cover;
1049 | object-position: center;
1050 | z-index: 1;
1051 | }
1052 |
1053 | &:before {
1054 | // Placeholder for empty thumbnails
1055 | content: "";
1056 | width: 100%;
1057 | position: absolute;
1058 | top: 0;
1059 | left: 0;
1060 | padding-top: 56.25%;
1061 | background-color: #303136;
1062 | background-image: linear-gradient(45deg, #121417, #17253f);
1063 | border-radius: 12px;
1064 | z-index: 0;
1065 | }
1066 | }
1067 |
1068 | }
1069 |
1070 | }
1071 |
1072 | /* Card expanded state in feed */
1073 | body:not(.reader) main#stream div.flux.active.current {
1074 |
1075 | /* Card expanded header, serves as a top panel. */
1076 | .flux_header {
1077 | position: sticky;
1078 | top: -4px;
1079 | left: 0;
1080 | background-color: var(--frss-background-color, white);
1081 | z-index: 1;
1082 | padding: 12px;
1083 | margin-top: -4px;
1084 | border-bottom-left-radius: 2px;
1085 | border-bottom-right-radius: 2px;
1086 | pointer-events: none; /* Prevents card header clicks from closing the rss item. */
1087 |
1088 |
1089 | .item a,
1090 | .item .dropdown-close,
1091 | .item .dropdown-menu {
1092 | /* Reenable action buttons within card header */
1093 | pointer-events: all;
1094 | }
1095 |
1096 | .item .dropdown-menu {
1097 | z-index: 1100;
1098 |
1099 | li.item.share {
1100 | min-width: 100%;
1101 | margin: 0;
1102 | }
1103 | }
1104 |
1105 | .item.titleAuthorSummaryDate {
1106 | display: flex;
1107 | flex-direction: column;
1108 | flex-wrap: wrap;
1109 | order: -1;
1110 | min-width: unset;
1111 | flex: 0 0 100%;
1112 | margin-bottom: 6px;
1113 | z-index: 10;
1114 | height: auto;
1115 |
1116 |
1117 | /* Card title */
1118 | a {
1119 | /* Shorten width to prevent overlap with close button. */
1120 | max-width: calc(100% - 50px);
1121 | pointer-events: none;
1122 | }
1123 |
1124 | .item-element.date {
1125 | /* Hide date in theater mode to save vertical space */
1126 | display: none;
1127 | }
1128 | }
1129 |
1130 |
1131 | li.item.website {
1132 | /* Hide website icon in theater mode expanded view */
1133 | display: none;
1134 | }
1135 |
1136 | li.item.thumbnail {
1137 | /* Hide thumbnail in theater mode expanded view */
1138 | display: none;
1139 | }
1140 |
1141 | }
1142 |
1143 | /* Card content container */
1144 | article {
1145 | display: flex;
1146 | flex-direction: column;
1147 | background-color: hsl(0deg 0% 9%);
1148 | }
1149 | }
1150 |
1151 | /* Card icons and button behavior */
1152 | body:not(.reader) main#stream {
1153 | div.flux {
1154 |
1155 | li.item.share,
1156 | li.item.labels {
1157 | /**
1158 | * Specific adjustment due to these card buttons containing dropdown menu
1159 | * - Share button
1160 | * - Playlists button (labels)
1161 | */
1162 |
1163 | &:has(.dropdown-target:target) {
1164 | /* Temporarily elevate z-index to prevent other buttons to show on top of dropdown. */
1165 | z-index: 15;
1166 | }
1167 |
1168 | .item-element.dropdown {
1169 | display: flex;
1170 | width: 100%;
1171 | height: 100%;
1172 | padding: 0;
1173 |
1174 | a.dropdown-toggle {
1175 | /* Make button clickable across the entire button area, opposed to only the icon. */
1176 | width: 100%;
1177 | display: inline-flex;
1178 | margin: 0;
1179 | padding: 0;
1180 | height: 100%;
1181 | border-radius: 999px;
1182 | justify-content: center;
1183 | align-items: center;
1184 | gap: 4px;
1185 | }
1186 | }
1187 | }
1188 |
1189 | .flux_header {
1190 | .summary {
1191 | /* No support for summary */
1192 | display: none;
1193 | }
1194 |
1195 | li.item.manage a,
1196 | li.item.website.icon a,
1197 | li.item.link a,
1198 | li.item.labels a,
1199 | li.item.share > a {
1200 | display: flex;
1201 | align-items: center;
1202 | justify-content: center;
1203 | height: 100%;
1204 | box-sizing: border-box;
1205 | }
1206 |
1207 | li.item.website.full,
1208 | li.item.website.name {
1209 | /* For user's who set website setting to "icon and name" or "name" */
1210 | a {
1211 | color: white;
1212 | padding: 0;
1213 | }
1214 | }
1215 |
1216 | li.item.manage,
1217 | li.item.website.icon,
1218 | li.item.website.full,
1219 | li.item.link,
1220 | li.item.labels,
1221 | li.item.share {
1222 | flex: 1;
1223 | background-color: unset;
1224 | display: inline-flex;
1225 | height: auto;
1226 | justify-content: center;
1227 | border-radius: 999px;
1228 | margin: 0 2px;
1229 | padding: 0;
1230 | z-index: 10;
1231 | transition: background-color 0.1s;
1232 | order: 2;
1233 | min-height: 34px;
1234 |
1235 | li.item.share {
1236 | width: 100%;
1237 | }
1238 |
1239 | a {
1240 | padding: 4px 6px;
1241 | text-align: center;
1242 | width: 100%;
1243 | }
1244 |
1245 | &:nth-child(2) {
1246 | /* Place favorite button to the far left */
1247 | order: 1;
1248 | }
1249 |
1250 | &:hover {
1251 | background-color: hsla(0, 0%, 100%, 0.08)
1252 | }
1253 | }
1254 |
1255 |
1256 | li.item.labels {
1257 |
1258 | .dropdown-header {
1259 | display: flex;
1260 | font-size: 0;
1261 |
1262 | &:before {
1263 | content: "Playlists";
1264 | letter-spacing: 1px;
1265 | text-transform: uppercase;
1266 | font-size: 1rem;
1267 | }
1268 |
1269 | a[href="./?c=tag"] {
1270 | margin-left: 8px;
1271 | font-size: 1rem;
1272 | visibility: visible;
1273 | width: unset;
1274 | }
1275 | }
1276 |
1277 | .item-element.dropdown:not(:has(.dropdown-target:target)) {
1278 | /* Playlists button (labels) on card: Hide close button as it displays even after onblur. */
1279 | a.dropdown-close {
1280 | display: none;
1281 | }
1282 | }
1283 |
1284 | }
1285 |
1286 |
1287 | li.item.manage:hover,
1288 | li.item.website.icon:hover,
1289 | li.item.link:hover,
1290 | li.item.labels:hover,
1291 | li.item.share:hover {
1292 | background-color: hsla(0, 0%, 100%, 0.08)
1293 | }
1294 |
1295 | &:not(.active) {
1296 | li.item.manage a,
1297 | li.item.website.icon a,
1298 | li.item.link a,
1299 | li.item.labels a,
1300 | li.item.share a {
1301 | /* Hide card actions by default */
1302 | visibility: hidden;
1303 | background-color: unset;
1304 | }
1305 |
1306 | &:hover {
1307 | /* Display card actions on hover */
1308 | li.item.manage a,
1309 | li.item.website.icon a,
1310 | li.item.link a,
1311 | li.item.labels a,
1312 | li.item.share a {
1313 | visibility: visible;
1314 | }
1315 | }
1316 |
1317 | @media (max-width: 600px) {
1318 | /* Always display card actions on mobile */
1319 | li.item.manage a,
1320 | li.item.website.icon a,
1321 | li.item.link a,
1322 | li.item.labels a,
1323 | li.item.share a {
1324 | visibility: visible !important;
1325 | }
1326 | }
1327 | }
1328 |
1329 | .website a:hover .favicon,
1330 | a.website:hover .favicon {
1331 | /* Disable greyscale effect on website icon hover */
1332 | filter: unset;
1333 | }
1334 |
1335 | li.item.share {
1336 | order: 3;
1337 | }
1338 | }
1339 |
1340 | &.active.current .flux_header {
1341 | li.item.manage a,
1342 | li.item.website.icon a,
1343 | li.item.link a,
1344 | li.item.labels a,
1345 | li.item.share a {
1346 | visibility: visible;
1347 | }
1348 | }
1349 |
1350 | /* Favorites (bookmark) button */
1351 | &.favorite {
1352 | .flux_header li.item.manage a.bookmark {
1353 | visibility: visible;
1354 | filter: brightness(1.5) sepia(1) hue-rotate(-25deg) saturate(4) contrast(1.2);
1355 | }
1356 |
1357 | .flux_header li.item.manage:nth-child(2) {
1358 | background-color: hsl(33.65deg 100% 54.9% / 13%);
1359 |
1360 | &:hover {
1361 | background-color: hsl(33.65deg 100% 54.9% / 20%);
1362 | }
1363 | }
1364 | }
1365 |
1366 | /* Favicon as avatar */
1367 | .flux_header {
1368 | position: relative;
1369 |
1370 | li.item.website.icon,
1371 | li.item.website.full {
1372 | /* If user set "Website: Icon" in Display settings */
1373 | display: flex;
1374 | width: 100%;
1375 | flex: 0 0 100%;
1376 | max-width: unset;
1377 | flex-direction: column;
1378 | position: absolute;
1379 | top: 0;
1380 | left: 0;
1381 | justify-content: flex-start;
1382 | border-radius: 0;
1383 | pointer-events: none;
1384 |
1385 | .websiteName {
1386 | /* No support for favicon website name. Instead, use display setting "Article icons: Author" */
1387 | display: none;
1388 | }
1389 |
1390 | & ~ .item.titleAuthorSummaryDate {
1391 | /* If website favicon exist, add padding to make space for it. */
1392 | padding-left: calc(var(--favicon-avatar-size) + var(--favicon-avatar-size-margin));
1393 | }
1394 |
1395 | &:hover {
1396 | background-color: unset;
1397 | }
1398 |
1399 | &:before {
1400 | /* Position favicon responsively. knowing that the thumbnail will always be 16:9 */
1401 | content: "";
1402 | width: 100%;
1403 | height: auto;
1404 | aspect-ratio: 16 / 9;
1405 | z-index: -1;
1406 | position: relative;
1407 | visibility: hidden;
1408 | pointer-events: none;
1409 | }
1410 |
1411 | a.item-element {
1412 | display: flex;
1413 | width: calc(var(--favicon-avatar-size) + var(--favicon-avatar-size-margin));
1414 | visibility: visible;
1415 | pointer-events: all;
1416 | box-sizing: unset;
1417 | }
1418 |
1419 | img.favicon {
1420 | height: var(--favicon-avatar-size);
1421 | width: var(--favicon-avatar-size);
1422 | background-color: hsl(0deg 0% 100% / 18%);
1423 | padding: 8px;
1424 | box-sizing: border-box;
1425 | border-radius: 50%;
1426 | margin-top: 2px;
1427 | }
1428 |
1429 | }
1430 | }
1431 |
1432 | /* Change card footer from "My labels" to "+ Add to playlist" */
1433 | &.active.current article.flux_content {
1434 | footer ul.horizontal-list.bottom .item.labels .item-element.dropdown a[href^="#dropdown-labels-"] {
1435 | font-size: 0;
1436 | white-space: nowrap;
1437 | color: white;
1438 |
1439 | &:before {
1440 | content: "+ Add to playlist";
1441 | font-weight: bold;
1442 | font-size: 14px;
1443 | }
1444 |
1445 | & + .dropdown-menu .dropdown-header {
1446 | font-size: 0;
1447 |
1448 | &:before {
1449 | content: "Playlists";
1450 | letter-spacing: 1px;
1451 | text-transform: uppercase;
1452 | font-size: 1rem;
1453 | }
1454 |
1455 | a[href="./?c=tag"] {
1456 | margin-left: 8px;
1457 | font-size: 1rem;
1458 | }
1459 | }
1460 |
1461 | .icon {
1462 | display: none;
1463 | }
1464 | }
1465 |
1466 | .content .text {
1467 | overflow-x: hidden;
1468 | }
1469 |
1470 | }
1471 | }
1472 | }
1473 |
1474 | /*****************************************
1475 | * END "CARD"
1476 | ****************************************/
1477 |
1478 |
1479 |
1480 |
1481 |
1482 |
1483 | /*****************************************
1484 | *
1485 | * BEGIN "MAPCO THEME DARK MODE"
1486 | *
1487 | * Applies dark mode to the "Mapco" theme.
1488 | * Content within `