diff --git a/.gitignore b/.gitignore
index 56010bb..994d707 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ public/data/*.json
tmp/*
.DS_Store
._*
+.rvmrc
\ No newline at end of file
diff --git a/public/css/main.css b/public/css/main.css
index 276daad..e431da8 100644
--- a/public/css/main.css
+++ b/public/css/main.css
@@ -1,4 +1,4 @@
-BODY {
+body {
font-family: verdana, "helvetica neue", helvetica, sans-serif;
font-size: 10pt;
color: #000;
@@ -7,23 +7,29 @@ BODY {
border-top: 12px solid #f70;
}
-#page { min-width: 760px; max-width: 1050px; padding: 0px 36px; margin: 20px auto; }
+ol { margin: 0; padding: 0; }
-A { color: #000; text-decoration: none; }
-A:visited { color: #666; }
+#page { position: relative; min-width: 760px; max-width: 1050px; padding: 0px 36px; margin: 20px auto; }
-H1 { font-size: 2.5em; margin-bottom: 0; }
+a { color: #000; text-decoration: none; }
+a:visited { color: #666; }
-P.subhead { margin-top: 0; margin-bottom: 30px; line-height: 1.5em; }
+h1 { font-size: 2.5em; margin-bottom: 0; }
+
+p.subhead { margin-top: 0; margin-bottom: 30px; line-height: 1.5em; }
#slidercontainer { height: 52px; font-size: 0.8em; margin: 0px 24px; padding-top: 8px }
-#items { font-family: verdana, sans-serif; }
-#items ol { margin-left: 0px; list-style-position: outside; padding-left: 2.2em; }
-#items li { margin-bottom: 8px; }
-#items li.new { background-color: #fed }
+#handle_slider { -webkit-transition: all .75s ease-in-out; }
+
+#items { font-family: verdana, sans-serif; list-style-position: outside; }
+#items li { list-style: none; width: 100%; }
+
+#items li .container { -webkit-transition: all .75s ease-in-out; }
+#items li .container.new { background-color: #fed; }
+#items.animating li .container.new { background-color: white; }
-.sub { font-size: 7pt }
+.sub { font-size: 7pt; margin-bottom: 8px; }
.site { font-size: 8pt }
.sub, .sub A, .site { color: #666; }
diff --git a/public/js/app.js b/public/js/app.js
index 35473ec..738bea3 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -3,13 +3,13 @@
function updateItems(e, ui) {
// a poor man's updating status indicator
- $('#items').css('background-color', '#f6f6ef');
+ // $('#items').css('background-color', '#f6f6ef'); (Idea: Display overlay and fade)
$.getJSON('/data/' + $('select#slider').val() + '.json',function(json){
if (json) {
$('#items').css('background-color', 'inherit');
- var display = '
';
+ var display = "";
$.each(json,function(i,item){
// If the item is a local link on HN, prepend the HN URL
@@ -17,24 +17,23 @@ function updateItems(e, ui) {
item.href = 'http://news.ycombinator.com/' + item.href;
}
- var klasses = '';
+ var klasses = ' ';
// Once enough days have been crawled with the updated crawler, just use is_new.. checking the hour count is a temporary hack
if ((item.hours_ago < 2) || item.is_new) {
- klasses = 'new';
+ klasses = klasses + 'new';
};
-
- display += '' + item.title + ' ';
+
+ display += '' + item.title + ' ';
if (item.site) {
display += '
(' + item.site + ') ';
}
- display += '
';
+ display += '
';
});
- display += ' ';
-
- $('#items').html(display);
+ $('#data').html(display);
+ $('#items').quicksand($("#data > li"));
};
});
}
diff --git a/public/js/jquery.quicksand.js b/public/js/jquery.quicksand.js
new file mode 100644
index 0000000..63dcabd
--- /dev/null
+++ b/public/js/jquery.quicksand.js
@@ -0,0 +1,280 @@
+/*
+
+Quicksand 1.2.1
+
+Reorder and filter items with a nice shuffling animation.
+
+Copyright (c) 2010 Jacek Galanciak (razorjack.net) and agilope.com
+Big thanks for Piotr Petrus (riddle.pl) for deep code review and wonderful docs & demos.
+
+Dual licensed under the MIT and GPL version 2 licenses.
+http://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt
+http://github.com/jquery/jquery/blob/master/GPL-LICENSE.txt
+
+Project site: http://razorjack.net/quicksand
+Github site: http://github.com/razorjack/quicksand
+
+*/
+
+(function ($) {
+ $.fn.quicksand = function (collection, customOptions) {
+ var options = {
+ duration: 750,
+ easing: 'swing',
+ attribute: 'data-id', // attribute to recognize same items within source and dest
+ adjustHeight: 'auto', // 'dynamic' animates height during shuffling (slow), 'auto' adjusts it before or after the animation, false leaves height constant
+ useScaling: true, // disable it if you're not using scaling effect or want to improve performance
+ enhancement: function(c) {}, // Visual enhacement (eg. font replacement) function for cloned elements
+ selector: '> *'
+ };
+ $.extend(options, customOptions);
+
+ if ($.browser.msie || (typeof($.fn.scale) == 'undefined')) {
+ // Got IE and want scaling effect? Kiss my ass.
+ options.useScaling = false;
+ }
+
+ var callbackFunction;
+ if (typeof(arguments[1]) == 'function') {
+ var callbackFunction = arguments[1];
+ } else if (typeof(arguments[2] == 'function')) {
+ var callbackFunction = arguments[2];
+ }
+
+
+ return this.each(function (i) {
+ var val;
+ var animationQueue = []; // used to store all the animation params before starting the animation; solves initial animation slowdowns
+ var $collection = $(collection).clone(); // destination (target) collection
+ var $sourceParent = $(this); // source, the visible container of source collection
+ var sourceHeight = $(this).css('height'); // used to keep height and document flow during the animation
+
+ var destHeight;
+ var adjustHeightOnCallback = false;
+
+ var offset = $($sourceParent).offset(); // offset of visible container, used in animation calculations
+ var offsets = []; // coordinates of every source collection item
+
+ var $source = $(this).find(options.selector); // source collection items
+
+ // Replace the collection and quit if IE6
+ if ($.browser.msie && $.browser.version.substr(0,1)<7) {
+ $sourceParent.html('').append($collection);
+ return;
+ }
+
+ // Gets called when any animation is finished
+ var postCallbackPerformed = 0; // prevents the function from being called more than one time
+ var postCallback = function () {
+ if (!postCallbackPerformed) {
+ $sourceParent.html($dest.html()); // put target HTML into visible source container
+ if (typeof callbackFunction == 'function') {
+ callbackFunction.call(this);
+ }
+ if (adjustHeightOnCallback) {
+ $sourceParent.css('height', destHeight);
+ }
+ $sourceParent.removeClass('animating');
+ options.enhancement($sourceParent); // Perform custom visual enhancements on a newly replaced collection
+ postCallbackPerformed = 1;
+ }
+ };
+
+ // Position: relative situations
+ var $correctionParent = $sourceParent.offsetParent();
+ var correctionOffset = $correctionParent.offset();
+ if ($correctionParent.css('position') == 'relative') {
+ if ($correctionParent.get(0).nodeName.toLowerCase() == 'body') {
+
+ } else {
+ correctionOffset.top += parseFloat($correctionParent.css('border-top-width'));
+ correctionOffset.left += parseFloat($correctionParent.css('border-left-width'));
+ }
+ } else {
+ correctionOffset.top -= parseFloat($correctionParent.css('border-top-width'));
+ correctionOffset.left -= parseFloat($correctionParent.css('border-left-width'));
+ correctionOffset.top -= parseFloat($correctionParent.css('margin-top'));
+ correctionOffset.left -= parseFloat($correctionParent.css('margin-left'));
+ }
+
+
+ // keeps nodes after source container, holding their position
+ $sourceParent.css('height', $(this).height());
+ $sourceParent.addClass('animating');
+
+ // get positions of source collections
+ $source.each(function (i) {
+ offsets[i] = $(this).offset();
+ });
+
+ // stops previous animations on source container
+ $(this).stop();
+ $source.each(function (i) {
+ $(this).stop(); // stop animation of collection items
+ var rawObj = $(this).get(0);
+
+ rawObj.style.position = 'absolute';
+ rawObj.style.margin = '0';
+ rawObj.style.top = (offsets[i].top - parseFloat(rawObj.style.marginTop) - correctionOffset.top) + 'px';
+ rawObj.style.left = (offsets[i].left - parseFloat(rawObj.style.marginLeft) - correctionOffset.left) + 'px';
+ });
+
+ // create temporary container with destination collection
+ var $dest = $($sourceParent).clone();
+ var rawDest = $dest.get(0);
+ rawDest.innerHTML = '';
+ rawDest.setAttribute('id', '');
+ rawDest.style.height = 'auto';
+ rawDest.style.width = $sourceParent.width() + 'px';
+ $dest.append($collection);
+ // insert node into HTML
+ // Note that the node is under visible source container in the exactly same position
+ // The browser render all the items without showing them (opacity: 0.0)
+ // No offset calculations are needed, the browser just extracts position from underlayered destination items
+ // and sets animation to destination positions.
+ $dest.insertBefore($sourceParent);
+ $dest.css('opacity', 0.0);
+ rawDest.style.zIndex = -1;
+
+ rawDest.style.margin = '0';
+ rawDest.style.position = 'absolute';
+ rawDest.style.top = offset.top - correctionOffset.top + 'px';
+ rawDest.style.left = offset.left - correctionOffset.left + 'px';
+
+
+
+
+
+ if (options.adjustHeight === 'dynamic') {
+ // If destination container has different height than source container
+ // the height can be animated, adjusting it to destination height
+ $sourceParent.animate({height: $dest.height()}, options.duration, options.easing);
+ } else if (options.adjustHeight === 'auto') {
+ destHeight = $dest.height();
+ if (parseFloat(sourceHeight) < parseFloat(destHeight)) {
+ // Adjust the height now so that the items don't move out of the container
+ $sourceParent.css('height', destHeight);
+ } else {
+ // Adjust later, on callback
+ adjustHeightOnCallback = true;
+ }
+ }
+
+ // Now it's time to do shuffling animation
+ // First of all, we need to identify same elements within source and destination collections
+ $source.each(function (i) {
+ var destElement = [];
+ if (typeof(options.attribute) == 'function') {
+
+ val = options.attribute($(this));
+ $collection.each(function() {
+ if (options.attribute(this) == val) {
+ destElement = $(this);
+ return false;
+ }
+ });
+ } else {
+ destElement = $collection.filter('[' + options.attribute + '=' + $(this).attr(options.attribute) + ']');
+ }
+ if (destElement.length) {
+ // The item is both in source and destination collections
+ // It it's under different position, let's move it
+ if (!options.useScaling) {
+ animationQueue.push(
+ {
+ element: $(this),
+ animation:
+ {top: destElement.offset().top - correctionOffset.top,
+ left: destElement.offset().left - correctionOffset.left,
+ opacity: 1.0
+ }
+ });
+
+ } else {
+ animationQueue.push({
+ element: $(this),
+ animation: {top: destElement.offset().top - correctionOffset.top,
+ left: destElement.offset().left - correctionOffset.left,
+ opacity: 1.0,
+ scale: '1.0'
+ }
+ });
+
+ }
+ } else {
+ // The item from source collection is not present in destination collections
+ // Let's remove it
+ if (!options.useScaling) {
+ animationQueue.push({element: $(this),
+ animation: {opacity: '0.0'}});
+ } else {
+ animationQueue.push({element: $(this), animation: {opacity: '0.0',
+ scale: '0.0'}});
+ }
+ }
+ });
+
+ $collection.each(function (i) {
+ // Grab all items from target collection not present in visible source collection
+
+ var sourceElement = [];
+ var destElement = [];
+ if (typeof(options.attribute) == 'function') {
+ val = options.attribute($(this));
+ $source.each(function() {
+ if (options.attribute(this) == val) {
+ sourceElement = $(this);
+ return false;
+ }
+ });
+
+ $collection.each(function() {
+ if (options.attribute(this) == val) {
+ destElement = $(this);
+ return false;
+ }
+ });
+ } else {
+ sourceElement = $source.filter('[' + options.attribute + '=' + $(this).attr(options.attribute) + ']');
+ destElement = $collection.filter('[' + options.attribute + '=' + $(this).attr(options.attribute) + ']');
+ }
+
+ var animationOptions;
+ if (sourceElement.length === 0) {
+ // No such element in source collection...
+ if (!options.useScaling) {
+ animationOptions = {
+ opacity: '1.0'
+ };
+ } else {
+ animationOptions = {
+ opacity: '1.0',
+ scale: '1.0'
+ };
+ }
+ // Let's create it
+ d = destElement.clone();
+ var rawDestElement = d.get(0);
+ rawDestElement.style.position = 'absolute';
+ rawDestElement.style.margin = '0';
+ rawDestElement.style.top = destElement.offset().top - correctionOffset.top + 'px';
+ rawDestElement.style.left = destElement.offset().left - correctionOffset.left + 'px';
+ d.css('opacity', 0.0); // IE
+ if (options.useScaling) {
+ d.css('transform', 'scale(0.0)');
+ }
+ d.appendTo($sourceParent);
+
+ animationQueue.push({element: $(d),
+ animation: animationOptions});
+ }
+ });
+
+ $dest.remove();
+ options.enhancement($sourceParent); // Perform custom visual enhancements during the animation
+ for (i = 0; i < animationQueue.length; i++) {
+ animationQueue[i].element.animate(animationQueue[i].animation, options.duration, options.easing, postCallback);
+ }
+ });
+ };
+})(jQuery);
\ No newline at end of file
diff --git a/views/index.erb b/views/index.erb
index 0e32752..b7125bf 100644
--- a/views/index.erb
+++ b/views/index.erb
@@ -7,7 +7,8 @@
-
+
+
@@ -32,8 +33,10 @@
-
-
+