Presenting Designs

14 January 2018

Designers (of all types, graphic, product, ux, etc.) - I have a question for you.

When you present your work, how many versions, looks, ideas do you send and what is your strategy for helping stakeholders reach a decision to move forward with said designs?

—marc hemeon · 9:05 PM – 8 Jan 2018

I begin each critique by explaining and demonstrating the problem, and then I present one solution. I usually include a few edge-case mockups and Principle animations to ensure the team thoroughly comprehends the proposal.

However, the team doesn't know I have a Sketch file up my sleeve. It's crucial to keep every iteration and have the ability to explain why the presented solution is ideal. If you have enough iterations you can be ready when the team inevitably asks "What if you tried x" questions.

Marc asks what one does to help stakeholders reach a decision because there is no guarantee that the team will agree with your proposal. You will receive both high-level and pixel-level feedback. Never take it personally. You are all trying to achieve the same goal: make your customers successful. When you receive feedback you must take notes. I usually flip between Google Slides and Things while I'm connected to Google Meet or the TV. The team will appreciate watching you carefully write down their suggestions.

Do not immediately reject what your team suggests. Note it and then reflect after the meeting. There's a good chance you do not fully understand the suggestion or your gut reaction is mistaken. When you present updated mockups and animations in a future meeting, take extra time to explain why the team's suggestions did or didn't work. This shows thoughtfulness and your team will appreciate it. By including stakeholders in the design process you will converge on a solution over time.

Permalink
*    *    *
1 million views on Unsplash

A Year on Unsplash

07 January 2018

2017 was a great year for my photography craft. I dedicated a lot of time to shooting, editing, and, most importantly, sharing. Of course I share on the Gram, but last year I also cross-posted my favorite photos on Unsplash, became involved in the Unsplash Slack channel, and hosted two photowalk events in San Francisco. This earned a spot in the Best of 2017 post as a Community Member of the Year. Giddy up.

Oh, and I reached 1,000,000 views on my photos. Next milestone? 10,000,000! I love this community. Below are some of my favorite photos.

See more on Unsplash
Permalink
*    *    *

World's 50 Best Bars

07 January 2018

A friend mentioned that a World's 50 Best Bars list exists, so now I need to go to them. Clearly I need to visit Singapore and London because I'm only 10% complete.

  1. American Bar – London
  2. Dandelyan – London
  3. The Nomad – New York
  4. Connaught Bar – London
  5. Dead Rabbit – New York
  6. The Clumsies – Athens
  7. Manhattan – Singapore
  8. Attaboy – New York
  9. Bar Termini – London
  10. Speak Low – Shanghai
  11. Little Red Door – Paris
  12. Happiness Forgets – London
  13. High Five – Tokyo
  14. Licoreria Limantour – Mexico City
  15. Atlas – Singapore
  16. Dante – New York
  17. Oriole – New York
  18. Broken Shaker – Miami Beach
  19. Candelabra – Paris
  20. Himkok – Oslo
  21. The Gibson – London
  22. Black Pearl – Melbourne
  23. Floreria Atlantico – Buenos Aires
  24. Operation Dagger – Singapore
  25. Hongkong Street – Singapore
  26. Trick Dog – San Francisco
  27. Sweet Liberty – Miami Beach
  28. Indulge Experimental Bistro – Taipai
  29. Lost & Found – Nicosia
  30. Baba Au Rum – Athens
  31. Tippling Club – Singapore
  32. Blacktail – New York
  33. Jerry Thomas Speakeasy – Rome
  34. Le Syndicat – Paris
  35. Tales & Spirits – Amsterdam
  36. Bar Benfiddich – Tokyo
  37. Employees Only – New York
  38. Schumann's – Munich
  39. La Factoria – San Juan
  40. Quinary – Hong Kong
  41. Aviary – Chicago
  42. Mace – New York
  43. Nightjar – London
  44. Linje Tio – Stockholm
  45. The Baxter Inn – Sydney
  46. ABV – San Francisco
  47. Native – Singapore
  48. Tommy's – San Francisco
  49. Lobster Bar – Hong Kong
  50. Imperial Craft – Tel Aviv
Permalink
*    *    *

Home Networking Gear

09 November 2017

Not only will you have a blazing fast Internet connection and WiFi network with these products, but you will also save money. That's right. Renting a cable modem and WiFi router from Comcast costs money. Continuing (somehow) to have a phone line costs money. With this setup you will own a faster cable modem, you will own a faster, more reliable WiFi router, and you will not pay a monthly fee for a phone line or cable modem. Ready?

cable modem
Cable Modem
Arris Surfboard cable modem
  • ARRIS SURFboard SB6190 DOCSIS 3.0
  • $97.99 · Amazon

The ARRIS SURFboard connects your home to the outside world (Comcast for example), and it's surprisingly easy to set up. Just screw the cable that comes out of your wall into it. Then call your Internet provider and tell them the modem's serial number. When you have the strength to leave your house, take the old modem to your Internet provider's retail store. Your bill should decrease since you aren't renting a modem anymore, but I would double check your next bill to make sure.

eero wifi router
WiFi Router
eero WiFi System
  • eero Home WiFi System
  • $318.99 · Amazon

The eero provides wireless Internet connectivity in your home. I know it's expensive, but it's so much more than your typical router. It uses mesh technology to make the connections between your devices and modem much faster and more reliable. You can save some money by purchasing only one beacon instead of two. Installation requires the eero mobile app, connecting an Ethernet cable between your cable modem and the eero, and plugging in the beacons in different rooms.

eero wifi router
Ethernet Switch
netgear ethernet switch
  • Netgear 5-Port Gigabit Ethernet Switch
  • $18.95 · Amazon

A switch allows you to connect multiple devices to your router using Ethernet cables. Why is this important? Devices like your TV, Blu-ray player, Apple TV, Xbox One, PlayStation 4, etc. all want (or may even require) an Internet connection. If they are communicating with the outside world, they may steal bandwith from your wireless network that your iPhone, iPad, or MacBook want. However, if you connect them directly to your router, they won't disrupt your home's wireless network. The problem is the eero does not have a bunch of extra Ethernet ports. That's where the switch helps! It turns the eero's extra port into five ports. To set up the switch just connect one of the eero's Ethernet ports to one of the switch's Ethernet ports using an Ethernet cable. Simple.

ooma telephone
Telephone
ooma voip phone and handset
  • Ooma Telo Phone Service
  • $119.44 · Amazon

Do you live with someone who won't give up a home phone even after you remind them that it's 2018? Fortunately there is an affordable solution. Ooma is a one-time purchase VOIP service. Setting it up requires a few clicks on their website, choosing a phone number, and connecting it to your Ooma using the provided Etherent cable.

ethernet cable
Ethernet Cable
ethernet cable
  • AmazonBasics RJ45 Cat7 Ethernet Cable
  • $6.50 · Amazon

Connect your other Internet-connected devices to your new Ethernet switch using these cables to decrease your wireless network's traffic. For example, I have a TV, Blu-ray player (yes, a Blu-ray player), and Apple TV connected to my switch. I don't own an Xbox or PlayStation because I'm afraid of becoming addicted to video games. I only play Civilization 6 a couple times per year and that satisfies me.

If you have any questions please feel free to hit me up on Twitter.

Permalink
*    *    *

JSD4 Final Project

29 November 2016

The final project for my General Assembly JavaScript class is particularly cool. I built a dashboard for people to see my latest Instagram photo, Dribbble shot, and Tweet on one page using React, Node, Firebase, and Express. The project is live on Heroku.

Thank you Jordan, Kyle, and Arthur for pushing me to improve my code.

Node.js
// SETUP
// ---------------------------------------------------
var express = require('express');
var app = express();
var router = express.Router();
app.set('port', (process.env.PORT || 5000));
app.use(express.static(__dirname + '/public'));
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');

// routes
router.get('/', function(req, res) {
  res.render('home');
});
router.get('/final', function(req, res) {
  res.render('final');
});
router.get('/contacts', function(req, res) {
  res.render('contacts');
});
app.use('/', router);
app.use('/final', router);
app.listen(app.get('port'), function() {
  console.log('Node app is running on port', app.get('port'));
});

// TWITTER
// ---------------------------------------------------
var Twitter = require('twitter');
var client = new Twitter({
  consumer_key: 'NICE',
  consumer_secret: 'TRY',
  access_token_key: 'BRO',
  access_token_secret: 'BROAGAIN'
});
var params = {screen_name: 'diklein'};
router.get('/tweets', function (req, res) {
  client.get('statuses/user_timeline', params, function(error, tweets, response) {
     if (!error) {
        var tweetJson = JSON.stringify(tweets);
        res.send(tweetJson);
     }
     else {
        console.log(error);
     }
  });
});

// INSTAGRAM
// ---------------------------------------------------
router.get('/instagram', function (req, res) {
  client.get(
  'https://api.instagram.com/v1/users/69315/media/recent/?access_token=NICETRY&count=1',
  function(error, photos, response) {
     if (!error) {
        var instagramJson = JSON.stringify(photos);
        // console.log(instagramJson);
        res.send(instagramJson);
     }
  });
});

// DRIBBBLE
// ---------------------------------------------------
var request = require('request');
router.get('/dribbble', function (req, res) {
  request('https://api.dribbble.com/v1/users/diklein/shots?access_token=WHATUPSON',
  function (error, response, body) {
     if (!error && response.statusCode == 200) {
        res.send(body);
     }
  })
});
JavaScript
// RENDER PARENT COMPONENT
// --------------------------------------------------
var RenderComponent = React.createClass({
  // set up the empty state for all data
  getInitialState: function () {
     return {
        // likes first
        twitterLikes: 0,
        dribbbleLikes: 0,
        instagramLikes: 0,
        // twitter data container
        twitterSnapshot: {
           permalink: '',
           text: '',
           time: '',
           timeYear: '',
           timeDayName: '',
           timeDayOfMonth: '',
           timeMonth: ''
        },
        // dribbble data container
        dribbbleSnapshot: {
           permalink: '',
           photoURL: '',
           caption: '',
           time: '',
           timeYear: '',
           timeDayName: '',
           timeDayOfMonth: '',
           timeMonth: ''
        },
        // instagram data container
        instagramSnapshot: {
           permalink: '',
           photoURL: '',
           caption: '',
           time: '',
           timeYear: '',
           timeDayName: '',
           timeDayOfMonth: '',
           timeMonth: ''
        }
     };
  },

  // set up firebase before page is ready
  componentWillMount() {
     this.ref = new Firebase("https://dk-jsd4.firebaseio.com/");
  },

  // after page is ready go round up the data
  componentDidMount() {
     // get firebase data and set likes state
     this.ref.on('value', function (snapshot) {
        var data = snapshot.val();
        if (data !== null) {
           this.setState({
              twitterLikes: data.twitter,
              instagramLikes: data.instagram,
              dribbbleLikes: data.dribbble
           });
        }
     }.bind(this));

     // set state with twitter content with promise
     getTwitterData().then(function (data) {
        this.setState({
           twitterSnapshot: data
        });
     }.bind(this));

     // set state with dribbble content with promise
     getDribbbleData().then(function (data) {
        this.setState({
           dribbbleSnapshot: data
        });
     }.bind(this));

     // set state with instagram content with promise
     getInstagramData().then(function (data) {
        this.setState({
           instagramSnapshot: data
        });
     }.bind(this));

  },

  // if user clicks twitter like link
  increaseTwitterLike: function(e) {
     e.preventDefault();
     // make a new variable because of timing
     const newLikes = this.state.twitterLikes + 1;
     this.setState({ twitterLikes: newLikes });
     // only update twitter value in firebase
     // '.child' allows you to write to a specific part of the firebase data
     this.ref.child('twitter').set(newLikes);
  },

  // if user clicks dribbble like link
  increaseDribbbleLike: function(e) {
     e.preventDefault();
     // make a new variable because of timing
     const newLikes = this.state.dribbbleLikes + 1;
     this.setState({ dribbbleLikes: newLikes });
     // only update dribbble value in firebase
     this.ref.child('dribbble').set(newLikes);
  },

  // if user clicks instagramLike like link
  increaseInstagramLike: function(e) {
     e.preventDefault();
     // make a new variable because of timing
     const newLikes = this.state.instagramLikes + 1;
     this.setState({ instagramLikes: newLikes });
     // only update instagram value in firebase
     this.ref.child('instagram').set(newLikes);
  },

  // render individual components
  // each component receives props — likes, state, and click function}
  // '...' saves significant space by not specifying each part of the object
  render: function() {
     return(
        <div>
        <TwitterComponent likes={this.state.twitterLikes} {...this.state.twitterSnapshot} onClick={this.increaseTwitterLike} />
        <InstagramComponent likes={this.state.instagramLikes} {...this.state.instagramSnapshot} onClick={this.increaseInstagramLike} />
        <DribbbleComponent likes={this.state.dribbbleLikes} {...this.state.dribbbleSnapshot} onClick={this.increaseDribbbleLike} />
        </div>
     )
  }
});

// TWITTER
// --------------------------------------------------
function getTwitterData() {
  // set up promise for json data
  return $.getJSON('/tweets').then(function(tweets) {
     const firstTweet = tweets.filter(tweet => (tweet.text)[0] !== '@')[0];
     const firstTweetDate = new Date(firstTweet.created_at.toString());
     // return data from json
     return {
        'permalink': "https://twitter.com/diklein/status/" + firstTweet.id_str,
        'text': firstTweet.text,
        'time': firstTweetDate.created_at,
        'timeYear': firstTweetDate.getFullYear(),
        'timeDayName': days[firstTweetDate.getDay()],
        'timeDayOfMonth': firstTweetDate.getUTCDate(),
        'timeMonth': months[(firstTweetDate.getUTCMonth() + 1)]
     };
  });
}

// twitter component
function TwitterComponent(props) {
  return (
     <div className="twitter">
        {props.text}
        <span className="time">
           <a href={props.permalink}>{props.timeDayName} {props.timeMonth} {props.timeDayofMonth}, {props.timeYear}</a>
            · <a href='#' onClick={props.onClick} className="twitterLike">Like {props.likes > 0 && ' · ' + props.likes}</a>
        </span>
     </div>
  )
}

// DRIBBBLE
// --------------------------------------------------
// get data out of json
function getDribbbleData() {
  // set up promise for json data
  return $.getJSON('/dribbble').then(function(shots) {
     // setup
     const firstShot = shots[0];
     const firstShotDate = new Date(firstShot.created_at);
     const fixingCaption = firstShot.description.replace('<p>','');
     // return data from json
     return {
        'permalink': firstShot.html_url,
        'photoURL': firstShot.images.hidpi,
        'caption': fixingCaption.replace('</p>',''),
        'time': firstShotDate,
        'timeYear': firstShotDate.getFullYear(),
        'timeDayName': days[firstShotDate.getDay()],
        'timeDayOfMonth': firstShotDate.getUTCDate(),
        'timeMonth': months[(firstShotDate.getUTCMonth() + 1)]
     };
  });
}

// dribbble component
function DribbbleComponent(props) {
  return (
     <div className="dribbble">
        <div className="imgContainer">
           <img src={props.photoURL} />
           </div>
        <div className="dribbbleContent">
           <span className="caption">
              {props.caption}
           </span>
           <span className="time">
              <a href={props.permalink}>{props.timeDayName}, {props.timeMonth} {props.timeDayOfMonth}, {props.timeYear}</a>
               · <a href='#' onClick={props.onClick} className="dribbbleLike">Like {props.likes > 0 && ' · ' + props.likes}</a>
           </span>
        </div>
     </div>
  )
}

// INSTAGRAM
// --------------------------------------------------
// get data out of json
function getInstagramData() {
  // set up promise for json data
  return $.getJSON('/instagram').then(function(photos) {
     // setup
     const firstPhoto = photos.data[0];
     const firstPhotoDate = new Date(firstPhoto.created_time * 1000);
     // return data from json
     return {
        'permalink': firstPhoto.link,
        'photoURL': firstPhoto.images.standard_resolution.url,
        'caption': firstPhoto.caption.text,
        'time': firstPhotoDate,
        'timeYear': firstPhotoDate.getFullYear(),
        'timeDayName': days[firstPhotoDate.getDay()],
        'timeDayOfMonth': firstPhotoDate.getUTCDate(),
        'timeMonth': months[(firstPhotoDate.getUTCMonth() + 1)]
     };
  });
}

// instagram component
function InstagramComponent(props) {
  return (
     <div className="instagram">
        <div className="imgContainer">
           <img src={props.photoURL} />
        </div>
        <div className="instagramContent">
           <span className="caption">
              {props.caption}
           </span>
           <span className="time">
              <a href={props.permalink}>{props.timeDayName}, {props.timeMonth} {props.timeDayOfMonth}, {props.timeYear}</a>
               · 
              <a href='#' onClick={props.onClick} className="instagramLike">Like {props.likes > 0 && ' · ' + props.likes}</a>
           </span>
        </div>
     </div>
  )
}

// render the component that contains the smaller components
const parent = document.querySelector(".parent");
ReactDOM.render(<RenderComponent />, parent);
Permalink
*    *    *
Older Posts →