Skip to content

Instantly share code, notes, and snippets.

@tomger
Created September 30, 2010 21:52
Show Gist options
  • Select an option

  • Save tomger/605390 to your computer and use it in GitHub Desktop.

Select an option

Save tomger/605390 to your computer and use it in GitHub Desktop.

Revisions

  1. tomger revised this gist Oct 11, 2010. 1 changed file with 41 additions and 8 deletions.
    49 changes: 41 additions & 8 deletions fatc.html
    Original file line number Diff line number Diff line change
    @@ -140,6 +140,9 @@
    // Endlessly load older tweets when the user scrolls down:
    show_more_loop();
    }
    and {
    ui_mute_loop();
    }
    }
    finally {
    // Clean up:
    @@ -164,9 +167,42 @@
    function update_counter() {
    counter.text(140 - status_el.val().length);
    }

    function ui_mute_loop() {
    if (!window["localStorage"]) return;

    var filterArray = [];
    tweetFilter = function(tweet) {
    for (var i = filterArray.length; i >= 0; --i) {
    var keyword = filterArray[i];
    if (!keyword) continue;
    if (keyword.indexOf("@") == 0 && tweet.user.screen_name == keyword.substring(1)) return true;
    else if (tweet.text.indexOf(keyword) != -1) return true;
    }

    return false;
    };

    var button = $("<a href='#'>Mute list</a>").prependTo("#session_buttons");
    try {
    while(true) {
    filterArray = localStorage.mute ? localStorage.mute.split(" ") : [];
    button.$click();
    var rv = prompt("Enter a space-separated list of #keywords or @users you want to mute.", localStorage.mute);
    if (rv != null) {
    localStorage.mute = rv;
    $(window).trigger("settingschanged");
    }
    }
    } finally {
    button.remove();
    }
    }

    // Append/prepend a tweet to the timeline
    function showTweet(tweet, append) {
    if (window["tweetFilter"] && tweetFilter(tweet)) return;

    var date = new Date(tweet.created_at.replace(/^\w+ (\w+) (\d+) ([\d:]+) \+0000 (\d+)$/,
    "$1 $2 $4 $3 UTC"));
    var elapsed = Math.round(((new Date()).getTime() - date.getTime()) / 60000);
    @@ -249,19 +285,16 @@
    for (var i = timeline.length-1, tweet; tweet = timeline[i]; --i)
    showTweet(tweet, false);
    }
    // Twitter is rate-limited to ~150calls/h. Wait for 60 seconds
    // until we fetch more tweets.
    hold(60*1000);
    // We could also add an override 'refresh-now' button by sticking
    // the hold() in a waitfor/or:
    /*

    waitfor {
    // Twitter is rate-limited to ~150calls/h. Wait for 60 seconds
    // until we fetch more tweets.
    hold(60*1000);
    }
    or {
    $("#refresh_button").$click();
    $(window).waitFor("settingschanged");
    $("#timeline").empty();
    }
    */
    }
    }

  2. tomger revised this gist Oct 3, 2010. 1 changed file with 22 additions and 2 deletions.
    24 changes: 22 additions & 2 deletions fatc.html
    Original file line number Diff line number Diff line change
    @@ -169,7 +169,26 @@
    function showTweet(tweet, append) {
    var date = new Date(tweet.created_at.replace(/^\w+ (\w+) (\d+) ([\d:]+) \+0000 (\d+)$/,
    "$1 $2 $4 $3 UTC"));

    var elapsed = Math.round(((new Date()).getTime() - date.getTime()) / 60000);
    if (elapsed < 60) {
    var date_string = elapsed + " minutes";
    }
    else if (elapsed < 60*24) {
    var date_string = Math.round(elapsed / 60) + " hours";
    }
    else {
    var date_string = date.getDate() + "/" + date.getMonth();
    }

    if (tweet.entities && tweet.entities.urls) {
    for (var i = tweet.entities.urls.length - 1, entity; entity = tweet.entities.urls[i]; --i) {
    tweet.text = common.supplant("{a}<a target='_blank' href='{b}'>{b}</a>{c}", {
    a: tweet.text.substring(0, entity.indices[0]),
    b: entity.url,
    c: tweet.text.substring(entity.indices[1])
    });
    }
    }
    // Construct the html for the tweet. Note how we can use
    // multiline strings in StratifiedJS. We also use the
    // 'supplant' function from the 'common' module for getting
    @@ -192,7 +211,7 @@
    text: tweet.text,
    image: tweet.user.profile_image_url,
    screenname: tweet.user.screen_name,
    meta: date.toLocaleDateString() + " - " + date.toLocaleTimeString()
    meta: date_string
    }));
    }

    @@ -220,6 +239,7 @@
    while (true) {
    // Fetch tweets from twitter:
    var timeline = fetch_tweets({
    include_entities: true,
    count: 30,
    since_id: $(".tweet_wrapper:first").attr("tweetid")
    });
  3. tomger revised this gist Oct 1, 2010. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions fatc.html
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,5 @@
    <!DOCTYPE html>
    <html>
    <head>
    <html><head>
    <title>Fork-A-Twitter-Client</title>
    <!--
  4. tomger revised this gist Oct 1, 2010. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions fatc.html
    Original file line number Diff line number Diff line change
    @@ -84,7 +84,7 @@

    // Show the twitter connect button:
    T("#login").connectButton();
    // And finally... run our application logic in an endless loop:
    // Run our application logic in an endless loop:
    while (true) {
    try {
    main();
    @@ -106,7 +106,7 @@
    $("#timeline").empty();

    try {
    // First let's set the last tweet and the background based on the
    // Let's set the last tweet and the background based on the
    // user's prefs:
    var profile = T.call("users/show", {user_id: T.currentUser.id});
    $("#current_user").text(profile.screen_name);
  5. tomger revised this gist Oct 1, 2010. 1 changed file with 74 additions and 73 deletions.
    147 changes: 74 additions & 73 deletions fatc.html
    Original file line number Diff line number Diff line change
    @@ -78,6 +78,79 @@
    var tweeting_button = $("#tweeting_button");
    var status_el = $("#status");
    var counter = $("#tweeting_status");

    //----------------------------------------------------------------------
    // main program loop

    // Show the twitter connect button:
    T("#login").connectButton();
    // And finally... run our application logic in an endless loop:
    while (true) {
    try {
    main();
    }
    catch(e) {
    alert("Error:"+e);
    }
    }

    //----------------------------------------------------------------------
    // main application logic:

    function main() {
    // First wait until we're connected:
    if (!T.isConnected())
    T.waitforEvent("authComplete");
    $("#login").hide();
    $("#welcome").hide();
    $("#timeline").empty();

    try {
    // First let's set the last tweet and the background based on the
    // user's prefs:
    var profile = T.call("users/show", {user_id: T.currentUser.id});
    $("#current_user").text(profile.screen_name);
    $("#current_user").prepend("<img src='"+profile.profile_image_url + "'/>");
    setLatestTweet(profile.status);
    $("body").css({
    background: common.supplant("#{color} url({image}) {repeat}", {
    color: profile.profile_background_color,
    image: profile.profile_background_image_url,
    repeat: profile.profile_background_tile ? "repeat" : "no-repeat"
    })
    });

    // Now we'll do several things in parallel, by combining them with
    // the stratified construct waitfor/and:
    waitfor {
    waitfor_signout();
    // Now exit main(). The other clauses in the waitfor/and will
    // automatically be aborted cleanly (i.e. eventlisteners will be
    // removed, etc).
    return;
    }
    and {
    // Endlessly handle tweets by the user:
    tweet_loop();
    }
    and {
    // Endlessly update the timeline:
    update_timeline_loop();
    }
    and {
    // Endlessly load older tweets when the user scrolls down:
    show_more_loop();
    }
    }
    finally {
    // Clean up:
    $("#current_user").empty();
    $("#timeline").empty();
    $("#welcome").show();
    $("body").css({background:""});
    }
    }


    //----------------------------------------------------------------------
    // Helper functions
    @@ -263,79 +336,7 @@
    $("#login").show();
    }
    }

    //----------------------------------------------------------------------
    // main application logic:

    function main() {
    // First wait until we're connected:
    if (!T.isConnected())
    T.waitforEvent("authComplete");
    $("#login").hide();
    $("#welcome").hide();
    $("#timeline").empty();

    try {
    // First let's set the last tweet and the background based on the
    // user's prefs:
    var profile = T.call("users/show", {user_id: T.currentUser.id});
    $("#current_user").text(profile.screen_name);
    $("#current_user").prepend("<img src='"+profile.profile_image_url + "'/>");
    setLatestTweet(profile.status);
    $("body").css({
    background: common.supplant("#{color} url({image}) {repeat}", {
    color: profile.profile_background_color,
    image: profile.profile_background_image_url,
    repeat: profile.profile_background_tile ? "repeat" : "no-repeat"
    })
    });

    // Now we'll do several things in parallel, by combining them with
    // the stratified construct waitfor/and:
    waitfor {
    waitfor_signout();
    // Now exit main(). The other clauses in the waitfor/and will
    // automatically be aborted cleanly (i.e. eventlisteners will be
    // removed, etc).
    return;
    }
    and {
    // Endlessly handle tweets by the user:
    tweet_loop();
    }
    and {
    // Endlessly update the timeline:
    update_timeline_loop();
    }
    and {
    // Endlessly load older tweets when the user scrolls down:
    show_more_loop();
    }
    }
    finally {
    // Clean up:
    $("#current_user").empty();
    $("#timeline").empty();
    $("#welcome").show();
    $("body").css({background:""});
    }
    }

    //----------------------------------------------------------------------
    // main program loop

    // Show the twitter connect button:
    T("#login").connectButton();
    // And finally... run our application logic in an endless loop:
    while (true) {
    try {
    main();
    }
    catch(e) {
    alert("Error:"+e);
    }
    }


    </script>
    <style>
    body {
  6. tomger revised this gist Oct 1, 2010. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion fatc.html
    Original file line number Diff line number Diff line change
    @@ -60,7 +60,7 @@
    */

    var API_KEY = "4Snb518PCIpEbYFt4Obw";
    var API_KEY = "OQTPVZIstxfxFVB2kD5oA";

    //----------------------------------------------------------------------
    // Initialization
  7. tomger revised this gist Oct 1, 2010. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion fatc.html
    Original file line number Diff line number Diff line change
    @@ -103,7 +103,7 @@
    // 'supplant' function from the 'common' module for getting
    // values into our string:
    $("#timeline")[append ? "append" : "prepend"](common.supplant("\
    <div class='timeline_item'>
    <div class='timeline_item user_{screenname}'>
    <div class='tweet_wrapper' tweetid='{tweetid}'>
    <span class='tweet_thumb'>
    <img src='{image}' width='48' height='48'/>
  8. tomger revised this gist Oct 1, 2010. 1 changed file with 8 additions and 8 deletions.
    16 changes: 8 additions & 8 deletions fatc.html
    Original file line number Diff line number Diff line change
    @@ -147,10 +147,10 @@
    // Run an endless loop:
    while (true) {
    // Fetch tweets from twitter:
    var timeline = fetch_tweets(
    {count: 30,
    since_id: $(".tweet_wrapper:first").attr("tweetid")
    });
    var timeline = fetch_tweets({
    count: 30,
    since_id: $(".tweet_wrapper:first").attr("tweetid")
    });

    if (timeline && timeline.length) {
    // Prepend tweets to timeline:
    @@ -200,10 +200,10 @@
    $(".timeline_item:last").$click();
    }
    or {
    timeline = fetch_tweets(
    {count: 30,
    max_id: $(".tweet_wrapper:last").attr("tweetid")-1
    });
    timeline = fetch_tweets({
    count: 30,
    max_id: $(".tweet_wrapper:last").attr("tweetid")-1
    });
    if (!timeline.length) return; // we've loaded all tweets there are
    }
    finally {
  9. tomger created this gist Sep 30, 2010.
    571 changes: 571 additions & 0 deletions fatc.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,571 @@
    <!DOCTYPE html>
    <html>
    <head>
    <title>Fork-A-Twitter-Client</title>
    <!--
    Basic OniApollo/StratifiedJS Twitter Client application scaffold.
    See http://fatc.onilabs.com/
    THIS FILE IS IN THE PUBLIC DOMAIN.
    You can use this file however we want, but if you do something
    interesting with it I'd love to hear about it!
    @tomg
    Make sure the following line points to the location of your oni-apollo.js!
    You can hotlink to http://code.onilabs.com/0.9.2/oni-apollo.js if you
    want:
    -->
    <script src="http://code.onilabs.com/0.9.2/oni-apollo.js"></script>

    <script type="text/sjs">
    /*
    This is more of a scaffold than a proper twitter client.
    It lacks any sophistication and basically just allows you to sign-in
    and tweet, it shows and auto-updates your timeline, and it loads older
    tweets when you scroll down the page.
    It's easy to extend, so go on and hack it :-)
    Oh, and IT RUNS COMPLETELY CLIENT-SIDE. There is no server code involved.
    It talks to twitter directly. You don't even need a proper domain to
    run it from; you can serve it from 'localhost'. E.g. if you have a Mac,
    you can serve it via Personal Web Sharing - see
    http://www.macinstruct.com/node/112 .
    * HOW TO GET A TWITTER APPLICATION ID:
    Log into twitter and go to http://dev.twitter.com/apps/ .
    Register a new application, setting the 'Callback URL' to the site where
    your app will live (this can be 'http://localhost'!).
    Set the 'Default Access type' to 'Read & Write'.
    Then change the application id below to the @Anywhere API key of your app.
    * ABOUT STRATIFIED JAVASCRIPT / ONI APOLLO:
    Oni Apollo, the library that we use here, allows you to make calls to
    the Twitter API directly from the client in a NON-CALLBACK style.
    See http://onilabs.com/api#twitter.
    It allows you to call any function in the standard RESTful Twitter API
    (see http://dev.twitter.com/doc) in this way:
    var result = T.call("statuses/home_timeline", params);
    StratifiedJS is the extended JS used by Oni Apollo to make the
    non-callback style possible (and more!). See http://stratifiedjs.org/sjsdocs.
    */

    var API_KEY = "4Snb518PCIpEbYFt4Obw";

    //----------------------------------------------------------------------
    // Initialization

    // Load the @Anywhere API and init with your application id:
    var T = require("twitter").initAnywhere({id:API_KEY});

    // We'll use jquery; load it from Google's CDN and install stratified
    // bindings ($click, etc):
    require("jquery-binding").install();

    // We'll also use various methods from the common module (supplant, ...)
    var common = require("common");

    var tweeting_button = $("#tweeting_button");
    var status_el = $("#status");
    var counter = $("#tweeting_status");

    //----------------------------------------------------------------------
    // Helper functions

    function setLatestTweet(tweet) {
    $("#latest")[tweet?"show":"hide"]();
    if (tweet)
    $("#latest span").text(tweet.text);
    }

    // 'characters left' counter
    function update_counter() {
    counter.text(140 - status_el.val().length);
    }

    // Append/prepend a tweet to the timeline
    function showTweet(tweet, append) {
    var date = new Date(tweet.created_at.replace(/^\w+ (\w+) (\d+) ([\d:]+) \+0000 (\d+)$/,
    "$1 $2 $4 $3 UTC"));

    // Construct the html for the tweet. Note how we can use
    // multiline strings in StratifiedJS. We also use the
    // 'supplant' function from the 'common' module for getting
    // values into our string:
    $("#timeline")[append ? "append" : "prepend"](common.supplant("\
    <div class='timeline_item'>
    <div class='tweet_wrapper' tweetid='{tweetid}'>
    <span class='tweet_thumb'>
    <img src='{image}' width='48' height='48'/>
    </span>
    <span class='tweet_body'>
    <span class='screenname'>{screenname}</span>
    <span class='content'>{text}</span>
    <span class='meta'>{meta}</span>
    </span>
    </div>
    </div>
    ", {
    tweetid: tweet.id,
    text: tweet.text,
    image: tweet.user.profile_image_url,
    screenname: tweet.user.screen_name,
    meta: date.toLocaleDateString() + " - " + date.toLocaleTimeString()
    }));
    }

    // Helper to fetch new tweets:
    function fetch_tweets(params) {
    try {
    return T.call("statuses/home_timeline", params);
    }
    catch(e) {
    // Are we still connected? If not, quit the app:
    if (!T.isConnected())
    throw "Disconnected";
    // else try again in 10s:
    // XXX should really have a back-off algo here
    // XXX should examine exception object.
    hold(10*1000);
    return fetch_tweets(params);
    }
    }

    // Function that periodically fetches new tweets from twitter and
    // displays them:
    function update_timeline_loop() {
    // Run an endless loop:
    while (true) {
    // Fetch tweets from twitter:
    var timeline = fetch_tweets(
    {count: 30,
    since_id: $(".tweet_wrapper:first").attr("tweetid")
    });

    if (timeline && timeline.length) {
    // Prepend tweets to timeline:
    for (var i = timeline.length-1, tweet; tweet = timeline[i]; --i)
    showTweet(tweet, false);
    }
    // Twitter is rate-limited to ~150calls/h. Wait for 60 seconds
    // until we fetch more tweets.
    hold(60*1000);
    // We could also add an override 'refresh-now' button by sticking
    // the hold() in a waitfor/or:
    /*
    waitfor {
    hold(60*1000);
    }
    or {
    $("#refresh_button").$click();
    }
    */
    }
    }

    // Helper that waits until the user scrolls to the bottom of the page:
    function waitforScrollToBottom() {
    do {
    $(window).$scroll();
    }
    while ($(document).height()- $(window).height() - $(document).scrollTop() > 300)
    }

    // Runs a loop that waits for the user to scroll to the bottom, and
    // then loads more tweets:
    function show_more_loop() {
    while (true) {
    waitforScrollToBottom();

    var timeline = null;
    waitfor {
    // show a cancel button:
    $("#timeline").append("\
    <div class='timeline_item loading_more'>
    Loading more tweets... click here to cancel
    </div>");
    // wait for it to be clicked; if that happens before the request
    // completes, the request will be cancelled, by virtue of the
    // waitfor/or.
    $(".timeline_item:last").$click();
    }
    or {
    timeline = fetch_tweets(
    {count: 30,
    max_id: $(".tweet_wrapper:last").attr("tweetid")-1
    });
    if (!timeline.length) return; // we've loaded all tweets there are
    }
    finally {
    // remove the cancel button:
    $(".timeline_item:last").remove();
    }

    if (timeline && timeline.length) {
    // Append tweets to timeline:
    for (var i = 0, tweet; tweet = timeline[i]; ++i)
    showTweet(tweet, true);
    }
    }
    }

    // Runs an endless loop that checks for the 'tweet' button to be
    // clicked and sends out a tweet if it is:
    function tweet_loop() {
    try {
    $(".tweet_box").show();
    while (true) {
    tweeting_button.$click();
    tweeting_button.attr("disabled", "disabled");
    $("#tweeting_status").text("Tweeting...");
    try {
    var tweet = T.call("statuses/update", {status: status_el.val() });
    status_el.val("");
    showTweet(tweet);
    setLatestTweet(tweet);
    }
    catch (e) {
    alert("Error posting: " + e.response.error);
    }
    update_counter();
    tweeting_button.removeAttr("disabled");
    }
    }
    finally {
    $(".tweet_box").hide();
    }
    }

    // shows a signout button and blocks until it is clicked
    function waitfor_signout() {
    try {
    // Show a signout button and wait for it to be clicked:
    var signout = $("<a href='#'>Sign out of twitter</a>").appendTo("#logout");
    var e = signout.$click();
    e.returnValue = false;
    e.preventDefault();

    // Ok, signout button was clicked; sign out, hide button & clear timeline:
    twttr.anywhere.signOut();
    }
    finally {
    signout.remove();
    $("#login").show();
    }
    }

    //----------------------------------------------------------------------
    // main application logic:

    function main() {
    // First wait until we're connected:
    if (!T.isConnected())
    T.waitforEvent("authComplete");
    $("#login").hide();
    $("#welcome").hide();
    $("#timeline").empty();

    try {
    // First let's set the last tweet and the background based on the
    // user's prefs:
    var profile = T.call("users/show", {user_id: T.currentUser.id});
    $("#current_user").text(profile.screen_name);
    $("#current_user").prepend("<img src='"+profile.profile_image_url + "'/>");
    setLatestTweet(profile.status);
    $("body").css({
    background: common.supplant("#{color} url({image}) {repeat}", {
    color: profile.profile_background_color,
    image: profile.profile_background_image_url,
    repeat: profile.profile_background_tile ? "repeat" : "no-repeat"
    })
    });

    // Now we'll do several things in parallel, by combining them with
    // the stratified construct waitfor/and:
    waitfor {
    waitfor_signout();
    // Now exit main(). The other clauses in the waitfor/and will
    // automatically be aborted cleanly (i.e. eventlisteners will be
    // removed, etc).
    return;
    }
    and {
    // Endlessly handle tweets by the user:
    tweet_loop();
    }
    and {
    // Endlessly update the timeline:
    update_timeline_loop();
    }
    and {
    // Endlessly load older tweets when the user scrolls down:
    show_more_loop();
    }
    }
    finally {
    // Clean up:
    $("#current_user").empty();
    $("#timeline").empty();
    $("#welcome").show();
    $("body").css({background:""});
    }
    }

    //----------------------------------------------------------------------
    // main program loop

    // Show the twitter connect button:
    T("#login").connectButton();
    // And finally... run our application logic in an endless loop:
    while (true) {
    try {
    main();
    }
    catch(e) {
    alert("Error:"+e);
    }
    }

    </script>
    <style>
    body {
    -webkit-font-smoothing: antialiased;
    padding: 0; margin: 0;
    background: #C0FFD8;
    }
    h2 {
    color: #999;
    text-shadow: white 0px 1px 0px;
    font-size:17px;
    margin: 0 0 10px 0;
    }
    body, textarea, input {
    font: 15px sans-serif;
    line-height: 19px;
    }
    #timeline {
    margin: 0;
    padding: 0;
    }
    #top_bar {
    background: rgba(0,0,0,0.2);
    filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#AA000000,endColorstr=#AA000000);
    height: 2.5em;
    margin-bottom: 10px;
    line-height: 2.5em;
    }
    #session_buttons {
    float:right;
    margin-right: 10px;
    font-size: 12px;
    }
    #session_buttons * {
    color: #fff;
    text-shadow: 0px 1px rgba(0,0,0,0.2);
    font-weight:bold;
    }
    #logout, #login, #current_user {
    display:inline;
    margin-left:10px;
    }
    #current_user img {
    width: 20px; height:20px;
    vertical-align:middle;
    margin: -2px 5px 0 0;
    }
    #title_bar {
    color: #fff;
    font-weight:bold;
    font-size: 20px;
    text-shadow: 0px 1px rgba(0,0,0,0.3);
    }
    #wrapper, #title_bar {
    width: 480px;
    margin: 0 auto;
    }

    #wrapper {
    padding: 0;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    background:#fff;
    overflow:hidden;
    }
    #info {
    width: 441px;
    padding: 0 0px 40px 0;
    }
    #status {
    border: 1px solid #aaa;
    padding: 4px 2px;
    height: 2.5em;
    resize: none;
    overflow:auto;
    margin-bottom: 5px;
    width:435px;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    -webkit-box-shadow: white 0px 1px;
    -moz-box-shadow: white 0px 1px;
    }
    #tweeting_button_container {
    float:right;
    margin:0;
    }
    #tweeting_button {

    }
    #tweeting_status {
    position:relative;
    right: 5px;
    top: 3px;
    color: #555;
    }
    #latest {
    float:left;
    width: 300px;
    line-height: 16px;
    height: 30px;
    overflow:hidden;
    }
    .tweet_wrapper {
    margin: 0;
    line-height: 16px;
    display:block;
    }
    .tweet_wrapper, .tweet_box {
    padding: 15px 30px 10px 20px;
    }
    .tweet_box {
    background: #f8f8f8;
    -moz-border-radius: 3px 3px 0 0;
    display:none;
    }
    .timeline_item, .tweet_box {
    border-bottom: 1px solid #eee;
    }
    .tweet_thumb {
    position:absolute;
    display: block;
    height: 50px;
    }
    .tweet_body {
    margin: -5px 0 0 63px;
    min-height:58px;
    display:block;
    word-wrap: break-word;
    }
    .tweet_body .content {
    color: #444
    }
    .tweet_body .meta {
    display:block;
    line-height: 25px;
    }
    .tweet_body .meta, #latest {
    font-size: 11px;
    color: #666;
    }
    .tweet_body .screenname {
    color: #222;
    line-height:19px;
    display:block;
    font-weight:bold;
    }
    .loading_more {
    text-align:center;
    color: #666;
    padding: 20px;
    }
    #toolbar {
    position:absolute;
    top: 10px; right: 10px;
    }
    button {
    background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ddd));
    background: -moz-linear-gradient(center top, #fff, #ddd);
    -webkit-box-shadow: white 0px 1px;
    -moz-box-shadow: white 0px 1px;
    text-shadow: 0px 1px white;
    color: #555;
    border: 1px solid #aaa;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;

    cursor: pointer;
    overflow: visible;
    vertical-align: middle;
    white-space: nowrap;
    text-align:center;
    margin:0;
    font-weight: normal;
    height: 2.0em;
    line-height:1.2em;
    padding: 0.1em 0.6em;
    font-size: 15px;
    }
    button:hover {
    border-color: #888;
    }
    button:active {
    border-color: #444;
    }
    #welcome {
    margin: 0 auto;
    width: 280px;
    font-size: 20px;
    line-height: 27px;
    color: #444;
    background: transparent url(http://fatc.onilabs.com/fatc.png) no-repeat top right;
    padding: 30px 200px 30px 0;
    }
    #welcome a {color: #222}
    </style>
    </head>
    <body>
    <div id="top_bar">
    <div id="session_buttons">
    <div id="current_user"></div>
    <div id="login"></div>
    <div id="logout"></div>
    </div>
    <div id="title_bar">Fork-A-Twitter-Client</div>
    </div>
    <div id="wrapper">
    <div class="tweet_box">
    <h2>What's happening?</h2>
    <div id="info">
    <textarea id="status" onkeyup="update_counter(this)"></textarea>
    <span id="latest"><strong>latest: </strong><span></span></span>
    <span id="tweeting_button_container">
    <span id="tweeting_status"></span>
    <button id="tweeting_button">Tweet</button>
    </span>
    </div>
    </div>
    <div id="timeline">
    </div>
    </div>
    <div id="welcome">
    Welcome to <strong>your</strong> Twitter client.
    <p>
    Connect to Twitter using the blue button at the top.<br/>
    Better still, <a href="http://fatc.onilabs.com/">fork the code and hack it.</a>
    </p>
    </div>
    </body>

    </html>