├── aware.js ├── demo.html ├── readme.md ├── shkmark_example.png └── time_of_day.html /aware.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | /* 4 | 5 | A Javascript library to help create dynamic 6 | reader-aware interfaces to content. 7 | 8 | by Ben Brown ben@xoxco.com 9 | */ 10 | 11 | var lastVisit = false; 12 | var cookieRegex = new RegExp("(?:^|.*;\\s*)lastVisit\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"); 13 | 14 | /* Helpful little date helper here! */ 15 | Date.prototype.getDOY = function() { 16 | var onejan = new Date(this.getFullYear(),0,1); 17 | return Math.ceil((this - onejan) / 86400000); 18 | } 19 | 20 | function setLastVisit(date) { 21 | if (window.localStorage) { 22 | window.localStorage.setItem('lastVisit',date); 23 | } else { 24 | document.cookie = 'lastVisit=' + escape(date) + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/"; 25 | } 26 | } 27 | 28 | 29 | function getLastVisit() { 30 | if (window.localStorage) { 31 | return window.localStorage.getItem('lastVisit'); 32 | } else { 33 | var maybeLastVisit = unescape(document.cookie.replace(cookieRegex, "$1")); 34 | return !isNaN(new Date(maybeLastVisit)) ? maybeLastVisit : null; 35 | } 36 | } 37 | 38 | function pluralizeString(num,str) { 39 | if (num==1) { 40 | return str; 41 | } else { 42 | return str+'s'; 43 | } 44 | } 45 | 46 | function relativeTimestamp(ms) { 47 | var seconds = Math.floor(ms / 1000); 48 | 49 | if (seconds < 60) { 50 | return seconds + pluralizeString(seconds,' second'); 51 | } 52 | 53 | var minutes = Math.floor(seconds/60); 54 | if (minutes < 60) { 55 | return minutes + pluralizeString(minutes,' minute'); 56 | } 57 | 58 | var hours = Math.floor(minutes/60); 59 | if (hours < 24) { 60 | return hours + pluralizeString(hours,' hour'); 61 | } 62 | 63 | var days = Math.floor(hours/24); 64 | if (days < 7) { 65 | return days + pluralizeString(days,' day'); 66 | } 67 | 68 | var weeks = Math.floor(days/7); 69 | return weeks + pluralizeString(weeks,' week'); 70 | 71 | } 72 | 73 | 74 | // insert a bookmark with a relative timestamp after the last new item on the page. 75 | $.fn.shkmark = function(options) { 76 | var settings = { 77 | 'className': 'shkmark', 78 | 'element': 'li', 79 | 'newIndicator':'.new' 80 | } 81 | 82 | $.extend(settings,options); 83 | 84 | if (!$(this).length || !$(this).filter(settings.newIndicator).length) { 85 | return; 86 | } 87 | 88 | if (!lastVisit) { 89 | lastVisit = getLastVisit(); 90 | } 91 | if (!lastVisit) { return; } 92 | lastVisit = new Date(lastVisit); 93 | var now = new Date(); 94 | 95 | var message = 'You started reading here ' + relativeTimestamp(now-lastVisit) + ' ago'; 96 | 97 | var bookmark = document.createElement(settings.element); 98 | $(bookmark).addClass(settings.className); 99 | $(bookmark).html(message); 100 | 101 | 102 | if($(this).last()[0]!=$(this).filter(settings.newIndicator)[0]) { 103 | $(this).filter(settings.newIndicator).last().after(bookmark); 104 | } 105 | 106 | 107 | } 108 | 109 | $.fn.time_of_day = function(time_of_day) { 110 | // What time of day is it? 111 | // Is it sunny or dark? 112 | // Is it lunch time? Or late night? 113 | /* 114 | 115 | 4-7 early morning 116 | 7-11 morning / breakfast time 117 | 11-13 noonish / lunch time 118 | 13-16 afternoon 119 | 16-19 early evening 120 | 19-21 evening / dinner time 121 | 21-23 night 122 | 23-4 latenight 123 | 124 | 7-19 daytime 125 | 19-7 nighttime 126 | 127 | */ 128 | reader.morning = reader.afternoon = reader.lunchtime = reader.daytime = reader.nighttime = false; 129 | if (time_of_day >= 4 && time_of_day < 6) { 130 | reader.time_of_day = 'early'; 131 | } else if (time_of_day >= 6 && time_of_day < 8) { 132 | reader.time_of_day = 'earlymorning'; 133 | reader.morning = true; 134 | } else if (time_of_day >= 8 && time_of_day < 11) { 135 | reader.time_of_day = 'latemorning'; 136 | reader.morning = true; 137 | } else if (time_of_day >= 11 && time_of_day < 13) { 138 | reader.time_of_day = 'noonish'; 139 | reader.afternoon = true; 140 | reader.lunchtime = true; // this is an illusion. 141 | } else if (time_of_day >= 13 && time_of_day < 16) { 142 | reader.time_of_day = 'afternoon'; 143 | reader.afternoon = true; 144 | } else if (time_of_day >= 16 && time_of_day < 19) { 145 | reader.time_of_day = 'earlyevening'; 146 | reader.afternoon = true; 147 | } else if (time_of_day >= 19 && time_of_day < 21) { 148 | reader.time_of_day = 'evening'; 149 | } else if (time_of_day >= 21 && time_of_day < 23) { 150 | reader.time_of_day = 'night'; 151 | } else if (time_of_day >= 23 || time_of_day < 4) { 152 | reader.time_of_day = 'latenight'; 153 | } 154 | 155 | if (time_of_day >= 6 && time_of_day <19) { 156 | reader.daytime = true; 157 | $('body').addClass('daytime'); 158 | 159 | } else { 160 | reader.nighttime = true; 161 | $('body').addClass('nighttime'); 162 | } 163 | 164 | if (reader.morning) { 165 | $('body').addClass('morning'); 166 | } 167 | if (reader.afternoon) { 168 | $('body').addClass('afternoon'); 169 | } 170 | 171 | $('body').addClass(reader.time_of_day); 172 | 173 | } 174 | 175 | $.fn.aware = function(options) { 176 | 177 | var settings = { 178 | dateAttribute: 'data-pubDate', 179 | bufferTime: 60*60*1000 // by default, leave things new if they are an hour old or less 180 | 181 | } 182 | 183 | var reader = {}; 184 | 185 | 186 | $.extend(settings,options); 187 | 188 | // retrieve user's last visit timestamp 189 | // but make sure not to override it if already set once this session! 190 | if (!lastVisit) { 191 | lastVisit = getLastVisit(); 192 | } 193 | var now = new Date(); 194 | if (!lastVisit) { 195 | setLastVisit(now); 196 | $('body').addClass('first-visit'); 197 | reader.lastVisit = now; 198 | reader.firstVisit = true; 199 | reader.secondsSinceLastVisit = 0; 200 | window.reader = reader; 201 | } else { 202 | lastVisit = new Date(lastVisit); 203 | reader.lastVisit = lastVisit; 204 | 205 | if (lastVisit.getDOY() < now.getDOY()) { 206 | $('body').addClass('first-visit-of-day'); 207 | $('body').addClass('repeat-visitor'); 208 | reader.firstVisitOfDay = true; 209 | reader.repeatVisitor = true; 210 | 211 | } else { 212 | if (!$('body').hasClass('first-visit')) { 213 | $('body').addClass('repeat-visitor'); 214 | reader.repeatVisitor = true; 215 | } 216 | } 217 | } 218 | 219 | if (!reader.firstVisit) { 220 | this.each(function() { 221 | // find the date element 222 | var postDate = $(this).attr(settings.dateAttribute); 223 | if (postDate) { 224 | var arr = postDate.split(/[- :]/); 225 | postTimestamp = new Date(arr[0], arr[1]-1, arr[2], arr[3], arr[4], arr[5]); 226 | if (postTimestamp > lastVisit-settings.bufferTime) { 227 | $(this).addClass('new'); 228 | } else { 229 | $(this).addClass('seen'); 230 | } 231 | 232 | } 233 | 234 | }); 235 | } 236 | 237 | 238 | reader.secondsSinceLastVisit = Math.floor((now-lastVisit)/1000); 239 | reader.timeSinceLastVisit = relativeTimestamp(now-lastVisit); 240 | 241 | window.reader = reader; 242 | 243 | $.fn.time_of_day(new Date().getHours()); 244 | 245 | 246 | setLastVisit(now); 247 | } 248 | 249 | })(jQuery); -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | Reader Aware Design Demo 3 | 4 | 5 | 11 | 201 | 202 | 203 | 204 | 205 |
206 | Powered by Aware.js: Get the code 207 |
208 |
209 |

Why can't web pages adjust their design to help you read them?

210 |

With our magical jQuery Plugin, they can!

211 |
212 |
213 |

Why not help our readers see what's new to them, or tune the layout for different viewing scenarios?

214 |

This jQuery extension extends responsive design beyond screen size measurements, allowing designers and developers 215 | to adjust layouts to real life use cases using only CSS. 216 |

217 |

Reload to see the page adjust itself

218 |

View as a first time visitor

219 |

View as a repeat visitor, visiting for the first time of the day

220 |
221 |
222 | 223 | You are a first time visitor. Reload to see the layout adjust! 224 | You are a repeat visitor, but this is your first visit today. Reload to see the layout adjust! 225 | You a repeat visitor, coming more than once a day. 226 | 227 |
228 | 229 |
230 | 231 |

New Content Zine

232 |
233 |

Welcome back!

234 |
235 | 236 | 237 | 248 | 249 | 250 | 285 | 286 | 287 | 288 | 381 | 382 |
383 | 384 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Aware.js 2 | ## Make Your Site Reader Aware 3 | 4 | Aware.js is a simple jQuery plugin that allows a site to customize and personalize the display of content based on a reader's behavior 5 | without requiring login, authentication, or any server-side processing. 6 | 7 | [Read about the concept of "reader aware design."](http://notes.xoxco.com/post/36089202908/reader-aware-design) 8 | 9 | [View Demo of Aware.js in action.](http://xoxco.com/projects/code/aware/demo.html) This demo uses Aware.js and CSS only to drastically alter the layout an otherwise static page in three different ways based on the reader's personalized relationship with the content. 10 | 11 | Aware also provides a handy method for personalizing the display of content based on the time of day. 12 | 13 | [View Demo of Aware's time of day feature.](http://xoxco.com/projects/code/aware/time_of_day.html) This demo uses Aware.js and CSS only to change the mood of a page depending on what time it is. 14 | 15 | Created by [XOXCO](http://xoxco.com) 16 | 17 | 18 | ## Download 19 | 20 | [Get it from Github](https://github.com/xoxco/awarejs) 21 | 22 | Aware.js is released under the MIT Open Source license. It's free to use, and you can use it however you want! 23 | 24 | ## Instructions 25 | 26 | Aware.js bundles several features into one easy to implement library. To add it to your site, include the script tag, and then initiate the plugin: 27 | 28 | $().aware(); 29 | 30 | Aware.js will then leap into action, and perform a variety of useful functions like: 31 | 32 | **Tracking the amount of time between a reader's visits** 33 | 34 | Aware.js will track a reader's visits, and provide information about the reader's habits, making information available to developers via CSS classes AND via a Javascript *reader* object. 35 | 36 | Classes will be added to the body tag to represent various states a reader might be in: 37 | 38 | .first-visit - This is the reader's first visit on record. 39 | 40 | .repeat-visitor - This reader has been to the site at least once before. 41 | 42 | .first-visit-of-day - This is a reader who has visited before, but not today. 43 | 44 | In addition, the *reader* object contains this same information, along with some specific information about previous visits. This is useful for Javascript developers who want to add more dynamic features to their site based on this information (and who don't want to query the DOM for classes) 45 | 46 | reader.firstVisit - true/false 47 | reader.repeatVisitor - true/false 48 | reader.firstVisitOfDay - true/false 49 | 50 | reader.lastVisit - Javascript Date object 51 | reader.secondsSinceLastVisit - seconds since last visit 52 | reader.timeSinceLastVisit - relative timestamp "28 minutes" 53 | 54 | This simple information can be used to mildly or drastically alter the layout of a page. For example, first time visitors can be shown a special introduction, while repeat visitors are shown only the newest things. Visitors who haven't come in a while can be presented with a recap of recent, important items. 55 | 56 | **Flag Content As New or Seen** 57 | 58 | Aware.js can also flag content on a page as new to the reader, helping them keep up-to-date without remembering what they read last visit. 59 | 60 | To enable this, the publication date of each piece of content must be included as an HTML5 data attribute, *data-pubDate* in the format YYYY-MM-DD HH:MM:SS, as below: 61 | 62 |
63 | POST HERE 64 |
65 | 66 | Once these attributes are added to the markup, tell Aware.js to process them by passing in a element selector: 67 | 68 | $('.post').aware(); 69 | 70 | Any matching element with a date after the reader's last visit will have the *.new* class added. Any item with a date before the reader's last visit will have *.seen* added. 71 | 72 | Highlight new elements by using the .new class in your CSS: 73 | 74 | .post.new { 75 | background: #FFFF99; 76 | } 77 | 78 | **Track the reader's local time of day** 79 | 80 | What if your site could look different during breakfast than it does late at night? Aware.js adds CSS classes based on the time of day. 81 | Consider how your visitor's needs might be different as the natural cycle of the day progresses. 82 | 83 | Is the sun out? Or is it dark? Aware.js adds a class for daytime and nighttime: 84 | 85 | .daytime 86 | .nighttime 87 | 88 | There are additional classes for smaller slices of the day: 89 | 90 | .daytime 91 | .morning 92 | .earlymorning 93 | .latemorning 94 | .afternoon 95 | .noonish 96 | .earlyevening 97 | .nighttime 98 | .evening 99 | .night 100 | .latenight 101 | .early 102 | 103 | In addition, the *reader* object contains this same information: 104 | 105 | reader.time_of_day - early,earlymorning, latemorning, 106 | noonish,earlyevening,evening,night, or latenight 107 | reader.morning - true/false 108 | reader.afternoon - true/false 109 | reader.daytime - true/false 110 | reader.nighttime - true/false 111 | 112 | 113 | **Insert Relative Bookmark** 114 | 115 | Inspired by the Andre Torrez's image sharing site, [Mlkshk](http://mlkshk.com), Aware.js also provides a method for inserting relative bookmarks into the stream of content to clearly delineate content added since the reader's last visit. [See demo.](http://xoxco.com/projects/code/aware/demo.html) 116 | 117 | ![Shkmark Demo](http://xoxco.com/projects/code/aware/shkmark_example.png) 118 | 119 | To inject a relative bookmark *below the last new post*, call the deferentially named *shkmark()* function after calling *aware()*. The relative bookmark will include the message, "You started reading here *timestamp* ago." 120 | 121 | $('.post').aware(); 122 | $('.post').shkmark(); 123 | 124 | // this would result in a div with class .bookmark being added: 125 | //
  • You started reading here 5 minutes ago
  • 126 | 127 | ## Options 128 | 129 | Aware.js has several options that can be used to customize its behavior: 130 | 131 | $(optional_selector).aware({ 132 | dateAttribute: 'data-pubDate', 133 | // change the name of the attribute that contains the date 134 | 135 | 136 | bufferTime: 10*60*1000 137 | // time in milliseconds that a freshly published piece 138 | // of content should be flagged as new regardless of reader's last visit. 139 | }); 140 | 141 | By default, shkmarks are created as <li> elements with the class .shkmark, but these can be overridden: 142 | 143 | $(.post').shkmark({ 144 | element: 'div', 145 | className: 'bookmark' 146 | }); 147 | 148 | // this would result in a div with class .bookmark being added: 149 | //
    You started reading here 5 minutes ago
    150 | 151 | -------------------------------------------------------------------------------- /shkmark_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoxco/awarejs/824cebead08e3bb20401c5334689ea19bc89b4bf/shkmark_example.png -------------------------------------------------------------------------------- /time_of_day.html: -------------------------------------------------------------------------------- 1 | 2 | Reader Aware Design Demo 3 | 4 | 5 | 10 | 125 | 126 | 155 | 156 | 157 |
    158 | Powered by Aware.js: Get the code 159 |
    160 |
    161 |

    Good Morning!

    162 |

    Good Afternoon!

    163 |

    Good Evening!

    164 | 165 |

    166 | The design of this page
    167 | responds to the time
    168 | where you're sitting right now.

    169 |
    170 |

    Change the time

    171 |

    12

    172 | 173 |
    174 |

    Alter the layout or content of your site
    175 | based on the time of day
    176 | and a bunch of other "reader aware" attributes
    177 | with no server side code: 178 |

    179 |

    Aware.js

    180 |
    181 | --------------------------------------------------------------------------------