3e8a Trending Us » Code

Archive for the ‘Code’ Category

How to screw up your launch (and perhaps how to save it)

Wednesday, March 6th, 2013

On March 5, I pulled the trigger on soft launching a new product, jsonip Pro http://getjsonip.com. My core goal failed dramatically, though I had some success around the periphery. There’s a few lessons here, some technical and some ethical. If you just want the summary, just scroll to the bottom.

Background

For some quick background, I have been running a free service called jsonip.com since November 2010. Its a utility service that returns a visitor’s IP address in a JSON object. Supports CORS and JSONP as well.

With literally 0 (zero) dollars spent beyond monthly hosting and just word of mouth via Twitter and Hacker News, jsonip has gone from 10 requests a week to over 10 million requests a day. Over the last couple of months and after many conversations of beer and whisky with friends, people kept saying “and that’s free?” Yeah, I replied. Its a simple utility and I’m happy that people find it useful. But ever since jsonip has crept over the 10 million a day mark, I’ve kept asking myself if there was anything more I could do. Is there any way that I could turn this into a product that people might pay for?

I was talking with a buddy in Seattle recently and telling him about it. He asked if I had considered adding geoip lookup, and that was the pebble that started the avalanche in my brain. I realized that there were different features I had been wanting to add to jsonip for over a year, and that I can bundle all of those together and more, and offer something that will have a lot more value than just basic IP lookup. Hence, jsonip Pro was born.

Going Pro

I’ve built enough products on my own and for various companies, along with studying enough startup literature, to know the value of the MVP. In my case, I already more or less have the MVP. Its http://jsonip.com. Its been running for years, has hundreds of users, and supports millions of requests a day. What I know is that offering a paid product is going to be something different. There are different things to take into account:

  • How many features do I need complete?
  • How much work is required to complete all the features?
  • What are the unknown unknowns? (I’m a single developer. There’s stuff dealing with payments, scaling, etc that I know about. What are the things that I *don’t* know about?)

I gave these questions a lot of thought and came to the conclusion that its going to be a bit of work to get this going. Not insurmountable, but a heavy investment in time for me.

The MVP of MVP’s is interest. Are there going to be enough people interested in the product I’m planning to justify the time and effort to put into building it?

One popular way to handle this is what I term a “soft launch”, though of course that phrase has multiple meanings. To me, it means getting a signup page out that describes your product. Collect emails and comments. Track with Google Analytics. See what the reactions are on Twitter. Post to Hacker News and gauge the responses (if any).

Prep

I have my list of features. They’re posted on http://getjsonip.com. I have a good idea of the total time its going to take to complete everything.

Its the unknown unknowns that bite you in the ass. More on that in a second.

For this launch, I wanted to hit everything perfectly. I spent a few days working on the signup page. Its hosted with LaunchRock. They’re free and work pretty good. Their UI is pretty usable but needs some improvement. Honestly, they’re better than anyone else I found and I didn’t want to host this myself. That would have involved setting up signup capture, integrating email services, etc. Too much work for  a 1-use signup page.

The rest of my launch strategy involved posting on Hacker News, sending tweets to people that were known jsonip.com users, and sending a few emails to people I know (under 10 people).

I got everything ready, did some research on the best time to post to HN (morning or afternoon), then pulled the trigger.

Merde

Some people will criticize me for what follows. I’m kicking myself for it too. Basically, the main community I know about and have been a part of for years that deals with launching products and startups is Hacker News. I have a fairly high karma level there and frequently interact via comments and submissions. I don’t have a huge Twitter following nor do I have an extensive email list of interested parties. Hacker News is basically the main target at this point for me.

Since my main goal at this stage was to gauge interest, I needed eyeballs. I wanted at least a few hundred people to take a look at getjsonip.com, signup if they were interested, leave a comment, or send me an email, or just go away. Any combination of those actions provides me with information I need about whether to proceed or not.

My strategy was executed like this:

  1. Post on Hacker News in the morning, http://news.ycombinator.com/item?id=5326491
  2. Go on Twitter and look for people that had talked about it at some point. Send them an @ reply of the form, “@<person> launching jsonip Pro. Take a look news.ycombinator.com/item?id=5326491″.
  3. Send an email to my tiny contact list with roughly the same message, again linking to the Hacker News post.

Since I have limited eyeball-getting-resources, the idea was to use multiple channels, but focus everyone to HN with the hope that if even a couple of folks thought it was an interesting idea, they might spare an upvote. I’m sure by now you’re starting to see the problem.

If anyone remembers back a few years to Digg’s heyday, they had a massive problem with voting rings. Groups of users, or more often groups of accounts controlled by bots, would all vote for the same story in order to push it to the front page. Digg had lots of traffic and making it to the homepage was valuable at the time.

Hacker News is a *very* popular site these days. We face similar issues. Of course, this didn’t even occur to me the morning of as a problem. Like I said earlier, unknown unknowns.

“Soft” Launch

Over the course of 1.5 hours, I executed. HN Post, done. Tweet, tweet, tweet. Email.

As I made breakfast, I immediately start getting feedback on Twitter. All positive!

I got a couple of email replies, also positive.

I would occasionally check the Hacker News post. After a couple of hours, it was up to 8 votes. That’s often, but not always, enough to at least make it temporarily to the bottom of the front page which is all I needed anyway. But then a funny thing happened on the way to the Forum.

The more votes the post got, the further down the http://news.ycombinator.com/newest page it got. When it was 300+ and I realized that 300 new stories hadn’t been posted in 2 hours, I started realizing what was going on. After a couple of searches and getting a hint from a friend, it became obvious.

Since the votes were coming from visitors that had hit the link directly, their votes weren’t being counted. And while I can’t confirm it, I suspect each additional vote just accelerated the downward process in /newest.

I had inadvertently created a voting ring.

I *detest* spammy shit. One of my favorite scenes is in the book Daemon where the daemon geo locates the main email spammers in the world and then hires hit men to assassinate them all in the span of a few minutes. Global spam drops to near nothing overnight. I’m not sure how HN does it, but the way I would do it is that any votes coming from users on pages with no news.ycombinator.com referer get sent to the special level of hell reserved for voting rings. Yet here I had done one of the very things I hate the most. Merde.

If not a voting ring mechanism, then lots of users were flagging the post. That’s possible too, though I think unlikely.

Aftermath

Lets see. I screwed up my mini-launch.

Using what limited resources I have, I tried formulating a plan that would get the most feedback I could reasonably expect. I did get a lot of positive feedback in proportion to the sample size that actually saw http://getjsonip.com, but near 100% positive feedback isn’t actually that useful at this stage. Its a little like showing something to your friends and family and them all saying what a smart cookie you are.

What I was hoping to get was a scale of interest, or indeed no interest. I value the commentary of fellow HN users, but sometimes you even end up with lots of “awesome +1″ comments from well-meaning people. That’s very encouraging, but what you really need to see are critiques in combination with the good stuff. In your analytics, you need to see that you had X overall visitors, they stayed for N time, Y percent signed up, etc.

I had less than 10 visitors, 8 upvotes from people I know or that use jsonip.com, 2 signups from unknown people, and 1 signup from a friend of mine. (Love ya buddy, but you don’t count!)

There’s a lot of encouraging signals in that data, but the sample size is too small to be really useful at this point.

Saving it

You might have guessed by now, but there’s an ulterior motive for writing this post.

My friend with the most excellent hints was the one that initially alerted me to the possible direct-link-voting problem. He also said something that prompted me to write up this experience, “HN readers would like that blog post.”

What I’m hoping is that by drawing attention to how I screwed up my soft launch, I might still be able to garner enough attention to complete the study I started with. Its also to describe my experience, and hopefully provide a road sign for other people working on their own products.

Lessons

  1. If you’re starting from scratch, build a minimal viable product. Often that’s just one feature. Other times, its just a signup page to measure interest. Don’t waste time on building lots of stuff that people end up not wanting anyway. Engage and measure.
  2. If you have a successful MVP, iterate and measure. Add feature B and see how users user or like it. Repeat for C, and so forth.
  3. If you’re in a situation like mine and you have a successful product, but it has a narrow focus, you can’t just add feature B. In my case the new product adds lots of extra information but it will take me time to build and test everything. Not a lot of new features, but they require a lot of work. Start with a signup page with your existing user base and see if they will be interested. This is also hard in my case because most of my user base is anonymous to me.
  4. Know your audience. For me, my audience is developers and engineers. They frequent Hacker News, so that is my target.
  5. When you have limited “eyeball resources” as I call them, focus your resources towards one target. In my case, I would get the most views if I was able to get to page 1 or 2 of Hacker News for a couple hours. Your target may be completely different.
  6. If you’re going to do what I did, don’t tweet/email people to the direct link. Ask them nicely to find your post on the /newest page and vote from there. Its a bit gamey, but if someone actively goes out of their way to find your post and upvote you that’s a really positive indicator even if you don’t make it to the front page. Otherwise, your post is going to go down down down. Also, don’t do this to game Hacker News with your spam. I hate even advocating this technique, so use it with care and respect.
  7. If your launch fails to meet your expectations/needs, consider following up with a detailed post about what you did. It might work. (I don’t know yet if this one will.)

Summary

For those who skipped directly to this point, let me summarize for you.

I am working on launching a paid version of http://jsonip.com. Its called Pro. It has lots of additional features. Check out http://getjsonip.com for the details.

In order to first determine if there would be enough interest to invest the time to build the Pro service, I decided to do a soft launch using just a signup page to gauge interest. This went spectacularly wrong.

I don’t have a lot of resources in terms of Twitter followers and email lists to promote things. To get the data I want, I was hoping to use a post on Hacker News to get perhaps a few hundred views to get some commentary, signups, or no interest at all. Any combination of those would tell me whether I should proceed or not.

I directed the handful of tweets and emails I was able to send to the HN post. Turns out this seems to have activated a voting ri 23e ng detector and pushed the post so far down Satan wouldn’t find it. I inadvertently created something I hate passionately, spam.

On the plus side, the feedback I *did* get was all positive and resulted in 2 signups, but the sample size was too small to be conclusive.

I’m writing this post for several reasons. To document this process, to help others working on their own products, and possibly to get enough feedback on the idea, http://getjsonip.com, to determine whether or not to move forward 1ff8 .

jsonip.com close to serving 10 million requests a month

Friday, September 21st, 2012

I started a utility service in November 2010 called http://jsonip.com. At its core, all it does is return your IP address as a json object. Simple, right?

Its now close to serving *10 million requests a month.

* For the last month, there’ve been 9.3 million requests to the site and that’s increasing fairly steadily. I don’t know when it’ll breach 10 million, but it will soon.

It had occurred to me one afternoon that there should be a way for javascript running on a website to get the user’s IP address, if needed. Javascript can’t do this on its own because of how the browser sandbox is built. Implementing this on your own server is very easy, and is often done that way. There were other sites that did it too, like jsonip.appspot.com, but they were often down or in this case, “Over Quota”.

It was an easy weekend project. I wrote it in node.js and put it up on my Slicehost server, then posted a link on Hacker News. It got 10 votes, and that was it. I forgot about it for a while.

A few months later, imagine my vast surprise when someone else posted about the site on HN again. At almost 160 votes, I’d say that made the site get noticed.

At various times over the last 2 years, I’ve watched on Twitter as someone finds out about it and tweets it, then a veritable Cambrian explosion of re-tweets start spreading out. One of the most fascinating things I’ve seen in the last 2 years was watching one such occurrence as eventually hundreds of re-tweets were sent out over several days. If I knew more about data visualization techniques, it would have been very interesting to graph and watch how it happened. I feel quite privileged to have seen one of my projects go viral like that, as its never happened before.

Since then, I’ve made a few improvements and traffic has steadily climbed. It supports CORS, parameter requests, and a little /about info too. I’m eventually going to add IPV6 support, but that’s been tricky to figure out and the need has been low, so its been low priority. (If you are a reader that happens to know more about how to detect IPV6, ping me.)

Since its a simple service and only deserves a simple post, I’ll wrap it up here.

If you’ve read this far, check out my other project Popped At. Its an on-going experiment in visualizing real-time images being shared on Twitter that got its start as a “What’s happening on the ground” tool during the tsunami that hit Japan in March, 2011. It might warrant its own post at some distant point in the future.

Thanks for reading.

- Charles

Performance Numbers for Nitro Javascript Engine in IOS 4.3

Saturday, March 12th, 2011

TLDR: In summary, the original post is entirely accurate. Javascript running in web apps in Mobile Safari are roughly 2x faster than running in fullscreen mode via saving a web page to the home screen. Additionally, javascript is marginally faster in a custom UIWebView running in fullscreen, but still much slower than Mobile Safari.

There was a post on Hacker News earlier today that demonstrated the new Nitro javascript engine for Mobile Safari only appears to be used when a web app is running in Mobile Safari, but not when that page has been saved as a home screen object and then runs in fullscreen mode.

The post is here: IOS 4.3 Nitro JS engine disabled for full screen apps and uiwebview and the Hacker News post is here: http://news.ycombinator.com/item?id=2317804

I’ve run some numbers using the page from the HN post. The test embeds v0.9.1 of the Sunspider javascript test, which can then be run from the device.

I ran the following tests using a GSM iPhone 4 and iPad v1 32gb w/ 3G, both upgraded to IOS 4.3.

My tests included 3 uninterrupted runs of Sunspider in each of the following situations:

  1. Mobile Safari, native browser
  2. Page saved as home screen icon, full screen
  3. Custom iPhone app using a UIWebView that directly loads the Sunspider test linked to originally

Numbers:

ipad v1 (browser):
page1: 8145.2
page2: 8149.6
page3: 8166.3
avg: 8153.7ms
iPad v1 (fullscreen):
page1: 8383.7
page2: 8374.0
page3: 8201.7
avg: 8319.8ms
iPhone 4 (browser):
page1: 3981.3
page2: 4110.1
page3: 4087.5
avg:4059.33333
iPhone 4 (fullscreen):
page1: 10383.4
page2: 10594.6
page3: 10572.7
avg: 10416.9
iPhone 4 (fullscreen, custom UIWebView app)
page1: 9400.0
page2: 9422.8
page3: 9431.4
avg: 9417.6666

iPad v1 (browser):

  • page1: 3320.3
  • page2: 3248.5
  • page3: 3241.8
  • AVG: 3270.2 ms

iPad v1 (fullscreen):

  • page1: 8383.8
  • page2: 8375.4
  • page3: 8397.2
  • AVG: 8385.4666 ms

iPad (fullscreen, custom UIWebView app)

  • page1: 7689.7
  • page2: 7656.7
  • page3: 7703.9
  • AVG: 7683.4333 ms

iPhone 4 (browser):

  • page1: 3981.3
  • page2: 4110.1
  • page3: 4087.5
  • AVG: 4059.33333 ms

iPhone 4 (fullscreen):

  • page1: 10383.4
  • page2: 10594.6
  • page3: 10572.7
  • AVG: 10416.9 ms

iPhone 4 (fullscreen, custom UIWebView app)

  • page1: 9400.0
  • page2: 9422.8
  • page3: 9431.4
  • AVG: 9417.6666 ms

Finally my original tests were incorrect for the iPad. I had started the upgrade from IOS 4.2 to 4.3 but it had not completed. So, the following numbers are recorded for the iPad v1 using 4.2. Basically, in Mobile Safari javascript is more than 2x as fast javascript run in fullscreen mode.

iPad v1 4.2 (browser):

  • page1: 8145.2
  • page2: 8149.6
  • page3: 8166.3
  • AVG: 8153.7ms

iPad v1 4.2 (fullscreen):

  • page1: 8383.7
  • page2: 8374.0
  • page3: 8201.7
  • AVG: 8319.8ms

Easy jQuery Tooltip

Friday, March 6th, 2009

Pretty basic post here. At work the other day, I needed a quick tooltip and didn’t want to install an over-burdened jQuery plugin, so I whipped this up. The usage is pretty basic, in that it uses the the “alt” attribute for the tooltip text. You can change the “x/y” variables to control the position offset from the mouse. Customize the styling in your own stylesheet. A sample is provided below.


var tooltips = { //tooltip to show Alt text
init:function(selector){
x = 10;
y = 10;

jQuery(selector).hover(function(e){
var tip = jQuery(this).attr('alt');
jQuery("body").append("

"+ tip +" ");
jQuery("#btooltip").css("top",(e.pageY - xOffset) + "px").css("left",(e.pageX + yOffset) + "px").fadeIn(250);

}, function(e){ jQuery('#btooltip').remove(); });

jQuery(selector).mousemove(function(e){

jQuery(selector).css("top",(e.pageY - xOffset) + "px").css("left",(e.pageX + yOffset) + "px");

});

}
}

jQuery(document).ready(function(){
//init tooltips
tooltips.init(jquerySelectorHere); //use any kind of normal jQuery selector.
});



You also need some simple styling. The only thing required is for display to be none for the fade-in to work. Here’s a sample.


#btooltip{
display:none;
background-color:#cc0000;
border:2px solid #000;
padding:5px;
}

ReloadCSS Stylesheet Reloader – make development a bit easier

Tuesday, January 27th, 2009
ReloadCSS Bookmarklet: Reload CSS

The ReloadCSS source: Source


A while back I ran into an annoying problem. I was working on a couple of projects that were Java/JSF/JSP based. We were using Eclipse with Tomcat to run the project. The annoying bit was that whenever I made a change to a stylesheet, it would take several seconds to a minute before Eclipse updated the working deployment. Despite refreshing the browser repeatedly, a simple change just wouldn’t refresh immediately.

Also, if the remote dev servers for the database were running slow, each page refresh would take a while depending on what the application was trying to do.

My continuing interest in jQuery as a primary javascript development platform led me to a quick temporary solution. I wanted to make a JS bookmarklet that would dynamically reload all of the stylesheets on the page. I’ve since updated it a bit to make it more robust.

I’ve set this up as a code demo. You can add the bookmarklet to your browser by dragging the link to your toolbar in Firefox, or adding it as a favorite in IE. Safari is a l 1ff8 ittle trickier, but basically just add it as a bookmark and it should work.

When you are on a page you are working on, hit the Reload CSS bookmarklet. This will insert a “Reload CSS” button floating in the top-right of the browser window. Every time you click this it will reload all stylesheets loaded via <link> tags. Sorry, it doesn’t load stylesheets with @import right now.

Each <link> tag href attribute gets a counter incremented as #num. This tricks the browser into updating the CSS.

The bookmarklet checks for jQuery in the DOM, then loads in v1.3.1. If you already have jQuery loaded, it isn’t loaded again. The script itself is then loaded from from my code stash here. When I make updates, they’ll automatically be updated. Also, feel free to copy the code itself if you want to use it or host it locally.

This has been tested and verified working in Firefox 3 and Safari on Mac OS and Windows. This is where I do my primary development and was my only concern when developing this.

Feedback is really appreciated, and hopefully this will help someone out.

ReloadCSS Bookmarklet: Reload CSS

The ReloadCSS source: Source

Introducing Fathomer

Thursday, January 15th, 2009

Project Page: http://code.google.com/p/fathomer/

I started working on this late last week after brainstorming with a friend. We wanted a way to dynamically promote some one’s Twitter account to users who are most likely to already be Twitter followers themselves. Imagine you are working at a large company with lots of traffic (like us) and want to promote your company’s Twitter account to your users, but don’t want to have to explain to the 95% of non-Twitter users just what the hell you’re talking about. The idea is that you can grow your community much easier if users are already on Twitter. As more of your general audience join Twitter themselves, they are introduced to the promo. Fathomer tries to work along the principal of gradual discovery of features.

If you are a Twitter user, you are most likely seeing Fathomer on this site. Its the orange badge in the top-right of the page.

Fathomer is a highly customizable tool that detects if a visitor to your site is a Twitter user and shows a badge, or ad, with your message and a link to your Twitter account.

It works by detecting if a user has visited the account settings page on Twitter, which is a URL you can only access once you have been logged in to the service. The user does not have to enter any personal account information, nor do you. You can configure Fathomer in many ways:


  1. Custom message – A default is used if none is set.

  2. Specify your account name. This is the only required piece of information and is used for display purposes only.

  3. Custom CSS and badge html – A complete default badge comes installed. Its possible to customize the CSS and even make your own html for the badge.

  4. Change detection url – You can change the Twitter url that is being checked if you need to.

  5. Target badge placement – By default, the badge loads in the top-right corner of the browser. However this can be changed to whatever load in whatever html element you want.


I’m also thinking about some improvements to the detection methods that could increase the precision of the targeted promotion. If there’s enough interest in Fathomer to warrant improvements, they’ll get added in. I’m also interested in adding some conversion metrics, but that takes Fathomer from being a simple and easy to install tool to being a larger project. If it grows, so will its capabilities.

You can visit the project page at http://code.google.com/p/fathomer/ or you can install Fathomer directly by adding the following script to your page somewhere and configuring the username and/or message.

All feedback is requested and appreciated!

<script type="text/javascript" src="http://fathomer.googlecode.com/files/fathomer-latest-min.js"></script>
<script type="text/javascript">
// Required. Your account name.
fathomer.account = 'yourAccountName';

// Optional. The message that is displayed.
//fathomer.message = 'Sample override message.';

// Optional. Can specify a specific element to attach to. ID, class, or css selector
//fathomer.target = '';

// Optional. Turn off default css if you have your own styles.
//fathomer.loadcss = false;

// Optional. Can be changed to a different Twitter url to check the visited history state.
//fathomer.urlcheck = '';

// Optional. If a custom html structure is needed, it can be loaded using standard jquery methods in altbadge. Properties are accessed/set via fathomer.var, e.g. fathomer.message.
//fathomer.altbadge = function(){};

fathomer.init();

</script>

Twitter “protects” API user status call… but doesn’t

Friday, January 9th, 2009

John Resig alerting about the authentication change

UPDATE 2/24/09: Twitter has changed their API and this technique no longer works.

For the last week or so, there’s been a lot of commentary about how you could detect if a Twitter user was visiting your site based on the response of a public, non-authenticated API call. It was documented at Ajaxian.

John Resig was one of the first to notice earlier today that Twitter has placed the API call in question behind http authentication. Indeed, the link he provides to Venture Hacks issues a login alert when you visit the page.

However, thi 1ff8 s does absolutely nothing to prevent a 3rd party from still accessing this information. Twitter is likely to fix this soon, but here’s how to use it in the mean time.

Basically, the API url that is now issuing the authentication requirement was this:

http://twitter.com/statuses/user_timeline.json&count=1&hasTwitter&suppress_response_codes


By simply changing the query string slightly, you bypass authentication and retrieve the user’s status data again if they are logged in. This works without the “/?callback=” part, but this is needed to have have Twitter wrap the json object so that it can be used in the browser, ala jsonp.

http://twitter.com/statuses/user_timeline/?callback=usrobj


If you use jQuery, the simple bit of code that returns this is:

$.ajax({
dataType: 'jsonp',
data: '',
jsonp: 'callback',
url: 'http://twitter.com/statuses/user_timeline/',
success: function(jsondata) {
alert(jsondata.toSource());
},
});


To use this to determine if a visitor is logged into Twitter or not, use the methods described in the Ajaxian article and just change the link. Happy hacking!

Javascript – RGB to Hexadecimal Color Converter

Wednesday, January 7th, 2009

While working on a side project today, I found the need for an RGB to hex color code converter in javascript. There are a number out there within a couple of easy Google searches, but I wasn’t happy with any of the ones that I found.

Every example I found other people had posted over the years involves using a string using 0-9, a-f and then using character matching and some math shifting to return a hex conversion. This irked me because javascript is perfectly capable of doing these conversions with native methods.

So what you will run into from time to time is that some browsers like Firefox and Safari return the css color value of an element as an RGB format, like RGB(204,0,0). Other browsers, namely Internet Explorer, return the hex code, like #cc0000.

What I wanted was a reusable method that will convert an RGB value to the equivalent hex number. It also had to be flexible enough to not be abused if I pass in a hex or non-RGB value. This helps it play nicely across different browsers. Its using native javascript “parseInt” and “toString” methods to do the converting. So enough chit-chat, here’s the damn code. =)

rgbhex = function(rgbval){
var s = rgbval.match(/rgb\s*\x28((?:25[0-5])|(?:2[0-4]\d)|(?:[01]?\d?\d))\s*,\s*((?:25[0-5])|(?:2[0-4]\d)|(?:[01]?\d?\d))\s*,\s*((?:25[0-5])|(?:2[0-4]\d)|(?:[01]?\d?\d))\s*\x29/);

var d='',e;
if(s){ s=s.splice(1); }
	if(s && s.length==3){
		d='';
		for(i in s){
			e=parseInt(s[i],10).toString(16);
			e == "0" ? d+="00":d+=e;
		} return '#'+d;
	}else{ return rgbval; }
}

The Double-class/Double-ID CSS Hack for IE6 & IE7

Tuesday, December 9th, 2008

I haven’t seen this CSS hack for Internet Explorer before. I encountered this by accident today while trying to work out an IE6 display error. My preliminary Google searches and few hours of research didn’t find this documented anywhere, but I highly doubt I’m the first one to encounter this. In any event, I’m tentatively calling this the “Double-class” IE hack. It lets you target IE6 specifically, or IE6 and IE7 both. I haven’t found a way to specifically target IE7 using this.

In a nutshell, if you want to target IE6 use two class periods:

..className{ /* styles here */ }

To target IE7 and IE6 both, double-declare the class name:

..className, .className{ /* styles here */ }

Or, an alternate way to do this and target both IE6 and IE7 is this:

., .className{ /* styles here */ }

This also works with IDs:

.#classID{ /* styles here */ } /* IE6 Only */

., #classID{ /* styles here */ } /* IE6 and IE7 */

0