diff --git a/factories/Common.php b/factories/Common.php
index dfae934d0..e222266ea 100644
--- a/factories/Common.php
+++ b/factories/Common.php
@@ -12,19 +12,20 @@
*/
$factory->define(\OpenCFP\Domain\Model\User::class, function (\Faker\Generator $faker) {
return [
- 'email' => $faker->unique()->safeEmail,
- 'password' => \password_hash('secret', PASSWORD_BCRYPT),
- 'activated' => 1,
- 'first_name' => $faker->firstName,
- 'last_name' => $faker->lastName,
- 'company' => $faker->company,
- 'twitter' => '@' . $faker->userName,
- 'activated_at' => $faker->dateTimeInInterval('-2 months', '-1 months'),
- 'last_login' => $faker->dateTimeInInterval('-5 days', 'now'),
- 'transportation' => $faker->randomElement([0, 1]),
- 'hotel' => $faker->randomElement([0, 1]),
- 'info' => $faker->realText(),
- 'bio' => $faker->realText(),
+ 'email' => $faker->unique()->safeEmail,
+ 'password' => \password_hash('secret', PASSWORD_BCRYPT),
+ 'activated' => 1,
+ 'first_name' => $faker->firstName,
+ 'last_name' => $faker->lastName,
+ 'company' => $faker->company,
+ 'twitter' => '@' . $faker->userName,
+ 'joindin_username' => $faker->userName,
+ 'activated_at' => $faker->dateTimeInInterval('-2 months', '-1 months'),
+ 'last_login' => $faker->dateTimeInInterval('-5 days', 'now'),
+ 'transportation' => $faker->randomElement([0, 1]),
+ 'hotel' => $faker->randomElement([0, 1]),
+ 'info' => $faker->realText(),
+ 'bio' => $faker->realText(),
];
});
diff --git a/migrations/20181002234013_create_user_joind_in_username_column.php b/migrations/20181002234013_create_user_joind_in_username_column.php
new file mode 100644
index 000000000..7b028d58e
--- /dev/null
+++ b/migrations/20181002234013_create_user_joind_in_username_column.php
@@ -0,0 +1,73 @@
+getAdapter()->getAdapter();
+ $options = $adapter->getOptions();
+ $this->capsule = new Capsule();
+ $this->capsule->addConnection([
+ 'driver' => 'mysql',
+ 'database' => $options['name'],
+ ]);
+ $this->capsule->getConnection()->setPdo($adapter->getConnection());
+ $this->capsule->bootEloquent();
+ $this->capsule->setAsGlobal();
+ }
+
+ public function up(): void
+ {
+ // Create joindin_username
+ $this->table('users')
+ ->addColumn('joindin_username', 'string', ['null' => true])
+ ->update();
+
+ // Go through each record in user, strip out (https://joind.in/user/) and copy to joindin_username
+ $joindInRegex = '/^https:\/\/joind\.in\/user\/(.{1,100})$/';
+
+ $users = EloquentUser::all();
+
+ foreach ($users as $user) {
+ if (\preg_match($joindInRegex, $user->url, $matches) === 1) {
+ $user->joindin_username = $matches[1];
+ $user->url = null;
+ $user->save();
+ }
+ }
+ }
+
+ public function down(): void
+ {
+ // Go through each record in user, update `url` to move the joindin_username to there
+ $users = EloquentUser::all();
+
+ foreach ($users as $user) {
+ $user->url = $user->joindin_username
+ ? 'https://joind.in/user/' . $user->joindin_username
+ : null;
+ $user->joindin_username = null;
+ $user->save();
+ }
+ // Drop the joindin_username column
+ $this->table('users')
+ ->removeColumn('joindin_username')
+ ->update();
+ }
+}
diff --git a/resources/views/admin/speaker/view.twig b/resources/views/admin/speaker/view.twig
index 70df39c88..2ccdbb62d 100644
--- a/resources/views/admin/speaker/view.twig
+++ b/resources/views/admin/speaker/view.twig
@@ -15,6 +15,15 @@
diff --git a/resources/views/dashboard.twig b/resources/views/dashboard.twig
index 4d36cfdff..546f80320 100644
--- a/resources/views/dashboard.twig
+++ b/resources/views/dashboard.twig
@@ -11,7 +11,7 @@ function deleteTalk(tid) {
if (data.delete == 'ok') {
$("#talk-"+tid).remove();
} else if (data.delete == 'no-user') {
- alert("You must be logged in to delete talks")
+ alert("You must be logged in to delete talks");
} else {
alert("Unable to delete talk");
}
@@ -47,11 +47,22 @@ function deleteTalk(tid) {
{% if profile.company %}
{{ profile.company }}
{% endif %}
+
{% if profile.twitter %}
- @{{ profile.twitter }}
+ @{{ profile.twitter }}
+ {% endif %}
+
+ {% if profile.joindInUsername %}
+
+ {{ profile.joindInUsername }}
+
{% endif %}
+
{% if profile.url %}
- | {{ profile.url }}
+
+ {{ profile.url }}
{% endif %}
diff --git a/resources/views/forms/_user.twig b/resources/views/forms/_user.twig
index ab24ee879..a7f035eb1 100644
--- a/resources/views/forms/_user.twig
+++ b/resources/views/forms/_user.twig
@@ -20,10 +20,13 @@
Twitter
- joind.in
-
+ joind.in username
+
- {% if site.online_conference == false %}
+ URL
+
+
+ {% if site.online_conference == false %}
Travel Assistance
diff --git a/src/Domain/Speaker/SpeakerProfile.php b/src/Domain/Speaker/SpeakerProfile.php
index 965dfda46..a5bc0b7d4 100755
--- a/src/Domain/Speaker/SpeakerProfile.php
+++ b/src/Domain/Speaker/SpeakerProfile.php
@@ -140,6 +140,36 @@ public function getTwitterUrl(): string
return 'https:://twitter.com/' . $twitter;
}
+ /**
+ * @throws NotAllowedException
+ *
+ * @return null|string
+ */
+ public function getJoindInUsername()
+ {
+ $this->assertAllowedToSee('joindin_username');
+
+ return $this->speaker->joindin_username;
+ }
+
+ /**
+ * @throws NotAllowedException
+ *
+ * @return null|string
+ */
+ public function getJoindInUrl()
+ {
+ $this->assertAllowedToSee('joindin_username');
+
+ $username = $this->speaker->joindin_username;
+
+ if ($username === null || \trim($username) === '') {
+ return '';
+ }
+
+ return 'https://joind.in/user/' . $username;
+ }
+
/**
* @throws NotAllowedException
*
diff --git a/src/Http/Action/Profile/EditAction.php b/src/Http/Action/Profile/EditAction.php
index 7642f830b..8e76ac46b 100755
--- a/src/Http/Action/Profile/EditAction.php
+++ b/src/Http/Action/Profile/EditAction.php
@@ -73,22 +73,23 @@ public function __invoke(HttpFoundation\Request $request): HttpFoundation\Respon
$speakerData = Model\User::find($user->getId())->toArray();
$content = $this->twig->render('user/edit.twig', [
- 'email' => $user->getLogin(),
- 'first_name' => $speakerData['first_name'],
- 'last_name' => $speakerData['last_name'],
- 'company' => $speakerData['company'],
- 'twitter' => $speakerData['twitter'],
- 'url' => $speakerData['url'],
- 'speaker_info' => $speakerData['info'],
- 'speaker_bio' => $speakerData['bio'],
- 'speaker_photo' => $speakerData['photo_path'],
- 'preview_photo' => $this->path->uploadPath() . $speakerData['photo_path'],
- 'airport' => $speakerData['airport'],
- 'transportation' => $speakerData['transportation'],
- 'hotel' => $speakerData['hotel'],
- 'id' => $user->getId(),
- 'formAction' => $this->urlGenerator->generate('user_update'),
- 'buttonInfo' => 'Update Profile',
+ 'email' => $user->getLogin(),
+ 'first_name' => $speakerData['first_name'],
+ 'last_name' => $speakerData['last_name'],
+ 'company' => $speakerData['company'],
+ 'twitter' => $speakerData['twitter'],
+ 'joindin_username' => $speakerData['joindin_username'],
+ 'url' => $speakerData['url'],
+ 'speaker_info' => $speakerData['info'],
+ 'speaker_bio' => $speakerData['bio'],
+ 'speaker_photo' => $speakerData['photo_path'],
+ 'preview_photo' => $this->path->uploadPath() . $speakerData['photo_path'],
+ 'airport' => $speakerData['airport'],
+ 'transportation' => $speakerData['transportation'],
+ 'hotel' => $speakerData['hotel'],
+ 'id' => $user->getId(),
+ 'formAction' => $this->urlGenerator->generate('user_update'),
+ 'buttonInfo' => 'Update Profile',
]);
return new HttpFoundation\Response($content);
diff --git a/src/Http/Action/Profile/ProcessAction.php b/src/Http/Action/Profile/ProcessAction.php
index 9ea095922..748b72df4 100755
--- a/src/Http/Action/Profile/ProcessAction.php
+++ b/src/Http/Action/Profile/ProcessAction.php
@@ -126,18 +126,19 @@ public function __invoke(HttpFoundation\Request $request): HttpFoundation\Respon
private function getFormData(HttpFoundation\Request $request): array
{
return [
- 'email' => $request->get('email'),
- 'user_id' => $request->get('id'),
- 'first_name' => $request->get('first_name'),
- 'last_name' => $request->get('last_name'),
- 'company' => $request->get('company'),
- 'twitter' => $request->get('twitter'),
- 'url' => $request->get('url'),
- 'airport' => $request->get('airport'),
- 'transportation' => (int) $request->get('transportation'),
- 'hotel' => (int) $request->get('hotel'),
- 'speaker_info' => $request->get('speaker_info') ?: null,
- 'speaker_bio' => $request->get('speaker_bio') ?: null,
+ 'email' => $request->get('email'),
+ 'user_id' => $request->get('id'),
+ 'first_name' => $request->get('first_name'),
+ 'last_name' => $request->get('last_name'),
+ 'company' => $request->get('company'),
+ 'twitter' => $request->get('twitter'),
+ 'joindin_username' => $request->get('joindin_username'),
+ 'url' => $request->get('url'),
+ 'airport' => $request->get('airport'),
+ 'transportation' => (int) $request->get('transportation'),
+ 'hotel' => (int) $request->get('hotel'),
+ 'speaker_info' => $request->get('speaker_info') ?: null,
+ 'speaker_bio' => $request->get('speaker_bio') ?: null,
];
}
diff --git a/src/Http/Form/SignupForm.php b/src/Http/Form/SignupForm.php
index 44acecfcd..bb605fb8a 100755
--- a/src/Http/Form/SignupForm.php
+++ b/src/Http/Form/SignupForm.php
@@ -34,6 +34,7 @@ class SignupForm extends Form
'hotel',
'speaker_photo',
'agree_coc',
+ 'joindin_username',
'url',
];
@@ -61,7 +62,7 @@ public function validateAll(string $action = 'create'): bool
$validSpeakerBio = $this->validateSpeakerBio();
}
- return $this->validateEmail() && $validPasswords && $this->validateFirstName() && $this->validateLastName() && $this->validateUrl() && $validSpeakerInfo && $validSpeakerBio && $this->validateSpeakerPhoto() && $agreeCoc;
+ return $this->validateEmail() && $validPasswords && $this->validateFirstName() && $this->validateLastName() && $this->validateUrl() && $validSpeakerInfo && $validSpeakerBio && $this->validateSpeakerPhoto() && $agreeCoc && $this->validateJoindInUsername();
}
public function validateSpeakerPhoto(): bool
@@ -201,15 +202,29 @@ public function validateLastName(): bool
return $validationResponse;
}
+ public function validateJoindInUsername(): bool
+ {
+ if (!isset($this->cleanData['joindin_username'])
+ || $this->cleanData['joindin_username'] == ''
+ || \preg_match('/^[a-zA-Z0-9\-_\.]{1,100}$/', $this->cleanData['joindin_username'])
+ ) {
+ return true;
+ }
+
+ $this->addErrorMessage('Please enter a valid joind.in username.');
+
+ return false;
+ }
+
public function validateUrl(): bool
{
- if (\preg_match('/^https:\/\/joind\.in\/user\/[a-zA-Z0-9\-_\.]{1,100}$/', $this->cleanData['url'])
+ if (\filter_var($this->cleanData['url'], FILTER_VALIDATE_URL) !== false
|| !isset($this->cleanData['url'])
|| $this->cleanData['url'] == ''
) {
return true;
}
- $this->addErrorMessage('You did not enter a valid joind.in URL');
+ $this->addErrorMessage('Please enter a valid URL.');
return false;
}
diff --git a/tests/Integration/Http/Action/Signup/ProcessActionTest.php b/tests/Integration/Http/Action/Signup/ProcessActionTest.php
index 2681db310..165110c38 100755
--- a/tests/Integration/Http/Action/Signup/ProcessActionTest.php
+++ b/tests/Integration/Http/Action/Signup/ProcessActionTest.php
@@ -28,22 +28,23 @@ public function signUpWorksCorrectly()
$password = $faker->password;
$response = $this->post('/signup', [
- 'first_name' => $faker->firstName,
- 'last_name' => $faker->lastName,
- 'email' => $faker->email,
- 'company' => null,
- 'twitter' => null,
- 'url' => 'https://joind.in/user/abc123',
- 'password' => $password,
- 'password2' => $password,
- 'airport' => null,
- 'speaker_info' => null,
- 'speaker_bio' => null,
- 'transportation' => null,
- 'hotel' => null,
- 'buttonInfo' => 'Create my speaker profile',
- 'coc' => 1,
- 'privacy' => 1,
+ 'first_name' => $faker->firstName,
+ 'last_name' => $faker->lastName,
+ 'email' => $faker->email,
+ 'company' => null,
+ 'twitter' => null,
+ 'url' => 'https://example.com',
+ 'joindin_username' => $faker->userName,
+ 'password' => $password,
+ 'password2' => $password,
+ 'airport' => null,
+ 'speaker_info' => null,
+ 'speaker_bio' => null,
+ 'transportation' => null,
+ 'hotel' => null,
+ 'buttonInfo' => 'Create my speaker profile',
+ 'coc' => 1,
+ 'privacy' => 1,
]);
$this->assertResponseIsRedirect($response);
@@ -61,22 +62,23 @@ public function signUpWithoutJoindInWorks()
$password = $faker->password;
$response = $this->post('/signup', [
- 'first_name' => $faker->firstName,
- 'last_name' => $faker->lastName,
- 'email' => $faker->email,
- 'company' => null,
- 'twitter' => null,
- 'url' => null,
- 'password' => $password,
- 'password2' => $password,
- 'airport' => null,
- 'speaker_info' => null,
- 'speaker_bio' => null,
- 'transportation' => null,
- 'hotel' => null,
- 'buttonInfo' => 'Create my speaker profile',
- 'coc' => 1,
- 'privacy' => 1,
+ 'first_name' => $faker->firstName,
+ 'last_name' => $faker->lastName,
+ 'email' => $faker->email,
+ 'company' => null,
+ 'twitter' => null,
+ 'url' => null,
+ 'joindin_username' => null,
+ 'password' => $password,
+ 'password2' => $password,
+ 'airport' => null,
+ 'speaker_info' => null,
+ 'speaker_bio' => null,
+ 'transportation' => null,
+ 'hotel' => null,
+ 'buttonInfo' => 'Create my speaker profile',
+ 'coc' => 1,
+ 'privacy' => 1,
]);
$this->assertSessionHasFlashMessage("You've successfully created your account!", $this->session());
diff --git a/tests/Unit/Domain/Speaker/SpeakerProfileTest.php b/tests/Unit/Domain/Speaker/SpeakerProfileTest.php
index 8079624cd..4f675f16a 100644
--- a/tests/Unit/Domain/Speaker/SpeakerProfileTest.php
+++ b/tests/Unit/Domain/Speaker/SpeakerProfileTest.php
@@ -338,6 +338,96 @@ public function getTwitterUrlReturnsTwitterUrlWhenTwitterPropertyIsNeitherHidden
$this->assertSame($expected, $profile->getTwitterUrl());
}
+ /**
+ * @test
+ */
+ public function getJoindInUsernameThrowsNotAllowedExceptionIfPropertyIsHidden(): void
+ {
+ $hiddenProperties = [
+ 'joindin_username',
+ ];
+
+ $speaker = $this->createUserMock();
+
+ $profile = new SpeakerProfile($speaker, $hiddenProperties);
+
+ $this->expectException(NotAllowedException::class);
+
+ $profile->getJoindInUsername();
+ }
+
+ /**
+ * @test
+ */
+ public function getJoindInUsernameReturnsJoindInUsernameIfPropertyIsNotHidden(): void
+ {
+ $joindinUsername = $this->faker()->userName;
+
+ $speaker = $this->createUserMock([
+ 'joindin_username' => $joindinUsername,
+ ]);
+
+ $profile = new SpeakerProfile($speaker);
+
+ $this->assertSame($joindinUsername, $profile->getJoindInUsername());
+ }
+
+ /**
+ * @test
+ * @dataProvider providerEmptyValue
+ *
+ * @param null|string $value
+ */
+ public function getJoindInUrlThrowsNotAllowedExceptionIfPropertyIsHidden(): void
+ {
+ $hiddenProperties = [
+ 'joindin_username',
+ ];
+
+ $speaker = $this->createUserMock();
+
+ $profile = new SpeakerProfile($speaker, $hiddenProperties);
+
+ $this->expectException(NotAllowedException::class);
+
+ $profile->getJoindInUrl();
+ }
+
+ /**
+ * @test
+ * @dataProvider providerEmptyValue
+ *
+ * @param null|string $value
+ */
+ public function getJoindInUrlReturnsEmptyStringWhenJoindInUsernameIsNotHiddenButEmpty($value): void
+ {
+ $speaker = $this->createUserMock([
+ 'joindin_username' => $value,
+ ]);
+
+ $profile = new SpeakerProfile($speaker);
+
+ $this->assertSame('', $profile->getJoindInUrl());
+ }
+
+ /**
+ * @test
+ */
+ public function getJoindInUrlReturnsJoindInUrlIfPropertyIsNotHidden(): void
+ {
+ $joindinUsername = $this->faker()->userName;
+
+ $speaker = $this->createUserMock([
+ 'joindin_username' => $joindinUsername,
+ ]);
+
+ $expectedUrl = 'https://joind.in/user/' . $joindinUsername;
+
+ $profile = new SpeakerProfile($speaker);
+
+ $this->assertSame($expectedUrl, $profile->getJoindInUrl());
+ }
+
/**
* @test
*/
diff --git a/tests/Unit/Http/Form/SignupFormTest.php b/tests/Unit/Http/Form/SignupFormTest.php
index bea1b47c3..c6ea72c60 100755
--- a/tests/Unit/Http/Form/SignupFormTest.php
+++ b/tests/Unit/Http/Form/SignupFormTest.php
@@ -286,8 +286,45 @@ public function lastNameProvider(): array
}
/**
- * Test that the Joind.in URL is being validated correctly
+ * @test
*
+ * @param string $joindin_username
+ * @param bool $expectedResponse
+ * @dataProvider joindInUsernameProvider
+ */
+ public function joindInUsernameIsValidatedCorrectly($joindin_username, $expectedResponse)
+ {
+ $data['joindin_username'] = $joindin_username;
+
+ $form = new \OpenCFP\Http\Form\SignupForm($data, $this->purifier);
+ $form->sanitize();
+
+ $this->assertSame(
+ $expectedResponse,
+ $form->validateJoindInUsername(),
+ 'Did not validate joind.in username as expected'
+ );
+ }
+
+ public function joindInUsernameProvider(): array
+ {
+ return [
+ [null, true],
+ [false, true],
+ ['', true],
+ ['abc123', true],
+ ['do re mi', false],
+ ['_FirstLast', true],
+ ['first-last', true],
+ ['first@last', false],
+ ['first#last', false],
+ ['first.last', true],
+ [\str_repeat('X', 100), true],
+ [\str_repeat('X', 101), false],
+ ];
+ }
+
+ /**
* @test
*
* @param string $url
@@ -307,34 +344,19 @@ public function urlIsValidatedCorrectly($url, $expectedResponse)
);
}
- /**
- * Data provider for urlIsValidatedCorrectly
- *
- * @return array
- */
public function urlProvider(): array
{
- $validBaseUrl = 'https://joind.in/user/';
- $longUrl = '';
-
- for ($x = 1; $x <= 256; ++$x) {
- $longUrl .= 'X';
- }
-
return [
- [$validBaseUrl . 'abc123', true],
- [$validBaseUrl, false],
[null, true],
[false, true],
['', true],
- ['http://example.net', false],
- ['http://joind.in/user/abc123', false],
- [$validBaseUrl . 'do re mi', false],
- [$validBaseUrl . '_FirstLast', true],
- [$validBaseUrl . 'first-last', true],
- [$validBaseUrl . 'firstname.last', true],
- [$validBaseUrl . '._name_.', true],
- [$validBaseUrl . $longUrl, false],
+ ['http://example.net', true],
+ ['example', false],
+ ['http://example.com/do re mi', false],
+ ['http://example.com/_FirstLast', true],
+ ['http://example.com/first-last', true],
+ ['https://', false],
+ ['$19.95 plus shipping and handling', false],
];
}
diff --git a/web/assets/css/app.css b/web/assets/css/app.css
index 3a0a93d96..a23690888 100644
--- a/web/assets/css/app.css
+++ b/web/assets/css/app.css
@@ -1,5 +1,6 @@
-@import url(//fonts.googleapis.com/css?family=Roboto|Zilla+Slab);/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}header,nav{display:block}h1{font-size:2em;margin:.67em 0}main{display:block}a{background-color:transparent;-webkit-text-decoration-skip:objects}b,strong{font-weight:inherit;font-weight:bolder}code{font-family:monospace,monospace;font-size:1em}img{border-style:none}svg:not(:root){overflow:hidden}button,input,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}textarea{overflow:auto}[type=checkbox]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}[hidden]{display:none}html{font-family:sans-serif}h1,h2,h3,h4,p{margin:0}button{background:transparent;padding:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}ul{margin:0}[tabindex="-1"]:focus{outline:none!important}*,:after,:before{border:0 solid #dae4e9}[type=button],[type=reset],[type=submit],button{border-radius:0}img{max-width:100%}button,input,select,textarea{font-family:inherit}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:inherit;opacity:.5}input:-ms-input-placeholder,input::-ms-input-placeholder,textarea:-ms-input-placeholder,textarea::-ms-input-placeholder{color:inherit;opacity:.5}input::placeholder,textarea::placeholder{color:inherit;opacity:.5}.btn{padding:.75rem 1.5rem}.btn{border-radius:.25rem}.btn-brand{color:#fff;border-width:2px}.btn-brand{background-color:#e04c3e;border-color:#e04c3e}.btn-white{background-color:#fff;color:#e04c3e}.btn-white,.btn-white-o{border-width:2px;border-color:#fff}.btn-white-o{color:#fff}.btn-white-o:hover{background-color:#fff;color:#e04c3e}.btn-brand-o{border-width:2px;border-color:#e04c3e;color:#e04c3e}.btn-brand-o:hover{background-color:#e04c3e;color:#fff}.container{width:100%}@media (min-width:576px){.container{max-width:576px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:992px){.container{max-width:992px}}@media (min-width:1200px){.container{max-width:1200px}}.list-reset{list-style:none;padding:0}.bg-grey-dark{background-color:#70818a}.bg-grey-lighter{background-color:#f3f7f9}.bg-red-lightest{background-color:#fcebea}.bg-orange-lightest{background-color:#fff5eb}.bg-green-lightest{background-color:#e3fcec}.bg-teal-lightest{background-color:#e8fffe}.bg-blue-lightest{background-color:#eff8ff}.bg-indigo-lightest{background-color:#e6e8ff}.bg-brand{background-color:#e04c3e}.bg-cover{background-size:cover}.border-grey{border-color:#9babb4}.border-grey-light{border-color:#dae4e9}.border-white{border-color:#fff}.border-orange{border-color:#f6993f}.border-green-dark{border-color:#1f9d55}.border-teal{border-color:#4dc0b5}.border-indigo{border-color:#6574cd}.border-brand{border-color:#e04c3e}.rounded{border-radius:.25rem}.rounded-sm{border-radius:.125rem}.rounded-full{border-radius:9999px}.border-4{border-width:4px}.border{border-width:1px}.border-t-4{border-top-width:4px}.border-b-4{border-bottom-width:4px}.border-l-4{border-left-width:4px}.border-t-8{border-top-width:8px}.border-r{border-right-width:1px}.border-b{border-bottom-width:1px}.cursor-pointer{cursor:pointer}.block{display:block}.inline-block{display:inline-block}.table{display:table}.hidden{display:none}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}.items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.justify-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flex-1{-webkit-box-flex:1;-ms-flex:1;flex:1}.font-serif{font-family:Zilla Slab,serif}.font-thin{font-weight:200}.font-normal{font-weight:400}.font-bold{font-weight:700}.h-10{height:2.5rem}.h-16{height:4rem}.h-full{height:100%}.leading-loose{line-height:2}.m-0{margin:0}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mr-1{margin-right:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.mt-3{margin-top:.75rem}.mr-3{margin-right:.75rem}.mb-3{margin-bottom:.75rem}.ml-3{margin-left:.75rem}.mt-4{margin-top:1rem}.mr-4{margin-right:1rem}.mb-4{margin-bottom:1rem}.ml-4{margin-left:1rem}.mt-6{margin-top:1.5rem}.mr-6{margin-right:1.5rem}.mb-6{margin-bottom:1.5rem}.mt-8{margin-top:2rem}.mr-8{margin-right:2rem}.mb-8{margin-bottom:2rem}.ml-8{margin-left:2rem}.mr-16{margin-right:4rem}.-mt-2{margin-top:-.5rem}.-mt-4{margin-top:-1rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-8{padding:2rem}.p-24{padding:6rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.fixed{position:fixed}.absolute{position:absolute}.shadow{-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.1);box-shadow:0 2px 4px 0 rgba(0,0,0,.1)}.text-center{text-align:center}.text-right{text-align:right}.text-soft{color:#9babb4}.text-dark{color:#364349}.text-brand{color:#e04c3e}.text-grey-dark{color:#70818a}.text-grey{color:#9babb4}.text-white{color:#fff}.text-red-dark{color:#cc1f1a}.text-orange-darkest{color:#542605}.text-orange-dark{color:#de751f}.text-green-dark{color:#1f9d55}.text-teal-darkest{color:#0d3331}.text-indigo-darkest{color:#191e38}.text-indigo-dark{color:#5661b3}.hover\:text-dark:hover{color:#364349}.hover\:text-brand:hover{color:#e04c3e}.text-xs{font-size:.75rem}.text-sm{font-size:.875rem}.text-base{font-size:1rem}.text-lg{font-size:1.125rem}.text-5xl{font-size:3rem}.uppercase{text-transform:uppercase}.underline{text-decoration:underline}.hover\:underline:hover{text-decoration:underline}.whitespace-no-wrap{white-space:nowrap}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.w-10{width:2.5rem}.w-16{width:4rem}.w-32{width:8rem}.w-1\/2{width:50%}.w-2\/3{width:66.66667%}.w-1\/4{width:25%}.w-2\/5{width:40%}.w-3\/5{width:60%}.w-1\/6{width:16.66667%}.w-5\/6{width:83.33333%}.w-full{width:100%}
+@import url(//fonts.googleapis.com/css?family=Roboto|Zilla+Slab);/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}header,nav{display:block}h1{font-size:2em;margin:.67em 0}main{display:block}a{background-color:transparent;-webkit-text-decoration-skip:objects}b,strong{font-weight:inherit;font-weight:bolder}code{font-family:monospace,monospace;font-size:1em}img{border-style:none}svg:not(:root){overflow:hidden}button,input,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}textarea{overflow:auto}[type=checkbox]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}[hidden]{display:none}html{font-family:sans-serif}h1,h2,h3,h4,p{margin:0}button{background:transparent;padding:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}ul{margin:0}[tabindex="-1"]:focus{outline:none!important}*,:after,:before{border:0 solid #dae4e9}img{border-style:solid}[type=button],[type=reset],[type=submit],button{border-radius:0}img{max-width:100%}button,input,select,textarea{font-family:inherit}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:inherit;opacity:.5}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:inherit;opacity:.5}input::placeholder,textarea::placeholder{color:inherit;opacity:.5}.btn{padding:.75rem 1.5rem}.btn{border-radius:.25rem}.btn-brand{color:#fff;border-width:2px}.btn-brand{background-color:#e04c3e;border-color:#e04c3e}.btn-white{background-color:#fff;color:#e04c3e}.btn-white,.btn-white-o{border-width:2px;border-color:#fff}.btn-white-o{color:#fff}.btn-white-o:hover{background-color:#fff;color:#e04c3e}.btn-brand-o{border-width:2px;border-color:#e04c3e;color:#e04c3e}.btn-brand-o:hover{background-color:#e04c3e;color:#fff}.container{width:100%}@media (min-width:576px){.container{max-width:576px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:992px){.container{max-width:992px}}@media (min-width:1200px){.container{max-width:1200px}}.list-reset{list-style:none;padding:0}.bg-grey-dark{background-color:#70818a}.bg-grey-lighter{background-color:#f3f7f9}.bg-red-lightest{background-color:#fcebea}.bg-orange-lightest{background-color:#fff5eb}.bg-green-lightest{background-color:#e3fcec}.bg-teal-lightest{background-color:#e8fffe}.bg-blue-lightest{background-color:#eff8ff}.bg-indigo-lightest{background-color:#e6e8ff}.bg-brand{background-color:#e04c3e}.bg-cover{background-size:cover}.border-grey{border-color:#9babb4}.border-grey-light{border-color:#dae4e9}.border-white{border-color:#fff}.border-orange{border-color:#f6993f}.border-green-dark{border-color:#1f9d55}.border-teal{border-color:#4dc0b5}.border-indigo{border-color:#6574cd}.border-brand{border-color:#e04c3e}.rounded{border-radius:.25rem}.rounded-sm{border-radius:.125rem}.rounded-full{border-radius:9999px}.border-4{border-width:4px}.border{border-width:1px}.border-t-4{border-top-width:4px}.border-b-4{border-bottom-width:4px}.border-l-4{border-left-width:4px}.border-t-8{border-top-width:8px}.border-r{border-right-width:1px}.border-b{border-bottom-width:1px}.cursor-pointer{cursor:pointer}.block{display:block}.inline-block{display:inline-block}.table{display:table}.hidden{display:none}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}.items-center{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.justify-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.flex-1{-webkit-box-flex:1;-ms-flex:1;flex:1}.font-serif{font-family:Zilla Slab,serif}.font-thin{font-weight:200}.font-normal{font-weight:400}.font-bold{font-weight:700}.h-10{height:2.5rem}.h-16{height:4rem}.h-full{height:100%}.leading-loose{line-height:2}.m-0{margin:0}.my-1{margin-top:.25rem;margin-bottom:.25rem}.my-4{margin-top:1rem;margin-bottom:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mr-1{margin-right:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.mt-3{margin-top:.75rem}.mr-3{margin-right:.75rem}.mb-3{margin-bottom:.75rem}.ml-3{margin-left:.75rem}.mt-4{margin-top:1rem}.mr-4{margin-right:1rem}.mb-4{margin-bottom:1rem}.ml-4{margin-left:1rem}.mt-6{margin-top:1.5rem}.mr-6{margin-right:1.5rem}.mb-6{margin-bottom:1.5rem}.mt-8{margin-top:2rem}.mr-8{margin-right:2rem}.mb-8{margin-bottom:2rem}.ml-8{margin-left:2rem}.mr-16{margin-right:4rem}.-mt-2{margin-top:-.5rem}.-mt-4{margin-top:-1rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-8{padding:2rem}.p-24{padding:6rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.fixed{position:fixed}.absolute{position:absolute}.shadow{-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.1);box-shadow:0 2px 4px 0 rgba(0,0,0,.1)}.text-center{text-align:center}.text-right{text-align:right}.text-soft{color:#9babb4}.text-dark{color:#364349}.text-brand{color:#e04c3e}.text-grey-dark{color:#70818a}.text-grey{color:#9babb4}.text-white{color:#fff}.text-red-dark{color:#cc1f1a}.text-orange-darkest{color:#542605}.text-orange-dark{color:#de751f}.text-green-dark{color:#1f9d55}.text-teal-darkest{color:#0d3331}.text-indigo-darkest{color:#191e38}.text-indigo-dark{color:#5661b3}.hover\:text-dark:hover{color:#364349}.hover\:text-brand:hover{color:#e04c3e}.text-xs{font-size:.75rem}.text-sm{font-size:.875rem}.text-base{font-size:1rem}.text-lg{font-size:1.125rem}.text-5xl{font-size:3rem}.uppercase{text-transform:uppercase}.underline{text-decoration:underline}.hover\:underline:hover{text-decoration:underline}.whitespace-no-wrap{white-space:nowrap}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.w-10{width:2.5rem}.w-16{width:4rem}.w-32{width:8rem}.w-1\/2{width:50%}.w-2\/3{width:66.66667%}.w-1\/4{width:25%}.w-2\/5{width:40%}.w-3\/5{width:60%}.w-1\/6{width:16.66667%}.w-5\/6{width:83.33333%}.w-full{width:100%}
+
/*!
* Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
- */@font-face{font-family:FontAwesome;src:url("/assets/fonts/fontawesome-webfont.eot?v=4.7.0");src:url("/assets/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0") format("embedded-opentype"),url("/assets/fonts/fontawesome-webfont.woff2?v=4.7.0") format("woff2"),url("/assets/fonts/fontawesome-webfont.woff?v=4.7.0") format("woff"),url("/assets/fonts/fontawesome-webfont.ttf?v=4.7.0") format("truetype"),url("/assets/fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular") format("svg");font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-5x{font-size:5em}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-star:before{content:"\F005"}.fa-clock-o:before{content:"\F017"}.fa-plane:before{content:"\F072"}.fa-comments:before{content:"\F086"}.fa-trophy:before{content:"\F091"}.fa-upload:before{content:"\F093"}.fa-twitter:before{content:"\F099"}.fa-credit-card:before{content:"\F09D"}.fa-bullhorn:before{content:"\F0A1"}.fa-envelope:before{content:"\F0E0"}.fa-file-text-o:before{content:"\F0F6"}.fa-check-square:before{content:"\F14A"}.fa-thumbs-up:before{content:"\F164"}.fa-thumbs-down:before{content:"\F165"}.fa-graduation-cap:before{content:"\F19D"}.fa-building:before{content:"\F1AD"}.fa-television:before{content:"\F26C"}.fa-commenting-o:before{content:"\F27B"}.fa-user-circle:before{content:"\F2BD"}html{overflow-y:scroll;-webkit-box-sizing:border-box;box-sizing:border-box}*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue;font-size:1rem;line-height:1.5;color:#364349;margin:0;padding-bottom:2rem}[role=button],button{cursor:pointer}img{vertical-align:middle}button,input{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue}a{color:#364349;text-decoration:none}h1,h2,h3,p{margin-bottom:.5rem}ul{list-style:disc;margin-top:.75rem;margin-bottom:.75rem;padding-left:2rem}ul ul{margin:0}ul>li{margin-bottom:.25rem}label{color:#70818a;font-size:.875rem}input[type=email],input[type=password],input[type=text]{padding:.5rem .75rem;width:100%}input[type=email],input[type=password],input[type=text],select{margin-bottom:.5rem;display:block;border-width:1px;border-color:#9babb4;border-radius:.125rem}select{padding:.5rem}textarea{padding:.75rem .5rem;width:100%;resize:vertical;border-radius:.125rem;border-color:#9babb4}div:empty{display:none}.pagerfanta a,.pagerfanta span{padding-left:.5rem;padding-right:.5rem}.opencfp-login,.opencfp-signup{color:#fff;width:16.66667%;min-width:20rem}
\ No newline at end of file
+ */@font-face{font-family:FontAwesome;src:url("/assets/fonts/fontawesome-webfont.eot?v=4.7.0");src:url("/assets/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0") format("embedded-opentype"),url("/assets/fonts/fontawesome-webfont.woff2?v=4.7.0") format("woff2"),url("/assets/fonts/fontawesome-webfont.woff?v=4.7.0") format("woff"),url("/assets/fonts/fontawesome-webfont.ttf?v=4.7.0") format("truetype"),url("/assets/fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular") format("svg");font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-star:before{content:"\F005"}.fa-clock-o:before{content:"\F017"}.fa-plane:before{content:"\F072"}.fa-comments:before{content:"\F086"}.fa-trophy:before{content:"\F091"}.fa-upload:before{content:"\F093"}.fa-twitter:before{content:"\F099"}.fa-credit-card:before{content:"\F09D"}.fa-bullhorn:before{content:"\F0A1"}.fa-envelope:before{content:"\F0E0"}.fa-file-text-o:before{content:"\F0F6"}.fa-desktop:before{content:"\F108"}.fa-check-square:before{content:"\F14A"}.fa-thumbs-up:before{content:"\F164"}.fa-thumbs-down:before{content:"\F165"}.fa-graduation-cap:before{content:"\F19D"}.fa-building:before{content:"\F1AD"}.fa-television:before{content:"\F26C"}.fa-commenting-o:before{content:"\F27B"}.fa-user-circle:before{content:"\F2BD"}html{overflow-y:scroll;-webkit-box-sizing:border-box;box-sizing:border-box}*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue;font-size:1rem;line-height:1.5;color:#364349;margin:0;padding-bottom:2rem}[role=button],button{cursor:pointer}img{vertical-align:middle}button,input{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue}a{color:#364349;text-decoration:none}h1,h2,h3,p{margin-bottom:.5rem}ul{list-style:disc;margin-top:.75rem;margin-bottom:.75rem;padding-left:2rem}ul ul{margin:0}ul>li{margin-bottom:.25rem}label{color:#70818a;font-size:.875rem}input[type=email],input[type=password],input[type=text]{padding:.5rem .75rem;width:100%}input[type=email],input[type=password],input[type=text],select{margin-bottom:.5rem;display:block;border-width:1px;border-color:#9babb4;border-radius:.125rem}select{padding:.5rem}textarea{padding:.75rem .5rem;width:100%;resize:vertical;border-radius:.125rem;border-color:#9babb4}div:empty{display:none}.pagerfanta a,.pagerfanta span{padding-left:.5rem;padding-right:.5rem}.opencfp-login,.opencfp-signup{color:#fff;width:16.66667%;min-width:20rem}
\ No newline at end of file