Travelling the World

Text Messages – Easy Peasy

We recently released a feature that allows our users receive a text message if a bus they are awaiting is running late. It’s all pretty simple, we take the output of this form and run a little excel macro to convert the entered data into email addresses. We then email the affected parties and they get the the alert via SMS. There’s a couple hard-coded values in this macro for simplicity, modify it to suit your needs:

'Transform cell phone numbers to email addresses for sending text messages
'Boris Masis 08/19/2009

Option Explicit
 
Sub ProcessTextMessaging()
Dim emailDomain
Range("L1").Value = "Email"
Range("C2").Select
 
    'Loop though until we run out of rows
    Do Until ActiveCell.Value = ""
 
        Select Case ActiveCell.Value
 
              Case "Alltel"
                  emailDomain = "@message.alltel.com"
 
              Case "AT&T"
                  emailDomain = "@txt.att.net"
 
              Case "Boost"
                  emailDomain = "@myboostmobile.com"
 
              Case "Cricket"
                  emailDomain = "@sms.mycricket.com"
 
              Case "MetroPCS"
                  emailDomain = "@mymetropcs.com"
 
              Case "Sprint"
                  emailDomain = "@messaging.sprintpcs.com"
 
              Case "Nextel (Sprint)"
                  emailDomain = "@page.nextel.com"
 
              Case "T-Mobile"
                  emailDomain = "@tmomail.net"
 
              Case "Tracfone"
                  emailDomain = "@mmst5.tracfone.com"
 
              Case "US Cellular"
                  emailDomain = "@email.uscc.net"
 
              Case "Verizon"
                  emailDomain = "@vtext.com"
 
              Case "Virgin"
                  emailDomain = "@vmobl.com"
 
        End Select
 
        'write the email to a new cell
        ActiveCell.Offset(0, 9).Value = ActiveCell.Offset(0, -1) & emailDomain & ","
 
        'select the next cell down
        ActiveCell.Offset(1, 0).Select
 
    Loop
 
End Sub

The providers above have over 1 million U.S. subscribers, and almost all providers have email to SMS gateways.

You can download my xls file that includes this macro, or the .xla file which is an Excel add-in that will add a “Process Text Messaging” section to your Tools Menu. I followed the template at http://www.cpearson.com/excel/CreateAddIn.aspx to create this add-in.

A common web development concern is the www subdomain. Do you host at http://www.site.com or http://site.com? Which ever you choose you want to be sure that visitors who type in the other get re-directed properly.

If you use IIS you can setup a new website to do such a permanent redirect, but that solution doesn’t scale well. For example, we run 42 websites using one install of umbraco, and consequently one IIS website. We’d need 42 new websites in IIS to use the built-in redirect feature.

One example site is http://en.culturalcare.com, and I want to make sure that if someone types in http://www.en.culturalcare.com they get redirected properly. The solution is to write a little generic URL re-writer yourself using the global.asax file.

< %@ Application Language="C#" %>
<script runat="server">
protected void Application_BeginRequest(Object sender, EventArgs e)
		{
			string domainName = Request.Url.Host.Replace("www.", "").ToLower();
			string sOldPath = HttpContext.Current.Request.Path.ToLower();
			string sPage = "http://" + domainName + sOldPath;
			Response.Clear();
			Response.Status = "301 Moved Permanently";
			Response.AddHeader("Location",sPage);
			Response.End();
		}
</script>

As you can see the code removes “www.” from the url and 301 redirects to the non-www version. It also passes the full path.

You could modify the code slightly to add www’s instead of removing them if that’s the desired effect:

< %@ Application Language="C#" %>
<script runat="server">
protected void Application_BeginRequest(Object sender, EventArgs e)
		{
			string domainName = Request.Url.Host.ToLower();
			string sOldPath = HttpContext.Current.Request.Path.ToLower();
			string sPage = "http://www." + domainName + sOldPath;
			Response.Clear();
			Response.Status = "301 Moved Permanently";
			Response.AddHeader("Location",sPage);
			Response.End();
		}
</script>

Download a full example for use with IIS.

To implement:

  • create a new site in IIS and add host headers for all of the domains you would like to redirect
  • extract the zip above to the directory setup in IIS
  • setup wildcard application mapping in IIS to let the global.asax file handle all requests (not just ones that end in .aspx, etc). On windows server 2003 just go to Home Directory -> Configuration and paste in the aspnet_isapi.dll location in the wildcard application maps section. By default the path is C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll (uncheck “Verify that file exists”)

We sure love our wufoo around here, and one thing we’ve been using more and more is smart routing for our entries. For example, when someone fills out a lead form we want to route the entry to the appropriate sales person based on the state of the inquiry.

Wufoo doesn’t natively provide “smart” routing like this, but its simple enough to add through some google filters magic. Here’s how we do it:

  • Setup a google apps or gmail account for the routing
  • Create filters in this account for emails that arrive from no-reply@wufoo.com and contain certain words (for example, the state name). When a state matches, forward the entry to the appropriate sales person.
  • Point your wufoo notifications to go your new gmail account

One gmail limitation is that only 20 filters can forward to other addresses per account. This is easy to workaround by creating multiple accounts (i.e. states1, states2) and listing all of them in your wufoo notification settings.

We recently completed work on AuPairVideos.com. The site went from concept to launch in just over a month, in part thanks to its lightweight single-page design. We ended up writing ~150 lines of code in each layer:

  • 152 lines of HTML
  • 165 lines of CSS
  • 159 lines of JavaScript
  • 147 lines of PHP
  • 88 lines of SQL

If only everything could be so simple.

au-pair-videos

Tools such as Quick Ribbon are great for generating simple and effective ribbons such as this one:

ribbon

A small problem though is the potential to block content underneath the ribbon transparency area. For example, that “About Us” link is obscured by the large square ribbon image.

A simple workaround is to split the ribbon image in two (or more) pieces as I’ve highlighted below and stitch them together in CSS. Now the “About Us” link can be clicked just fine.

ribbon2

#ribbon-top {
    position:absolute;
    top:0;
    left:20px;
}
 
#ribbon-bottom {
    position:absolute;
    top:75px;
    left:0;
}

If you’ve never played with Yahoo Pipes, you should. The innovative interface alone is worth it.

Here’s a simple yahoo pipes example: lets say that you have a blog that you want to syndicate differently to different audiences (for example we have a Tumblr blog and we want to exclude some of the content from the rss feed for use with facebook pages). The solution is simple; add a tag to posts that you want to exclude on tumblr and then use yahoo pipes to filter out those posts and generate a new RSS feed.

You can try it with your own Tumblr blog and tag.

If you’ve ever setup an SSL certificate you know that its a hassle. Renewing your SSL certificate is likewise un-intuitive and takes more steps than it should. It turns out that paying for a certificate renewal does not automatically renew the certificate you have installed, you actually have to install the renewed copy.

Our setup involves a wildcard GoDaddy SSL certificate hosted on IIS6. The renewal process goes something like this:

  1. You then have to go to “Manage Certificate” in Godaddy and request a renewal.
  2. Generate the CSR in IIS:
    • Go to the “Directory Security” tab for SSL site in IIS.
    • Click the “Server Certificate.” button (located in the “Secure communications” area)
    • Click “Next” in the Welcome to the “Web Server Certificate Wizard” window.
    • Select “Renew”; then click “Next.”
    • Select “Prepare the request now, but send it later” and click “Next.”
    • Paste the CSR into the Godaddy form.
  3. Godaddy will issue the new certificate (you should get an email instantaneously)
  4. Process the pending request in IIS using the .crt file sent by GoDaddy.
  5. Select “replace the current certificate” and select the certificate with the new expiration date.
  6. If you’re using a wildcard certificate and want to use it with other sites select “export the current certificate to a .pfx file”
  7. To use the exported wildcard certificate on a different website you will first need to remove the current certificate on that website and then import the one from the .pfx (there will be momentary downtime while you do this).

IE8 came out today. Along the way Microsoft fixed a bunch or rendering bugs, which is great, but you might run into problems of you previously used conditional comments such as:

<!--[if IE]>
<link rel="stylesheet" href="patches-ie.css" type="text/css" media="all" />
< ![endif]-->

The trouble is that IE8 will still read that patches-ie.css file and apply some styles that may no longer be needed because the bugs they address have since been fixed. Microsoft recommends that you update your pages to do more precise version detection. That’s probably the best solution, but let’s say that you don’t want to update all of your web applications.

The quick and dirty solution for you is the * hack. IE8 no longer supports the * hack, so you can edit your patches-ie.css file to make it look like this:

*margin-top: 30px; /* for versions prior to IE8 */

Of course you can also reverse this to target IE8:

float: right;  /* all browsers (including IE8) */
*float: none; /* override for IE7 and prior*/

Be sure to use Xenocode to test various version of Internet Explorer side by side and make sure the hack worked.

TiddlyWiki is an awesome reusable non-linear personal web notebook. That’s a lot of words. Keyword is awesome.

There have been a number of server-side implementations of TiddlyWiki that allow you to save data to a server (as opposed to locally as TiddlyWiki does by default). PHPTiddlyWiki is a good simple implementation using PHP/mySQL, but its an abandoned project with a couple of minor (but important) bugs. I’ve re-packaged PHPTiddlyWiki with the necessary fixes. Note that this is still essentially a non-upgradable version, but its easy to get it running today and I’m happy with the existing functionality. Here’s a video of what it can now do:

1. Download PHPTiddlyWiki:
Download my re-packaged version of PHPTiddlyWiki which includes a bug fix which was previously preventing proper server saves. Follow the instructions in tiddly_readme.html to install PHPTiddlyWiki in sub-directory on your server. You will need to have PHP and mySQL running (as most web hosts do)

2. Password protect:
You will probably want to password protect your wiki. You can do this easily using standard .htaccess/.htpasswd. Your host may even provide a GUI to do this though DirectAdmin (as mine does) or CPanel. Note that there are some special instruction if you’re using Wordpress. I had to put the ErrorDocuments referenced in that link after “ RewriteEngine On RewriteBase / ” in my .htaccess file.

3. Configure your theme:
The default PHPTiddlyWiki theme is a nice blue one, but I’m used to the Getting Things Done theme. I’ve hacked together an imitation style sheet (its probably imperfect, but fairly easy to tweak). Go to “All” Tiddlers and enter the following in the StyleSheetLayout Tiddler:

/*{{{*/
 
/*
#displayArea{
position:absolute;
top:0;
width: 520px;
_width: 545px; /* CSS UNDERSCORE HACK FOR PROPER WIN/IE DISPLAY */
}
 
#mainMenu, #licensePanel{
float:right;
clear:right;
margin-right:0;
margin-left:10px;
}
 
#contentWrapper{
width:1000px;
}
 
#sidebar{
font-size:12px;
margin:10px 10px 0 0;
width:215px;
}
 
#licensePanel{
margin-right:0px;
}
 
#logger{
xdisplay:block;
}
*/
 
/*}}}*/

Enter the following for the StyleSheet Tiddler:

/***
!Calendar CSS
***/
/*{{{*/
.calendar{
 border-bottom: 1px solid #550000;
}
 
.viewer .calendar{
 width: 220px;
}
 
#mainMenu .calendar{
 font-size: 8px;
 cursor: pointer;
 width: 100%;
 border: 0;
 border-collapse: collapse;
}
 
#mainMenu .calendar .button{
 border: 0;
}
 
#mainMenu .calendar td{
 font-size: 8pt;
 padding: 0;
 background: #fff;
 border: 0;
}
 
#mainMenu .calendar a{
 margin: 0;
 color: #000;
 background: transparent;
}
 
#mainMenu .calendar a:hover{
 color: #000;
 background: transparent;
}
 
#mainMenu .calendarMonthname,
#mainMenu .calendar .calendarMonthTitle td a{
 color: #fff;
}
 
#mainMenu .calendarDaysOfWeek td{
 background: #500;
 color: #fff;
}
 
/*}}}*/
 
/***
!GTD Style
 
!Generic rules /%==================================================================== %/
***/
/*{{{*/
body {
 background: #464646 url('http://shared.snapgrid.com/images/tiddlywiki/bodygradient.png') repeat-x top fixed;
 color: #000;
 font: .82em/1.25em Arial, "Trebuchet MS", "Bitstream Vera Sans", Verdana, Helvetica, Arial, sans-serif;
/*"Lucida Sans Unicode", "Lucida Grande","Trebuchet MS", */
}
/*}}}*/
 
/***
!Header rules /%====================================================================== %/
***/
/*{{{*/
#contentWrapper{
 margin: 0 auto;
 width: 59em;
 position: relative;
}
 
.header, .tagging, .tagged{
 display:none;
}
 
#siteTitle {
font-size:30px;
}
 
#siteSubtitle {
 font-size: 1em;
 padding-left: .8em;;
}
 
#titleLine{
 background: transparent;
 padding: 0;
}
 
#titleLine a {
 color: #CCFF66
 background: transparent;
}
/*}}}*/
 
/***
!Sidebar rules /%====================================================================== %/
***/
/*{{{*/
#sidebar{
 left: 0;
 width: 18em;
 margin: .9em .9em 0 0;
 color: #000;
 background: transparent;
}
 
.button {
 border:none;
}
/*}}}*/
 
/***
!Main menu rules /%=================================================================== %/
***/
/*{{{*/
#mainMenu {
line-height: 166%;
background: #600;
border-right: 3px solid #500;
font-size:12px;
margin:10px 10px 0 0;
width:215px;
}
 
#mainMenu h1{
 padding: 0;
 margin: 0;
 font-size: 1em;
 font-weight: normal;
}
 
#mainMenu ul{
 padding: 0;
 margin: 0;
 list-style: none;
}
 
#mainMenu h1 a,
#mainMenu li a,
#mainMenu li a.button{
display: block;
 padding: 0 5px 0 10px;
border: 0;
 border-bottom: 1px solid #500;
 border-top: 1px solid #900;
margin: 0;
}
 
 
#mainMenu a,
#mainMenu a.button{
background:#770000 none repeat scroll 0 0;
border-bottom:1px solid #550000;
border-top:1px solid #990000;
color:#FFFFFF;
display:block;
height:22px;
line-height:22px;
padding:0 5px 0 10px;
}
 
#mainMenu a:hover,
#mainMenu a.button:hover {
 background: #b00;
 color: #fff;
}
 
/*}}}*/
 
/***
!Sidebar options rules /%============================================================ %/
***/
/*{{{*/
#sidebarOptions {
 background: #eeb;
 border-right: 3px solid #bb8;
 color: #B4C675;
 padding: .5em 0;
}
 
#sidebarOptions a {
margin:3px 10px;
}
 
#sidebarOptions a.button{
display:block;
}
 
#sidebarOptions a:hover, #sidebarOptions a:active {
 color: #fff;
 background: #700;
 border: 0;
}
 
#sidebarOptions input{
 margin: 2px 10px;
 border: 1px inset #333;
padding: 0;
}
 
#sidebarOptions .sliderPanel {
 background: #fff;
 color: #000;
 padding: 5px 10px;
 font-size: .9em;
}
 
#sidebarOptions .sliderPanel a{
 font-weight: normal;
 margin: 0;
}
 
#sidebarOptions .sliderPanel a:link,#sidebarOptions .sliderPanel a:visited {
 color: #700;
}
 
#sidebarOptions .sliderPanel a:hover,#sidebarOptions .sliderPanel a:active {
 color: #fff;
 background: #700;
}
/*}}}*/
 
/***
!Sidebar tabs rules /%===================================================================== %/
***/
/*{{{*/
#sidebarTabs {
 background: transparent;
 border-right: 3px solid #740;
 border-bottom: 3px solid #520;
 border: 0;
 padding: 0;
}
 
#contentWrapper #sidebarTabs a,
#contentWrapper #displayArea .tabContents a{
 color: #fff;
}
 
#contentWrapper #sidebarTabs a:hover,
#contentWrapper #displayArea .tabContents a:hover {
 background: #000;
 color: #fff;
}
 
#contentWrapper #sidebarTabs a:active,
#contentWrapper #displayArea .tabContents a:active{
 color: #000;
}
 
#contentWrapper .tabSelected {
 background: #960;
}
 
#contentWrapper .tabUnselected{
 background: #660;
}
 
#contentWrapper #sidebar .tabset{
 background: #eeb;
 border-right: 3px solid #bb8;
 padding: 0 0 0 .75em;
}
 
#contentWrapper .tabContents{
 font-size: .95em;
 background: #960;
 border:0;
 border-right: 3px solid #740;
 border-bottom: 3px solid #520;
 padding: .75em;
}
 
#contentWrapper .tabContents{
 width: auto;
}
 
#contentWrapper #sidebarTabs .tabContents .tabset,
#contentWrapper .tabContents .tabset{
 border: 0;
 padding: 0;
 background: transparent;
}
 
#contentWrapper .tabContents .tabSelected,
#contentWrapper .tabContents .tabContents {
 background: #700;
 border: 0;
}
 
#contentWrapper .tabContents .tabUnselected {
 background: #440;
}
 
#contentWrapper .tabset a {
 color: #fff;
 padding: .2em .7em;
 margin: 0 .17em 0 0;
 height: 2em;
position: static;
}
 
#contentWrapper .tabset a:hover {
 background: #000;
 color: #fff;
}
 
#contentWrapper .tabset a:active {
 color: #000;
}
 
#contentWrapper .tabContents ul{
 margin: 0;
 padding: 0;
 list-style: none;
}
 
#contentWrapper .tabContents .tabContents ul{
 color: #eeb;
}
 
.tabContents ul a,
.tabContents ul .button{
 color: #fff;
 display: block;
 padding: .1em 0 .1em .7em;
 background: transparent;
 border: 0;
}
 
.tabContents ul a:hover {
 color: #fff;
 background: #000;
}
/*}}}*/
 
/***
!License panel rules /%==================================================================== %/
***/
/*{{{*/
#licensePanel {
 padding: 0px 1em;
 font-size: .9em;
}
 
#licensePanel a {
 color: #960;
 display: block;
 margin-top: .9em;
}
 
#licensePanel a:hover {
 color: #fff;
 background: transparent;
}
/*}}}*/
 
/***
!Popup rules /%================================================================= %/
***/
/*{{{*/
.popup {
 font-size: .8em;
 padding: 0em;
 background: #333;
 border: 1px solid #000;
}
 
.popup hr {
 margin: 1px 0 0 0;
 visibility: hidden;
}
 
.popup li.disabled {
 color: #666;
}
 
.popup li a,
.popup li a:visited{
 color: #000;
 border: .1em outset #cf6;
 background: #cf6;
}
 
.popup li a:hover {
border: .1em outset #cf6;
 background: #ef9;
 color: #000;
}
/*}}}*/
 
/***
!Message area rules /%================================================================= %/
***/
/*{{{*/
#messageArea{
 font-size: .9em;
 padding: .4em;
 background: #FFE72F;
 border-right: .25em solid #da1;
 border-bottom: .25em solid #a80;
 
position: fixed;
 top: 10px;
 right: 10px;
 color: #000;
}
 
#contentWrapper #messageArea a{
 color: #00e;
 text-decoration: none;
}
 
#contentWrapper #messageArea a:hover{
 color: #00e;
 text-decoration: underline;
 background: transparent;
}
 
#contentWrapper #messageArea .messageToolbar a.button{
 border: 1px solid #da1;
}
 
#contentWrapper #messageArea .messageToolbar a.button:hover{
 color: #00e;
 text-decoration: none;
 border: 1px solid #000;
 background: #fff;
}
 
/*}}}*/
/***
!Tiddler display rules /%================================================================== %/
***/
/*{{{*/
#displayArea {
 width: 39.75em;
 margin: 0 0 0 17em;
position:absolute;
top:0;
}
 
.subtitle{
padding:5px;
}
 
 
.tiddler {
 margin: 0 0 .9em 0;
 padding: 0 1em;
 border-right: .25em solid #aaa;
 border-bottom: .25em solid #555;
 background: #fff;
}
 
.title {
 font-size: 1.5em;
 font-weight: bold;
 color: #900;
padding:5px;
}
 
.toolbar {
 font-size: .8em;
 padding: .5em 0;
}
 
.toolbar .button{
 padding: .1em .3em;
 color: #000;
 
 border: .1em outset #cf6;
 background: #cf6;
margin: .1em;
}
 
.toolbar .button:hover {
 background: #ef9;
 color: #000;
}
 
.toolbar .button:active {
 background: #ff0;
}
 
/*}}}*/
 
/***
!Viewer rules /% ------------------------------------------------------------------------------------------ %/
***/
/*{{{*/
.viewer {
 line-height: 1.4em;
 font-size: 1em;
}
 
.viewer a:link, .viewer a:visited {
 color: #15b;
}
 
.viewer a:hover {
 color: #fff;
 background: #000;
}
 
.viewer .button{
 background: transparent;
 border-top: 1px solid #eee;
 border-left: 1px solid #eee;
 border-bottom: 1px solid #000;
 border-right: 1px solid #000;
}
 
.viewer .button:hover{
 background: #eee;
 color: #000;
}
 
.viewer .button:active{
 background: #ccc;
 border-bottom: 1px solid #eee;
 border-right: 1px solid #eee;
 border-top: 1px solid #111;
 border-left: 1px solid #111;
}
 
 
.viewer blockquote {
 border-left: 3px solid #777;
 margin: .3em;
 padding: .3em;
}
 
.viewer pre{
 background: #fefefe;
 border: 1px solid #f1f1f1;
}
 
.viewer pre, .viewer code{
 color: #000;
}
 
.viewer ul {
 padding-left: 30px;
}
 
.viewer ol {
 padding-left: 30px;
}
ul{
list-style-type: asquare;
}
ol{ 
 list-style-type: decimal;
}
 
ol ol{ 
 list-style-type: lower-alpha;
}
 
ol ol ol{ 
 list-style-type: lower-roman;
}
 
.viewer ul, .viewer ol, .viewer p {
 margin: .0;
}
 
.viewer li {
 margin: .2em 0;
}
 
h1,h2,h3,h4,h5,h6 {
 color: #000;
 font-weight: bold;
 background: #eee;
 padding: 2px 10px;
 margin: 5px 0;
}
 
.viewer h1 {font-size: 1.3em;}
.viewer h2 {font-size: 1.2em;}
.viewer h3 {font-size: 1.1em;}
.viewer h4 {font-size: 1em;}
.viewer h5 { font-size: .9em;}
.viewer h6 { font-size: .8em;}
 
.viewer table {
 border: 2px solid #303030;
 font-size: 11px;
 margin: 10px 0;
}
 
.viewer th, .viewer thead td{
 color: #000;
 background: #eee;
 border: 1px solid #aaa;
 padding: 0 3px;
}
 
.viewer td {
 border: 1px solid #aaa;
 padding: 0 3px;
}
 
.viewer caption {
 padding: 3px;
}
 
.viewer hr {
 border: none;
 border-top: dotted 1px #777;
 height: 1px;
 color: #777;
 margin: 7px 0;
}
 
.viewer
{
 margin: .5em 0 0 0;
 padding: .5em 0;
 border-top: 1px solid #ccc;
}
 
.highlight {
 color: #000;
 background: #ffe72f;
}
/*}}}*/
 
/***
!Editor rules /% ----------------------------------------------------------------------------------------- %/
***/
/*{{{*/
.editor {
 font-size: .8em;
 color: #402C74;
 padding: .3em 0;
}
 
.editor input, .editor textarea {
 font: 1.1em/130% "Andale Mono", "Monaco", "Lucida Console", "Courier New", monospace;
 margin: 0;
 border: 1px inset #333;
 padding: 2px 0;
}
 
.editor textarea {
 height: 42em;
 width: 100%;
}
 
input:focus, textarea:focus
{
 background: #ffe;
 border: 1px solid #000;
}
.footer
{
 padding: .5em 0;
 margin: .5em 0;
 border-top: 1px solid #ddd;
 color: #555;
 text-align: center; 
}
/*}}}*/
 
/***
!IE Display hacks /% ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~%/
***/
/*{{{*/
body{
 _text-align: center;
}
 
#contentWrapper
{
/* _width: 770px; CSS UNDERSCORE HACK FOR PROPER WIN/IE DISPLAY */
 _text-align: left; /* CSS UNDERSCORE HACK FOR PROPER WIN/IE DISPLAY */ 
}
 
#messageArea{
 _position: absolute;
}
/*}}}*/

Turns out that it’s actually fairly easy to create a simple static Facebook application to display some data without needing to work with the Facebook API or any back-end code at all. For example, take a look at the World Map Tab on our Facebook Page. This tab was created by pulling some static HTML content from our web server, there’s really nothing Facebook specific about it, and the process is largely the same as creating a simple HTML page.

Here’s an overview video:





About

Boris Masis is a 20-something living in Boston, Massachusetts travelling the world.


Calendar


via 30 boxes


Bookmarks


via delicious