2009
12.17

A styles-based box-shadow for IE? Holy guacamole Batman!

So every client-side dev would love to have their pages look as nice in IE as they do in browsers that don’t suck (FF, Chrome, etc. – you know the drill). Well here is one more technique you can use to do just that. With XBS you can add some box-shadowy goodness to your designs without too much effort.

Example – click here

To see the effect of the technique using the MS filters, you’ll need to open it in IE – I had to say it, there is always one dewey out there…

Things you’ll need:

  • A wrapper div around your content element
  • An absolutely positioned div to use shadow css class on; this goes in the wrapper
  • Some special styles (IE did a great job of making this part rediculous, thanks MS.)

The Code:

1
2
3
4
    <div id="wrap">
        <div class="shadow"></div>
        <div id="content">Test content words here.</div>
    </div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#wrap
{
    position: absolute; /* REQUIRES SOME POSITION TO BE SET, CAN BE RELATIVE, ABSOLUTE, OR FIXED */
    top: 200px;
    left: 200px;
}  

    .shadow
    {
        position: absolute;
        height: 100%;
        width: 100%;
        top: 0px;
        left: 0px;
        bottom: 0px;
        left: 0px;
        background: #000;
        filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='10');
        -moz-box-shadow:0 0 10px 5px #000000;
        -webkit-box-shadow:0 0 10px 5px #000000; /* NOTICE THAT THE SPREAD-RADIUS IS HALF THE MS FILTER'S PixelRadius */
        opacity: 0.50;
        z-index: 1;
        top: -10px\9; /* CSS HACK FOR ALL IE VERSIONS - MAY WANT TO DO CONDITIONALS INSTEAD */
        left: -10px\9; /* DITTO */
    }
   
    #content
    {
        position: relative; /* REQUIRES SOME POSITION TO BE SET FOR Z-INDEX TO WORK */
        width: 200px;
        height: 200px;
        background: #ff0000;
        z-index: 2; /* MAKE THIS ONE HIGHER THAN THE SHADOW ELEMENT */
    }

What’s the catch?

IE’s filters can cause page-scroll slowness if applied in rediculous fashion. In playing around with it I found that the single largest contributing factor to page-scroll slowness was the radius of the shadow, even very large elements with smaller shadows – 10px for instance – performed well. As such, I would not advise using this for shadows that radiate too far out on large elements or using the technique on hundreds of profile photos that all sit on one page of a gallery. In other words, be sensible. You can always test your implementation for slowness of redraws easily by scrolling a bit up and down in IE – believe me, when you upset the MS filter god, you’ll know immediately.

To get matching box-shadow sizes make sure that your spread-radius on your other-than-IE browsers’ box-shadow property declaration is exactly half of the MS filter’s PixelRadius property. You also need to set the top/left values for IE to the negative value equivalent of size of the radius you set for the MS filter.

Last let-down, I promise: You know how you can do box shadows at different X and Y offsets in the other browsers… Well, to do that, you need to set the top/left positions or margins of the shadowed box in IE, sucks I know, but I mostly use symmetrical shadows on my boxes anyway. You can always go cry to Steve Ballmer if you find any of this frustrating.

The Wrap-up

I will be accepting compliments or whatever cash you have in your pocket currently. Seriously though, seeing box-shadows in IE like this could almost make a person cry tears of joy.

2009
11.20

Working at Mozilla – yes it rocks, hard.

About a month ago (yeah we are blog-post lazy, sue me) a member of the CityCrawler developer team joined the ranks at Mozilla (makers of Firefox); that he is me, Daniel. How did it all happen? Well about 3 months back I took a look at the Mozilla Careers page and thought,

What the hell, I can do this, what have I got to lose?

About a month later, I was sitting in front of my soon-to-be-boss Nick, he was asking me for a decision. After a breif period of lag between brain synapse, I blurted out “Yes, definitely, where do I sign, shall I do it in blood?” (Well not the last part.)

From there I started at the product lead for Jetpack, a sexy new extension framework for Firefox that is about to throw blows and take names in the browser space. The people who I work with are incredibly smart and have one of the greatest work ethics I have witnessed. It is a unique place with a charm that is unique in the working world.

In my opinion, there is not a better job on Earth a young internet dude such as myself could have, and everyday I wake up thankful for it.

Mom, Dad, thanks for sticking by me, I will do you proud!

2009
09.25

Mootools Inpinity Slider

To Inpinity and Beyond!

I recently needed a double pinned slider for a range selection ui I was developing for CityCrawler (our little local search project that is in the works) and found that the other Mootools based multi-pinned sliders out on the web did not support more than 2 pins.

To add a further, more academic challenge to it, I also thought:

Oh what the hell, this thing should be able to have an infinite number of pins too.

Why you ask? Ahh, because I ran out of Sierra Nevadas that night just got really curious how small I could make it and still offer an infinite pin feature.

The Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
var Slider = new Class({

    Implements: [Events, Options],

    Binds: ['clickedElement', 'draggedKnob', 'syncOrder', 'scrolledElement'],

    options: {/*
        onTick: $empty(intPosition),
        onChange: $empty(intStep),
        onComplete: $empty(strStep),
        onSetCollision: $empty(setKnob,[hitKnobs]),
        onDragCollision: $empty(draggedKnob, hitKnob)*/

        onTick: function(position){
            if (this.options.snap) position = this.toPosition(this.step);
            this.knob.setStyle(this.property, position);
        },
        snap: false,
        offset: 0,
        range: false,
        wheel: false,
        steps: 100,
        mode: 'horizontal',
        multiKnob: false,
        measure: false
    },

    initialize: function(element, knob, options){
        this.setOptions(options);
        this.element = document.id(element);
        this.knobSpacing = this.options.knobSpacing;
        this.knob = document.id(knob).store('slider', this);
        this.previousChange = this.previousEnd = this.step = -1;
        var offset, limit = {}, modifiers = {'x': false, 'y': false};
        switch (this.options.mode){
            case 'vertical':
                this.axis = 'y';
                this.property = 'top';
                offset = 'offsetHeight';
                break;
            case 'horizontal':
                this.axis = 'x';
                this.property = 'left';
                offset = 'offsetWidth';
        }
        if(this.options.measure){
          this.full = this.element.measure(function(){ this.half = this.knob[offset] / 2; return this.element[offset] - this.knob[offset] + (this.options.offset * 2); }.bind(this));
        }
        else{
          this.full = this.element[offset] - this.knob[offset] + (this.options.offset * 2);
                  this.half = this.knob[offset] / 2;
        }
        this.min = $chk(this.options.range[0]) ? this.options.range[0] : 0;
        this.max = $chk(this.options.range[1]) ? this.options.range[1] : this.options.steps;
        this.range = this.max - this.min;
        this.steps = this.options.steps || this.full;
        this.stepSize = Math.abs(this.range) / this.steps;
        this.stepWidth = this.stepSize * this.full / Math.abs(this.range) ;
       
        //added either relative or absolute positioning to the element and absolutely positioned knobs so knobs do not clear their respective lefts and rights
        var elementPosition = this.element.getStyle('position');
        if(elementPosition != 'relative' || elementPosition != 'absolute'){ this.element.setStyle('position', 'relative'); }
        this.knob.setStyle('position', (this.options.multiKnob) ? 'absolute' : 'relative').setStyle(this.property, - this.options.offset);
        modifiers[this.axis] = this.property;
        limit[this.axis] = [- this.options.offset, this.full - this.options.offset];

        this.bound = {
            clickedElement: this.clickedElement.bind(this),
            scrolledElement: this.scrolledElement.bindWithEvent(this),
            draggedKnob: this.draggedKnob.bind(this)
        };

        var dragOptions = {
            snap: 0,
            limit: limit,
            modifiers: modifiers,
            onDrag: this.bound.draggedKnob,
            onStart: this.bound.draggedKnob,
            onBeforeStart: (function(){
                this.isDragging = true;
            }).bind(this),
            onComplete: function(){
                this.isDragging = false;
                this.draggedKnob();
                this.knob.removeClass('collided');
                this.drag.firstStep = true;
                this.syncOrder();
                this.end();
            }.bind(this)
        };
        if (this.options.snap){
            dragOptions.grid = Math.ceil(this.stepWidth);
            dragOptions.limit[this.axis][1] = this.full;
        }

        this.drag = new Drag(this.knob, dragOptions), this.drag.firstStep = true;
        this.attach();
    },

    attach: function(){
        //click and mousewheel events are not added to multiknob sliders for obvious reasons
        if(!this.options.multiKnob){
            this.element.addEvent('mousedown', this.bound.clickedElement);
            if (this.options.wheel) this.element.addEvent('mousewheel', this.bound.scrolledElement);
        }
        this.drag.attach();
        return this;
    },

    detach: function(){
        this.element.removeEvent('mousedown', this.bound.clickedElement);
        this.element.removeEvent('mousewheel', this.bound.scrolledElement);
        this.drag.detach();
        return this;
    },

    set: function(step){
        if (!((this.range > 0) ^ (step < this.min))) step = this.min;
        if (!((this.range > 0) ^ (step > this.max))) step = this.max;
           
        //added check to prevented knobs from being set to steps that violate the spacing of knobs
        var instances = this.element.getChildren().retrieve('slider').erase(this).filter(function(e){ return e != null; }), hitKnobs = [];
       
            instances.each(function(e, i){
                var knobSpacing = Math.max(this.knobSpacing, e.knobSpacing);
                if($defined(this.knobSpacing) && $defined(e.knobSpacing) && step > e.step - knobSpacing && step < e.step + knobSpacing){ hitKnobs.push(e.knob) };
            }.bind(this));
           
        if(hitKnobs.length > 0){ this.fireEvent('setCollision', [this.knob, hitKnobs]); return; }    
        else{    
            this.step = Math.round(step);    
            this.checkStep();
            this.fireEvent('tick', this.toPosition(this.step));
            this.syncOrder();
            this.end();
            return this;
        }
    },

    clickedElement: function(event){
        if (this.isDragging || event.target == this.knob) return;

        var dir = this.range < 0 ? -1 : 1;
        var position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
        position = position.limit(-this.options.offset, this.full -this.options.offset);

        this.step = Math.round(this.min + dir * this.toStep(position));
        this.checkStep();
        this.fireEvent('tick', position);
        this.end();
    },

    scrolledElement: function(event){
        var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
        this.set(mode ? this.step - this.stepSize : this.step + this.stepSize);
        event.stop();
    },

    draggedKnob: function(){
        var dir = this.range < 0 ? -1 : 1;
        var position = this.drag.value.now[this.axis];
       
        var lastKnob = this.knob.getAllPrevious(), lastKnob = lastKnob.filter(function(e,i,a){ return e.retrieve('slider').knobSpacing != null; })[0];
        var nextKnob = this.knob.getAllNext(), nextKnob = nextKnob.filter(function(e,i,a){ return e.retrieve('slider').knobSpacing != null; })[0];
        var lastSlider = (lastKnob) ? lastKnob.retrieve('slider') : {}, nextSlider = (nextKnob) ? nextKnob.retrieve('slider') : {};
       
        position = position.limit(-this.options.offset, this.full -this.options.offset);

            //added check to prevented knobs from being dragged to steps that violate the spacing of knobs
            if(this.step > this.toStep(position)){
                var knobSpacing = Math.max(this.knobSpacing, lastSlider.knobSpacing);        
                if($defined(lastSlider.knobSpacing) && $defined(this.knobSpacing) && position < lastSlider.step * lastSlider.stepWidth + knobSpacing * lastSlider.stepWidth){
                    this.set(lastSlider.step + knobSpacing);
                    if(!this.knob.hasClass('collided') && !this.drag.firstStep){ this.knob.addClass('collided'); this.fireEvent('dragCollision', [this.knob, lastKnob]); }
                    this.drag.firstStep = false;
                }
                else{ this.knob.removeClass('collided'); this.step = Math.round(this.min + dir * this.toStep(position)); }
            }
            else{
                var knobSpacing = Math.max(this.knobSpacing, nextSlider.knobSpacing);        
                if($defined(nextSlider.knobSpacing) && $defined(this.knobSpacing) && position > nextSlider.step * nextSlider.stepWidth - knobSpacing * nextSlider.stepWidth){
                    this.set(nextSlider.step - knobSpacing);
                    if(!this.knob.hasClass('collided') && !this.drag.firstStep){ this.knob.addClass('collided'); this.fireEvent('dragCollision', [this.knob, nextKnob]); }
                    this.drag.firstStep = false;
                }
                else{ this.knob.removeClass('collided'); this.step = Math.round(this.min + dir * this.toStep(position)); }                
            }
           
        this.checkStep();
       
    },
   
    syncOrder:function(){
        var pinhash = $H();
        var sorted = this.element.getChildren().retrieve('slider').filter(function(e){ return e != null; }).map(function(e){ pinhash.include(e.step,e.knob); return e.step }).sort(function(a, b){ return a - b; });
        pinhash.each(function(v,k,h){
            var pin = h.get(sorted[0]);
            if(pin){
                pin.inject(this.element, 'bottom');
                sorted.shift();
            }
        }.bind(this));    
    },
   
    checkStep: function(){
        if (this.previousChange != this.step){
            this.previousChange = this.step;
            this.fireEvent('change', this.step);
        }
    },

    end: function(){
        if (this.previousEnd !== this.step){
            this.previousEnd = this.step;
            this.fireEvent('complete', this.step + '');
        }
    },

    toStep: function(position){
        var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
        return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
    },

    toPosition: function(step){
        return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
    }

});

//Here is an example usage, you basically call a Mootools slider instance up for each pin. You must pass in multiKnob: true
//  - there is also an added feature bonus in that you can specify a knob spacing for each of the knobs you add to your slider,
//    this lets you set the step-wise distance away that each pin will keep from one another.

$$('#search_filter_primaryWrap .slideknob').each(function(e,i){
   
    var slidelabel_min = e.getParent().getPrevious(), slidelabel_max = e.getParent().getNext();
   
    new Slider(e.getParent(), e, {
      range: [0, 100],
      steps: 100,
      multiKnob: true,
      knobSpacing: 5,
      onChange: function(pos){
            (e.hasClass('slideknob_min')) ? slidelabel_min.set('html', 'min<div>'+ pos +'%</div>') : slidelabel_max.set('html', 'max<div>'+ pos +'%</div>');
      }
    }).set((e.hasClass('slideknob_min')) ? 0 : 100);
   
});

The Wrap-up

So that is my bad mofo of a Class.refactor for the Mootools More Slider plug-in, have any feedback or optimization ideas for it?

2009
08.05

$Uses – Final

$Uses, the one time being used didn’t feel so dirty!

I whittled $Uses down to a faster more agile implement with the added bonus of css file inclusions as well. The script is certainly faster and does its job with less checking than the others past versions.

A recap on what $Uses can do:

  1. Check before calling a function or code block to see if the scripts/css that the function or code relies upon is there.
  2. List needed scripts/css in Classes for auto pulling via the Uses: mutator, wrap code blocks large and small, use it inline on functions, even on event functions:  $(’#div’).addEvent(’click’, function(){ }.uses({jsScriptsArray, cssFileArray, otherOptions}));
  3. Fire a function once when all files are loaded as a first run initialization of the Class, wrapped code block, or inline attached function.
  4. Retrieves scripts/css and adds a version number global if it is present as the var siteVersion (you can change this to whatever you want in the source).

The Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
Function.implement({
   uses:function(obj){
      var self = this;
      if(typeof obj == 'undefined'){return self;}
      window.jsDict = (window.jsDict) ? window.jsDict : $$('script').get('src'), window.cssDict = (window.cssDict) ? window.cssDict : $$('link').get('href');
      var h = $H(obj), jsd = window.jsDict.toString(), cssd = window.cssDict.toString(), js = (h.get('js')) ? h.get('js').filter(function(e){ return !jsd.contains(e); }) : [], css = (h.get('css')) ? h.get('css').filter(function(e){ return !cssd.contains(e); }) : [], bindWith = (obj.bindWith) ? obj.bindWith : window;
      if(css.length > 0){
        css.each(function(e){
            new Asset.css(e + ((typeof siteVersion != 'undefined') ? ('.css?v=' + siteVersion) : '.css'));
            window.cssDict.push(e);
        });
      }
      if(js.length > 0){
          var usesable = function(){
             var count = 0, onInit = h.get('onInit') || $empty;
             js.each(function(e){
                count++;
                new Asset.javascript(e + ((typeof siteVersion != 'undefined') ? ('.js?v=' + siteVersion) : '.js'),{ onload:function(){
                   count--;
                   window.jsDict.push(e);
                   if(count == 0){ count--;  
                       onInit();
                       self.bind(bindWith)();
                   }
                }});
             });
         }
         return usesable;
     }
     else{ return self; }
  }
});

Window.implement({
    $Uses:function(obj, fn){
        (fn || $empty).uses(obj)();
    }
});

Class.Mutators.Uses = function(uses){
    return uses;
};

Class.Mutators.initialize = function(initialize){
    return function(){
        $splat(this.Binds).each(function(name){
            var original = this[name];
            if (original) this[name] = original.bind(this);
        }, this);
        var pass = [this, arguments];
        $Uses(this.Uses, function(){ return initialize.apply(pass[0], pass[1]); });
    };
};

// How you use it:

// For dependancy includes on a Class:

var eventApp = new Class({
 
    Implements: [Events, Chain, Options],
   
    Binds: ['validate','request','display'],
   
    Uses: {js:['js/liquidSlider','js/datepicker'] , css:['styles/event_app', 'styles/datepicker']},
   
    initialize: function(element, options) {
        this.setOptions(options);
        var app = this;
       
    yada yada yada...


// To wrap any section of code:

$Uses({js:['js/citycrawler'], onInit:function(){ alert("I'm about to fire a first run init function") }}, function(){
    cityCrawlerControler = new CityCrawler({
        pageSize: 15,
        resultElem: $('result_list_ul'),
        gMapElem: $('googleMap')
    });
    cityCrawlerControler.executeQuery($('txt_BusinessSearch').value, true);
});


// Directly on a function:

someFn(){}.uses({js:['js/citycrawler'], onInit:function(){ alert("I'm about to fire the attached function only once on loaded initialization") }})

WWMD? (What Would Mootools Do?)

There is a more broadly scoped project being developed by the Mootools dev team deep inside their underground lair called Depender that is likely to debute in Mootools 2.0 as a More plugin, links courtesy of Aaron Newton of the Clientcide blog:

Code on Git:  http://github.com/mootools/mootools-more/blob/master/Source/Core/Depender.js

Docs for Depender:  http://github.com/mootools/mootools-more/blob/master/Docs/Core/Depender.md

The Wrap-up

This script plug-in is almost as extensive as what Mootools has cooking with Depender, it essentially trades a few Depender features in favor of a super lightweight and simple payload.  Disclosure: On a few sites I have, I will be using Depender because they do have a need for broad dependancy and instance checks.

Hope the script help you with your work ;)

2009
08.03

The Creative Intro:

Window.MainEntry:
$Uses

Mootools Pronunciation:
\’dollar-yoo-ses’\

Function.implement:
verb

Etymology:
Native Function implementation for script and css includes in the land of Moo

Date:
First established in the early 21st century

The Reason:

Recently I was in need of a lightweight solution for verification of the existence of scripts on my pages prior to execution of certain functions and Classes.  Basically, errors suck and undefined makes me see red.  I also wanted the added bonus of not having to load more js than I needed.I did the script the way I did because I didn’t need crazy ass heirarchical dependency checking or to block certain scripts and not others. I just needed it to accomplish one thing: To know if a script/css file was present or not, and if not, to go get it before doing something.

Update:

This post’s contents have been depricated please see the final version of $Uses for the code:  http://blog.citycrawler.com/?p=47

2009
05.28

These Google folks really care about the web.

From the very opening keynote, it is clearly evident that the spirit and passion of this company gravitates around advancing adoption of open web standards, a desire to truely help the user, and enabling developer creativity to blossom.  At the first Google IO there was a bold promise made about Google’s commitment to driving the web and the browser forward.  At Google IO 2009, the fulfillment of this promise is evident in almost every product and initiative the company is involved with.  I can only describe parts of what was they discussed and demonstrated as emotionally moving.  Stop for a minute and think about that, I believe it is a powerful indicator of being on the right track.  When a technology company begins to evoke such emotive feelings in folks…yeah, that is not the ordinary and everyday.

In the keynote on day 1, Vic Gundotra (Google’s Vice President of Engineering) discussed all the new HTML 5 features that were being supported by Google Chrome and other web browsers.  Internet Explorer was MIA on all accounts in relation to HTML 5, they literally mentioned IE as the “elephant in the room”.  I am personally appalled as a Product Manager that the individual heading the Internet Explorer team would fail to remain competitive, turn a cold shoulder to the requests of the community, and seemingly operate with a “just good enough” attitude when it comes to such a cornerstone product.  Fail, fail, fail.  I hope this is a wake-up call to Microsoft that the web train is going to move forward whether they’re on board or not.

All attendees were given a special Google IO 2009 version of the G2 cell phone with a free month of T-mobile service.  Yeah, the phone rocks, but T-Mobile, or a hacked connection to AT&T, are the only real carrier choices right now, so I basically have an elegant and advanced paperweight on my hands after the 30 days are up…  I can’t fault Google on that one, I have Verizon currently and have asked many, many times over the past year for them to offer a phone that isn’t abjectly horrible i.e. iPhone, Palm Pre, or an Android phone.  They’re responses ranged from opinion to ridiculous; no matter what phone I get next month, it will not be serviced by Verizon. That is what you get for not listening to the resoundingly loud outcries from your customers!

The sessions have been helpful, we learned a thing or two from a UK Googler about the Google crawler’s ability to assess javascript and how to lead the crawler correctly on an ajax heavy site without getting negative hits for javascript redirects. More on that one in a different post.

If you haven’t had the chance to go to this conference I would absolutely suggest you do.  Seeing the way this company conducts itself on the operational and execution level is an educational experience in and of  itself!

2009
04.30

Almost 1 year ago to the day we set out to create a new form of local search that focused on three key issues we felt were the most compelling problems present in that sector.  While other sites possess variying degrees of these elements none, in our opinion, service them well enough.  Well what are they?

  • User experience, beyond the traditional Neilsen idea of it (no knocking Neilsen, we just have different plans…)
  • Depth of content – real depth, not just the appearance thereof
  • Market dominating creativity – arguably the most important

User Experience -

There is a time for adherence and a time to move beyond.  What does that mean to us?  It means IE6 users aren’t welcome – why should we apologize, you’re the one with the crappy browser, it means that users without JS (all 5 of you) need to take off your tin foil hats – the government is not after you, or are they…ha ha, it means that our competitors can HAVE the lowest user denominator.

Depth of Content -

A lot of other sites in this arena seemingly focus on creating their own little mini-social networking sites.  We think our app is good, but butting heads with the likes of FaceBook and MySpace…we’ll leave that to our competitors too.  We are constantly focused on creating unique, deep content features that enable users to know the fine grain details of the local places they are looking at.  What did Billy say to Sally about that little bar in town?  Who gives a $h_t?  What beers are on tap?  When do the stop serving they late night bar food?  Those are the questions we want to answer.

Market dominating creativity -

We want to lead the market by the nose by setting the tone for innovation.  If I see a great feature out there that I, or one of ours, did not think of first, we failed on that point.  “Outside the box” is not a case by case effort, it is a train of thought that you develop, it needs to always stay switched on.  Some companies are very top down in their approach to leadership and direction of their products.  I think this is corporate America’s biggest failure.  Creativity and good ideas are not resigned to top brass.  In fact, may times those folk are so out of touch with the state-of-the-art for their industry, their companies would be better off if they just took a sabbatical.  Why can’t your company’s next big idea com from that secretary you count on for your critical tasks, or that heads-down engineer you rely on technically for so much?  People are not trained monkeys, they are dynamic individuals that are at any time prone to surprise the hell out of you, so support that bottom-up creativity however you can.

CityCrawler was developed with all these ideas at its core.  You’ll see it in all its release-ready glory soon enough!