├── README.md ├── enablePanelSwipe.js ├── getPrefixedProperty.js ├── index.htm ├── normalize.css └── style.css /README.md: -------------------------------------------------------------------------------- 1 | # flipboard-style-dom 2 | Flipboard-style news reader using DOM for mobile. 60FPS 3 | 4 | * Works on iOS, Android 4+, Firefox, Windows Phone 8 5 | * Clone and open index.htm 6 | 7 | 8 | Demo: http://premii.com/play/flipboard-style-news/ -------------------------------------------------------------------------------- /enablePanelSwipe.js: -------------------------------------------------------------------------------- 1 | 2 | (function (window) { 3 | 4 | var helper = window.helper || (window.helper = {}); 5 | 6 | var prefixedTransform = helper.getPrefixedProperty('transform'); 7 | 8 | helper.closest = function (el, fn) { 9 | while (el) { 10 | if (fn(el)) { 11 | return el; 12 | } 13 | el = el.parentNode; 14 | } 15 | }; 16 | 17 | helper.pointerEventToXY = function(e){ 18 | var out = {x:0, y:0}; 19 | 20 | if (e.type.indexOf('touch') === 0) { 21 | var touch = e.touches[0] || e.changedTouches[0]; 22 | out.x = touch.pageX; 23 | out.y = touch.pageY; 24 | } 25 | else if (e.type.indexOf('mouse') === 0) { 26 | out.x = e.pageX; 27 | out.y = e.pageY; 28 | } 29 | return out; 30 | }; 31 | 32 | helper.easeOutQuad = function (t) { 33 | return t*(2-t); 34 | }; 35 | 36 | var animatePanels = function(topPanelOptions, bottomPanelOptions, availableDistance, availableDuration, direction, callback) { 37 | 38 | var topPanel = topPanelOptions.page, 39 | bottomPanel = bottomPanelOptions.page, 40 | topPanelEndPosition = topPanelOptions.endPosition, 41 | topPanelStartPosition = topPanelOptions.startPosition; 42 | 43 | var startTime = Date.now(), 44 | duration = availableDuration / availableDistance * Math.abs(topPanelStartPosition - topPanelEndPosition), 45 | currentY = topPanelStartPosition, 46 | frameCount = 0; 47 | 48 | 49 | direction = direction || 1; 50 | // console.warn('animatePages(): duration', duration); 51 | 52 | callback = callback || function () {}; 53 | 54 | var getBottomPanelYPosition = function(x, direction) { 55 | return (x - (availableDistance * direction)) / 2; 56 | }; 57 | 58 | topPanel.style.display = 'block'; 59 | bottomPanel.style.display = 'block'; 60 | 61 | var animate = function () { 62 | // console.log('animate()'); 63 | var currentTime = Date.now(), 64 | time = Math.min(1, ((currentTime - startTime) / duration)), 65 | easedT = helper.easeOutQuad(time); 66 | 67 | frameCount++; 68 | 69 | currentY = (easedT * (topPanelEndPosition - (topPanelStartPosition))) + topPanelStartPosition; 70 | 71 | topPanel.style[prefixedTransform] = 'translate3d(0, ' + (currentY) + 'px, 0)'; 72 | 73 | bottomPanel.style[prefixedTransform] = 'translate3d(0, ' + getBottomPanelYPosition(currentY, direction) + 'px, 0)'; 74 | 75 | if (time < 1 && ((direction === -1 && currentY < topPanelEndPosition) || (direction === 1 && currentY > topPanelEndPosition))) { 76 | window.requestAnimationFrame(animate); 77 | } 78 | else { 79 | callback({ 80 | frameCount: frameCount, 81 | time: +new Date - startTime 82 | }); 83 | } 84 | }; 85 | 86 | window.requestAnimationFrame(animate); 87 | }; 88 | 89 | var hideAllChildren = function(visibleItems) { 90 | var item, 91 | visibleChildIndex; 92 | 93 | visibleItems[0].classList.add('swipe-current'); 94 | 95 | for(var i = 0, len = visibleItems.length; i < len; i++) { 96 | item = visibleItems[i]; 97 | 98 | if (item.classList.contains('swipe-current')) { 99 | visibleChildIndex = i; 100 | if (i > 0) { 101 | visibleItems[i - 1].style.display = 'block'; //previous item visible 102 | } 103 | item.style.display = 'block'; //make current item visible 104 | } 105 | else { 106 | item.style.display = 'none'; //all other hidden 107 | } 108 | 109 | if (i == visibleChildIndex + 1) { 110 | item.style.display = 'block'; //make next item is visible 111 | item.style[prefixedTransform] = 'translate3d(0, 100%, 0)'; 112 | } 113 | else if (i == visibleChildIndex - 1) { 114 | item.style.display = 'block'; //make previous item is visible 115 | item.style[prefixedTransform] = 'translate3d(0, -100%, 0)'; 116 | } 117 | } 118 | }; 119 | 120 | var calculateTopPanelYPosition = function(visibleWindowHeight, bottomYPosition, direction) { 121 | direction = direction || 1; 122 | 123 | var topYPosition = (visibleWindowHeight * direction) + (bottomYPosition * 2); 124 | if (direction === 1) { 125 | if (topYPosition < 0) { 126 | topYPosition = 0; 127 | } 128 | } 129 | else if (direction === -1) { 130 | if (topYPosition > 0) { 131 | topYPosition = 0; 132 | } 133 | } 134 | 135 | return topYPosition; 136 | }; 137 | 138 | var enablePanelSwipe = function (option) { 139 | 140 | var scrollableOptions = {}; 141 | scrollableOptions.scrollRegion = option.scrollRegion; 142 | scrollableOptions.children = option.children; 143 | scrollableOptions.afterAnimationCallback = option.callback || function() {}; 144 | 145 | var swipeDirty = false, 146 | startXY, 147 | continueAnimation = 0, 148 | allowedTimePerPixel = 65/100, 149 | allowedTime = 350; // maximum allowed time to travel available distance (visiblePanelHeight) 150 | 151 | var isAnimating = false, 152 | swipeVerticalPossible = false, 153 | currentPanel, 154 | nextPanel, 155 | prevPanel, 156 | swipeToPanel, 157 | visiblePanelHeight, 158 | touchStartTime; 159 | 160 | var lastXY, 161 | panelPositions; 162 | 163 | var onTouchStart = function(event) { 164 | 165 | var target = event.target; 166 | 167 | if (!isAnimating && scrollableOptions.scrollRegion.contains(target)) { 168 | 169 | currentPanel = helper.closest(target, function (el) { 170 | return el.classList.contains('swipe-current'); 171 | }); 172 | 173 | if (currentPanel) { 174 | swipeVerticalPossible = true; 175 | 176 | touchStartTime = +new Date(); 177 | startXY = helper.pointerEventToXY(event); 178 | visiblePanelHeight = currentPanel.clientHeight; 179 | nextPanel = currentPanel.nextElementSibling; 180 | prevPanel = currentPanel.previousElementSibling; 181 | } 182 | } 183 | }; 184 | 185 | var cleanUpOnTouchEnd = function() { 186 | 187 | if (swipeDirty === 1) { 188 | if (prevPanel) { 189 | prevPanel.style.display = 'none'; 190 | } 191 | nextPanel = swipeToPanel.nextElementSibling; 192 | 193 | if (nextPanel) { 194 | nextPanel.style[prefixedTransform] = 'translate3d(0, 100%, 0)'; 195 | nextPanel.style.display = 'block'; 196 | } 197 | } 198 | else if (swipeDirty === -1) { 199 | 200 | if (nextPanel) { 201 | nextPanel.style.display = 'none'; 202 | } 203 | prevPanel = swipeToPanel.previousElementSibling; 204 | 205 | if (prevPanel) { 206 | prevPanel.style[prefixedTransform] = 'translate3d(0, -100%, 0)'; 207 | prevPanel.style.display = 'block'; 208 | } 209 | } 210 | 211 | if (swipeVerticalPossible && isAnimating) { 212 | scrollableOptions.afterAnimationCallback(swipeToPanel); 213 | } 214 | 215 | swipeVerticalPossible = false; 216 | 217 | isAnimating = false; 218 | nextPanel = null; 219 | prevPanel = null; 220 | currentPanel = null; 221 | 222 | swipeDirty = false; 223 | lastXY = null; 224 | 225 | }; 226 | 227 | var onTouchEnd = function(event) { 228 | 229 | if (swipeVerticalPossible && swipeDirty && !isAnimating) { 230 | 231 | swipeDirty = swipeDirty === 1 ? 1 : -1; 232 | 233 | allowedTime = visiblePanelHeight * allowedTimePerPixel; 234 | 235 | isAnimating = true; 236 | animatePanels({ 237 | page: swipeToPanel, 238 | startPosition: panelPositions.top, 239 | endPosition: 0 240 | }, 241 | { 242 | page: currentPanel, 243 | startPosition: panelPositions.bottom 244 | }, 245 | visiblePanelHeight, 246 | allowedTime, 247 | swipeDirty, 248 | function(op) { 249 | 250 | var bottomYPosition = (swipeDirty === 1) ? '-100%' : '100%'; 251 | currentPanel.style[prefixedTransform] = 'translate3d(0, ' + bottomYPosition + ', 0)'; 252 | 253 | currentPanel.classList.remove('swipe-current'); 254 | swipeToPanel.classList.add('swipe-current'); 255 | 256 | window.setTimeout(function() { 257 | swipeToPanel.querySelector('.fleft').innerHTML = op.frameCount + ' frames ' + ' / ' + op.time + 'ms'; 258 | }, 100); 259 | cleanUpOnTouchEnd(); 260 | }); 261 | 262 | } 263 | else if (!isAnimating) { 264 | cleanUpOnTouchEnd(); 265 | } 266 | 267 | }; 268 | 269 | 270 | var onTouchMove = function(event) { 271 | 272 | event.preventDefault(); 273 | 274 | if (isAnimating || !swipeVerticalPossible) { 275 | return; 276 | } 277 | 278 | lastXY = helper.pointerEventToXY(event); 279 | 280 | var swipeDiff = lastXY.y - startXY.y; 281 | 282 | if (Math.abs(swipeDiff) > 0) { 283 | 284 | if (swipeDiff > 0 && prevPanel) { 285 | swipeToPanel = prevPanel; 286 | swipeDirty = -1; 287 | } 288 | else if (swipeDiff < 0 && nextPanel) { 289 | swipeToPanel = nextPanel; 290 | swipeDirty = 1; 291 | } 292 | else { 293 | swipeToPanel = null; 294 | swipeDirty = 0; 295 | } 296 | 297 | if (swipeToPanel) { 298 | 299 | panelPositions = { 300 | bottom: swipeDiff, 301 | top: calculateTopPanelYPosition(visiblePanelHeight, swipeDiff, swipeDirty) 302 | }; 303 | 304 | currentPanel.style[prefixedTransform] = 'translate3d(0, ' + panelPositions.bottom + 'px, 0)'; 305 | currentPanel.style.zIndex = 1; 306 | 307 | swipeToPanel.style[prefixedTransform] = 'translate3d(0, ' + panelPositions.top + 'px, 0)'; 308 | swipeToPanel.style.zIndex = 2; 309 | } 310 | } 311 | }; 312 | 313 | // console.log(scrollableOptions.children); 314 | hideAllChildren(scrollableOptions.children); 315 | 316 | //Adding event everytime init gets called. May be add a check so only one event is added. 317 | 318 | scrollableOptions.scrollRegion.addEventListener('touchstart', onTouchStart, false); 319 | scrollableOptions.scrollRegion.addEventListener('touchmove', onTouchMove, false); 320 | scrollableOptions.scrollRegion.addEventListener('touchend', onTouchEnd, false); 321 | }; 322 | 323 | helper.enablePanelSwipe = enablePanelSwipe ; 324 | 325 | }(window)); 326 | -------------------------------------------------------------------------------- /getPrefixedProperty.js: -------------------------------------------------------------------------------- 1 | 2 | (function(window){ 3 | 4 | var helper = window.helper || (window.helper = {}); 5 | 6 | var prefixedProperties = {}; 7 | var testDiv = document.createElement('div'); 8 | var availablePrefixes = ["Webkit", "webkit", "ms", "Moz", "moz", "o", "O"]; 9 | 10 | var getPrefixedProperty = function (property) { 11 | 12 | var camelCaseProperty; 13 | if (!prefixedProperties[property]) { 14 | prefixedProperties[property] = property; 15 | 16 | if (typeof testDiv.style[property] === 'undefined') { 17 | camelCaseProperty = property.charAt(0).toUpperCase() + property.slice(1); 18 | 19 | for (var i = 0; i < availablePrefixes.length; i++) { 20 | if (typeof testDiv.style[availablePrefixes[i] + camelCaseProperty] !== "undefined") { 21 | availablePrefixes = [availablePrefixes[i]]; 22 | prefixedProperties[property] = availablePrefixes[i] + camelCaseProperty; 23 | break; 24 | } 25 | } 26 | } 27 | } 28 | 29 | return prefixedProperties[property]; 30 | }; 31 | 32 | helper.getPrefixedProperty = getPrefixedProperty; 33 | 34 | }(this)); 35 | -------------------------------------------------------------------------------- /index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Flipboard-style News Reader Using DOM 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 373 |
374 | 375 | 376 | 377 | 378 | 379 | 390 | 391 | 392 | 393 | 394 | -------------------------------------------------------------------------------- /normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /** 47 | * Address styling not present in IE 8/9. 48 | */ 49 | 50 | [hidden] { 51 | display: none; 52 | } 53 | 54 | /* ========================================================================== 55 | Base 56 | ========================================================================== */ 57 | 58 | /** 59 | * 1. Prevent system color scheme's background color being used in Firefox, IE, 60 | * and Opera. 61 | * 2. Prevent system color scheme's text color being used in Firefox, IE, and 62 | * Opera. 63 | * 3. Set default font family to sans-serif. 64 | * 4. Prevent iOS text size adjust after orientation change, without disabling 65 | * user zoom. 66 | */ 67 | 68 | html { 69 | background: #fff; /* 1 */ 70 | color: #000; /* 2 */ 71 | font-family: sans-serif; /* 3 */ 72 | -ms-text-size-adjust: 100%; /* 4 */ 73 | -webkit-text-size-adjust: 100%; /* 4 */ 74 | } 75 | 76 | /** 77 | * Remove default margin. 78 | */ 79 | 80 | body { 81 | margin: 0; 82 | } 83 | 84 | /* ========================================================================== 85 | Links 86 | ========================================================================== */ 87 | 88 | /** 89 | * Address `outline` inconsistency between Chrome and other browsers. 90 | */ 91 | 92 | a:focus { 93 | outline: thin dotted; 94 | } 95 | 96 | /** 97 | * Improve readability when focused and also mouse hovered in all browsers. 98 | */ 99 | 100 | a:active, 101 | a:hover { 102 | outline: 0; 103 | } 104 | 105 | /* ========================================================================== 106 | Typography 107 | ========================================================================== */ 108 | 109 | /** 110 | * Address variable `h1` font-size and margin within `section` and `article` 111 | * contexts in Firefox 4+, Safari 5, and Chrome. 112 | */ 113 | 114 | h1 { 115 | font-size: 2em; 116 | margin: 0.67em 0; 117 | } 118 | 119 | /** 120 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 121 | */ 122 | 123 | abbr[title] { 124 | border-bottom: 1px dotted; 125 | } 126 | 127 | /** 128 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 129 | */ 130 | 131 | b, 132 | strong { 133 | font-weight: bold; 134 | } 135 | 136 | /** 137 | * Address styling not present in Safari 5 and Chrome. 138 | */ 139 | 140 | dfn { 141 | font-style: italic; 142 | } 143 | 144 | /** 145 | * Address differences between Firefox and other browsers. 146 | */ 147 | 148 | hr { 149 | -moz-box-sizing: content-box; 150 | box-sizing: content-box; 151 | height: 0; 152 | } 153 | 154 | /** 155 | * Address styling not present in IE 8/9. 156 | */ 157 | 158 | mark { 159 | background: #ff0; 160 | color: #000; 161 | } 162 | 163 | /** 164 | * Correct font family set oddly in Safari 5 and Chrome. 165 | */ 166 | 167 | code, 168 | kbd, 169 | pre, 170 | samp { 171 | font-family: monospace, serif; 172 | font-size: 93%; 173 | } 174 | 175 | /** 176 | * Improve readability of pre-formatted text in all browsers. 177 | */ 178 | 179 | pre { 180 | white-space: pre-wrap; 181 | } 182 | 183 | /** 184 | * Set consistent quote types. 185 | */ 186 | 187 | q { 188 | quotes: "\201C" "\201D" "\2018" "\2019"; 189 | } 190 | 191 | /** 192 | * Address inconsistent and variable font size in all browsers. 193 | */ 194 | 195 | small { 196 | font-size: 80%; 197 | } 198 | 199 | /** 200 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 201 | */ 202 | 203 | sub, 204 | sup { 205 | font-size: 75%; 206 | line-height: 0; 207 | position: relative; 208 | vertical-align: baseline; 209 | } 210 | 211 | sup { 212 | top: -0.5em; 213 | } 214 | 215 | sub { 216 | bottom: -0.25em; 217 | } 218 | 219 | /* ========================================================================== 220 | Embedded content 221 | ========================================================================== */ 222 | 223 | /** 224 | * Remove border when inside `a` element in IE 8/9. 225 | */ 226 | 227 | img { 228 | border: 0; 229 | } 230 | 231 | /** 232 | * Correct overflow displayed oddly in IE 9. 233 | */ 234 | 235 | svg:not(:root) { 236 | overflow: hidden; 237 | } 238 | 239 | /* ========================================================================== 240 | Figures 241 | ========================================================================== */ 242 | 243 | /** 244 | * Address margin not present in IE 8/9 and Safari 5. 245 | */ 246 | 247 | figure { 248 | margin: 0; 249 | } 250 | 251 | /* ========================================================================== 252 | Forms 253 | ========================================================================== */ 254 | 255 | /** 256 | * Define consistent border, margin, and padding. 257 | */ 258 | 259 | fieldset { 260 | border: 1px solid #c0c0c0; 261 | margin: 0 2px; 262 | padding: 0.35em 0.625em 0.75em; 263 | } 264 | 265 | /** 266 | * 1. Correct `color` not being inherited in IE 8/9. 267 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 268 | */ 269 | 270 | legend { 271 | border: 0; /* 1 */ 272 | padding: 0; /* 2 */ 273 | } 274 | 275 | /** 276 | * 1. Correct font family not being inherited in all browsers. 277 | * 2. Correct font size not being inherited in all browsers. 278 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 279 | */ 280 | 281 | button, 282 | input, 283 | select, 284 | textarea { 285 | font-family: inherit; /* 1 */ 286 | font-size: 100%; /* 2 */ 287 | margin: 0; /* 3 */ 288 | } 289 | 290 | /** 291 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 292 | * the UA stylesheet. 293 | */ 294 | 295 | button, 296 | input { 297 | line-height: normal; 298 | } 299 | 300 | /** 301 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 302 | * All other form control elements do not inherit `text-transform` values. 303 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 304 | * Correct `select` style inheritance in Firefox 4+ and Opera. 305 | */ 306 | 307 | button, 308 | select { 309 | text-transform: none; 310 | } 311 | 312 | /** 313 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 314 | * and `video` controls. 315 | * 2. Correct inability to style clickable `input` types in iOS. 316 | * 3. Improve usability and consistency of cursor style between image-type 317 | * `input` and others. 318 | */ 319 | 320 | button, 321 | html input[type="button"], /* 1 */ 322 | input[type="reset"], 323 | input[type="submit"] { 324 | -webkit-appearance: button; /* 2 */ 325 | cursor: pointer; /* 3 */ 326 | } 327 | 328 | /** 329 | * Re-set default cursor for disabled elements. 330 | */ 331 | 332 | button[disabled], 333 | html input[disabled] { 334 | cursor: default; 335 | } 336 | 337 | /** 338 | * 1. Address box sizing set to `content-box` in IE 8/9. 339 | * 2. Remove excess padding in IE 8/9. 340 | */ 341 | 342 | input[type="checkbox"], 343 | input[type="radio"] { 344 | box-sizing: border-box; /* 1 */ 345 | padding: 0; /* 2 */ 346 | } 347 | 348 | /** 349 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 350 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 351 | * (include `-moz` to future-proof). 352 | */ 353 | 354 | input[type="search"] { 355 | -webkit-appearance: textfield; /* 1 */ 356 | -moz-box-sizing: content-box; 357 | -webkit-box-sizing: content-box; /* 2 */ 358 | box-sizing: content-box; 359 | } 360 | 361 | /** 362 | * Remove inner padding and search cancel button in Safari 5 and Chrome 363 | * on OS X. 364 | */ 365 | 366 | input[type="search"]::-webkit-search-cancel-button, 367 | input[type="search"]::-webkit-search-decoration { 368 | -webkit-appearance: none; 369 | } 370 | 371 | /** 372 | * Remove inner padding and border in Firefox 4+. 373 | */ 374 | 375 | button::-moz-focus-inner, 376 | input::-moz-focus-inner { 377 | border: 0; 378 | padding: 0; 379 | } 380 | 381 | /** 382 | * 1. Remove default vertical scrollbar in IE 8/9. 383 | * 2. Improve readability and alignment in all browsers. 384 | */ 385 | 386 | textarea { 387 | overflow: auto; /* 1 */ 388 | vertical-align: top; /* 2 */ 389 | } 390 | 391 | /* ========================================================================== 392 | Tables 393 | ========================================================================== */ 394 | 395 | /** 396 | * Remove most spacing between table cells. 397 | */ 398 | 399 | table { 400 | border-collapse: collapse; 401 | border-spacing: 0; 402 | } 403 | 404 | ::-webkit-input-placeholder { 405 | color: #999; 406 | } 407 | :-moz-placeholder { 408 | color: #999; 409 | } 410 | ::-moz-placeholder { 411 | color: #999; 412 | } 413 | :-ms-input-placeholder { 414 | color: #999; 415 | } -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | font-size: 15px; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | margin: 0 auto; 9 | padding: 0; 10 | font-family: 'Source Sans Pro', Helvetica Neue, Segoe UI, trebuchet MS, Arial, sans-serif; 11 | line-height: 1.618; 12 | color: #33393f; 13 | -webkit-font-smoothing: antialiased; 14 | font-size: inherit; 15 | height: 100%; 16 | } 17 | 18 | a { 19 | text-decoration: none; 20 | outline: none; 21 | } 22 | 23 | .fleft { 24 | float: left; 25 | } 26 | 27 | .fright { 28 | float: right; 29 | } 30 | 31 | .clearfix:after { 32 | content: ""; 33 | display: table; 34 | clear: both; 35 | } 36 | 37 | a:focus, 38 | a:active { 39 | outline: 0; 40 | } 41 | 42 | .list { 43 | margin: 0; 44 | padding: 0; 45 | list-style: none; 46 | } 47 | 48 | .theme-bg-lighter { 49 | background: rgb(255, 251, 250); 50 | } 51 | 52 | .theme-bg-lighter-dark { 53 | background: #fcf8f7; 54 | } 55 | 56 | .absolute, 57 | .card { 58 | position: absolute; 59 | top: 0; 60 | left: 0; 61 | right: 0; 62 | bottom: 0; 63 | } 64 | 65 | .card { 66 | display: none; 67 | } 68 | 69 | .scroll-swipe { 70 | overflow: hidden; 71 | } 72 | 73 | .card .full-image { 74 | background-repeat: no-repeat; 75 | background-size: cover; 76 | background-position: 50% 50%; 77 | color: #fff; 78 | } 79 | 80 | .card .full-image .content { 81 | top: auto; 82 | padding: 15px; 83 | } 84 | 85 | .card .full-image .title { 86 | font-size: 1.6rem; 87 | font-weight: 300; 88 | line-height: 1.2; 89 | } 90 | 91 | .card .item-story .story-content { 92 | position: absolute; 93 | top: 50%; 94 | left: 0; 95 | right: 0; 96 | padding: 15px; 97 | -webkit-transform: translate(0, -50%); 98 | transform: translate(0, -50%); 99 | overflow: hidden; 100 | } 101 | 102 | .card .item-story-image .story-image { 103 | position: absolute; 104 | top: 0; 105 | bottom: 60%; 106 | left: 0; 107 | right: 0; 108 | height: auto; 109 | max-height: none; 110 | background-repeat: no-repeat; 111 | background-size: cover; 112 | background-position: 50% 50%; 113 | } 114 | 115 | .card .item-story-image .story-content { 116 | position: absolute; 117 | top: 40%; 118 | left: 0; 119 | right: 0; 120 | bottom: 50px; 121 | padding: 0 15px; 122 | overflow: hidden; 123 | } 124 | 125 | .card .item-index { 126 | position: absolute; 127 | top: 10px; 128 | left: 10px; 129 | font-style: normal; 130 | border-radius: 50%; 131 | width: 30px; 132 | height: 30px; 133 | padding: 0; 134 | line-height: 30px; 135 | border: 1px solid #ddd; 136 | text-align: center; 137 | color: #ddd; 138 | z-index: 1; 139 | } 140 | 141 | .card .title { 142 | font-size: 1.14rem; 143 | font-weight: 600; 144 | line-height: 1.5; 145 | } 146 | 147 | .card .description { 148 | margin-top: 5px; 149 | } 150 | 151 | .card .media-logo { 152 | position: absolute; 153 | height: 45px; 154 | width: 45px; 155 | background-size: cover; 156 | right: 20px; 157 | bottom: 20px; 158 | border-radius: 10px; 159 | } 160 | 161 | .card .domain { 162 | font-size: 1rem; 163 | opacity: 0.7; 164 | font-weight: 300; 165 | } 166 | 167 | .card .reddit-meta { 168 | position: absolute; 169 | left: 0; 170 | right: 0; 171 | bottom: 0; 172 | padding: 10px 15px; 173 | font-weight: 300; 174 | color: rgba(0, 0, 0, 0.5); 175 | } --------------------------------------------------------------------------------